This commit is contained in:
shengchanzhe 2023-10-12 13:42:50 +08:00
parent a13e87195f
commit ee064c2c55
8 changed files with 5157 additions and 5018 deletions

View File

@ -4,3 +4,4 @@ export * from './user'
export * from './prompt' export * from './prompt'
export * from './settings' export * from './settings'
export * from './auth' export * from './auth'
export * from './text'

View File

@ -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)
}

View File

@ -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)
},
},
})

View File

@ -1,8 +1,8 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
import CryptoJS from 'crypto-js'
const APPID = '2eda6c2e' const APPID = '2eda6c2e'
const API_SECRET = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2' const API_SECRET = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2'
const API_KEY = '12ec1f9d113932575fc4b114a2f60ffd' const API_KEY = '12ec1f9d113932575fc4b114a2f60ffd'
import CryptoJS from 'crypto-js'
// import Worker from './transcode.worker.js' // import Worker from './transcode.worker.js'
const transWorker = new Worker(new URL('./transcode.worker.js', import.meta.url)) const transWorker = new Worker(new URL('./transcode.worker.js', import.meta.url))
let startTime = '' let startTime = ''
@ -42,7 +42,7 @@ const IatRecorder = class {
this.resultText = '' this.resultText = ''
// wpgs下的听写结果需要中间状态辅助记录 // wpgs下的听写结果需要中间状态辅助记录
this.resultTextTemp = '' this.resultTextTemp = ''
transWorker.onmessage = function(event) { transWorker.onmessage = function (event) {
// console.log("构造方法中",self.audioData) // console.log("构造方法中",self.audioData)
self.audioData.push(...event.data) self.audioData.push(...event.data)
} }
@ -50,73 +50,80 @@ const IatRecorder = class {
// 修改录音听写状态 // 修改录音听写状态
setStatus(status) { setStatus(status) {
this.onWillStatusChange && this.onWillStatusChange
this.status !== status && && this.status !== status
this.onWillStatusChange(this.status, status) && this.onWillStatusChange(this.status, status)
this.status = status this.status = status
} }
setResultText({ resultText, resultTextTemp } = {}) { setResultText({ resultText, resultTextTemp } = {}) {
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '') this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
resultText !== undefined && (this.resultText = resultText) resultText !== undefined && (this.resultText = resultText)
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp) resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
} }
// 修改听写参数 // 修改听写参数
setParams({ language, accent } = {}) { setParams({ language, accent } = {}) {
language && (this.language = language) language && (this.language = language)
accent && (this.accent = accent) accent && (this.accent = accent)
} }
// 连接websocket // 连接websocket
connectWebSocket() { connectWebSocket() {
return getWebSocketUrl().then(url => { return getWebSocketUrl().then((url) => {
let iatWS let iatWS
if ('WebSocket' in window) { if ('WebSocket' in window) {
iatWS = new WebSocket(url) iatWS = new WebSocket(url)
} else if ('MozWebSocket' in window) { }
else if ('MozWebSocket' in window) {
iatWS = new MozWebSocket(url) iatWS = new MozWebSocket(url)
} else { }
else {
alert('浏览器不支持WebSocket') alert('浏览器不支持WebSocket')
return return
} }
this.webSocket = iatWS this.webSocket = iatWS
this.setStatus('init') this.setStatus('init')
iatWS.onopen = e => { iatWS.onopen = (e) => {
this.setStatus('ing') this.setStatus('ing')
// 重新开始录音 // 重新开始录音
setTimeout(() => { setTimeout(() => {
this.webSocketSend() this.webSocketSend()
}, 500) }, 500)
} }
iatWS.onmessage = e => { iatWS.onmessage = (e) => {
this.result(e.data) this.result(e.data)
} }
iatWS.onerror = e => { iatWS.onerror = (e) => {
this.recorderStop() this.recorderStop()
} }
iatWS.onclose = e => { iatWS.onclose = (e) => {
endTime = Date.parse(new Date()) endTime = Date.parse(new Date())
// console.log('持续时间', endTime - startTime) // console.log('持续时间', endTime - startTime)
this.recorderStop() this.recorderStop()
} }
}) })
} }
// 初始化浏览器录音 // 初始化浏览器录音
recorderInit() { recorderInit() {
navigator.getUserMedia = navigator.getUserMedia
navigator.getUserMedia || = navigator.getUserMedia
navigator.webkitGetUserMedia || || navigator.webkitGetUserMedia
navigator.mozGetUserMedia || || navigator.mozGetUserMedia
navigator.msGetUserMedia || navigator.msGetUserMedia
// 创建音频环境 // 创建音频环境
try { try {
this.audioContext = new (window.AudioContext || this.audioContext = new (window.AudioContext
window.webkitAudioContext)() || window.webkitAudioContext)()
this.audioContext.resume() this.audioContext.resume()
if (!this.audioContext) { if (!this.audioContext) {
alert('浏览器不支持webAudioApi相关接口') alert('浏览器不支持webAudioApi相关接口')
return return
} }
} catch (e) { }
catch (e) {
if (!this.audioContext) { if (!this.audioContext) {
alert('浏览器不支持webAudioApi相关接口') alert('浏览器不支持webAudioApi相关接口')
return return
@ -130,34 +137,37 @@ const IatRecorder = class {
audio: true, audio: true,
video: false, video: false,
}) })
.then(stream => { .then((stream) => {
getMediaSuccess(stream) getMediaSuccess(stream)
}) })
.catch(e => { .catch((e) => {
getMediaFail(e) getMediaFail(e)
}) })
} else if (navigator.getUserMedia) { }
else if (navigator.getUserMedia) {
navigator.getUserMedia( navigator.getUserMedia(
{ {
audio: true, audio: true,
video: false, video: false,
}, },
stream => { (stream) => {
getMediaSuccess(stream) getMediaSuccess(stream)
}, },
function(e) { (e) => {
getMediaFail1(e) getMediaFail1(e)
}, },
) )
} else { }
else {
if ( if (
navigator.userAgent.toLowerCase().match(/chrome/) && navigator.userAgent.toLowerCase().match(/chrome/)
location.origin.indexOf('https://') < 0 && !location.origin.includes('https://')
) { ) {
alert( alert(
'chrome下获取浏览器录音功能因为安全性问题需要在localhost或127.0.0.1或https下才能获取权限', 'chrome下获取浏览器录音功能因为安全性问题需要在localhost或127.0.0.1或https下才能获取权限',
) )
} else { }
else {
alert('无法获取浏览器录音功能,请升级软件版本') alert('无法获取浏览器录音功能,请升级软件版本')
} }
this.voiceDialog = 0 this.voiceDialog = 0
@ -165,12 +175,12 @@ const IatRecorder = class {
return return
} }
// 获取浏览器录音权限成功的回调 // 获取浏览器录音权限成功的回调
const getMediaSuccess = stream => { const getMediaSuccess = (stream) => {
this.voiceDialog = 1 this.voiceDialog = 1
// console.log('getMediaSuccess') // console.log('getMediaSuccess')
// 创建一个用于通过JavaScript直接处理音频 // 创建一个用于通过JavaScript直接处理音频
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1) this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
this.scriptProcessor.onaudioprocess = e => { this.scriptProcessor.onaudioprocess = (e) => {
// 去处理音频数据 // 去处理音频数据
if (this.status === 'ing') { if (this.status === 'ing') {
// console.log(transWorker) // console.log(transWorker)
@ -186,37 +196,38 @@ const IatRecorder = class {
this.connectWebSocket() this.connectWebSocket()
} }
const getMediaFail = e => { const getMediaFail = (e) => {
alert(e) alert(e)
this.voiceDialog = 0 this.voiceDialog = 0
// console.log(e) // console.log(e)
this.audioContext && this.audioContext.close() this.audioContext && this.audioContext.close()
this.audioContext = undefined this.audioContext = undefined
// 关闭websocket // 关闭websocket
if (this.webSocket && this.webSocket.readyState === 1) { if (this.webSocket && this.webSocket.readyState === 1)
this.webSocket.close() this.webSocket.close()
}
} }
const getMediaFail1 = e => { const getMediaFail1 = (e) => {
alert('请求麦克风失败,请添加权限!') alert('请求麦克风失败,请添加权限!')
this.voiceDialog = 0 this.voiceDialog = 0
// console.log(e) // console.log(e)
this.audioContext && this.audioContext.close() this.audioContext && this.audioContext.close()
this.audioContext = undefined this.audioContext = undefined
// 关闭websocket // 关闭websocket
if (this.webSocket && this.webSocket.readyState === 1) { if (this.webSocket && this.webSocket.readyState === 1)
this.webSocket.close() this.webSocket.close()
}
} }
} }
recorderStart() { recorderStart() {
if (!this.audioContext) { if (!this.audioContext) {
this.recorderInit() this.recorderInit()
} else { }
else {
this.audioContext.resume() this.audioContext.resume()
this.connectWebSocket() this.connectWebSocket()
} }
} }
// 暂停录音 // 暂停录音
recorderStop() { recorderStop() {
// safari下suspend后再次resume录音内容将是空白设置safari下不做suspend // safari下suspend后再次resume录音内容将是空白设置safari下不做suspend
@ -224,11 +235,12 @@ const IatRecorder = class {
!( !(
/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen) /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen)
) )
) { )
this.audioContext && this.audioContext.suspend() this.audioContext && this.audioContext.suspend()
}
this.setStatus('end') this.setStatus('end')
} }
// 处理音频数据 // 处理音频数据
// transAudioData(audioData) { // transAudioData(audioData) {
// audioData = transAudioData.transaction(audioData) // audioData = transAudioData.transaction(audioData)
@ -239,25 +251,26 @@ const IatRecorder = class {
let binary = '' let binary = ''
const bytes = new Uint8Array(buffer) const bytes = new Uint8Array(buffer)
const len = bytes.byteLength const len = bytes.byteLength
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++)
binary += String.fromCharCode(bytes[i]) binary += String.fromCharCode(bytes[i])
}
return window.btoa(binary) return window.btoa(binary)
} }
// 向webSocket发送数据 // 向webSocket发送数据
webSocketSend() { webSocketSend() {
if (this.webSocket.readyState !== 1) { if (this.webSocket.readyState !== 1)
return return
}
let audioData = this.audioData.splice(0, 1280) let audioData = this.audioData.splice(0, 1280)
const params = { const params = {
common: { common: {
app_id: this.appId, app_id: this.appId,
}, },
business: { business: {
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用 language: this.language, // 小语种可在控制台--语音听写(流式)--方言/语种处添加试用
domain: 'iat', domain: 'iat',
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用 accent: this.accent, // 中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
vad_eos: 5000, vad_eos: 5000,
dwa: 'wpgs', dwa: 'wpgs',
}, },
@ -282,7 +295,7 @@ const IatRecorder = class {
return return
} }
if (this.audioData.length === 0) { if (this.audioData.length === 0) {
//点击暂停录音 // 点击暂停录音
// console.log('自动关闭', this.status) // console.log('自动关闭', this.status)
if (this.status === 'end') { if (this.status === 'end') {
this.webSocket.send( this.webSocket.send(
@ -314,6 +327,7 @@ const IatRecorder = class {
) )
}, 40) }, 40)
} }
result(resultData) { result(resultData) {
// 识别结束 // 识别结束
const jsonData = JSON.parse(resultData) const jsonData = JSON.parse(resultData)
@ -322,10 +336,10 @@ const IatRecorder = class {
let str = '' let str = ''
const resultStr = '' const resultStr = ''
const ws = data.ws 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 str = str + ws[i].cw[0].w
}
// console.log('识别的结果为:', str) // console.log('', str)
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能) // 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果替换范围为rg字段 // 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果替换范围为rg字段
if (data.pgs) { if (data.pgs) {
@ -339,28 +353,29 @@ const IatRecorder = class {
this.setResultText({ this.setResultText({
resultTextTemp: this.resultText + str, resultTextTemp: this.resultText + str,
}) })
} else { }
else {
this.setResultText({ this.setResultText({
resultText: this.resultText + str, resultText: this.resultText + str,
}) })
} }
} }
if (jsonData.code === 0 && jsonData.data.status === 2) { if (jsonData.code === 0 && jsonData.data.status === 2)
this.webSocket.close() this.webSocket.close()
}
if (jsonData.code !== 0) { if (jsonData.code !== 0)
this.webSocket.close() this.webSocket.close()
// console.log(`${jsonData.code}:${jsonData.message}`) // console.log(`${jsonData.code}:${jsonData.message}`)
}
} }
start() { start() {
this.recorderStart() this.recorderStart()
this.setResultText({ resultText: '', resultTextTemp: '' }) this.setResultText({ resultText: '', resultTextTemp: '' })
} }
stop() { stop() {
this.recorderStop() this.recorderStop()
} }
} }
export default IatRecorder export default IatRecorder

View File

@ -1,330 +1,350 @@
import CryptoJS from 'crypto-js'
const APPID = '2eda6c2e' const APPID = '2eda6c2e'
const API_SECRET = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2' const API_SECRET = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2'
const API_KEY = '12ec1f9d113932575fc4b114a2f60ffd' const API_KEY = '12ec1f9d113932575fc4b114a2f60ffd'
import CryptoJS from 'crypto-js'
// import Worker from './transcode.worker.js' // import Worker from './transcode.worker.js'
// const transWorker = new Worker() // const transWorker = new Worker()
const transWorker = new Worker(new URL('./transcode.worker.js', import.meta.url)) const transWorker = new Worker(new URL('./transcode.worker.js', import.meta.url))
console.log(transWorker) console.log(transWorker)
var startTime = "" let startTime = ''
var endTime = "" let endTime = ''
function getWebSocketUrl(){ function getWebSocketUrl() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 请求地址根据语种不同变化 // 请求地址根据语种不同变化
var url = 'wss://iat-api.xfyun.cn/v2/iat' let url = 'wss://iat-api.xfyun.cn/v2/iat'
var host = 'iat-api.xfyun.cn' const host = 'iat-api.xfyun.cn'
var apiKey = API_KEY const apiKey = API_KEY
var apiSecret = API_SECRET const apiSecret = API_SECRET
var date = new Date().toGMTString() const date = new Date().toGMTString()
var algorithm = 'hmac-sha256' const algorithm = 'hmac-sha256'
var headers = 'host date request-line' const headers = 'host date request-line'
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1` const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret) const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
var signature = CryptoJS.enc.Base64.stringify(signatureSha) const signature = CryptoJS.enc.Base64.stringify(signatureSha)
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"` const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
var authorization = btoa(authorizationOrigin) const authorization = btoa(authorizationOrigin)
url = `${url}?authorization=${authorization}&date=${date}&host=${host}` url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
resolve(url) console.log(date)
}) resolve(url)
})
} }
const IatRecorder = class { const IatRecorder = class {
constructor({ language, accent, appId } = {}) { constructor({ language, accent, appId } = {}) {
let self = this const self = this
this.status = 'null' this.status = 'null'
this.language = language || 'zh_cn' this.language = language || 'zh_cn'
this.accent = accent || 'mandarin' this.accent = accent || 'mandarin'
this.appId = appId || APPID this.appId = appId || APPID
// 记录音频数据 // 记录音频数据
this.audioData = [] this.audioData = []
// 记录听写结果 // 记录听写结果
this.resultText = '' this.resultText = ''
// wpgs下的听写结果需要中间状态辅助记录 // wpgs下的听写结果需要中间状态辅助记录
this.resultTextTemp = '' this.resultTextTemp = ''
transWorker.onmessage = function (event) { transWorker.onmessage = function (event) {
// console.log("构造方法中",self.audioData) // console.log("构造方法中",self.audioData)
self.audioData.push(...event.data) 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()
} }
} }
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

View File

@ -1,38 +1,78 @@
self.onmessage = function(e){ // self.onmessage = function(e){
transAudioData.transcode(e.data) // 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 = { const transAudioData = {
transcode(audioData) { transcode(audioData) {
let output = transAudioData.to16kHz(audioData) let output = transAudioData.to16kHz(audioData)
output = transAudioData.to16BitPCM(output) output = transAudioData.to16BitPCM(output)
output = Array.from(new Uint8Array(output.buffer)) output = Array.from(new Uint8Array(output.buffer))
self.postMessage(output) self.postMessage(output)
}, // return output
to16kHz(audioData) { },
var data = new Float32Array(audioData) to16kHz(audioData) {
var fitCount = Math.round(data.length * (16000 / 44100)) const data = new Float32Array(audioData)
var newData = new Float32Array(fitCount) const fitCount = Math.round(data.length * (16000 / 44100))
var springFactor = (data.length - 1) / (fitCount - 1) const newData = new Float32Array(fitCount)
newData[0] = data[0] const springFactor = (data.length - 1) / (fitCount - 1)
for (let i = 1; i < fitCount - 1; i++) { newData[0] = data[0]
var tmp = i * springFactor for (let i = 1; i < fitCount - 1; i++) {
var before = Math.floor(tmp).toFixed() const tmp = i * springFactor
var after = Math.ceil(tmp).toFixed() const before = Math.floor(tmp).toFixed()
var atPoint = tmp - before const after = Math.ceil(tmp).toFixed()
newData[i] = data[before] + (data[after] - data[before]) * atPoint const atPoint = tmp - before
} newData[i] = data[before] + (data[after] - data[before]) * atPoint
newData[fitCount - 1] = data[data.length - 1] }
return newData newData[fitCount - 1] = data[data.length - 1]
}, return newData
to16BitPCM(input) { },
var dataLength = input.length * (16 / 8) to16BitPCM(input) {
var dataBuffer = new ArrayBuffer(dataLength) const dataLength = input.length * (16 / 8)
var dataView = new DataView(dataBuffer) const dataBuffer = new ArrayBuffer(dataLength)
var offset = 0 const dataView = new DataView(dataBuffer)
for (var i = 0; i < input.length; i++, offset += 2) { let offset = 0
var s = Math.max(-1, Math.min(1, input[i])) for (let i = 0; i < input.length; i++, offset += 2) {
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true) const s = Math.max(-1, Math.min(1, input[i]))
} dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
return dataView }
}, return dataView
},
} }

View File

@ -1,123 +1,127 @@
<script setup lang='ts'> <script setup lang='ts'>
import type { Ref } from "vue"; import type { Ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from "vue"; import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { useRoute } from "vue-router"; import { useRoute } from 'vue-router'
import { storeToRefs } from "pinia"; import { storeToRefs } from 'pinia'
import { import {
NAutoComplete, NAutoComplete,
NButton, NButton,
NInput, NInput,
useDialog, useDialog,
useMessage, useMessage,
} from "naive-ui"; } from 'naive-ui'
import html2canvas from "html2canvas"; import html2canvas from 'html2canvas'
import { Message } from "./components"; import { Message } from './components'
import { useScroll } from "./hooks/useScroll"; import { useScroll } from './hooks/useScroll'
import { useChat } from "./hooks/useChat"; import { useChat } from './hooks/useChat'
import { useUsingContext } from "./hooks/useUsingContext"; import { useUsingContext } from './hooks/useUsingContext'
import HeaderComponent from "./components/Header/index.vue"; import HeaderComponent from './components/Header/index.vue'
import { HoverButton, SvgIcon } from "@/components/common"; import { HoverButton, SvgIcon } from '@/components/common'
import { useBasicLayout } from "@/hooks/useBasicLayout"; import { useBasicLayout } from '@/hooks/useBasicLayout'
import { useChatStore, usePromptStore } from "@/store"; import { useChatStore, usePromptStore } from '@/store'
import { fetchChatAPIProcess } from "@/api"; import { fetchChatAPIProcess } from '@/api'
import { t } from "@/locales"; import { t } from '@/locales'
import IatRecorder from '@/utils/test.js'
// import IatRecorder from '@/utils/larRcorder.js'
// import socket from "@/websocket/socket"; // import socket from "@/websocket/socket";
// let socket = new WebSocket("wss://chat.lihaink.cn/chat"); // let socket = new WebSocket("wss://chat.lihaink.cn/chat");
// //
const connection = new Push({ const connection = new Push({
url: 'wss://chat.lihaink.cn/zhanti/push', // websocket url: 'wss://chat.lihaink.cn/zhanti/push', // websocket
app_key: 'aaea61749929eb53a4bd75a1474c1d27', app_key: 'aaea61749929eb53a4bd75a1474c1d27',
auth: '/plugin/webman/push/auth' // () auth: '/plugin/webman/push/auth', // ()
}); })
// uid1 // uid1
var uid = 1; const uid = 1
// user-1uid1 // user-1uid1
var user_channel = connection.subscribe('user-' + uid); const user_channel = connection.subscribe(`user-${uid}`)
console.log(user_channel) console.log(user_channel)
// user-1message // user-1message
user_channel.on('message', function(data: any) { user_channel.on('message', (data: any) => {
// data // data
console.log('收到命令', 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 chatStore = useChatStore()
const dialog = useDialog();
const ms = useMessage();
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 { uuid } = route.params as { uuid: string }
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } =
useChat();
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
const { usingContext, toggleUsingContext } = useUsingContext();
const { uuid } = route.params as { uuid: string }; const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
const dataSources = computed(() => chatStore.getChatByUuid(+uuid));
const conversationList = computed(() => const conversationList = computed(() =>
dataSources.value.filter( dataSources.value.filter(
(item) => !item.inversion && !!item.conversationOptions item => !item.inversion && !!item.conversationOptions,
) ),
); )
const prompt = ref<string>(""); const prompt = ref<string>('')
const loading = ref<boolean>(false); const loading = ref<boolean>(false)
const inputRef = ref<Ref | null>(null); const inputRef = ref<Ref | null>(null)
// PromptStore // PromptStore
const promptStore = usePromptStore(); const promptStore = usePromptStore()
// 使storeToRefsstore // 使storeToRefsstore
const { promptList: promptTemplate } = storeToRefs<any>(promptStore); const { promptList: promptTemplate } = storeToRefs<any>(promptStore)
// loading // loading
dataSources.value.forEach((item, index) => { dataSources.value.forEach((item, index) => {
if (item.loading) updateChatSome(+uuid, index, { loading: false }); if (item.loading)
}); updateChatSome(+uuid, index, { loading: false })
})
function handleSubmit() { function handleSubmit() {
onConversation(); onConversation()
} }
// //
async function 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 = ()=>{ await promise()
return new Promise((resolve, reject)=>{
// WebSocket
socket.onopen = () => {
console.log("socket已连接");
resolve(null);
};
})
}
await promise(); // WebSocket
socket.onclose = (event: any) => {
console.log('连接已关闭: ', event)
}
// WebSocket if (loading.value)
socket.onclose = (event: any) => { return
console.log(`连接已关闭: `, event);
};
if (loading.value) return; if (!message || message.trim() === '')
return
if (!message || message.trim() === "") return; controller = new AbortController()
controller = new AbortController();
addChat(+uuid, { addChat(+uuid, {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
@ -126,65 +130,66 @@ async function onConversation() {
error: false, error: false,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, options: null }, requestOptions: { prompt: message, options: null },
}); })
scrollToBottom(); scrollToBottom()
loading.value = true; loading.value = true
prompt.value = ""; prompt.value = ''
let options: Chat.ConversationRequest = {}; let options: Chat.ConversationRequest = {}
const lastContext = const lastContext
conversationList.value[conversationList.value.length - 1] = conversationList.value[conversationList.value.length - 1]
?.conversationOptions; ?.conversationOptions
if (lastContext && usingContext.value) options = { ...lastContext }; if (lastContext && usingContext.value)
options = { ...lastContext }
addChat(+uuid, { addChat(+uuid, {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
text: "思考中", text: '思考中',
loading: true, loading: true,
inversion: false, inversion: false,
error: false, error: false,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
}); })
scrollToBottom(); scrollToBottom()
try { try {
let lastText = ""; let lastText = ''
console.log('发送消息', message); console.log('发送消息', message)
const fetchChatAPIOnce = async () => { const fetchChatAPIOnce = async () => {
socket.send(JSON.stringify({ socket.send(JSON.stringify({
tts: 0, tts: 0,
data: [ data: [
{ {
role: "user", role: 'user',
content: message, content: message,
}, },
], ],
})); }))
// WebSocket // WebSocket
socket.onmessage = (event: any) => { socket.onmessage = (event: any) => {
let msg = JSON.parse(event.data) const msg = JSON.parse(event.data)
// console.log(`: `, msg.payload.choices.text[0].content); // console.log(`: `, msg.payload.choices.text[0].content);
// console.log(`: `, dataSources.value[dataSources.value.length - 1].text); // console.log(`: `, dataSources.value[dataSources.value.length - 1].text);
lastText += msg.payload.choices.text[0].content; lastText += msg.payload.choices.text[0].content
updateChat( updateChat(
+uuid, +uuid,
dataSources.value.length - 1, dataSources.value.length - 1,
{ {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
text: lastText, text: lastText,
inversion: false, inversion: false,
error: false, error: false,
loading: true, loading: true,
conversationOptions: { conversationId: msg.header.sid, parentMessageId: msg.header.sid }, conversationOptions: { conversationId: msg.header.sid, parentMessageId: msg.header.sid },
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
}, },
) )
}; }
// await fetchChatAPIProcess<Chat.ConversationResponse>({ // await fetchChatAPIProcess<Chat.ConversationResponse>({
// prompt: message, // 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(); await fetchChatAPIOnce()
} catch (error: any) { }
const errorMessage = error?.message ?? t("common.wrong"); catch (error: any) {
const errorMessage = error?.message ?? t('common.wrong')
if (error.message === "canceled") { if (error.message === 'canceled') {
updateChatSome(+uuid, dataSources.value.length - 1, { updateChatSome(+uuid, dataSources.value.length - 1, {
loading: false, loading: false,
}); })
scrollToBottomIfAtBottom(); scrollToBottomIfAtBottom()
return; return
} }
const currentChat = getChatByUuidAndIndex( const currentChat = getChatByUuidAndIndex(
+uuid, +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, { updateChatSome(+uuid, dataSources.value.length - 1, {
text: `${currentChat.text}\n[${errorMessage}]`, text: `${currentChat.text}\n[${errorMessage}]`,
error: false, error: false,
loading: false, loading: false,
}); })
return; return
} }
updateChat(+uuid, dataSources.value.length - 1, { updateChat(+uuid, dataSources.value.length - 1, {
@ -265,60 +271,64 @@ async function onConversation() {
loading: false, loading: false,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
}); })
scrollToBottomIfAtBottom(); scrollToBottomIfAtBottom()
} finally { }
loading.value = false; finally {
loading.value = false
} }
} }
async function onRegenerate(index: number) { 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, { updateChat(+uuid, index, {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
text: "", text: '',
inversion: false, inversion: false,
error: false, error: false,
loading: true, loading: true,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
}); })
try { try {
let lastText = ""; let lastText = ''
const fetchChatAPIOnce = async () => { const fetchChatAPIOnce = async () => {
await fetchChatAPIProcess<Chat.ConversationResponse>({ await fetchChatAPIProcess<Chat.ConversationResponse>({
prompt: message, prompt: message,
options, options,
signal: controller.signal, signal: controller.signal,
onDownloadProgress: ({ event }) => { onDownloadProgress: ({ event }) => {
const xhr = event.target; const xhr = event.target
const { responseText } = xhr; const { responseText } = xhr
// Always process the final line // Always process the final line
const lastIndex = responseText.lastIndexOf( const lastIndex = responseText.lastIndexOf(
"\n", '\n',
responseText.length - 2 responseText.length - 2,
); )
let chunk = responseText; let chunk = responseText
if (lastIndex !== -1) chunk = responseText.substring(lastIndex); if (lastIndex !== -1)
chunk = responseText.substring(lastIndex)
try { try {
const data = JSON.parse(chunk); const data = JSON.parse(chunk)
updateChat(+uuid, index, { updateChat(+uuid, index, {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
text: lastText + (data.text ?? ""), text: lastText + (data.text ?? ''),
inversion: false, inversion: false,
error: false, error: false,
loading: true, loading: true,
@ -327,34 +337,36 @@ async function onRegenerate(index: number) {
parentMessageId: data.id, parentMessageId: data.id,
}, },
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
}); })
if ( if (
openLongReply && openLongReply
data.detail.choices[0].finish_reason === "length" && data.detail.choices[0].finish_reason === 'length'
) { ) {
options.parentMessageId = data.id; options.parentMessageId = data.id
lastText = data.text; lastText = data.text
message = ""; message = ''
return fetchChatAPIOnce(); return fetchChatAPIOnce()
} }
} catch (error) { }
catch (error) {
// //
} }
}, },
}); })
updateChatSome(+uuid, index, { loading: false }); updateChatSome(+uuid, index, { loading: false })
}; }
await fetchChatAPIOnce(); await fetchChatAPIOnce()
} catch (error: any) { }
if (error.message === "canceled") { catch (error: any) {
if (error.message === 'canceled') {
updateChatSome(+uuid, index, { updateChatSome(+uuid, index, {
loading: false, loading: false,
}); })
return; return
} }
const errorMessage = error?.message ?? t("common.wrong"); const errorMessage = error?.message ?? t('common.wrong')
updateChat(+uuid, index, { updateChat(+uuid, index, {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
@ -364,97 +376,104 @@ async function onRegenerate(index: number) {
loading: false, loading: false,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
}); })
} finally { }
loading.value = false; finally {
loading.value = false
} }
} }
function handleExport() { function handleExport() {
if (loading.value) return; if (loading.value)
return
const d = dialog.warning({ const d = dialog.warning({
title: t("chat.exportImage"), title: t('chat.exportImage'),
content: t("chat.exportImageConfirm"), content: t('chat.exportImageConfirm'),
positiveText: t("common.yes"), positiveText: t('common.yes'),
negativeText: t("common.no"), negativeText: t('common.no'),
onPositiveClick: async () => { onPositiveClick: async () => {
try { try {
d.loading = true; d.loading = true
const ele = document.getElementById("image-wrapper"); const ele = document.getElementById('image-wrapper')
const canvas = await html2canvas(ele as HTMLDivElement, { const canvas = await html2canvas(ele as HTMLDivElement, {
useCORS: true, useCORS: true,
}); })
const imgUrl = canvas.toDataURL("image/png"); const imgUrl = canvas.toDataURL('image/png')
const tempLink = document.createElement("a"); const tempLink = document.createElement('a')
tempLink.style.display = "none"; tempLink.style.display = 'none'
tempLink.href = imgUrl; tempLink.href = imgUrl
tempLink.setAttribute("download", "chat-shot.png"); tempLink.setAttribute('download', 'chat-shot.png')
if (typeof tempLink.download === "undefined") if (typeof tempLink.download === 'undefined')
tempLink.setAttribute("target", "_blank"); tempLink.setAttribute('target', '_blank')
document.body.appendChild(tempLink); document.body.appendChild(tempLink)
tempLink.click(); tempLink.click()
document.body.removeChild(tempLink); document.body.removeChild(tempLink)
window.URL.revokeObjectURL(imgUrl); window.URL.revokeObjectURL(imgUrl)
d.loading = false; d.loading = false
ms.success(t("chat.exportSuccess")); ms.success(t('chat.exportSuccess'))
Promise.resolve(); Promise.resolve()
} catch (error: any) { }
ms.error(t("chat.exportFailed")); catch (error: any) {
} finally { ms.error(t('chat.exportFailed'))
d.loading = false; }
finally {
d.loading = false
} }
}, },
}); })
} }
function handleDelete(index: number) { function handleDelete(index: number) {
if (loading.value) return; if (loading.value)
return
dialog.warning({ dialog.warning({
title: t("chat.deleteMessage"), title: t('chat.deleteMessage'),
content: t("chat.deleteMessageConfirm"), content: t('chat.deleteMessageConfirm'),
positiveText: t("common.yes"), positiveText: t('common.yes'),
negativeText: t("common.no"), negativeText: t('common.no'),
onPositiveClick: () => { onPositiveClick: () => {
chatStore.deleteChatByUuid(+uuid, index); chatStore.deleteChatByUuid(+uuid, index)
}, },
}); })
} }
function handleClear() { function handleClear() {
if (loading.value) return; if (loading.value)
return
dialog.warning({ dialog.warning({
title: t("chat.clearChat"), title: t('chat.clearChat'),
content: t("chat.clearChatConfirm"), content: t('chat.clearChatConfirm'),
positiveText: t("common.yes"), positiveText: t('common.yes'),
negativeText: t("common.no"), negativeText: t('common.no'),
onPositiveClick: () => { onPositiveClick: () => {
chatStore.clearChatByUuid(+uuid); chatStore.clearChatByUuid(+uuid)
}, },
}); })
} }
function handleEnter(event: KeyboardEvent) { function handleEnter(event: KeyboardEvent) {
if (!isMobile.value) { if (!isMobile.value) {
if (event.key === "Enter" && !event.shiftKey) { if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault(); event.preventDefault()
handleSubmit(); handleSubmit()
} }
} else { }
if (event.key === "Enter" && event.ctrlKey) { else {
event.preventDefault(); if (event.key === 'Enter' && event.ctrlKey) {
handleSubmit(); event.preventDefault()
handleSubmit()
} }
} }
} }
function handleStop() { function handleStop() {
if (loading.value) { if (loading.value) {
controller.abort(); controller.abort()
loading.value = false; loading.value = false
} }
} }
@ -462,109 +481,116 @@ function handleStop() {
// 使valuevalue() // 使valuevalue()
// key,renderOptionvaluerenderLabel // key,renderOptionvaluerenderLabel
const searchOptions = computed(() => { const searchOptions = computed(() => {
if (prompt.value.startsWith("/")) { if (prompt.value.startsWith('/')) {
return promptTemplate.value return promptTemplate.value
.filter((item: { key: string }) => .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 }) => { .map((obj: { value: any }) => {
return { return {
label: obj.value, label: obj.value,
value: obj.value, value: obj.value,
}; }
}); })
} else {
return [];
} }
}); else {
return []
}
})
// valuekey // valuekey
const renderOption = (option: { label: string }) => { const renderOption = (option: { label: string }) => {
for (const i of promptTemplate.value) { 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 = ()=> { const recordReady = () => {
console.info("按钮就绪!"); console.info('按钮就绪!')
} }
const recordStart = ()=> { const recordStart = () => {
console.info("录音开始"); console.info('录音开始')
} }
const showResult = (text)=> { const showResult = (text) => {
console.info("收到识别结果:", text); console.info('收到识别结果:', text)
} }
const recordStop = ()=> { const recordStop = () => {
console.info("录音结束"); console.info('录音结束')
} }
const recordNoResult = (text)=> { const recordNoResult = (text) => {
console.info("没有录到什么,请重试"); console.info('没有录到什么,请重试')
} }
const recordComplete = (text)=> { const recordComplete = (text) => {
console.info("识别完成! 最终结果:", text); console.info('识别完成! 最终结果:', text)
} }
const recordFailed = (error)=> { const recordFailed = (error) => {
console.info("识别失败,错误栈:", error); console.info('识别失败,错误栈:', error)
} }
</script> </script>
<template> <template>
@ -586,30 +612,14 @@ const result = ref("")
class="w-full max-w-screen-xl m-auto dark:bg-[#101014]" class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
:class="[isMobile ? 'p-2' : 'p-4']" :class="[isMobile ? 'p-2' : 'p-4']"
> >
<!-- <div @click="click">录音开始</div> --> <!-- <div @click="click">录音开始</div> -->
<div>浏览器录音听写<button id="btn_control" @click="click">开始录音</button></div> <div>
<br /> 浏览器录音听写<button id="btn_control" @click="click">
<div id="result"></div> 开始录音
</button>
</div>
<!-- <voice-input-button <br>
appId="2eda6c2e" <div id="result" />
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> -->
<template v-if="!dataSources.length"> <template v-if="!dataSources.length">
<div <div

8577
yarn.lock

File diff suppressed because it is too large Load Diff