Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
A
ai-chat-ui
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
chenghong_tao
ai-chat-ui
Commits
01be378d
Commit
01be378d
authored
May 15, 2025
by
chenghong_tao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add 显示工作流,fix自动滚动
parent
e4ac9f90
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
120 additions
and
70 deletions
+120
-70
Chat.vue
src/components/Chat.vue
+11
-8
aiBubble.vue
src/components/chatSubassembly/aiBubble.vue
+63
-41
inputMessage.vue
src/components/chatSubassembly/inputMessage.vue
+14
-2
workflowItem.vue
src/components/chatSubassembly/workflowItem.vue
+27
-19
task.js
src/store/task.js
+5
-0
No files found.
src/components/Chat.vue
View file @
01be378d
...
...
@@ -14,6 +14,7 @@
:historyMsgId=
"item?.id"
:messageType=
"item.type"
:content=
"item.content"
@
reStartSSE=
"scrollToBottomStart"
/>
<userBubble
v-else
:content=
"item.content"
/>
</div>
...
...
@@ -68,15 +69,19 @@ const userQuery = (query, isLoading=false) => {
})
if
(
isLoading
)
{
inputMessageRef
.
value
.
changeLoading
(
true
)
// 每隔500ms滚动到底部
nextTick
(()
=>
{
scrollTimer
.
value
=
setInterval
(()
=>
{
scrollToBottom
()
},
300
)
})
scrollToBottomStart
()
}
}
const
scrollToBottomStart
=
()
=>
{
clearInterval
(
scrollTimer
.
value
)
// 每隔500ms滚动到底部
nextTick
(()
=>
{
scrollTimer
.
value
=
setInterval
(()
=>
{
scrollToBottom
()
},
300
)
})
}
const
defaultHeight
=
ref
(
62
);
const
scrollbarStyle
=
computed
(()
=>
{
...
...
@@ -119,8 +124,6 @@ watch(() => props.historyMsgList, (newValue) => {
content
:
item
?.
answer
,
id
:
item
.
id
})
console
.
log
(
'zhisx'
);
},
{
immediate
:
true
})
})
...
...
src/components/chatSubassembly/aiBubble.vue
View file @
01be378d
...
...
@@ -6,14 +6,13 @@
<
template
#
content
>
<div
class=
"custom-bubble-content"
v-adjust-width
>
<!-- 工作流 -->
<
!--
<
workflowItem
v-if=
"
workflowContent.length > 0
"
<workflowItem
v-if=
"
taskStore.showWorkflow
"
:workflowContent=
"workflowContent"
ref=
"workflowItemRef"
@
workflow-is-error=
"workflowIsError"
/>
-->
/>
<!-- 使用 MarkdownRenderer 组件渲染 messageContent -->
<div
class=
"icon"
v-if=
"workflowStatus && workflowStatus != 'success'"
>
<div
class=
"icon"
v-if=
"workflowStatus && workflowStatus != 'success'
&& !taskStore.showWorkflow
"
>
<div
v-if=
"workflowStatus == 'loading'"
>
<el-icon
style=
"font-size: 18px"
class=
"is-loaidng"
>
<Loading
/>
...
...
@@ -33,35 +32,60 @@
</
template
>
<
template
#
footer
>
<div
class=
"footer-container"
>
<el-button
type=
"info"
:icon=
"Refresh"
size=
"small"
circle
<el-tooltip
effect=
"dark"
content=
"重新回答"
placement=
"top"
:disabled=
"taskStore.isLoading"
@
click=
"reStartSSE"
/>
<el-button
:type=
"clickLike ? 'success' : 'info'"
class=
"iconfont icon-zan"
size=
"small"
circle
@
click=
"feedback('like')"
/>
<el-button
:type=
"clickDisLike ? 'success' : 'info'"
class=
"iconfont icon-zan icon-rotate"
size=
"small"
circle
@
click=
"feedback('dislike')"
/>
<el-button
color=
"#626aef"
:icon=
"DocumentCopy"
size=
"small"
circle
@
click=
"copyContent()"
/>
>
<el-button
type=
"info"
:icon=
"Refresh"
size=
"small"
circle
:disabled=
"taskStore.isLoading"
@
click=
"reStartSSE"
/>
</el-tooltip>
<el-tooltip
effect=
"dark"
:content=
"clickLike ? '取消点赞' : '点赞'"
placement=
"top"
>
<el-button
:type=
"clickLike ? 'success' : 'info'"
class=
"iconfont icon-zan"
size=
"small"
circle
@
click=
"feedback('like')"
/>
</el-tooltip>
<el-tooltip
effect=
"dark"
:content=
"clickDisLike ? '取消点踩' : '点踩'"
placement=
"top"
>
<el-button
:type=
"clickDisLike ? 'success' : 'info'"
class=
"iconfont icon-zan icon-rotate"
size=
"small"
circle
@
click=
"feedback('dislike')"
/>
</el-tooltip>
<el-tooltip
effect=
"dark"
content=
"复制"
placement=
"top"
>
<el-button
color=
"#626aef"
:icon=
"DocumentCopy"
size=
"small"
circle
@
click=
"copyContent()"
/>
</el-tooltip>
</div>
</
template
>
</Bubble>
...
...
@@ -86,7 +110,7 @@ const clickDisLike = ref(false)
const
isReStartWorkflow
=
ref
(
false
)
const
workflowStatus
=
ref
(
null
)
const
emits
=
defineEmits
([
"reStartSSE"
]);
const
props
=
defineProps
({
query
:
{
type
:
Array
,
...
...
@@ -192,12 +216,13 @@ const workflowContent = computed(() => {
return
workflowList
;
});
//
const workflowItemRef = ref(null);
const
workflowItemRef
=
ref
(
null
);
const
reStartSSE
=
()
=>
{
isReStartWorkflow
.
value
=
true
;
// if(workflowItemRef.value) {
// workflowItemRef.value.clearWorkflow();
// }
if
(
workflowItemRef
.
value
)
{
workflowItemRef
.
value
.
clearWorkflow
();
}
emits
(
"reStartSSE"
)
startSSE
(
props
.
query
);
console
.
log
(
'reStartSSE'
,
props
.
query
,
props
.
messageType
,
props
.
content
)
};
...
...
@@ -301,9 +326,6 @@ function fallbackCopyTextToClipboard(text) {
document
.
body
.
removeChild
(
textArea
);
}
// const workflowIsError = (isError) => {
// emits("workflow-is-error", isError);
// };
</
script
>
<
style
lang=
"less"
scoped
>
.message-content {
...
...
src/components/chatSubassembly/inputMessage.vue
View file @
01be378d
...
...
@@ -8,11 +8,18 @@
v-model=
"senderValue"
:loading=
"senderLoading"
clearable
variant=
"updown"
:auto-size=
"
{ minRows: 2, maxRows: 5 }"
@submit="handleSubmit"
>
<template
#
prefix
>
<div
class=
"action-list-self-wrap"
>
<el-checkbox
v-model=
"showWorkflow"
label=
"显示工作流"
style=
"margin-right: 10px;"
border
/>
<el-checkbox
v-model=
"deepThink"
label=
"深度思考"
style=
"margin-right: 10px;"
border
/>
</div>
</
template
>
<
template
#
action-list
>
<div
class=
"action-list-self-wrap"
>
<el-checkbox
v-model=
"deepThink"
label=
"深度思考"
style=
"margin-right: 20px;"
border
/>
<el-button
type=
"danger"
link
...
...
@@ -62,7 +69,8 @@ const senderRef = ref();
const
senderValue
=
ref
(
""
);
const
senderLoading
=
ref
(
false
);
const
inputRef
=
ref
(
null
);
const
deepThink
=
ref
(
false
);
const
deepThink
=
ref
(
taskStore
.
deepThink
||
false
);
const
showWorkflow
=
ref
(
taskStore
.
showWorkflow
||
false
);
watch
(()
=>
deepThink
.
value
,
(
newValue
)
=>
{
taskStore
.
setDeepThink
(
newValue
)
...
...
@@ -72,6 +80,10 @@ watch(() => taskStore.isLoading, (newValue) => {
senderLoading
.
value
=
newValue
;
})
watch
(()
=>
showWorkflow
.
value
,
(
newValue
)
=>
{
taskStore
.
setShowWorkflow
(
newValue
)
})
const
emit
=
defineEmits
([
"userQuery"
,
"getInputHeight"
]);
function
handleSubmit
()
{
senderLoading
.
value
=
true
;
...
...
src/components/chatSubassembly/workflowItem.vue
View file @
01be378d
...
...
@@ -11,8 +11,8 @@
<WarningFilled
/>
</el-icon>
</div>
<el-collapse
class=
"workflow-collapse"
>
<el-collapse-item
title=
"工作流"
>
<el-collapse
class=
"workflow-collapse"
v-model=
"activeNames"
>
<el-collapse-item
title=
"工作流"
name=
"workflow"
>
<ThoughtChain
:thinking-items=
"thinkingItems"
row-key=
"codeId"
dot-size=
"small"
style=
"margin-left: 10px"
/>
</el-collapse-item>
...
...
@@ -36,6 +36,8 @@ const props = defineProps({
const
thinkingItems
=
ref
([]);
const
workflowStatus
=
ref
(
'loading'
)
const
activeNames
=
ref
([
'workflow'
])
const
emit
=
defineEmits
([
'workflow-is-error'
])
watch
(
...
...
@@ -47,7 +49,6 @@ watch(
if
(
item
.
event
===
"error"
)
{
ElMessage
.
error
(
item
.
message
)
workflowStatus
.
value
=
'error'
;
emit
(
'workflow-is-error'
,
true
)
break
;
}
if
(
item
.
event
===
"workflow_finished"
)
{
...
...
@@ -59,30 +60,37 @@ watch(
item
.
event
!==
"message_end"
&&
item
.
event
!==
"message"
)
{
const
dataId
=
item
?.
data
?.
id
;
const
index
=
thinkingItems
.
value
.
findIndex
(
(
ti
)
=>
ti
.
codeId
===
item
.
data
.
i
d
(
ti
)
=>
ti
.
codeId
===
dataI
d
);
const
dataId
=
item
?.
data
?.
id
;
// 判断是否为 code、tool 节点,如果是则排除
// const isExcludeNode = item?.data?.node_type === "code" || item?.data?.node_type === "tool"
;
// 非代码节点才添加到 thinkingItems 中
//
if (!isExcludeNode) {
const
excludedNodeTypes
=
[
'code'
,
'start'
,
'question-classifier'
,
'if-else'
,
'answer'
]
;
const
isExcludeNode
=
excludedNodeTypes
.
includes
(
item
?.
data
?.
node_type
);
if
(
!
isExcludeNode
)
{
if
(
index
===
-
1
)
{
thinkingItems
.
value
.
push
({
codeId
:
dataId
,
status
:
"loading"
,
isCanExpand
:
false
,
isDefaultExpand
:
false
,
title
:
item
.
data
.
title
,
thinkTitle
:
""
,
thinkContent
:
""
,
});
// 如果存在迭代节点,且迭代节点正在执行,则不再添加后续节点
const
iterationItem
=
thinkingItems
.
value
.
filter
(
m
=>
m
.
type
===
'iteration'
)
if
(
iterationItem
.
length
>
0
&&
iterationItem
[
0
].
status
===
'loading'
)
{
console
.
log
(
'迭代中'
);
}
else
{
thinkingItems
.
value
.
push
({
codeId
:
dataId
,
status
:
"loading"
,
isCanExpand
:
false
,
isDefaultExpand
:
false
,
title
:
item
.
data
.
title
,
thinkTitle
:
""
,
thinkContent
:
""
,
type
:
item
?.
data
?.
node_type
});
}
}
else
{
if
(
item
.
event
===
"node_finished"
)
{
if
(
item
.
event
===
"node_finished"
||
item
.
event
===
"iteration_completed"
)
{
thinkingItems
.
value
[
index
].
status
=
"success"
;
}
}
//
}
}
}
}
console
.
log
(
"thinkingItems.value"
,
thinkingItems
.
value
);
...
...
src/store/task.js
View file @
01be378d
...
...
@@ -9,6 +9,7 @@ export const useTaskStore = defineStore('task', {
workflow_run_id
:
''
,
// 当前工作流运行id
isLoading
:
false
,
deepThink
:
false
,
showWorkflow
:
false
}
},
actions
:
{
...
...
@@ -32,6 +33,9 @@ export const useTaskStore = defineStore('task', {
setDeepThink
(
think
)
{
this
.
deepThink
=
think
},
setShowWorkflow
(
show
)
{
this
.
showWorkflow
=
show
},
clear
()
{
this
.
taskId
=
''
this
.
conversation_id
=
''
...
...
@@ -39,6 +43,7 @@ export const useTaskStore = defineStore('task', {
this
.
workflow_run_id
=
''
this
.
isLoading
=
false
this
.
deepThink
=
false
this
.
showWorkflow
=
false
}
},
persist
:
{
...
...
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