Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
C
CharIP-Electron
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ali
CharIP-Electron
Commits
a5aa32b4
Commit
a5aa32b4
authored
Dec 14, 2023
by
ali
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 照片数字人直播接入
parent
9b2696f3
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
105558 additions
and
663 deletions
+105558
-663
settings.json
.vscode/settings.json
+1
-0
env.d.ts
env.d.ts
+2
-2
package-lock.json
package-lock.json
+0
-9
package.json
package.json
+1
-1
IPCs.ts
src/main/IPCs.ts
+27
-0
http.ts
src/main/utils/http.ts
+28
-0
index.ts
src/preload/index.ts
+4
-2
HeaderLayout.vue
src/renderer/components/layout/HeaderLayout.vue
+15
-0
index.html
src/renderer/index.html
+1
-1
HwWebRTC.ts
src/renderer/plugins/live/HwWebRTC.ts
+49
-49
PhotoRole.ts
src/renderer/plugins/live/PhotoRole.ts
+278
-251
HWLLSPlayer.js
src/renderer/public/HWLLSPlayer.js
+52616
-0
PhotoScreen.vue
src/renderer/screens/PhotoScreen.vue
+1
-1
ShowPhoto.vue
src/renderer/screens/ShowPhoto.vue
+88
-37
photo.ts
src/renderer/store/photo.ts
+18
-7
settings.ts
src/renderer/store/settings.ts
+3
-1
export.ts
src/renderer/utils/HWLLS_SDK_Web_2.3.0/export.ts
+1
-1
HWLLSPlayer.d.ts
src/renderer/utils/HWLLS_SDK_Web_2.3.0/lib/HWLLSPlayer.d.ts
+9
-9
HWLLSPlayer.js
src/renderer/utils/HWLLS_SDK_Web_2.3.0/lib/HWLLSPlayer.js
+52392
-275
http.ts
src/renderer/utils/http.ts
+13
-13
index.ts
src/renderer/utils/index.ts
+3
-3
tsconfig.json
tsconfig.json
+8
-1
No files found.
.vscode/settings.json
View file @
a5aa32b4
...
...
@@ -20,6 +20,7 @@
"editor.tabSize"
:
2
,
"cSpell.words"
:
[
"flvjs"
,
"superres"
,
"Vosk"
],
"editor.inlineSuggest.showToolbar"
:
"always"
...
...
env.d.ts
View file @
a5aa32b4
import
type
{
HWLLSPlayer
}
from
'@/renderer/utils/HWLLS_SDK_Web_2.3.0/export'
;
import
type
{
HWLLSPlayer
}
from
'@/renderer/utils/HWLLS_SDK_Web_2.3.0/export'
declare
global
{
// eslint-disable-next-line no-unused-vars
...
...
package-lock.json
View file @
a5aa32b4
...
...
@@ -12,7 +12,6 @@
"axios"
:
"^1.6.2"
,
"electron-store"
:
"^8.1.0"
,
"EventEmitter"
:
"^1.0.0"
,
"events"
:
"^3.3.0"
,
"flv.js"
:
"^1.6.2"
,
"pinia"
:
"^2.1.7"
,
"pinia-plugin-persistedstate"
:
"^3.2.0"
,
...
...
@@ -5552,14 +5551,6 @@
"node"
:
">=0.12"
}
},
"node_modules/events"
:
{
"version"
:
"3.3.0"
,
"resolved"
:
"https://registry.npmmirror.com/events/-/events-3.3.0.tgz"
,
"integrity"
:
"sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
,
"engines"
:
{
"node"
:
">=0.8.x"
}
},
"node_modules/exit-hook"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmmirror.com/exit-hook/-/exit-hook-1.1.1.tgz"
,
...
...
package.json
View file @
a5aa32b4
src/main/IPCs.ts
View file @
a5aa32b4
import
{
BrowserWindow
,
ipcMain
,
shell
,
BrowserWindowConstructorOptions
,
app
}
from
'electron'
import
Constants
from
'./utils/Constants'
import
fs
from
'fs'
import
http
from
'./utils/http'
/*
* IPC Communications
...
...
@@ -7,6 +9,12 @@ import Constants from './utils/Constants'
export
default
class
IPCs
{
static
browserWindows
:
Map
<
string
,
BrowserWindow
[]
>
=
new
Map
()
// 读取本地文件
static
readFile
(
path
)
{
const
file
=
fs
.
readFileSync
(
path
)
return
file
}
static
initialize
(
window
:
BrowserWindow
):
void
{
ipcMain
.
on
(
'mesGetUserData'
,
()
=>
{
window
.
webContents
.
send
(
'msgReceivedUserData'
,
app
.
getPath
(
'userData'
))
...
...
@@ -78,6 +86,9 @@ export default class IPCs {
await
win
.
loadURL
(
url
)
// Initialize IPC Communication
IPCs
.
initializeChildWindow
(
win
)
if
(
!
IPCs
.
browserWindows
.
has
(
url
))
{
IPCs
.
browserWindows
.
set
(
url
,
[])
}
...
...
@@ -106,4 +117,20 @@ export default class IPCs {
}
})
}
static
initializeChildWindow
(
window
:
BrowserWindow
)
{
ipcMain
.
on
(
'fileUpload'
,
async
(
event
,
path
:
string
)
=>
{
const
content
=
IPCs
.
readFile
(
path
)
const
formData
=
new
FormData
()
const
blob
=
new
Blob
([
content
],
{
type
:
'audio/wav'
})
formData
.
append
(
'file'
,
blob
)
const
response
=
await
http
({
url
:
'https://beta.laihua.com/api/upload/file'
,
method
:
'POST'
,
data
:
formData
})
window
.
webContents
.
send
(
'msgReceivedFileUploadResponse'
,
response
)
})
}
}
src/main/utils/http.ts
0 → 100644
View file @
a5aa32b4
import
axios
from
'axios'
import
type
{
AxiosRequestConfig
}
from
'axios'
export
const
axiosInstance
=
axios
.
create
()
export
interface
ApiResult
<
T
=
unknown
>
{
error
?:
boolean
code
?:
number
message
?:
string
msg
?:
string
data
?:
T
[
k
:
string
]:
any
}
export
default
async
function
http
<
T
>
(
input
:
AxiosRequestConfig
):
Promise
<
ApiResult
<
T
>>
{
try
{
const
response
=
await
axiosInstance
(
input
)
if
(
response
.
status
===
200
)
{
return
response
.
data
}
return
{
error
:
true
}
}
catch
(
error
)
{
return
{
code
:
(
error
as
any
).
response
?.
code
||
(
error
as
any
).
code
,
data
:
(
error
as
any
)?.
response
?.
data
}
}
}
src/preload/index.ts
View file @
a5aa32b4
...
...
@@ -8,13 +8,15 @@ const mainAvailChannels: string[] = [
'openWindow'
,
'openDevTools'
,
'mesGetUserData'
,
'mesGetAppData'
'mesGetAppData'
,
'fileUpload'
]
const
rendererAvailChannels
:
string
[]
=
[
'msgReceivedVersion'
,
'msgReceivedFilePath'
,
'msgReceivedUserData'
,
'msgReceivedAppData'
'msgReceivedAppData'
,
'msgReceivedFileUploadResponse'
]
contextBridge
.
exposeInMainWorld
(
'mainApi'
,
{
...
...
src/renderer/components/layout/HeaderLayout.vue
View file @
a5aa32b4
...
...
@@ -45,6 +45,12 @@ const asrItems = ref([
'vosk_ws'
// 'Whisper Api'
])
const
liveHosts
=
ref
([
'http://111.229.216.162:9000'
,
'http://124.221.182.173:9000'
,
'http://110.42.214.59:9000'
,
'http://122.51.32.12:9000'
])
const
asrSelect
=
ref
(
setting
.
asr
)
const
source
=
computed
(()
=>
{
...
...
@@ -186,6 +192,15 @@ function clear() {
:model-value=
"setting.llmUrl"
></v-text-field>
<v-select
v-model=
"setting.liveHost.value"
style=
"margin-top: 22px"
:items=
"liveHosts"
:rules=
"[(v) => !!v || '请选择音色']"
label=
"直播地址"
required
></v-select>
<v-slider
v-model=
"setting.llmToTTSSliceLength.value"
label=
"TTS 分句长度"
...
...
src/renderer/index.html
View file @
a5aa32b4
...
...
@@ -6,6 +6,6 @@
<body>
<div
id=
"app"
></div>
</body>
<script
src=
"./
utils/HWLLS_SDK_Web_2.3.0/lib/
HWLLSPlayer.js"
></script>
<script
src=
"./HWLLSPlayer.js"
></script>
<script
type=
"module"
src=
"./main.ts"
></script>
</html>
src/renderer/plugins/live/HwWebRTC.ts
View file @
a5aa32b4
import
EventEmitter
from
'EventEmitter'
;
import
EventEmitter
from
'EventEmitter'
/**
*
...
...
@@ -30,41 +30,41 @@ import EventEmitter from 'EventEmitter';
*
*/
export
type
StartPlayOptions
=
{
objectFit
?:
'contain'
|
'cover'
|
'fill'
;
muted
?:
boolean
;
sessionId
?:
string
;
showLoading
?:
boolean
;
autoPlay
?:
boolean
;
objectFit
?:
'contain'
|
'cover'
|
'fill'
muted
?:
boolean
sessionId
?:
string
showLoading
?:
boolean
autoPlay
?:
boolean
poster
?:
{
url
?:
string
;
mode
?:
'fill'
|
'crop'
;
startEnable
?:
boolean
;
pauseEnable
:
boolean
;
}
;
}
;
url
?:
string
mode
?:
'fill'
|
'crop'
startEnable
?:
boolean
pauseEnable
:
boolean
}
}
// 自定义事件类型
export
type
HwEventType
=
'videoStart'
|
'audioStart'
|
'audioBroken'
|
'videoBroken'
|
'error'
;
// 场景页切换
export
type
HwEventType
=
'videoStart'
|
'audioStart'
|
'audioBroken'
|
'videoBroken'
|
'error'
// 场景页切换
export
type
HwEventTypeData
<
T
extends
HwEventType
>
=
{
videoStart
:
[]
;
audioStart
:
[]
;
audioBroken
:
[]
;
videoBroken
:
[]
;
error
:
[{
code
:
number
;
message
:
string
}]
;
}[
T
]
;
videoStart
:
[]
audioStart
:
[]
audioBroken
:
[]
videoBroken
:
[]
error
:
[{
code
:
number
;
message
:
string
}]
}[
T
]
export
type
HwEventTypeFn
<
T
extends
HwEventType
>
=
{
// eslint-disable-next-line no-unused-vars
[
K
in
T
]:
(...
args
:
HwEventTypeData
<
T
>
)
=>
void
;
}[
T
]
;
[
K
in
T
]:
(...
args
:
HwEventTypeData
<
T
>
)
=>
void
}[
T
]
export
class
HwWebRTC
extends
EventEmitter
{
elementId
=
''
;
startPlayOptions
:
StartPlayOptions
|
null
=
null
;
client
:
any
=
null
;
elementId
=
''
startPlayOptions
:
StartPlayOptions
|
null
=
null
client
:
any
=
null
constructor
(
id
:
string
,
log
:
'none'
|
'error'
|
'warn'
|
'info'
|
'debug'
=
'none'
)
{
super
()
;
this
.
elementId
=
id
;
super
()
this
.
elementId
=
id
// setLogLevel(log);
}
...
...
@@ -75,7 +75,7 @@ export class HwWebRTC extends EventEmitter {
* @returns 是否成功
*/
emit
<
T
extends
HwEventType
>
(
event
:
T
,
...
args
:
HwEventTypeData
<
T
>
):
boolean
{
return
super
.
emit
(
event
,
...
args
)
;
return
super
.
emit
(
event
,
...
args
)
}
/**
...
...
@@ -86,16 +86,16 @@ export class HwWebRTC extends EventEmitter {
*/
on
<
T
extends
HwEventType
>
(
event
:
T
,
fn
:
HwEventTypeFn
<
T
>
):
this
{
// fn 可能确实只有一个参数, 只能使用as
return
super
.
on
(
event
,
fn
as
(...
args
:
any
[])
=>
void
)
;
return
super
.
on
(
event
,
fn
as
(...
args
:
any
[])
=>
void
)
}
/**
* 预处理:获取浏览器的版本号、检查兼容性
*/
static
async
isBrowserSupport
()
{
let
check
=
false
;
check
=
await
window
.
HWLLSPlayer
.
checkSystemRequirements
()
;
return
check
;
let
check
=
false
check
=
await
window
.
HWLLSPlayer
.
checkSystemRequirements
()
return
check
}
/**
...
...
@@ -109,51 +109,51 @@ export class HwWebRTC extends EventEmitter {
objectFit
:
'contain'
}
)
{
if
(
this
.
client
)
this
.
destroyed
()
;
this
.
startPlayOptions
=
options
;
this
.
client
=
window
.
HWLLSPlayer
.
createClient
(
'webrtc'
)
;
if
(
this
.
client
)
this
.
destroyed
()
this
.
startPlayOptions
=
options
this
.
client
=
window
.
HWLLSPlayer
.
createClient
(
'webrtc'
)
await
this
.
client
.
startPlay
(
url
,
{
elementId
:
this
.
elementId
,
...
this
.
startPlayOptions
})
;
this
.
client
.
enableStreamStateDetection
(
true
,
2
)
;
this
.
_bindEvents
()
;
})
this
.
client
.
enableStreamStateDetection
(
true
,
2
)
this
.
_bindEvents
()
}
private
_bindEvents
()
{
this
.
client
.
on
(
'video-start'
,
()
=>
{
this
.
emit
(
'videoStart'
)
;
})
;
this
.
emit
(
'videoStart'
)
})
this
.
client
.
on
(
'audio-start'
,
()
=>
{
this
.
emit
(
'audioStart'
)
;
})
;
this
.
emit
(
'audioStart'
)
})
this
.
client
.
on
(
'audio-broken'
,
()
=>
{
this
.
emit
(
'audioBroken'
)
;
})
;
this
.
emit
(
'audioBroken'
)
})
this
.
client
.
on
(
'video-broken'
,
()
=>
{
this
.
emit
(
'videoBroken'
)
;
})
;
this
.
emit
(
'videoBroken'
)
})
// this.client.on('audio-recovery', () => {
// logManage.log('----------------> audio-recovery', 1);
// });
// this.client.on('video-recovery', () => {
// logManage.log('----------------> video-recovery', 1);
// });
this
.
client
.
on
(
'Error'
,
(
error
:
any
)
=>
this
.
emit
(
'error'
,
error
))
;
this
.
client
.
on
(
'Error'
,
(
error
:
any
)
=>
this
.
emit
(
'error'
,
error
))
}
/**
* 停止播放:停止播放请求
*/
stopPlay
()
{
this
.
client
&&
this
.
client
.
stopPlay
()
;
this
.
client
&&
this
.
client
.
stopPlay
()
}
/**
* 后处理:销毁客户端等。
*/
destroyed
()
{
this
.
client
?.
offAllEvents
()
;
this
.
client
?.
destoryClient
()
;
this
.
client
?.
offAllEvents
()
this
.
client
?.
destoryClient
()
}
}
src/renderer/plugins/live/PhotoRole.ts
View file @
a5aa32b4
This diff is collapsed.
Click to expand it.
src/renderer/public/HWLLSPlayer.js
0 → 100644
View file @
a5aa32b4
This diff is collapsed.
Click to expand it.
src/renderer/screens/PhotoScreen.vue
View file @
a5aa32b4
...
...
@@ -53,7 +53,7 @@ async function appendPhoto(url: string) {
return
'图片加载失败!'
}
photo
.
list
.
value
.
push
({
url
})
photo
.
list
.
value
.
push
({
url
,
liveUrl
:
url
})
urlValue
.
value
=
''
return
true
...
...
src/renderer/screens/ShowPhoto.vue
View file @
a5aa32b4
...
...
@@ -11,21 +11,21 @@ import type {
import
{
audioAiTTS
,
localTTS
}
from
'../plugins/tts'
import
useStore
from
'@/renderer/store'
import
flvjs
from
'flv.js'
import
{
Photo
Role
}
from
'@/renderer/plugins/live/PhotoRole'
;
import
{
Photo
Answer
,
PhotoRole
}
from
'@/renderer/plugins/live/PhotoRole'
const
router
=
useRouter
()
const
route
=
useRoute
()
const
{
settings
}
=
useStore
()
const
{
settings
,
photo
}
=
useStore
()
let
sampleRate
=
48000
const
bufferSize
=
8192
const
iconMicrophone
=
new
URL
(
'/images/microphone-input.svg'
,
import
.
meta
.
url
).
href
const
recordVolume
=
ref
(
0
)
const
url
=
route
.
query
.
url
as
string
const
microphoneState
=
ref
<
'waitInput'
|
'input'
|
'loading'
|
'disabled'
>
(
'waitInput'
)
const
microphoneState
=
ref
<
'waitInput'
|
'input'
|
'loading'
|
'disabled'
|
'reply'
>
(
'waitInput'
)
const
videoElement
=
ref
<
HTMLVideoElement
|
null
>
(
null
)
const
can
=
ref
<
HTMLCanvasElement
|
null
>
(
null
)
let
photoRole
:
PhotoRole
|
null
=
null
;
let
photoRole
:
PhotoRole
|
null
=
null
let
flvPlayer
:
flvjs
.
Player
|
null
=
null
onMounted
(()
=>
{
...
...
@@ -42,40 +42,55 @@ function loadImg(): Promise<HTMLImageElement> {
}
async
function
init
()
{
microphoneState
.
value
=
'loading'
const
img
=
await
loadImg
()
const
videoEle
=
videoElement
.
value
const
canvasEle
=
can
.
value
const
ctx
=
canvasEle
&&
canvasEle
.
getContext
(
'2d'
)
if
(
!
videoEle
||
!
canvasEle
||
!
ctx
)
return
draw
(
ctx
,
img
)
canvasEle
.
width
=
img
.
naturalWidth
canvasEle
.
height
=
img
.
naturalHeight
photoRole
=
new
PhotoRole
(
url
,
canvasEle
);
const
item
=
photo
.
list
.
find
((
i
)
=>
i
.
url
===
url
)
photoRole
=
new
PhotoRole
(
settings
.
liveHost
,
`
${
item
?.
liveUrl
}
`, canvasEle)
photoRole.on('asyncAnswer', (ans) => {
if (ans.playState === 'playing') {
microphoneState.value = 'reply'
return
}
if (
microphoneState.value === 'reply' &&
ans.playState === 'pause' &&
photoRole!.taskQueueLength === 0 &&
answerArray.length === 0
) {
microphoneState.value = 'input'
}
})
// initPlayer(videoEle);
try {
await photoRole.init()
} catch (error) {
console.error(error)
return
}
microphoneState.value = 'waitInput'
const fps = 1000 / 30
let lastTime = Date.now()
const updateFrame = () => {
if (Date.now() - lastTime > fps) {
draw
(
ctx
,
img
,
videoEle
,
{
width
:
579
,
height
:
579
,
center
:
{
x
:
295
,
y
:
168
},
r_w
:
304
,
r_h
:
304
})
photoRole?.draw()
lastTime = Date.now()
}
requestAnimationFrame(updateFrame)
}
requestAnimationFrame(updateFrame)
await
photoRole
.
initLive
();
}
function draw(
...
...
@@ -258,7 +273,7 @@ async function startVoskWsAudioInput() {
}
await initVoskWS()
sampleRate
=
8
000
sampleRate =
16
000
const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
...
...
@@ -275,7 +290,17 @@ async function startVoskWsAudioInput() {
source.connect(processor)
processor.connect(audioContext.destination)
processor
.
onaudioprocess
=
(
audioDataChunk
)
=>
postAudio
(
audioDataChunk
)
processor.onaudioprocess = (audioDataChunk) => {
if (
microphoneState.value === 'loading' ||
microphoneState.value === 'disabled' ||
microphoneState.value === 'reply'
) {
return
}
postAudio(audioDataChunk)
}
await analyzeMicrophoneVolume(mediaStream, (val) => {
recordVolume.value = val
...
...
@@ -340,16 +365,20 @@ function endAudioInput() {
}
}
const answerArray: { text: string; isLast: boolean }[] = []
async function onAsr(question: string) {
console.log('---------------->question: ', question)
endAudioInput
()
microphoneState.value = 'loading'
const ws = await initLLMSocket()
inputContext.ws = ws
let sliceAnswer = ''
let answer = ''
const
answerArray
:
string
[]
=
[]
answerArray.length = 0
let isTime = true
photoRole!.answerArgs = new PhotoAnswer()
ws.onmessage = (message) => {
try {
...
...
@@ -360,18 +389,17 @@ async function onAsr(question: string) {
}
if (event === 'stream_end') {
answerArray
.
push
(
sliceAnswer
)
runTTSTask
(
answerArray
)
sliceAnswer
=
''
answerArray
.
push
(
sliceAnswer
)
answerArray.push({ text: sliceAnswer, isLast: true })
sliceAnswer = ''
runTTSTask(answerArray)
inputContext.ws?.close()
console.log('----------------> answer: ', answer)
return
}
answer += text
photoRole!.answerArgs!.answer += answer
photoRole!.answerArgs!._typingAnswer.push(answer)
isTime && console.time('sliceAnswer')
isTime = false
...
...
@@ -381,7 +409,7 @@ async function onAsr(question: string) {
sliceAnswer += t
if (/[。,?!;,.?!;]/.test(t) && sliceAnswer.length >= settings.llmToTTSSliceLength) {
console.timeEnd('sliceAnswer')
answerArray
.
push
(
sliceAnswer
)
answerArray.push(
{ text: sliceAnswer, isLast: true }
)
runTTSTask(answerArray)
sliceAnswer = ''
isTime = true
...
...
@@ -405,7 +433,7 @@ function initLLMSocket(): Promise<WebSocket> {
}
let isTTSRunning = false
async
function
runTTSTask
(
tasks
:
string
[])
{
async function runTTSTask(tasks:
{ text: string; isLast: boolean }
[]) {
if (isTTSRunning) return
isTTSRunning = true
...
...
@@ -413,20 +441,24 @@ async function runTTSTask(tasks: string[]) {
while (tasks.length) {
const task = tasks.shift()
if (!task) break
if
(
task
.
length
<
1
)
continue
if (task.
text.trim().
length < 1) continue
console.time(task + ' TTS: ')
const res = await localTTS({
url: settings.ttsHost,
text
:
task
,
text: task
.text
,
audio_path: settings.userData
})
console.log('----------------> TTS:', res[0].text)
console.timeEnd(task + ' TTS: ')
const
audio
=
new
Audio
(
`file://
${
res
[
0
].
text
}
`
)
audio
.
load
()
ttsAudios
.
push
(
audio
)
runAudioPlay
()
console.log('---------------->', res[0].text)
const audioPath = await uploadFile({ filePath: res[0].text })
photoRole?.enQueue({
taskId: photoRole.sessionId,
audioUrl: `
https
:
//resources.laihua.com/${audioPath}`,
isLast
:
task
.
isLast
}
)
}
} catch (error) {
console.error(error)
...
...
@@ -435,6 +467,21 @@ async function runTTSTask(tasks: string[]) {
isTTSRunning = false
}
function uploadFile({ filePath }: { filePath: string }) {
return new Promise<string>((resolve, reject) => {
window.mainApi.receive(
'msgReceivedFileUploadResponse',
(event: Event, result: { code: number; data: null | { filename: string } }) => {
if (result.code !== 200) {
return reject(JSON.stringify(result))
}
resolve(result.data?.filename || '')
}
)
window.mainApi.send('fileUpload', filePath)
})
}
const ttsAudios: HTMLAudioElement[] = []
let isPlayRunning = false
async function runAudioPlay() {
...
...
@@ -452,7 +499,6 @@ async function runAudioPlay() {
}
await audio.play()
}
</
script
>
<
template
>
...
...
@@ -477,12 +523,17 @@ async function runAudioPlay() {
color=
"#fff"
variant=
"elevated"
size=
"x-large"
:disabled=
"microphoneState === 'loading' || microphoneState === 'disabled'"
:disabled=
"
microphoneState === 'loading' ||
microphoneState === 'disabled' ||
microphoneState === 'reply'
"
@
pointerdown=
"startVoskWsAudioInput"
>
<v-icon
v-if=
"microphoneState === 'waitInput'"
icon=
"mdi-microphone"
></v-icon>
<v-icon
v-if=
"microphoneState === 'loading'"
icon=
"mdi-microphone-settings"
></v-icon>
<v-icon
v-if=
"microphoneState === 'disabled'"
icon=
"mdi-microphone-off"
></v-icon>
<v-icon
v-if=
"microphoneState === 'reply'"
icon=
"mdi-message-reply-text-outline"
></v-icon>
<template
v-if=
"microphoneState === 'input'"
>
<img
width=
"30"
height=
"30"
:src=
"iconMicrophone"
alt=
""
srcset=
""
/>
...
...
src/renderer/store/photo.ts
View file @
a5aa32b4
import
{
defineStore
}
from
'pinia'
type
IPhoto
=
{
list
:
{
url
:
string
}[]
list
:
{
url
:
string
;
liveUrl
:
string
}[]
}
const
usePhotoStore
=
defineStore
(
'photo'
,
{
...
...
@@ -10,22 +10,33 @@ const usePhotoStore = defineStore('photo', {
({
list
:
[
{
url
:
new
URL
(
'/images/photo/1.png'
,
import
.
meta
.
url
).
href
url
:
new
URL
(
'/images/photo/1.png'
,
import
.
meta
.
url
).
href
,
liveUrl
:
'https://resources.laihua.com/2023-12-14/11772300-9a47-11ee-84b0-fbd08f47254f.png'
},
{
url
:
new
URL
(
'/images/photo/2.png'
,
import
.
meta
.
url
).
href
url
:
new
URL
(
'/images/photo/2.png'
,
import
.
meta
.
url
).
href
,
liveUrl
:
'https://resources.laihua.com/2023-12-14/32b3e530-9a47-11ee-8702-5ddbbcc07698.png'
},
{
url
:
new
URL
(
'/images/photo/3.png'
,
import
.
meta
.
url
).
href
url
:
new
URL
(
'/images/photo/3.png'
,
import
.
meta
.
url
).
href
,
liveUrl
:
'https://resources.laihua.com/2023-12-14/55060f00-9a47-11ee-8702-5ddbbcc07698.png'
},
{
url
:
new
URL
(
'/images/photo/4.png'
,
import
.
meta
.
url
).
href
url
:
new
URL
(
'/images/photo/4.png'
,
import
.
meta
.
url
).
href
,
liveUrl
:
'https://resources.laihua.com/2023-12-14/81a0d220-9a47-11ee-84b0-fbd08f47254f.png'
},
{
url
:
new
URL
(
'/2023-11-2/93ffb6a7-ae93-4918-944e-877016ba266b.png'
,
import
.
meta
.
url
).
href
url
:
new
URL
(
'/2023-11-2/93ffb6a7-ae93-4918-944e-877016ba266b.png'
,
import
.
meta
.
url
).
href
,
liveUrl
:
'https://resources.laihua.com/2023-11-2/93ffb6a7-ae93-4918-944e-877016ba266b.png'
},
{
url
:
new
URL
(
'/2023-11-2/6fa9a127-2ce5-43ea-a543-475bf9354eda.png'
,
import
.
meta
.
url
).
href
url
:
new
URL
(
'/2023-11-2/6fa9a127-2ce5-43ea-a543-475bf9354eda.png'
,
import
.
meta
.
url
).
href
,
liveUrl
:
'https://resources.laihua.com/2023-12-14/b7523e40-9a47-11ee-84b0-fbd08f47254f.png'
}
]
})
as
IPhoto
,
...
...
src/renderer/store/settings.ts
View file @
a5aa32b4
...
...
@@ -27,6 +27,7 @@ export type ISettings = {
llmUrl
:
string
llmToTTSSliceLength
:
number
voskWsLUrl
:
string
liveHost
:
string
}
const
useSettingsStore
=
defineStore
(
'settings'
,
{
...
...
@@ -61,7 +62,8 @@ const useSettingsStore = defineStore('settings', {
isOpenDevTools
:
false
,
llmUrl
:
'ws://127.0.0.1:9899/api/v1/stream'
,
llmToTTSSliceLength
:
20
,
voskWsLUrl
:
'ws://127.0.0.1:2700'
voskWsLUrl
:
'ws://127.0.0.1:2700'
,
liveHost
:
'http://122.51.32.12:9000'
})
as
ISettings
,
getters
:
{},
actions
:
{
...
...
src/renderer/utils/HWLLS_SDK_Web_2.3.0/export.ts
View file @
a5aa32b4
export
*
as
HWLLSPlayer
from
'./lib/HWLLSPlayer'
;
\ No newline at end of file
export
*
as
HWLLSPlayer
from
'./lib/HWLLSPlayer'
src/renderer/utils/HWLLS_SDK_Web_2.3.0/lib/HWLLSPlayer.d.ts
View file @
a5aa32b4
declare
const
_default
:
{
getVersion
:
any
;
checkSystemRequirements
:
any
;
setParameter
:
any
;
createClient
:
any
;
saveLog
:
any
;
setLogLevel
:
any
;
uploadLog
:
any
;
}
;
export
{
_default
as
default
}
;
getVersion
:
any
checkSystemRequirements
:
any
setParameter
:
any
createClient
:
any
saveLog
:
any
setLogLevel
:
any
uploadLog
:
any
}
export
{
_default
as
default
}
src/renderer/utils/HWLLS_SDK_Web_2.3.0/lib/HWLLSPlayer.js
View file @
a5aa32b4
This diff is collapsed.
Click to expand it.
src/renderer/utils/http.ts
View file @
a5aa32b4
import
axios
from
'axios'
;
import
type
{
AxiosRequestConfig
}
from
'axios'
;
import
axios
from
'axios'
import
type
{
AxiosRequestConfig
}
from
'axios'
export
const
axiosInstance
=
axios
.
create
()
;
export
const
axiosInstance
=
axios
.
create
()
export
interface
ApiResult
<
T
=
unknown
>
{
error
?:
boolean
;
code
?:
number
;
message
?:
string
;
msg
?:
string
;
data
?:
T
;
[
k
:
string
]:
any
;
error
?:
boolean
code
?:
number
message
?:
string
msg
?:
string
data
?:
T
[
k
:
string
]:
any
}
export
default
async
function
http
<
T
>
(
input
:
AxiosRequestConfig
):
Promise
<
ApiResult
<
T
>>
{
try
{
const
response
=
await
axiosInstance
(
input
)
;
const
response
=
await
axiosInstance
(
input
)
if
(
response
.
status
===
200
)
{
return
response
.
data
;
return
response
.
data
}
return
{
error
:
true
}
;
return
{
error
:
true
}
}
catch
(
error
)
{
return
{
code
:
(
error
as
any
).
response
?.
code
||
(
error
as
any
).
code
,
data
:
(
error
as
any
)?.
response
?.
data
}
;
}
}
}
src/renderer/utils/index.ts
View file @
a5aa32b4
...
...
@@ -9,10 +9,10 @@ export default class Utils {
static
guid
()
{
function
S4
()
{
return
(((
1
+
Math
.
random
())
*
0x10000
)
|
0
).
toString
(
16
).
substring
(
1
)
;
return
(((
1
+
Math
.
random
())
*
0x10000
)
|
0
).
toString
(
16
).
substring
(
1
)
}
return
S4
()
+
S4
()
+
'-'
+
S4
()
+
'-'
+
S4
()
+
'-'
+
S4
()
+
'-'
+
S4
()
+
S4
()
+
S4
()
;
return
S4
()
+
S4
()
+
'-'
+
S4
()
+
'-'
+
S4
()
+
'-'
+
S4
()
+
'-'
+
S4
()
+
S4
()
+
S4
()
}
}
export
const
{
getCurrentLocale
,
openExternal
,
guid
}
=
Utils
export
const
{
getCurrentLocale
,
openExternal
,
guid
}
=
Utils
tsconfig.json
View file @
a5aa32b4
...
...
@@ -27,5 +27,12 @@
"path"
:
"./tsconfig.node.json"
}
],
"exclude"
:
[
"node_modules"
,
"dist"
,
"rollup.config.js"
,
"*.json"
,
"*.js"
,
"src/renderer/utils/HWLLS_SDK_Web_2.3.0/**'"
]
"exclude"
:
[
"node_modules"
,
"dist"
,
"rollup.config.js"
,
"*.json"
,
"*.js"
,
"src/renderer/utils/HWLLS_SDK_Web_2.3.0/**'"
]
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment