Files
maestro/app/src/api.js
T
wangjia 8765e61498 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>
2026-06-29 11:30:32 +08:00

81 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 真实数据层:直连本地 maestro daemon 的 REST + WS(按 ⑧ 前端 API 契约)。
// 路径用同源相对地址,dev 下经 vite.config.js 的 proxy 反代到 :4517。
async function http(method, path, body) {
const opt = { method, headers: {} };
if (body !== undefined) {
opt.headers['Content-Type'] = 'application/json';
opt.body = JSON.stringify(body);
}
const res = await fetch(path, opt);
if (!res.ok) {
let msg = res.statusText;
try { msg = (await res.json()).error || msg; } catch { /* 非 JSON 错误体 */ }
throw new Error(`${method} ${path}${res.status} ${msg}`);
}
if (res.status === 204) return null;
const ct = res.headers.get('content-type') || '';
return ct.includes('application/json') ? res.json() : res.text();
}
export const api = {
listProjects: () => http('GET', '/api/projects'),
projectTasks: (pid) => http('GET', `/api/projects/${pid}/tasks`),
projectEvents: (pid) => http('GET', `/api/projects/${pid}/events`),
agents: () => http('GET', '/api/agents'),
approvals: () => http('GET', '/api/approvals'),
usage: () => http('GET', '/api/usage'),
syncProject: (pid) => http('POST', `/api/projects/${pid}/sync`),
decide: (taskId, action, reason, merge) =>
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`),
requeueTask: (taskId) => http('POST', `/api/tasks/${taskId}/requeue`),
deleteTask: (taskId) => http('DELETE', `/api/tasks/${taskId}`),
// 附件上传走 multipart(不能用 JSON helpercontent-type 由浏览器带 boundary
uploadAttachments: async (taskId, files) => {
const fd = new FormData();
for (const f of files) fd.append('files', f, f.name);
const res = await fetch(`/api/tasks/${taskId}/attachments`, { method: 'POST', body: fd });
if (!res.ok) {
let msg = res.statusText;
try { msg = (await res.json()).error || msg; } catch { /* 非 JSON */ }
throw new Error(`上传附件失败 → ${res.status} ${msg}`);
}
return res.json();
},
};
// WS 单向订阅:连上后服务端推送 events 表记录。断线自动重连(指数退避,封顶 10s)。
export function subscribeEvents(onEvent, onStatus) {
let ws = null;
let backoff = 500;
let closedByUser = false;
function connect() {
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
ws = new WebSocket(`${proto}://${location.host}/ws`);
ws.onopen = () => { backoff = 500; onStatus?.('open'); };
ws.onmessage = (msg) => {
try { onEvent(JSON.parse(msg.data)); } catch { /* 忽略非 JSON 帧 */ }
};
ws.onclose = () => {
onStatus?.('closed');
if (closedByUser) return;
setTimeout(connect, backoff);
backoff = Math.min(backoff * 2, 10000);
};
ws.onerror = () => ws.close();
}
connect();
return () => { closedByUser = true; ws?.close(); };
}