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

View File

@ -1,36 +1,37 @@
import CryptoJS from 'crypto-js'
const APPID = '2eda6c2e'
const API_SECRET = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2'
const API_KEY = '12ec1f9d113932575fc4b114a2f60ffd'
import CryptoJS from 'crypto-js'
// import Worker from './transcode.worker.js'
// const transWorker = new Worker()
const transWorker = new Worker(new URL('./transcode.worker.js', import.meta.url))
console.log(transWorker)
var startTime = ""
var endTime = ""
let startTime = ''
let endTime = ''
function getWebSocketUrl(){
function getWebSocketUrl() {
return new Promise((resolve, reject) => {
// 请求地址根据语种不同变化
var url = 'wss://iat-api.xfyun.cn/v2/iat'
var host = 'iat-api.xfyun.cn'
var apiKey = API_KEY
var apiSecret = API_SECRET
var date = new Date().toGMTString()
var algorithm = 'hmac-sha256'
var headers = 'host date request-line'
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
var signature = CryptoJS.enc.Base64.stringify(signatureSha)
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
var authorization = btoa(authorizationOrigin)
let url = 'wss://iat-api.xfyun.cn/v2/iat'
const host = 'iat-api.xfyun.cn'
const apiKey = API_KEY
const apiSecret = API_SECRET
const date = new Date().toGMTString()
const algorithm = 'hmac-sha256'
const headers = 'host date request-line'
const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
const signature = CryptoJS.enc.Base64.stringify(signatureSha)
const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
const authorization = btoa(authorizationOrigin)
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
console.log(date)
resolve(url)
})
}
const IatRecorder = class {
constructor({ language, accent, appId } = {}) {
let self = this
const self = this
this.status = 'null'
this.language = language || 'zh_cn'
this.accent = accent || 'mandarin'
@ -52,57 +53,64 @@ const IatRecorder = class {
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status)
this.status = status
}
setResultText({ resultText, resultTextTemp } = {}) {
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
resultText !== undefined && (this.resultText = resultText)
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
}
// 修改听写参数
setParams({ language, accent } = {}) {
language && (this.language = language)
accent && (this.accent = accent)
}
// 连接websocket
connectWebSocket() {
return getWebSocketUrl().then(url => {
return getWebSocketUrl().then((url) => {
let iatWS
if ('WebSocket' in window) {
iatWS = new WebSocket(url)
} else if ('MozWebSocket' in window) {
}
else if ('MozWebSocket' in window) {
iatWS = new MozWebSocket(url)
} else {
}
else {
alert('浏览器不支持WebSocket')
return
}
this.webSocket = iatWS
this.setStatus('init')
iatWS.onopen = e => {
iatWS.onopen = (e) => {
this.setStatus('ing')
// 重新开始录音
setTimeout(() => {
this.webSocketSend()
}, 500)
}
iatWS.onmessage = e => {
iatWS.onmessage = (e) => {
this.result(e.data)
}
iatWS.onerror = e => {
iatWS.onerror = (e) => {
this.recorderStop()
}
iatWS.onclose = e => {
iatWS.onclose = (e) => {
console.log('关闭原因', e)
endTime = Date.parse(new Date())
console.log("持续时间",endTime-startTime)
console.log('持续时间', endTime - startTime)
this.recorderStop()
}
})
}
// 初始化浏览器录音
recorderInit() {
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia
navigator.getUserMedia
= navigator.getUserMedia
|| navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia
|| navigator.msGetUserMedia
// 创建音频环境
try {
@ -112,7 +120,8 @@ const IatRecorder = class {
alert('浏览器不支持webAudioApi相关接口')
return
}
} catch (e) {
}
catch (e) {
if (!this.audioContext) {
alert('浏览器不支持webAudioApi相关接口')
return
@ -126,45 +135,47 @@ const IatRecorder = class {
audio: true,
video: false,
})
.then(stream => {
.then((stream) => {
getMediaSuccess(stream)
})
.catch(e => {
.catch((e) => {
getMediaFail(e)
})
} else if (navigator.getUserMedia) {
}
else if (navigator.getUserMedia) {
navigator.getUserMedia(
{
audio: true,
video: false,
},
stream => {
(stream) => {
getMediaSuccess(stream)
},
function(e) {
(e) => {
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')
}
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 => {
let getMediaSuccess = (stream) => {
// 创建一个用于通过JavaScript直接处理音频
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
this.scriptProcessor.onaudioprocess = e => {
this.scriptProcessor.onaudioprocess = (e) => {
// 去处理音频数据
if (this.status === 'ing') {
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)
// 连接
@ -177,56 +188,61 @@ const IatRecorder = class {
this.audioContext && this.audioContext.close()
this.audioContext = undefined
// 关闭websocket
if (this.webSocket && this.webSocket.readyState === 1) {
if (this.webSocket && this.webSocket.readyState === 1)
this.webSocket.close()
}
}
}
recorderStart() {
if (!this.audioContext) {
this.recorderInit()
} else {
}
else {
this.audioContext.resume()
this.connectWebSocket()
}
}
// 暂停录音
recorderStop() {
// safari下suspend后再次resume录音内容将是空白设置safari下不做suspend
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))){
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)
// }
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++) {
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) {
if (this.webSocket.readyState !== 1)
return
}
let audioData = this.audioData.splice(0, 1280)
var params = {
const params = {
common: {
app_id: this.appId,
},
business: {
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
language: this.language, // 小语种可在控制台--语音听写(流式)--方言/语种处添加试用
domain: 'iat',
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
accent: this.accent, // 中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
},
data: {
status: 0,
@ -235,20 +251,20 @@ const IatRecorder = class {
audio: this.toBase64(audioData),
},
}
console.log("参数language",this.language)
console.log("参数accent",this.accent)
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未连接")
console.log('websocket未连接', this.webSocket)
this.audioData = []
clearInterval(this.handlerInterval)
return
}
if (this.audioData.length === 0) {
console.log("自动关闭",this.status)
console.log('自动关闭', this.status)
if (this.status === 'end') {
this.webSocket.send(
JSON.stringify({
@ -258,7 +274,7 @@ const IatRecorder = class {
encoding: 'raw',
audio: '',
},
})
}),
)
this.audioData = []
clearInterval(this.handlerInterval)
@ -275,22 +291,23 @@ const IatRecorder = class {
encoding: 'raw',
audio: this.toBase64(audioData),
},
})
}),
)
}, 40)
}
result(resultData) {
// 识别结束
let jsonData = JSON.parse(resultData)
const jsonData = JSON.parse(resultData)
if (jsonData.data && jsonData.data.result) {
let data = jsonData.data.result
const data = jsonData.data.result
let str = ''
let resultStr = ''
let ws = data.ws
for (let i = 0; i < ws.length; i++) {
const resultStr = ''
const ws = data.ws
for (let i = 0; i < ws.length; i++)
str = str + ws[i].cw[0].w
}
console.log("识别的结果为:",str)
console.log('识别的结果为:', str)
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果替换范围为rg字段
if (data.pgs) {
@ -304,27 +321,30 @@ const IatRecorder = class {
this.setResultText({
resultTextTemp: this.resultText + str,
})
} else {
}
else {
this.setResultText({
resultText: this.resultText + str,
})
}
}
if (jsonData.code === 0 && jsonData.data.status === 2) {
if (jsonData.code === 0 && jsonData.data.status === 2)
this.webSocket.close()
}
if (jsonData.code !== 0) {
this.webSocket.close()
console.log(`${jsonData.code}:${jsonData.message}`)
}
}
start() {
this.recorderStart()
this.setResultText({ resultText: '', resultTextTemp: '' })
}
stop() {
this.recorderStop()
}
}
}
export default IatRecorder
export default IatRecorder

View File

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

View File

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

8577
yarn.lock

File diff suppressed because it is too large Load Diff