From 31b3969b96f46c12256d9d893b9f44d9148f1705 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Mon, 2 Mar 2026 02:18:28 +0800 Subject: [PATCH] Enhance ToolLibrary by adding sourceKey to ToolParameterDraft and updating related functions for improved schema management. Introduce normalization functions for object schemas and defaults, and refactor buildToolParameterConfig to utilize these enhancements. Update state management in ToolLibraryPage to accommodate new schema handling and defaults integration. --- web/pages/ToolLibrary.tsx | 109 +++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/web/pages/ToolLibrary.tsx b/web/pages/ToolLibrary.tsx index 2738cfd..975b3d2 100644 --- a/web/pages/ToolLibrary.tsx +++ b/web/pages/ToolLibrary.tsx @@ -24,6 +24,7 @@ type ToolParameterType = 'string' | 'number' | 'integer' | 'boolean' | 'object' type ToolParameterDraft = { id: string; key: string; + sourceKey: string; type: ToolParameterType; required: boolean; description: string; @@ -36,6 +37,7 @@ const TOOL_PARAMETER_TYPES: ToolParameterType[] = ['string', 'number', 'integer' const newParameterDraft = (): ToolParameterDraft => ({ id: `param_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`, key: '', + sourceKey: '', type: 'string', required: false, description: '', @@ -106,6 +108,7 @@ const draftsFromSchema = (schema: Record | undefined, defaults: Rec return { id: `param_${key}_${Math.random().toString(16).slice(2, 8)}`, key, + sourceKey: key, type: schemaTypeOrDefault(typedSpec.type), required: requiredSet.has(key), description: String(typedSpec.description || ''), @@ -119,6 +122,7 @@ const draftsFromSchema = (schema: Record | undefined, defaults: Rec drafts.push({ id: `param_${key}_${Math.random().toString(16).slice(2, 8)}`, key, + sourceKey: key, type: typeof value === 'number' && Number.isInteger(value) ? 'integer' : (typeof value as ToolParameterType) || 'string', required: false, description: '', @@ -129,7 +133,31 @@ const draftsFromSchema = (schema: Record | undefined, defaults: Rec return drafts; }; -const buildToolParameterConfig = (drafts: ToolParameterDraft[]): { schema: Record; defaults: Record; error?: string } => { +const normalizeObjectSchema = (value: any): Record => { + const schema = value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {}; + const properties = schema.properties && typeof schema.properties === 'object' && !Array.isArray(schema.properties) + ? schema.properties + : {}; + const required = Array.isArray(schema.required) ? schema.required.map((item: any) => String(item)) : []; + return { + ...schema, + type: 'object', + properties, + required, + }; +}; + +const normalizeDefaultsObject = (value: any): Record => + value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {}; + +const buildToolParameterConfig = ( + drafts: ToolParameterDraft[], + baseSchemaInput?: Record +): { schema: Record; defaults: Record; error?: string } => { + const baseSchema = normalizeObjectSchema(baseSchemaInput); + const baseProperties = baseSchema.properties && typeof baseSchema.properties === 'object' && !Array.isArray(baseSchema.properties) + ? (baseSchema.properties as Record) + : {}; const properties: Record = {}; const required: string[] = []; const defaults: Record = {}; @@ -138,18 +166,26 @@ const buildToolParameterConfig = (drafts: ToolParameterDraft[]): { schema: Recor for (const draft of drafts) { const key = draft.key.trim(); if (!key) continue; - if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { - return { schema: {}, defaults: {}, error: `参数标识不合法: ${key}` }; - } if (seen.has(key)) { return { schema: {}, defaults: {}, error: `参数标识重复: ${key}` }; } seen.add(key); - properties[key] = { - type: draft.type, - ...(draft.description.trim() ? { description: draft.description.trim() } : {}), - }; + const sourceKey = String(draft.sourceKey || '').trim(); + const preservedRaw = ( + (sourceKey && Object.prototype.hasOwnProperty.call(baseProperties, sourceKey) ? baseProperties[sourceKey] : undefined) + ?? baseProperties[key] + ); + const preservedSpec = preservedRaw && typeof preservedRaw === 'object' && !Array.isArray(preservedRaw) + ? { ...(preservedRaw as Record) } + : {}; + properties[key] = preservedSpec; + properties[key].type = draft.type; + if (draft.description.trim()) { + properties[key].description = draft.description.trim(); + } else { + delete properties[key].description; + } if (draft.required) required.push(key); if (draft.hasDefault) { const parsed = parseDefaultValueByType(draft.type, draft.defaultValueText); @@ -157,11 +193,15 @@ const buildToolParameterConfig = (drafts: ToolParameterDraft[]): { schema: Recor return { schema: {}, defaults: {}, error: parsed.error }; } defaults[key] = parsed.value; + properties[key].default = parsed.value; + } else { + delete properties[key].default; } } return { schema: { + ...baseSchema, type: 'object', properties, required, @@ -202,6 +242,8 @@ export const ToolLibraryPage: React.FC = () => { const [toolHttpUrl, setToolHttpUrl] = useState(''); const [toolHttpHeadersText, setToolHttpHeadersText] = useState('{}'); const [toolHttpTimeoutMs, setToolHttpTimeoutMs] = useState(10000); + const [toolSchema, setToolSchema] = useState>({ type: 'object', properties: {}, required: [] }); + const [toolParameterDefaults, setToolParameterDefaults] = useState>({}); const [toolParameters, setToolParameters] = useState([]); const [schemaDrawerOpen, setSchemaDrawerOpen] = useState(false); const [schemaEditorText, setSchemaEditorText] = useState(''); @@ -237,9 +279,12 @@ export const ToolLibraryPage: React.FC = () => { setToolHttpUrl(''); setToolHttpHeadersText('{}'); setToolHttpTimeoutMs(10000); + const emptySchema = { type: 'object', properties: {}, required: [] }; + setToolSchema(emptySchema); + setToolParameterDefaults({}); setToolParameters([]); setSchemaEditorError(''); - setSchemaEditorText(JSON.stringify({ type: 'object', properties: {}, required: [] }, null, 2)); + setSchemaEditorText(JSON.stringify(emptySchema, null, 2)); setSchemaDrawerOpen(false); setIsToolModalOpen(true); }; @@ -257,13 +302,18 @@ export const ToolLibraryPage: React.FC = () => { setToolHttpUrl(tool.httpUrl || ''); setToolHttpHeadersText(JSON.stringify(tool.httpHeaders || {}, null, 2)); setToolHttpTimeoutMs(tool.httpTimeoutMs || 10000); - setToolParameters(draftsFromSchema(tool.parameterSchema, tool.parameterDefaults)); + const normalizedSchema = normalizeObjectSchema(tool.parameterSchema); + const mergedDefaults = { + ...defaultsFromSchema(normalizedSchema), + ...normalizeDefaultsObject(tool.parameterDefaults), + }; + setToolSchema(normalizedSchema); + setToolParameterDefaults(mergedDefaults); + setToolParameters(draftsFromSchema(normalizedSchema, mergedDefaults)); setSchemaEditorError(''); setSchemaEditorText( JSON.stringify( - (tool.parameterSchema && typeof tool.parameterSchema === 'object') - ? tool.parameterSchema - : { type: 'object', properties: {}, required: [] }, + normalizedSchema, null, 2 ) @@ -273,11 +323,14 @@ export const ToolLibraryPage: React.FC = () => { }; const openSchemaDrawer = () => { - const parameterConfig = buildToolParameterConfig(toolParameters); + const parameterConfig = buildToolParameterConfig(toolParameters, toolSchema); if (!parameterConfig.error) { + setToolSchema(parameterConfig.schema); + setToolParameterDefaults(parameterConfig.defaults); + setToolParameters((prev) => prev.map((item) => ({ ...item, sourceKey: item.key.trim() || item.sourceKey }))); setSchemaEditorText(JSON.stringify(parameterConfig.schema, null, 2)); - } else if (editingTool?.parameterSchema && typeof editingTool.parameterSchema === 'object') { - setSchemaEditorText(JSON.stringify(editingTool.parameterSchema, null, 2)); + } else if (toolSchema && typeof toolSchema === 'object') { + setSchemaEditorText(JSON.stringify(toolSchema, null, 2)); } else if (!schemaEditorText.trim()) { setSchemaEditorText(JSON.stringify({ type: 'object', properties: {}, required: [] }, null, 2)); } @@ -325,19 +378,11 @@ export const ToolLibraryPage: React.FC = () => { return; } - const normalizedSchema: Record = { - ...parsedSchema, - type: 'object', - properties: parsedSchema.properties && typeof parsedSchema.properties === 'object' && !Array.isArray(parsedSchema.properties) - ? parsedSchema.properties - : {}, - required: Array.isArray(parsedSchema.required) ? parsedSchema.required : [], - }; - - const currentParameterConfig = buildToolParameterConfig(toolParameters); - const currentDefaults = currentParameterConfig.error ? {} : currentParameterConfig.defaults; - const mergedDefaults = { ...currentDefaults, ...defaultsFromSchema(normalizedSchema) }; + const normalizedSchema = normalizeObjectSchema(parsedSchema); + const mergedDefaults = { ...normalizeDefaultsObject(toolParameterDefaults), ...defaultsFromSchema(normalizedSchema) }; + setToolSchema(normalizedSchema); + setToolParameterDefaults(mergedDefaults); setToolParameters(draftsFromSchema(normalizedSchema, mergedDefaults)); setSchemaEditorError(''); setSchemaEditorText(JSON.stringify(normalizedSchema, null, 2)); @@ -439,7 +484,7 @@ export const ToolLibraryPage: React.FC = () => { try { setSaving(true); let parsedHeaders: Record = {}; - const parameterConfig = buildToolParameterConfig(toolParameters); + const parameterConfig = buildToolParameterConfig(toolParameters, toolSchema); if (parameterConfig.error) { alert(parameterConfig.error); setSaving(false); @@ -447,6 +492,10 @@ export const ToolLibraryPage: React.FC = () => { } const parsedParameterSchema = parameterConfig.schema; const parsedParameterDefaults = parameterConfig.defaults; + setToolSchema(parsedParameterSchema); + setToolParameterDefaults(parsedParameterDefaults); + setToolParameters((prev) => prev.map((item) => ({ ...item, sourceKey: item.key.trim() || item.sourceKey }))); + setSchemaEditorText(JSON.stringify(parsedParameterSchema, null, 2)); if (toolCategory === 'query') { if ( @@ -807,7 +856,7 @@ export const ToolLibraryPage: React.FC = () => { )}

- 支持系统指令和信息查询两类工具。Default Args 会在模型未传值时自动补齐。 + 支持系统指令和信息查询两类工具。复杂 JSON Schema(如 items/anyOf/minItems)请在 Schema 抽屉编辑,参数表单修改时会保留这些扩展字段。