Download apps/web/src/components/features/NativeConfigurationEditor.tsx from hunian003/message: direct link, hf CLI and curl.
- Browser
- Download file 5.35 kB
-
https://huggingface.co/spaces/hunian003/message/resolve/main/apps/web/src/components/features/NativeConfigurationEditor.tsx
- Command line
-
hf download hf://spaces/hunian003/message/apps/web/src/components/features/NativeConfigurationEditor.tsx
-
curl -L -o NativeConfigurationEditor.tsx https://huggingface.co/spaces/hunian003/message/resolve/main/apps/web/src/components/features/NativeConfigurationEditor.tsx
5.35 kB
| import { useState, type FormEvent } from 'react'; | |
| import { Button } from '@components/ui/button'; | |
| import { Label } from '@components/ui/label'; | |
| import type { NativePlugin } from '@typings/native'; | |
| const field = 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring'; | |
| const reasons: Record<string, string> = { | |
| 'transport-managed-by-host': 'MCP 传输由宿主管理,不能通过插件配置修改', | |
| 'management-required': '系统核心条目由宿主管理', | |
| unaddressable: '此条目没有可写的 profile 配置行', | |
| }; | |
| const entryId = (plugin: NativePlugin) => plugin.entry_id ?? plugin.entryId ?? ''; | |
| interface ConfigurationEditorProps { | |
| plugins: NativePlugin[]; | |
| busy: boolean; | |
| onConfigure: (id: string, name: string, config: Record<string, unknown>) => void; | |
| } | |
| /** 配置写入期望状态;用户明确提交后才进入异步确认流程。 */ | |
| export function NativeConfigurationEditor({ plugins, busy, onConfigure }: ConfigurationEditorProps) { | |
| const [selectedId, setSelectedId] = useState(''); | |
| const [draft, setDraft] = useState('{}'); | |
| const [error, setError] = useState(''); | |
| const selected = plugins.find(plugin => entryId(plugin) === selectedId); | |
| const readOnlyReason = selected?.readOnlyReason | |
| ? reasons[selected.readOnlyReason] ?? selected.readOnlyReason | |
| : !selected || !entryId(selected) ? '请选择具有完整条目标识的插件' | |
| : !selected.enabled ? '请先在期望配置中启用该 bundle,再编辑配置' | |
| : selected.config === undefined ? '服务端尚未提供当前配置,请刷新后重试,避免覆盖已有值' : ''; | |
| const restore = (plugin = selected) => { | |
| setDraft(JSON.stringify(plugin?.config ?? {}, null, 2)); | |
| setError(''); | |
| }; | |
| const submit = (event: FormEvent) => { | |
| event.preventDefault(); | |
| if (!selected || readOnlyReason || busy) return; | |
| try { | |
| const value: unknown = JSON.parse(draft); | |
| if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(); | |
| setError(''); | |
| onConfigure(entryId(selected), selected.moduleName, value as Record<string, unknown>); | |
| } catch { setError('配置必须是有效的 JSON 对象,例如 {"prefix":"示例:"}'); } | |
| }; | |
| return <form onSubmit={submit} className="space-y-3 border-t border-console-border pt-5" aria-labelledby="plugin-config-heading"> | |
| <h3 id="plugin-config-heading" className="text-sm font-semibold">编辑插件配置</h3> | |
| <p className="text-sm text-console-text-secondary">选择可编辑的插件条目,提交会替换该条目的完整 JSON 配置。通用 JSON 禁止填写字面密码、token、key 等真实凭据;需要凭据的字段请使用环境变量引用。变更先进入期望状态,不立即中断会话,重启后才生效。</p> | |
| <div className="rounded-md bg-console-surface-raised p-3 text-xs leading-relaxed text-console-text-secondary"> | |
| <p>引用必须是精确的 JSON 对象:<span className="font-mono">{'{"$env":"SERVICE_TOKEN"}'}</span>。这里只填写引用名,请勿输入真实值。</p> | |
| <p className="mt-2">名称必须位于运营者 MESSAGE_PLUGIN_ENV_ALLOWLIST_JSON 为当前 profile 配置的允许名单中。实际值由运营者环境提供,仅注入该 profile 的 worker;配置界面、API 和操作历史只保存或显示引用。引用未获允许或实际值缺失时,启动将失败。</p> | |
| <p className="mt-2">页面只检查 JSON 对象结构;引用规则及插件配置类型由后端验证。这里不提供凭据保存功能。</p> | |
| </div> | |
| <div><Label htmlFor="config-plugin">配置目标条目</Label> | |
| <select id="config-plugin" className={`${field} mt-2`} value={selectedId} disabled={busy} onChange={event => { | |
| const id = event.target.value; setSelectedId(id); restore(plugins.find(plugin => entryId(plugin) === id)); | |
| }}><option value="">请选择插件条目</option>{plugins.map((plugin, index) => <option key={entryId(plugin) || index} value={entryId(plugin)} disabled={!entryId(plugin)}>{plugin.moduleName} · {entryId(plugin) || '无条目标识'}{plugin.readOnlyReason ? '(只读)' : ''}</option>)}</select> | |
| </div> | |
| {selected && <> | |
| <p className="break-all text-xs text-console-text-secondary">条目 {entryId(selected)} · 配置行 {selected.row_id ?? selected.patchId ?? '—'}</p> | |
| {readOnlyReason && <p role="note" className="rounded-md bg-console-surface-raised p-3 text-sm">只读:{readOnlyReason}</p>} | |
| <div><Label htmlFor="plugin-config-json">插件配置 JSON</Label><textarea id="plugin-config-json" className={`${field} mt-2 font-mono`} rows={8} spellCheck={false} value={draft} onChange={event => { setDraft(event.target.value); setError(''); }} readOnly={Boolean(readOnlyReason)} disabled={busy} /></div> | |
| {error && <p role="alert" className="text-sm text-red-700">{error}</p>} | |
| <div className="flex flex-wrap gap-2"><Button type="submit" disabled={busy || Boolean(readOnlyReason)}>提交配置</Button><Button type="button" variant="outline" onClick={() => restore()} disabled={busy}>恢复当前配置</Button></div> | |
| </>} | |
| {!plugins.length && <p className="text-sm text-console-text-secondary">暂无插件条目,请先安装 bundle 并读取 profile 状态</p>} | |
| </form>; | |
| } | |