fix(web): workflow's chat
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2025-12-10 16:46:17
|
* @Date: 2025-12-10 16:46:17
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2025-12-11 13:40:18
|
* @Last Modified time: 2026-01-12 20:41:27
|
||||||
*/
|
*/
|
||||||
import { type FC, useRef, useEffect } from 'react'
|
import { type FC, useRef, useEffect } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
@@ -55,7 +55,7 @@ const ChatContent: FC<ChatContentProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{/* 消息气泡框 */}
|
{/* 消息气泡框 */}
|
||||||
<div className={clsx('rb:border rb:text-left rb:rounded-lg rb:mt-1.5 rb:leading-4.5 rb:p-[10px_12px_2px_12px] rb:inline-block rb:max-w-100', contentClassNames, {
|
<div className={clsx('rb:border rb:text-left rb:rounded-lg rb:mt-1.5 rb:leading-4.5 rb:p-[10px_12px_2px_12px] rb:inline-block rb:max-w-100 rb:wrap-break-word', contentClassNames, {
|
||||||
// 错误消息样式(内容为null且非助手消息)
|
// 错误消息样式(内容为null且非助手消息)
|
||||||
'rb:border-[rgba(255,93,52,0.30)] rb:bg-[rgba(255,93,52,0.08)] rb:text-[#FF5D34]': errorDesc && item.role === 'assistant' && item.content === null,
|
'rb:border-[rgba(255,93,52,0.30)] rb:bg-[rgba(255,93,52,0.08)] rb:text-[#FF5D34]': errorDesc && item.role === 'assistant' && item.content === null,
|
||||||
// 助手消息样式
|
// 助手消息样式
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cookieUtils } from './request'
|
import { cookieUtils } from './request'
|
||||||
export const clearAuthData = () => {
|
export const clearAuthData = () => {
|
||||||
console.log("Clearing auth data and redirecting to login");
|
console.log("Clearing auth data and redirecting to login");
|
||||||
sessionStorage.clear();
|
// sessionStorage.clear();
|
||||||
localStorage.clear()
|
// localStorage.clear()
|
||||||
cookieUtils.clear();
|
cookieUtils.clear();
|
||||||
}
|
}
|
||||||
@@ -12,43 +12,57 @@ export function parseSSEToJSON(sseString: string) {
|
|||||||
const lines = sseString.trim().split('\n')
|
const lines = sseString.trim().split('\n')
|
||||||
|
|
||||||
let currentEvent: SSEMessage = {}
|
let currentEvent: SSEMessage = {}
|
||||||
|
let dataContent = ''
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('event:')) {
|
||||||
|
if (currentEvent.event && dataContent) {
|
||||||
|
currentEvent.data = parseDataContent(dataContent)
|
||||||
|
events.push(currentEvent)
|
||||||
|
}
|
||||||
|
currentEvent = { event: line.substring(6).trim() }
|
||||||
|
dataContent = ''
|
||||||
|
} else if (line.startsWith('data:')) {
|
||||||
|
if (dataContent) dataContent += '\n'
|
||||||
|
dataContent += line.substring(5).trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (currentEvent.event && dataContent) {
|
||||||
|
currentEvent.data = parseDataContent(dataContent)
|
||||||
|
console.log('currentEvent', currentEvent)
|
||||||
|
events.push(currentEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDataContent(dataContent: string): string | object {
|
||||||
try {
|
try {
|
||||||
for (const line of lines) {
|
// 第一层解码:HTML实体
|
||||||
if (line.startsWith('event:')) {
|
let unescaped = dataContent
|
||||||
if (Object.keys(currentEvent).length > 0) {
|
.replace(/"/g, '"')
|
||||||
events.push(currentEvent)
|
.replace(/&/g, '&')
|
||||||
currentEvent = {}
|
.replace(/</g, '<')
|
||||||
}
|
.replace(/>/g, '>')
|
||||||
currentEvent.event = line.substring(6).trim()
|
.replace(/'/g, "'")
|
||||||
} else if (line.startsWith('data:')) {
|
|
||||||
const dataStr = line.substring(5).trim()
|
// 解析第一层JSON
|
||||||
if (dataStr) {
|
const firstParse = JSON.parse(unescaped)
|
||||||
try {
|
|
||||||
// 尝试解析为 JSON
|
// 如果data字段是字符串且包含JSON,解析data层但保持chunk为字符串
|
||||||
currentEvent.data = JSON.parse(dataStr)
|
if (firstParse.data && typeof firstParse.data === 'string' && firstParse.data.includes("{")) {
|
||||||
} catch {
|
try {
|
||||||
// JSON 解析失败时,检查是否是被转义的 JSON 字符串
|
firstParse.data = JSON.parse(firstParse.data)
|
||||||
try {
|
} catch {
|
||||||
const unescaped = dataStr.replace(/"/g, '"').replace(/&/g, '&')
|
// 保持原字符串
|
||||||
currentEvent.data = JSON.parse(unescaped)
|
|
||||||
} catch {
|
|
||||||
// 如果仍然失败,保存为原始字符串
|
|
||||||
currentEvent.data = dataStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(currentEvent).length > 0) {
|
return firstParse
|
||||||
events.push(currentEvent)
|
} catch {
|
||||||
}
|
return dataContent
|
||||||
|
|
||||||
return events
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Parse stream error:', error)
|
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,16 +94,30 @@ export const handleSSE = async (url: string, data: any, onMessage?: (data: SSEMe
|
|||||||
|
|
||||||
const reader = response.body.getReader();
|
const reader = response.body.getReader();
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
|
let buffer = ''; // 添加缓冲区来处理不完整的消息
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
|
|
||||||
const chunk = decoder.decode(value, { stream: true });
|
const chunk = decoder.decode(value, { stream: true });
|
||||||
if (onMessage) {
|
buffer += chunk;
|
||||||
onMessage(parseSSEToJSON(chunk) ?? {});
|
|
||||||
|
// 处理完整的事件
|
||||||
|
const events = buffer.split('\n\n');
|
||||||
|
buffer = events.pop() || ''; // 保留最后一个可能不完整的事件
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
if (event.trim() && onMessage) {
|
||||||
|
onMessage(parseSSEToJSON(event) ?? {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理剩余的缓冲区内容
|
||||||
|
if (buffer.trim() && onMessage) {
|
||||||
|
onMessage(parseSSEToJSON(buffer) ?? {});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
|||||||
const [chatList, setChatList] = useState<ChatItem[]>([])
|
const [chatList, setChatList] = useState<ChatItem[]>([])
|
||||||
const [variables, setVariables] = useState<StartVariableItem[]>([])
|
const [variables, setVariables] = useState<StartVariableItem[]>([])
|
||||||
const [streamLoading, setStreamLoading] = useState(false)
|
const [streamLoading, setStreamLoading] = useState(false)
|
||||||
|
const [conversationId, setConversationId] = useState<string | null>(null)
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -100,7 +101,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
|||||||
setStreamLoading(false)
|
setStreamLoading(false)
|
||||||
|
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
const { chunk } = item.data as { chunk: string; };
|
const { chunk, conversation_id } = item.data as { chunk: string; conversation_id: string | null; };
|
||||||
|
|
||||||
switch(item.event) {
|
switch(item.event) {
|
||||||
case 'message':
|
case 'message':
|
||||||
@@ -131,6 +132,10 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
|||||||
setStreamLoading(false)
|
setStreamLoading(false)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conversation_id && conversationId !== conversation_id) {
|
||||||
|
setConversationId(conversation_id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +143,8 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
|||||||
draftRun(appId, {
|
draftRun(appId, {
|
||||||
message: message,
|
message: message,
|
||||||
variables: params,
|
variables: params,
|
||||||
stream: true
|
stream: true,
|
||||||
|
conversation_id: conversationId
|
||||||
}, handleStreamMessage)
|
}, handleStreamMessage)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user