From eea95c19615987d66dde4b547df1c28b4febe8f9 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Mon, 4 May 2026 22:52:41 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9Awebui=E6=94=AF=E6=8C=81=E6=9B=B4?= =?UTF-8?q?=E5=8A=A0=E4=BC=98=E5=8C=96=E7=9A=84=E6=A8=A1=E5=9E=8B=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E4=BC=98=E5=8C=96=E5=A4=9A=E5=A4=84UI?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=EF=BC=8C=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E8=A7=86=E8=A7=89=E5=92=8Ccache=E4=BB=B7=E6=A0=BC=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E9=87=8D=E8=A1=A8=E8=BE=BE=E4=B8=8D?= =?UTF-8?q?=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=A1=A8=E6=83=85=E5=8C=85=E8=B7=AF=E5=BE=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/package-lock.json | 4 +- dashboard/package.json | 2 +- .../dynamic-form/DynamicConfigForm.tsx | 76 +----- .../components/dynamic-form/DynamicField.tsx | 60 ++++- .../__tests__/DynamicField.test.tsx | 76 ++++++ .../src/components/expression-reviewer.tsx | 16 +- dashboard/src/i18n/locales/zh.json | 12 +- dashboard/src/lib/config-api.ts | 12 +- dashboard/src/routes/config/bot.tsx | 174 +++++++++--- .../bot/hooks/ListItemEditorHookFactory.tsx | 126 +++++++-- .../config/bot/hooks/complexFieldHooks.tsx | 40 +++ .../src/routes/config/bot/hooks/index.ts | 1 + dashboard/src/routes/config/model.tsx | 248 +++++++++++++----- .../config/model/components/ModelCardList.tsx | 5 + .../config/model/components/ModelTable.tsx | 14 +- .../model/components/TaskConfigCard.tsx | 127 ++++++--- .../src/routes/config/model/constants.ts | 24 +- .../config/model/hooks/useModelAutoSave.ts | 3 + .../routes/config/model/hooks/useModelTour.ts | 63 ++++- dashboard/src/routes/config/model/types.ts | 3 + .../src/routes/config/modelProvider/index.tsx | 27 +- dashboard/src/routes/mcp-settings.tsx | 25 +- dashboard/src/routes/resource/emoji/index.tsx | 2 +- .../resource/expression/ExpressionDialogs.tsx | 6 +- .../resource/expression/ExpressionList.tsx | 12 +- .../src/routes/resource/expression/index.tsx | 48 ++-- .../src/routes/resource/jargon/index.tsx | 2 +- dashboard/src/types/config-schema.ts | 3 +- dashboard/src/types/expression.ts | 1 + src/chat/replyer/maisaka_generator_base.py | 21 +- src/config/config.py | 2 +- src/config/config_base.py | 1 - src/config/model_configs.py | 42 +-- src/config/official_configs.py | 27 +- src/emoji_system/emoji_manager.py | 109 +++++--- src/webui/config_schema.py | 3 - src/webui/routers/expression.py | 199 ++++++++++---- uv.lock | 26 +- 38 files changed, 1188 insertions(+), 454 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 494c87bf..77877140 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -1,12 +1,12 @@ { "name": "maibot-dashboard", - "version": "1.0.3", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "maibot-dashboard", - "version": "1.0.3", + "version": "1.0.5", "dependencies": { "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-javascript": "^6.2.4", diff --git a/dashboard/package.json b/dashboard/package.json index 3db4e7ac..85e72315 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -1,7 +1,7 @@ { "name": "maibot-dashboard", "private": true, - "version": "1.0.4", + "version": "1.0.5", "type": "module", "main": "./out/main/index.js", "scripts": { diff --git a/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx b/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx index 71d1de92..b057e6f4 100644 --- a/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx +++ b/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx @@ -76,7 +76,6 @@ function DynamicConfigSection({ basePath, hooks, level, - mergedChildren = [], nestedSchema, onChange, sectionDescription, @@ -87,11 +86,6 @@ function DynamicConfigSection({ basePath: string hooks: FieldHookRegistry level: number - mergedChildren?: Array<{ - key: string - schema: ConfigSchema - values: Record - }> nestedSchema: ConfigSchema onChange: (field: string, value: unknown) => void sectionDescription?: string @@ -100,9 +94,7 @@ function DynamicConfigSection({ values: Record }) { const [advancedVisible, setAdvancedVisible] = React.useState(false) - const hasAdvanced = - hasTopLevelAdvancedFields(nestedSchema) || - mergedChildren.some((child) => hasTopLevelAdvancedFields(child.schema)) + const hasAdvanced = hasTopLevelAdvancedFields(nestedSchema) return ( @@ -135,37 +127,6 @@ function DynamicConfigSection({ level={level} advancedVisible={hasAdvanced ? advancedVisible : undefined} /> - {mergedChildren.map((child) => { - const childTitle = resolveSectionTitle(child.schema) - const childDescription = resolveSectionDescription(child.schema, childTitle) - const parentPath = basePath.includes('.') - ? basePath.replace(/\.[^.]+$/, '') - : '' - const childPath = buildFieldPath(parentPath, child.key) - - return ( -
-
-
- -

{childTitle}

-
- {childDescription && ( -

{childDescription}

- )} -
- onChange(`${child.key}.${field}`, value)} - basePath={childPath} - hooks={hooks} - level={level} - advancedVisible={hasAdvanced ? advancedVisible : undefined} - /> -
- ) - })}
) @@ -197,17 +158,6 @@ export const DynamicConfigForm: React.FC = ({ () => new Map(schema.fields.map((field) => [field.name, field])), [schema.fields], ) - const mergedChildKeys = React.useMemo(() => { - const keys = new Set() - for (const nestedSchema of Object.values(schema.nested ?? {})) { - for (const childKey of nestedSchema.uiMergeChildren ?? []) { - if (schema.nested?.[childKey]) { - keys.add(childKey) - } - } - } - return keys - }, [schema.nested]) const renderField = (field: FieldSchema) => { const fieldPath = buildFieldPath(basePath, field.name) @@ -294,7 +244,6 @@ export const DynamicConfigForm: React.FC = ({ {schema.nested && Object.entries(schema.nested) - .filter(([key]) => !mergedChildKeys.has(key)) .map(([key, nestedSchema]) => { const nestedField = fieldMap.get(key) const nestedFieldPath = buildFieldPath(basePath, key) @@ -342,34 +291,11 @@ export const DynamicConfigForm: React.FC = ({ const sectionTitle = resolveSectionTitle(nestedSchema) const sectionDescription = resolveSectionDescription(nestedSchema, sectionTitle) - const mergedChildren = (nestedSchema.uiMergeChildren ?? []) - .map((childKey) => { - const childSchema = schema.nested?.[childKey] - if (!childSchema) { - return null - } - - return { - key: childKey, - schema: childSchema, - values: (values[childKey] as Record) || {}, - } - }) - .filter( - ( - child, - ): child is { - key: string - schema: ConfigSchema - values: Record - } => Boolean(child), - ) if (level === 0) { return ( ) || {}} onChange={onChange} diff --git a/dashboard/src/components/dynamic-form/DynamicField.tsx b/dashboard/src/components/dynamic-form/DynamicField.tsx index a0e72304..27ce7856 100644 --- a/dashboard/src/components/dynamic-form/DynamicField.tsx +++ b/dashboard/src/components/dynamic-form/DynamicField.tsx @@ -31,6 +31,23 @@ export const DynamicField: React.FC = ({ value, onChange, }) => { + const parseNumericValue = (rawValue: unknown, fallback: number) => { + if (typeof rawValue === 'number' && Number.isFinite(rawValue)) { + return rawValue + } + + if (typeof rawValue === 'string') { + const parsedValue = schema.type === 'integer' + ? parseInt(rawValue, 10) + : parseFloat(rawValue) + if (Number.isFinite(parsedValue)) { + return parsedValue + } + } + + return fallback + } + const renderPrimitiveArrayEditor = () => { const itemType = schema.items?.type ?? 'string' const arrayValue = Array.isArray(value) @@ -94,6 +111,12 @@ export const DynamicField: React.FC = ({ return } + const isRuleTypeSelect = + schema.name === 'rule_type' && + (schema.type === 'select' || schema['x-widget'] === 'select') + const inlineDescription = isRuleTypeSelect ? '' : schema.description + const selectHoverDescription = isRuleTypeSelect ? schema.description : undefined + const renderFieldHeader = () => (
- {schema.description && ( + {inlineDescription && ( - {schema.description} + {inlineDescription} )}
@@ -129,6 +152,9 @@ export const DynamicField: React.FC = ({ case 'slider': return renderSlider() case 'input': + if (type === 'integer' || type === 'number') { + return renderNumberInput() + } return renderTextInput() case 'number': return renderNumberInput() @@ -214,7 +240,7 @@ export const DynamicField: React.FC = ({ * 渲染 Slider 组件(用于 number 类型 + x-widget: slider) */ const renderSlider = () => { - const numValue = typeof value === 'number' ? value : (schema.default as number ?? 0) + const numValue = parseNumericValue(value, schema.default as number ?? 0) const min = schema.minValue ?? 0 const max = schema.maxValue ?? 100 const step = schema.step ?? 1 @@ -241,7 +267,7 @@ export const DynamicField: React.FC = ({ * 渲染 Input[type="number"] 组件(用于 number/integer 类型) */ const renderNumberInput = () => { - const numValue = typeof value === 'number' ? value : (schema.default as number ?? 0) + const numValue = parseNumericValue(value, schema.default as number ?? 0) const min = schema.minValue const max = schema.maxValue const step = schema.step ?? (schema.type === 'integer' ? 1 : 0.1) @@ -250,7 +276,12 @@ export const DynamicField: React.FC = ({ onChange(parseFloat(e.target.value) || 0)} + onChange={(e) => { + const nextValue = schema.type === 'integer' + ? parseInt(e.target.value, 10) + : parseFloat(e.target.value) + onChange(Number.isFinite(nextValue) ? nextValue : 0) + }} min={min} max={max} step={step} @@ -262,7 +293,12 @@ export const DynamicField: React.FC = ({ * 渲染 Input[type="text"] 组件(用于 string 类型) */ const renderTextInput = (type: 'password' | 'text' = 'text') => { - const strValue = typeof value === 'string' ? value : (schema.default as string ?? '') + const strValue = + typeof value === 'string' + ? value + : value === null || value === undefined + ? String(schema.default ?? '') + : String(value) return ( = ({ */ const renderTextarea = () => { const strValue = typeof value === 'string' ? value : (schema.default as string ?? '') + const minHeight = typeof schema['x-textarea-min-height'] === 'number' + ? schema['x-textarea-min-height'] + : undefined + const rows = typeof schema['x-textarea-rows'] === 'number' + ? schema['x-textarea-rows'] + : 4 + return (