This commit is contained in:
parent
a13e87195f
commit
ee064c2c55
|
@ -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'
|
||||||
|
|
|
@ -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 */
|
/* 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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', // 订阅鉴权(仅限于私有频道)
|
||||||
});
|
})
|
||||||
|
|
||||||
// 假设用户uid为1
|
// 假设用户uid为1
|
||||||
var uid = 1;
|
const uid = 1
|
||||||
// 浏览器监听user-1频道的消息,也就是用户uid为1的用户消息
|
// 浏览器监听user-1频道的消息,也就是用户uid为1的用户消息
|
||||||
var user_channel = connection.subscribe('user-' + uid);
|
const user_channel = connection.subscribe(`user-${uid}`)
|
||||||
console.log(user_channel)
|
console.log(user_channel)
|
||||||
// 当user-1频道有message事件的消息时
|
// 当user-1频道有message事件的消息时
|
||||||
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()
|
||||||
|
|
||||||
// 使用storeToRefs,保证store修改后,联想部分能够重新渲染
|
// 使用storeToRefs,保证store修改后,联想部分能够重新渲染
|
||||||
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() {
|
||||||
// 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
|
// 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
|
||||||
// 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
|
// 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
|
||||||
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 []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// value反渲染key
|
// value反渲染key
|
||||||
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
|
||||||
|
|
Loading…
Reference in New Issue