diff --git a/app/src/api.js b/app/src/api.js
index 8056f70..3d75ab5 100644
--- a/app/src/api.js
+++ b/app/src/api.js
@@ -30,6 +30,7 @@ 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),
+ createProject: (body) => http('POST', '/api/projects', body),
};
// WS 单向订阅:连上后服务端推送 events 表记录。断线自动重连(指数退避,封顶 10s)。
diff --git a/app/src/app.jsx b/app/src/app.jsx
index ebfac06..16f736e 100644
--- a/app/src/app.jsx
+++ b/app/src/app.jsx
@@ -11,6 +11,43 @@ 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 }) {
+ 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: '创建' }
+ : { title: 'New project', name: 'Name', repo: 'Repo path', branch: 'Default branch', mode: 'Mode', namePh: 'my-app', repoPh: '/Users/you/code/my-app', create: 'Create' };
+ const [name, setName] = React.useState('');
+ const [repoPath, setRepoPath] = React.useState('');
+ const [defaultBranch, setDefaultBranch] = React.useState('main');
+ const [autonomy, setAutonomy] = React.useState('manual');
+ const submit = (e) => {
+ e.preventDefault();
+ if (!name.trim() || !repoPath.trim()) return;
+ onCreate({ name: name.trim(), repoPath: repoPath.trim(), defaultBranch: defaultBranch.trim() || 'main', autonomy });
+ };
+ return (
+
+ );
+}
+
export function App() {
const I18N = window.MAESTRO_I18N;
const [lang, setLang] = React.useState(() => {
@@ -166,6 +203,27 @@ export function App() {
try { await api.syncProject(currentId); toast('ok', t.toastSynced); loadProject(currentId); loadGlobal(); }
catch (e) { toast('warn', '同步失败:' + e.message); }
};
+ const createTask = async (payload) => {
+ try {
+ await api.createTask(currentId, payload);
+ toast('ok', t.toastCreated);
+ loadProject(currentId); loadGlobal();
+ } catch (e) { toast('warn', '创建失败:' + e.message); }
+ };
+ const saveConfig = async (patch) => {
+ try {
+ await api.patchProject(currentId, patch);
+ setShowConfig(false); toast('ok', t.toastSaved); loadGlobal();
+ } catch (e) { toast('warn', '保存失败:' + e.message); }
+ };
+ const [showNewProject, setShowNewProject] = React.useState(false);
+ const createProject = async (body) => {
+ try {
+ const p = await api.createProject(body);
+ setShowNewProject(false); toast('ok', '项目已创建 · ' + body.name);
+ await loadGlobal(); setCurrentId(p.id);
+ } catch (e) { toast('warn', '创建项目失败:' + e.message); }
+ };
return (
@@ -174,7 +232,7 @@ export function App() {
toast('warn', t.toastModal)}
collapsed={collapsed} onToggleCollapse={toggleCollapse}
- onSelect={setCurrentId} onNewProject={() => toast('warn', t.toastModal)} />
+ onSelect={setCurrentId} onNewProject={() => setShowNewProject(true)} />
{project ? setTheme(theme === 'light' ? 'dark' : 'light')}
@@ -183,13 +241,13 @@ export function App() {
onSync={onSync}
onToggleConfig={() => setShowConfig(!showConfig)} /> : null}
{showConfig && project ? { setShowConfig(false); toast('ok', t.toastSaved); }}
+ onSave={saveConfig}
onSync={onSync}
onClose={() => setShowConfig(false)} /> : null}
{tasks.length ? (
-
+
) : (
{t.empty}
@@ -200,6 +258,7 @@ export function App() {
{archiveItem ?
setArchiveItem(null)} /> : null}
+ {showNewProject ? setShowNewProject(false)} /> : null}
{toasts.map((x) => {x.text})}
diff --git a/design/ui_kits/console/TaskTree.jsx b/design/ui_kits/console/TaskTree.jsx
index c9bab6b..2110d87 100644
--- a/design/ui_kits/console/TaskTree.jsx
+++ b/design/ui_kits/console/TaskTree.jsx
@@ -201,22 +201,35 @@ function TaskDetail({ task, t, byId, onJump }) {
function NewTaskPanel({ tasks, onCancel, onCreate, t }) {
const { Button, Input, Select, ComplexitySeg } = window.MaestroDesignSystem_a6a290;
+ const [title, setTitle] = React.useState('');
+ const [complexity, setComplexity] = React.useState('auto');
+ const [priority, setPriority] = React.useState('1');
+ const [parentId, setParentId] = React.useState('');
+ const submit = (e) => {
+ e.preventDefault();
+ if (!title.trim()) return;
+ onCreate({ title: title.trim(), complexity, priority: Number(priority), parentId: parentId || null });
+ };
+ // 扁平化任务树供父任务下拉(含子任务)
+ const flat = [];
+ const walk = (list, prefix) => (list || []).forEach((tk) => { flat.push({ value: tk.id, label: prefix + tk.title }); walk(tk.children, prefix + '— '); });
+ walk(tasks, '');
return (