feat(console): 任务树筛选/折叠/统一标题 + 侧栏全局Agent卡片/拖拽排序/全局设置弹框/记忆项目 + 状态点真实化
任务树:四维多选下拉筛选(状态/复杂度/优先级/执行者)、已选置顶、新建任务按钮移右上角(0任务也能建)。 四块主区(Agent/审批闸/任务树/归档)统一标题组件(图标+名称+数量徽标)+ 折叠按钮(localStorage 记忆)。 侧栏:全局 Agent 弹层改常驻卡片(项目默认3+折叠+按活跃排序)、顶部项目拖拽排序、全局设置移入用户菜单弹框(保存/取消,落库)、记忆当前项目。 adaptProject 用后端 summary 的 running/attention 判状态点颜色(青/琥珀/灰)+ 紫色关注数徽标;agent 标题不再越界;i18n 5 语言补全。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+4
-4
@@ -19,13 +19,13 @@ export function adaptProject(p) {
|
||||
const s = p.summary || {};
|
||||
let state = 'idle';
|
||||
if (p.status === 'paused') state = 'paused';
|
||||
else if (s.executing > 0) state = 'running';
|
||||
else if (s.blocked > 0) state = 'blocked';
|
||||
else if (s.alive > 0) state = 'running';
|
||||
else if (s.running > 0) state = 'running'; // agent 在跑(executor/planner)→ 青点
|
||||
else if (s.blocked > 0) state = 'blocked'; // 被依赖阻塞 → 琥珀点
|
||||
// 其余 idle(灰点);待审批/需人工数走下面 pending 紫徽标
|
||||
return {
|
||||
id: p.id, name: p.name, path: shortPath(p.repoPath), branch: p.defaultBranch,
|
||||
autonomy: p.autonomy, concurrency: p.concurrency, hue: hueFor(p.name),
|
||||
state, pending: s.pending || 0, agents: s.executing || 0,
|
||||
state, pending: s.attention || 0, agents: s.executing || 0,
|
||||
// 透传给 ConfigPanel 用的原始字段
|
||||
model: p.model, verifyCmd: p.verifyCmd, maxRetries: p.maxRetries, repoPath: p.repoPath,
|
||||
logo: p.logo, budgetUsd: p.budgetUsd ?? null, budgetPeriod: p.budgetPeriod ?? 'month',
|
||||
|
||||
@@ -30,7 +30,10 @@ export const api = {
|
||||
http('POST', `/api/tasks/${taskId}/decide`, { action, reason, merge }),
|
||||
createTask: (pid, body) => http('POST', `/api/projects/${pid}/tasks`, body),
|
||||
patchProject: (pid, body) => http('PATCH', `/api/projects/${pid}`, body),
|
||||
reorderProjects: (order) => http('POST', '/api/projects/reorder', { order }),
|
||||
createProject: (body) => http('POST', '/api/projects', body),
|
||||
getSettings: () => http('GET', '/api/settings'),
|
||||
putSettings: (data) => http('PUT', '/api/settings', data),
|
||||
takeover: (taskId) => http('POST', `/api/tasks/${taskId}/takeover`),
|
||||
patchTask: (taskId, body) => http('PATCH', `/api/tasks/${taskId}`, body),
|
||||
cancelTask: (taskId) => http('POST', `/api/tasks/${taskId}/cancel`),
|
||||
|
||||
+33
-6
@@ -12,7 +12,7 @@ const { Toast } = window.MaestroDesignSystem_a6a290;
|
||||
const USER = { name: 'local', handle: '@local', plan: 'Claude Code', initial: 'L', hue: 200 };
|
||||
|
||||
// 新建项目模态(design 无现成 surface,用 DS 组件就地拼装)。标签 zh/en 自含。
|
||||
function NewProjectModal({ t, lang, onCreate, onClose }) {
|
||||
function NewProjectModal({ t, lang, onCreate, onClose, defaultAutonomy }) {
|
||||
const { Button, Input, Select } = window.MaestroDesignSystem_a6a290;
|
||||
const L = lang === 'zh'
|
||||
? { title: '新建项目', name: '项目名', repo: '仓库路径', branch: '默认分支', mode: '工作模式', namePh: 'my-app', repoPh: '/Users/you/code/my-app', create: '创建' }
|
||||
@@ -20,7 +20,7 @@ function NewProjectModal({ t, lang, onCreate, onClose }) {
|
||||
const [name, setName] = React.useState('');
|
||||
const [repoPath, setRepoPath] = React.useState('');
|
||||
const [defaultBranch, setDefaultBranch] = React.useState('main');
|
||||
const [autonomy, setAutonomy] = React.useState('manual');
|
||||
const [autonomy, setAutonomy] = React.useState(defaultAutonomy || 'manual');
|
||||
const submit = (e) => {
|
||||
e.preventDefault();
|
||||
if (!name.trim() || !repoPath.trim()) return;
|
||||
@@ -133,6 +133,7 @@ export function App() {
|
||||
return I18N[saved] ? saved : 'zh';
|
||||
});
|
||||
const t = I18N[lang];
|
||||
const { Button } = window.MaestroDesignSystem_a6a290;
|
||||
|
||||
// ── 真实数据 state ──────────────────────────────────
|
||||
const [projectsRaw, setProjectsRaw] = React.useState([]);
|
||||
@@ -141,11 +142,14 @@ export function App() {
|
||||
const [eventsRaw, setEventsRaw] = React.useState([]);
|
||||
const [agentsResp, setAgentsResp] = React.useState({ totalActive: 0, agents: [] });
|
||||
const [usage, setUsage] = React.useState(null);
|
||||
const [currentId, setCurrentId] = React.useState(null);
|
||||
const [settings, setSettings] = React.useState(null);
|
||||
const [currentId, setCurrentId] = React.useState(() => localStorage.getItem('maestro-kit-project') || null);
|
||||
React.useEffect(() => { if (currentId) localStorage.setItem('maestro-kit-project', currentId); }, [currentId]);
|
||||
const [wsState, setWsState] = React.useState('closed');
|
||||
|
||||
// ── UI state(沿用原型)─────────────────────────────
|
||||
const [showConfig, setShowConfig] = React.useState(false);
|
||||
const [showNew, setShowNew] = React.useState(false);
|
||||
const [archiveItem, setArchiveItem] = React.useState(null);
|
||||
const [streamAgent, setStreamAgent] = React.useState(null);
|
||||
const [takeoverInfo, setTakeoverInfo] = React.useState(null);
|
||||
@@ -210,7 +214,9 @@ export function App() {
|
||||
try {
|
||||
const [pj, ag, us, ap] = await Promise.all([api.listProjects(), api.agents(), api.usage(), api.approvals()]);
|
||||
setProjectsRaw(pj); setAgentsResp(ag); setUsage(us); setApprovalsRaw(ap);
|
||||
setCurrentId((cur) => cur || (pj[0] && pj[0].id) || null);
|
||||
// settings 端点可能未部署到旧 daemon:独立拉取,失败则降级为默认(不阻断主加载)
|
||||
api.getSettings().then(setSettings).catch(() => setSettings(null));
|
||||
setCurrentId((cur) => (cur && pj.some((p) => p.id === cur) ? cur : (pj[0] && pj[0].id) || null));
|
||||
} catch (e) { toast('warn', '加载失败:' + e.message); }
|
||||
}, []);
|
||||
|
||||
@@ -223,6 +229,18 @@ export function App() {
|
||||
} catch (e) { toast('warn', '加载任务失败:' + e.message); }
|
||||
}, []);
|
||||
|
||||
// 左侧栏项目拖拽排序:乐观重排 + 落库(reorder 写 sortOrder)
|
||||
const reorderProjects = async (order) => {
|
||||
setProjectsRaw((prev) => order.map((id) => prev.find((p) => p.id === id)).filter(Boolean));
|
||||
try { await api.reorderProjects(order); } catch (e) { toast('warn', '排序保存失败:' + e.message); loadGlobal(); }
|
||||
};
|
||||
|
||||
// 用户级全局默认配置:弹框保存 → 落库
|
||||
const saveSettings = async (data) => {
|
||||
try { const saved = await api.putSettings(data); setSettings(saved); toast('ok', t.toastSaved); }
|
||||
catch (e) { toast('warn', '保存失败:' + e.message); }
|
||||
};
|
||||
|
||||
React.useEffect(() => { loadGlobal(); }, [loadGlobal]);
|
||||
React.useEffect(() => { loadProject(currentId); }, [currentId, loadProject]);
|
||||
|
||||
@@ -346,7 +364,8 @@ export function App() {
|
||||
<window.MaestroKitSidebar projects={projects} currentId={currentId} t={t}
|
||||
global={global} user={USER} summary={agentSummary} onGlobalConfig={() => toast('warn', t.toastModal)}
|
||||
collapsed={collapsed} onToggleCollapse={toggleCollapse}
|
||||
onSelect={setCurrentId} onNewProject={() => setShowNewProject(true)} />
|
||||
onSelect={setCurrentId} onNewProject={() => setShowNewProject(true)} onReorder={reorderProjects}
|
||||
settings={settings} onSaveSettings={saveSettings} />
|
||||
<main style={{ overflowY: 'auto', padding: '0 22px 60px' }}>
|
||||
{project ? <window.MaestroKitTopbar project={project} t={t} theme={theme}
|
||||
onToggleTheme={() => setTheme(theme === 'light' ? 'dark' : 'light')}
|
||||
@@ -354,6 +373,14 @@ export function App() {
|
||||
counts={counts}
|
||||
onSync={onSync}
|
||||
onToggleConfig={() => setShowConfig(!showConfig)} /> : null}
|
||||
{project ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 14 }}>
|
||||
<Button variant="solid" onClick={() => setShowNew((s) => !s)}>{t.newTask}</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{showNew && project ? <window.MaestroKitNewTaskPanel tasks={tasks} t={t}
|
||||
onCancel={() => setShowNew(false)}
|
||||
onCreate={(payload) => { setShowNew(false); createTask(payload); }} /> : null}
|
||||
{showConfig && project ? <window.MaestroKitConfigPanel project={project} t={t} lang={lang}
|
||||
onSave={saveConfig}
|
||||
onSync={onSync}
|
||||
@@ -372,7 +399,7 @@ export function App() {
|
||||
</main>
|
||||
<window.MaestroKitEventPanel events={events} collapsed={evCollapsed} onToggleCollapse={toggleEvents} t={t} />
|
||||
{archiveItem ? <window.MaestroKitArchiveModal item={archiveItem} t={t} onClose={() => setArchiveItem(null)} /> : null}
|
||||
{showNewProject ? <NewProjectModal t={t} lang={lang} onCreate={createProject} onClose={() => setShowNewProject(false)} /> : null}
|
||||
{showNewProject ? <NewProjectModal t={t} lang={lang} defaultAutonomy={settings?.autonomy} onCreate={createProject} onClose={() => setShowNewProject(false)} /> : null}
|
||||
{streamAgent ? <StreamModal agent={streamAgent} onClose={() => setStreamAgent(null)} /> : null}
|
||||
{takeoverInfo ? <TakeoverModal info={takeoverInfo} onClose={() => setTakeoverInfo(null)} /> : null}
|
||||
<div style={{ position: 'fixed', bottom: 18, left: '50%', transform: 'translateX(-50%)', zIndex: 1200, display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center' }}>
|
||||
|
||||
Reference in New Issue
Block a user