// 项目设置面板(提案 2026-09-30-cross-session-reading):首个项目级设置 UI。 // schema 驱动渲染 project scope(4.9 覆盖链:system → user → project), // 写 putProjectSettings;后续项目级设置项经 settings schema 自动出现,零开发。 import { useEffect, useMemo, useState } from 'react' import { api } from '../api' import { store, useStore } from '../store' import type { SettingDesc } from '../contracts' import { SettingsShell, Btn, EmptyState } from '@ui' import { SettingInput, flatten, unflatten, groupNames } from './SettingsModal' export function ProjectSettingsModal() { const projectId = useStore(s => s.ui.projectSettingsId) const projects = useStore(s => s.projects) const project = projects.find(p => p.id === projectId) const [schema, setSchema] = useState([]) const [draft, setDraft] = useState>({}) const [loaded, setLoaded] = useState(false) const [saved, setSaved] = useState(false) useEffect(() => { if (!projectId) return setLoaded(false) api.getSettings(projectId).then(s => { setSchema(s.schema) setDraft(flatten(s.project)) setLoaded(true) }).catch(() => undefined) }, [projectId]) const groups = useMemo(() => { const g: Record = {} for (const d of schema) (g[d.group] ??= []).push(d) return g }, [schema]) if (!projectId) return null const save = async () => { if (!projectId) return try { await api.putProjectSettings(projectId, unflatten(draft)) setSaved(true) setTimeout(() => setSaved(false), 1500) } catch (e) { store.toast(`项目设置保存失败:${e}`) } } return ( undefined} onClose={() => store.openProjectSettings(null)} sections={[{ key: 'project', label: '本项目', icon: 'folder' }]} footer={saved ? ✓ 已保存 : '项目设置覆盖用户设置(new-run 类对新任务生效)'} >
{!loaded ? ( 加载中… ) : ( <>
此处仅保存本项目的覆盖值;留空即沿用用户/系统设置。跨会话读取在「会话协作」组。
{Object.entries(groups).map(([g, descs]) => (

{groupNames[g] ?? g}

{descs.map(d => (
setDraft(x => ({ ...x, [d.key]: v }))} /> {d.effect === 'hot' ? '立即' : d.effect === 'new-run' ? '新任务' : '需重启'}
))}
))} 保存项目设置 )}
) }