@humanclaw/humanclaw 1.0.2 → 1.1.1
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 +621 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -67,6 +67,11 @@ function initSchema(db2) {
|
|
|
67
67
|
CREATE INDEX IF NOT EXISTS idx_tasks_job_id ON tasks(job_id);
|
|
68
68
|
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_id ON tasks(assignee_id);
|
|
69
69
|
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
70
|
+
|
|
71
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
72
|
+
key TEXT PRIMARY KEY,
|
|
73
|
+
value TEXT NOT NULL
|
|
74
|
+
);
|
|
70
75
|
`);
|
|
71
76
|
}
|
|
72
77
|
|
|
@@ -368,6 +373,264 @@ function dispatchJob(request, db2) {
|
|
|
368
373
|
return { ...job, tasks };
|
|
369
374
|
}
|
|
370
375
|
|
|
376
|
+
// src/llm/claude.ts
|
|
377
|
+
var ClaudeProvider = class {
|
|
378
|
+
apiKey;
|
|
379
|
+
model;
|
|
380
|
+
constructor(apiKey, model) {
|
|
381
|
+
this.apiKey = apiKey;
|
|
382
|
+
this.model = model || "claude-sonnet-4-20250514";
|
|
383
|
+
}
|
|
384
|
+
async complete(request) {
|
|
385
|
+
const systemMessages = request.messages.filter((m) => m.role === "system");
|
|
386
|
+
const nonSystemMessages = request.messages.filter((m) => m.role !== "system");
|
|
387
|
+
const body = {
|
|
388
|
+
model: this.model,
|
|
389
|
+
max_tokens: request.max_tokens || 4096,
|
|
390
|
+
messages: nonSystemMessages.map((m) => ({ role: m.role, content: m.content }))
|
|
391
|
+
};
|
|
392
|
+
if (request.temperature !== void 0) {
|
|
393
|
+
body.temperature = request.temperature;
|
|
394
|
+
}
|
|
395
|
+
if (systemMessages.length > 0) {
|
|
396
|
+
body.system = systemMessages.map((m) => m.content).join("\n\n");
|
|
397
|
+
}
|
|
398
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
399
|
+
method: "POST",
|
|
400
|
+
headers: {
|
|
401
|
+
"Content-Type": "application/json",
|
|
402
|
+
"x-api-key": this.apiKey,
|
|
403
|
+
"anthropic-version": "2023-06-01"
|
|
404
|
+
},
|
|
405
|
+
body: JSON.stringify(body)
|
|
406
|
+
});
|
|
407
|
+
if (!response.ok) {
|
|
408
|
+
const errorText = await response.text();
|
|
409
|
+
throw new Error(`Claude API error (${response.status}): ${errorText}`);
|
|
410
|
+
}
|
|
411
|
+
const data = await response.json();
|
|
412
|
+
const textBlock = data.content.find((c) => c.type === "text");
|
|
413
|
+
if (!textBlock) {
|
|
414
|
+
throw new Error("Claude API returned no text content");
|
|
415
|
+
}
|
|
416
|
+
return { content: textBlock.text };
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// src/llm/openai.ts
|
|
421
|
+
var OpenAIProvider = class {
|
|
422
|
+
apiKey;
|
|
423
|
+
model;
|
|
424
|
+
constructor(apiKey, model) {
|
|
425
|
+
this.apiKey = apiKey;
|
|
426
|
+
this.model = model || "gpt-4o";
|
|
427
|
+
}
|
|
428
|
+
async complete(request) {
|
|
429
|
+
const body = {
|
|
430
|
+
model: this.model,
|
|
431
|
+
max_tokens: request.max_tokens || 4096,
|
|
432
|
+
messages: request.messages.map((m) => ({ role: m.role, content: m.content }))
|
|
433
|
+
};
|
|
434
|
+
if (request.temperature !== void 0) {
|
|
435
|
+
body.temperature = request.temperature;
|
|
436
|
+
}
|
|
437
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
438
|
+
method: "POST",
|
|
439
|
+
headers: {
|
|
440
|
+
"Content-Type": "application/json",
|
|
441
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
442
|
+
},
|
|
443
|
+
body: JSON.stringify(body)
|
|
444
|
+
});
|
|
445
|
+
if (!response.ok) {
|
|
446
|
+
const errorText = await response.text();
|
|
447
|
+
throw new Error(`OpenAI API error (${response.status}): ${errorText}`);
|
|
448
|
+
}
|
|
449
|
+
const data = await response.json();
|
|
450
|
+
const content = data.choices[0]?.message?.content;
|
|
451
|
+
if (!content) {
|
|
452
|
+
throw new Error("OpenAI API returned no content");
|
|
453
|
+
}
|
|
454
|
+
return { content };
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// src/models/config.ts
|
|
459
|
+
function getConfig(key, db2) {
|
|
460
|
+
const conn = db2 ?? getDb();
|
|
461
|
+
const row = conn.prepare("SELECT value FROM config WHERE key = ?").get(key);
|
|
462
|
+
return row?.value;
|
|
463
|
+
}
|
|
464
|
+
function setConfig(key, value, db2) {
|
|
465
|
+
const conn = db2 ?? getDb();
|
|
466
|
+
conn.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run(key, value);
|
|
467
|
+
}
|
|
468
|
+
function deleteConfig(key, db2) {
|
|
469
|
+
const conn = db2 ?? getDb();
|
|
470
|
+
conn.prepare("DELETE FROM config WHERE key = ?").run(key);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/llm/index.ts
|
|
474
|
+
function getLlmConfig() {
|
|
475
|
+
const dbProvider = getConfig("llm_provider");
|
|
476
|
+
const dbApiKey = getConfig("llm_api_key");
|
|
477
|
+
const dbModel = getConfig("llm_model");
|
|
478
|
+
const provider = dbProvider || process.env.HUMANCLAW_LLM_PROVIDER || "claude";
|
|
479
|
+
const apiKey = dbApiKey || process.env.HUMANCLAW_LLM_API_KEY || "";
|
|
480
|
+
const model = dbModel || process.env.HUMANCLAW_LLM_MODEL;
|
|
481
|
+
if (!apiKey) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
"\u672A\u914D\u7F6E LLM API Key\u3002\u8BF7\u5728 Dashboard \u8BBE\u7F6E\u4E2D\u914D\u7F6E\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF HUMANCLAW_LLM_API_KEY\u3002"
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
if (provider !== "claude" && provider !== "openai") {
|
|
487
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 LLM \u63D0\u4F9B\u5546: ${provider}\u3002\u652F\u6301: claude, openai`);
|
|
488
|
+
}
|
|
489
|
+
return { provider, apiKey, model: model || void 0 };
|
|
490
|
+
}
|
|
491
|
+
function createLlmProvider(config) {
|
|
492
|
+
const cfg = config || getLlmConfig();
|
|
493
|
+
switch (cfg.provider) {
|
|
494
|
+
case "claude":
|
|
495
|
+
return new ClaudeProvider(cfg.apiKey, cfg.model);
|
|
496
|
+
case "openai":
|
|
497
|
+
return new OpenAIProvider(cfg.apiKey, cfg.model);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/services/planner.ts
|
|
502
|
+
function buildSystemPrompt() {
|
|
503
|
+
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
|
|
504
|
+
|
|
505
|
+
\u89C4\u5219\uFF1A
|
|
506
|
+
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
|
|
507
|
+
2. \u6839\u636E\u6BCF\u4E2A Agent \u7684\u6280\u80FD\uFF08capabilities\uFF09\u548C\u5F53\u524D\u8D1F\u8F7D\u6765\u5339\u914D\u5206\u914D
|
|
508
|
+
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
|
|
509
|
+
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
|
|
510
|
+
5. \u6BCF\u4E2A Agent \u6700\u591A\u5206\u914D\u4E00\u4E2A\u4EFB\u52A1\uFF08\u9664\u975E\u4EBA\u624B\u4E0D\u591F\uFF09
|
|
511
|
+
|
|
512
|
+
\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
|
|
513
|
+
|
|
514
|
+
\`\`\`json
|
|
515
|
+
[
|
|
516
|
+
{
|
|
517
|
+
"assignee_id": "agent\u7684ID",
|
|
518
|
+
"assignee_name": "agent\u7684\u540D\u5B57",
|
|
519
|
+
"todo_description": "\u7B80\u77ED\u7684\u4EFB\u52A1\u6807\u9898/\u63CF\u8FF0",
|
|
520
|
+
"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",
|
|
521
|
+
"deadline": "ISO 8601 \u65F6\u95F4"
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
\`\`\``;
|
|
525
|
+
}
|
|
526
|
+
function buildUserPrompt(prompt, agents) {
|
|
527
|
+
const now = /* @__PURE__ */ new Date();
|
|
528
|
+
const agentList = agents.map((a) => {
|
|
529
|
+
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";
|
|
530
|
+
const speed = a.avg_delivery_hours !== null ? `\u5E73\u5747\u4EA4\u4ED8\u65F6\u95F4 ${a.avg_delivery_hours}h` : "\u6682\u65E0\u5386\u53F2\u6570\u636E";
|
|
531
|
+
return `- ${a.name} (ID: ${a.agent_id}) \u6280\u80FD: [${a.capabilities.join(", ")}] ${load} ${speed}`;
|
|
532
|
+
}).join("\n");
|
|
533
|
+
return `\u5F53\u524D\u65F6\u95F4: ${now.toISOString()}
|
|
534
|
+
|
|
535
|
+
\u53EF\u7528\u7684\u7269\u7406\u8282\u70B9\uFF1A
|
|
536
|
+
${agentList}
|
|
537
|
+
|
|
538
|
+
\u9700\u6C42\uFF1A
|
|
539
|
+
${prompt}
|
|
540
|
+
|
|
541
|
+
\u8BF7\u6839\u636E\u4EE5\u4E0A\u4FE1\u606F\u62C6\u89E3\u4EFB\u52A1\u5E76\u5206\u914D\u3002\u8F93\u51FA JSON \u6570\u7EC4\u3002`;
|
|
542
|
+
}
|
|
543
|
+
function extractJson(raw) {
|
|
544
|
+
const codeBlockMatch = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
545
|
+
if (codeBlockMatch) {
|
|
546
|
+
return codeBlockMatch[1].trim();
|
|
547
|
+
}
|
|
548
|
+
const arrayMatch = raw.match(/\[[\s\S]*\]/);
|
|
549
|
+
if (arrayMatch) {
|
|
550
|
+
return arrayMatch[0];
|
|
551
|
+
}
|
|
552
|
+
return raw.trim();
|
|
553
|
+
}
|
|
554
|
+
function validatePlannedTasks(parsed, validAgentIds, agents) {
|
|
555
|
+
if (!Array.isArray(parsed)) {
|
|
556
|
+
throw new Error("LLM \u8FD4\u56DE\u7684\u4E0D\u662F\u6709\u6548\u7684\u4EFB\u52A1\u6570\u7EC4");
|
|
557
|
+
}
|
|
558
|
+
if (parsed.length === 0) {
|
|
559
|
+
throw new Error("LLM \u672A\u751F\u6210\u4EFB\u4F55\u4EFB\u52A1");
|
|
560
|
+
}
|
|
561
|
+
return parsed.map((item, i) => {
|
|
562
|
+
if (!item.todo_description || typeof item.todo_description !== "string") {
|
|
563
|
+
throw new Error(`\u4EFB\u52A1 ${i + 1} \u7F3A\u5C11 todo_description`);
|
|
564
|
+
}
|
|
565
|
+
if (!item.briefing || typeof item.briefing !== "string") {
|
|
566
|
+
throw new Error(`\u4EFB\u52A1 ${i + 1} \u7F3A\u5C11 briefing (\u8BDD\u672F)`);
|
|
567
|
+
}
|
|
568
|
+
let assigneeId = String(item.assignee_id || "");
|
|
569
|
+
let assigneeName = String(item.assignee_name || "");
|
|
570
|
+
if (!validAgentIds.has(assigneeId)) {
|
|
571
|
+
const fallback = agents[0];
|
|
572
|
+
if (fallback) {
|
|
573
|
+
assigneeId = fallback.agent_id;
|
|
574
|
+
assigneeName = fallback.name;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
let deadline = String(item.deadline || "");
|
|
578
|
+
if (!deadline || isNaN(Date.parse(deadline))) {
|
|
579
|
+
deadline = new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString();
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
assignee_id: assigneeId,
|
|
583
|
+
assignee_name: assigneeName,
|
|
584
|
+
todo_description: String(item.todo_description),
|
|
585
|
+
briefing: String(item.briefing),
|
|
586
|
+
deadline
|
|
587
|
+
};
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
async function planJob(request, provider, db2) {
|
|
591
|
+
const conn = db2 ?? getDb();
|
|
592
|
+
const allAgents = listAgentsWithMetrics(conn);
|
|
593
|
+
let agents;
|
|
594
|
+
if (request.agent_ids && request.agent_ids.length > 0) {
|
|
595
|
+
agents = allAgents.filter((a) => request.agent_ids.includes(a.agent_id));
|
|
596
|
+
if (agents.length === 0) {
|
|
597
|
+
throw new Error("\u6307\u5B9A\u7684 Agent \u5747\u4E0D\u5B58\u5728");
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
agents = allAgents.filter((a) => a.status === "IDLE");
|
|
601
|
+
if (agents.length === 0) {
|
|
602
|
+
agents = allAgents.filter((a) => a.status !== "OFFLINE" && a.status !== "OOM");
|
|
603
|
+
}
|
|
604
|
+
if (agents.length === 0) {
|
|
605
|
+
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");
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const llm = provider ?? createLlmProvider();
|
|
609
|
+
const systemPrompt = buildSystemPrompt();
|
|
610
|
+
const userPrompt = buildUserPrompt(request.prompt, agents);
|
|
611
|
+
const response = await llm.complete({
|
|
612
|
+
messages: [
|
|
613
|
+
{ role: "system", content: systemPrompt },
|
|
614
|
+
{ role: "user", content: userPrompt }
|
|
615
|
+
],
|
|
616
|
+
temperature: 0.3,
|
|
617
|
+
max_tokens: 4096
|
|
618
|
+
});
|
|
619
|
+
const jsonStr = extractJson(response.content);
|
|
620
|
+
let parsed;
|
|
621
|
+
try {
|
|
622
|
+
parsed = JSON.parse(jsonStr);
|
|
623
|
+
} catch {
|
|
624
|
+
throw new Error("AI \u8FD4\u56DE\u7684\u5185\u5BB9\u65E0\u6CD5\u89E3\u6790\u4E3A JSON\uFF0C\u8BF7\u91CD\u8BD5");
|
|
625
|
+
}
|
|
626
|
+
const validIds = new Set(agents.map((a) => a.agent_id));
|
|
627
|
+
const plannedTasks = validatePlannedTasks(parsed, validIds, agents);
|
|
628
|
+
return {
|
|
629
|
+
original_prompt: request.prompt,
|
|
630
|
+
planned_tasks: plannedTasks
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
371
634
|
// src/routes/jobs.ts
|
|
372
635
|
var router2 = Router2();
|
|
373
636
|
router2.post("/create", (req, res) => {
|
|
@@ -399,6 +662,21 @@ router2.get("/active", (_req, res) => {
|
|
|
399
662
|
const jobs = listActiveJobs();
|
|
400
663
|
res.json({ jobs });
|
|
401
664
|
});
|
|
665
|
+
router2.post("/plan", async (req, res) => {
|
|
666
|
+
const body = req.body;
|
|
667
|
+
if (!body.prompt || typeof body.prompt !== "string") {
|
|
668
|
+
res.status(400).json({ error: "prompt is required" });
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
const plan = await planJob(body);
|
|
673
|
+
res.json(plan);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
676
|
+
const status = message.includes("API Key") || message.includes("API key") ? 503 : 400;
|
|
677
|
+
res.status(status).json({ error: message });
|
|
678
|
+
}
|
|
679
|
+
});
|
|
402
680
|
router2.get("/:job_id", (req, res) => {
|
|
403
681
|
const { job_id } = req.params;
|
|
404
682
|
const job = getJobWithTasks(job_id);
|
|
@@ -565,6 +843,49 @@ router4.post("/:job_id/sync", async (req, res) => {
|
|
|
565
843
|
});
|
|
566
844
|
var sync_default = router4;
|
|
567
845
|
|
|
846
|
+
// src/routes/config.ts
|
|
847
|
+
import { Router as Router5 } from "express";
|
|
848
|
+
var router5 = Router5();
|
|
849
|
+
router5.get("/", (_req, res) => {
|
|
850
|
+
const provider = getConfig("llm_provider") || process.env.HUMANCLAW_LLM_PROVIDER || "claude";
|
|
851
|
+
const apiKey = getConfig("llm_api_key") || process.env.HUMANCLAW_LLM_API_KEY || "";
|
|
852
|
+
const model = getConfig("llm_model") || process.env.HUMANCLAW_LLM_MODEL || "";
|
|
853
|
+
const keySource = getConfig("llm_api_key") ? "dashboard" : process.env.HUMANCLAW_LLM_API_KEY ? "env" : "none";
|
|
854
|
+
res.json({
|
|
855
|
+
provider,
|
|
856
|
+
api_key_set: apiKey.length > 0,
|
|
857
|
+
api_key_masked: apiKey ? apiKey.slice(0, 8) + "..." + apiKey.slice(-4) : "",
|
|
858
|
+
api_key_source: keySource,
|
|
859
|
+
model
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
router5.put("/", (req, res) => {
|
|
863
|
+
const { provider, api_key, model } = req.body;
|
|
864
|
+
if (provider !== void 0) {
|
|
865
|
+
if (provider !== "claude" && provider !== "openai") {
|
|
866
|
+
res.status(400).json({ error: "\u4E0D\u652F\u6301\u7684\u63D0\u4F9B\u5546\uFF0C\u652F\u6301: claude, openai" });
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
setConfig("llm_provider", provider);
|
|
870
|
+
}
|
|
871
|
+
if (api_key !== void 0) {
|
|
872
|
+
if (api_key === "") {
|
|
873
|
+
deleteConfig("llm_api_key");
|
|
874
|
+
} else {
|
|
875
|
+
setConfig("llm_api_key", api_key);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (model !== void 0) {
|
|
879
|
+
if (model === "") {
|
|
880
|
+
deleteConfig("llm_model");
|
|
881
|
+
} else {
|
|
882
|
+
setConfig("llm_model", model);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
res.json({ ok: true });
|
|
886
|
+
});
|
|
887
|
+
var config_default = router5;
|
|
888
|
+
|
|
568
889
|
// src/dashboard.ts
|
|
569
890
|
function getDashboardHtml() {
|
|
570
891
|
return `<!DOCTYPE html>
|
|
@@ -580,17 +901,20 @@ body{background:var(--bg);color:var(--text);font-family:var(--font-sans);min-hei
|
|
|
580
901
|
header{padding:20px 32px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:14px}
|
|
581
902
|
header h1{font-family:var(--font-mono);font-size:22px;color:var(--accent);letter-spacing:1.5px}
|
|
582
903
|
.subtitle{color:var(--text-dim);font-size:12px;border-left:1px solid var(--border);padding-left:14px}
|
|
904
|
+
.hdr-spacer{flex:1}
|
|
905
|
+
.gear-btn{background:none;border:1px solid var(--border);color:var(--text-dim);width:34px;height:34px;border-radius:8px;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;transition:all .15s}
|
|
906
|
+
.gear-btn:hover{color:var(--accent);border-color:var(--accent);background:var(--accent-dim)}
|
|
583
907
|
nav{display:flex;border-bottom:1px solid var(--border);padding:0 32px;gap:4px}
|
|
584
908
|
.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}
|
|
585
909
|
.tab:hover{color:var(--text);background:var(--surface)}
|
|
586
910
|
.tab.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
587
911
|
main{padding:24px 32px;max-width:1200px}
|
|
588
912
|
.panel{display:none}.panel.active{display:block}
|
|
589
|
-
/*
|
|
913
|
+
/* Cards */
|
|
590
914
|
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;transition:border-color .15s}
|
|
591
915
|
.card:hover{border-color:color-mix(in srgb,var(--accent) 40%,transparent)}
|
|
592
916
|
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin-top:16px}
|
|
593
|
-
/*
|
|
917
|
+
/* Agent Card */
|
|
594
918
|
.agent-header{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
595
919
|
.dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
|
596
920
|
.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)}
|
|
@@ -603,13 +927,13 @@ main{padding:24px 32px;max-width:1200px}
|
|
|
603
927
|
.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}
|
|
604
928
|
.agent-actions button{background:var(--red);color:#fff;border:none;border-radius:6px;padding:4px 10px;font-size:11px;cursor:pointer}
|
|
605
929
|
.agent-actions button:hover{opacity:.8}
|
|
606
|
-
/*
|
|
930
|
+
/* Stats Bar */
|
|
607
931
|
.stats{display:flex;gap:12px;flex-wrap:wrap}
|
|
608
932
|
.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 22px;text-align:center;min-width:90px}
|
|
609
933
|
.stat-val{font-size:28px;font-weight:700;font-family:var(--font-mono)}
|
|
610
934
|
.stat-lbl{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-top:2px}
|
|
611
|
-
/*
|
|
612
|
-
.form-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;margin-top:16px;max-width:
|
|
935
|
+
/* Forms */
|
|
936
|
+
.form-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;margin-top:16px;max-width:620px}
|
|
613
937
|
.form-card h3{font-size:15px;color:var(--accent);font-family:var(--font-mono);margin-bottom:16px}
|
|
614
938
|
.fg{margin-bottom:14px}
|
|
615
939
|
.fg label{display:block;font-size:12px;color:var(--text-dim);margin-bottom:5px;font-weight:500}
|
|
@@ -620,17 +944,18 @@ main{padding:24px 32px;max-width:1200px}
|
|
|
620
944
|
.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}
|
|
621
945
|
.btn:hover{opacity:.85;transform:translateY(-1px)}
|
|
622
946
|
.btn:active{transform:translateY(0)}
|
|
947
|
+
.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
|
|
623
948
|
.btn-primary{background:var(--accent);color:var(--bg)}
|
|
624
949
|
.btn-green{background:var(--green);color:#fff}
|
|
625
950
|
.btn-danger{background:var(--red);color:#fff}
|
|
626
951
|
.btn-ghost{background:transparent;color:var(--accent);border:1px solid var(--border)}
|
|
627
952
|
.btn-ghost:hover{background:var(--accent-dim)}
|
|
628
953
|
.btn-sm{padding:6px 14px;font-size:12px}
|
|
629
|
-
.btn-group{display:flex;gap:10px;margin-top:16px}
|
|
630
|
-
/*
|
|
954
|
+
.btn-group{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap}
|
|
955
|
+
/* Section header */
|
|
631
956
|
.section-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}
|
|
632
957
|
.section-hd h2{font-size:16px;font-weight:600}
|
|
633
|
-
/*
|
|
958
|
+
/* Job card */
|
|
634
959
|
.job-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-top:14px}
|
|
635
960
|
.job-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
|
|
636
961
|
.job-title{font-weight:600;font-size:14px}
|
|
@@ -646,30 +971,58 @@ main{padding:24px 32px;max-width:1200px}
|
|
|
646
971
|
.tcard{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:6px;font-size:12px}
|
|
647
972
|
.tcard-trace{font-family:var(--font-mono);font-size:10px;color:var(--accent);margin-bottom:3px}
|
|
648
973
|
.tcard-meta{font-size:10px;color:var(--text-dim);margin-top:3px}
|
|
649
|
-
/*
|
|
650
|
-
.task-row{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:10px;position:relative}
|
|
651
|
-
.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}
|
|
652
|
-
.task-row .fg{margin-bottom:10px}
|
|
653
|
-
.task-row .fg:last-child{margin-bottom:0}
|
|
654
|
-
.task-row-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
|
655
|
-
/* \u2500\u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
974
|
+
/* Toast */
|
|
656
975
|
.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)}
|
|
657
976
|
.toast.ok{background:var(--green);color:#fff}.toast.err{background:var(--red);color:#fff}
|
|
658
977
|
@keyframes slide-up{from{transform:translateY(16px);opacity:0}to{transform:translateY(0);opacity:1}}
|
|
659
|
-
/*
|
|
978
|
+
/* Empty state */
|
|
660
979
|
.empty-state{text-align:center;padding:48px 20px;color:var(--text-dim)}
|
|
661
980
|
.empty-state .icon{font-size:48px;margin-bottom:12px;opacity:.6}
|
|
662
981
|
.empty-state p{font-size:14px;margin-bottom:16px;max-width:360px;margin-left:auto;margin-right:auto;line-height:1.6}
|
|
663
|
-
/*
|
|
664
|
-
.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:
|
|
665
|
-
.overlay .form-card{max-width:
|
|
982
|
+
/* Modal/Overlay */
|
|
983
|
+
.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}
|
|
984
|
+
.overlay .form-card{max-width:620px;width:100%;max-height:85vh;overflow-y:auto;margin:0}
|
|
666
985
|
@keyframes fade-in{from{opacity:0}to{opacity:1}}
|
|
986
|
+
/* Agent chips for AI planning */
|
|
987
|
+
.chip-bar{display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap}
|
|
988
|
+
.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}
|
|
989
|
+
.chip-filter.active{background:var(--accent-dim);color:var(--accent);border-color:var(--accent)}
|
|
990
|
+
.agent-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
|
|
991
|
+
.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}
|
|
992
|
+
.achip:hover{border-color:var(--text-dim)}
|
|
993
|
+
.achip.selected{border-color:var(--accent);background:var(--accent-dim)}
|
|
994
|
+
.achip .adot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
995
|
+
.achip .adot.IDLE{background:var(--green)}.achip .adot.BUSY{background:var(--yellow)}.achip .adot.OFFLINE{background:var(--red)}.achip .adot.OOM{background:var(--purple)}
|
|
996
|
+
/* Plan preview cards */
|
|
997
|
+
.plan-card{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px}
|
|
998
|
+
.plan-card-hd{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
|
999
|
+
.plan-card-hd .adot{width:8px;height:8px;border-radius:50%}
|
|
1000
|
+
.plan-card-agent{font-weight:600;font-size:13px}
|
|
1001
|
+
.plan-card-agentid{font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}
|
|
1002
|
+
.plan-card-desc{font-size:13px;margin-bottom:10px;font-weight:500}
|
|
1003
|
+
.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}
|
|
1004
|
+
.briefing-label{font-size:10px;color:var(--accent);font-weight:600;margin-bottom:4px;font-family:var(--font-mono)}
|
|
1005
|
+
.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}
|
|
1006
|
+
.briefing-copy:hover{color:var(--accent);border-color:var(--accent)}
|
|
1007
|
+
.plan-card-dl{font-size:11px;color:var(--text-dim);font-family:var(--font-mono)}
|
|
1008
|
+
/* Spinner */
|
|
1009
|
+
.spinner-wrap{text-align:center;padding:40px 20px}
|
|
1010
|
+
.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}
|
|
1011
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
1012
|
+
.spinner-text{color:var(--text-dim);font-size:13px;margin-top:12px}
|
|
1013
|
+
/* Manual task row */
|
|
1014
|
+
.task-row{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:10px;position:relative}
|
|
1015
|
+
.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}
|
|
1016
|
+
.task-row .fg{margin-bottom:10px}
|
|
1017
|
+
.task-row .fg:last-child{margin-bottom:0}
|
|
667
1018
|
</style>
|
|
668
1019
|
</head>
|
|
669
1020
|
<body>
|
|
670
1021
|
<header>
|
|
671
1022
|
<h1>HumanClaw</h1>
|
|
672
1023
|
<span class="subtitle">\u5F02\u6B65\u7269\u7406\u8282\u70B9\u7F16\u6392\u6846\u67B6</span>
|
|
1024
|
+
<span class="hdr-spacer"></span>
|
|
1025
|
+
<button class="gear-btn" onclick="showSettings()" title="\u8BBE\u7F6E">⚙</button>
|
|
673
1026
|
</header>
|
|
674
1027
|
<nav>
|
|
675
1028
|
<button class="tab active" data-panel="fleet">\u78B3\u57FA\u7B97\u529B\u6C60</button>
|
|
@@ -691,6 +1044,8 @@ function toast(msg,ok){
|
|
|
691
1044
|
setTimeout(()=>t.remove(),3500);
|
|
692
1045
|
}
|
|
693
1046
|
let cachedAgents=[];
|
|
1047
|
+
let currentPlan=null;
|
|
1048
|
+
let selectedAgentIds=new Set();
|
|
694
1049
|
|
|
695
1050
|
// \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
|
|
696
1051
|
// FLEET
|
|
@@ -781,13 +1136,12 @@ function tcard(t){
|
|
|
781
1136
|
async function loadPipeline(el){
|
|
782
1137
|
el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u4E2D...</p></div>';
|
|
783
1138
|
try{
|
|
784
|
-
// refresh agents cache
|
|
785
1139
|
try{const ar=await fetch(API+'/nodes/status');const ad=await ar.json();cachedAgents=ad.agents||[]}catch{}
|
|
786
1140
|
const r=await fetch(API+'/jobs/active');
|
|
787
1141
|
const d=await r.json();
|
|
788
1142
|
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>';
|
|
789
1143
|
if(!d.jobs.length){
|
|
790
|
-
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\
|
|
1144
|
+
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';
|
|
791
1145
|
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';
|
|
792
1146
|
h+='</p></div>';
|
|
793
1147
|
el.innerHTML=h;return;
|
|
@@ -809,19 +1163,143 @@ async function loadPipeline(el){
|
|
|
809
1163
|
el.innerHTML=h;
|
|
810
1164
|
}catch(e){el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u5931\u8D25: '+e.message+'</p></div>'}
|
|
811
1165
|
}
|
|
1166
|
+
|
|
1167
|
+
// \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
|
|
812
1168
|
window.showCreateJob=function(){
|
|
813
1169
|
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}
|
|
1170
|
+
currentPlan=null;
|
|
1171
|
+
// Pre-select all IDLE agents
|
|
1172
|
+
selectedAgentIds=new Set(cachedAgents.filter(a=>a.status==='IDLE').map(a=>a.agent_id));
|
|
814
1173
|
const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
|
|
815
1174
|
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
|
|
1175
|
+
renderPlanStep1(ov);
|
|
1176
|
+
document.body.appendChild(ov);
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
function renderPlanStep1(ov){
|
|
1180
|
+
let chipFilter='all';
|
|
1181
|
+
function renderChips(filter){
|
|
1182
|
+
chipFilter=filter;
|
|
1183
|
+
const filtered=filter==='idle'?cachedAgents.filter(a=>a.status==='IDLE'):cachedAgents;
|
|
1184
|
+
let ch='<div class="chip-bar">';
|
|
1185
|
+
ch+='<span class="chip-filter'+(filter==='all'?' active':'')+'" onclick="window._filterChips(\\'all\\')">\u5168\u90E8</span>';
|
|
1186
|
+
ch+='<span class="chip-filter'+(filter==='idle'?' active':'')+'" onclick="window._filterChips(\\'idle\\')">\u4EC5\u7A7A\u95F2</span>';
|
|
1187
|
+
ch+='</div>';
|
|
1188
|
+
ch+='<div class="agent-chips">';
|
|
1189
|
+
for(const a of filtered){
|
|
1190
|
+
const sel=selectedAgentIds.has(a.agent_id);
|
|
1191
|
+
ch+='<div class="achip'+(sel?' selected':'')+'" onclick="window._toggleChip(\\''+a.agent_id+'\\')">';
|
|
1192
|
+
ch+='<span class="adot '+a.status+'"></span>';
|
|
1193
|
+
ch+=esc(a.name);
|
|
1194
|
+
ch+='</div>';
|
|
1195
|
+
}
|
|
1196
|
+
ch+='</div>';
|
|
1197
|
+
return ch;
|
|
1198
|
+
}
|
|
1199
|
+
window._filterChips=function(f){
|
|
1200
|
+
chipFilter=f;
|
|
1201
|
+
const el=document.getElementById('agent-chip-area');
|
|
1202
|
+
if(el)el.innerHTML=renderChips(f);
|
|
1203
|
+
};
|
|
1204
|
+
window._toggleChip=function(id){
|
|
1205
|
+
if(selectedAgentIds.has(id))selectedAgentIds.delete(id);
|
|
1206
|
+
else selectedAgentIds.add(id);
|
|
1207
|
+
const el=document.getElementById('agent-chip-area');
|
|
1208
|
+
if(el)el.innerHTML=renderChips(chipFilter);
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
ov.innerHTML='<div class="form-card"><h3>AI \u667A\u80FD\u89C4\u5212</h3>'
|
|
1212
|
+
+'<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>'
|
|
1213
|
+
+'<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>'
|
|
1214
|
+
+'<div id="agent-chip-area">'+renderChips('all')+'</div></div>'
|
|
1215
|
+
+'<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>'
|
|
1216
|
+
+'<div class="btn-group">'
|
|
1217
|
+
+'<button class="btn btn-primary" onclick="planWithAI()">AI \u89C4\u5212</button>'
|
|
1218
|
+
+'<button class="btn btn-ghost" onclick="showManualCreate()">\u624B\u52A8\u521B\u5EFA</button>'
|
|
1219
|
+
+'<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>'
|
|
1220
|
+
+'</div></div>';
|
|
1221
|
+
setTimeout(()=>{const ta=document.getElementById('plan-prompt');if(ta)ta.focus()},50);
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
window.planWithAI=async function(){
|
|
1225
|
+
const prompt=document.getElementById('plan-prompt').value.trim();
|
|
1226
|
+
const callback=document.getElementById('plan-callback')?.value.trim()||'';
|
|
1227
|
+
if(!prompt){toast('\u8BF7\u8F93\u5165\u9700\u6C42\u63CF\u8FF0',false);return}
|
|
1228
|
+
if(selectedAgentIds.size===0){toast('\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u7269\u7406\u8282\u70B9',false);return}
|
|
1229
|
+
|
|
1230
|
+
const ov=document.getElementById('overlay');
|
|
1231
|
+
const fc=ov.querySelector('.form-card');
|
|
1232
|
+
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>';
|
|
1233
|
+
|
|
1234
|
+
try{
|
|
1235
|
+
const r=await fetch(API+'/jobs/plan',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt,agent_ids:Array.from(selectedAgentIds)})});
|
|
1236
|
+
const d=await r.json();
|
|
1237
|
+
if(!r.ok){
|
|
1238
|
+
toast(d.error||'\u89C4\u5212\u5931\u8D25',false);
|
|
1239
|
+
renderPlanStep1(ov);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
currentPlan={...d,openclaw_callback:callback};
|
|
1243
|
+
renderPlanStep2(ov);
|
|
1244
|
+
}catch(e){
|
|
1245
|
+
toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);
|
|
1246
|
+
renderPlanStep1(ov);
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
function renderPlanStep2(ov){
|
|
1251
|
+
const p=currentPlan;
|
|
1252
|
+
let h='<h3>\u89C4\u5212\u9884\u89C8</h3>';
|
|
1253
|
+
h+='<div style="font-size:12px;color:var(--text-dim);margin-bottom:14px">\u9700\u6C42: '+esc(p.original_prompt)+'</div>';
|
|
1254
|
+
for(let i=0;i<p.planned_tasks.length;i++){
|
|
1255
|
+
const t=p.planned_tasks[i];
|
|
1256
|
+
const agent=cachedAgents.find(a=>a.agent_id===t.assignee_id);
|
|
1257
|
+
const status=agent?agent.status:'IDLE';
|
|
1258
|
+
h+='<div class="plan-card">';
|
|
1259
|
+
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>';
|
|
1260
|
+
h+='<div class="plan-card-desc">'+esc(t.todo_description)+'</div>';
|
|
1261
|
+
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>';
|
|
1262
|
+
h+='<div class="plan-card-dl">\u622A\u6B62: '+new Date(t.deadline).toLocaleString()+'</div>';
|
|
1263
|
+
h+='</div>';
|
|
1264
|
+
}
|
|
1265
|
+
h+='<div class="btn-group">';
|
|
1266
|
+
h+='<button class="btn btn-green" onclick="dispatchPlan()">\u786E\u8BA4\u5206\u53D1</button>';
|
|
1267
|
+
h+='<button class="btn btn-ghost" onclick="renderPlanStep1(document.getElementById(\\'overlay\\'))">\u91CD\u65B0\u89C4\u5212</button>';
|
|
1268
|
+
h+='<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>';
|
|
1269
|
+
h+='</div>';
|
|
1270
|
+
ov.querySelector('.form-card').innerHTML=h;
|
|
1271
|
+
}
|
|
1272
|
+
window.renderPlanStep1=renderPlanStep1;
|
|
1273
|
+
|
|
1274
|
+
window._copyBriefing=function(i){
|
|
1275
|
+
const text=currentPlan.planned_tasks[i].briefing;
|
|
1276
|
+
navigator.clipboard.writeText(text).then(()=>toast('\u8BDD\u672F\u5DF2\u590D\u5236',true)).catch(()=>toast('\u590D\u5236\u5931\u8D25',false));
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
window.dispatchPlan=async function(){
|
|
1280
|
+
const p=currentPlan;
|
|
1281
|
+
const tasks=p.planned_tasks.map(t=>({assignee_id:t.assignee_id,todo_description:t.todo_description,deadline:t.deadline}));
|
|
1282
|
+
try{
|
|
1283
|
+
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})});
|
|
1284
|
+
const d=await r.json();
|
|
1285
|
+
if(!r.ok){toast(d.error||'\u5206\u53D1\u5931\u8D25',false);return}
|
|
1286
|
+
toast('\u4EFB\u52A1\u5DF2\u5206\u53D1\uFF01Job: '+d.job_id,true);
|
|
1287
|
+
document.getElementById('overlay').remove();
|
|
1288
|
+
load('pipeline');
|
|
1289
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
// \u2500\u2500\u2500 Manual creation (fallback) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1293
|
+
window.showManualCreate=function(){
|
|
1294
|
+
const ov=document.getElementById('overlay');
|
|
816
1295
|
const optionsHtml=cachedAgents.map(a=>'<option value="'+a.agent_id+'">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');
|
|
817
1296
|
const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);
|
|
818
|
-
ov.innerHTML='<
|
|
1297
|
+
ov.querySelector('.form-card').innerHTML='<h3>\u624B\u52A8\u521B\u5EFA\u4EFB\u52A1</h3>'
|
|
819
1298
|
+'<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>'
|
|
820
1299
|
+'<div class="fg"><label>OpenClaw \u56DE\u8C03\u5730\u5740 (\u53EF\u9009)</label><input id="cj-callback" placeholder="https://..."/></div>'
|
|
821
1300
|
+'<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>'
|
|
822
|
-
+'<div id="task-rows"><div class="task-row"
|
|
823
|
-
+'<div class="btn-group"><button class="btn btn-primary" onclick="submitJob()">\u521B\u5EFA\u5E76\u5206\u53D1</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div
|
|
824
|
-
document.body.appendChild(ov);
|
|
1301
|
+
+'<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>'
|
|
1302
|
+
+'<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>';
|
|
825
1303
|
};
|
|
826
1304
|
window.addTaskRow=function(){
|
|
827
1305
|
const optionsHtml=cachedAgents.map(a=>'<option value="'+a.agent_id+'">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');
|
|
@@ -900,6 +1378,50 @@ window.doReject=async function(){
|
|
|
900
1378
|
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
901
1379
|
};
|
|
902
1380
|
|
|
1381
|
+
// \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
|
|
1382
|
+
// SETTINGS
|
|
1383
|
+
// \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
|
|
1384
|
+
window.showSettings=async function(){
|
|
1385
|
+
const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
|
|
1386
|
+
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
|
|
1387
|
+
ov.innerHTML='<div class="form-card"><h3>LLM \u8BBE\u7F6E</h3><div class="spinner-wrap"><div class="spinner"></div></div></div>';
|
|
1388
|
+
document.body.appendChild(ov);
|
|
1389
|
+
|
|
1390
|
+
try{
|
|
1391
|
+
const r=await fetch(API+'/config');
|
|
1392
|
+
const cfg=await r.json();
|
|
1393
|
+
const fc=ov.querySelector('.form-card');
|
|
1394
|
+
let statusHtml='';
|
|
1395
|
+
if(cfg.api_key_set){
|
|
1396
|
+
const src=cfg.api_key_source==='dashboard'?'Dashboard \u914D\u7F6E':'\u73AF\u5883\u53D8\u91CF';
|
|
1397
|
+
statusHtml='<div style="background:var(--accent-dim);border:1px solid color-mix(in srgb,var(--accent) 25%,transparent);border-radius:8px;padding:10px 14px;margin-bottom:16px;font-size:12px"><span style="color:var(--green)">✓</span> API Key \u5DF2\u914D\u7F6E <span style="color:var(--text-dim)">('+esc(cfg.api_key_masked)+' | \u6765\u6E90: '+src+')</span></div>';
|
|
1398
|
+
}else{
|
|
1399
|
+
statusHtml='<div style="background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.25);border-radius:8px;padding:10px 14px;margin-bottom:16px;font-size:12px;color:var(--red)">⚠ \u672A\u914D\u7F6E API Key\uFF0CAI \u89C4\u5212\u529F\u80FD\u4E0D\u53EF\u7528</div>';
|
|
1400
|
+
}
|
|
1401
|
+
fc.innerHTML='<h3>LLM \u8BBE\u7F6E</h3>'+statusHtml
|
|
1402
|
+
+'<div class="fg"><label>\u63D0\u4F9B\u5546</label><select id="cfg-provider"><option value="claude"'+(cfg.provider==='claude'?' selected':'')+'>Claude (Anthropic)</option><option value="openai"'+(cfg.provider==='openai'?' selected':'')+'>OpenAI</option></select></div>'
|
|
1403
|
+
+'<div class="fg"><label>API Key</label><input id="cfg-key" type="password" placeholder="'+(cfg.api_key_set?'\u5DF2\u914D\u7F6E\uFF0C\u7559\u7A7A\u5219\u4E0D\u4FEE\u6539':'\u8F93\u5165\u4F60\u7684 API Key...')+'"/><div class="hint">Claude: sk-ant-... | OpenAI: sk-...</div></div>'
|
|
1404
|
+
+'<div class="fg"><label>\u6A21\u578B <span style="color:var(--text-dim);font-weight:400">(\u53EF\u9009\uFF0C\u7559\u7A7A\u7528\u9ED8\u8BA4)</span></label><input id="cfg-model" value="'+esc(cfg.model||'')+'" placeholder="\u4F8B: claude-sonnet-4-20250514 / gpt-4o"/></div>'
|
|
1405
|
+
+'<div class="btn-group"><button class="btn btn-primary" onclick="saveSettings()">\u4FDD\u5B58</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div>';
|
|
1406
|
+
}catch{
|
|
1407
|
+
ov.querySelector('.form-card').innerHTML='<h3>LLM \u8BBE\u7F6E</h3><p style="color:var(--red)">\u52A0\u8F7D\u914D\u7F6E\u5931\u8D25</p>';
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
window.saveSettings=async function(){
|
|
1411
|
+
const provider=document.getElementById('cfg-provider').value;
|
|
1412
|
+
const apiKey=document.getElementById('cfg-key').value.trim();
|
|
1413
|
+
const model=document.getElementById('cfg-model').value.trim();
|
|
1414
|
+
const body={provider};
|
|
1415
|
+
if(apiKey)body.api_key=apiKey;
|
|
1416
|
+
body.model=model;
|
|
1417
|
+
try{
|
|
1418
|
+
const r=await fetch(API+'/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
1419
|
+
if(!r.ok){const d=await r.json();toast(d.error||'\u4FDD\u5B58\u5931\u8D25',false);return}
|
|
1420
|
+
toast('\u8BBE\u7F6E\u5DF2\u4FDD\u5B58',true);
|
|
1421
|
+
document.getElementById('overlay').remove();
|
|
1422
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1423
|
+
};
|
|
1424
|
+
|
|
903
1425
|
// \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
|
|
904
1426
|
// TABS & INIT
|
|
905
1427
|
// \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
|
|
@@ -940,6 +1462,7 @@ function createServer(port = 2026) {
|
|
|
940
1462
|
app.use("/api/v1/jobs", jobs_default);
|
|
941
1463
|
app.use("/api/v1/tasks", tasks_default);
|
|
942
1464
|
app.use("/api/v1/jobs", sync_default);
|
|
1465
|
+
app.use("/api/v1/config", config_default);
|
|
943
1466
|
const dashboardHtml = getDashboardHtml();
|
|
944
1467
|
app.get("/", (_req, res) => {
|
|
945
1468
|
res.type("html").send(dashboardHtml);
|
|
@@ -1059,5 +1582,78 @@ program.command("status").description("Show active jobs overview").action(() =>
|
|
|
1059
1582
|
console.log();
|
|
1060
1583
|
}
|
|
1061
1584
|
});
|
|
1585
|
+
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) => {
|
|
1586
|
+
const db2 = getDb();
|
|
1587
|
+
initSchema(db2);
|
|
1588
|
+
p.intro(chalk.bgCyan(" AI \u667A\u80FD\u89C4\u5212 "));
|
|
1589
|
+
let prompt = promptArg;
|
|
1590
|
+
if (!prompt) {
|
|
1591
|
+
const input = await p.text({
|
|
1592
|
+
message: "\u8F93\u5165\u4F60\u7684\u9700\u6C42:",
|
|
1593
|
+
placeholder: "\u4F8B: \u5B8C\u6210\u9996\u9875\u91CD\u6784\uFF0C\u5305\u62EC\u5BFC\u822A\u680F\u548C\u5185\u5BB9\u533A\u7684\u54CD\u5E94\u5F0F\u6539\u7248",
|
|
1594
|
+
validate: (v) => !v ? "\u9700\u6C42\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A" : void 0
|
|
1595
|
+
});
|
|
1596
|
+
if (p.isCancel(input)) {
|
|
1597
|
+
p.cancel("\u5DF2\u53D6\u6D88");
|
|
1598
|
+
process.exit(0);
|
|
1599
|
+
}
|
|
1600
|
+
prompt = input;
|
|
1601
|
+
}
|
|
1602
|
+
const agentIds = opts?.agents?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1603
|
+
const spin = p.spinner();
|
|
1604
|
+
spin.start("AI \u6B63\u5728\u5206\u6790\u9700\u6C42\u3001\u5339\u914D\u8282\u70B9\u3001\u751F\u6210\u8BDD\u672F...");
|
|
1605
|
+
try {
|
|
1606
|
+
const plan = await planJob({ prompt, agent_ids: agentIds }, void 0, db2);
|
|
1607
|
+
spin.stop("\u89C4\u5212\u5B8C\u6210\uFF01");
|
|
1608
|
+
console.log(chalk.bold(`
|
|
1609
|
+
\u9700\u6C42: ${chalk.white(plan.original_prompt)}
|
|
1610
|
+
`));
|
|
1611
|
+
for (const task of plan.planned_tasks) {
|
|
1612
|
+
console.log(chalk.cyan(` \u250C\u2500 ${chalk.bold(task.assignee_name)} (${chalk.dim(task.assignee_id)})`));
|
|
1613
|
+
console.log(chalk.cyan(" \u2502") + ` \u4EFB\u52A1: ${task.todo_description}`);
|
|
1614
|
+
console.log(chalk.cyan(" \u2502") + ` \u8BDD\u672F: ${chalk.italic(task.briefing)}`);
|
|
1615
|
+
console.log(chalk.cyan(" \u2502") + ` \u622A\u6B62: ${new Date(task.deadline).toLocaleString()}`);
|
|
1616
|
+
console.log(chalk.cyan(" \u2514\u2500"));
|
|
1617
|
+
console.log();
|
|
1618
|
+
}
|
|
1619
|
+
if (opts?.dispatch) {
|
|
1620
|
+
const tasks = plan.planned_tasks.map((t) => ({
|
|
1621
|
+
assignee_id: t.assignee_id,
|
|
1622
|
+
todo_description: t.todo_description,
|
|
1623
|
+
deadline: t.deadline
|
|
1624
|
+
}));
|
|
1625
|
+
const job = dispatchJob({
|
|
1626
|
+
original_prompt: plan.original_prompt,
|
|
1627
|
+
openclaw_callback: "",
|
|
1628
|
+
tasks
|
|
1629
|
+
}, db2);
|
|
1630
|
+
p.outro(`${chalk.green("\u5DF2\u5206\u53D1\uFF01")} Job: ${chalk.bold(job.job_id)}`);
|
|
1631
|
+
} else {
|
|
1632
|
+
const confirm2 = await p.confirm({
|
|
1633
|
+
message: "\u786E\u8BA4\u5206\u53D1\u8FD9\u4E9B\u4EFB\u52A1\uFF1F"
|
|
1634
|
+
});
|
|
1635
|
+
if (p.isCancel(confirm2) || !confirm2) {
|
|
1636
|
+
p.outro(chalk.dim("\u5DF2\u53D6\u6D88\u5206\u53D1"));
|
|
1637
|
+
process.exit(0);
|
|
1638
|
+
}
|
|
1639
|
+
const tasks = plan.planned_tasks.map((t) => ({
|
|
1640
|
+
assignee_id: t.assignee_id,
|
|
1641
|
+
todo_description: t.todo_description,
|
|
1642
|
+
deadline: t.deadline
|
|
1643
|
+
}));
|
|
1644
|
+
const job = dispatchJob({
|
|
1645
|
+
original_prompt: plan.original_prompt,
|
|
1646
|
+
openclaw_callback: "",
|
|
1647
|
+
tasks
|
|
1648
|
+
}, db2);
|
|
1649
|
+
p.outro(`${chalk.green("\u5DF2\u5206\u53D1\uFF01")} Job: ${chalk.bold(job.job_id)}`);
|
|
1650
|
+
}
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
spin.stop("\u89C4\u5212\u5931\u8D25");
|
|
1653
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1654
|
+
p.outro(chalk.red(message));
|
|
1655
|
+
process.exit(1);
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1062
1658
|
program.parse();
|
|
1063
1659
|
//# sourceMappingURL=index.js.map
|