{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "ai-model-editor", "title": "AI Model Editor", "description": "Controlled canonical Model editor with injected Provider/Endpoint references, modalities, capabilities, limits, cost, defaults, and status", "dependencies": ["@wener/ai@^0.1.21", "daisyui"], "registryDependencies": ["https://ui-components.wener.me/r/ai-config-editor.json"], "files": [ { "path": "registry/default/blocks/ai-model-editor/ai-model-editor-types.ts", "content": "import type { Model } from '@wener/ai/schema';\nimport type { ComponentPropsWithRef } from 'react';\nimport type {\n\tAiConfigEditorOption,\n\tAiConfigEditorSlots,\n\tAiConfigSchema,\n\tAiConfigValidationMode,\n\tAiResourceEditorMessageOverrides,\n\tAiResourceEditorMessages,\n} from '../../ui/ai-config-editor';\n\nexport type AiModelEditorField =\n\t| 'id'\n\t| 'name'\n\t| 'title'\n\t| 'description'\n\t| 'providerId'\n\t| 'endpointId'\n\t| 'endpointKey'\n\t| 'type'\n\t| 'family'\n\t| 'apiType'\n\t| 'contextWindow'\n\t| 'maxInputTokens'\n\t| 'maxOutputTokens'\n\t| 'modalities'\n\t| 'capabilities'\n\t| 'limits'\n\t| 'cost'\n\t| 'defaults'\n\t| 'status'\n\t| 'releaseDate'\n\t| 'knowledge'\n\t| 'openWeights'\n\t| 'headers'\n\t| 'tags'\n\t| 'labels'\n\t| 'options'\n\t| 'extensions'\n\t| 'enabled';\nexport type AiModelEditorSection =\n\t| 'identity'\n\t| 'binding'\n\t| 'limits'\n\t| 'modalities'\n\t| 'capabilities'\n\t| 'costDefaults'\n\t| 'metadata';\nexport type AiModelEditorMessages = AiResourceEditorMessages;\nexport type AiModelEditorMessageOverrides = AiResourceEditorMessageOverrides;\n\nexport type AiModelEditorProps = Omit, 'onChange' | 'title'> & {\n\tvalue: Model;\n\tonChange: (value: Model) => void;\n\tonSubmit?: (value: Model) => void;\n\tschema?: AiConfigSchema;\n\tvalidationMode?: AiConfigValidationMode;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tmessages?: AiModelEditorMessageOverrides;\n\tslots?: AiConfigEditorSlots;\n\tproviderOptions?: readonly AiConfigEditorOption[];\n\tendpointOptions?: readonly AiConfigEditorOption[];\n\tapiTypeOptions?: readonly AiConfigEditorOption[];\n};\n", "type": "registry:lib", "target": "@components/blocks/ai-model-editor/ai-model-editor-types.ts" }, { "path": "registry/default/blocks/ai-model-editor/ai-model-advanced-fields.tsx", "content": "import type { Model, ModelCapabilities, ModelCost, ModelDefaults, ModelLimits } from '@wener/ai/schema';\nimport {\n\ttype AiConfigEditorSlots,\n\tAiConfigFieldGrid,\n\tAiConfigNumberField,\n\tAiConfigTextField,\n\tAiConfigToggleField,\n} from '../../ui/ai-config-editor';\n\nconst capabilityFields: readonly { key: keyof Omit; label: string }[] = [\n\t{ key: 'messageGeneration', label: '消息生成' },\n\t{ key: 'textCompletion', label: '文本补全' },\n\t{ key: 'embedding', label: '向量嵌入' },\n\t{ key: 'imageGeneration', label: '图像生成' },\n\t{ key: 'imageEditing', label: '图像编辑' },\n\t{ key: 'reranking', label: '重排序' },\n\t{ key: 'tokenCounting', label: 'Token 计数' },\n\t{ key: 'reasoning', label: 'Reasoning' },\n\t{ key: 'toolCall', label: '工具调用' },\n\t{ key: 'attachment', label: '附件' },\n\t{ key: 'temperature', label: 'Temperature' },\n\t{ key: 'structuredOutput', label: '结构化输出' },\n\t{ key: 'jsonSchema', label: 'JSON Schema' },\n\t{ key: 'jsonObject', label: 'JSON Object' },\n];\n\nexport type AiModelAdvancedFieldsProps = {\n\tvalue: Model;\n\tonChange: (value: Model) => void;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tslots?: AiConfigEditorSlots;\n};\n\nexport function AiModelCapabilitiesFields({\n\tvalue,\n\tonChange,\n\tdisabled = false,\n\treadOnly = false,\n}: AiModelAdvancedFieldsProps) {\n\tconst capabilities = value.capabilities ?? {};\n\tconst update = (key: keyof Omit, enabled: boolean) =>\n\t\tonChange({ ...value, capabilities: { ...capabilities, [key]: enabled } });\n\treturn (\n\t\t\n\t\t\t{capabilityFields.map(({ key, label }) => (\n\t\t\t\t update(key, enabled)}\n\t\t\t\t/>\n\t\t\t))}\n\t\t\n\t);\n}\n\nexport function AiModelLimitFields({\n\tvalue,\n\tonChange,\n\tdisabled = false,\n\treadOnly = false,\n}: AiModelAdvancedFieldsProps) {\n\tconst limits = value.limits ?? {};\n\tconst update = (patch: Partial) => onChange({ ...value, limits: { ...limits, ...patch } });\n\treturn (\n\t\t\n\t\t\t update({ context })}\n\t\t\t/>\n\t\t\t update({ input })}\n\t\t\t/>\n\t\t\t update({ output })}\n\t\t\t/>\n\t\t\t update({ requestsPerMinute })}\n\t\t\t/>\n\t\t\t update({ tokensPerMinute })}\n\t\t\t/>\n\t\t\n\t);\n}\n\nexport function AiModelCostDefaultFields({\n\tvalue,\n\tonChange,\n\tdisabled = false,\n\treadOnly = false,\n}: AiModelAdvancedFieldsProps) {\n\tconst cost = value.cost ?? {};\n\tconst defaults = value.defaults ?? {};\n\tconst updateCost = (patch: Partial) => onChange({ ...value, cost: { ...cost, ...patch } });\n\tconst updateDefaults = (patch: Partial) => onChange({ ...value, defaults: { ...defaults, ...patch } });\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

成本

\n\t\t\t\t\n\t\t\t\t\t updateCost({ input })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateCost({ output })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateCost({ cacheRead })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateCost({ cacheWrite })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateCost({ reasoning })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateCost({ currency: optional(currency) })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateCost({ unit: optional(unit) })}\n\t\t\t\t\t/>\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t

默认推理参数

\n\t\t\t\t\n\t\t\t\t\t updateDefaults({ temperature })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateDefaults({ topP })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateDefaults({ topK })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateDefaults({ maxTokens })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateDefaults({ frequencyPenalty })}\n\t\t\t\t\t/>\n\t\t\t\t\t updateDefaults({ presencePenalty })}\n\t\t\t\t\t/>\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t);\n}\n\nfunction optional(value: string): string | undefined {\n\treturn value.trim() ? value : undefined;\n}\n", "type": "registry:component", "target": "@components/blocks/ai-model-editor/ai-model-advanced-fields.tsx" }, { "path": "registry/default/blocks/ai-model-editor/ai-model-editor.tsx", "content": "'use client';\n\nimport { DefaultApiRegistry, type Model, ModelSchema } from '@wener/ai/schema';\nimport type { ReactNode } from 'react';\nimport {\n\tAiConfigEditor,\n\tAiConfigFieldGrid,\n\tAiConfigFieldSlot,\n\tAiConfigFormSection,\n\tAiConfigKeyValueField,\n\tAiConfigNumberField,\n\tAiConfigSelectField,\n\tAiConfigStringListField,\n\tAiConfigTextareaField,\n\tAiConfigTextField,\n\tAiConfigToggleField,\n\taiConfigFieldError,\n\tJsonValueEditor,\n\tmergeResourceEditorMessages,\n\tuseAiConfigDraft,\n} from '../../ui/ai-config-editor';\nimport { AiModelCapabilitiesFields, AiModelCostDefaultFields, AiModelLimitFields } from './ai-model-advanced-fields';\nimport type { AiModelEditorField, AiModelEditorMessages, AiModelEditorProps } from './ai-model-editor-types';\n\nexport const defaultAiModel: Model = ModelSchema.parse({\n\tid: 'model-new',\n\tname: 'model-new',\n\ttype: 'chat',\n\tenabled: true,\n});\nconst apiTypes = DefaultApiRegistry.apiTypes.map((item) => ({ value: item.name, label: item.name }));\nconst families = DefaultApiRegistry.families.map((item) => ({ value: item.name, label: item.name }));\nconst modelTypes = ['chat', 'completion', 'embedding', 'rerank', 'generation', 'image', 'audio', 'video'].map(\n\t(value) => ({ value, label: value }),\n);\nconst statuses = ['alpha', 'beta', 'active', 'deprecated', 'disabled'].map((value) => ({ value, label: value }));\n\nconst defaultMessages: AiModelEditorMessages = {\n\ttitle: 'AI Model 配置',\n\tdescription: '绑定 Provider/Endpoint,并配置模型能力、限制、成本和默认参数。',\n\tsections: {\n\t\tidentity: '身份',\n\t\tbinding: 'Provider 与 API 绑定',\n\t\tlimits: '上下文与 Token 限制',\n\t\tmodalities: '输入输出模态',\n\t\tcapabilities: '能力',\n\t\tcostDefaults: '成本与默认参数',\n\t\tmetadata: '状态、标签与扩展',\n\t},\n\tfields: {\n\t\tid: 'ID',\n\t\tname: '名称',\n\t\ttitle: '显示名称',\n\t\tdescription: '描述',\n\t\tproviderId: 'Provider',\n\t\tendpointId: 'Endpoint',\n\t\tendpointKey: 'Endpoint Key',\n\t\ttype: '模型类型',\n\t\tfamily: 'API Family',\n\t\tapiType: 'API 类型',\n\t\tcontextWindow: 'Context Window',\n\t\tmaxInputTokens: '最大输入 Token',\n\t\tmaxOutputTokens: '最大输出 Token',\n\t\tmodalities: '模态',\n\t\tcapabilities: '能力',\n\t\tlimits: '高级限制',\n\t\tcost: '成本',\n\t\tdefaults: '默认参数',\n\t\tstatus: '状态',\n\t\treleaseDate: '发布日期',\n\t\tknowledge: '知识截止/说明',\n\t\topenWeights: '开放权重',\n\t\theaders: '请求头',\n\t\ttags: '标签',\n\t\tlabels: 'Labels',\n\t\toptions: 'Options',\n\t\textensions: 'Extensions',\n\t\tenabled: '启用 Model',\n\t},\n};\n\nexport function AiModelEditor({\n\tvalue,\n\tonChange,\n\tonSubmit,\n\tschema = ModelSchema,\n\tvalidationMode,\n\tdisabled = false,\n\treadOnly = false,\n\tmessages: overrides,\n\tslots,\n\tproviderOptions = [],\n\tendpointOptions = [],\n\tapiTypeOptions = apiTypes,\n\t...props\n}: AiModelEditorProps) {\n\tconst messages = mergeResourceEditorMessages(defaultMessages, overrides);\n\tconst controller = useAiConfigDraft({\n\t\tvalue,\n\t\tfallbackValue: defaultAiModel,\n\t\tschema,\n\t\tonChange,\n\t\tonSubmit,\n\t\tvalidationMode,\n\t});\n\tconst draft = controller.draft;\n\tconst locked = disabled || readOnly;\n\tconst patch = (next: Partial) => controller.setDraft({ ...draft, ...next });\n\tconst field = (name: AiModelEditorField, defaultField: ReactNode) => (\n\t\t\n\t);\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'id',\n\t\t\t\t\t\t patch({ id })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'name',\n\t\t\t\t\t\t patch({ name })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'title',\n\t\t\t\t\t\t patch({ title: optional(title) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'type',\n\t\t\t\t\t\t patch({ type: type ? (type as Model['type']) : undefined })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'description',\n\t\t\t\t\t patch({ description: optional(description) })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'providerId',\n\t\t\t\t\t\t patch({ providerId: optional(providerId) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'endpointId',\n\t\t\t\t\t\t patch({ endpointId: optional(endpointId) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'endpointKey',\n\t\t\t\t\t\t patch({ endpointKey: optional(endpointKey) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'family',\n\t\t\t\t\t\t patch({ family: optional(family) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'apiType',\n\t\t\t\t\t\t patch({ apiType: optional(apiType) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'contextWindow',\n\t\t\t\t\t\t patch({ contextWindow })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'maxInputTokens',\n\t\t\t\t\t\t patch({ maxInputTokens })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'maxOutputTokens',\n\t\t\t\t\t\t patch({ maxOutputTokens })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'limits',\n\t\t\t\t\t\t,\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t
\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'modalities',\n\t\t\t\t\t\t patch({ modalities: { ...draft.modalities, input } })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t patch({ modalities: { ...draft.modalities, output } })}\n\t\t\t\t\t/>\n\t\t\t\t
\n\t\t\t
\n\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'capabilities',\n\t\t\t\t\t,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'cost',\n\t\t\t\t\t,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'status',\n\t\t\t\t\t\t patch({ status: status ? (status as Model['status']) : undefined })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'releaseDate',\n\t\t\t\t\t\t patch({ releaseDate: optional(releaseDate) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'knowledge',\n\t\t\t\t\t\t patch({ knowledge: optional(knowledge) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'openWeights',\n\t\t\t\t\t\t patch({ openWeights })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'headers',\n\t\t\t\t\t\t patch({ headers })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'labels',\n\t\t\t\t\t\t patch({ labels })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\t{field(\n\t\t\t\t\t'tags',\n\t\t\t\t\t patch({ tags })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'options',\n\t\t\t\t\t\t patch({ options: options as Model['options'] })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'extensions',\n\t\t\t\t\t\t patch({ extensions: extensions as Model['extensions'] })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\t{field(\n\t\t\t\t\t'enabled',\n\t\t\t\t\t patch({ enabled })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t
\n\t\t\n\t);\n}\n\nfunction optional(value: string): string | undefined {\n\treturn value.trim() ? value : undefined;\n}\n", "type": "registry:component", "target": "@components/blocks/ai-model-editor/ai-model-editor.tsx" }, { "path": "registry/default/blocks/ai-model-editor/index.ts", "content": "export * from './ai-model-advanced-fields';\nexport * from './ai-model-editor';\nexport * from './ai-model-editor-types';\n", "type": "registry:component", "target": "@components/blocks/ai-model-editor/index.ts" } ], "type": "registry:block" }