This commit is contained in:
parent
a13e87195f
commit
ee064c2c55
|
@ -4,3 +4,4 @@ export * from './user'
|
|||
export * from './prompt'
|
||||
export * from './settings'
|
||||
export * from './auth'
|
||||
export * from './text'
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { ss } from '@/utils/storage'
|
||||
|
||||
const LOCAL_NAME = 'textStorage'
|
||||
|
||||
export interface TextInfo {
|
||||
pormat: string
|
||||
}
|
||||
|
||||
export interface TextState {
|
||||
textInfo: TextInfo
|
||||
}
|
||||
|
||||
export function defaultSetting(): TextState {
|
||||
return {
|
||||
pormat: '',
|
||||
}
|
||||
}
|
||||
|
||||
export function getLocalState(): TextState {
|
||||
const localSetting: TextState | undefined = ss.get(LOCAL_NAME)
|
||||
return { ...defaultSetting(), ...localSetting }
|
||||
}
|
||||
|
||||
export function setLocalState(setting: TextState): void {
|
||||
ss.set(LOCAL_NAME, setting)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { TextInfo, TextState } from './helper'
|
||||
import { defaultSetting, getLocalState, setLocalState } from './helper'
|
||||
|
||||
export const useTextStore = defineStore('text-store', {
|
||||
state: (): TextState => getLocalState(),
|
||||
actions: {
|
||||
updateTextInfo(textInfo: Partial<TextInfo>) {
|
||||
this.textInfo = { ...this.textInfo, ...textInfo }
|
||||
this.recordState()
|
||||
},
|
||||
|
||||
resetTextInfo() {
|
||||
this.textInfo = { ...defaultSetting().textInfo }
|
||||
this.recordState()
|
||||
},
|
||||
|
||||
recordState() {
|
||||
setLocalState(this.$state)
|
||||
},
|
||||
},
|
||||
})
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable no-undef */
|
||||
import CryptoJS from 'crypto-js'
|
||||
const APPID = '2eda6c2e'
|
||||
const API_SECRET = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2'
|
||||
const API_KEY = '12ec1f9d113932575fc4b114a2f60ffd'
|
||||
import CryptoJS from 'crypto-js'
|
||||
// import Worker from './transcode.worker.js'
|
||||
const transWorker = new Worker(new URL('./transcode.worker.js', import.meta.url))
|
||||
let startTime = ''
|
||||
|
@ -42,7 +42,7 @@ const IatRecorder = class {
|
|||
this.resultText = ''
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = ''
|
||||
transWorker.onmessage = function(event) {
|
||||
transWorker.onmessage = function (event) {
|
||||
// console.log("构造方法中",self.audioData)
|
||||
self.audioData.push(...event.data)
|
||||
}
|
||||
|
@ -50,73 +50,80 @@ const IatRecorder = class {
|
|||
|
||||
// 修改录音听写状态
|
||||
setStatus(status) {
|
||||
this.onWillStatusChange &&
|
||||
this.status !== status &&
|
||||
this.onWillStatusChange(this.status, status)
|
||||
this.onWillStatusChange
|
||||
&& this.status !== status
|
||||
&& this.onWillStatusChange(this.status, status)
|
||||
this.status = status
|
||||
}
|
||||
|
||||
setResultText({ resultText, resultTextTemp } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
|
||||
resultText !== undefined && (this.resultText = resultText)
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
|
||||
}
|
||||
|
||||
// 修改听写参数
|
||||
setParams({ language, accent } = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
}
|
||||
|
||||
// 连接websocket
|
||||
connectWebSocket() {
|
||||
return getWebSocketUrl().then(url => {
|
||||
return getWebSocketUrl().then((url) => {
|
||||
let iatWS
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url)
|
||||
} else if ('MozWebSocket' in window) {
|
||||
}
|
||||
else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
alert('浏览器不支持WebSocket')
|
||||
return
|
||||
}
|
||||
this.webSocket = iatWS
|
||||
this.setStatus('init')
|
||||
iatWS.onopen = e => {
|
||||
iatWS.onopen = (e) => {
|
||||
this.setStatus('ing')
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend()
|
||||
}, 500)
|
||||
}
|
||||
iatWS.onmessage = e => {
|
||||
iatWS.onmessage = (e) => {
|
||||
this.result(e.data)
|
||||
}
|
||||
iatWS.onerror = e => {
|
||||
iatWS.onerror = (e) => {
|
||||
this.recorderStop()
|
||||
}
|
||||
iatWS.onclose = e => {
|
||||
iatWS.onclose = (e) => {
|
||||
endTime = Date.parse(new Date())
|
||||
// console.log('持续时间', endTime - startTime)
|
||||
this.recorderStop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
navigator.getUserMedia =
|
||||
navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia
|
||||
navigator.getUserMedia
|
||||
= navigator.getUserMedia
|
||||
|| navigator.webkitGetUserMedia
|
||||
|| navigator.mozGetUserMedia
|
||||
|| navigator.msGetUserMedia
|
||||
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = new (window.AudioContext ||
|
||||
window.webkitAudioContext)()
|
||||
this.audioContext = new (window.AudioContext
|
||||
|| window.webkitAudioContext)()
|
||||
this.audioContext.resume()
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口')
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口')
|
||||
return
|
||||
|
@ -130,34 +137,37 @@ const IatRecorder = class {
|
|||
audio: true,
|
||||
video: false,
|
||||
})
|
||||
.then(stream => {
|
||||
.then((stream) => {
|
||||
getMediaSuccess(stream)
|
||||
})
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
getMediaFail(e)
|
||||
})
|
||||
} else if (navigator.getUserMedia) {
|
||||
}
|
||||
else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia(
|
||||
{
|
||||
audio: true,
|
||||
video: false,
|
||||
},
|
||||
stream => {
|
||||
(stream) => {
|
||||
getMediaSuccess(stream)
|
||||
},
|
||||
function(e) {
|
||||
(e) => {
|
||||
getMediaFail1(e)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (
|
||||
navigator.userAgent.toLowerCase().match(/chrome/) &&
|
||||
location.origin.indexOf('https://') < 0
|
||||
navigator.userAgent.toLowerCase().match(/chrome/)
|
||||
&& !location.origin.includes('https://')
|
||||
) {
|
||||
alert(
|
||||
'chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限',
|
||||
)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
alert('无法获取浏览器录音功能,请升级软件版本')
|
||||
}
|
||||
this.voiceDialog = 0
|
||||
|
@ -165,12 +175,12 @@ const IatRecorder = class {
|
|||
return
|
||||
}
|
||||
// 获取浏览器录音权限成功的回调
|
||||
const getMediaSuccess = stream => {
|
||||
const getMediaSuccess = (stream) => {
|
||||
this.voiceDialog = 1
|
||||
// console.log('getMediaSuccess')
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
|
||||
this.scriptProcessor.onaudioprocess = e => {
|
||||
this.scriptProcessor.onaudioprocess = (e) => {
|
||||
// 去处理音频数据
|
||||
if (this.status === 'ing') {
|
||||
// console.log(transWorker)
|
||||
|
@ -186,37 +196,38 @@ const IatRecorder = class {
|
|||
this.connectWebSocket()
|
||||
}
|
||||
|
||||
const getMediaFail = e => {
|
||||
const getMediaFail = (e) => {
|
||||
alert(e)
|
||||
this.voiceDialog = 0
|
||||
// console.log(e)
|
||||
this.audioContext && this.audioContext.close()
|
||||
this.audioContext = undefined
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
if (this.webSocket && this.webSocket.readyState === 1)
|
||||
this.webSocket.close()
|
||||
}
|
||||
}
|
||||
const getMediaFail1 = e => {
|
||||
const getMediaFail1 = (e) => {
|
||||
alert('请求麦克风失败,请添加权限!')
|
||||
this.voiceDialog = 0
|
||||
// console.log(e)
|
||||
this.audioContext && this.audioContext.close()
|
||||
this.audioContext = undefined
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
if (this.webSocket && this.webSocket.readyState === 1)
|
||||
this.webSocket.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recorderStart() {
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit()
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.audioContext.resume()
|
||||
this.connectWebSocket()
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停录音
|
||||
recorderStop() {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
|
@ -224,11 +235,12 @@ const IatRecorder = class {
|
|||
!(
|
||||
/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen)
|
||||
)
|
||||
) {
|
||||
)
|
||||
this.audioContext && this.audioContext.suspend()
|
||||
}
|
||||
|
||||
this.setStatus('end')
|
||||
}
|
||||
|
||||
// 处理音频数据
|
||||
// transAudioData(audioData) {
|
||||
// audioData = transAudioData.transaction(audioData)
|
||||
|
@ -239,25 +251,26 @@ const IatRecorder = class {
|
|||
let binary = ''
|
||||
const bytes = new Uint8Array(buffer)
|
||||
const len = bytes.byteLength
|
||||
for (let i = 0; i < len; i++) {
|
||||
for (let i = 0; i < len; i++)
|
||||
binary += String.fromCharCode(bytes[i])
|
||||
}
|
||||
|
||||
return window.btoa(binary)
|
||||
}
|
||||
|
||||
// 向webSocket发送数据
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
if (this.webSocket.readyState !== 1)
|
||||
return
|
||||
}
|
||||
|
||||
let audioData = this.audioData.splice(0, 1280)
|
||||
const params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
language: this.language, // 小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
accent: this.accent, // 中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
vad_eos: 5000,
|
||||
dwa: 'wpgs',
|
||||
},
|
||||
|
@ -282,7 +295,7 @@ const IatRecorder = class {
|
|||
return
|
||||
}
|
||||
if (this.audioData.length === 0) {
|
||||
//点击暂停录音
|
||||
// 点击暂停录音
|
||||
// console.log('自动关闭', this.status)
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
|
@ -314,6 +327,7 @@ const IatRecorder = class {
|
|||
)
|
||||
}, 40)
|
||||
}
|
||||
|
||||
result(resultData) {
|
||||
// 识别结束
|
||||
const jsonData = JSON.parse(resultData)
|
||||
|
@ -322,10 +336,10 @@ const IatRecorder = class {
|
|||
let str = ''
|
||||
const resultStr = ''
|
||||
const ws = data.ws
|
||||
for (let i = 0; i < ws.length; i++) {
|
||||
for (let i = 0; i < ws.length; i++)
|
||||
str = str + ws[i].cw[0].w
|
||||
}
|
||||
// console.log('识别的结果为:', str)
|
||||
|
||||
// console.log('', str)
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
|
@ -339,28 +353,29 @@ const IatRecorder = class {
|
|||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str,
|
||||
})
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2)
|
||||
this.webSocket.close()
|
||||
}
|
||||
if (jsonData.code !== 0) {
|
||||
|
||||
if (jsonData.code !== 0)
|
||||
this.webSocket.close()
|
||||
// console.log(`${jsonData.code}:${jsonData.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.recorderStart()
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' })
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.recorderStop()
|
||||
}
|
||||
}
|
||||
|
||||
export default IatRecorder
|
||||
|
||||
|
|
|
@ -1,330 +1,350 @@
|
|||
import CryptoJS from 'crypto-js'
|
||||
const APPID = '2eda6c2e'
|
||||
const API_SECRET = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2'
|
||||
const API_KEY = '12ec1f9d113932575fc4b114a2f60ffd'
|
||||
import CryptoJS from 'crypto-js'
|
||||
// import Worker from './transcode.worker.js'
|
||||
// const transWorker = new Worker()
|
||||
const transWorker = new Worker(new URL('./transcode.worker.js', import.meta.url))
|
||||
console.log(transWorker)
|
||||
var startTime = ""
|
||||
var endTime = ""
|
||||
let startTime = ''
|
||||
let endTime = ''
|
||||
|
||||
function getWebSocketUrl(){
|
||||
function getWebSocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求地址根据语种不同变化
|
||||
var url = 'wss://iat-api.xfyun.cn/v2/iat'
|
||||
var host = 'iat-api.xfyun.cn'
|
||||
var apiKey = API_KEY
|
||||
var apiSecret = API_SECRET
|
||||
var date = new Date().toGMTString()
|
||||
var algorithm = 'hmac-sha256'
|
||||
var headers = 'host date request-line'
|
||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
|
||||
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
|
||||
var signature = CryptoJS.enc.Base64.stringify(signatureSha)
|
||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
|
||||
var authorization = btoa(authorizationOrigin)
|
||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
|
||||
resolve(url)
|
||||
})
|
||||
// 请求地址根据语种不同变化
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat'
|
||||
const host = 'iat-api.xfyun.cn'
|
||||
const apiKey = API_KEY
|
||||
const apiSecret = API_SECRET
|
||||
const date = new Date().toGMTString()
|
||||
const algorithm = 'hmac-sha256'
|
||||
const headers = 'host date request-line'
|
||||
const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
|
||||
const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
|
||||
const signature = CryptoJS.enc.Base64.stringify(signatureSha)
|
||||
const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
|
||||
const authorization = btoa(authorizationOrigin)
|
||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
|
||||
console.log(date)
|
||||
resolve(url)
|
||||
})
|
||||
}
|
||||
const IatRecorder = class {
|
||||
constructor({ language, accent, appId } = {}) {
|
||||
let self = this
|
||||
this.status = 'null'
|
||||
this.language = language || 'zh_cn'
|
||||
this.accent = accent || 'mandarin'
|
||||
this.appId = appId || APPID
|
||||
// 记录音频数据
|
||||
this.audioData = []
|
||||
// 记录听写结果
|
||||
this.resultText = ''
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = ''
|
||||
transWorker.onmessage = function (event) {
|
||||
// console.log("构造方法中",self.audioData)
|
||||
self.audioData.push(...event.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 修改录音听写状态
|
||||
setStatus(status) {
|
||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status)
|
||||
this.status = status
|
||||
}
|
||||
setResultText({ resultText, resultTextTemp } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
|
||||
resultText !== undefined && (this.resultText = resultText)
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
|
||||
}
|
||||
// 修改听写参数
|
||||
setParams({ language, accent } = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
}
|
||||
// 连接websocket
|
||||
connectWebSocket() {
|
||||
return getWebSocketUrl().then(url => {
|
||||
let iatWS
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url)
|
||||
} else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url)
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket')
|
||||
return
|
||||
}
|
||||
this.webSocket = iatWS
|
||||
this.setStatus('init')
|
||||
iatWS.onopen = e => {
|
||||
this.setStatus('ing')
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend()
|
||||
}, 500)
|
||||
}
|
||||
iatWS.onmessage = e => {
|
||||
this.result(e.data)
|
||||
}
|
||||
iatWS.onerror = e => {
|
||||
this.recorderStop()
|
||||
}
|
||||
iatWS.onclose = e => {
|
||||
endTime = Date.parse(new Date())
|
||||
console.log("持续时间",endTime-startTime)
|
||||
this.recorderStop()
|
||||
}
|
||||
})
|
||||
}
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
navigator.getUserMedia =
|
||||
navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia
|
||||
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
||||
this.audioContext.resume()
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口')
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取浏览器录音权限
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
audio: true,
|
||||
video: false,
|
||||
})
|
||||
.then(stream => {
|
||||
getMediaSuccess(stream)
|
||||
})
|
||||
.catch(e => {
|
||||
getMediaFail(e)
|
||||
})
|
||||
} else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia(
|
||||
{
|
||||
audio: true,
|
||||
video: false,
|
||||
},
|
||||
stream => {
|
||||
getMediaSuccess(stream)
|
||||
},
|
||||
function(e) {
|
||||
getMediaFail(e)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
|
||||
alert('chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限')
|
||||
} else {
|
||||
alert('无法获取浏览器录音功能,请升级浏览器或使用chrome')
|
||||
}
|
||||
this.audioContext && this.audioContext.close()
|
||||
return
|
||||
}
|
||||
// 获取浏览器录音权限成功的回调
|
||||
let getMediaSuccess = stream => {
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
|
||||
this.scriptProcessor.onaudioprocess = e => {
|
||||
// 去处理音频数据
|
||||
if (this.status === 'ing') {
|
||||
transWorker.postMessage(e.inputBuffer.getChannelData(0))
|
||||
// this.audioData.push(e.inputBuffer.getChannelData(0))
|
||||
}
|
||||
}
|
||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
||||
this.mediaSource = this.audioContext.createMediaStreamSource(stream)
|
||||
// 连接
|
||||
this.mediaSource.connect(this.scriptProcessor)
|
||||
this.scriptProcessor.connect(this.audioContext.destination)
|
||||
this.connectWebSocket()
|
||||
}
|
||||
|
||||
let getMediaFail = (e) => {
|
||||
this.audioContext && this.audioContext.close()
|
||||
this.audioContext = undefined
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
this.webSocket.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
recorderStart() {
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit()
|
||||
} else {
|
||||
this.audioContext.resume()
|
||||
this.connectWebSocket()
|
||||
}
|
||||
}
|
||||
// 暂停录音
|
||||
recorderStop() {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))){
|
||||
this.audioContext && this.audioContext.suspend()
|
||||
}
|
||||
this.setStatus('end')
|
||||
}
|
||||
// 处理音频数据
|
||||
// transAudioData(audioData) {
|
||||
// audioData = transAudioData.transaction(audioData)
|
||||
// this.audioData.push(...audioData)
|
||||
// }
|
||||
// 对处理后的音频数据进行base64编码,
|
||||
toBase64(buffer) {
|
||||
var binary = ''
|
||||
var bytes = new Uint8Array(buffer)
|
||||
var len = bytes.byteLength
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i])
|
||||
}
|
||||
return window.btoa(binary)
|
||||
}
|
||||
// 向webSocket发送数据
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
return
|
||||
}
|
||||
let audioData = this.audioData.splice(0, 1280)
|
||||
var params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
},
|
||||
data: {
|
||||
status: 0,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData),
|
||||
},
|
||||
}
|
||||
console.log("参数language:",this.language)
|
||||
console.log("参数accent:",this.accent)
|
||||
this.webSocket.send(JSON.stringify(params))
|
||||
startTime = Date.parse(new Date())
|
||||
this.handlerInterval = setInterval(() => {
|
||||
// websocket未连接
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
console.log("websocket未连接")
|
||||
this.audioData = []
|
||||
clearInterval(this.handlerInterval)
|
||||
return
|
||||
}
|
||||
if (this.audioData.length === 0) {
|
||||
console.log("自动关闭",this.status)
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 2,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: '',
|
||||
},
|
||||
})
|
||||
)
|
||||
this.audioData = []
|
||||
clearInterval(this.handlerInterval)
|
||||
}
|
||||
return false
|
||||
}
|
||||
audioData = this.audioData.splice(0, 1280)
|
||||
// 中间帧
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 1,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData),
|
||||
},
|
||||
})
|
||||
)
|
||||
}, 40)
|
||||
}
|
||||
result(resultData) {
|
||||
// 识别结束
|
||||
let jsonData = JSON.parse(resultData)
|
||||
if (jsonData.data && jsonData.data.result) {
|
||||
let data = jsonData.data.result
|
||||
let str = ''
|
||||
let resultStr = ''
|
||||
let ws = data.ws
|
||||
for (let i = 0; i < ws.length; i++) {
|
||||
str = str + ws[i].cw[0].w
|
||||
}
|
||||
console.log("识别的结果为:",str)
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
if (data.pgs === 'apd') {
|
||||
// 将resultTextTemp同步给resultText
|
||||
this.setResultText({
|
||||
resultText: this.resultTextTemp,
|
||||
})
|
||||
}
|
||||
// 将结果存储在resultTextTemp中
|
||||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str,
|
||||
})
|
||||
} else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
this.webSocket.close()
|
||||
}
|
||||
if (jsonData.code !== 0) {
|
||||
this.webSocket.close()
|
||||
console.log(`${jsonData.code}:${jsonData.message}`)
|
||||
}
|
||||
}
|
||||
start() {
|
||||
this.recorderStart()
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' })
|
||||
}
|
||||
stop() {
|
||||
this.recorderStop()
|
||||
const IatRecorder = class {
|
||||
constructor({ language, accent, appId } = {}) {
|
||||
const self = this
|
||||
this.status = 'null'
|
||||
this.language = language || 'zh_cn'
|
||||
this.accent = accent || 'mandarin'
|
||||
this.appId = appId || APPID
|
||||
// 记录音频数据
|
||||
this.audioData = []
|
||||
// 记录听写结果
|
||||
this.resultText = ''
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = ''
|
||||
transWorker.onmessage = function (event) {
|
||||
// console.log("构造方法中",self.audioData)
|
||||
self.audioData.push(...event.data)
|
||||
}
|
||||
}
|
||||
|
||||
export default IatRecorder
|
||||
// 修改录音听写状态
|
||||
setStatus(status) {
|
||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status)
|
||||
this.status = status
|
||||
}
|
||||
|
||||
setResultText({ resultText, resultTextTemp } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
|
||||
resultText !== undefined && (this.resultText = resultText)
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
|
||||
}
|
||||
|
||||
// 修改听写参数
|
||||
setParams({ language, accent } = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
}
|
||||
|
||||
// 连接websocket
|
||||
connectWebSocket() {
|
||||
return getWebSocketUrl().then((url) => {
|
||||
let iatWS
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url)
|
||||
}
|
||||
else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url)
|
||||
}
|
||||
else {
|
||||
alert('浏览器不支持WebSocket')
|
||||
return
|
||||
}
|
||||
this.webSocket = iatWS
|
||||
this.setStatus('init')
|
||||
iatWS.onopen = (e) => {
|
||||
this.setStatus('ing')
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend()
|
||||
}, 500)
|
||||
}
|
||||
iatWS.onmessage = (e) => {
|
||||
this.result(e.data)
|
||||
}
|
||||
iatWS.onerror = (e) => {
|
||||
this.recorderStop()
|
||||
}
|
||||
iatWS.onclose = (e) => {
|
||||
console.log('关闭原因', e)
|
||||
endTime = Date.parse(new Date())
|
||||
console.log('持续时间', endTime - startTime)
|
||||
this.recorderStop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
navigator.getUserMedia
|
||||
= navigator.getUserMedia
|
||||
|| navigator.webkitGetUserMedia
|
||||
|| navigator.mozGetUserMedia
|
||||
|| navigator.msGetUserMedia
|
||||
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
||||
this.audioContext.resume()
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口')
|
||||
return
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取浏览器录音权限
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
audio: true,
|
||||
video: false,
|
||||
})
|
||||
.then((stream) => {
|
||||
getMediaSuccess(stream)
|
||||
})
|
||||
.catch((e) => {
|
||||
getMediaFail(e)
|
||||
})
|
||||
}
|
||||
else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia(
|
||||
{
|
||||
audio: true,
|
||||
video: false,
|
||||
},
|
||||
(stream) => {
|
||||
getMediaSuccess(stream)
|
||||
},
|
||||
(e) => {
|
||||
getMediaFail(e)
|
||||
},
|
||||
)
|
||||
}
|
||||
else {
|
||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && !location.origin.includes('https://'))
|
||||
alert('chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限')
|
||||
|
||||
else
|
||||
alert('无法获取浏览器录音功能,请升级浏览器或使用chrome')
|
||||
|
||||
this.audioContext && this.audioContext.close()
|
||||
return
|
||||
}
|
||||
// 获取浏览器录音权限成功的回调
|
||||
let getMediaSuccess = (stream) => {
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
|
||||
this.scriptProcessor.onaudioprocess = (e) => {
|
||||
// 去处理音频数据
|
||||
if (this.status === 'ing')
|
||||
transWorker.postMessage(e.inputBuffer.getChannelData(0))
|
||||
// this.audioData.push(e.inputBuffer.getChannelData(0))
|
||||
}
|
||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
||||
this.mediaSource = this.audioContext.createMediaStreamSource(stream)
|
||||
// 连接
|
||||
this.mediaSource.connect(this.scriptProcessor)
|
||||
this.scriptProcessor.connect(this.audioContext.destination)
|
||||
this.connectWebSocket()
|
||||
}
|
||||
|
||||
let getMediaFail = (e) => {
|
||||
this.audioContext && this.audioContext.close()
|
||||
this.audioContext = undefined
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1)
|
||||
this.webSocket.close()
|
||||
}
|
||||
}
|
||||
|
||||
recorderStart() {
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit()
|
||||
}
|
||||
else {
|
||||
this.audioContext.resume()
|
||||
this.connectWebSocket()
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停录音
|
||||
recorderStop() {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen)))
|
||||
this.audioContext && this.audioContext.suspend()
|
||||
|
||||
this.setStatus('end')
|
||||
}
|
||||
|
||||
// 处理音频数据
|
||||
transAudioData(audioData) {
|
||||
audioData = transAudioData.transaction(audioData)
|
||||
this.audioData.push(...audioData)
|
||||
}
|
||||
|
||||
// 对处理后的音频数据进行base64编码,
|
||||
toBase64(buffer) {
|
||||
let binary = ''
|
||||
const bytes = new Uint8Array(buffer)
|
||||
const len = bytes.byteLength
|
||||
for (let i = 0; i < len; i++)
|
||||
binary += String.fromCharCode(bytes[i])
|
||||
|
||||
return window.btoa(binary)
|
||||
}
|
||||
|
||||
// 向webSocket发送数据
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1)
|
||||
return
|
||||
|
||||
let audioData = this.audioData.splice(0, 1280)
|
||||
const params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, // 小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, // 中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
},
|
||||
data: {
|
||||
status: 0,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData),
|
||||
},
|
||||
}
|
||||
console.log('参数language:', this.language)
|
||||
console.log('参数accent:', this.accent)
|
||||
this.webSocket.send(JSON.stringify(params))
|
||||
startTime = Date.parse(new Date())
|
||||
this.handlerInterval = setInterval(() => {
|
||||
// websocket未连接
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
console.log('websocket未连接', this.webSocket)
|
||||
this.audioData = []
|
||||
clearInterval(this.handlerInterval)
|
||||
return
|
||||
}
|
||||
if (this.audioData.length === 0) {
|
||||
console.log('自动关闭', this.status)
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 2,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: '',
|
||||
},
|
||||
}),
|
||||
)
|
||||
this.audioData = []
|
||||
clearInterval(this.handlerInterval)
|
||||
}
|
||||
return false
|
||||
}
|
||||
audioData = this.audioData.splice(0, 1280)
|
||||
// 中间帧
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 1,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData),
|
||||
},
|
||||
}),
|
||||
)
|
||||
}, 40)
|
||||
}
|
||||
|
||||
result(resultData) {
|
||||
// 识别结束
|
||||
const jsonData = JSON.parse(resultData)
|
||||
if (jsonData.data && jsonData.data.result) {
|
||||
const data = jsonData.data.result
|
||||
let str = ''
|
||||
const resultStr = ''
|
||||
const ws = data.ws
|
||||
for (let i = 0; i < ws.length; i++)
|
||||
str = str + ws[i].cw[0].w
|
||||
|
||||
console.log('识别的结果为:', str)
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
if (data.pgs === 'apd') {
|
||||
// 将resultTextTemp同步给resultText
|
||||
this.setResultText({
|
||||
resultText: this.resultTextTemp,
|
||||
})
|
||||
}
|
||||
// 将结果存储在resultTextTemp中
|
||||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str,
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2)
|
||||
this.webSocket.close()
|
||||
|
||||
if (jsonData.code !== 0) {
|
||||
this.webSocket.close()
|
||||
console.log(`${jsonData.code}:${jsonData.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.recorderStart()
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' })
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.recorderStop()
|
||||
}
|
||||
}
|
||||
|
||||
export default IatRecorder
|
||||
|
|
|
@ -1,38 +1,78 @@
|
|||
self.onmessage = function(e){
|
||||
transAudioData.transcode(e.data)
|
||||
// self.onmessage = function(e){
|
||||
// transAudioData.transcode(e.data)
|
||||
// }
|
||||
// let transAudioData = {
|
||||
// transcode(audioData) {
|
||||
// let output = transAudioData.to16kHz(audioData)
|
||||
// output = transAudioData.to16BitPCM(output)
|
||||
// output = Array.from(new Uint8Array(output.buffer))
|
||||
// self.postMessage(output)
|
||||
// },
|
||||
// to16kHz(audioData) {
|
||||
// var data = new Float32Array(audioData)
|
||||
// var fitCount = Math.round(data.length * (16000 / 44100))
|
||||
// var newData = new Float32Array(fitCount)
|
||||
// var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
// newData[0] = data[0]
|
||||
// for (let i = 1; i < fitCount - 1; i++) {
|
||||
// var tmp = i * springFactor
|
||||
// var before = Math.floor(tmp).toFixed()
|
||||
// var after = Math.ceil(tmp).toFixed()
|
||||
// var atPoint = tmp - before
|
||||
// newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
// }
|
||||
// newData[fitCount - 1] = data[data.length - 1]
|
||||
// return newData
|
||||
// },
|
||||
// to16BitPCM(input) {
|
||||
// var dataLength = input.length * (16 / 8)
|
||||
// var dataBuffer = new ArrayBuffer(dataLength)
|
||||
// var dataView = new DataView(dataBuffer)
|
||||
// var offset = 0
|
||||
// for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
// var s = Math.max(-1, Math.min(1, input[i]))
|
||||
// dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
||||
// }
|
||||
// return dataView
|
||||
// },
|
||||
// }
|
||||
|
||||
self.onmessage = function (e) {
|
||||
transAudioData.transcode(e.data)
|
||||
}
|
||||
let transAudioData = {
|
||||
transcode(audioData) {
|
||||
let output = transAudioData.to16kHz(audioData)
|
||||
output = transAudioData.to16BitPCM(output)
|
||||
output = Array.from(new Uint8Array(output.buffer))
|
||||
self.postMessage(output)
|
||||
},
|
||||
to16kHz(audioData) {
|
||||
var data = new Float32Array(audioData)
|
||||
var fitCount = Math.round(data.length * (16000 / 44100))
|
||||
var newData = new Float32Array(fitCount)
|
||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
var tmp = i * springFactor
|
||||
var before = Math.floor(tmp).toFixed()
|
||||
var after = Math.ceil(tmp).toFixed()
|
||||
var atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
to16BitPCM(input) {
|
||||
var dataLength = input.length * (16 / 8)
|
||||
var dataBuffer = new ArrayBuffer(dataLength)
|
||||
var dataView = new DataView(dataBuffer)
|
||||
var offset = 0
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]))
|
||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
||||
}
|
||||
return dataView
|
||||
},
|
||||
const transAudioData = {
|
||||
transcode(audioData) {
|
||||
let output = transAudioData.to16kHz(audioData)
|
||||
output = transAudioData.to16BitPCM(output)
|
||||
output = Array.from(new Uint8Array(output.buffer))
|
||||
self.postMessage(output)
|
||||
// return output
|
||||
},
|
||||
to16kHz(audioData) {
|
||||
const data = new Float32Array(audioData)
|
||||
const fitCount = Math.round(data.length * (16000 / 44100))
|
||||
const newData = new Float32Array(fitCount)
|
||||
const springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
const tmp = i * springFactor
|
||||
const before = Math.floor(tmp).toFixed()
|
||||
const after = Math.ceil(tmp).toFixed()
|
||||
const atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
to16BitPCM(input) {
|
||||
const dataLength = input.length * (16 / 8)
|
||||
const dataBuffer = new ArrayBuffer(dataLength)
|
||||
const dataView = new DataView(dataBuffer)
|
||||
let offset = 0
|
||||
for (let i = 0; i < input.length; i++, offset += 2) {
|
||||
const s = Math.max(-1, Math.min(1, input[i]))
|
||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
|
||||
}
|
||||
return dataView
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,123 +1,127 @@
|
|||
|
||||
<script setup lang='ts'>
|
||||
import type { Ref } from "vue";
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { storeToRefs } from "pinia";
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import {
|
||||
NAutoComplete,
|
||||
NButton,
|
||||
NInput,
|
||||
useDialog,
|
||||
useMessage,
|
||||
} from "naive-ui";
|
||||
import html2canvas from "html2canvas";
|
||||
import { Message } from "./components";
|
||||
import { useScroll } from "./hooks/useScroll";
|
||||
import { useChat } from "./hooks/useChat";
|
||||
import { useUsingContext } from "./hooks/useUsingContext";
|
||||
import HeaderComponent from "./components/Header/index.vue";
|
||||
import { HoverButton, SvgIcon } from "@/components/common";
|
||||
import { useBasicLayout } from "@/hooks/useBasicLayout";
|
||||
import { useChatStore, usePromptStore } from "@/store";
|
||||
import { fetchChatAPIProcess } from "@/api";
|
||||
import { t } from "@/locales";
|
||||
} from 'naive-ui'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { Message } from './components'
|
||||
import { useScroll } from './hooks/useScroll'
|
||||
import { useChat } from './hooks/useChat'
|
||||
import { useUsingContext } from './hooks/useUsingContext'
|
||||
import HeaderComponent from './components/Header/index.vue'
|
||||
import { HoverButton, SvgIcon } from '@/components/common'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
import { useChatStore, usePromptStore } from '@/store'
|
||||
import { fetchChatAPIProcess } from '@/api'
|
||||
import { t } from '@/locales'
|
||||
|
||||
import IatRecorder from '@/utils/test.js'
|
||||
// import IatRecorder from '@/utils/larRcorder.js'
|
||||
|
||||
// import socket from "@/websocket/socket";
|
||||
|
||||
// let socket = new WebSocket("wss://chat.lihaink.cn/chat");
|
||||
|
||||
// 接收命令
|
||||
const connection = new Push({
|
||||
url: 'wss://chat.lihaink.cn/zhanti/push', // websocket地址
|
||||
app_key: 'aaea61749929eb53a4bd75a1474c1d27',
|
||||
auth: '/plugin/webman/push/auth' // 订阅鉴权(仅限于私有频道)
|
||||
});
|
||||
url: 'wss://chat.lihaink.cn/zhanti/push', // websocket地址
|
||||
app_key: 'aaea61749929eb53a4bd75a1474c1d27',
|
||||
auth: '/plugin/webman/push/auth', // 订阅鉴权(仅限于私有频道)
|
||||
})
|
||||
|
||||
// 假设用户uid为1
|
||||
var uid = 1;
|
||||
const uid = 1
|
||||
// 浏览器监听user-1频道的消息,也就是用户uid为1的用户消息
|
||||
var user_channel = connection.subscribe('user-' + uid);
|
||||
const user_channel = connection.subscribe(`user-${uid}`)
|
||||
console.log(user_channel)
|
||||
// 当user-1频道有message事件的消息时
|
||||
user_channel.on('message', function(data: any) {
|
||||
// data里是消息内容
|
||||
console.log('收到命令', data);
|
||||
});
|
||||
user_channel.on('message', (data: any) => {
|
||||
// data里是消息内容
|
||||
console.log('收到命令', data)
|
||||
})
|
||||
|
||||
let controller = new AbortController()
|
||||
|
||||
let controller = new AbortController();
|
||||
const openLongReply = import.meta.env.VITE_GLOB_OPEN_LONG_REPLY === 'true'
|
||||
|
||||
const openLongReply = import.meta.env.VITE_GLOB_OPEN_LONG_REPLY === "true";
|
||||
const route = useRoute()
|
||||
const dialog = useDialog()
|
||||
const ms = useMessage()
|
||||
|
||||
const route = useRoute();
|
||||
const dialog = useDialog();
|
||||
const ms = useMessage();
|
||||
const chatStore = useChatStore()
|
||||
|
||||
const chatStore = useChatStore();
|
||||
const { isMobile } = useBasicLayout()
|
||||
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex }
|
||||
= useChat()
|
||||
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
|
||||
const { usingContext, toggleUsingContext } = useUsingContext()
|
||||
|
||||
const { isMobile } = useBasicLayout();
|
||||
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } =
|
||||
useChat();
|
||||
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
|
||||
const { usingContext, toggleUsingContext } = useUsingContext();
|
||||
const { uuid } = route.params as { uuid: string }
|
||||
|
||||
const { uuid } = route.params as { uuid: string };
|
||||
|
||||
const dataSources = computed(() => chatStore.getChatByUuid(+uuid));
|
||||
const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
|
||||
const conversationList = computed(() =>
|
||||
dataSources.value.filter(
|
||||
(item) => !item.inversion && !!item.conversationOptions
|
||||
)
|
||||
);
|
||||
item => !item.inversion && !!item.conversationOptions,
|
||||
),
|
||||
)
|
||||
|
||||
const prompt = ref<string>("");
|
||||
const loading = ref<boolean>(false);
|
||||
const inputRef = ref<Ref | null>(null);
|
||||
const prompt = ref<string>('')
|
||||
const loading = ref<boolean>(false)
|
||||
const inputRef = ref<Ref | null>(null)
|
||||
|
||||
// 添加PromptStore
|
||||
const promptStore = usePromptStore();
|
||||
const promptStore = usePromptStore()
|
||||
|
||||
// 使用storeToRefs,保证store修改后,联想部分能够重新渲染
|
||||
const { promptList: promptTemplate } = storeToRefs<any>(promptStore);
|
||||
const { promptList: promptTemplate } = storeToRefs<any>(promptStore)
|
||||
|
||||
// 未知原因刷新页面,loading 状态不会重置,手动重置
|
||||
dataSources.value.forEach((item, index) => {
|
||||
if (item.loading) updateChatSome(+uuid, index, { loading: false });
|
||||
});
|
||||
if (item.loading)
|
||||
updateChatSome(+uuid, index, { loading: false })
|
||||
})
|
||||
|
||||
function handleSubmit() {
|
||||
onConversation();
|
||||
onConversation()
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
async function onConversation() {
|
||||
let message = prompt.value;
|
||||
const message = prompt.value
|
||||
|
||||
const socket = new WebSocket('wss://chat.lihaink.cn/chat')
|
||||
|
||||
let socket = new WebSocket("wss://chat.lihaink.cn/chat");
|
||||
const promise = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 监听WebSocket连接打开事件
|
||||
socket.onopen = () => {
|
||||
console.log('socket已连接')
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let promise = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
// 监听WebSocket连接打开事件
|
||||
socket.onopen = () => {
|
||||
console.log("socket已连接");
|
||||
resolve(null);
|
||||
};
|
||||
})
|
||||
}
|
||||
await promise()
|
||||
|
||||
await promise();
|
||||
// 监听WebSocket关闭事件
|
||||
socket.onclose = (event: any) => {
|
||||
console.log('连接已关闭: ', event)
|
||||
}
|
||||
|
||||
// 监听WebSocket关闭事件
|
||||
socket.onclose = (event: any) => {
|
||||
console.log(`连接已关闭: `, event);
|
||||
};
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
if (loading.value) return;
|
||||
if (!message || message.trim() === '')
|
||||
return
|
||||
|
||||
if (!message || message.trim() === "") return;
|
||||
|
||||
controller = new AbortController();
|
||||
controller = new AbortController()
|
||||
|
||||
addChat(+uuid, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
|
@ -126,65 +130,66 @@ async function onConversation() {
|
|||
error: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: null },
|
||||
});
|
||||
scrollToBottom();
|
||||
})
|
||||
scrollToBottom()
|
||||
|
||||
loading.value = true;
|
||||
prompt.value = "";
|
||||
loading.value = true
|
||||
prompt.value = ''
|
||||
|
||||
let options: Chat.ConversationRequest = {};
|
||||
const lastContext =
|
||||
conversationList.value[conversationList.value.length - 1]
|
||||
?.conversationOptions;
|
||||
let options: Chat.ConversationRequest = {}
|
||||
const lastContext
|
||||
= conversationList.value[conversationList.value.length - 1]
|
||||
?.conversationOptions
|
||||
|
||||
if (lastContext && usingContext.value) options = { ...lastContext };
|
||||
if (lastContext && usingContext.value)
|
||||
options = { ...lastContext }
|
||||
|
||||
addChat(+uuid, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: "思考中",
|
||||
text: '思考中',
|
||||
loading: true,
|
||||
inversion: false,
|
||||
error: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
});
|
||||
scrollToBottom();
|
||||
})
|
||||
scrollToBottom()
|
||||
|
||||
try {
|
||||
let lastText = "";
|
||||
console.log('发送消息', message);
|
||||
let lastText = ''
|
||||
console.log('发送消息', message)
|
||||
|
||||
const fetchChatAPIOnce = async () => {
|
||||
socket.send(JSON.stringify({
|
||||
tts: 0,
|
||||
data: [
|
||||
{
|
||||
role: "user",
|
||||
role: 'user',
|
||||
content: message,
|
||||
},
|
||||
],
|
||||
}));
|
||||
}))
|
||||
|
||||
// 监听WebSocket接收消息事件
|
||||
socket.onmessage = (event: any) => {
|
||||
let msg = JSON.parse(event.data)
|
||||
// console.log(`收到消息: `, msg.payload.choices.text[0].content);
|
||||
// console.log(`当前消息: `, dataSources.value[dataSources.value.length - 1].text);
|
||||
lastText += msg.payload.choices.text[0].content;
|
||||
updateChat(
|
||||
+uuid,
|
||||
dataSources.value.length - 1,
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: lastText,
|
||||
inversion: false,
|
||||
error: false,
|
||||
loading: true,
|
||||
conversationOptions: { conversationId: msg.header.sid, parentMessageId: msg.header.sid },
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
},
|
||||
)
|
||||
};
|
||||
// 监听WebSocket接收消息事件
|
||||
socket.onmessage = (event: any) => {
|
||||
const msg = JSON.parse(event.data)
|
||||
// console.log(`收到消息: `, msg.payload.choices.text[0].content);
|
||||
// console.log(`当前消息: `, dataSources.value[dataSources.value.length - 1].text);
|
||||
lastText += msg.payload.choices.text[0].content
|
||||
updateChat(
|
||||
+uuid,
|
||||
dataSources.value.length - 1,
|
||||
{
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: lastText,
|
||||
inversion: false,
|
||||
error: false,
|
||||
loading: true,
|
||||
conversationOptions: { conversationId: msg.header.sid, parentMessageId: msg.header.sid },
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// await fetchChatAPIProcess<Chat.ConversationResponse>({
|
||||
// prompt: message,
|
||||
|
@ -228,33 +233,34 @@ async function onConversation() {
|
|||
// }
|
||||
// },
|
||||
// })
|
||||
updateChatSome(+uuid, dataSources.value.length - 1, { loading: false });
|
||||
};
|
||||
updateChatSome(+uuid, dataSources.value.length - 1, { loading: false })
|
||||
}
|
||||
|
||||
await fetchChatAPIOnce();
|
||||
} catch (error: any) {
|
||||
const errorMessage = error?.message ?? t("common.wrong");
|
||||
await fetchChatAPIOnce()
|
||||
}
|
||||
catch (error: any) {
|
||||
const errorMessage = error?.message ?? t('common.wrong')
|
||||
|
||||
if (error.message === "canceled") {
|
||||
if (error.message === 'canceled') {
|
||||
updateChatSome(+uuid, dataSources.value.length - 1, {
|
||||
loading: false,
|
||||
});
|
||||
scrollToBottomIfAtBottom();
|
||||
return;
|
||||
})
|
||||
scrollToBottomIfAtBottom()
|
||||
return
|
||||
}
|
||||
|
||||
const currentChat = getChatByUuidAndIndex(
|
||||
+uuid,
|
||||
dataSources.value.length - 1
|
||||
);
|
||||
dataSources.value.length - 1,
|
||||
)
|
||||
|
||||
if (currentChat?.text && currentChat.text !== "") {
|
||||
if (currentChat?.text && currentChat.text !== '') {
|
||||
updateChatSome(+uuid, dataSources.value.length - 1, {
|
||||
text: `${currentChat.text}\n[${errorMessage}]`,
|
||||
error: false,
|
||||
loading: false,
|
||||
});
|
||||
return;
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updateChat(+uuid, dataSources.value.length - 1, {
|
||||
|
@ -265,60 +271,64 @@ async function onConversation() {
|
|||
loading: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
});
|
||||
scrollToBottomIfAtBottom();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
})
|
||||
scrollToBottomIfAtBottom()
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onRegenerate(index: number) {
|
||||
if (loading.value) return;
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
controller = new AbortController();
|
||||
controller = new AbortController()
|
||||
|
||||
const { requestOptions } = dataSources.value[index];
|
||||
const { requestOptions } = dataSources.value[index]
|
||||
|
||||
let message = requestOptions?.prompt ?? "";
|
||||
let message = requestOptions?.prompt ?? ''
|
||||
|
||||
let options: Chat.ConversationRequest = {};
|
||||
let options: Chat.ConversationRequest = {}
|
||||
|
||||
if (requestOptions.options) options = { ...requestOptions.options };
|
||||
if (requestOptions.options)
|
||||
options = { ...requestOptions.options }
|
||||
|
||||
loading.value = true;
|
||||
loading.value = true
|
||||
|
||||
updateChat(+uuid, index, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: "",
|
||||
text: '',
|
||||
inversion: false,
|
||||
error: false,
|
||||
loading: true,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
});
|
||||
})
|
||||
|
||||
try {
|
||||
let lastText = "";
|
||||
let lastText = ''
|
||||
const fetchChatAPIOnce = async () => {
|
||||
await fetchChatAPIProcess<Chat.ConversationResponse>({
|
||||
prompt: message,
|
||||
options,
|
||||
signal: controller.signal,
|
||||
onDownloadProgress: ({ event }) => {
|
||||
const xhr = event.target;
|
||||
const { responseText } = xhr;
|
||||
const xhr = event.target
|
||||
const { responseText } = xhr
|
||||
// Always process the final line
|
||||
const lastIndex = responseText.lastIndexOf(
|
||||
"\n",
|
||||
responseText.length - 2
|
||||
);
|
||||
let chunk = responseText;
|
||||
if (lastIndex !== -1) chunk = responseText.substring(lastIndex);
|
||||
'\n',
|
||||
responseText.length - 2,
|
||||
)
|
||||
let chunk = responseText
|
||||
if (lastIndex !== -1)
|
||||
chunk = responseText.substring(lastIndex)
|
||||
try {
|
||||
const data = JSON.parse(chunk);
|
||||
const data = JSON.parse(chunk)
|
||||
updateChat(+uuid, index, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
text: lastText + (data.text ?? ""),
|
||||
text: lastText + (data.text ?? ''),
|
||||
inversion: false,
|
||||
error: false,
|
||||
loading: true,
|
||||
|
@ -327,34 +337,36 @@ async function onRegenerate(index: number) {
|
|||
parentMessageId: data.id,
|
||||
},
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
});
|
||||
})
|
||||
|
||||
if (
|
||||
openLongReply &&
|
||||
data.detail.choices[0].finish_reason === "length"
|
||||
openLongReply
|
||||
&& data.detail.choices[0].finish_reason === 'length'
|
||||
) {
|
||||
options.parentMessageId = data.id;
|
||||
lastText = data.text;
|
||||
message = "";
|
||||
return fetchChatAPIOnce();
|
||||
options.parentMessageId = data.id
|
||||
lastText = data.text
|
||||
message = ''
|
||||
return fetchChatAPIOnce()
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
//
|
||||
}
|
||||
},
|
||||
});
|
||||
updateChatSome(+uuid, index, { loading: false });
|
||||
};
|
||||
await fetchChatAPIOnce();
|
||||
} catch (error: any) {
|
||||
if (error.message === "canceled") {
|
||||
})
|
||||
updateChatSome(+uuid, index, { loading: false })
|
||||
}
|
||||
await fetchChatAPIOnce()
|
||||
}
|
||||
catch (error: any) {
|
||||
if (error.message === 'canceled') {
|
||||
updateChatSome(+uuid, index, {
|
||||
loading: false,
|
||||
});
|
||||
return;
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const errorMessage = error?.message ?? t("common.wrong");
|
||||
const errorMessage = error?.message ?? t('common.wrong')
|
||||
|
||||
updateChat(+uuid, index, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
|
@ -364,97 +376,104 @@ async function onRegenerate(index: number) {
|
|||
loading: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
if (loading.value) return;
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
const d = dialog.warning({
|
||||
title: t("chat.exportImage"),
|
||||
content: t("chat.exportImageConfirm"),
|
||||
positiveText: t("common.yes"),
|
||||
negativeText: t("common.no"),
|
||||
title: t('chat.exportImage'),
|
||||
content: t('chat.exportImageConfirm'),
|
||||
positiveText: t('common.yes'),
|
||||
negativeText: t('common.no'),
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
d.loading = true;
|
||||
const ele = document.getElementById("image-wrapper");
|
||||
d.loading = true
|
||||
const ele = document.getElementById('image-wrapper')
|
||||
const canvas = await html2canvas(ele as HTMLDivElement, {
|
||||
useCORS: true,
|
||||
});
|
||||
const imgUrl = canvas.toDataURL("image/png");
|
||||
const tempLink = document.createElement("a");
|
||||
tempLink.style.display = "none";
|
||||
tempLink.href = imgUrl;
|
||||
tempLink.setAttribute("download", "chat-shot.png");
|
||||
if (typeof tempLink.download === "undefined")
|
||||
tempLink.setAttribute("target", "_blank");
|
||||
})
|
||||
const imgUrl = canvas.toDataURL('image/png')
|
||||
const tempLink = document.createElement('a')
|
||||
tempLink.style.display = 'none'
|
||||
tempLink.href = imgUrl
|
||||
tempLink.setAttribute('download', 'chat-shot.png')
|
||||
if (typeof tempLink.download === 'undefined')
|
||||
tempLink.setAttribute('target', '_blank')
|
||||
|
||||
document.body.appendChild(tempLink);
|
||||
tempLink.click();
|
||||
document.body.removeChild(tempLink);
|
||||
window.URL.revokeObjectURL(imgUrl);
|
||||
d.loading = false;
|
||||
ms.success(t("chat.exportSuccess"));
|
||||
Promise.resolve();
|
||||
} catch (error: any) {
|
||||
ms.error(t("chat.exportFailed"));
|
||||
} finally {
|
||||
d.loading = false;
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
document.body.removeChild(tempLink)
|
||||
window.URL.revokeObjectURL(imgUrl)
|
||||
d.loading = false
|
||||
ms.success(t('chat.exportSuccess'))
|
||||
Promise.resolve()
|
||||
}
|
||||
catch (error: any) {
|
||||
ms.error(t('chat.exportFailed'))
|
||||
}
|
||||
finally {
|
||||
d.loading = false
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function handleDelete(index: number) {
|
||||
if (loading.value) return;
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
dialog.warning({
|
||||
title: t("chat.deleteMessage"),
|
||||
content: t("chat.deleteMessageConfirm"),
|
||||
positiveText: t("common.yes"),
|
||||
negativeText: t("common.no"),
|
||||
title: t('chat.deleteMessage'),
|
||||
content: t('chat.deleteMessageConfirm'),
|
||||
positiveText: t('common.yes'),
|
||||
negativeText: t('common.no'),
|
||||
onPositiveClick: () => {
|
||||
chatStore.deleteChatByUuid(+uuid, index);
|
||||
chatStore.deleteChatByUuid(+uuid, index)
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
if (loading.value) return;
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
dialog.warning({
|
||||
title: t("chat.clearChat"),
|
||||
content: t("chat.clearChatConfirm"),
|
||||
positiveText: t("common.yes"),
|
||||
negativeText: t("common.no"),
|
||||
title: t('chat.clearChat'),
|
||||
content: t('chat.clearChatConfirm'),
|
||||
positiveText: t('common.yes'),
|
||||
negativeText: t('common.no'),
|
||||
onPositiveClick: () => {
|
||||
chatStore.clearChatByUuid(+uuid);
|
||||
chatStore.clearChatByUuid(+uuid)
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function handleEnter(event: KeyboardEvent) {
|
||||
if (!isMobile.value) {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
handleSubmit();
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
handleSubmit()
|
||||
}
|
||||
} else {
|
||||
if (event.key === "Enter" && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
handleSubmit();
|
||||
}
|
||||
else {
|
||||
if (event.key === 'Enter' && event.ctrlKey) {
|
||||
event.preventDefault()
|
||||
handleSubmit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleStop() {
|
||||
if (loading.value) {
|
||||
controller.abort();
|
||||
loading.value = false;
|
||||
controller.abort()
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -462,109 +481,116 @@ function handleStop() {
|
|||
// 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
|
||||
// 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
|
||||
const searchOptions = computed(() => {
|
||||
if (prompt.value.startsWith("/")) {
|
||||
if (prompt.value.startsWith('/')) {
|
||||
return promptTemplate.value
|
||||
.filter((item: { key: string }) =>
|
||||
item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())
|
||||
item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase()),
|
||||
)
|
||||
.map((obj: { value: any }) => {
|
||||
return {
|
||||
label: obj.value,
|
||||
value: obj.value,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
// value反渲染key
|
||||
const renderOption = (option: { label: string }) => {
|
||||
for (const i of promptTemplate.value) {
|
||||
if (i.value === option.label) return [i.key];
|
||||
if (i.value === option.label)
|
||||
return [i.key]
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const placeholder = computed(() => {
|
||||
if (isMobile.value) return t("chat.placeholderMobile");
|
||||
return t("chat.placeholder");
|
||||
});
|
||||
|
||||
const buttonDisabled = computed(() => {
|
||||
return loading.value || !prompt.value || prompt.value.trim() === "";
|
||||
});
|
||||
|
||||
const footerClass = computed(() => {
|
||||
let classes = ["p-4"];
|
||||
if (isMobile.value)
|
||||
classes = [
|
||||
"sticky",
|
||||
"left-0",
|
||||
"bottom-0",
|
||||
"right-0",
|
||||
"p-2",
|
||||
"pr-3",
|
||||
"overflow-hidden",
|
||||
];
|
||||
return classes;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
scrollToBottom();
|
||||
if (inputRef.value && !isMobile.value) inputRef.value?.focus();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (loading.value) controller.abort();
|
||||
});
|
||||
|
||||
import IatRecorder from "@/utils/test.js"
|
||||
// import IatRecorder from "@/utils/larRcorder.js"
|
||||
let a = true;
|
||||
const click = ()=>{
|
||||
// new RecordXunfei();
|
||||
const iatRecorder = new IatRecorder();
|
||||
if(a) {
|
||||
iatRecorder.start();
|
||||
a = !a;
|
||||
console.log('录音开始');
|
||||
|
||||
}else {
|
||||
iatRecorder.stop();
|
||||
a=!a;
|
||||
console.log('录音结束');
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
import voiceInputButton from 'voice-input-button2'
|
||||
const placeholder = computed(() => {
|
||||
if (isMobile.value)
|
||||
return t('chat.placeholderMobile')
|
||||
return t('chat.placeholder')
|
||||
})
|
||||
|
||||
const result = ref("")
|
||||
const buttonDisabled = computed(() => {
|
||||
return loading.value || !prompt.value || prompt.value.trim() === ''
|
||||
})
|
||||
|
||||
const footerClass = computed(() => {
|
||||
let classes = ['p-4']
|
||||
if (isMobile.value) {
|
||||
classes = [
|
||||
'sticky',
|
||||
'left-0',
|
||||
'bottom-0',
|
||||
'right-0',
|
||||
'p-2',
|
||||
'pr-3',
|
||||
'overflow-hidden',
|
||||
]
|
||||
}
|
||||
return classes
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
scrollToBottom()
|
||||
if (inputRef.value && !isMobile.value)
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (loading.value)
|
||||
controller.abort()
|
||||
})
|
||||
const iatRecorder = reactive(new IatRecorder())
|
||||
let a = true
|
||||
|
||||
watch(() => iatRecorder.resultText, (n, o) => {
|
||||
console.log('监听', n)
|
||||
prompt.value = n
|
||||
})
|
||||
|
||||
const click = () => {
|
||||
// new RecordXunfei();
|
||||
if (a) {
|
||||
iatRecorder.start(prompt.value)
|
||||
a = !a
|
||||
console.log('录音开始')
|
||||
}
|
||||
else {
|
||||
iatRecorder.stop()
|
||||
a = !a
|
||||
console.log('录音结束')
|
||||
console.log('最终结果', iatRecorder)
|
||||
}
|
||||
}
|
||||
|
||||
const result = ref('')
|
||||
|
||||
// 录音
|
||||
const recordReady = ()=> {
|
||||
console.info("按钮就绪!");
|
||||
}
|
||||
const recordStart = ()=> {
|
||||
console.info("录音开始");
|
||||
}
|
||||
const showResult = (text)=> {
|
||||
console.info("收到识别结果:", text);
|
||||
}
|
||||
const recordStop = ()=> {
|
||||
console.info("录音结束");
|
||||
}
|
||||
const recordNoResult = (text)=> {
|
||||
console.info("没有录到什么,请重试");
|
||||
}
|
||||
const recordComplete = (text)=> {
|
||||
console.info("识别完成! 最终结果:", text);
|
||||
}
|
||||
const recordFailed = (error)=> {
|
||||
console.info("识别失败,错误栈:", error);
|
||||
}
|
||||
|
||||
const recordReady = () => {
|
||||
console.info('按钮就绪!')
|
||||
}
|
||||
const recordStart = () => {
|
||||
console.info('录音开始')
|
||||
}
|
||||
const showResult = (text) => {
|
||||
console.info('收到识别结果:', text)
|
||||
}
|
||||
const recordStop = () => {
|
||||
console.info('录音结束')
|
||||
}
|
||||
const recordNoResult = (text) => {
|
||||
console.info('没有录到什么,请重试')
|
||||
}
|
||||
const recordComplete = (text) => {
|
||||
console.info('识别完成! 最终结果:', text)
|
||||
}
|
||||
const recordFailed = (error) => {
|
||||
console.info('识别失败,错误栈:', error)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -586,30 +612,14 @@ const result = ref("")
|
|||
class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
|
||||
:class="[isMobile ? 'p-2' : 'p-4']"
|
||||
>
|
||||
<!-- <div @click="click">录音开始</div> -->
|
||||
<div>浏览器录音听写:<button id="btn_control" @click="click">开始录音</button></div>
|
||||
<br />
|
||||
<div id="result"></div>
|
||||
|
||||
|
||||
<!-- <voice-input-button
|
||||
appId="2eda6c2e"
|
||||
apiKey="MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2"
|
||||
apiSecret="12ec1f9d113932575fc4b114a2f60ffd"
|
||||
v-model="result"
|
||||
@record="showResult"
|
||||
@record-start="recordStart"
|
||||
@record-stop="recordStop"
|
||||
@record-blank="recordNoResult"
|
||||
@record-failed="recordFailed"
|
||||
@record-ready="recordReady"
|
||||
@record-complete="recordComplete"
|
||||
interactiveMode="touch"
|
||||
color="#fff"
|
||||
tipPosition="top"
|
||||
>
|
||||
<template slot="no-speak">没听清您说的什么</template>
|
||||
</voice-input-button> -->
|
||||
<!-- <div @click="click">录音开始</div> -->
|
||||
<div>
|
||||
浏览器录音听写:<button id="btn_control" @click="click">
|
||||
开始录音
|
||||
</button>
|
||||
</div>
|
||||
<br>
|
||||
<div id="result" />
|
||||
|
||||
<template v-if="!dataSources.length">
|
||||
<div
|
||||
|
|
Loading…
Reference in New Issue