From 504967aba9fdf09fc6c140369a99ce0a9d38f4ee Mon Sep 17 00:00:00 2001 From: DBT Date: Thu, 26 Feb 2026 02:29:00 +0000 Subject: [PATCH] webui config: improve array editing with dropdown select + create + safe json mode --- webui/src/components/RecursiveConfig.tsx | 138 +++++++++++++++++++++-- 1 file changed, 130 insertions(+), 8 deletions(-) diff --git a/webui/src/components/RecursiveConfig.tsx b/webui/src/components/RecursiveConfig.tsx index e6b1ccb..d4e9ed7 100644 --- a/webui/src/components/RecursiveConfig.tsx +++ b/webui/src/components/RecursiveConfig.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; interface RecursiveConfigProps { @@ -8,9 +8,97 @@ interface RecursiveConfigProps { onChange: (path: string, val: any) => void; } +const isPrimitive = (v: any) => ['string', 'number', 'boolean'].includes(typeof v) || v === null; + +const PrimitiveArrayEditor: React.FC<{ + value: any[]; + path: string; + onChange: (next: any[]) => void; +}> = ({ value, path, onChange }) => { + const [draft, setDraft] = useState(''); + const [selected, setSelected] = useState(''); + + const suggestions = useMemo(() => { + // 基础建议项:从当前值推导 + 针对常见配置路径补充 + const base = new Set(value.map((v) => String(v))); + if (path.includes('tools') || path.includes('tool')) { + ['read', 'write', 'edit', 'exec', 'process', 'message', 'nodes', 'memory_search'].forEach((x) => base.add(x)); + } + if (path.includes('channels')) { + ['telegram', 'discord', 'whatsapp', 'slack', 'signal'].forEach((x) => base.add(x)); + } + return Array.from(base).filter(Boolean); + }, [value, path]); + + const addValue = (v: string) => { + const val = v.trim(); + if (!val) return; + if (value.some((x) => String(x) === val)) return; + onChange([...value, val]); + }; + + const removeAt = (idx: number) => { + onChange(value.filter((_, i) => i !== idx)); + }; + + return ( +
+
+ {value.length === 0 && (empty)} + {value.map((item, idx) => ( + + {String(item)} + + + ))} +
+ +
+ setDraft(e.target.value)} + placeholder="输入新值后添加" + className="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-indigo-500" + /> + + {suggestions.map((s) => ( + + + + + +
+
+ ); +}; + const RecursiveConfig: React.FC = ({ data, labels, path = '', onChange }) => { const { t } = useTranslation(); - + if (typeof data !== 'object' || data === null) return null; return ( @@ -18,7 +106,41 @@ const RecursiveConfig: React.FC = ({ data, labels, path = {Object.entries(data).map(([key, value]) => { const currentPath = path ? `${path}.${key}` : key; const label = labels[key] || key.replace(/_/g, ' '); - + + if (Array.isArray(value)) { + const allPrimitive = value.every(isPrimitive); + return ( +
+
+ {label} + {currentPath} +
+
+ {allPrimitive ? ( + onChange(currentPath, next)} + /> + ) : ( +