@humanclaw/humanclaw 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +757 -129
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -368,6 +368,246 @@ function dispatchJob(request, db2) {
|
|
|
368
368
|
return { ...job, tasks };
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
// src/llm/claude.ts
|
|
372
|
+
var ClaudeProvider = class {
|
|
373
|
+
apiKey;
|
|
374
|
+
model;
|
|
375
|
+
constructor(apiKey, model) {
|
|
376
|
+
this.apiKey = apiKey;
|
|
377
|
+
this.model = model || "claude-sonnet-4-20250514";
|
|
378
|
+
}
|
|
379
|
+
async complete(request) {
|
|
380
|
+
const systemMessages = request.messages.filter((m) => m.role === "system");
|
|
381
|
+
const nonSystemMessages = request.messages.filter((m) => m.role !== "system");
|
|
382
|
+
const body = {
|
|
383
|
+
model: this.model,
|
|
384
|
+
max_tokens: request.max_tokens || 4096,
|
|
385
|
+
messages: nonSystemMessages.map((m) => ({ role: m.role, content: m.content }))
|
|
386
|
+
};
|
|
387
|
+
if (request.temperature !== void 0) {
|
|
388
|
+
body.temperature = request.temperature;
|
|
389
|
+
}
|
|
390
|
+
if (systemMessages.length > 0) {
|
|
391
|
+
body.system = systemMessages.map((m) => m.content).join("\n\n");
|
|
392
|
+
}
|
|
393
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
394
|
+
method: "POST",
|
|
395
|
+
headers: {
|
|
396
|
+
"Content-Type": "application/json",
|
|
397
|
+
"x-api-key": this.apiKey,
|
|
398
|
+
"anthropic-version": "2023-06-01"
|
|
399
|
+
},
|
|
400
|
+
body: JSON.stringify(body)
|
|
401
|
+
});
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
const errorText = await response.text();
|
|
404
|
+
throw new Error(`Claude API error (${response.status}): ${errorText}`);
|
|
405
|
+
}
|
|
406
|
+
const data = await response.json();
|
|
407
|
+
const textBlock = data.content.find((c) => c.type === "text");
|
|
408
|
+
if (!textBlock) {
|
|
409
|
+
throw new Error("Claude API returned no text content");
|
|
410
|
+
}
|
|
411
|
+
return { content: textBlock.text };
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/llm/openai.ts
|
|
416
|
+
var OpenAIProvider = class {
|
|
417
|
+
apiKey;
|
|
418
|
+
model;
|
|
419
|
+
constructor(apiKey, model) {
|
|
420
|
+
this.apiKey = apiKey;
|
|
421
|
+
this.model = model || "gpt-4o";
|
|
422
|
+
}
|
|
423
|
+
async complete(request) {
|
|
424
|
+
const body = {
|
|
425
|
+
model: this.model,
|
|
426
|
+
max_tokens: request.max_tokens || 4096,
|
|
427
|
+
messages: request.messages.map((m) => ({ role: m.role, content: m.content }))
|
|
428
|
+
};
|
|
429
|
+
if (request.temperature !== void 0) {
|
|
430
|
+
body.temperature = request.temperature;
|
|
431
|
+
}
|
|
432
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers: {
|
|
435
|
+
"Content-Type": "application/json",
|
|
436
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
437
|
+
},
|
|
438
|
+
body: JSON.stringify(body)
|
|
439
|
+
});
|
|
440
|
+
if (!response.ok) {
|
|
441
|
+
const errorText = await response.text();
|
|
442
|
+
throw new Error(`OpenAI API error (${response.status}): ${errorText}`);
|
|
443
|
+
}
|
|
444
|
+
const data = await response.json();
|
|
445
|
+
const content = data.choices[0]?.message?.content;
|
|
446
|
+
if (!content) {
|
|
447
|
+
throw new Error("OpenAI API returned no content");
|
|
448
|
+
}
|
|
449
|
+
return { content };
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// src/llm/index.ts
|
|
454
|
+
function getLlmConfig() {
|
|
455
|
+
const provider = process.env.HUMANCLAW_LLM_PROVIDER || "claude";
|
|
456
|
+
const apiKey = process.env.HUMANCLAW_LLM_API_KEY || "";
|
|
457
|
+
const model = process.env.HUMANCLAW_LLM_MODEL;
|
|
458
|
+
if (!apiKey) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
"\u672A\u914D\u7F6E LLM API Key\u3002\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF HUMANCLAW_LLM_API_KEY\u3002\n\u4F8B\u5982: export HUMANCLAW_LLM_API_KEY=sk-..."
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
if (provider !== "claude" && provider !== "openai") {
|
|
464
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 LLM \u63D0\u4F9B\u5546: ${provider}\u3002\u652F\u6301: claude, openai`);
|
|
465
|
+
}
|
|
466
|
+
return { provider, apiKey, model };
|
|
467
|
+
}
|
|
468
|
+
function createLlmProvider(config) {
|
|
469
|
+
const cfg = config || getLlmConfig();
|
|
470
|
+
switch (cfg.provider) {
|
|
471
|
+
case "claude":
|
|
472
|
+
return new ClaudeProvider(cfg.apiKey, cfg.model);
|
|
473
|
+
case "openai":
|
|
474
|
+
return new OpenAIProvider(cfg.apiKey, cfg.model);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/services/planner.ts
|
|
479
|
+
function buildSystemPrompt() {
|
|
480
|
+
return `\u4F60\u662F HumanClaw \u4EFB\u52A1\u7F16\u6392\u89C4\u5212\u5668\u3002\u4F60\u7684\u5DE5\u4F5C\u662F\u5C06\u7528\u6237\u7684\u9700\u6C42\u62C6\u89E3\u4E3A\u53EF\u4EE5\u5206\u53D1\u7ED9\u7269\u7406\u8282\u70B9\uFF08\u771F\u5B9E\u4EBA\u7C7B\uFF09\u6267\u884C\u7684\u72EC\u7ACB\u5B50\u4EFB\u52A1\u3002
|
|
481
|
+
|
|
482
|
+
\u89C4\u5219\uFF1A
|
|
483
|
+
1. \u5C06\u9700\u6C42\u62C6\u89E3\u4E3A\u6241\u5E73\u7684\u3001\u65E0\u4F9D\u8D56\u7684\u5B50\u4EFB\u52A1\u5217\u8868\uFF08\u6BCF\u4E2A\u4EFB\u52A1\u53EF\u4EE5\u72EC\u7ACB\u6267\u884C\uFF09
|
|
484
|
+
2. \u6839\u636E\u6BCF\u4E2A Agent \u7684\u6280\u80FD\uFF08capabilities\uFF09\u548C\u5F53\u524D\u8D1F\u8F7D\u6765\u5339\u914D\u5206\u914D
|
|
485
|
+
3. \u4E3A\u6BCF\u4E2A\u4EFB\u52A1\u751F\u6210\u4E00\u6BB5\u300C\u8BDD\u672F\u300D\u2014\u2014 \u8FD9\u662F\u76F4\u63A5\u53D1\u7ED9\u8BE5\u4EBA\u7C7B\u6267\u884C\u8005\u7684\u4EFB\u52A1\u8BF4\u660E\uFF0C\u8BED\u6C14\u81EA\u7136\u3001\u6E05\u6670\u3001\u4E13\u4E1A\uFF0C\u5305\u542B\u5177\u4F53\u7684\u4EA4\u4ED8\u7269\u8981\u6C42
|
|
486
|
+
4. \u6839\u636E\u4EFB\u52A1\u590D\u6742\u5EA6\u8BBE\u7F6E\u5408\u7406\u7684\u622A\u6B62\u65F6\u95F4\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u65F6\u95F4\uFF09
|
|
487
|
+
5. \u6BCF\u4E2A Agent \u6700\u591A\u5206\u914D\u4E00\u4E2A\u4EFB\u52A1\uFF08\u9664\u975E\u4EBA\u624B\u4E0D\u591F\uFF09
|
|
488
|
+
|
|
489
|
+
\u4F60\u5FC5\u987B\u4E25\u683C\u8F93\u51FA\u4EE5\u4E0B JSON \u683C\u5F0F\uFF08\u4E0D\u8981\u8F93\u51FA\u4EFB\u4F55\u5176\u4ED6\u5185\u5BB9\uFF09\uFF1A
|
|
490
|
+
|
|
491
|
+
\`\`\`json
|
|
492
|
+
[
|
|
493
|
+
{
|
|
494
|
+
"assignee_id": "agent\u7684ID",
|
|
495
|
+
"assignee_name": "agent\u7684\u540D\u5B57",
|
|
496
|
+
"todo_description": "\u7B80\u77ED\u7684\u4EFB\u52A1\u6807\u9898/\u63CF\u8FF0",
|
|
497
|
+
"briefing": "\u8BDD\u672F\uFF1A\u8BE6\u7EC6\u7684\u4EFB\u52A1\u8BF4\u660E\uFF0C\u5305\u62EC\u76EE\u6807\u3001\u4EA4\u4ED8\u7269\u8981\u6C42\u3001\u6CE8\u610F\u4E8B\u9879\u3002\u8BED\u6C14\u50CF\u4E00\u4E2A\u9760\u8C31\u7684\u9879\u76EE\u7ECF\u7406\u5728\u7ED9\u7EC4\u5458\u5206\u914D\u4EFB\u52A1\u3002",
|
|
498
|
+
"deadline": "ISO 8601 \u65F6\u95F4"
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
\`\`\``;
|
|
502
|
+
}
|
|
503
|
+
function buildUserPrompt(prompt, agents) {
|
|
504
|
+
const now = /* @__PURE__ */ new Date();
|
|
505
|
+
const agentList = agents.map((a) => {
|
|
506
|
+
const load = a.active_task_count > 0 ? `\u5F53\u524D\u6709 ${a.active_task_count} \u4E2A\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1` : "\u5F53\u524D\u7A7A\u95F2";
|
|
507
|
+
const speed = a.avg_delivery_hours !== null ? `\u5E73\u5747\u4EA4\u4ED8\u65F6\u95F4 ${a.avg_delivery_hours}h` : "\u6682\u65E0\u5386\u53F2\u6570\u636E";
|
|
508
|
+
return `- ${a.name} (ID: ${a.agent_id}) \u6280\u80FD: [${a.capabilities.join(", ")}] ${load} ${speed}`;
|
|
509
|
+
}).join("\n");
|
|
510
|
+
return `\u5F53\u524D\u65F6\u95F4: ${now.toISOString()}
|
|
511
|
+
|
|
512
|
+
\u53EF\u7528\u7684\u7269\u7406\u8282\u70B9\uFF1A
|
|
513
|
+
${agentList}
|
|
514
|
+
|
|
515
|
+
\u9700\u6C42\uFF1A
|
|
516
|
+
${prompt}
|
|
517
|
+
|
|
518
|
+
\u8BF7\u6839\u636E\u4EE5\u4E0A\u4FE1\u606F\u62C6\u89E3\u4EFB\u52A1\u5E76\u5206\u914D\u3002\u8F93\u51FA JSON \u6570\u7EC4\u3002`;
|
|
519
|
+
}
|
|
520
|
+
function extractJson(raw) {
|
|
521
|
+
const codeBlockMatch = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
522
|
+
if (codeBlockMatch) {
|
|
523
|
+
return codeBlockMatch[1].trim();
|
|
524
|
+
}
|
|
525
|
+
const arrayMatch = raw.match(/\[[\s\S]*\]/);
|
|
526
|
+
if (arrayMatch) {
|
|
527
|
+
return arrayMatch[0];
|
|
528
|
+
}
|
|
529
|
+
return raw.trim();
|
|
530
|
+
}
|
|
531
|
+
function validatePlannedTasks(parsed, validAgentIds, agents) {
|
|
532
|
+
if (!Array.isArray(parsed)) {
|
|
533
|
+
throw new Error("LLM \u8FD4\u56DE\u7684\u4E0D\u662F\u6709\u6548\u7684\u4EFB\u52A1\u6570\u7EC4");
|
|
534
|
+
}
|
|
535
|
+
if (parsed.length === 0) {
|
|
536
|
+
throw new Error("LLM \u672A\u751F\u6210\u4EFB\u4F55\u4EFB\u52A1");
|
|
537
|
+
}
|
|
538
|
+
return parsed.map((item, i) => {
|
|
539
|
+
if (!item.todo_description || typeof item.todo_description !== "string") {
|
|
540
|
+
throw new Error(`\u4EFB\u52A1 ${i + 1} \u7F3A\u5C11 todo_description`);
|
|
541
|
+
}
|
|
542
|
+
if (!item.briefing || typeof item.briefing !== "string") {
|
|
543
|
+
throw new Error(`\u4EFB\u52A1 ${i + 1} \u7F3A\u5C11 briefing (\u8BDD\u672F)`);
|
|
544
|
+
}
|
|
545
|
+
let assigneeId = String(item.assignee_id || "");
|
|
546
|
+
let assigneeName = String(item.assignee_name || "");
|
|
547
|
+
if (!validAgentIds.has(assigneeId)) {
|
|
548
|
+
const fallback = agents[0];
|
|
549
|
+
if (fallback) {
|
|
550
|
+
assigneeId = fallback.agent_id;
|
|
551
|
+
assigneeName = fallback.name;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
let deadline = String(item.deadline || "");
|
|
555
|
+
if (!deadline || isNaN(Date.parse(deadline))) {
|
|
556
|
+
deadline = new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString();
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
assignee_id: assigneeId,
|
|
560
|
+
assignee_name: assigneeName,
|
|
561
|
+
todo_description: String(item.todo_description),
|
|
562
|
+
briefing: String(item.briefing),
|
|
563
|
+
deadline
|
|
564
|
+
};
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
async function planJob(request, provider, db2) {
|
|
568
|
+
const conn = db2 ?? getDb();
|
|
569
|
+
const allAgents = listAgentsWithMetrics(conn);
|
|
570
|
+
let agents;
|
|
571
|
+
if (request.agent_ids && request.agent_ids.length > 0) {
|
|
572
|
+
agents = allAgents.filter((a) => request.agent_ids.includes(a.agent_id));
|
|
573
|
+
if (agents.length === 0) {
|
|
574
|
+
throw new Error("\u6307\u5B9A\u7684 Agent \u5747\u4E0D\u5B58\u5728");
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
agents = allAgents.filter((a) => a.status === "IDLE");
|
|
578
|
+
if (agents.length === 0) {
|
|
579
|
+
agents = allAgents.filter((a) => a.status !== "OFFLINE" && a.status !== "OOM");
|
|
580
|
+
}
|
|
581
|
+
if (agents.length === 0) {
|
|
582
|
+
throw new Error("\u6CA1\u6709\u53EF\u7528\u7684\u7269\u7406\u8282\u70B9\u3002\u8BF7\u5148\u5728\u78B3\u57FA\u7B97\u529B\u6C60\u4E2D\u6DFB\u52A0\u8282\u70B9\u3002");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const llm = provider ?? createLlmProvider();
|
|
586
|
+
const systemPrompt = buildSystemPrompt();
|
|
587
|
+
const userPrompt = buildUserPrompt(request.prompt, agents);
|
|
588
|
+
const response = await llm.complete({
|
|
589
|
+
messages: [
|
|
590
|
+
{ role: "system", content: systemPrompt },
|
|
591
|
+
{ role: "user", content: userPrompt }
|
|
592
|
+
],
|
|
593
|
+
temperature: 0.3,
|
|
594
|
+
max_tokens: 4096
|
|
595
|
+
});
|
|
596
|
+
const jsonStr = extractJson(response.content);
|
|
597
|
+
let parsed;
|
|
598
|
+
try {
|
|
599
|
+
parsed = JSON.parse(jsonStr);
|
|
600
|
+
} catch {
|
|
601
|
+
throw new Error("AI \u8FD4\u56DE\u7684\u5185\u5BB9\u65E0\u6CD5\u89E3\u6790\u4E3A JSON\uFF0C\u8BF7\u91CD\u8BD5");
|
|
602
|
+
}
|
|
603
|
+
const validIds = new Set(agents.map((a) => a.agent_id));
|
|
604
|
+
const plannedTasks = validatePlannedTasks(parsed, validIds, agents);
|
|
605
|
+
return {
|
|
606
|
+
original_prompt: request.prompt,
|
|
607
|
+
planned_tasks: plannedTasks
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
371
611
|
// src/routes/jobs.ts
|
|
372
612
|
var router2 = Router2();
|
|
373
613
|
router2.post("/create", (req, res) => {
|
|
@@ -399,6 +639,21 @@ router2.get("/active", (_req, res) => {
|
|
|
399
639
|
const jobs = listActiveJobs();
|
|
400
640
|
res.json({ jobs });
|
|
401
641
|
});
|
|
642
|
+
router2.post("/plan", async (req, res) => {
|
|
643
|
+
const body = req.body;
|
|
644
|
+
if (!body.prompt || typeof body.prompt !== "string") {
|
|
645
|
+
res.status(400).json({ error: "prompt is required" });
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
const plan = await planJob(body);
|
|
650
|
+
res.json(plan);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
653
|
+
const status = message.includes("API Key") || message.includes("API key") ? 503 : 400;
|
|
654
|
+
res.status(status).json({ error: message });
|
|
655
|
+
}
|
|
656
|
+
});
|
|
402
657
|
router2.get("/:job_id", (req, res) => {
|
|
403
658
|
const { job_id } = req.params;
|
|
404
659
|
const job = getJobWithTasks(job_id);
|
|
@@ -572,133 +827,254 @@ function getDashboardHtml() {
|
|
|
572
827
|
<head>
|
|
573
828
|
<meta charset="UTF-8"/>
|
|
574
829
|
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
|
575
|
-
<title>HumanClaw -
|
|
830
|
+
<title>HumanClaw - \u5F02\u6B65\u7269\u7406\u8282\u70B9\u7F16\u6392</title>
|
|
576
831
|
<style>
|
|
577
|
-
:root{--bg:#
|
|
832
|
+
:root{--bg:#0f1117;--surface:#1a1d27;--surface-hover:#242836;--border:#2e3346;--text:#e2e4ed;--text-dim:#7b8196;--accent:#00d4ff;--accent-dim:rgba(0,212,255,.12);--green:#22c55e;--yellow:#eab308;--red:#ef4444;--purple:#a855f7;--font-mono:'SF Mono','JetBrains Mono','Fira Code',monospace;--font-sans:-apple-system,'Inter','Segoe UI',sans-serif;--radius:10px}
|
|
578
833
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
579
|
-
body{background:var(--bg);color:var(--text);font-family:var(--font-sans);min-height:100vh}
|
|
580
|
-
header{padding:
|
|
581
|
-
header h1{font-family:var(--font-mono);font-size:
|
|
582
|
-
.subtitle{color:var(--text-dim);font-size:
|
|
583
|
-
nav{display:flex;
|
|
584
|
-
.tab{background:none;border:none;color:var(--text-dim);padding:12px
|
|
585
|
-
.tab:hover{color:var(--text)}
|
|
834
|
+
body{background:var(--bg);color:var(--text);font-family:var(--font-sans);min-height:100vh;line-height:1.5}
|
|
835
|
+
header{padding:20px 32px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:14px}
|
|
836
|
+
header h1{font-family:var(--font-mono);font-size:22px;color:var(--accent);letter-spacing:1.5px}
|
|
837
|
+
.subtitle{color:var(--text-dim);font-size:12px;border-left:1px solid var(--border);padding-left:14px}
|
|
838
|
+
nav{display:flex;border-bottom:1px solid var(--border);padding:0 32px;gap:4px}
|
|
839
|
+
.tab{background:none;border:none;color:var(--text-dim);padding:12px 18px;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-family:var(--font-sans);font-weight:500}
|
|
840
|
+
.tab:hover{color:var(--text);background:var(--surface)}
|
|
586
841
|
.tab.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
587
|
-
main{padding:24px 32px}
|
|
842
|
+
main{padding:24px 32px;max-width:1200px}
|
|
588
843
|
.panel{display:none}.panel.active{display:block}
|
|
589
|
-
|
|
590
|
-
.
|
|
591
|
-
.
|
|
592
|
-
.
|
|
593
|
-
|
|
594
|
-
.
|
|
595
|
-
.
|
|
596
|
-
.
|
|
597
|
-
.
|
|
598
|
-
.agent-
|
|
599
|
-
.
|
|
600
|
-
.
|
|
601
|
-
.
|
|
602
|
-
.agent-
|
|
603
|
-
.
|
|
604
|
-
.
|
|
605
|
-
.
|
|
606
|
-
|
|
607
|
-
.
|
|
608
|
-
.
|
|
609
|
-
.
|
|
610
|
-
.
|
|
611
|
-
|
|
612
|
-
.
|
|
613
|
-
.
|
|
614
|
-
.
|
|
615
|
-
.
|
|
616
|
-
.
|
|
617
|
-
.
|
|
618
|
-
.
|
|
619
|
-
.
|
|
620
|
-
.
|
|
621
|
-
.
|
|
622
|
-
.
|
|
623
|
-
.
|
|
624
|
-
.form-group input,.form-group textarea{width:100%;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:10px 14px;color:var(--text);font-size:14px;font-family:var(--font-mono);outline:none;transition:border-color .2s}
|
|
625
|
-
.form-group input:focus,.form-group textarea:focus{border-color:var(--accent)}
|
|
626
|
-
.form-group textarea{min-height:120px;resize:vertical}
|
|
627
|
-
.btn-group{display:flex;gap:12px;margin-top:20px}
|
|
628
|
-
.btn{padding:10px 24px;border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:opacity .2s}
|
|
629
|
-
.btn:hover{opacity:.85}
|
|
630
|
-
.btn:disabled{opacity:.5;cursor:not-allowed}
|
|
844
|
+
/* Cards */
|
|
845
|
+
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;transition:border-color .15s}
|
|
846
|
+
.card:hover{border-color:color-mix(in srgb,var(--accent) 40%,transparent)}
|
|
847
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin-top:16px}
|
|
848
|
+
/* Agent Card */
|
|
849
|
+
.agent-header{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
850
|
+
.dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
|
851
|
+
.dot.IDLE{background:var(--green);box-shadow:0 0 8px var(--green)}.dot.BUSY{background:var(--yellow);box-shadow:0 0 8px var(--yellow)}.dot.OFFLINE{background:var(--red);box-shadow:0 0 6px var(--red)}.dot.OOM{background:var(--purple);box-shadow:0 0 8px var(--purple)}
|
|
852
|
+
.agent-name{font-weight:600;font-size:15px}
|
|
853
|
+
.agent-id{color:var(--text-dim);font-family:var(--font-mono);font-size:11px;margin-bottom:8px}
|
|
854
|
+
.caps{display:flex;flex-wrap:wrap;gap:5px;margin-top:6px}
|
|
855
|
+
.cap{background:var(--accent-dim);border:1px solid color-mix(in srgb,var(--accent) 25%,transparent);border-radius:4px;padding:1px 8px;font-size:11px;color:var(--accent)}
|
|
856
|
+
.agent-meta{margin-top:10px;font-size:11px;color:var(--text-dim);display:flex;gap:12px}
|
|
857
|
+
.agent-actions{margin-top:12px;display:flex;gap:6px}
|
|
858
|
+
.agent-actions select{background:var(--surface-hover);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11px;cursor:pointer;outline:none}
|
|
859
|
+
.agent-actions button{background:var(--red);color:#fff;border:none;border-radius:6px;padding:4px 10px;font-size:11px;cursor:pointer}
|
|
860
|
+
.agent-actions button:hover{opacity:.8}
|
|
861
|
+
/* Stats Bar */
|
|
862
|
+
.stats{display:flex;gap:12px;flex-wrap:wrap}
|
|
863
|
+
.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 22px;text-align:center;min-width:90px}
|
|
864
|
+
.stat-val{font-size:28px;font-weight:700;font-family:var(--font-mono)}
|
|
865
|
+
.stat-lbl{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-top:2px}
|
|
866
|
+
/* Forms */
|
|
867
|
+
.form-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;margin-top:16px;max-width:620px}
|
|
868
|
+
.form-card h3{font-size:15px;color:var(--accent);font-family:var(--font-mono);margin-bottom:16px}
|
|
869
|
+
.fg{margin-bottom:14px}
|
|
870
|
+
.fg label{display:block;font-size:12px;color:var(--text-dim);margin-bottom:5px;font-weight:500}
|
|
871
|
+
.fg input,.fg textarea,.fg select{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px 12px;color:var(--text);font-size:13px;font-family:var(--font-sans);outline:none;transition:border-color .15s}
|
|
872
|
+
.fg input:focus,.fg textarea:focus,.fg select:focus{border-color:var(--accent)}
|
|
873
|
+
.fg textarea{min-height:80px;resize:vertical;font-family:var(--font-mono);font-size:12px}
|
|
874
|
+
.fg .hint{font-size:11px;color:var(--text-dim);margin-top:4px}
|
|
875
|
+
.btn{padding:10px 22px;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:6px}
|
|
876
|
+
.btn:hover{opacity:.85;transform:translateY(-1px)}
|
|
877
|
+
.btn:active{transform:translateY(0)}
|
|
878
|
+
.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
|
|
631
879
|
.btn-primary{background:var(--accent);color:var(--bg)}
|
|
880
|
+
.btn-green{background:var(--green);color:#fff}
|
|
632
881
|
.btn-danger{background:var(--red);color:#fff}
|
|
633
|
-
.
|
|
634
|
-
.
|
|
635
|
-
.
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
.
|
|
639
|
-
.
|
|
640
|
-
|
|
641
|
-
.
|
|
882
|
+
.btn-ghost{background:transparent;color:var(--accent);border:1px solid var(--border)}
|
|
883
|
+
.btn-ghost:hover{background:var(--accent-dim)}
|
|
884
|
+
.btn-sm{padding:6px 14px;font-size:12px}
|
|
885
|
+
.btn-group{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap}
|
|
886
|
+
/* Section header */
|
|
887
|
+
.section-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}
|
|
888
|
+
.section-hd h2{font-size:16px;font-weight:600}
|
|
889
|
+
/* Job card */
|
|
890
|
+
.job-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-top:14px}
|
|
891
|
+
.job-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
|
|
892
|
+
.job-title{font-weight:600;font-size:14px}
|
|
893
|
+
.job-id{font-family:var(--font-mono);font-size:11px;color:var(--text-dim)}
|
|
894
|
+
.pbar-wrap{margin:6px 0 14px}
|
|
895
|
+
.pbar-label{font-size:11px;color:var(--text-dim);margin-bottom:4px}
|
|
896
|
+
.pbar{background:var(--surface-hover);border-radius:4px;height:6px;overflow:hidden}
|
|
897
|
+
.pbar-fill{background:var(--accent);height:100%;border-radius:4px;transition:width .3s}
|
|
898
|
+
.kanban{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}
|
|
899
|
+
.lane{min-height:40px}
|
|
900
|
+
.lane-hd{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;padding-bottom:4px;border-bottom:2px solid var(--border)}
|
|
901
|
+
.lane-hd.y{color:var(--yellow);border-color:var(--yellow)}.lane-hd.r{color:var(--red);border-color:var(--red)}.lane-hd.g{color:var(--green);border-color:var(--green)}
|
|
902
|
+
.tcard{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:6px;font-size:12px}
|
|
903
|
+
.tcard-trace{font-family:var(--font-mono);font-size:10px;color:var(--accent);margin-bottom:3px}
|
|
904
|
+
.tcard-meta{font-size:10px;color:var(--text-dim);margin-top:3px}
|
|
905
|
+
/* Toast */
|
|
906
|
+
.toast{position:fixed;bottom:24px;right:24px;padding:12px 20px;border-radius:8px;font-size:13px;z-index:9999;animation:slide-up .25s ease;font-weight:500;box-shadow:0 8px 24px rgba(0,0,0,.4)}
|
|
907
|
+
.toast.ok{background:var(--green);color:#fff}.toast.err{background:var(--red);color:#fff}
|
|
908
|
+
@keyframes slide-up{from{transform:translateY(16px);opacity:0}to{transform:translateY(0);opacity:1}}
|
|
909
|
+
/* Empty state */
|
|
910
|
+
.empty-state{text-align:center;padding:48px 20px;color:var(--text-dim)}
|
|
911
|
+
.empty-state .icon{font-size:48px;margin-bottom:12px;opacity:.6}
|
|
912
|
+
.empty-state p{font-size:14px;margin-bottom:16px;max-width:360px;margin-left:auto;margin-right:auto;line-height:1.6}
|
|
913
|
+
/* Modal/Overlay */
|
|
914
|
+
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:100;display:flex;align-items:flex-start;justify-content:center;padding-top:60px;animation:fade-in .15s ease}
|
|
915
|
+
.overlay .form-card{max-width:620px;width:100%;max-height:85vh;overflow-y:auto;margin:0}
|
|
916
|
+
@keyframes fade-in{from{opacity:0}to{opacity:1}}
|
|
917
|
+
/* Agent chips for AI planning */
|
|
918
|
+
.chip-bar{display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap}
|
|
919
|
+
.chip-filter{background:var(--surface-hover);color:var(--text-dim);border:1px solid var(--border);border-radius:14px;padding:3px 12px;font-size:11px;cursor:pointer;transition:all .15s}
|
|
920
|
+
.chip-filter.active{background:var(--accent-dim);color:var(--accent);border-color:var(--accent)}
|
|
921
|
+
.agent-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
|
|
922
|
+
.achip{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:6px;user-select:none}
|
|
923
|
+
.achip:hover{border-color:var(--text-dim)}
|
|
924
|
+
.achip.selected{border-color:var(--accent);background:var(--accent-dim)}
|
|
925
|
+
.achip .adot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
926
|
+
.achip .adot.IDLE{background:var(--green)}.achip .adot.BUSY{background:var(--yellow)}.achip .adot.OFFLINE{background:var(--red)}.achip .adot.OOM{background:var(--purple)}
|
|
927
|
+
/* Plan preview cards */
|
|
928
|
+
.plan-card{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px}
|
|
929
|
+
.plan-card-hd{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
|
930
|
+
.plan-card-hd .adot{width:8px;height:8px;border-radius:50%}
|
|
931
|
+
.plan-card-agent{font-weight:600;font-size:13px}
|
|
932
|
+
.plan-card-agentid{font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}
|
|
933
|
+
.plan-card-desc{font-size:13px;margin-bottom:10px;font-weight:500}
|
|
934
|
+
.briefing-box{background:var(--surface);border-left:3px solid var(--accent);border-radius:0 6px 6px 0;padding:10px 14px;font-size:12px;line-height:1.6;color:var(--text);margin-bottom:8px;position:relative}
|
|
935
|
+
.briefing-label{font-size:10px;color:var(--accent);font-weight:600;margin-bottom:4px;font-family:var(--font-mono)}
|
|
936
|
+
.briefing-copy{position:absolute;top:8px;right:8px;background:var(--surface-hover);border:1px solid var(--border);color:var(--text-dim);border-radius:4px;padding:2px 8px;font-size:10px;cursor:pointer}
|
|
937
|
+
.briefing-copy:hover{color:var(--accent);border-color:var(--accent)}
|
|
938
|
+
.plan-card-dl{font-size:11px;color:var(--text-dim);font-family:var(--font-mono)}
|
|
939
|
+
/* Spinner */
|
|
940
|
+
.spinner-wrap{text-align:center;padding:40px 20px}
|
|
941
|
+
.spinner{display:inline-block;width:28px;height:28px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite}
|
|
942
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
943
|
+
.spinner-text{color:var(--text-dim);font-size:13px;margin-top:12px}
|
|
944
|
+
/* Manual task row */
|
|
945
|
+
.task-row{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:10px;position:relative}
|
|
946
|
+
.task-row .remove-task{position:absolute;top:8px;right:10px;background:none;border:none;color:var(--red);cursor:pointer;font-size:16px;line-height:1}
|
|
947
|
+
.task-row .fg{margin-bottom:10px}
|
|
948
|
+
.task-row .fg:last-child{margin-bottom:0}
|
|
642
949
|
</style>
|
|
643
950
|
</head>
|
|
644
951
|
<body>
|
|
645
952
|
<header>
|
|
646
953
|
<h1>HumanClaw</h1>
|
|
647
|
-
<
|
|
954
|
+
<span class="subtitle">\u5F02\u6B65\u7269\u7406\u8282\u70B9\u7F16\u6392\u6846\u67B6</span>
|
|
648
955
|
</header>
|
|
649
956
|
<nav>
|
|
650
|
-
<button class="tab active" data-panel="fleet"
|
|
651
|
-
<button class="tab" data-panel="pipeline"
|
|
652
|
-
<button class="tab" data-panel="terminal">I/O
|
|
957
|
+
<button class="tab active" data-panel="fleet">\u78B3\u57FA\u7B97\u529B\u6C60</button>
|
|
958
|
+
<button class="tab" data-panel="pipeline">\u7F16\u6392\u5927\u76D8</button>
|
|
959
|
+
<button class="tab" data-panel="terminal">I/O \u7EC8\u7AEF</button>
|
|
653
960
|
</nav>
|
|
654
961
|
<main>
|
|
655
962
|
<section id="fleet" class="panel active"></section>
|
|
656
963
|
<section id="pipeline" class="panel"></section>
|
|
657
964
|
<section id="terminal" class="panel"></section>
|
|
658
965
|
</main>
|
|
966
|
+
|
|
659
967
|
<script>
|
|
660
968
|
const API='/api/v1';
|
|
661
969
|
function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
|
|
970
|
+
function toast(msg,ok){
|
|
971
|
+
document.querySelectorAll('.toast').forEach(t=>t.remove());
|
|
972
|
+
const t=document.createElement('div');t.className='toast '+(ok?'ok':'err');t.textContent=msg;document.body.appendChild(t);
|
|
973
|
+
setTimeout(()=>t.remove(),3500);
|
|
974
|
+
}
|
|
975
|
+
let cachedAgents=[];
|
|
976
|
+
let currentPlan=null;
|
|
977
|
+
let selectedAgentIds=new Set();
|
|
662
978
|
|
|
663
|
-
// \
|
|
979
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
980
|
+
// FLEET
|
|
981
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
664
982
|
async function loadFleet(el){
|
|
665
|
-
el.innerHTML='<div class="empty"
|
|
983
|
+
el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u4E2D...</p></div>';
|
|
666
984
|
try{
|
|
667
985
|
const r=await fetch(API+'/nodes/status');
|
|
668
986
|
const d=await r.json();
|
|
669
|
-
|
|
670
|
-
h
|
|
671
|
-
h+='<div class="
|
|
672
|
-
h+='<div class="
|
|
673
|
-
h+='<div class="
|
|
674
|
-
h+='<div class="
|
|
987
|
+
cachedAgents=d.agents||[];
|
|
988
|
+
let h='<div class="section-hd"><h2>\u78B3\u57FA\u7B97\u529B\u6C60</h2><button class="btn btn-primary btn-sm" onclick="showAddAgent()">+ \u6DFB\u52A0\u8282\u70B9</button></div>';
|
|
989
|
+
h+='<div class="stats">';
|
|
990
|
+
h+='<div class="stat"><div class="stat-val">'+d.total+'</div><div class="stat-lbl">\u603B\u8BA1</div></div>';
|
|
991
|
+
h+='<div class="stat"><div class="stat-val" style="color:var(--green)">'+d.idle+'</div><div class="stat-lbl">\u7A7A\u95F2</div></div>';
|
|
992
|
+
h+='<div class="stat"><div class="stat-val" style="color:var(--yellow)">'+d.busy+'</div><div class="stat-lbl">\u5FD9\u788C</div></div>';
|
|
993
|
+
h+='<div class="stat"><div class="stat-val" style="color:var(--red)">'+d.offline+'</div><div class="stat-lbl">\u79BB\u7EBF</div></div>';
|
|
994
|
+
h+='<div class="stat"><div class="stat-val" style="color:var(--purple)">'+d.oom+'</div><div class="stat-lbl">\u5D29\u6E83</div></div>';
|
|
675
995
|
h+='</div>';
|
|
676
|
-
if(!d.agents.length){
|
|
677
|
-
|
|
996
|
+
if(!d.agents.length){
|
|
997
|
+
h+='<div class="empty-state" style="margin-top:32px"><div class="icon">\u{1F464}</div><p>\u8FD8\u6CA1\u6709\u7269\u7406\u8282\u70B9\u3002\u70B9\u51FB\u4E0A\u65B9\u300C+ \u6DFB\u52A0\u8282\u70B9\u300D\u6CE8\u518C\u4F60\u7684\u7B2C\u4E00\u4E2A\u78B3\u57FA\u7B97\u529B\u5355\u5143\u3002</p></div>';
|
|
998
|
+
el.innerHTML=h;return;
|
|
999
|
+
}
|
|
1000
|
+
h+='<div class="grid">';
|
|
678
1001
|
for(const a of d.agents){
|
|
679
|
-
h+='<div class="
|
|
680
|
-
h+='<div class="agent-id">'+a.agent_id+'</div
|
|
681
|
-
for(const c of a.capabilities)h+='<span class="cap
|
|
682
|
-
h+='
|
|
683
|
-
if(a.avg_delivery_hours!==null)h+='
|
|
1002
|
+
h+='<div class="card"><div class="agent-header"><span class="dot '+a.status+'"></span><span class="agent-name">'+esc(a.name)+'</span></div>';
|
|
1003
|
+
h+='<div class="agent-id">'+a.agent_id+'</div>';
|
|
1004
|
+
h+='<div class="caps">';for(const c of a.capabilities)h+='<span class="cap">'+esc(c)+'</span>';h+='</div>';
|
|
1005
|
+
h+='<div class="agent-meta"><span>\u4EFB\u52A1: '+a.active_task_count+'</span>';
|
|
1006
|
+
if(a.avg_delivery_hours!==null)h+='<span>\u5E73\u5747\u4EA4\u4ED8: '+a.avg_delivery_hours+'h</span>';
|
|
1007
|
+
h+='</div>';
|
|
1008
|
+
h+='<div class="agent-actions">';
|
|
1009
|
+
h+='<select onchange="changeStatus(\\''+a.agent_id+'\\',this.value)">';
|
|
1010
|
+
for(const s of ['IDLE','BUSY','OFFLINE','OOM'])h+='<option'+(s===a.status?' selected':'')+'>'+s+'</option>';
|
|
1011
|
+
h+='</select>';
|
|
1012
|
+
h+='<button onclick="deleteAgent(\\''+a.agent_id+'\\')">\u5220\u9664</button>';
|
|
684
1013
|
h+='</div></div>';
|
|
685
1014
|
}
|
|
686
1015
|
h+='</div>';
|
|
687
1016
|
el.innerHTML=h;
|
|
688
|
-
}catch{el.innerHTML='<div class="empty"
|
|
1017
|
+
}catch(e){el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u5931\u8D25: '+e.message+'</p></div>'}
|
|
689
1018
|
}
|
|
1019
|
+
window.showAddAgent=function(){
|
|
1020
|
+
const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
|
|
1021
|
+
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
|
|
1022
|
+
ov.innerHTML='<div class="form-card"><h3>+ \u6DFB\u52A0\u7269\u7406\u8282\u70B9</h3>'
|
|
1023
|
+
+'<div class="fg"><label>\u8282\u70B9\u540D\u79F0</label><input id="aa-name" placeholder="\u4F8B: \u524D\u7AEF\u8001\u674E"/></div>'
|
|
1024
|
+
+'<div class="fg"><label>\u6280\u80FD\u6807\u7B7E</label><input id="aa-caps" placeholder="\u4F8B: UI/UX, \u524D\u7AEF\u5F00\u53D1, \u6297\u538B\u80FD\u529B\u5F3A"/><div class="hint">\u591A\u4E2A\u6807\u7B7E\u7528\u9017\u53F7\u5206\u9694</div></div>'
|
|
1025
|
+
+'<div class="btn-group"><button class="btn btn-primary" onclick="submitAgent()">\u6CE8\u518C\u8282\u70B9</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div></div>';
|
|
1026
|
+
document.body.appendChild(ov);
|
|
1027
|
+
document.getElementById('aa-name').focus();
|
|
1028
|
+
};
|
|
1029
|
+
window.submitAgent=async function(){
|
|
1030
|
+
const name=document.getElementById('aa-name').value.trim();
|
|
1031
|
+
const caps=document.getElementById('aa-caps').value.trim();
|
|
1032
|
+
if(!name){toast('\u8BF7\u8F93\u5165\u8282\u70B9\u540D\u79F0',false);return}
|
|
1033
|
+
if(!caps){toast('\u8BF7\u8F93\u5165\u81F3\u5C11\u4E00\u4E2A\u6280\u80FD\u6807\u7B7E',false);return}
|
|
1034
|
+
try{
|
|
1035
|
+
const r=await fetch(API+'/nodes',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,capabilities:caps.split(',').map(s=>s.trim()).filter(Boolean)})});
|
|
1036
|
+
const d=await r.json();
|
|
1037
|
+
if(!r.ok){toast(d.error||'\u6CE8\u518C\u5931\u8D25',false);return}
|
|
1038
|
+
toast('\u8282\u70B9 '+d.agent_id+' \u6CE8\u518C\u6210\u529F\uFF01',true);
|
|
1039
|
+
document.getElementById('overlay').remove();
|
|
1040
|
+
load('fleet');
|
|
1041
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1042
|
+
};
|
|
1043
|
+
window.changeStatus=async function(id,status){
|
|
1044
|
+
try{
|
|
1045
|
+
const r=await fetch(API+'/nodes/'+id+'/status',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({status})});
|
|
1046
|
+
if(!r.ok){const d=await r.json();toast(d.error||'\u66F4\u65B0\u5931\u8D25',false);return}
|
|
1047
|
+
toast('\u72B6\u6001\u5DF2\u66F4\u65B0',true);
|
|
1048
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1049
|
+
};
|
|
1050
|
+
window.deleteAgent=async function(id){
|
|
1051
|
+
if(!confirm('\u786E\u5B9A\u5220\u9664\u8282\u70B9 '+id+'\uFF1F'))return;
|
|
1052
|
+
try{
|
|
1053
|
+
const r=await fetch(API+'/nodes/'+id,{method:'DELETE'});
|
|
1054
|
+
if(!r.ok){const d=await r.json();toast(d.error||'\u5220\u9664\u5931\u8D25',false);return}
|
|
1055
|
+
toast('\u8282\u70B9\u5DF2\u5220\u9664',true);load('fleet');
|
|
1056
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1057
|
+
};
|
|
690
1058
|
|
|
691
|
-
// \
|
|
692
|
-
|
|
693
|
-
|
|
1059
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
1060
|
+
// PIPELINE
|
|
1061
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
1062
|
+
function tcard(t){
|
|
1063
|
+
return '<div class="tcard"><div class="tcard-trace">'+t.trace_id+'</div><div>'+esc(t.todo_description)+'</div><div class="tcard-meta">'+t.assignee_id+' | '+new Date(t.deadline).toLocaleString()+'</div></div>';
|
|
694
1064
|
}
|
|
695
1065
|
async function loadPipeline(el){
|
|
696
|
-
el.innerHTML='<div class="empty"
|
|
1066
|
+
el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u4E2D...</p></div>';
|
|
697
1067
|
try{
|
|
1068
|
+
try{const ar=await fetch(API+'/nodes/status');const ad=await ar.json();cachedAgents=ad.agents||[]}catch{}
|
|
698
1069
|
const r=await fetch(API+'/jobs/active');
|
|
699
1070
|
const d=await r.json();
|
|
700
|
-
|
|
701
|
-
|
|
1071
|
+
let h='<div class="section-hd"><h2>\u5F02\u6B65\u7F16\u6392\u5927\u76D8</h2><button class="btn btn-primary btn-sm" onclick="showCreateJob()">+ \u521B\u5EFA\u4EFB\u52A1</button></div>';
|
|
1072
|
+
if(!d.jobs.length){
|
|
1073
|
+
h+='<div class="empty-state" style="margin-top:32px"><div class="icon">\u{1F4CB}</div><p>\u6682\u65E0\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1\u3002\u70B9\u51FB\u4E0A\u65B9\u300C+ \u521B\u5EFA\u4EFB\u52A1\u300D\uFF0C\u8F93\u5165\u9700\u6C42\u540E AI \u81EA\u52A8\u89C4\u5212\u5206\u53D1\u3002';
|
|
1074
|
+
if(!cachedAgents.length)h+='<br/><br/>\u26A0\uFE0F \u9700\u8981\u5148\u5728\u300C\u78B3\u57FA\u7B97\u529B\u6C60\u300D\u4E2D\u6DFB\u52A0\u7269\u7406\u8282\u70B9\u3002';
|
|
1075
|
+
h+='</p></div>';
|
|
1076
|
+
el.innerHTML=h;return;
|
|
1077
|
+
}
|
|
702
1078
|
for(const j of d.jobs){
|
|
703
1079
|
const res=j.tasks.filter(t=>t.status==='RESOLVED').length;
|
|
704
1080
|
const tot=j.tasks.length;
|
|
@@ -706,55 +1082,234 @@ async function loadPipeline(el){
|
|
|
706
1082
|
const dispatched=j.tasks.filter(t=>t.status==='DISPATCHED'||t.status==='PENDING');
|
|
707
1083
|
const overdue=j.tasks.filter(t=>t.status==='OVERDUE');
|
|
708
1084
|
const done=j.tasks.filter(t=>t.status==='RESOLVED');
|
|
709
|
-
h+='<div class="job-card"><div class="job-
|
|
710
|
-
h+='<div class="
|
|
711
|
-
h+='<div
|
|
712
|
-
h+='<div class="kanban"><div class="
|
|
713
|
-
h+='<div class="
|
|
714
|
-
h+='<div class="
|
|
1085
|
+
h+='<div class="job-card"><div class="job-hd"><span class="job-title">'+esc(j.original_prompt)+'</span><span class="job-id">'+j.job_id+'</span></div>';
|
|
1086
|
+
h+='<div class="pbar-wrap"><div class="pbar-label">'+res+'/'+tot+' \u5DF2\u5B8C\u6210 ('+pct+'%)</div><div class="pbar"><div class="pbar-fill" style="width:'+pct+'%"></div></div></div>';
|
|
1087
|
+
if(pct===100)h+='<div style="margin-bottom:12px"><button class="btn btn-green btn-sm" onclick="syncJob(\\''+j.job_id+'\\')">\u805A\u5408\u5E76\u540C\u6B65\u5230 OpenClaw</button></div>';
|
|
1088
|
+
h+='<div class="kanban"><div class="lane"><div class="lane-hd y">\u5DF2\u5206\u53D1 ('+dispatched.length+')</div>'+dispatched.map(tcard).join('')+'</div>';
|
|
1089
|
+
h+='<div class="lane"><div class="lane-hd r">\u5DF2\u8D85\u65F6 ('+overdue.length+')</div>'+overdue.map(tcard).join('')+'</div>';
|
|
1090
|
+
h+='<div class="lane"><div class="lane-hd g">\u5DF2\u4EA4\u4ED8 ('+done.length+')</div>'+done.map(tcard).join('')+'</div></div></div>';
|
|
715
1091
|
}
|
|
716
1092
|
el.innerHTML=h;
|
|
717
|
-
}catch{el.innerHTML='<div class="empty"
|
|
1093
|
+
}catch(e){el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u5931\u8D25: '+e.message+'</p></div>'}
|
|
718
1094
|
}
|
|
719
1095
|
|
|
720
|
-
// \u2500\u2500\u2500
|
|
721
|
-
function
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1096
|
+
// \u2500\u2500\u2500 AI Planning Flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1097
|
+
window.showCreateJob=function(){
|
|
1098
|
+
if(!cachedAgents.length){toast('\u8BF7\u5148\u5728\u300C\u78B3\u57FA\u7B97\u529B\u6C60\u300D\u4E2D\u6DFB\u52A0\u81F3\u5C11\u4E00\u4E2A\u7269\u7406\u8282\u70B9',false);return}
|
|
1099
|
+
currentPlan=null;
|
|
1100
|
+
// Pre-select all IDLE agents
|
|
1101
|
+
selectedAgentIds=new Set(cachedAgents.filter(a=>a.status==='IDLE').map(a=>a.agent_id));
|
|
1102
|
+
const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
|
|
1103
|
+
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
|
|
1104
|
+
renderPlanStep1(ov);
|
|
1105
|
+
document.body.appendChild(ov);
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
function renderPlanStep1(ov){
|
|
1109
|
+
let chipFilter='all';
|
|
1110
|
+
function renderChips(filter){
|
|
1111
|
+
chipFilter=filter;
|
|
1112
|
+
const filtered=filter==='idle'?cachedAgents.filter(a=>a.status==='IDLE'):cachedAgents;
|
|
1113
|
+
let ch='<div class="chip-bar">';
|
|
1114
|
+
ch+='<span class="chip-filter'+(filter==='all'?' active':'')+'" onclick="window._filterChips(\\'all\\')">\u5168\u90E8</span>';
|
|
1115
|
+
ch+='<span class="chip-filter'+(filter==='idle'?' active':'')+'" onclick="window._filterChips(\\'idle\\')">\u4EC5\u7A7A\u95F2</span>';
|
|
1116
|
+
ch+='</div>';
|
|
1117
|
+
ch+='<div class="agent-chips">';
|
|
1118
|
+
for(const a of filtered){
|
|
1119
|
+
const sel=selectedAgentIds.has(a.agent_id);
|
|
1120
|
+
ch+='<div class="achip'+(sel?' selected':'')+'" onclick="window._toggleChip(\\''+a.agent_id+'\\')">';
|
|
1121
|
+
ch+='<span class="adot '+a.status+'"></span>';
|
|
1122
|
+
ch+=esc(a.name);
|
|
1123
|
+
ch+='</div>';
|
|
1124
|
+
}
|
|
1125
|
+
ch+='</div>';
|
|
1126
|
+
return ch;
|
|
1127
|
+
}
|
|
1128
|
+
window._filterChips=function(f){
|
|
1129
|
+
chipFilter=f;
|
|
1130
|
+
const el=document.getElementById('agent-chip-area');
|
|
1131
|
+
if(el)el.innerHTML=renderChips(f);
|
|
1132
|
+
};
|
|
1133
|
+
window._toggleChip=function(id){
|
|
1134
|
+
if(selectedAgentIds.has(id))selectedAgentIds.delete(id);
|
|
1135
|
+
else selectedAgentIds.add(id);
|
|
1136
|
+
const el=document.getElementById('agent-chip-area');
|
|
1137
|
+
if(el)el.innerHTML=renderChips(chipFilter);
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
ov.innerHTML='<div class="form-card"><h3>AI \u667A\u80FD\u89C4\u5212</h3>'
|
|
1141
|
+
+'<div class="fg"><label>\u8F93\u5165\u4F60\u7684\u9700\u6C42</label><textarea id="plan-prompt" rows="3" placeholder="\u4F8B: \u5B8C\u6210\u9996\u9875\u91CD\u6784\uFF0C\u5305\u62EC\u5BFC\u822A\u680F\u3001\u5185\u5BB9\u533A\u548C\u9875\u811A\u7684\u54CD\u5E94\u5F0F\u6539\u7248" style="font-family:var(--font-sans);font-size:13px"></textarea></div>'
|
|
1142
|
+
+'<div class="fg"><label>\u9009\u62E9\u53C2\u4E0E\u7684\u7269\u7406\u8282\u70B9 <span style="color:var(--text-dim);font-weight:400">(\u9ED8\u8BA4\u9009\u4E2D\u7A7A\u95F2\u8282\u70B9)</span></label>'
|
|
1143
|
+
+'<div id="agent-chip-area">'+renderChips('all')+'</div></div>'
|
|
1144
|
+
+'<div class="fg"><label>OpenClaw \u56DE\u8C03\u5730\u5740 <span style="color:var(--text-dim);font-weight:400">(\u53EF\u9009)</span></label><input id="plan-callback" placeholder="https://..."/></div>'
|
|
1145
|
+
+'<div class="btn-group">'
|
|
1146
|
+
+'<button class="btn btn-primary" onclick="planWithAI()">AI \u89C4\u5212</button>'
|
|
1147
|
+
+'<button class="btn btn-ghost" onclick="showManualCreate()">\u624B\u52A8\u521B\u5EFA</button>'
|
|
1148
|
+
+'<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>'
|
|
1149
|
+
+'</div></div>';
|
|
1150
|
+
setTimeout(()=>{const ta=document.getElementById('plan-prompt');if(ta)ta.focus()},50);
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
window.planWithAI=async function(){
|
|
1154
|
+
const prompt=document.getElementById('plan-prompt').value.trim();
|
|
1155
|
+
const callback=document.getElementById('plan-callback')?.value.trim()||'';
|
|
1156
|
+
if(!prompt){toast('\u8BF7\u8F93\u5165\u9700\u6C42\u63CF\u8FF0',false);return}
|
|
1157
|
+
if(selectedAgentIds.size===0){toast('\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u7269\u7406\u8282\u70B9',false);return}
|
|
1158
|
+
|
|
1159
|
+
const ov=document.getElementById('overlay');
|
|
1160
|
+
const fc=ov.querySelector('.form-card');
|
|
1161
|
+
fc.innerHTML='<h3>AI \u667A\u80FD\u89C4\u5212</h3><div class="spinner-wrap"><div class="spinner"></div><div class="spinner-text">AI \u6B63\u5728\u5206\u6790\u9700\u6C42\u3001\u5339\u914D\u8282\u70B9\u3001\u751F\u6210\u8BDD\u672F...</div></div>';
|
|
1162
|
+
|
|
1163
|
+
try{
|
|
1164
|
+
const r=await fetch(API+'/jobs/plan',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt,agent_ids:Array.from(selectedAgentIds)})});
|
|
1165
|
+
const d=await r.json();
|
|
1166
|
+
if(!r.ok){
|
|
1167
|
+
toast(d.error||'\u89C4\u5212\u5931\u8D25',false);
|
|
1168
|
+
renderPlanStep1(ov);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
currentPlan={...d,openclaw_callback:callback};
|
|
1172
|
+
renderPlanStep2(ov);
|
|
1173
|
+
}catch(e){
|
|
1174
|
+
toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);
|
|
1175
|
+
renderPlanStep1(ov);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
function renderPlanStep2(ov){
|
|
1180
|
+
const p=currentPlan;
|
|
1181
|
+
let h='<h3>\u89C4\u5212\u9884\u89C8</h3>';
|
|
1182
|
+
h+='<div style="font-size:12px;color:var(--text-dim);margin-bottom:14px">\u9700\u6C42: '+esc(p.original_prompt)+'</div>';
|
|
1183
|
+
for(let i=0;i<p.planned_tasks.length;i++){
|
|
1184
|
+
const t=p.planned_tasks[i];
|
|
1185
|
+
const agent=cachedAgents.find(a=>a.agent_id===t.assignee_id);
|
|
1186
|
+
const status=agent?agent.status:'IDLE';
|
|
1187
|
+
h+='<div class="plan-card">';
|
|
1188
|
+
h+='<div class="plan-card-hd"><span class="adot '+status+'"></span><span class="plan-card-agent">'+esc(t.assignee_name)+'</span><span class="plan-card-agentid">'+t.assignee_id+'</span></div>';
|
|
1189
|
+
h+='<div class="plan-card-desc">'+esc(t.todo_description)+'</div>';
|
|
1190
|
+
h+='<div class="briefing-box"><div class="briefing-label">\u8BDD\u672F (\u53EF\u76F4\u63A5\u53D1\u7ED9 TA)</div><button class="briefing-copy" onclick="window._copyBriefing('+i+')">\u590D\u5236</button>'+esc(t.briefing)+'</div>';
|
|
1191
|
+
h+='<div class="plan-card-dl">\u622A\u6B62: '+new Date(t.deadline).toLocaleString()+'</div>';
|
|
1192
|
+
h+='</div>';
|
|
1193
|
+
}
|
|
1194
|
+
h+='<div class="btn-group">';
|
|
1195
|
+
h+='<button class="btn btn-green" onclick="dispatchPlan()">\u786E\u8BA4\u5206\u53D1</button>';
|
|
1196
|
+
h+='<button class="btn btn-ghost" onclick="renderPlanStep1(document.getElementById(\\'overlay\\'))">\u91CD\u65B0\u89C4\u5212</button>';
|
|
1197
|
+
h+='<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>';
|
|
1198
|
+
h+='</div>';
|
|
1199
|
+
ov.querySelector('.form-card').innerHTML=h;
|
|
725
1200
|
}
|
|
1201
|
+
window.renderPlanStep1=renderPlanStep1;
|
|
1202
|
+
|
|
1203
|
+
window._copyBriefing=function(i){
|
|
1204
|
+
const text=currentPlan.planned_tasks[i].briefing;
|
|
1205
|
+
navigator.clipboard.writeText(text).then(()=>toast('\u8BDD\u672F\u5DF2\u590D\u5236',true)).catch(()=>toast('\u590D\u5236\u5931\u8D25',false));
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
window.dispatchPlan=async function(){
|
|
1209
|
+
const p=currentPlan;
|
|
1210
|
+
const tasks=p.planned_tasks.map(t=>({assignee_id:t.assignee_id,todo_description:t.todo_description,deadline:t.deadline}));
|
|
1211
|
+
try{
|
|
1212
|
+
const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:p.original_prompt,openclaw_callback:p.openclaw_callback||'',tasks})});
|
|
1213
|
+
const d=await r.json();
|
|
1214
|
+
if(!r.ok){toast(d.error||'\u5206\u53D1\u5931\u8D25',false);return}
|
|
1215
|
+
toast('\u4EFB\u52A1\u5DF2\u5206\u53D1\uFF01Job: '+d.job_id,true);
|
|
1216
|
+
document.getElementById('overlay').remove();
|
|
1217
|
+
load('pipeline');
|
|
1218
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// \u2500\u2500\u2500 Manual creation (fallback) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1222
|
+
window.showManualCreate=function(){
|
|
1223
|
+
const ov=document.getElementById('overlay');
|
|
1224
|
+
const optionsHtml=cachedAgents.map(a=>'<option value="'+a.agent_id+'">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');
|
|
1225
|
+
const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);
|
|
1226
|
+
ov.querySelector('.form-card').innerHTML='<h3>\u624B\u52A8\u521B\u5EFA\u4EFB\u52A1</h3>'
|
|
1227
|
+
+'<div class="fg"><label>\u4EFB\u52A1\u63CF\u8FF0 (\u539F\u59CB\u9700\u6C42)</label><input id="cj-prompt" placeholder="\u4F8B: \u5B8C\u6210\u9996\u9875\u6539\u7248"/></div>'
|
|
1228
|
+
+'<div class="fg"><label>OpenClaw \u56DE\u8C03\u5730\u5740 (\u53EF\u9009)</label><input id="cj-callback" placeholder="https://..."/></div>'
|
|
1229
|
+
+'<div style="margin-bottom:10px;display:flex;justify-content:space-between;align-items:center"><label style="font-size:13px;font-weight:600;color:var(--text)">\u5B50\u4EFB\u52A1\u5217\u8868</label><button class="btn btn-ghost btn-sm" onclick="addTaskRow()">+ \u6DFB\u52A0\u5B50\u4EFB\u52A1</button></div>'
|
|
1230
|
+
+'<div id="task-rows"><div class="task-row"><div class="fg"><label>\u6307\u6D3E\u8282\u70B9</label><select class="tr-agent">'+optionsHtml+'</select></div><div class="fg"><label>\u4EFB\u52A1\u63CF\u8FF0</label><input class="tr-desc" placeholder="\u5177\u4F53\u8981\u505A\u4EC0\u4E48..."/></div><div class="fg"><label>\u622A\u6B62\u65F6\u95F4</label><input class="tr-deadline" type="datetime-local" value="'+tomorrow+'"/></div></div></div>'
|
|
1231
|
+
+'<div class="btn-group"><button class="btn btn-primary" onclick="submitJob()">\u521B\u5EFA\u5E76\u5206\u53D1</button><button class="btn btn-ghost" onclick="renderPlanStep1(document.getElementById(\\'overlay\\'))">\u8FD4\u56DE AI \u89C4\u5212</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div>';
|
|
1232
|
+
};
|
|
1233
|
+
window.addTaskRow=function(){
|
|
1234
|
+
const optionsHtml=cachedAgents.map(a=>'<option value="'+a.agent_id+'">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');
|
|
1235
|
+
const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);
|
|
1236
|
+
const row=document.createElement('div');row.className='task-row';
|
|
1237
|
+
row.innerHTML='<button class="remove-task" onclick="this.parentElement.remove()">×</button>'
|
|
1238
|
+
+'<div class="fg"><label>\u6307\u6D3E\u8282\u70B9</label><select class="tr-agent">'+optionsHtml+'</select></div>'
|
|
1239
|
+
+'<div class="fg"><label>\u4EFB\u52A1\u63CF\u8FF0</label><input class="tr-desc" placeholder="\u5177\u4F53\u8981\u505A\u4EC0\u4E48..."/></div>'
|
|
1240
|
+
+'<div class="fg"><label>\u622A\u6B62\u65F6\u95F4</label><input class="tr-deadline" type="datetime-local" value="'+tomorrow+'"/></div>';
|
|
1241
|
+
document.getElementById('task-rows').appendChild(row);
|
|
1242
|
+
};
|
|
1243
|
+
window.submitJob=async function(){
|
|
1244
|
+
const prompt=document.getElementById('cj-prompt').value.trim();
|
|
1245
|
+
const callback=document.getElementById('cj-callback').value.trim();
|
|
1246
|
+
if(!prompt){toast('\u8BF7\u8F93\u5165\u4EFB\u52A1\u63CF\u8FF0',false);return}
|
|
1247
|
+
const rows=document.querySelectorAll('.task-row');
|
|
1248
|
+
const tasks=[];
|
|
1249
|
+
for(const row of rows){
|
|
1250
|
+
const aid=row.querySelector('.tr-agent').value;
|
|
1251
|
+
const desc=row.querySelector('.tr-desc').value.trim();
|
|
1252
|
+
const dl=row.querySelector('.tr-deadline').value;
|
|
1253
|
+
if(!desc){toast('\u6BCF\u4E2A\u5B50\u4EFB\u52A1\u90FD\u9700\u8981\u586B\u5199\u63CF\u8FF0',false);return}
|
|
1254
|
+
tasks.push({assignee_id:aid,todo_description:desc,deadline:new Date(dl).toISOString()});
|
|
1255
|
+
}
|
|
1256
|
+
if(!tasks.length){toast('\u81F3\u5C11\u6DFB\u52A0\u4E00\u4E2A\u5B50\u4EFB\u52A1',false);return}
|
|
1257
|
+
try{
|
|
1258
|
+
const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:prompt,openclaw_callback:callback,tasks})});
|
|
1259
|
+
const d=await r.json();
|
|
1260
|
+
if(!r.ok){toast(d.error||'\u521B\u5EFA\u5931\u8D25',false);return}
|
|
1261
|
+
toast('\u4EFB\u52A1\u5DF2\u521B\u5EFA\u5E76\u5206\u53D1\uFF01Job: '+d.job_id,true);
|
|
1262
|
+
document.getElementById('overlay').remove();
|
|
1263
|
+
load('pipeline');
|
|
1264
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1265
|
+
};
|
|
1266
|
+
window.syncJob=async function(jobId){
|
|
1267
|
+
try{
|
|
1268
|
+
const r=await fetch(API+'/jobs/'+jobId+'/sync',{method:'POST'});
|
|
1269
|
+
const d=await r.json();
|
|
1270
|
+
if(!r.ok){toast(d.error||'\u540C\u6B65\u5931\u8D25',false);return}
|
|
1271
|
+
toast('\u805A\u5408\u5B8C\u6210\uFF01'+d.sync.message,true);
|
|
1272
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
1276
|
+
// TERMINAL
|
|
1277
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
726
1278
|
function loadTerminal(el){
|
|
727
|
-
el.innerHTML='<div class="
|
|
728
|
-
+'<div class="form-
|
|
729
|
-
+'<div class="
|
|
730
|
-
+'<div class="
|
|
731
|
-
|
|
732
|
-
const tid=document.getElementById('trace-id').value.trim();
|
|
733
|
-
const txt=document.getElementById('result-text').value.trim();
|
|
734
|
-
if(!tid){showToast('Trace ID is required','error');return}
|
|
735
|
-
if(!txt){showToast('Delivery payload is required','error');return}
|
|
736
|
-
try{
|
|
737
|
-
const r=await fetch(API+'/tasks/resume',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:tid,result_data:{text:txt}})});
|
|
738
|
-
const d=await r.json();
|
|
739
|
-
if(!r.ok){showToast(d.error||'Failed','error');return}
|
|
740
|
-
showToast(d.job_complete?'Task resolved! Job complete.':'Task '+tid+' resolved.','success');
|
|
741
|
-
document.getElementById('trace-id').value='';document.getElementById('result-text').value='';
|
|
742
|
-
}catch{showToast('Network error','error')}
|
|
743
|
-
});
|
|
744
|
-
document.getElementById('btn-reject').addEventListener('click',async()=>{
|
|
745
|
-
const tid=document.getElementById('trace-id').value.trim();
|
|
746
|
-
if(!tid){showToast('Trace ID is required','error');return}
|
|
747
|
-
try{
|
|
748
|
-
const r=await fetch(API+'/tasks/reject',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:tid})});
|
|
749
|
-
const d=await r.json();
|
|
750
|
-
if(!r.ok){showToast(d.error||'Failed','error');return}
|
|
751
|
-
showToast('Task '+tid+' rejected and deadline extended.','success');
|
|
752
|
-
document.getElementById('trace-id').value='';document.getElementById('result-text').value='';
|
|
753
|
-
}catch{showToast('Network error','error')}
|
|
754
|
-
});
|
|
1279
|
+
el.innerHTML='<div class="section-hd"><h2>I/O \u4EA4\u4ED8\u7EC8\u7AEF</h2></div>'
|
|
1280
|
+
+'<div class="form-card" style="margin-top:12px"><h3>> \u63D0\u4EA4\u7269\u7406\u8282\u70B9\u4EA7\u51FA</h3>'
|
|
1281
|
+
+'<div class="fg"><label>Trace ID (\u8FFD\u8E2A\u7801)</label><input id="t-tid" placeholder="\u4F8B: TK-9527"/></div>'
|
|
1282
|
+
+'<div class="fg"><label>\u4EA4\u4ED8\u8F7D\u8377</label><textarea id="t-payload" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001\u5DE5\u4F5C\u6C47\u62A5\u6216\u4EE3\u7801..."></textarea></div>'
|
|
1283
|
+
+'<div class="btn-group"><button class="btn btn-primary" onclick="doResume()">\u63D0\u4EA4\u5E76\u6062\u590D (Resume)</button><button class="btn btn-danger" onclick="doReject()">\u6253\u56DE\u91CD\u505A (Reject)</button></div></div>';
|
|
755
1284
|
}
|
|
1285
|
+
window.doResume=async function(){
|
|
1286
|
+
const tid=document.getElementById('t-tid').value.trim();
|
|
1287
|
+
const payload=document.getElementById('t-payload').value.trim();
|
|
1288
|
+
if(!tid){toast('\u8BF7\u8F93\u5165 Trace ID',false);return}
|
|
1289
|
+
if(!payload){toast('\u8BF7\u8F93\u5165\u4EA4\u4ED8\u8F7D\u8377',false);return}
|
|
1290
|
+
try{
|
|
1291
|
+
const r=await fetch(API+'/tasks/resume',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:tid,result_data:{text:payload}})});
|
|
1292
|
+
const d=await r.json();
|
|
1293
|
+
if(!r.ok){toast(d.error||'\u63D0\u4EA4\u5931\u8D25',false);return}
|
|
1294
|
+
toast(d.job_complete?'\u4EFB\u52A1\u5DF2\u4EA4\u4ED8\uFF01Job \u5DF2\u5168\u90E8\u5B8C\u6210\uFF0C\u53EF\u4EE5\u805A\u5408\u540C\u6B65\u3002':'\u4EFB\u52A1 '+tid+' \u5DF2\u4EA4\u4ED8\u3002',true);
|
|
1295
|
+
document.getElementById('t-tid').value='';document.getElementById('t-payload').value='';
|
|
1296
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1297
|
+
};
|
|
1298
|
+
window.doReject=async function(){
|
|
1299
|
+
const tid=document.getElementById('t-tid').value.trim();
|
|
1300
|
+
if(!tid){toast('\u8BF7\u8F93\u5165 Trace ID',false);return}
|
|
1301
|
+
try{
|
|
1302
|
+
const r=await fetch(API+'/tasks/reject',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:tid})});
|
|
1303
|
+
const d=await r.json();
|
|
1304
|
+
if(!r.ok){toast(d.error||'\u64CD\u4F5C\u5931\u8D25',false);return}
|
|
1305
|
+
toast('\u4EFB\u52A1 '+tid+' \u5DF2\u6253\u56DE\uFF0C\u622A\u6B62\u65F6\u95F4\u5DF2\u5EF6\u957F 24 \u5C0F\u65F6\u3002',true);
|
|
1306
|
+
document.getElementById('t-tid').value='';document.getElementById('t-payload').value='';
|
|
1307
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1308
|
+
};
|
|
756
1309
|
|
|
757
|
-
// \
|
|
1310
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
1311
|
+
// TABS & INIT
|
|
1312
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
758
1313
|
const tabs=document.querySelectorAll('.tab');
|
|
759
1314
|
const panels=document.querySelectorAll('.panel');
|
|
760
1315
|
function load(id){
|
|
@@ -775,7 +1330,7 @@ load('fleet');
|
|
|
775
1330
|
setInterval(()=>{
|
|
776
1331
|
const a=document.querySelector('.tab.active');
|
|
777
1332
|
if(a&&a.dataset.panel!=='terminal')load(a.dataset.panel);
|
|
778
|
-
},
|
|
1333
|
+
},15000);
|
|
779
1334
|
</script>
|
|
780
1335
|
</body>
|
|
781
1336
|
</html>`;
|
|
@@ -911,5 +1466,78 @@ program.command("status").description("Show active jobs overview").action(() =>
|
|
|
911
1466
|
console.log();
|
|
912
1467
|
}
|
|
913
1468
|
});
|
|
1469
|
+
program.command("plan").description("AI-powered task planning from natural language").argument("[prompt]", "What you want to get done").option("--agents <ids>", "Comma-separated agent IDs (default: all IDLE)").option("--dispatch", "Automatically dispatch after planning").action(async (promptArg, opts) => {
|
|
1470
|
+
const db2 = getDb();
|
|
1471
|
+
initSchema(db2);
|
|
1472
|
+
p.intro(chalk.bgCyan(" AI \u667A\u80FD\u89C4\u5212 "));
|
|
1473
|
+
let prompt = promptArg;
|
|
1474
|
+
if (!prompt) {
|
|
1475
|
+
const input = await p.text({
|
|
1476
|
+
message: "\u8F93\u5165\u4F60\u7684\u9700\u6C42:",
|
|
1477
|
+
placeholder: "\u4F8B: \u5B8C\u6210\u9996\u9875\u91CD\u6784\uFF0C\u5305\u62EC\u5BFC\u822A\u680F\u548C\u5185\u5BB9\u533A\u7684\u54CD\u5E94\u5F0F\u6539\u7248",
|
|
1478
|
+
validate: (v) => !v ? "\u9700\u6C42\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A" : void 0
|
|
1479
|
+
});
|
|
1480
|
+
if (p.isCancel(input)) {
|
|
1481
|
+
p.cancel("\u5DF2\u53D6\u6D88");
|
|
1482
|
+
process.exit(0);
|
|
1483
|
+
}
|
|
1484
|
+
prompt = input;
|
|
1485
|
+
}
|
|
1486
|
+
const agentIds = opts?.agents?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1487
|
+
const spin = p.spinner();
|
|
1488
|
+
spin.start("AI \u6B63\u5728\u5206\u6790\u9700\u6C42\u3001\u5339\u914D\u8282\u70B9\u3001\u751F\u6210\u8BDD\u672F...");
|
|
1489
|
+
try {
|
|
1490
|
+
const plan = await planJob({ prompt, agent_ids: agentIds }, void 0, db2);
|
|
1491
|
+
spin.stop("\u89C4\u5212\u5B8C\u6210\uFF01");
|
|
1492
|
+
console.log(chalk.bold(`
|
|
1493
|
+
\u9700\u6C42: ${chalk.white(plan.original_prompt)}
|
|
1494
|
+
`));
|
|
1495
|
+
for (const task of plan.planned_tasks) {
|
|
1496
|
+
console.log(chalk.cyan(` \u250C\u2500 ${chalk.bold(task.assignee_name)} (${chalk.dim(task.assignee_id)})`));
|
|
1497
|
+
console.log(chalk.cyan(" \u2502") + ` \u4EFB\u52A1: ${task.todo_description}`);
|
|
1498
|
+
console.log(chalk.cyan(" \u2502") + ` \u8BDD\u672F: ${chalk.italic(task.briefing)}`);
|
|
1499
|
+
console.log(chalk.cyan(" \u2502") + ` \u622A\u6B62: ${new Date(task.deadline).toLocaleString()}`);
|
|
1500
|
+
console.log(chalk.cyan(" \u2514\u2500"));
|
|
1501
|
+
console.log();
|
|
1502
|
+
}
|
|
1503
|
+
if (opts?.dispatch) {
|
|
1504
|
+
const tasks = plan.planned_tasks.map((t) => ({
|
|
1505
|
+
assignee_id: t.assignee_id,
|
|
1506
|
+
todo_description: t.todo_description,
|
|
1507
|
+
deadline: t.deadline
|
|
1508
|
+
}));
|
|
1509
|
+
const job = dispatchJob({
|
|
1510
|
+
original_prompt: plan.original_prompt,
|
|
1511
|
+
openclaw_callback: "",
|
|
1512
|
+
tasks
|
|
1513
|
+
}, db2);
|
|
1514
|
+
p.outro(`${chalk.green("\u5DF2\u5206\u53D1\uFF01")} Job: ${chalk.bold(job.job_id)}`);
|
|
1515
|
+
} else {
|
|
1516
|
+
const confirm2 = await p.confirm({
|
|
1517
|
+
message: "\u786E\u8BA4\u5206\u53D1\u8FD9\u4E9B\u4EFB\u52A1\uFF1F"
|
|
1518
|
+
});
|
|
1519
|
+
if (p.isCancel(confirm2) || !confirm2) {
|
|
1520
|
+
p.outro(chalk.dim("\u5DF2\u53D6\u6D88\u5206\u53D1"));
|
|
1521
|
+
process.exit(0);
|
|
1522
|
+
}
|
|
1523
|
+
const tasks = plan.planned_tasks.map((t) => ({
|
|
1524
|
+
assignee_id: t.assignee_id,
|
|
1525
|
+
todo_description: t.todo_description,
|
|
1526
|
+
deadline: t.deadline
|
|
1527
|
+
}));
|
|
1528
|
+
const job = dispatchJob({
|
|
1529
|
+
original_prompt: plan.original_prompt,
|
|
1530
|
+
openclaw_callback: "",
|
|
1531
|
+
tasks
|
|
1532
|
+
}, db2);
|
|
1533
|
+
p.outro(`${chalk.green("\u5DF2\u5206\u53D1\uFF01")} Job: ${chalk.bold(job.job_id)}`);
|
|
1534
|
+
}
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
spin.stop("\u89C4\u5212\u5931\u8D25");
|
|
1537
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1538
|
+
p.outro(chalk.red(message));
|
|
1539
|
+
process.exit(1);
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
914
1542
|
program.parse();
|
|
915
1543
|
//# sourceMappingURL=index.js.map
|