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
d9ea3ea9
Commit
d9ea3ea9
authored
Jan 08, 2024
by
ali
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 照片数字人展示对话内容
parent
8a378618
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
326 additions
and
2 deletions
+326
-2
AnswerBox.vue
src/renderer/components/common/AnswerBox.vue
+291
-0
Ellipsis.gif
src/renderer/public/images/Ellipsis.gif
+0
-0
ShowPhoto.vue
src/renderer/screens/ShowPhoto.vue
+35
-2
No files found.
src/renderer/components/common/AnswerBox.vue
0 → 100644
View file @
d9ea3ea9
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
'vue'
;
interface
Props
{
visible
?:
boolean
;
question
?:
string
;
loading
?:
boolean
;
answerData
:
{
answerType
:
string
;
data
:
string
[];
subType
:
'text'
|
'image'
|
'video'
|
'relatelist'
|
'guslist'
|
'waitImage'
;
value
:
string
;
}[];
}
interface
EmitType
{
(
e
:
'inputQuestion'
,
value
:
string
):
void
;
}
const
emits
=
defineEmits
<
EmitType
>
();
withDefaults
(
defineProps
<
Props
>
(),
{
answerData
()
{
return
[];
},
visible
:
true
,
loading
:
false
,
question
:
''
});
const
isPreview
=
ref
(
false
);
const
previewUrl
=
ref
(
''
);
function
previewImg
(
url
:
string
)
{
previewUrl
.
value
=
url
;
isPreview
.
value
=
true
;
}
const
handleUrl
=
(
url
:
string
)
=>
{
if
(
url
===
''
)
return
''
;
return
`https://bf-saas.emotibot.com
${
url
}
`
;
};
function
inputQuestion
(
t
:
string
)
{
emits
(
'inputQuestion'
,
t
);
}
const
isPlay
=
ref
(
false
);
function
playVideo
()
{
(
document
.
querySelector
(
'#video'
)
as
HTMLVideoElement
).
play
();
isPlay
.
value
=
true
;
}
const
loadingImg
=
new
URL
(
'/images/Ellipsis.gif'
,
import
.
meta
.
url
).
href
;
</
script
>
<
template
>
<div
:style=
"
{ width: '170px' }"
class="ans-layout"
>
<div
v-show=
"question && question !== ''"
class=
"question-box"
>
<div
class=
"question-con"
>
<div
class=
"scroll"
>
{{
question
}}
</div>
</div>
</div>
<div
v-show=
"answerData.length > 0 && visible"
class=
"answer-box vertical"
>
<div
class=
"scroll"
:style=
"
{ 'max-height': '249.6px' }"
>
<div
v-for=
"(item, index) of answerData"
:key=
"index"
class=
"ans-item"
>
<!-- 文字 -->
<div
v-if=
"item.subType === 'text'"
class=
"text-box"
v-html=
"item.value"
></div>
<!-- 图片 -->
<div
v-else-if=
"item.subType === 'image'"
class=
"img-box"
>
<img
:src=
"handleUrl(item.value)"
@
click=
"previewImg(handleUrl(item.value))"
/>
</div>
<!-- 视频 -->
<div
v-else-if=
"item.subType === 'video'"
class=
"video-box"
>
<video
id=
"video"
controls
:src=
"handleUrl(item.value)"
></video>
<div
v-show=
"!isPlay"
class=
"play-mask"
>
<div
class=
"play"
@
click=
"playVideo()"
>
<img
src=
"@/assets/helper/play.svg"
/>
</div>
</div>
</div>
<!-- 相似问 -->
<div
v-else-if=
"item.subType === 'relatelist' || item.subType === 'guslist'"
class=
"recommend-box"
>
<div
class=
"text"
>
{{
item
.
value
}}
</div>
<ul
class=
"glist"
>
<li
v-for=
"(gitem, gindex) of item.data"
:key=
"gindex"
@
click=
"inputQuestion(gitem)"
>
{{
`${gindex + 1
}
. ${gitem
}
`
}}
<
/li
>
<
/ul
>
<
/div
>
<!--
等待
gif
-->
<
div
v
-
if
=
"loading"
class
=
"text-waitImage"
>
<
img
:
src
=
"loadingImg"
/>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
teleport
to
=
"body"
>
<
div
v
-
if
=
"isPreview"
class
=
"preview-box"
@
click
=
"isPreview = false"
>
<
div
class
=
"img"
:
style
=
"{ backgroundImage: `url(${previewUrl
}
)`
}
"
><
/div
>
<
/div
>
<
/teleport
>
<
/template
>
<
style
scoped
>
.
ans
-
layout
{
position
:
fixed
;
top
:
50
%
;
right
:
30
px
;
transform
:
translateY
(
-
130
px
);
}
.
question
-
box
{
width
:
100
%
;
display
:
flex
;
justify
-
content
:
right
;
color
:
#
FFFFFF
;
margin
-
bottom
:
10
px
;
}
.
question
-
box
.
question
-
con
{
max
-
width
:
100
%
;
background
:
rgba
(
70
,
121
,
254
,
0.7
);
border
-
radius
:
30
px
;
padding
:
15
px
;
}
.
question
-
box
.
question
-
con
.
scroll
{
max
-
height
:
57
px
;
}
.
answer
-
box
{
background
:
rgba
(
255
,
255
,
255
,
0.7
);
border
-
radius
:
30
px
;
border
:
1
px
solid
#
ffffff
;
color
:
#
333333
;
font
-
weight
:
400
;
}
.
answer
-
box
.
horizontal
{
padding
:
14
px
13
px
;
}
.
answer
-
box
.
vertical
{
padding
:
16
px
;
}
.
text
-
box
{
margin
-
bottom
:
15
px
;
}
.
img
-
box
,
.
video
-
box
{
width
:
100
%
;
border
-
radius
:
4
px
4
px
4
px
4
px
;
overflow
:
hidden
;
margin
-
bottom
:
15
px
;
position
:
relative
;
}
.
img
-
box
img
,
.
video
-
box
img
,
.
img
-
box
video
,
.
video
-
box
video
{
width
:
100
%
;
}
.
play
-
mask
{
width
:
100
%
;
height
:
100
%
;
display
:
flex
;
justify
-
content
:
center
;
align
-
items
:
center
;
position
:
absolute
;
top
:
0
;
left
:
0
;
cursor
:
pointer
;
}
.
text
-
waitImage
img
{
width
:
20
%
;
margin
:
0
auto
;
display
:
block
;
}
.
play
{
width
:
44
px
;
height
:
44
px
;
border
:
none
;
border
-
radius
:
50
%
;
opacity
:
0.75
;
}
.
preview
-
box
{
width
:
100
%
;
height
:
100
%
;
position
:
fixed
;
background
:
rgba
(
0
,
0
,
0
,
0.3
);
top
:
0
;
left
:
0
;
z
-
index
:
3
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
}
.
preview
-
box
.
img
{
width
:
80
%
;
height
:
80
%
;
background
-
repeat
:
no
-
repeat
;
background
-
size
:
contain
;
background
-
position
:
center
center
;
}
.
glist
>
li
{
cursor
:
pointer
;
text
-
decoration
:
underline
;
font
-
weight
:
400
;
color
:
#
6464
f8
;
line
-
height
:
24
px
;
}
.
glist
>
li
:
hover
,
.
glist
>
li
:
active
{
color
:
#
2
eacff
;
}
.
scroll
{
height
:
100
%
;
overflow
-
y
:
auto
;
}
.
scroll
::
-
webkit
-
scrollbar
{
width
:
6
px
;
height
:
8
px
;
}
.
scroll
::
-
webkit
-
scrollbar
-
track
{
background
:
transparent
;
border
-
radius
:
4
px
;
}
.
scroll
::
-
webkit
-
scrollbar
-
thumb
{
background
:
#
d8dee9
;
border
-
radius
:
4
px
;
}
.
scroll
::
-
webkit
-
scrollbar
-
thumb
:
hover
{
background
:
#
bdbdbe
;
}
@
media
(
width
:
254
px
)
{
.
ans
-
layout
{
min
-
width
:
150
px
;
transform
:
translateY
(
-
50
%
)
scale
(
0.78
)
!
important
;
transform
-
origin
:
left
center
;
}
}
<
/style
>
src/renderer/public/images/Ellipsis.gif
0 → 100644
View file @
d9ea3ea9
93.8 KB
src/renderer/screens/ShowPhoto.vue
View file @
d9ea3ea9
<!-- eslint-disable no-unused-vars -->
<!-- eslint-disable camelcase -->
<
script
setup
lang=
"ts"
>
import
{
onMounted
,
ref
}
from
'vue'
import
{
onMounted
,
ref
,
type
Ref
}
from
'vue'
import
{
useRoute
,
useRouter
}
from
'vue-router'
import
type
{
ServerMessagePartialResult
,
...
...
@@ -12,6 +12,7 @@ import { audioAiTTS, localTTS } from '../plugins/tts'
import
useStore
from
'@/renderer/store'
import
flvjs
from
'flv.js'
import
{
PhotoAnswer
,
PhotoRole
}
from
'@/renderer/plugins/live/PhotoRole'
import
AnswerBox
from
"@/renderer/components/common/AnswerBox.vue"
;
const
router
=
useRouter
()
const
route
=
useRoute
()
...
...
@@ -37,10 +38,22 @@ const inputContext: {
asrPartial
:
string
answerArray
:
{
text
:
string
;
isLast
:
boolean
}[]
steps
:
Promise
<
string
>
[]
answerProp
:
Ref
<
{
visible
:
boolean
;
question
:
string
;
loading
:
boolean
answerData
:
{
answerType
:
string
;
data
:
string
[];
subType
:
'text'
|
'image'
|
'video'
|
'relatelist'
|
'guslist'
;
value
:
string
;
}[]
}
>
}
=
{
asrPartial
:
''
,
answerArray
:
[],
steps
:
[]
steps
:
[],
answerProp
:
ref
({
question
:
''
,
loading
:
false
,
visible
:
false
,
answerData
:
[]
})
}
router
.
beforeEach
((
g
)
=>
{
...
...
@@ -102,6 +115,10 @@ async function init() {
}
async function onAsyncAnswer(ans: PhotoAnswer) {
if (inputContext.answerProp.value.answerData[0]) {
inputContext.answerProp.value.answerData[0].value = ans.asyncAnswer;
}
if (ans.playState === 'playing') {
microphoneState.value = 'reply'
return
...
...
@@ -259,6 +276,10 @@ async function endAudioInput() {
inputContext.asrPartial = ''
inputContext.answerArray.length = 0
inputContext.steps.length = 0
inputContext.answerProp.value.answerData.length = 0;
inputContext.answerProp.value.visible = false;
inputContext.answerProp.value.question = '';
inputContext.answerProp.value.loading = false;
// @ts-ignore
photoRole?.off('asyncAnswer', onAsyncAnswer)
await photoRole?.destroy()
...
...
@@ -299,6 +320,8 @@ async function onQ(question: string) {
console.log('----------------> question: ', question)
microphoneState.value = 'loading'
inputContext.answerProp.value.question = question;
inputContext.answerProp.value.loading = true;
const { pose, stepResolve, stepReject } = createStep()
inputContext.steps.length = 0
...
...
@@ -334,6 +357,13 @@ async function llmLoop(question: string) {
}
photoRole
!
.
answerArgs
=
answer
inputContext
.
answerProp
.
value
.
visible
=
true
;
inputContext
.
answerProp
.
value
.
answerData
.
push
({
subType
:
'text'
,
value
:
''
,
answerType
:
''
,
data
:
[]
})
// @ts-ignore
photoRole
!
.
off
(
'asyncAnswer'
,
onAsyncAnswer
)
photoRole
!
.
on
(
'asyncAnswer'
,
onAsyncAnswer
)
...
...
@@ -363,6 +393,7 @@ async function llmLoop(question: string) {
const
audioList
=
results
[
0
].
audio_list
as
string
[]
if
(
audioList
.
length
===
0
)
continue
const
isEnd
=
audioList
.
at
(
-
1
)
===
'stream_end'
inputContext
.
answerProp
.
value
.
loading
=
!
isEnd
if
(
isEnd
)
audioList
.
pop
()
...
...
@@ -482,6 +513,8 @@ async function down() {
<v-btn
color=
"red"
variant=
"text"
@
click=
"errorSnackbar = false"
>
Close
</v-btn>
</
template
>
</v-snackbar>
<answer-box
:loading=
"inputContext.answerProp.value.loading"
:visible=
"inputContext.answerProp.value.visible"
:question=
"inputContext.answerProp.value.question"
:answer-data=
"inputContext.answerProp.value.answerData"
></answer-box>
</template>
<
style
scoped
>
.voice
{
...
...
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