From bfc98efc9d050909b7c5f619a1b8402571cc8990 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Tue, 7 Apr 2026 11:17:14 +0800 Subject: [PATCH 01/54] fix(web): file add size --- web/package.json | 1 + web/src/components/Chat/ChatInput.tsx | 101 +------------- web/src/components/Chat/FileList.tsx | 114 ++++++++++++++++ web/src/components/CodeMirrorEditor/index.tsx | 20 ++- .../Conversation/components/FileUpload.tsx | 5 +- .../AddChatVariable/ChatVariableModal.tsx | 123 ++++++++++-------- .../components/Properties/CaseList/index.tsx | 2 +- .../components/Properties/RadioGroupBtn.tsx | 3 +- web/vite.config.ts | 13 +- 9 files changed, 213 insertions(+), 169 deletions(-) create mode 100644 web/src/components/Chat/FileList.tsx diff --git a/web/package.json b/web/package.json index 0284f397..b41ab9b5 100644 --- a/web/package.json +++ b/web/package.json @@ -16,6 +16,7 @@ "@codemirror/lang-cpp": "^6.0.3", "@codemirror/lang-java": "^6.0.2", "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-json": "^6.0.2", "@codemirror/lang-python": "^6.2.1", "@codemirror/lang-rust": "^6.0.2", "@codemirror/state": "^6.5.4", diff --git a/web/src/components/Chat/ChatInput.tsx b/web/src/components/Chat/ChatInput.tsx index 6495ff06..046d133d 100644 --- a/web/src/components/Chat/ChatInput.tsx +++ b/web/src/components/Chat/ChatInput.tsx @@ -5,10 +5,11 @@ * @Last Modified time: 2026-03-23 17:46:25 */ import { type FC, useEffect, useMemo, useState } from 'react' -import { Flex, Input, Spin } from 'antd' +import { Flex, Input } from 'antd' import clsx from 'clsx' import type { ChatInputProps } from './types' +import FileList from './FileList' /** * Chat Input Component @@ -52,6 +53,7 @@ const ChatInput: FC = ({ })) || [] }, [fileList]) + const handleSend = () => { if (loading || !inputValue || inputValue.trim() === '') return onSend(inputValue) @@ -64,100 +66,9 @@ const ChatInput: FC = ({ - {previewFileList.length > 0 &&
- - {previewFileList.map((file) => { - if (file.type?.includes('image')) { - return ( - -
- {file.name} -
handleDelete(file)} - >
-
-
- ) - } - if (file.type?.includes('video')) { - return ( - -
-
-
- ) - } - if (file.type?.includes('audio')) { - return ( - -
-
-
- ) - } - return ( - - -
-
-
{file.name}
-
{file.type?.split('/')[file.type?.split('/').length - 1]} · {file.size}
-
-
handleDelete(file)} - >
-
-
- ) - })} -
-
} +
+ +
{/* Message input area */} void; + wrap?: FlexProps['wrap']; + className?: string; +} + +const FileList: FC = ({ fileList, onDelete, wrap, + className = "rb:mx-3! rb:mt-3! rb:w-max!" + }) => { + if (!fileList.length) return null + + return ( + + {fileList.map((file) => { + if (file.type?.includes('image')) { + return ( + +
+ {file.name} + {onDelete &&
onDelete(file)} + >
} +
+
+ ) + } + if (file.type?.includes('video')) { + return ( + +
+
+
+ ) + } + if (file.type?.includes('audio')) { + return ( + +
+
+
+ ) + } + return ( + + +
+
+
{file.name}
+
{[file.type?.split('/').pop(), file.size].filter(item => item).join(' · ')}
+
+ {onDelete &&
onDelete(file)} + >
} +
+
+ ) + })} +
+ ) +} + +export default FileList diff --git a/web/src/components/CodeMirrorEditor/index.tsx b/web/src/components/CodeMirrorEditor/index.tsx index e100b75b..ec2a6780 100644 --- a/web/src/components/CodeMirrorEditor/index.tsx +++ b/web/src/components/CodeMirrorEditor/index.tsx @@ -6,12 +6,14 @@ */ import { useEffect, useRef, useMemo } from 'react'; import { EditorView, basicSetup } from 'codemirror'; +import { placeholder as cmPlaceholder } from '@codemirror/view'; import { EditorState } from '@codemirror/state'; import { python } from '@codemirror/lang-python'; import { javascript } from '@codemirror/lang-javascript'; import { java } from '@codemirror/lang-java'; import { cpp } from '@codemirror/lang-cpp'; import { rust } from '@codemirror/lang-rust'; +import { json } from '@codemirror/lang-json'; import { oneDark } from '@codemirror/theme-one-dark'; /** @@ -26,12 +28,14 @@ import { oneDark } from '@codemirror/theme-one-dark'; */ interface CodeMirrorEditorProps { value?: string; - language?: 'python' | 'python3' | 'javascript' | 'typescript' | 'java' | 'cpp' | 'c' | 'rust'; + language?: 'python' | 'python3' | 'javascript' | 'typescript' | 'java' | 'cpp' | 'c' | 'rust' | 'json'; onChange?: (value: string) => void; theme?: 'light' | 'dark'; readOnly?: boolean; height?: string; size?: 'default' | 'small'; + placeholder?: string; + variant?: 'outlined' | 'borderless'; } /** @@ -47,6 +51,7 @@ const languageExtensions: Record = { cpp: cpp(), c: cpp(), rust: rust(), + json: json(), }; /** @@ -61,6 +66,8 @@ const CodeMirrorEditor = ({ theme = 'light', readOnly = false, size, + placeholder, + variant = 'borderless', }: CodeMirrorEditorProps) => { // Reference to the DOM element that will contain the editor const editorRef = useRef(null); @@ -88,6 +95,7 @@ const CodeMirrorEditor = ({ } }), EditorState.readOnly.of(readOnly), // Set read-only mode + ...(placeholder ? [cmPlaceholder(placeholder)] : []), ]; // Apply dark theme if specified @@ -111,7 +119,7 @@ const CodeMirrorEditor = ({ return () => { viewRef.current?.destroy(); }; - }, [language, theme, readOnly]); + }, [language, theme, readOnly, placeholder]); /** * Update editor content when the value prop changes externally @@ -144,7 +152,13 @@ const CodeMirrorEditor = ({ return `${size === 'small' ? 16 : 20}px` }, [size]) - return
; + return ( +
+ ); }; export default CodeMirrorEditor; diff --git a/web/src/views/Conversation/components/FileUpload.tsx b/web/src/views/Conversation/components/FileUpload.tsx index 67fff913..0cbef569 100644 --- a/web/src/views/Conversation/components/FileUpload.tsx +++ b/web/src/views/Conversation/components/FileUpload.tsx @@ -237,8 +237,11 @@ const UploadFiles = forwardRef(({ percent: 0, type: rcFile.type, originFileObj: rcFile, - thumbUrl: URL.createObjectURL(rcFile) + thumbUrl: URL.createObjectURL(rcFile), + size: rcFile.size, } + + console.log('fileVo', fileVo) onChange?.(fileVo) request.uploadFile(action, formData, requestConfig) .then(res => { diff --git a/web/src/views/Workflow/components/AddChatVariable/ChatVariableModal.tsx b/web/src/views/Workflow/components/AddChatVariable/ChatVariableModal.tsx index 18495295..69ce73bd 100644 --- a/web/src/views/Workflow/components/AddChatVariable/ChatVariableModal.tsx +++ b/web/src/views/Workflow/components/AddChatVariable/ChatVariableModal.tsx @@ -6,7 +6,7 @@ */ import { forwardRef, useImperativeHandle, useState, useRef, useMemo } from 'react'; import { Form, Input, Select, InputNumber, Button, Row, Col, Flex, Spin } from 'antd'; -import clsx from 'clsx'; +import { PlusOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import type { ChatVariableModalRef } from './types' @@ -18,9 +18,31 @@ import UploadFileListModal from '@/views/Conversation/components/UploadFileListM import type { UploadFileListModalRef } from '@/views/Conversation/types' import { getFileInfoByUrl } from '@/api/fileStorage' import { transform_file_type } from '@/views/Conversation/components/FileUpload' +import RadioGroupBtn from '../Properties/RadioGroupBtn'; +import CodeMirrorEditor from '@/components/CodeMirrorEditor'; +import FileList from '@/components/Chat/FileList' const FormItem = Form.Item; +const object_placeholder = `# example +# { +# "name": "redbear", +# "age": 2 +# }` + +const array_object_placeholder = `# example +# [ +# { +# "name": "redbear", +# "age": 2 +# }, +# { +# "name": "redbear", +# "age": 2 +# } +# ] +` + interface ChatVariableModalProps { refresh: (value: ChatVariable, editIndex?: number) => void; } @@ -233,7 +255,7 @@ const ChatVariableModal = forwardRef { - form.setFieldValue('defaultValue', undefined); + form.setFieldValue('defaultValue', value === 'array[string]' ? [] : undefined); setFileList([]); if (value === 'file' || value === 'array[file]') form.setFieldsValue(defaultFileUploadValues as any); }} @@ -244,7 +266,8 @@ const ChatVariableModal = forwardRef - {type === 'file' || type === 'array[file]' ? ( + {type?.includes('file') + ? ( <> {previewFileList.length > 0 && ( - - {previewFileList.map((file) => ( - - {file.type?.includes('image') ? ( -
- {file.name} -
handleDelete(file)} - /> -
- ) : ( - -
-
-
{file.name}
-
- {file.type?.split('/').pop()} · {file.size} -
-
-
handleDelete(file)} - /> - - )} - - ))} - + )} - ) : ( + ) + : ['array[string]', 'array[number]', 'array[boolean]'].includes(type) + ? ( + + + {(fields, { add, remove }) => ( + + {fields.map(({ key, name }) => ( + + + {type === 'array[number]' + ? + : type === 'array[boolean]' + ? + : + } + +
remove(name)} + >
+
+ ))} + +
+ )} +
+
+ ) + : ( {type === 'number' ? : type === 'boolean' - ? } diff --git a/web/src/views/Workflow/components/Properties/CaseList/index.tsx b/web/src/views/Workflow/components/Properties/CaseList/index.tsx index 88eace06..5d3d2607 100644 --- a/web/src/views/Workflow/components/Properties/CaseList/index.tsx +++ b/web/src/views/Workflow/components/Properties/CaseList/index.tsx @@ -281,7 +281,7 @@ const CaseList: FC = ({ options={options} size="small" allowClear={false} - onChange={(val) => handleLeftFieldChange(caseIndex, conditionIndex, val)} + onChange={(val) => handleLeftFieldChange(caseIndex, conditionIndex, val as string)} variant="borderless" className="rb:w-36!" /> diff --git a/web/src/views/Workflow/components/Properties/RadioGroupBtn.tsx b/web/src/views/Workflow/components/Properties/RadioGroupBtn.tsx index 0e0166ae..8c475575 100644 --- a/web/src/views/Workflow/components/Properties/RadioGroupBtn.tsx +++ b/web/src/views/Workflow/components/Properties/RadioGroupBtn.tsx @@ -63,6 +63,7 @@ const RadioGroupBtn: FC = ({ allowClear = true, block = false, type, + className, }) => { /** Listen to value changes and trigger side effects via onValueChange callback */ useEffect(() => { @@ -86,7 +87,7 @@ const RadioGroupBtn: FC = ({ } return ( -
+
{/* Render each option as a selectable card */} {options.map(option => (
Date: Tue, 7 Apr 2026 11:46:39 +0800 Subject: [PATCH 02/54] fix(web): chat file ui --- web/src/assets/images/file/audio_disabled.svg | 13 + web/src/assets/images/file/csv_disabled.svg | 18 ++ web/src/assets/images/file/excel_disabled.svg | 17 ++ web/src/assets/images/file/html_disabled.svg | 17 ++ web/src/assets/images/file/json_disabled.svg | 14 ++ web/src/assets/images/file/md_disabled.svg | 19 ++ web/src/assets/images/file/pause.svg | 16 ++ web/src/assets/images/file/pdf_disabled.svg | 20 ++ web/src/assets/images/file/play.svg | 28 +++ web/src/assets/images/file/ppt_disabled.svg | 14 ++ web/src/assets/images/file/txt_disabled.svg | 14 ++ web/src/assets/images/file/video_disabled.svg | 16 ++ web/src/assets/images/file/word_disabled.svg | 15 ++ web/src/components/Chat/FileList.tsx | 236 +++++++++++------- web/src/components/Header/index.tsx | 4 +- 15 files changed, 368 insertions(+), 93 deletions(-) create mode 100644 web/src/assets/images/file/audio_disabled.svg create mode 100644 web/src/assets/images/file/csv_disabled.svg create mode 100644 web/src/assets/images/file/excel_disabled.svg create mode 100644 web/src/assets/images/file/html_disabled.svg create mode 100644 web/src/assets/images/file/json_disabled.svg create mode 100644 web/src/assets/images/file/md_disabled.svg create mode 100644 web/src/assets/images/file/pause.svg create mode 100644 web/src/assets/images/file/pdf_disabled.svg create mode 100644 web/src/assets/images/file/play.svg create mode 100644 web/src/assets/images/file/ppt_disabled.svg create mode 100644 web/src/assets/images/file/txt_disabled.svg create mode 100644 web/src/assets/images/file/video_disabled.svg create mode 100644 web/src/assets/images/file/word_disabled.svg diff --git a/web/src/assets/images/file/audio_disabled.svg b/web/src/assets/images/file/audio_disabled.svg new file mode 100644 index 00000000..93d83a0a --- /dev/null +++ b/web/src/assets/images/file/audio_disabled.svg @@ -0,0 +1,13 @@ + + + 音乐 + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/csv_disabled.svg b/web/src/assets/images/file/csv_disabled.svg new file mode 100644 index 00000000..29add1f6 --- /dev/null +++ b/web/src/assets/images/file/csv_disabled.svg @@ -0,0 +1,18 @@ + + + 编组 57 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/excel_disabled.svg b/web/src/assets/images/file/excel_disabled.svg new file mode 100644 index 00000000..5e2136e9 --- /dev/null +++ b/web/src/assets/images/file/excel_disabled.svg @@ -0,0 +1,17 @@ + + + Excel + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/html_disabled.svg b/web/src/assets/images/file/html_disabled.svg new file mode 100644 index 00000000..fa237301 --- /dev/null +++ b/web/src/assets/images/file/html_disabled.svg @@ -0,0 +1,17 @@ + + + Word + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/json_disabled.svg b/web/src/assets/images/file/json_disabled.svg new file mode 100644 index 00000000..267e2b46 --- /dev/null +++ b/web/src/assets/images/file/json_disabled.svg @@ -0,0 +1,14 @@ + + + JSON + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/md_disabled.svg b/web/src/assets/images/file/md_disabled.svg new file mode 100644 index 00000000..8fe81fe7 --- /dev/null +++ b/web/src/assets/images/file/md_disabled.svg @@ -0,0 +1,19 @@ + + + PDF + + + + + + + + + + MD + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/pause.svg b/web/src/assets/images/file/pause.svg new file mode 100644 index 00000000..0e26ece0 --- /dev/null +++ b/web/src/assets/images/file/pause.svg @@ -0,0 +1,16 @@ + + + 播放 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/pdf_disabled.svg b/web/src/assets/images/file/pdf_disabled.svg new file mode 100644 index 00000000..950edcb8 --- /dev/null +++ b/web/src/assets/images/file/pdf_disabled.svg @@ -0,0 +1,20 @@ + + + PDF + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/play.svg b/web/src/assets/images/file/play.svg new file mode 100644 index 00000000..f2ff9cb7 --- /dev/null +++ b/web/src/assets/images/file/play.svg @@ -0,0 +1,28 @@ + + + 播放 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/ppt_disabled.svg b/web/src/assets/images/file/ppt_disabled.svg new file mode 100644 index 00000000..f3da453e --- /dev/null +++ b/web/src/assets/images/file/ppt_disabled.svg @@ -0,0 +1,14 @@ + + + file-ppt-2-fill + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/txt_disabled.svg b/web/src/assets/images/file/txt_disabled.svg new file mode 100644 index 00000000..100565ce --- /dev/null +++ b/web/src/assets/images/file/txt_disabled.svg @@ -0,0 +1,14 @@ + + + txt + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/video_disabled.svg b/web/src/assets/images/file/video_disabled.svg new file mode 100644 index 00000000..f8f71c2a --- /dev/null +++ b/web/src/assets/images/file/video_disabled.svg @@ -0,0 +1,16 @@ + + + 编组 59 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/file/word_disabled.svg b/web/src/assets/images/file/word_disabled.svg new file mode 100644 index 00000000..d4f9e6ec --- /dev/null +++ b/web/src/assets/images/file/word_disabled.svg @@ -0,0 +1,15 @@ + + + Word + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/components/Chat/FileList.tsx b/web/src/components/Chat/FileList.tsx index 3c84a59f..41fed7d8 100644 --- a/web/src/components/Chat/FileList.tsx +++ b/web/src/components/Chat/FileList.tsx @@ -1,5 +1,6 @@ -import { type FC } from 'react' +import { type FC, useRef, useState } from 'react' import { Flex, Spin } from 'antd' +import { CloseOutlined } from '@ant-design/icons' import clsx from 'clsx' import type { UploadFile, FlexProps } from 'antd' @@ -13,101 +14,154 @@ interface FileListProps { const FileList: FC = ({ fileList, onDelete, wrap, className = "rb:mx-3! rb:mt-3! rb:w-max!" }) => { + const [playingUid, setPlayingUid] = useState(null) + const mediaRef = useRef(null) + + const handleClose = () => { + mediaRef.current?.pause() + setPlayingUid(null) + } + + const playingFile = fileList.find(f => f.uid === playingUid) + if (!fileList.length) return null - return ( - - {fileList.map((file) => { - if (file.type?.includes('image')) { - return ( - -
- {file.name} - {onDelete &&
onDelete(file)} - >
} -
-
- ) - } - if (file.type?.includes('video')) { - return ( - -
-
-
- ) - } - if (file.type?.includes('audio')) { - return ( - -
-
-
- ) - } - return ( - - -
{ + console.log('getFileIconClassName file', file) + if (file.status === 'uploading') { + return file.type?.includes('audio') + ? "rb:bg-[url('@/assets/images/file/audio_disabled.svg')]" + : file.type?.includes('video') + ? "rb:bg-[url('@/assets/images/file/video_disabled.svg')]" + : file.type?.includes('pdf') + ? "rb:bg-[url('@/assets/images/file/pdf_disabled.svg')]" + : (file.type?.includes('excel') || file.type?.includes('spreadsheetml.sheet')) + ? "rb:bg-[url('@/assets/images/file/excel_disabled.svg')]" + : file.type?.includes('csv') + ? "rb:bg-[url('@/assets/images/file/csv_disabled.svg')]" + : file.type?.includes('html') + ? "rb:bg-[url('@/assets/images/file/html_disabled.svg')]" + : file.type?.includes('json') + ? "rb:bg-[url('@/assets/images/file/json_disabled.svg')]" : file.type?.includes('ppt') + ? "rb:bg-[url('@/assets/images/file/ppt_disabled.svg')]" + : file.type?.includes('markdown') + ? "rb:bg-[url('@/assets/images/file/md_disabled.svg')]" + : file.type?.includes('text') + ? "rb:bg-[url('@/assets/images/file/txt_disabled.svg')]" + : (file.type?.includes('doc') || file.type?.includes('docx') || file.type?.includes('word') || file.type?.includes('wordprocessingml.document')) + ? "rb:bg-[url('@/assets/images/file/word_disabled.svg')]" + : "rb:bg-[url('@/assets/images/file/txt_disabled.svg')]" + } + return file.type?.includes('audio') + ? "rb:bg-[url('@/assets/images/file/audio.svg')]" + : file.type?.includes('video') + ? "rb:bg-[url('@/assets/images/file/video.svg')]" + : file.type?.includes('pdf') + ? "rb:bg-[url('@/assets/images/file/pdf.svg')]" + : (file.type?.includes('excel') || file.type?.includes('spreadsheetml.sheet')) + ? "rb:bg-[url('@/assets/images/file/excel.svg')]" + : file.type?.includes('csv') + ? "rb:bg-[url('@/assets/images/file/csv.svg')]" + : file.type?.includes('html') + ? "rb:bg-[url('@/assets/images/file/html.svg')]" + : file.type?.includes('json') + ? "rb:bg-[url('@/assets/images/file/json.svg')]" + : file.type?.includes('ppt') ? "rb:bg-[url('@/assets/images/file/ppt.svg')]" - : file.type?.includes('text') - ? "rb:bg-[url('@/assets/images/file/txt.svg')]" : file.type?.includes('markdown') - ? "rb:bg-[url('@/assets/images/file/md.svg')]" - : (file.type?.includes('doc') || file.type?.includes('docx') || file.type?.includes('word') || file.type?.includes('wordprocessingml.document')) - ? "rb:bg-[url('@/assets/images/file/word.svg')]" - : null - )} - >
-
-
{file.name}
-
{[file.type?.split('/').pop(), file.size].filter(item => item).join(' · ')}
-
- {onDelete &&
onDelete(file)} - >
} -
-
- ) - })} -
+ ? "rb:bg-[url('@/assets/images/file/md.svg')]" + : file.type?.includes('text') + ? "rb:bg-[url('@/assets/images/file/txt.svg')]" + : (file.type?.includes('doc') || file.type?.includes('docx') || file.type?.includes('word') || file.type?.includes('wordprocessingml.document')) + ? "rb:bg-[url('@/assets/images/file/word.svg')]" + : "rb:bg-[url('@/assets/images/file/txt.svg')]" + } + + return ( + <> + + {fileList.map((file) => { + if (file.type?.includes('image')) { + return ( + +
+ {file.name} + {onDelete &&
onDelete(file)} + >
} +
+
+ ) + } + return ( + + +
+
+
{file.name}
+
{[file.type?.split('/').pop(), file.size].filter(item => item).join(' · ')}
+
+ {file.status === 'done' && (file.type?.includes('video') || file.type?.includes('audio')) && +
playingUid === file.uid ? handleClose() : setPlayingUid(file.uid)} + >
+ } + {onDelete &&
onDelete(file)} + >
} +
+
+ ) + })} +
+ + {playingFile && ( +
+ + {playingFile.type?.includes('video') ? ( +
+ )} + ) } diff --git a/web/src/components/Header/index.tsx b/web/src/components/Header/index.tsx index ac59fcc2..0dd51bbd 100644 --- a/web/src/components/Header/index.tsx +++ b/web/src/components/Header/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:07:49 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-05 13:43:59 + * @Last Modified time: 2026-04-03 20:21:02 */ /** * AppHeader Component @@ -180,7 +180,7 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { > - {/[\u4e00-\u9fa5]/.test(user.username) ? user.username.slice(0, 2) : user.username[0]} + {/[\u4e00-\u9fa5]/.test(user.username) ? user.username.slice(user.username.length, -2) : user.username[0]} {user.username}
Date: Tue, 7 Apr 2026 12:05:20 +0800 Subject: [PATCH 03/54] fix(web): llm/document-extractor support file type variable --- .../components/Properties/VariableSelect.tsx | 8 +++--- .../Workflow/components/Properties/index.tsx | 25 ++++++++++++++++--- web/src/views/Workflow/constant.ts | 4 +-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/web/src/views/Workflow/components/Properties/VariableSelect.tsx b/web/src/views/Workflow/components/Properties/VariableSelect.tsx index a5428a60..38c48dba 100644 --- a/web/src/views/Workflow/components/Properties/VariableSelect.tsx +++ b/web/src/views/Workflow/components/Properties/VariableSelect.tsx @@ -20,7 +20,7 @@ interface VariableSelectProps { multiple?: boolean; size?: 'small' | 'middle' | 'large'; placeholder?: string; - variant?: 'outlined' | 'borderless'; + variant?: 'outlined' | 'borderless' | 'filled'; className?: string; onChange?: (value: string | string[], option: Suggestion | Suggestion[] | undefined) => void; } @@ -190,12 +190,12 @@ const VariableSelect: FC = ({ {/* Trigger */}
setOpen(o => !o)} diff --git a/web/src/views/Workflow/components/Properties/index.tsx b/web/src/views/Workflow/components/Properties/index.tsx index b2a82318..1270a4de 100644 --- a/web/src/views/Workflow/components/Properties/index.tsx +++ b/web/src/views/Workflow/components/Properties/index.tsx @@ -38,6 +38,7 @@ import RbCard from '@/components/RbCard/Card'; import ModelConfig from './ModelConfig' import ModelSelect from '@/components/ModelSelect' import ListOperator from './ListOperator' +import type { Variable } from "./VariableList/types"; /** * Props for Properties component @@ -229,7 +230,6 @@ const Properties: FC = ({ } return filteredList; }; - if (nodeType === 'llm') { // For LLM nodes that are children of iteration or loop nodes, include parent variables const parentLoopNode = selectedNode ? (() => { @@ -790,8 +790,25 @@ const Properties: FC = ({ return nodeTypeMatch || variableNameMatch; }); } - if (config.onFilterVariableNames) { - return baseVariableList.filter(variable => Array.isArray(config.onFilterVariableNames) && config.onFilterVariableNames.includes(variable.label)); + if (config.onFilterVariableType) { + const types = config.onFilterVariableType as string[]; + let list: Suggestion[] = [] + baseVariableList.forEach((variable) => { + if (variable.children?.length) { + const filteredChildren = variable.children.filter((c: Suggestion) => types.includes(c.dataType)); + console.log('filteredChildren', filteredChildren) + if (filteredChildren.length > 0) { + list.push({ ...variable, children: filteredChildren }); + } else if (types.includes(variable.dataType)) { + list.push({ ...variable, children: [] }); + } + } else if (types.includes(variable.dataType)) { + list.push(variable); + } + }); + + console.log('list', list) + return list } // Filter child nodes for iteration output if (config.filterChildNodes && selectedNode) { @@ -812,7 +829,7 @@ const Properties: FC = ({ } return baseVariableList; })()} - onChange={(value, option) => handleChangeVariableList(value, option, key)} + onChange={(value, option) => handleChangeVariableList(value as string, option, key)} size="small" /> : config.type === 'switch' diff --git a/web/src/views/Workflow/constant.ts b/web/src/views/Workflow/constant.ts index 7909d183..9621d968 100644 --- a/web/src/views/Workflow/constant.ts +++ b/web/src/views/Workflow/constant.ts @@ -128,7 +128,7 @@ export const nodeLibrary: NodeLibrary[] = [ }, vision_input: { type: 'variableList', - onFilterVariableNames: ['sys.files'] + onFilterVariableType: ['array[file]'] } } }, @@ -457,7 +457,7 @@ export const nodeLibrary: NodeLibrary[] = [ file_selector: { type: 'variableList', placeholder: 'common.pleaseSelect', - onFilterVariableNames: ['sys.files'] + onFilterVariableType: ['array[file]', 'file'] } } }, From 072d118935aa27b18a715d21280122945d6c4b04 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Tue, 7 Apr 2026 12:08:36 +0800 Subject: [PATCH 04/54] fix(web): add file type transform --- web/src/views/Conversation/components/FileUpload.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/views/Conversation/components/FileUpload.tsx b/web/src/views/Conversation/components/FileUpload.tsx index 0cbef569..c8715fd2 100644 --- a/web/src/views/Conversation/components/FileUpload.tsx +++ b/web/src/views/Conversation/components/FileUpload.tsx @@ -235,7 +235,7 @@ const UploadFiles = forwardRef(({ name: rcFile.name, status: 'uploading' as UploadFileStatus, percent: 0, - type: rcFile.type, + type: (rcFile.type && transform_file_type[rcFile.type as keyof typeof transform_file_type]) || rcFile.type || 'document', originFileObj: rcFile, thumbUrl: URL.createObjectURL(rcFile), size: rcFile.size, From 30b512e5549f58eb6f52aebc33e7918f41b224ae Mon Sep 17 00:00:00 2001 From: zhaoying Date: Tue, 7 Apr 2026 12:22:31 +0800 Subject: [PATCH 05/54] fix(web): DebounceSelect support page load --- web/src/components/DebounceSelect/index.tsx | 98 +++++++++++++-------- web/src/components/Header/index.tsx | 4 +- 2 files changed, 65 insertions(+), 37 deletions(-) diff --git a/web/src/components/DebounceSelect/index.tsx b/web/src/components/DebounceSelect/index.tsx index ab8379ad..9121b30d 100644 --- a/web/src/components/DebounceSelect/index.tsx +++ b/web/src/components/DebounceSelect/index.tsx @@ -10,6 +10,7 @@ interface OptionType { interface ApiResponse { items?: T[]; + page: { hasnext: boolean }; } export interface DebounceSelectProps extends Omit { @@ -23,8 +24,9 @@ export interface DebounceSelectProps extends Omit { labelKey?: string; /** Key name sent to the API for the search keyword */ searchKey?: string; + pageSize?: number; /** Custom fetch function — mutually exclusive with url */ - fetchOptions?: (search: string | null) => Promise; + fetchOptions?: (search: string | null, page: number) => Promise<{ options: DefaultOptionType[]; hasMore: boolean }>; /** Transform raw API items before rendering */ format?: (items: OptionType[]) => OptionType[]; debounceTimeout?: number; @@ -32,10 +34,11 @@ export interface DebounceSelectProps extends Omit { const DebounceSelect: FC = ({ url, - params = { page: 1, pagesize: 20 }, + params = {}, valueKey = 'value', labelKey = 'label', searchKey = 'search', + pageSize = 20, fetchOptions, format, debounceTimeout = 300, @@ -43,56 +46,81 @@ const DebounceSelect: FC = ({ }) => { const [fetching, setFetching] = useState(false); const [options, setOptions] = useState([]); + const [hasMore, setHasMore] = useState(true); + const pageRef = useRef(1); + const keywordRef = useRef(null); const fetchRef = useRef(0); - const timerRef = useRef>(); - // Load initial options on mount - useEffect(() => { - debounceFetcher(null); - }, []); + const fetchPage = useCallback((keyword: string | null, page: number, replace: boolean) => { + fetchRef.current += 1; + const fetchId = fetchRef.current; + setFetching(true); + + const promise = fetchOptions + ? fetchOptions(keyword, page) + : request + .get>(url!, { ...params, [searchKey]: keyword, page, pagesize: pageSize }) + .then((res) => { + const data: OptionType[] = Array.isArray(res) ? res : res?.items || []; + const formatted = format + ? format(data) + : data.map((item) => ({ label: item[labelKey], value: item[valueKey], avatar: item.avatar, raw: item })); + + console.log('more', res.page?.hasnext) + return { options: formatted, hasMore: res.page?.hasnext }; + }); + + promise + .then(({ options: newOptions, hasMore: more }) => { + if (fetchId !== fetchRef.current) return; + setOptions((prev) => (replace ? newOptions : [...prev, ...newOptions])); + setHasMore(more); + setFetching(false); + }) + .catch(() => setFetching(false)); + }, [url, params, searchKey, fetchOptions, format, valueKey, labelKey, pageSize]); const debounceFetcher = useCallback((keyword: string | null) => { clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { - fetchRef.current += 1; - const fetchId = fetchRef.current; - setOptions([]); - setFetching(true); - - const promise: Promise = fetchOptions - ? fetchOptions(keyword) - : request - .get>(url!, { ...params, [searchKey]: keyword }) - .then((res) => { - const data: OptionType[] = Array.isArray(res) ? res : res?.items || []; - const formatted = format ? format(data) : data.map((item) => ({ - label: item[labelKey], - value: item[valueKey], - avatar: item.avatar, - raw: item, - })); - return formatted; - }); - - promise - .then((newOptions) => { - if (fetchId !== fetchRef.current) return; - setOptions(newOptions); - setFetching(false); - }) - .catch(() => setFetching(false)); + keywordRef.current = keyword; + pageRef.current = 1; + fetchPage(keyword, 1, true); }, debounceTimeout); - }, [url, params, searchKey, fetchOptions, format, valueKey, labelKey, debounceTimeout]); + }, [fetchPage, debounceTimeout]); + + useEffect(() => { + debounceFetcher(null); + }, []); + + const handlePopupScroll = useCallback((e: React.UIEvent) => { + const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; + if (!fetching && hasMore && scrollHeight - scrollTop - clientHeight < 50) { + const nextPage = pageRef.current + 1; + pageRef.current = nextPage; + fetchPage(keywordRef.current, nextPage, false); + } + }, [fetching, hasMore, fetchPage]); return ( { if (props.name) ctx?.setValue(props.name, val) }} /> + return handleChange(e.target.value)} /> + return handleChange(e.target.value)} /> } }, select: ({ children, ...props }: any) => { diff --git a/web/src/views/ApplicationConfig/TestChat/index.tsx b/web/src/views/ApplicationConfig/TestChat/index.tsx index 563fb2d4..bfb9b569 100644 --- a/web/src/views/ApplicationConfig/TestChat/index.tsx +++ b/web/src/views/ApplicationConfig/TestChat/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-03-13 17:27:52 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-07 21:31:19 + * @Last Modified time: 2026-04-07 21:48:30 */ import { type FC, useState, useRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' @@ -95,7 +95,7 @@ const TestChat: FC = ({ useEffect(() => { getVariables() - }, [application, config]) + }, [application, JSON.stringify(config)]) useEffect(() => { return () => { From e9972834fe3f6e249ea9ccee3146d9dc5cc96989 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Tue, 7 Apr 2026 22:18:33 +0800 Subject: [PATCH 25/54] fix(web): markdown remeber history form --- web/src/components/Markdown/index.tsx | 32 ++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/web/src/components/Markdown/index.tsx b/web/src/components/Markdown/index.tsx index 4f48e655..bd33cf76 100644 --- a/web/src/components/Markdown/index.tsx +++ b/web/src/components/Markdown/index.tsx @@ -22,7 +22,7 @@ * @component */ -import { useState, useRef, useEffect, type FC, createContext, useContext, useCallback } from 'react' +import { useState, useRef, useEffect, type FC, createContext, useContext, useCallback, useMemo } from 'react' import { Image, Input, Select, Form, Checkbox, Radio, ColorPicker, DatePicker, TimePicker, InputNumber, Slider } from 'antd' import ReactMarkdown from 'react-markdown' import RemarkGfm from 'remark-gfm' @@ -44,6 +44,11 @@ const FormContext = createContext<{ onSubmit?: (values: Record) => void; } | null>(null) +/** Stable form wrapper component — state lives in RbMarkdown, survives components object rebuilds */ +const RbForm: FC = ({ children, ...props }) => ( +
{children}
+) + /** Props interface for RbMarkdown component */ interface RbMarkdownProps { /** Markdown content to render */ @@ -60,8 +65,8 @@ interface RbMarkdownProps { onFormSubmit?: (values: Record) => void; } -/** Build components with onFormSubmit callback */ -const buildComponents = (onFormSubmit?: (values: Record) => void) => ({ +/** Build stable components map — form submission handled via FormContext */ +const buildComponents = () => ({ h1: ({ children, ...props }: any) =>

{children}

, h2: ({ children, ...props }: any) =>

{children}

, h3: ({ children, ...props }: any) =>

{children}

, @@ -95,7 +100,7 @@ const buildComponents = (onFormSubmit?: (values: Record) => void) = td: ({ children, ...props }: any) => {children}, button: ({ children, ...props }: any) => { const ctx = useContext(FormContext) - return ctx?.onSubmit?.(ctx.values)}>{[children]} + return ctx?.onSubmit?.(ctx?.values ?? {})}>{[children]} }, input: ({ children, value, ...props }: any) => { const ctx = useContext(FormContext) @@ -125,7 +130,7 @@ const buildComponents = (onFormSubmit?: (values: Record) => void) = return case 'submit': case 'button': - return ctx?.onSubmit?.(ctx.values)}>{[props.value || children]} + return ctx?.onSubmit?.(ctx?.values ?? {})}>{[props.value || children]} case 'checkbox': return handleChange(e.target.checked)}>{children} case 'password': @@ -149,15 +154,7 @@ const buildComponents = (onFormSubmit?: (values: Record) => void) = const ctx = useContext(FormContext) return { if (props.name) ctx?.setValue(props.name, e.target.value) }}>{children} }, - form: ({ children, ...props }: any) => { - const [values, setValues] = useState>({}) - const setValue = useCallback((name: string, value: any) => setValues(prev => ({ ...prev, [name]: value })), []) - return ( - -
{children}
-
- ) - }, + form: RbForm, label: ({ children, ...props }: any) => { return }, @@ -172,7 +169,10 @@ const RbMarkdown: FC = ({ className, onFormSubmit, }) => { - const components = buildComponents(onFormSubmit) + const [formValues, setFormValues] = useState>({}) + const setValue = useCallback((name: string, value: any) => setFormValues(prev => ({ ...prev, [name]: value })), []) + const formCtx = useMemo(() => ({ values: formValues, setValue, onSubmit: onFormSubmit }), [formValues, setValue, onFormSubmit]) + const components = useMemo(() => buildComponents(), []) const [editContent, setEditContent] = useState(content) const textareaRef = useRef(null) @@ -239,6 +239,7 @@ const RbMarkdown: FC = ({ /** Render markdown preview mode */ return ( +