feat(web): workflow add opening_statement

This commit is contained in:
zhaoying
2026-04-02 19:32:47 +08:00
parent b20a65ce29
commit d60cb423a4
3 changed files with 73 additions and 44 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-13 17:27:52 * @Date: 2026-03-13 17:27:52
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-31 16:04:15 * @Last Modified time: 2026-04-02 17:58:07
*/ */
import { type FC, useState, useRef, useEffect } from 'react' import { type FC, useState, useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -63,6 +63,12 @@ interface NodeData {
state: Record<string, any>; state: Record<string, any>;
status?: 'completed' | 'failed'; status?: 'completed' | 'failed';
audio_url?: string; audio_url?: string;
citations?: {
document_id: string;
file_name: string;
knowledge_id: string;
score: string;
}[]
} }
const TestChat: FC<TestChatProps> = ({ const TestChat: FC<TestChatProps> = ({
@@ -111,8 +117,7 @@ const TestChat: FC<TestChatProps> = ({
} }
}]) }])
} }
let initVariables: Variable[] = [] let initVariables: Variable[] = []
switch (application.type) { switch (application.type) {
@@ -162,7 +167,7 @@ const TestChat: FC<TestChatProps> = ({
}]) }])
} }
const updateAssistantMessage = (content: string, audio_url?: string, audio_status?: string, citations?: any[]) => { const updateAssistantMessage = (content: string, audio_url?: string, audio_status?: string, citations?: NodeData['citations']) => {
setChatList(prev => { setChatList(prev => {
const newList = [...prev] const newList = [...prev]
const lastMsg = newList[newList.length - 1] const lastMsg = newList[newList.length - 1]
@@ -281,12 +286,7 @@ const TestChat: FC<TestChatProps> = ({
data.map(item => { data.map(item => {
const { conversation_id, content, message_length, audio_url, citations } = item.data as { const { conversation_id, content, message_length, audio_url, citations } = item.data as {
conversation_id: string, content: string, message_length: number; audio_url?: string; conversation_id: string, content: string, message_length: number; audio_url?: string;
citations?: { citations?: NodeData['citations']
document_id: string;
file_name: string;
knowledge_id: string;
score: string;
}[]
}; };
switch (item.event) { switch (item.event) {
case 'start': case 'start':
@@ -344,15 +344,15 @@ const TestChat: FC<TestChatProps> = ({
}) })
} }
const handleWorkflowSend = () => { const handleWorkflowSend = (msg?: string) => {
if (loading || !application || !message || !message?.trim()) return if (loading || !application || !((message && message?.trim() !== '') || (msg && msg?.trim() !== ''))) return
const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status)) const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
const variables = toolbarRef.current?.getVariables() || [] const variables = toolbarRef.current?.getVariables() || []
const { isCanSend, params } = buildVariableParams(variables) const { isCanSend, params } = buildVariableParams(variables)
if (!isCanSend) return if (!isCanSend) return
setLoading(true) setLoading(true)
addUserMessage(message, files) addUserMessage((msg || message) as string, files)
addAssistantMessage() addAssistantMessage()
toolbarRef.current?.setFiles([]) toolbarRef.current?.setFiles([])
setFileList([]) setFileList([])
@@ -361,7 +361,7 @@ const TestChat: FC<TestChatProps> = ({
draftRun( draftRun(
application.id, application.id,
formatParams(message, conversationId, files, params), formatParams((msg || message) as string, conversationId, files, params),
handleWorkflowStreamMessage handleWorkflowStreamMessage
) )
.catch((error) => { .catch((error) => {
@@ -383,7 +383,7 @@ const TestChat: FC<TestChatProps> = ({
const handleWorkflowStreamMessage = (data: SSEMessage[]) => { const handleWorkflowStreamMessage = (data: SSEMessage[]) => {
data.forEach(item => { data.forEach(item => {
const { content, conversation_id } = item.data as NodeData; const { content, conversation_id, citations } = item.data as NodeData;
switch (item.event) { switch (item.event) {
// Append streaming text chunks to assistant message // Append streaming text chunks to assistant message
case 'message': case 'message':
@@ -412,6 +412,9 @@ const TestChat: FC<TestChatProps> = ({
// Mark workflow as complete // Mark workflow as complete
case 'workflow_end': case 'workflow_end':
updateWorkflowEndMessage(item.data as NodeData) updateWorkflowEndMessage(item.data as NodeData)
if (citations && citations.length > 0) {
updateWorkflowEndMessage(item.data as NodeData, citations)
}
setStreamLoading(false) setStreamLoading(false)
setLoading(false) setLoading(false)
break break
@@ -536,7 +539,7 @@ const TestChat: FC<TestChatProps> = ({
}) })
} }
const updateWorkflowEndMessage = (data: NodeData) => { const updateWorkflowEndMessage = (data: NodeData, citations?: NodeData['citations']) => {
const { error, status } = data; const { error, status } = data;
setChatList(prev => { setChatList(prev => {
const newList = [...prev] const newList = [...prev]
@@ -547,6 +550,10 @@ const TestChat: FC<TestChatProps> = ({
status, status,
error, error,
content: newList[lastIndex].content === '' ? null : newList[lastIndex].content, content: newList[lastIndex].content === '' ? null : newList[lastIndex].content,
meta_data: {
...newList[lastIndex].meta_data || {},
citations
}
} }
} }
return newList return newList

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:27:56 * @Date: 2026-02-03 16:27:56
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-27 17:32:10 * @Last Modified time: 2026-04-02 17:49:51
*/ */
/** /**
* Copy Application Modal * Copy Application Modal
@@ -116,24 +116,24 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
layout="vertical" layout="vertical"
> >
<Flex vertical gap={12}> <Flex vertical gap={12}>
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
<SwitchFormItem
title={t('application.opening_statement')}
name={['opening_statement', "enabled"]}
desc={values?.opening_statement?.enabled ? undefined : t('application.opening_statement_desc')}
/>
{values?.opening_statement?.enabled && (() => {
const statement = values.opening_statement?.statement
return statement && statement.trim() !== '' ? <>
<div className="rb:bg-white rb:rounded-lg rb:py-1 rb:px-3 rb:mb-1">
{statement}
</div>
<Button block onClick={handleOpenStatementSettings}>{t('application.editOpeningStatement')}</Button>
</> : <Button block onClick={handleOpenStatementSettings}>{t('application.editOpeningStatement')}</Button>
})()}
<Form.Item name="opening_statement" hidden />
</div>
{source !== 'workflow' && <> {source !== 'workflow' && <>
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
<SwitchFormItem
title={t('application.opening_statement')}
name={['opening_statement', "enabled"]}
desc={values?.opening_statement?.enabled ? undefined : t('application.opening_statement_desc')}
/>
{values?.opening_statement?.enabled && (() => {
const statement = values.opening_statement?.statement
return statement && statement.trim() !== '' ? <>
<div className="rb:bg-white rb:rounded-lg rb:py-1 rb:px-3 rb:mb-1">
{statement}
</div>
<Button block onClick={handleOpenStatementSettings}>{t('application.editOpeningStatement')}</Button>
</> : <Button block onClick={handleOpenStatementSettings}>{t('application.editOpeningStatement')}</Button>
})()}
<Form.Item name="opening_statement" hidden />
</div>
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]"> <div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
<SwitchFormItem <SwitchFormItem
title={t(`memoryConversation.web_search`)} title={t(`memoryConversation.web_search`)}
@@ -148,14 +148,14 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
desc={t('application.text_to_speech_desc')} desc={t('application.text_to_speech_desc')}
/> />
</div> </div>
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
<SwitchFormItem
title={t(`application.citation`)}
name={['citation', "enabled"]}
desc={t('application.citation_desc')}
/>
</div>
</>} </>}
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
<SwitchFormItem
title={t(`application.citation`)}
name={['citation', "enabled"]}
desc={t('application.citation_desc')}
/>
</div>
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]"> <div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
<SwitchFormItem <SwitchFormItem

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-06 21:10:56 * @Date: 2026-02-06 21:10:56
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-27 17:30:47 * @Last Modified time: 2026-04-02 18:01:09
*/ */
/** /**
* Workflow Chat Component * Workflow Chat Component
@@ -66,6 +66,17 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef; data: Work
*/ */
const handleOpen = () => { const handleOpen = () => {
setOpen(true) setOpen(true)
if (features?.opening_statement?.statement && features?.opening_statement?.statement.trim() !== '') {
setChatList(prev => [...prev, {
role: 'assistant',
created_at: Date.now(),
content: features?.opening_statement?.statement,
meta_data: {
suggested_questions: features?.opening_statement?.suggested_questions || []
}
}])
}
} }
useEffect(() => { useEffect(() => {
@@ -164,7 +175,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef; data: Work
*/ */
const handleStreamMessage = (data: SSEMessage[]) => { const handleStreamMessage = (data: SSEMessage[]) => {
data.forEach(item => { data.forEach(item => {
const { content, conversation_id, node_id, cycle_id, cycle_idx, input, output, error, elapsed_time, status } = item.data as { const { content, conversation_id, node_id, cycle_id, cycle_idx, input, output, error, elapsed_time, status, citations } = item.data as {
content: string; content: string;
conversation_id: string | null; conversation_id: string | null;
cycle_id: string; cycle_id: string;
@@ -177,7 +188,13 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef; data: Work
elapsed_time?: string; elapsed_time?: string;
error?: any; error?: any;
state: Record<string, any>; state: Record<string, any>;
status?: 'completed' | 'failed' status?: 'completed' | 'failed',
citations?: {
document_id: string;
file_name: string;
knowledge_id: string;
score: string;
}[]
}; };
const node = graphRef.current?.getNodes().find(n => n.id === node_id); const node = graphRef.current?.getNodes().find(n => n.id === node_id);
@@ -312,6 +329,10 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef; data: Work
status, status,
error, error,
content: newList[lastIndex].content === '' ? null : newList[lastIndex].content, content: newList[lastIndex].content === '' ? null : newList[lastIndex].content,
meta_data: {
...newList[lastIndex].meta_data || {},
citations
}
} }
} }
return newList return newList
@@ -421,6 +442,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef; data: Work
renderRuntime={(item, index) => { renderRuntime={(item, index) => {
return <Runtime item={item} index={index} /> return <Runtime item={item} index={index} />
}} }}
onSend={handleSend}
/> />
<Flex align="center" gap={10} className="rb:relative rb:m-4! rb:mb-1!"> <Flex align="center" gap={10} className="rb:relative rb:m-4! rb:mb-1!">
<ChatInput <ChatInput