+ {schema.items.map((item, idx) => {
+ const key = 'key' in item ? item.key : `__item${idx}`
+ const value = values[key]
+ const desc =
+ 'description' in item && item.description ? (
+
{item.description}
+ ) : null
+
+ if (item.type === 'divider') {
+ return item.label ? (
+
+ {item.label}
+
+ ) : (
+
+ )
+ }
+ if (item.type === 'info') {
+ return (
+
+ {item.label}
+ {desc}
+
+ )
+ }
+ if (item.type === 'button') {
+ return (
+
+
+
{item.label}
+ {desc}
+
+
+
+ )
+ }
+
+ let control: ReactNode = null
+ switch (item.type) {
+ case 'string':
+ control = (
+
onChange(item.key, e.target.value)}
+ className={`${inputCls} w-56 shrink-0`}
+ />
+ )
+ break
+ case 'text':
+ return (
+
+
{item.label}
+ {desc}
+
+ )
+ case 'number':
+ control = (
+
onChange(item.key, e.target.value === '' ? undefined : Number(e.target.value))}
+ className={`${inputCls} w-28 shrink-0`}
+ />
+ )
+ break
+ case 'boolean':
+ control = (
+
+ )
+ break
+ case 'select':
+ control = (
+
+ )
+ break
+ case 'multiSelect': {
+ const arr = Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : []
+ control = (
+
+ {item.options.map((o) => {
+ const on = arr.includes(o.value)
+ return (
+
+ )
+ })}
+
+ )
+ break
+ }
+ case 'slider':
+ control = (
+
+ onChange(item.key, Number(e.target.value))}
+ className="w-36"
+ />
+
+ {typeof value === 'number' ? value : '—'}
+
+
+ )
+ break
+ case 'color':
+ control = (
+
+ {typeof value === 'string' ? value : '—'}
+ onChange(item.key, e.target.value)}
+ className="h-7 w-9 cursor-pointer rounded-control border border-line bg-transparent"
+ />
+
+ )
+ break
+ case 'path':
+ case 'image': {
+ const mode = item.type === 'image' ? 'image' : item.mode
+ control = (
+
+ {item.type === 'image' && imgUrls[item.key] && (
+

+ )}
+
+ {typeof value === 'string' && value ? value : ('placeholder' in item && item.placeholder) || '未设置'}
+
+
+
+ )
+ break
+ }
+ }
+
+ return (
+
+
+
{item.label}
+ {desc}
+
+ {control}
+
+ )
+ })}
+
+ )
+}
+
/** 系统提示词预览:整段 pi 提示词是大文本,memo 化避免弹窗内任意状态变化触发重渲染;挂载耗时靠过渡提交兜住 */
const PromptPreview = memo(function PromptPreview({ loading, text }: { loading: boolean; text: string }): ReactNode {
return (
@@ -774,6 +1103,50 @@ export function SettingsModal({
onClose: () => void
}): JSX.Element | null {
const [tab, setTab] = useState