diff --git a/.dockerignore b/.dockerignore index 654a03fd..61a88dff 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,3 +21,5 @@ temp/ tmp/ mai_knowledge/ depends-data/ +!depends-data/ +!depends-data/char_frequency.json diff --git a/AGENTS.md b/AGENTS.md index a66d71ca..e977b378 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,8 +32,7 @@ # 运行/调试/构建/测试/依赖 优先使用uv -依赖项以 pyproject.toml 为准 - +依赖项以 pyproject.toml 为准,要同步更新requirements.txt # 语言规范 项目的首选语言为简体中文,无论是注释语言,日志展示语言,还是 WebUI 展示语言都首要以简体中文为首要实现目标 @@ -45,6 +44,9 @@ # 关于 A_memorix 修改 如果修改涉及 `src/A_memorix`,请先阅读 `src/A_memorix/MODIFICATION_POLICY.md`。 +# prompt模板、 +涉及对prompt模板的修改,要同步修改英文和日文的文件,对齐到中文 + 默认原则: 1. `src/A_memorix` 的实现层改动应优先遵守 `src/A_memorix/MODIFICATION_POLICY.md` 中的归属约束。 2. 不要提交无边界的 `ruff`、格式化、导入整理或大面积实现整理。 @@ -52,3 +54,6 @@ # maibot插件开发文档 https://github.com/Mai-with-u/maibot-plugin-sdk/blob/main/docs/guide.md + +# 如何提交maibot插件 +https://github.com/Mai-with-u/plugin-repo/blob/main/CONTRIBUTING.md \ No newline at end of file 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..4bc866ff 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) @@ -225,6 +175,7 @@ export const DynamicConfigForm: React.FC = ({ value={values[field.name]} onChange={(v) => onChange(field.name, v)} schema={field} + parentValues={values} /> ) } @@ -235,6 +186,7 @@ export const DynamicConfigForm: React.FC = ({ value={values[field.name]} onChange={(v) => onChange(field.name, v)} schema={field} + parentValues={values} > = ({ ? [...normalFields, ...advancedFields] : normalFields + const groupFieldsByRow = (fields: FieldSchema[]) => { + const rows: FieldSchema[][] = [] + let currentRow: FieldSchema[] = [] + let currentRowKey: string | undefined + + for (const field of fields) { + const rowKey = field['x-row'] + if (rowKey && rowKey === currentRowKey) { + currentRow.push(field) + continue + } + + if (currentRow.length > 0) { + rows.push(currentRow) + } + + currentRow = [field] + currentRowKey = rowKey + } + + if (currentRow.length > 0) { + rows.push(currentRow) + } + + return rows + } + const renderFieldList = (fields: FieldSchema[]) => ( <> - {fields.map((field, index) => ( - + {groupFieldsByRow(fields).map((row, index) => ( + field.name).join('|')}> {index > 0 && } -
{renderField(field)}
+ {row.length > 1 ? ( +
+ {row.map((field) => ( +
{renderField(field)}
+ ))} +
+ ) : ( +
{renderField(row[0])}
+ )}
))} @@ -294,7 +284,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) @@ -313,6 +302,7 @@ export const DynamicConfigForm: React.FC = ({ onChange={(v) => onChange(key, v)} schema={nestedField ?? nestedSchema} nestedSchema={nestedSchema} + parentValues={values} /> ) @@ -326,6 +316,7 @@ export const DynamicConfigForm: React.FC = ({ onChange={(v) => onChange(key, v)} schema={nestedField ?? nestedSchema} nestedSchema={nestedSchema} + parentValues={values} > = ({ 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..27039cbe 100644 --- a/dashboard/src/components/dynamic-form/DynamicField.tsx +++ b/dashboard/src/components/dynamic-form/DynamicField.tsx @@ -8,6 +8,12 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Slider } from "@/components/ui/slider" import { Switch } from "@/components/ui/switch" import { Textarea } from "@/components/ui/textarea" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import type { FieldSchema } from "@/types/config-schema" @@ -31,6 +37,27 @@ export const DynamicField: React.FC = ({ value, onChange, }) => { + const isNumericField = schema.type === 'integer' || schema.type === 'number' + + const parseNumericValue = (rawValue: unknown, fallbackValue: unknown = 0) => { + if (typeof rawValue === 'number' && Number.isFinite(rawValue)) { + return rawValue + } + + if (typeof rawValue === 'string') { + const parsedValue = parseFloat(rawValue) + if (Number.isFinite(parsedValue)) { + return schema.type === 'integer' ? Math.trunc(parsedValue) : parsedValue + } + } + + if (fallbackValue !== rawValue) { + return parseNumericValue(fallbackValue, 0) + } + + return 0 + } + const renderPrimitiveArrayEditor = () => { const itemType = schema.items?.type ?? 'string' const arrayValue = Array.isArray(value) @@ -94,6 +121,10 @@ export const DynamicField: React.FC = ({ return } + const optionDescriptions = schema['x-option-descriptions'] ?? {} + const hasOptionDescriptions = Object.keys(optionDescriptions).length > 0 + const inlineDescription = hasOptionDescriptions ? '' : schema.description + const renderFieldHeader = () => (
- {schema.description && ( + {inlineDescription && ( - {schema.description} + {inlineDescription} )}
@@ -122,10 +153,14 @@ export const DynamicField: React.FC = ({ const renderInputComponent = () => { const widget = schema['x-widget'] const type = schema.type + const resolvedWidget = + isNumericField && (widget === 'input' || widget === 'number' || !widget) + ? 'number' + : widget // x-widget 优先 - if (widget) { - switch (widget) { + if (resolvedWidget) { + switch (resolvedWidget) { case 'slider': return renderSlider() case 'input': @@ -214,7 +249,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) const min = schema.minValue ?? 0 const max = schema.maxValue ?? 100 const step = schema.step ?? 1 @@ -241,7 +276,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) const min = schema.minValue const max = schema.maxValue const step = schema.step ?? (schema.type === 'integer' ? 1 : 0.1) @@ -250,7 +285,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 +302,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 (