{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "ai-service-editor", "title": "AI Service Editor", "description": "Controlled canonical Service editor for endpoint connection, plaintext authentication, credentials, capabilities, and extensions", "dependencies": ["@wener/ai@^0.1.21", "daisyui"], "registryDependencies": ["https://ui-components.wener.me/r/ai-config-editor.json"], "files": [ { "path": "registry/default/blocks/ai-service-editor/ai-service-editor-types.ts", "content": "import type { Service } 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 AiServiceEditorField =\n\t| 'id'\n\t| 'name'\n\t| 'title'\n\t| 'description'\n\t| 'type'\n\t| 'apiType'\n\t| 'stage'\n\t| 'environment'\n\t| 'env'\n\t| 'baseUrl'\n\t| 'host'\n\t| 'port'\n\t| 'providerId'\n\t| 'endpoints'\n\t| 'defaultEndpointId'\n\t| 'endpointKey'\n\t| 'apiKey'\n\t| 'username'\n\t| 'password'\n\t| 'headers'\n\t| 'auth'\n\t| 'credentials'\n\t| 'capabilities'\n\t| 'tags'\n\t| 'labels'\n\t| 'notes'\n\t| 'options'\n\t| 'extensions'\n\t| 'enabled';\nexport type AiServiceEditorSection = 'identity' | 'connection' | 'authentication' | 'capabilities' | 'metadata';\nexport type AiServiceEditorMessages = AiResourceEditorMessages;\nexport type AiServiceEditorMessageOverrides = AiResourceEditorMessageOverrides<\n\tAiServiceEditorField,\n\tAiServiceEditorSection\n>;\nexport type AiServiceEditorProps = Omit, 'onChange' | 'title'> & {\n\tvalue: Service;\n\tonChange: (value: Service) => void;\n\tonSubmit?: (value: Service) => void;\n\tschema?: AiConfigSchema;\n\tvalidationMode?: AiConfigValidationMode;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tmessages?: AiServiceEditorMessageOverrides;\n\tslots?: AiConfigEditorSlots;\n\tapiTypeOptions?: readonly AiConfigEditorOption[];\n\tproviderOptions?: readonly AiConfigEditorOption[];\n\tendpointOptions?: readonly AiConfigEditorOption[];\n};\n", "type": "registry:lib", "target": "@components/blocks/ai-service-editor/ai-service-editor-types.ts" }, { "path": "registry/default/blocks/ai-service-editor/ai-service-authentication-fields.tsx", "content": "import type { Service } from '@wener/ai/schema';\nimport type { ReactNode } from 'react';\nimport {\n\tAiConfigFieldGrid,\n\tAiConfigKeyValueField,\n\tAiConfigSecretField,\n\tAiConfigTextField,\n\tJsonValueEditor,\n} from '../../ui/ai-config-editor';\n\nexport type AiServiceAuthenticationFieldsProps = {\n\tvalue: Service;\n\tonChange: (value: Service) => void;\n\tlabels: { username: string; apiKey: string; password: string; headers: string; auth: string; credentials: string };\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\trevealIdentity?: number | string;\n\trenderField?: (\n\t\tname: 'username' | 'apiKey' | 'password' | 'headers' | 'auth' | 'credentials',\n\t\tfield: ReactNode,\n\t) => ReactNode;\n};\n\nexport function AiServiceAuthenticationFields({\n\tvalue,\n\tonChange,\n\tlabels,\n\tdisabled = false,\n\treadOnly = false,\n\trevealIdentity,\n\trenderField = (_name, field) => field,\n}: AiServiceAuthenticationFieldsProps) {\n\tconst patch = (next: Partial) => onChange({ ...value, ...next });\n\treturn (\n\t\t<>\n\t\t\t\n\t\t\t\t{renderField(\n\t\t\t\t\t'username',\n\t\t\t\t\t patch({ username: optional(username) })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\t{renderField(\n\t\t\t\t\t'apiKey',\n\t\t\t\t\t patch({ apiKey: optional(apiKey) })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\t{renderField(\n\t\t\t\t\t'password',\n\t\t\t\t\t patch({ password: optional(password) })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\n\t\t\t{renderField(\n\t\t\t\t'headers',\n\t\t\t\t patch({ headers })}\n\t\t\t\t/>,\n\t\t\t)}\n\t\t\t
\n\t\t\t\t{renderField(\n\t\t\t\t\t'auth',\n\t\t\t\t\t patch({ auth: auth as Service['auth'] })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\t{renderField(\n\t\t\t\t\t'credentials',\n\t\t\t\t\t patch({ credentials: credentials as Service['credentials'] })}\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-service-editor/ai-service-authentication-fields.tsx" }, { "path": "registry/default/blocks/ai-service-editor/ai-service-editor.tsx", "content": "'use client';\n\nimport { DefaultApiRegistry, type Service, ServiceSchema } from '@wener/ai/schema';\nimport type { ReactNode } from 'react';\nimport {\n\tAiConfigEditor,\n\tAiConfigFieldGrid,\n\tAiConfigFieldSlot,\n\tAiConfigFormSection,\n\tAiConfigKeyValueField,\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 { AiServiceAuthenticationFields } from './ai-service-authentication-fields';\nimport type { AiServiceEditorField, AiServiceEditorMessages, AiServiceEditorProps } from './ai-service-editor-types';\n\nexport const defaultAiService: Service = ServiceSchema.parse({\n\tid: 'service-new',\n\tname: 'service-new',\n\ttype: 'ai',\n\tenabled: true,\n});\nconst apiTypes = DefaultApiRegistry.apiTypes.map((item) => ({ value: item.name, label: item.name }));\nconst defaultMessages: AiServiceEditorMessages = {\n\ttitle: 'AI Service 配置',\n\tdescription: '配置 Agent 可消费的服务连接、认证和能力。',\n\tsections: {\n\t\tidentity: '身份',\n\t\tconnection: 'Endpoint 连接',\n\t\tauthentication: '认证与凭据',\n\t\tcapabilities: '能力',\n\t\tmetadata: '标签与扩展',\n\t},\n\tfields: {\n\t\tid: 'ID',\n\t\tname: '名称',\n\t\ttitle: '显示名称',\n\t\tdescription: '描述',\n\t\ttype: '服务类型',\n\t\tapiType: 'API 类型',\n\t\tstage: '阶段',\n\t\tenvironment: '环境',\n\t\tenv: '环境配置',\n\t\tbaseUrl: 'Base URL',\n\t\thost: 'Host',\n\t\tport: 'Port',\n\t\tproviderId: 'Provider 引用',\n\t\tendpoints: 'Endpoint 引用',\n\t\tdefaultEndpointId: '默认 Endpoint',\n\t\tendpointKey: 'Endpoint Key',\n\t\tapiKey: 'API Key',\n\t\tusername: '用户名',\n\t\tpassword: '密码',\n\t\theaders: '请求头',\n\t\tauth: 'Auth 配置',\n\t\tcredentials: 'Credentials',\n\t\tcapabilities: '能力',\n\t\ttags: '标签',\n\t\tlabels: 'Labels',\n\t\tnotes: '备注',\n\t\toptions: 'Options',\n\t\textensions: 'Extensions',\n\t\tenabled: '启用 Service',\n\t},\n};\n\nexport function AiServiceEditor({\n\tvalue,\n\tonChange,\n\tonSubmit,\n\tschema = ServiceSchema,\n\tvalidationMode,\n\tdisabled = false,\n\treadOnly = false,\n\tmessages: overrides,\n\tslots,\n\tapiTypeOptions = apiTypes,\n\tproviderOptions = [],\n\tendpointOptions = [],\n\t...props\n}: AiServiceEditorProps) {\n\tconst messages = mergeResourceEditorMessages(defaultMessages, overrides);\n\tconst controller = useAiConfigDraft({\n\t\tvalue,\n\t\tfallbackValue: defaultAiService,\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: AiServiceEditorField, 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: optional(type) })}\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\t{field(\n\t\t\t\t\t\t'stage',\n\t\t\t\t\t\t patch({ stage: optional(stage) })}\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'environment',\n\t\t\t\t\t\t patch({ environment: optional(environment) })}\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'baseUrl',\n\t\t\t\t\t\t patch({ baseUrl: optional(baseUrl) })}\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'host',\n\t\t\t\t\t\t patch({ host: optional(host) })}\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'port',\n\t\t\t\t\t\t patch({ port: parseServicePort(port) })}\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'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'defaultEndpointId',\n\t\t\t\t\t\t patch({ defaultEndpointId: optional(defaultEndpointId) })}\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\n\t\t\t\t{field(\n\t\t\t\t\t'endpoints',\n\t\t\t\t\t patch({ endpoints })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\t{field(\n\t\t\t\t\t'env',\n\t\t\t\t\ttypeof draft.env === 'string' || draft.env === undefined ? (\n\t\t\t\t\t\t patch({ env: optional(env) })}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t patch({ env })}\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\n\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'capabilities',\n\t\t\t\t\t patch({ 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
\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'tags',\n\t\t\t\t\t\t patch({ tags })}\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'notes',\n\t\t\t\t\t patch({ notes: optional(notes) })}\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 Service['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 Service['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\nexport function parseServicePort(value: string): number | string | undefined {\n\tconst trimmed = value.trim();\n\tif (!trimmed) return undefined;\n\tif (/^[1-9]\\d*$/u.test(trimmed)) {\n\t\tconst numeric = Number(trimmed);\n\t\tif (Number.isSafeInteger(numeric)) return numeric;\n\t}\n\treturn trimmed;\n}\n\nfunction optional(value: string): string | undefined {\n\treturn value.trim() ? value : undefined;\n}\n", "type": "registry:component", "target": "@components/blocks/ai-service-editor/ai-service-editor.tsx" }, { "path": "registry/default/blocks/ai-service-editor/index.ts", "content": "export * from './ai-service-authentication-fields';\nexport * from './ai-service-editor';\nexport * from './ai-service-editor-types';\n", "type": "registry:component", "target": "@components/blocks/ai-service-editor/index.ts" } ], "type": "registry:block" }