From 8d4f4a093b870cab90af273dc7ea07de6250a5ae Mon Sep 17 00:00:00 2001 From: buua436 Date: Wed, 24 Jun 2026 11:51:24 +0800 Subject: [PATCH] fix: restore dataflow defaults and SSE response (#16290) --- web/src/hooks/use-send-message.ts | 128 ++++++++++++++++-------------- web/src/pages/agent/empty-dsl.ts | 18 ++--- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/web/src/hooks/use-send-message.ts b/web/src/hooks/use-send-message.ts index c3c4f95243..ab8cd02778 100644 --- a/web/src/hooks/use-send-message.ts +++ b/web/src/hooks/use-send-message.ts @@ -123,6 +123,11 @@ export const useSendMessageBySSE = (url: string) => { body: JSON.stringify(body), signal: controller?.signal || sseRef.current?.signal, }); + const responseDataPromise = response + .clone() + .json() + .then((data: ResponseType) => data) + .catch(() => undefined); if (!response.ok) { let errorMessage = response.statusText || 'Request failed'; try { @@ -135,8 +140,6 @@ export const useSendMessageBySSE = (url: string) => { } catch { // Non-JSON error body; fall back to the HTTP status text. } - setDone(true); - resetAnswerList(); return { response, data: { @@ -152,76 +155,81 @@ export const useSendMessageBySSE = (url: string) => { ?.pipeThrough(new TextDecoderStream()) .pipeThrough(new EventSourceParserStream()) .getReader(); + let lastEventData: ResponseType | undefined; - // eslint-disable-next-line no-constant-condition - while (true) { - try { + try { + // eslint-disable-next-line no-constant-condition + while (true) { const x = await reader?.read(); - if (x) { - const { done, value } = x; - if (done) { - console.info('done'); - resetAnswerList(); - break; - } - try { - const raw = (value?.data ?? '').trim(); - // SSE end-of-stream sentinel — no payload, skip without - // surfacing a JSON.parse error to the console. - if (!raw) { - continue; - } - // Some upstreams double-wrap the body in a `data:` prefix; - // strip one layer so JSON.parse sees a real object. - const payload = raw.startsWith('data:') - ? raw.slice(5).trimStart() - : raw; - // Check the sentinel after prefix stripping so a - // `data: [DONE]` payload is caught and the stream - // loop is terminated. - if (payload === '[DONE]') { - break; - } - const val = JSON.parse(payload); - - console.info('data:', val); - if (typeof val?.code === 'number' && val.code !== 0) { - message.error(val.message); - } - - setAnswerList((list) => { - const nextList = [...list]; - nextList.push(val); - return nextList; - }); - } catch (e) { - console.warn(e); - } - } - } catch (e) { - if (e instanceof DOMException && e.name === 'AbortError') { - console.log('Request was aborted by user or logic.'); + if (!x) { break; } + const { done, value } = x; + if (done) { + break; + } + + try { + const raw = (value?.data ?? '').trim(); + // SSE end-of-stream sentinel — no payload, skip without + // surfacing a JSON.parse error to the console. + if (!raw) { + continue; + } + // Some upstreams double-wrap the body in a `data:` prefix; + // strip one layer so JSON.parse sees a real object. + const payload = raw.startsWith('data:') + ? raw.slice(5).trimStart() + : raw; + // Check the sentinel after prefix stripping so a + // `data: [DONE]` payload is caught and the stream + // loop is terminated. + if (payload === '[DONE]') { + break; + } + const val = JSON.parse(payload); + + if (typeof val?.code === 'number' && val.code !== 0) { + message.error(val.message); + } + lastEventData = val as ResponseType; + + setAnswerList((list) => { + const nextList = [...list]; + nextList.push(val); + return nextList; + }); + } catch (e) { + console.warn(e); + } + } + } catch (e) { + if (e instanceof DOMException && e.name === 'AbortError') { + console.log('Request was aborted by user or logic.'); + } else { + throw e; } } - console.info('done?'); - setDone(true); - resetAnswerList(); + + const responseData = await responseDataPromise; return { response, - data: { - code: 0, - data: true, - message: 'success', - status: response.status, - }, + data: + responseData ?? + lastEventData ?? + ({ + code: 0, + data: true, + message: 'success', + status: response.status, + } as ResponseType), }; } catch (e) { + console.warn(e); + return undefined; + } finally { setDone(true); resetAnswerList(); - - console.warn(e); } }, [initializeSseRef, url, resetAnswerList], diff --git a/web/src/pages/agent/empty-dsl.ts b/web/src/pages/agent/empty-dsl.ts index 821c3c40c3..c9ecee4fd3 100644 --- a/web/src/pages/agent/empty-dsl.ts +++ b/web/src/pages/agent/empty-dsl.ts @@ -1,14 +1,6 @@ -const FILE_ID = 'File'; +import { FileId, initialParserValues } from '@/pages/agent/constant/pipeline'; + const FILE_OPERATOR = 'File'; -const INITIAL_PARSER_VALUES = { - outputs: { - markdown: { type: 'string', value: '' }, - text: { type: 'string', value: '' }, - html: { type: 'string', value: '' }, - json: { type: 'Array', value: [] }, - }, - setups: [], -}; // Dataflow seed DSL. Kept separate from UI hooks so pure DSL helpers // can import it without pulling React modules into tests/runtime. @@ -16,7 +8,7 @@ export const DataflowEmptyDsl = { graph: { nodes: [ { - id: FILE_ID, + id: FileId, type: 'beginNode', position: { x: 50, @@ -31,7 +23,7 @@ export const DataflowEmptyDsl = { }, { data: { - form: INITIAL_PARSER_VALUES, + form: initialParserValues, label: 'Parser', name: 'Parser_0', }, @@ -54,7 +46,7 @@ export const DataflowEmptyDsl = { edges: [ { id: 'xy-edge__Filestart-Parser:HipSignsRhymeend', - source: FILE_ID, + source: FileId, sourceHandle: 'start', target: 'Parser:HipSignsRhyme', targetHandle: 'end',