From 6754834eb379a6960cca25b381af91c9b239cc2c Mon Sep 17 00:00:00 2001 From: zhaoying Date: Mon, 2 Feb 2026 11:40:19 +0800 Subject: [PATCH 1/9] fix(web): Restructure the CustomSelect component, repair the interface that is called multiple times when the form is updated --- web/src/components/CustomSelect/index.tsx | 7 ++++--- .../views/ApplicationConfig/components/AiPromptModal.tsx | 4 ++-- .../ApplicationConfig/components/ModelConfigModal.tsx | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/web/src/components/CustomSelect/index.tsx b/web/src/components/CustomSelect/index.tsx index 1887d635..f93014c9 100644 --- a/web/src/components/CustomSelect/index.tsx +++ b/web/src/components/CustomSelect/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, type FC, type Key } from 'react'; +import { useEffect, useState, useMemo, type FC, type Key } from 'react'; import { Select } from 'antd'; import type { SelectProps, DefaultOptionType } from 'antd/es/select'; import { useTranslation } from 'react-i18next'; @@ -47,13 +47,14 @@ const CustomSelect: FC = ({ }) => { const { t } = useTranslation(); const [options, setOptions] = useState([]); + const memoizedParams = useMemo(() => params, [JSON.stringify(params)]); useEffect(() => { - request.get>(url, params).then((res) => { + request.get>(url, memoizedParams).then((res) => { const data = Array.isArray(res) ? res : res?.items || []; setOptions(data); }); - }, [url, params]); + }, [url, memoizedParams]); const displayOptions = format ? format(options) : options; diff --git a/web/src/views/ApplicationConfig/components/AiPromptModal.tsx b/web/src/views/ApplicationConfig/components/AiPromptModal.tsx index 0c7bf480..198460eb 100644 --- a/web/src/views/ApplicationConfig/components/AiPromptModal.tsx +++ b/web/src/views/ApplicationConfig/components/AiPromptModal.tsx @@ -8,7 +8,7 @@ import { updatePromptMessages, createPromptSessions } from '@/api/prompt' import { getModelListUrl } from '@/api/models' import type { AiPromptModalRef, AiPromptVariableModalRef, AiPromptForm } from '../types' import RbModal from '@/components/RbModal' -import type { Model } from '@/views/ModelManagement/types' +import type { ModelListItem } from '@/views/ModelManagement/types' import ChatContent from '@/components/Chat/ChatContent' import Empty from '@/components/Empty' import ChatSendIcon from '@/assets/images/application/chatSend.svg' @@ -21,7 +21,7 @@ import Editor from './Editor' interface AiPromptModalProps { refresh: (value: string) => void; - defaultModel: Model | null; + defaultModel: ModelListItem | null; } const AiPromptModal = forwardRef(({ diff --git a/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx b/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx index 67fd654c..c3ffd83e 100644 --- a/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx +++ b/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx @@ -3,14 +3,14 @@ import { Form, Select } from 'antd'; import { useTranslation } from 'react-i18next'; import type { ModelConfig, ModelConfigModalRef, Config, Source } from '../types' -import type { Model } from '@/views/ModelManagement/types' +import type { ModelListItem } from '@/views/ModelManagement/types' import RbModal from '@/components/RbModal' import RbSlider from '@/components/RbSlider' const FormItem = Form.Item; interface ModelConfigModalProps { - modelList?: Model[]; + modelList?: ModelListItem[]; refresh: (values: ModelConfig, type: Source) => void; data: Config; } @@ -76,9 +76,9 @@ const ModelConfigModal = forwardRef( console.log('err', err) }); } - const handleChange = (_value: string, option: Model | Model[] | undefined) => { + const handleChange = (_value: string, option: ModelListItem | ModelListItem[] | undefined) => { if (source === 'chat') { - form.setFieldValue('label', (option as Model).name) + form.setFieldValue('label', (option as ModelListItem).name) } } From d62746fc8c3c0f70891cceef7c12a4eeff447b71 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Mon, 2 Feb 2026 10:39:04 +0800 Subject: [PATCH 2/9] feat(web): RadioGroupCard support block mode --- web/src/components/RadioGroupCard/index.tsx | 39 +++++++++++++-------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/web/src/components/RadioGroupCard/index.tsx b/web/src/components/RadioGroupCard/index.tsx index aa68852c..9e42f7cd 100644 --- a/web/src/components/RadioGroupCard/index.tsx +++ b/web/src/components/RadioGroupCard/index.tsx @@ -16,6 +16,7 @@ interface RadioCardProps extends Omit { onChange?: (value: string | null | undefined, option?: RadioCardOption) => void; itemRender?: (option: RadioCardOption) => ReactNode; allowClear?: boolean; + block?: boolean; } const RadioGroupCard: FC = ({ @@ -24,7 +25,8 @@ const RadioGroupCard: FC = ({ onValueChange, onChange, itemRender, - allowClear = true + allowClear = true, + block = false, }) => { // 监听value变化 useEffect(() => { @@ -45,23 +47,30 @@ const RadioGroupCard: FC = ({ } return ( -
+
{options.map(option => ( -
handleChange(option)}> - {itemRender ? itemRender(option) : ( - <> - {option.icon && } +
handleChange(option)}> + {itemRender ? itemRender(option) : ( + <> + {option.icon && } +
{option.label}
{option.labelDesc}
- - )} -
- ) - )} +
+ + )} +
+ ))}
); }; From 15a254c0cd52fffe5c392e196b8e691940723e6d Mon Sep 17 00:00:00 2001 From: zhaoying Date: Mon, 2 Feb 2026 10:45:44 +0800 Subject: [PATCH 3/9] feat(web): create space add icon --- web/src/assets/images/space/neo4j.png | Bin 0 -> 1424 bytes web/src/assets/images/space/rag.png | Bin 0 -> 1719 bytes web/src/i18n/en.ts | 9 +- web/src/i18n/zh.ts | 7 +- .../SpaceManagement/components/SpaceModal.tsx | 197 +++++++++++------- web/src/views/SpaceManagement/index.tsx | 11 +- web/src/views/SpaceManagement/types.ts | 9 +- 7 files changed, 146 insertions(+), 87 deletions(-) create mode 100644 web/src/assets/images/space/neo4j.png create mode 100644 web/src/assets/images/space/rag.png diff --git a/web/src/assets/images/space/neo4j.png b/web/src/assets/images/space/neo4j.png new file mode 100644 index 0000000000000000000000000000000000000000..74fc7a861762c7e7dad4f9d3491b139c8f0583ed GIT binary patch literal 1424 zcmYk*c{tR090%|p6SFB)%aV?LSSeGnt?jNyHoA5b@>JS373o=IT-^xCksP@b#x>3y zVGzwUh6cIM+|4Lgga$*C>AllG`}91o&-ZgYpZ`A3H^9XBu)Lg_96=EBM~@hqNwUee z_<f`l_@ zE}*$0@fwn@Bk2Z`C2k_+7E*2_^)~2tKz9S(9cdm&zl#hnWO^g(9iIP zf8+)rFA(_;@hk`hAt($*Q5cHC!H|eRaU@EjP!f%@Sd=|Mc^sGtVA8-!1S<(IC6d8T z0Xr2Hba2wZNk>&CUS)xs4enFaF@^jPMDwaVi` z8UFKL^HP+Xn~h#5Dvi5q6!U`4*w#(u%^eKyvi`B(^#*J1@kZ*-u}1p@Z7Un|j&|9D z%WAE*ESIsf%ToQBbs&Zk?8v_IE=#;J?1BHwo`M&9!h*{Rj2iPc%9!dmoujP^$vAIm zb#0yYgR)ka+3 z`!I_gzb$j?nNnW9E!u;jxr|^Z*4;n8&S}Jfdrn7H=hh~B*H+^oQcaxQ^_u6NJr5NI zHE0uYtuFeveJWD<>J3AkPb_`iN;awLs=nzfNmtXF-pS(17~Hly=gdji?JpQrktv_s zU~@@WpCRM6RjyT;U>lO7YtFLce%-Z%sFZiR)FZ7ykxP?eCz+gD%)i;j*&Mfoa5!Bp z>tUg?V4`HdMcR|8Bs^a6Q@K&ac7^%2&zwg7eJ#03p3tzDk(^uq_u-Mo>r$Ybl~9AcOB?s`iS1%1e*E)P6N(wVq+o)xOf+ku^RF z%H;f!LgU2`$6|9XJBRSJU)|e(x5Pq++MI4>uH6+e^!nmtnI9>ZMBX*kaY&b}M`o2c zon)%jdY&ENh}YW-7Cu}cr*zJ_&o4Sx_7^bJo9rLSPIV{n8AD;b>t8|^#ffW=D3-d< zSVt^dH$Z>D{uMQ`^mLsqs~=%(GnnyeOz zY=F??`idJzJd0+v6@zO$6?)f&oAJngG^22-m%H~>Wh`F^o=>AYd{$6oMM&Sdm2WBc z&_!`ubg1-L>AHOY{-GrX)O71!-$ox{le({9TcE%oRA3q@xEL#Pq6xfGMUOHCNx7nDg`$d5Q43R~ euhK3SeyOwlJ*XRqsf+tT>D9jpq zLKZG$!8{8VuVDEKR0b#UIdRK{8Ego#qcb_wGvz}#f?(jB$UCc3{=8fc)x|uTihzgzv#G4$K49}R^VPG z{Howzg@9@V)ga^@LJ76_cP;MM;Xxh3>JeU#hz2}tz@tVyZbW1gqM8xif|wROZ3V3r zv8{+}Lqa>AwIi_ue{|r*2mINIq)sGvA*Bncgl?pDBfT3hdyvtK%wD|e!|Oi0=|^@y zay}w|0EL4n8ba|9N=H!j3AN*>8%M(g8Yj^-iRMYPd`2r_3h$@TMwrHjFX);<_gD0M zMK1$=4D=Ib@o^RdggFe(VTdq~;dzV@7BISizZUR`i7_U|7csen&r6tE#`H42tYBsZ zUs+(VFuRJmRm>CESYU%mSi|BPmcC*68&=lATE{8}Y!1F{V4VvN7aN34a5u3@!1qwH zreqSgB7V!<%!0IKtIU2)!w_0*RpY?K;?nX;_aGmQMB>*VpFL%1Q~M^Kp(1%fvid=b zzP1UOTqv1LH*QL2^Cht@_EuQS8(fl-no0Xz+{t2ZzD-009Q+gu4%80o7py)%&mK15 z4EedY8%z%ugnXg}{rOvNn0lvSc!Yqkppe*9Yw`ypYK^$;TaRs;T6(rCCdr|ER;|K2 zxUpXK%D-z!9ck*SA7xR;e8XHG2ahSM2{v|U+f1s~c+_@o1a*3KCn*~WvJxYWo>w=K z{%$n6@6>65D~@4e4Lf91&&uTMb9}s+tlDxKV?{)T|IA{Rq!{(ikZwUryrW0(jv$SB zY2nr$880;o*}byrWZqOsgr2=TLyaOSp;L14U|=Hcw;Ubr4^HGtEu%0MJAWSO;gW@9 zCFgCTj^j3YRL3OYpm>!+s|LH5+o{7&lBG8U20Nao-k^KPY&SeC!>75R6gi_niyI9U z8FUumk*AMRFKtGJY`Da{a}!l{IK=yYQ;lP15N|TaQqL~#&Rmk~%yy3uAt~-NDBa#M zqMF69j&`wrG-~?nc&=qY%Y0H}&us4>Tlbv#v8#l^tKWOlOL15J)pOS}V(9J^pKzbx zc*pF6jlSuT$JWQ6s%DFI>!g03A$d;oobo)j=`qwsrZ)(AyD8ZIOE~TkdLc&6B1iV+y2m#;`gM{%4@ief3-8H`qb%Lx`PLv2-SOS+!s_x z;;ktu`sgCLZ|!H7nz6sBG7s+*FmsMM??o3qLD5Te;{8=S=w)uRyKG$0I}M4on09&N zI@Mn%_ow(At>zC*TMCR4-)*>PuHm&2EO;$x@h6`{!&6!cC!Be2Qtmq26)BWo_$%+n zBHsz~ZMui{M(&E(?J6v=EyjF{?jiNi&;e1Y1N#1~tIW&hQc+#WLRT`H7|RCLA)6EG z{!)WIJi2{JrjHIf-naAYm>;`SF%hl2hvxm%sGAjSxz+JuVk)K6rN8Z7`M|Mhp61+L zGkz-)753k_11TKZr3Tk!)%D~m^%0p2h5U3Wj{X>x*5${I>$_*%t|*%!NxiOPt})>9 zNmhpz=iF=|WfK0eLQ^)Ya@@g)E_e0FgAnDl!gn?`zua~lwU_G%nP5^9I-~Uz{g_u6 z#%yq1K${HT7R!Hpb-*k8sMw6Ldg#KOFM zlH+v7m)ww*`uGQ{$M&ONi$`V|^=g%Y5rGbF*M!RwTn*9}XO9TekInlXePuO5)54EYcD-{Qak literal 0 HcmV?d00001 diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 907dab09..03d68016 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -423,7 +423,9 @@ export const en = { remove: 'Remove', fileSizeTip: 'File size cannot exceed {{size}}MB', - fileAcceptTip: 'Unsupported file type:' + fileAcceptTip: 'Unsupported file type:', + nextStep: 'Next Step', + prevStep: 'Previous Step', }, model: { searchPlaceholder: 'search model…', @@ -1373,6 +1375,9 @@ export const en = { embeddingModel: 'Embedding Model', rerankModel: 'Rerank Model', configAlert: 'Space model configuration ensures that the space can correctly call the corresponding models to process business data during runtime.', + + basic: 'Basic Config', + models: 'Model Selection', }, memoryExtractionEngine: { title: 'Memory Engine Module Configuration Center', @@ -2039,7 +2044,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re 'code': { input_variables: 'Input Variables', output_variables: 'Output Variables', - refreshTip: '同步函数签名至代码', + refreshTip: 'Sync function signature to code', }, name: 'Key', type: 'Type', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 42438c5a..30d9481b 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -977,7 +977,9 @@ export const zh = { remove: '删除', fileSizeTip: '文件大小不能超过 {{size}}MB', - fileAcceptTip: '不支持的文件类型:' + fileAcceptTip: '不支持的文件类型:', + nextStep: '下一步', + prevStep: '上一步', }, product: { applicationManagement: '应用管理', @@ -1449,6 +1451,9 @@ export const zh = { embeddingModel: 'Embedding 模型', rerankModel: 'Rerank 模型', configAlert: '空间模型配置为空间的模型模型,保障空间运行时能正确的调用到相应的模型来处理业务数据。', + + basic: '基础配置', + models: '模型选择', }, memoryExtractionEngine: { title: '记忆引擎模块配置中心', diff --git a/web/src/views/SpaceManagement/components/SpaceModal.tsx b/web/src/views/SpaceManagement/components/SpaceModal.tsx index c02a8e9d..725db4b9 100644 --- a/web/src/views/SpaceManagement/components/SpaceModal.tsx +++ b/web/src/views/SpaceManagement/components/SpaceModal.tsx @@ -1,24 +1,31 @@ -import { forwardRef, useImperativeHandle, useState, useEffect } from 'react'; -import { Form, Input, App, Select } from 'antd'; +import { forwardRef, useImperativeHandle, useState } from 'react'; +import { Form, Input, App, Steps, Button } from 'antd'; import { useTranslation } from 'react-i18next'; -import type { SpaceModalData, SpaceModalRef, Space } from '../types' +import type { SpaceModalData, SpaceModalRef, Space, StorageType } from '../types' import RbModal from '@/components/RbModal' import { createWorkspace } from '@/api/workspaces' import RadioGroupCard from '@/components/RadioGroupCard' -import { getModelListUrl, getModelList } from '@/api/models' +import { getModelListUrl } from '@/api/models' import CustomSelect from '@/components/CustomSelect' -import type { ModelListItem } from '@/views/ModelManagement/types' +import UploadImages from '@/components/Upload/UploadImages' +import { getFileLink } from '@/api/fileStorage' +import ragIcon from '@/assets/images/space/rag.png' +import neo4jIcon from '@/assets/images/space/neo4j.png' const FormItem = Form.Item; interface SpaceModalProps { refresh: () => void; } -const types = [ +const types: StorageType[] = [ 'rag', 'neo4j', ] +const typeIcons: Record = { + rag: ragIcon, + neo4j: neo4jIcon +} const SpaceModal = forwardRef(({ refresh @@ -29,7 +36,7 @@ const SpaceModal = forwardRef(({ const [form] = Form.useForm(); const [loading, setLoading] = useState(false) const [editVo, setEditVo] = useState(null) - const [modelList, setModelList] = useState([]) + const [currentStep, setCurrentStep] = useState(0) const values = Form.useWatch([], form); @@ -39,7 +46,11 @@ const SpaceModal = forwardRef(({ form.resetFields(); setLoading(false) setEditVo(null) + setCurrentStep(0) }; + const handlePrevStep = () => { + setCurrentStep(prev => prev - 1) + } const handleOpen = (space?: Space) => { if (space) { @@ -58,33 +69,41 @@ const SpaceModal = forwardRef(({ form .validateFields() .then(() => { - setLoading(true) - createWorkspace(values as SpaceModalData) - .then(() => { - setLoading(false) - refresh() - handleClose() - message.success(t('common.createSuccess')) - }) - .catch(() => { - setLoading(false) - }); + if (currentStep === 0) { + setCurrentStep(1) + } else { + const { icon, ...rest } = values + let formData: SpaceModalData = { + ...rest + } + if (icon?.response?.data.file_id) { + getFileLink(icon?.response?.data.file_id).then(res => { + const logoRes = res as { url: string } + formData.icon = logoRes.url + formData.iconType = 'remote' + handleUpdate(formData) + }).catch(() => { + handleUpdate(formData) + }) + } + } }) .catch((err) => { console.log('err', err) }); } - - useEffect(() => { - getModels() - }, []) - - const getModels = () => { - getModelList({ type: 'llm,chat', pagesize: 100, page: 1, is_active: true }) - .then(res => { - const response = res as { items: ModelListItem[] } - setModelList(response.items) + const handleUpdate = (formData: SpaceModalData) => { + setLoading(true) + createWorkspace(formData) + .then(() => { + setLoading(false) + refresh() + handleClose() + message.success(t('common.createSuccess')) }) + .catch(() => { + setLoading(false) + }); } // 暴露给父组件的方法 @@ -98,78 +117,104 @@ const SpaceModal = forwardRef(({ title={t(`space.${editVo?.id ? 'editSpace' : 'createSpace'}`)} open={visible} onCancel={handleClose} - okText={t('common.save')} onOk={handleSave} + footer={[ + , + , + ]} confirmLoading={loading} > + ({ title: t(`space.${key}`) } ))} + className="rb:mb-6!" + />
+ - -