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 抽屉编辑,参数表单修改时会保留这些扩展字段。