@humanclaw/humanclaw 1.2.8 → 2.0.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/README.md +80 -50
- package/README_EN.md +83 -53
- package/dist/index.js +915 -47
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -72,6 +72,37 @@ function initSchema(db2) {
|
|
|
72
72
|
key TEXT PRIMARY KEY,
|
|
73
73
|
value TEXT NOT NULL
|
|
74
74
|
);
|
|
75
|
+
|
|
76
|
+
CREATE TABLE IF NOT EXISTS teams (
|
|
77
|
+
team_id TEXT PRIMARY KEY,
|
|
78
|
+
name TEXT NOT NULL,
|
|
79
|
+
description TEXT NOT NULL DEFAULT '',
|
|
80
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS team_members (
|
|
84
|
+
team_id TEXT NOT NULL REFERENCES teams(team_id) ON DELETE CASCADE,
|
|
85
|
+
agent_id TEXT NOT NULL REFERENCES agents(agent_id) ON DELETE CASCADE,
|
|
86
|
+
relationship TEXT NOT NULL DEFAULT '',
|
|
87
|
+
PRIMARY KEY (team_id, agent_id)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_team_members_agent ON team_members(agent_id);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS evaluations (
|
|
93
|
+
eval_id TEXT PRIMARY KEY,
|
|
94
|
+
job_id TEXT NOT NULL REFERENCES jobs(job_id) ON DELETE CASCADE,
|
|
95
|
+
agent_id TEXT NOT NULL REFERENCES agents(agent_id) ON DELETE CASCADE,
|
|
96
|
+
trace_id TEXT NOT NULL REFERENCES tasks(trace_id) ON DELETE CASCADE,
|
|
97
|
+
rating_system TEXT NOT NULL CHECK (rating_system IN ('ali', 'letter', 'em')),
|
|
98
|
+
rating TEXT NOT NULL,
|
|
99
|
+
weight REAL NOT NULL DEFAULT 1.0,
|
|
100
|
+
comment TEXT NOT NULL DEFAULT '',
|
|
101
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_evaluations_job ON evaluations(job_id);
|
|
105
|
+
CREATE INDEX IF NOT EXISTS idx_evaluations_agent ON evaluations(agent_id);
|
|
75
106
|
`);
|
|
76
107
|
const cols = db2.prepare("PRAGMA table_info(agents)").all();
|
|
77
108
|
if (!cols.some((c) => c.name === "relationship")) {
|
|
@@ -125,6 +156,19 @@ function deleteAgent(agentId, db2) {
|
|
|
125
156
|
const result = conn.prepare("DELETE FROM agents WHERE agent_id = ?").run(agentId);
|
|
126
157
|
return result.changes > 0;
|
|
127
158
|
}
|
|
159
|
+
function getAgentWithTeams(agentId, db2) {
|
|
160
|
+
const conn = db2 ?? getDb();
|
|
161
|
+
const agent = getAgent(agentId, conn);
|
|
162
|
+
if (!agent) return void 0;
|
|
163
|
+
const teams = conn.prepare(
|
|
164
|
+
`SELECT t.team_id, t.name AS team_name, tm.relationship
|
|
165
|
+
FROM team_members tm
|
|
166
|
+
JOIN teams t ON tm.team_id = t.team_id
|
|
167
|
+
WHERE tm.agent_id = ?
|
|
168
|
+
ORDER BY t.created_at`
|
|
169
|
+
).all(agentId);
|
|
170
|
+
return { ...agent, teams };
|
|
171
|
+
}
|
|
128
172
|
function listAgentsWithMetrics(db2) {
|
|
129
173
|
const conn = db2 ?? getDb();
|
|
130
174
|
const rows = conn.prepare(
|
|
@@ -203,6 +247,14 @@ router.patch("/:agent_id/status", (req, res) => {
|
|
|
203
247
|
}
|
|
204
248
|
res.json({ agent_id, status });
|
|
205
249
|
});
|
|
250
|
+
router.get("/:agent_id", (req, res) => {
|
|
251
|
+
const agent = getAgentWithTeams(req.params.agent_id);
|
|
252
|
+
if (!agent) {
|
|
253
|
+
res.status(404).json({ error: `Agent not found: ${req.params.agent_id}` });
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
res.json(agent);
|
|
257
|
+
});
|
|
206
258
|
router.delete("/:agent_id", (req, res) => {
|
|
207
259
|
const { agent_id } = req.params;
|
|
208
260
|
const deleted = deleteAgent(agent_id);
|
|
@@ -378,6 +430,70 @@ function dispatchJob(request, db2) {
|
|
|
378
430
|
return { ...job, tasks };
|
|
379
431
|
}
|
|
380
432
|
|
|
433
|
+
// src/models/team.ts
|
|
434
|
+
function createTeam(team, db2) {
|
|
435
|
+
const conn = db2 ?? getDb();
|
|
436
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
437
|
+
conn.prepare(
|
|
438
|
+
`INSERT INTO teams (team_id, name, description, created_at)
|
|
439
|
+
VALUES (?, ?, ?, ?)`
|
|
440
|
+
).run(team.team_id, team.name, team.description || "", now);
|
|
441
|
+
return { ...team, description: team.description || "", created_at: now };
|
|
442
|
+
}
|
|
443
|
+
function getTeam(teamId, db2) {
|
|
444
|
+
const conn = db2 ?? getDb();
|
|
445
|
+
const row = conn.prepare("SELECT * FROM teams WHERE team_id = ?").get(teamId);
|
|
446
|
+
return row || void 0;
|
|
447
|
+
}
|
|
448
|
+
function listTeams(db2) {
|
|
449
|
+
const conn = db2 ?? getDb();
|
|
450
|
+
return conn.prepare("SELECT * FROM teams ORDER BY created_at").all();
|
|
451
|
+
}
|
|
452
|
+
function deleteTeam(teamId, db2) {
|
|
453
|
+
const conn = db2 ?? getDb();
|
|
454
|
+
const result = conn.prepare("DELETE FROM teams WHERE team_id = ?").run(teamId);
|
|
455
|
+
return result.changes > 0;
|
|
456
|
+
}
|
|
457
|
+
function addTeamMember(teamId, agentId, relationship, db2) {
|
|
458
|
+
const conn = db2 ?? getDb();
|
|
459
|
+
conn.prepare(
|
|
460
|
+
`INSERT OR REPLACE INTO team_members (team_id, agent_id, relationship)
|
|
461
|
+
VALUES (?, ?, ?)`
|
|
462
|
+
).run(teamId, agentId, relationship || "");
|
|
463
|
+
}
|
|
464
|
+
function removeTeamMember(teamId, agentId, db2) {
|
|
465
|
+
const conn = db2 ?? getDb();
|
|
466
|
+
const result = conn.prepare("DELETE FROM team_members WHERE team_id = ? AND agent_id = ?").run(teamId, agentId);
|
|
467
|
+
return result.changes > 0;
|
|
468
|
+
}
|
|
469
|
+
function updateTeamMemberRelationship(teamId, agentId, relationship, db2) {
|
|
470
|
+
const conn = db2 ?? getDb();
|
|
471
|
+
const result = conn.prepare(
|
|
472
|
+
"UPDATE team_members SET relationship = ? WHERE team_id = ? AND agent_id = ?"
|
|
473
|
+
).run(relationship, teamId, agentId);
|
|
474
|
+
return result.changes > 0;
|
|
475
|
+
}
|
|
476
|
+
function getTeamWithMembers(teamId, db2) {
|
|
477
|
+
const conn = db2 ?? getDb();
|
|
478
|
+
const team = getTeam(teamId, conn);
|
|
479
|
+
if (!team) return void 0;
|
|
480
|
+
const members = conn.prepare(
|
|
481
|
+
`SELECT tm.team_id, tm.agent_id, tm.relationship, a.name AS agent_name, a.status AS agent_status
|
|
482
|
+
FROM team_members tm
|
|
483
|
+
JOIN agents a ON tm.agent_id = a.agent_id
|
|
484
|
+
WHERE tm.team_id = ?
|
|
485
|
+
ORDER BY a.created_at`
|
|
486
|
+
).all(teamId);
|
|
487
|
+
return { ...team, members };
|
|
488
|
+
}
|
|
489
|
+
function getTeamMemberRelationship(teamId, agentId, db2) {
|
|
490
|
+
const conn = db2 ?? getDb();
|
|
491
|
+
const row = conn.prepare(
|
|
492
|
+
"SELECT relationship FROM team_members WHERE team_id = ? AND agent_id = ?"
|
|
493
|
+
).get(teamId, agentId);
|
|
494
|
+
return row?.relationship;
|
|
495
|
+
}
|
|
496
|
+
|
|
381
497
|
// src/llm/claude.ts
|
|
382
498
|
var DEFAULT_BASE_URL = "https://api.anthropic.com";
|
|
383
499
|
var ClaudeProvider = class {
|
|
@@ -466,6 +582,62 @@ var OpenAIProvider = class {
|
|
|
466
582
|
}
|
|
467
583
|
};
|
|
468
584
|
|
|
585
|
+
// src/llm/responses.ts
|
|
586
|
+
var DEFAULT_BASE_URL3 = "https://api.openai.com";
|
|
587
|
+
var ResponsesProvider = class {
|
|
588
|
+
apiKey;
|
|
589
|
+
model;
|
|
590
|
+
baseUrl;
|
|
591
|
+
constructor(apiKey, model, baseUrl) {
|
|
592
|
+
this.apiKey = apiKey;
|
|
593
|
+
this.model = model || "gpt-4o";
|
|
594
|
+
this.baseUrl = (baseUrl || DEFAULT_BASE_URL3).replace(/\/+$/, "");
|
|
595
|
+
}
|
|
596
|
+
async complete(request) {
|
|
597
|
+
const systemMessages = request.messages.filter((m) => m.role === "system");
|
|
598
|
+
const nonSystemMessages = request.messages.filter((m) => m.role !== "system");
|
|
599
|
+
const input = nonSystemMessages.map((m) => ({
|
|
600
|
+
role: m.role,
|
|
601
|
+
content: m.content
|
|
602
|
+
}));
|
|
603
|
+
const body = {
|
|
604
|
+
model: this.model,
|
|
605
|
+
input
|
|
606
|
+
};
|
|
607
|
+
if (systemMessages.length > 0) {
|
|
608
|
+
body.instructions = systemMessages.map((m) => m.content).join("\n\n");
|
|
609
|
+
}
|
|
610
|
+
if (request.temperature !== void 0) {
|
|
611
|
+
body.temperature = request.temperature;
|
|
612
|
+
}
|
|
613
|
+
if (request.max_tokens) {
|
|
614
|
+
body.max_output_tokens = request.max_tokens;
|
|
615
|
+
}
|
|
616
|
+
const response = await fetch(`${this.baseUrl}/v1/responses`, {
|
|
617
|
+
method: "POST",
|
|
618
|
+
headers: {
|
|
619
|
+
"Content-Type": "application/json",
|
|
620
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
621
|
+
},
|
|
622
|
+
body: JSON.stringify(body)
|
|
623
|
+
});
|
|
624
|
+
if (!response.ok) {
|
|
625
|
+
const errorText = await response.text();
|
|
626
|
+
throw new Error(`Responses API error (${response.status}): ${errorText}`);
|
|
627
|
+
}
|
|
628
|
+
const data = await response.json();
|
|
629
|
+
for (const item of data.output) {
|
|
630
|
+
if (item.type === "message" && item.content) {
|
|
631
|
+
const textBlock = item.content.find((c) => c.type === "output_text");
|
|
632
|
+
if (textBlock) {
|
|
633
|
+
return { content: textBlock.text };
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
throw new Error("Responses API returned no text content");
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
469
641
|
// src/models/config.ts
|
|
470
642
|
function getConfig(key, db2) {
|
|
471
643
|
const conn = db2 ?? getDb();
|
|
@@ -482,12 +654,17 @@ function deleteConfig(key, db2) {
|
|
|
482
654
|
}
|
|
483
655
|
|
|
484
656
|
// src/llm/index.ts
|
|
657
|
+
function normalizeProvider(raw) {
|
|
658
|
+
if (raw === "claude") return "anthropic";
|
|
659
|
+
return raw;
|
|
660
|
+
}
|
|
485
661
|
function getLlmConfig() {
|
|
486
662
|
const dbProvider = getConfig("llm_provider");
|
|
487
663
|
const dbApiKey = getConfig("llm_api_key");
|
|
488
664
|
const dbModel = getConfig("llm_model");
|
|
489
665
|
const dbBaseUrl = getConfig("llm_base_url");
|
|
490
|
-
const
|
|
666
|
+
const rawProvider = dbProvider || process.env.HUMANCLAW_LLM_PROVIDER || "anthropic";
|
|
667
|
+
const provider = normalizeProvider(rawProvider);
|
|
491
668
|
const apiKey = dbApiKey || process.env.HUMANCLAW_LLM_API_KEY || "";
|
|
492
669
|
const model = dbModel || process.env.HUMANCLAW_LLM_MODEL;
|
|
493
670
|
const baseUrl = dbBaseUrl || process.env.HUMANCLAW_LLM_BASE_URL;
|
|
@@ -496,18 +673,21 @@ function getLlmConfig() {
|
|
|
496
673
|
"\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"
|
|
497
674
|
);
|
|
498
675
|
}
|
|
499
|
-
|
|
500
|
-
|
|
676
|
+
const validFormats = ["openai", "anthropic", "responses"];
|
|
677
|
+
if (!validFormats.includes(provider)) {
|
|
678
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 API \u683C\u5F0F: ${provider}\u3002\u652F\u6301: openai, anthropic, responses`);
|
|
501
679
|
}
|
|
502
680
|
return { provider, apiKey, model: model || void 0, baseUrl: baseUrl || void 0 };
|
|
503
681
|
}
|
|
504
682
|
function createLlmProvider(config) {
|
|
505
683
|
const cfg = config || getLlmConfig();
|
|
506
684
|
switch (cfg.provider) {
|
|
507
|
-
case "
|
|
685
|
+
case "anthropic":
|
|
508
686
|
return new ClaudeProvider(cfg.apiKey, cfg.model, cfg.baseUrl);
|
|
509
687
|
case "openai":
|
|
510
688
|
return new OpenAIProvider(cfg.apiKey, cfg.model, cfg.baseUrl);
|
|
689
|
+
case "responses":
|
|
690
|
+
return new ResponsesProvider(cfg.apiKey, cfg.model, cfg.baseUrl);
|
|
511
691
|
}
|
|
512
692
|
}
|
|
513
693
|
|
|
@@ -536,16 +716,23 @@ function buildSystemPrompt() {
|
|
|
536
716
|
]
|
|
537
717
|
\`\`\``;
|
|
538
718
|
}
|
|
539
|
-
function buildUserPrompt(prompt, agents) {
|
|
719
|
+
function buildUserPrompt(prompt, agents, teamContext) {
|
|
540
720
|
const now = /* @__PURE__ */ new Date();
|
|
541
721
|
const agentList = agents.map((a) => {
|
|
542
722
|
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";
|
|
543
723
|
const speed = a.avg_delivery_hours !== null ? `\u5E73\u5747\u4EA4\u4ED8\u65F6\u95F4 ${a.avg_delivery_hours}h` : "\u6682\u65E0\u5386\u53F2\u6570\u636E";
|
|
544
|
-
const
|
|
724
|
+
const relText = teamContext?.relationships.get(a.agent_id) || a.relationship;
|
|
725
|
+
const rel = relText ? ` \u5173\u7CFB: ${relText}` : "";
|
|
545
726
|
return `- ${a.name} (ID: ${a.agent_id}) \u6280\u80FD: [${a.capabilities.join(", ")}]${rel} ${load} ${speed}`;
|
|
546
727
|
}).join("\n");
|
|
728
|
+
let teamInfo = "";
|
|
729
|
+
if (teamContext) {
|
|
730
|
+
teamInfo = `
|
|
731
|
+
\u56E2\u961F: ${teamContext.name}${teamContext.description ? ` \u2014 ${teamContext.description}` : ""}
|
|
732
|
+
`;
|
|
733
|
+
}
|
|
547
734
|
return `\u5F53\u524D\u65F6\u95F4: ${now.toISOString()}
|
|
548
|
-
|
|
735
|
+
${teamInfo}
|
|
549
736
|
\u53EF\u7528\u7684\u78B3\u57FA\u8282\u70B9\uFF1A
|
|
550
737
|
${agentList}
|
|
551
738
|
|
|
@@ -605,7 +792,25 @@ async function planJob(request, provider, db2) {
|
|
|
605
792
|
const conn = db2 ?? getDb();
|
|
606
793
|
const allAgents = listAgentsWithMetrics(conn);
|
|
607
794
|
let agents;
|
|
608
|
-
|
|
795
|
+
let teamContext;
|
|
796
|
+
if (request.team_id) {
|
|
797
|
+
const team = getTeamWithMembers(request.team_id, conn);
|
|
798
|
+
if (!team) {
|
|
799
|
+
throw new Error(`\u56E2\u961F\u4E0D\u5B58\u5728: ${request.team_id}`);
|
|
800
|
+
}
|
|
801
|
+
if (team.members.length === 0) {
|
|
802
|
+
throw new Error(`\u56E2\u961F "${team.name}" \u4E2D\u6CA1\u6709\u6210\u5458`);
|
|
803
|
+
}
|
|
804
|
+
const memberIds = new Set(team.members.map((m) => m.agent_id));
|
|
805
|
+
agents = allAgents.filter((a) => memberIds.has(a.agent_id));
|
|
806
|
+
const relationships = /* @__PURE__ */ new Map();
|
|
807
|
+
for (const m of team.members) {
|
|
808
|
+
if (m.relationship) {
|
|
809
|
+
relationships.set(m.agent_id, m.relationship);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
teamContext = { name: team.name, description: team.description, relationships };
|
|
813
|
+
} else if (request.agent_ids && request.agent_ids.length > 0) {
|
|
609
814
|
agents = allAgents.filter((a) => request.agent_ids.includes(a.agent_id));
|
|
610
815
|
if (agents.length === 0) {
|
|
611
816
|
throw new Error("\u6307\u5B9A\u7684 Agent \u5747\u4E0D\u5B58\u5728");
|
|
@@ -621,7 +826,7 @@ async function planJob(request, provider, db2) {
|
|
|
621
826
|
}
|
|
622
827
|
const llm = provider ?? createLlmProvider();
|
|
623
828
|
const systemPrompt = buildSystemPrompt();
|
|
624
|
-
const userPrompt = buildUserPrompt(request.prompt, agents);
|
|
829
|
+
const userPrompt = buildUserPrompt(request.prompt, agents, teamContext);
|
|
625
830
|
const response = await llm.complete({
|
|
626
831
|
messages: [
|
|
627
832
|
{ role: "system", content: systemPrompt },
|
|
@@ -762,7 +967,7 @@ ${taskDescription}
|
|
|
762
967
|
4. \u5B57\u6570 200-400 \u5B57
|
|
763
968
|
5. \u76F4\u63A5\u8F93\u51FA\u6C47\u62A5\u5185\u5BB9\uFF0C\u4E0D\u8981\u52A0\u4EFB\u4F55\u683C\u5F0F\u524D\u7F00\u6216\u8BF4\u660E`;
|
|
764
969
|
}
|
|
765
|
-
async function simulateDelivery(traceId, provider, db2) {
|
|
970
|
+
async function simulateDelivery(traceId, provider, db2, teamId) {
|
|
766
971
|
const conn = db2 ?? getDb();
|
|
767
972
|
const task = getTask(traceId, conn);
|
|
768
973
|
if (!task) {
|
|
@@ -772,6 +977,11 @@ async function simulateDelivery(traceId, provider, db2) {
|
|
|
772
977
|
if (!agent) {
|
|
773
978
|
throw new Error(`Agent not found: ${task.assignee_id}`);
|
|
774
979
|
}
|
|
980
|
+
let relationship = agent.relationship;
|
|
981
|
+
if (teamId) {
|
|
982
|
+
const teamRel = getTeamMemberRelationship(teamId, agent.agent_id, conn);
|
|
983
|
+
if (teamRel) relationship = teamRel;
|
|
984
|
+
}
|
|
775
985
|
const llm = provider ?? createLlmProvider();
|
|
776
986
|
const response = await llm.complete({
|
|
777
987
|
messages: [
|
|
@@ -783,7 +993,7 @@ async function simulateDelivery(traceId, provider, db2) {
|
|
|
783
993
|
role: "user",
|
|
784
994
|
content: buildSimulatePrompt(
|
|
785
995
|
agent.name,
|
|
786
|
-
|
|
996
|
+
relationship,
|
|
787
997
|
agent.capabilities,
|
|
788
998
|
task.todo_description,
|
|
789
999
|
task.deadline
|
|
@@ -838,13 +1048,13 @@ router3.post("/reject", (req, res) => {
|
|
|
838
1048
|
}
|
|
839
1049
|
});
|
|
840
1050
|
router3.post("/simulate", async (req, res) => {
|
|
841
|
-
const { trace_id } = req.body;
|
|
1051
|
+
const { trace_id, team_id } = req.body;
|
|
842
1052
|
if (!trace_id) {
|
|
843
1053
|
res.status(400).json({ error: "trace_id is required" });
|
|
844
1054
|
return;
|
|
845
1055
|
}
|
|
846
1056
|
try {
|
|
847
|
-
const result = await simulateDelivery(trace_id);
|
|
1057
|
+
const result = await simulateDelivery(trace_id, void 0, void 0, team_id);
|
|
848
1058
|
res.json(result);
|
|
849
1059
|
} catch (error) {
|
|
850
1060
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -857,9 +1067,72 @@ var tasks_default = router3;
|
|
|
857
1067
|
// src/routes/sync.ts
|
|
858
1068
|
import { Router as Router4 } from "express";
|
|
859
1069
|
|
|
1070
|
+
// src/models/evaluation.ts
|
|
1071
|
+
var RATING_SYSTEMS = {
|
|
1072
|
+
ali: ["3.25", "3.5", "3.5+", "3.75", "4.0"],
|
|
1073
|
+
letter: ["S", "A", "B", "C", "D"],
|
|
1074
|
+
em: ["EM+", "EM", "MM+", "MM", "MM-"]
|
|
1075
|
+
};
|
|
1076
|
+
function validateRating(system, rating) {
|
|
1077
|
+
const values = RATING_SYSTEMS[system];
|
|
1078
|
+
return values ? values.includes(rating) : false;
|
|
1079
|
+
}
|
|
1080
|
+
function createEvaluation(evaluation, db2) {
|
|
1081
|
+
const conn = db2 ?? getDb();
|
|
1082
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1083
|
+
conn.prepare(
|
|
1084
|
+
`INSERT INTO evaluations (eval_id, job_id, agent_id, trace_id, rating_system, rating, weight, comment, created_at)
|
|
1085
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1086
|
+
).run(
|
|
1087
|
+
evaluation.eval_id,
|
|
1088
|
+
evaluation.job_id,
|
|
1089
|
+
evaluation.agent_id,
|
|
1090
|
+
evaluation.trace_id,
|
|
1091
|
+
evaluation.rating_system,
|
|
1092
|
+
evaluation.rating,
|
|
1093
|
+
evaluation.weight,
|
|
1094
|
+
evaluation.comment || "",
|
|
1095
|
+
now
|
|
1096
|
+
);
|
|
1097
|
+
return { ...evaluation, comment: evaluation.comment || "", created_at: now };
|
|
1098
|
+
}
|
|
1099
|
+
function rowToEvaluation(row) {
|
|
1100
|
+
return {
|
|
1101
|
+
...row,
|
|
1102
|
+
rating_system: row.rating_system
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function listEvaluationsByJob(jobId, db2) {
|
|
1106
|
+
const conn = db2 ?? getDb();
|
|
1107
|
+
const rows = conn.prepare(
|
|
1108
|
+
`SELECT e.*
|
|
1109
|
+
FROM evaluations e
|
|
1110
|
+
WHERE e.job_id = ?
|
|
1111
|
+
ORDER BY e.created_at`
|
|
1112
|
+
).all(jobId);
|
|
1113
|
+
return rows.map(rowToEvaluation);
|
|
1114
|
+
}
|
|
1115
|
+
function listEvaluationsByAgent(agentId, db2) {
|
|
1116
|
+
const conn = db2 ?? getDb();
|
|
1117
|
+
const rows = conn.prepare(
|
|
1118
|
+
`SELECT e.*, j.original_prompt, t.todo_description
|
|
1119
|
+
FROM evaluations e
|
|
1120
|
+
LEFT JOIN jobs j ON e.job_id = j.job_id
|
|
1121
|
+
LEFT JOIN tasks t ON e.trace_id = t.trace_id
|
|
1122
|
+
WHERE e.agent_id = ?
|
|
1123
|
+
ORDER BY e.created_at DESC`
|
|
1124
|
+
).all(agentId);
|
|
1125
|
+
return rows.map((r) => ({ ...rowToEvaluation(r), original_prompt: r.original_prompt, todo_description: r.todo_description }));
|
|
1126
|
+
}
|
|
1127
|
+
function deleteEvaluationsByJob(jobId, db2) {
|
|
1128
|
+
const conn = db2 ?? getDb();
|
|
1129
|
+
const result = conn.prepare("DELETE FROM evaluations WHERE job_id = ?").run(jobId);
|
|
1130
|
+
return result.changes;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
860
1133
|
// src/services/reviewer.ts
|
|
861
|
-
function buildReviewSystemPrompt() {
|
|
862
|
-
|
|
1134
|
+
function buildReviewSystemPrompt(ratingSystem) {
|
|
1135
|
+
let base = `\u4F60\u662F HumanClaw \u4EA4\u4ED8\u5BA1\u67E5\u5458\u3002\u4F60\u7684\u5DE5\u4F5C\u662F\u5BA1\u67E5\u78B3\u57FA\u8282\u70B9\u63D0\u4EA4\u7684\u6240\u6709\u4EFB\u52A1\u4EA4\u4ED8\u7269\uFF0C\u751F\u6210\u6574\u4F53\u5BA1\u67E5\u62A5\u544A\u3002
|
|
863
1136
|
|
|
864
1137
|
\u5BA1\u67E5\u8981\u70B9\uFF1A
|
|
865
1138
|
1. \u68C0\u67E5\u6BCF\u4E2A\u4EA4\u4ED8\u7269\u662F\u5426\u6EE1\u8DB3\u539F\u59CB\u4EFB\u52A1\u8981\u6C42
|
|
@@ -870,6 +1143,30 @@ function buildReviewSystemPrompt() {
|
|
|
870
1143
|
\u8F93\u51FA\u683C\u5F0F\uFF1A
|
|
871
1144
|
- \u4F7F\u7528 Markdown \u683C\u5F0F
|
|
872
1145
|
- \u5148\u7ED9\u603B\u7ED3\u8BC4\u5206\uFF0C\u518D\u9010\u4E2A\u5206\u6790\u6BCF\u4E2A\u5B50\u4EFB\u52A1\u7684\u4EA4\u4ED8\u8D28\u91CF`;
|
|
1146
|
+
if (ratingSystem) {
|
|
1147
|
+
const ratings = RATING_SYSTEMS[ratingSystem];
|
|
1148
|
+
const systemLabels = {
|
|
1149
|
+
ali: "\u963F\u91CC\u7EE9\u6548\u4F53\u7CFB",
|
|
1150
|
+
letter: "SABCD \u7B49\u7EA7",
|
|
1151
|
+
em: "EM/MM \u7EE9\u6548\u4F53\u7CFB"
|
|
1152
|
+
};
|
|
1153
|
+
base += `
|
|
1154
|
+
|
|
1155
|
+
\u540C\u65F6\uFF0C\u8BF7\u4E3A\u6BCF\u4E2A\u53C2\u4E0E\u7684\u78B3\u57FA\u8282\u70B9\u751F\u6210\u4E2A\u4EBA\u7EE9\u6548\u8BC4\u4EF7\u3002
|
|
1156
|
+
\u4F7F\u7528\u300C${systemLabels[ratingSystem]}\u300D\uFF0C\u53EF\u9009\u8BC4\u5206\u4E3A: ${ratings.join(" / ")}
|
|
1157
|
+
\u5728\u5BA1\u67E5\u62A5\u544A\u672B\u5C3E\uFF0C\u989D\u5916\u8F93\u51FA\u4E00\u4E2A JSON \u5757\uFF08\u7528 \`\`\`json \u5305\u88F9\uFF09\uFF0C\u683C\u5F0F\u5982\u4E0B\uFF1A
|
|
1158
|
+
\`\`\`json
|
|
1159
|
+
[
|
|
1160
|
+
{
|
|
1161
|
+
"agent_id": "\u6267\u884C\u8005ID",
|
|
1162
|
+
"trace_id": "\u4EFB\u52A1\u8FFD\u8E2A\u7801",
|
|
1163
|
+
"rating": "\u8BC4\u5206",
|
|
1164
|
+
"comment": "\u4E00\u53E5\u8BDD\u8BC4\u8BED"
|
|
1165
|
+
}
|
|
1166
|
+
]
|
|
1167
|
+
\`\`\``;
|
|
1168
|
+
}
|
|
1169
|
+
return base;
|
|
873
1170
|
}
|
|
874
1171
|
function buildReviewUserPrompt(originalPrompt, tasks) {
|
|
875
1172
|
let prompt = `\u539F\u59CB\u9700\u6C42: ${originalPrompt}
|
|
@@ -890,10 +1187,16 @@ function buildReviewUserPrompt(originalPrompt, tasks) {
|
|
|
890
1187
|
}
|
|
891
1188
|
prompt += `--- \u5B50\u4EFB\u52A1 ${i + 1} ---
|
|
892
1189
|
`;
|
|
893
|
-
prompt += `\u6267\u884C\u8005: ${t.assignee_id}
|
|
1190
|
+
prompt += `\u6267\u884C\u8005: ${t.assignee_name} (${t.assignee_id})
|
|
1191
|
+
`;
|
|
1192
|
+
prompt += `\u8FFD\u8E2A\u7801: ${t.trace_id}
|
|
894
1193
|
`;
|
|
895
1194
|
prompt += `\u4EFB\u52A1: ${t.todo_description}
|
|
896
1195
|
`;
|
|
1196
|
+
if (t.weight !== void 0 && t.weight !== 1) {
|
|
1197
|
+
prompt += `\u4EFB\u52A1\u6743\u91CD: ${t.weight}
|
|
1198
|
+
`;
|
|
1199
|
+
}
|
|
897
1200
|
prompt += `\u4EA4\u4ED8\u7269:
|
|
898
1201
|
${resultText}
|
|
899
1202
|
|
|
@@ -902,7 +1205,22 @@ ${resultText}
|
|
|
902
1205
|
prompt += "\u8BF7\u5BA1\u67E5\u4EE5\u4E0A\u6240\u6709\u4EA4\u4ED8\u7269\uFF0C\u751F\u6210\u5BA1\u67E5\u62A5\u544A\u3002";
|
|
903
1206
|
return prompt;
|
|
904
1207
|
}
|
|
905
|
-
|
|
1208
|
+
function extractEvaluationJson(raw) {
|
|
1209
|
+
const blocks = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/g);
|
|
1210
|
+
if (!blocks) return void 0;
|
|
1211
|
+
const lastBlock = blocks[blocks.length - 1];
|
|
1212
|
+
const jsonMatch = lastBlock.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
1213
|
+
if (!jsonMatch) return void 0;
|
|
1214
|
+
try {
|
|
1215
|
+
const parsed = JSON.parse(jsonMatch[1].trim());
|
|
1216
|
+
if (Array.isArray(parsed)) {
|
|
1217
|
+
return parsed;
|
|
1218
|
+
}
|
|
1219
|
+
} catch {
|
|
1220
|
+
}
|
|
1221
|
+
return void 0;
|
|
1222
|
+
}
|
|
1223
|
+
async function reviewJob(jobId, provider, db2, ratingSystem, taskWeights) {
|
|
906
1224
|
const conn = db2 ?? getDb();
|
|
907
1225
|
const job = getJobWithTasks(jobId, conn);
|
|
908
1226
|
if (!job) {
|
|
@@ -915,38 +1233,58 @@ async function reviewJob(jobId, provider, db2) {
|
|
|
915
1233
|
);
|
|
916
1234
|
}
|
|
917
1235
|
const llm = provider ?? createLlmProvider();
|
|
1236
|
+
const tasksWithNames = job.tasks.map((t) => {
|
|
1237
|
+
const agent = getAgent(t.assignee_id, conn);
|
|
1238
|
+
return {
|
|
1239
|
+
trace_id: t.trace_id,
|
|
1240
|
+
assignee_id: t.assignee_id,
|
|
1241
|
+
assignee_name: agent?.name || t.assignee_id,
|
|
1242
|
+
todo_description: t.todo_description,
|
|
1243
|
+
result_data: t.result_data,
|
|
1244
|
+
weight: taskWeights?.[t.trace_id]
|
|
1245
|
+
};
|
|
1246
|
+
});
|
|
918
1247
|
const response = await llm.complete({
|
|
919
1248
|
messages: [
|
|
920
|
-
{ role: "system", content: buildReviewSystemPrompt() },
|
|
1249
|
+
{ role: "system", content: buildReviewSystemPrompt(ratingSystem) },
|
|
921
1250
|
{
|
|
922
1251
|
role: "user",
|
|
923
|
-
content: buildReviewUserPrompt(
|
|
924
|
-
job.original_prompt,
|
|
925
|
-
job.tasks.map((t) => ({
|
|
926
|
-
assignee_id: t.assignee_id,
|
|
927
|
-
todo_description: t.todo_description,
|
|
928
|
-
result_data: t.result_data
|
|
929
|
-
}))
|
|
930
|
-
)
|
|
1252
|
+
content: buildReviewUserPrompt(job.original_prompt, tasksWithNames)
|
|
931
1253
|
}
|
|
932
1254
|
],
|
|
933
1255
|
temperature: 0.3,
|
|
934
1256
|
max_tokens: 4096
|
|
935
1257
|
});
|
|
936
|
-
|
|
1258
|
+
const result = {
|
|
937
1259
|
job_id: jobId,
|
|
938
1260
|
original_prompt: job.original_prompt,
|
|
939
1261
|
review: response.content,
|
|
940
1262
|
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
941
1263
|
};
|
|
1264
|
+
if (ratingSystem) {
|
|
1265
|
+
const evalData = extractEvaluationJson(response.content);
|
|
1266
|
+
if (evalData) {
|
|
1267
|
+
result.evaluations = evalData.map((e) => {
|
|
1268
|
+
const agent = getAgent(e.agent_id, conn);
|
|
1269
|
+
return {
|
|
1270
|
+
...e,
|
|
1271
|
+
agent_name: agent?.name || e.agent_id
|
|
1272
|
+
};
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return result;
|
|
942
1277
|
}
|
|
943
1278
|
|
|
944
1279
|
// src/routes/sync.ts
|
|
945
1280
|
var router4 = Router4();
|
|
946
1281
|
router4.post("/:job_id/review", async (req, res) => {
|
|
947
1282
|
const { job_id } = req.params;
|
|
1283
|
+
const { rating_system, task_weights } = req.body;
|
|
1284
|
+
const validSystems = ["ali", "letter", "em"];
|
|
1285
|
+
const ratingSystem = rating_system && validSystems.includes(rating_system) ? rating_system : void 0;
|
|
948
1286
|
try {
|
|
949
|
-
const result = await reviewJob(job_id);
|
|
1287
|
+
const result = await reviewJob(job_id, void 0, void 0, ratingSystem, task_weights);
|
|
950
1288
|
res.json(result);
|
|
951
1289
|
} catch (error) {
|
|
952
1290
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -960,7 +1298,8 @@ var sync_default = router4;
|
|
|
960
1298
|
import { Router as Router5 } from "express";
|
|
961
1299
|
var router5 = Router5();
|
|
962
1300
|
router5.get("/", (_req, res) => {
|
|
963
|
-
const
|
|
1301
|
+
const rawProvider = getConfig("llm_provider") || process.env.HUMANCLAW_LLM_PROVIDER || "anthropic";
|
|
1302
|
+
const provider = rawProvider === "claude" ? "anthropic" : rawProvider;
|
|
964
1303
|
const apiKey = getConfig("llm_api_key") || process.env.HUMANCLAW_LLM_API_KEY || "";
|
|
965
1304
|
const model = getConfig("llm_model") || process.env.HUMANCLAW_LLM_MODEL || "";
|
|
966
1305
|
const baseUrl = getConfig("llm_base_url") || process.env.HUMANCLAW_LLM_BASE_URL || "";
|
|
@@ -977,11 +1316,13 @@ router5.get("/", (_req, res) => {
|
|
|
977
1316
|
router5.put("/", (req, res) => {
|
|
978
1317
|
const { provider, api_key, model, base_url } = req.body;
|
|
979
1318
|
if (provider !== void 0) {
|
|
980
|
-
|
|
981
|
-
|
|
1319
|
+
const normalized = provider === "claude" ? "anthropic" : provider;
|
|
1320
|
+
const validFormats = ["openai", "anthropic", "responses"];
|
|
1321
|
+
if (!validFormats.includes(normalized)) {
|
|
1322
|
+
res.status(400).json({ error: `\u4E0D\u652F\u6301\u7684 API \u683C\u5F0F\u3002\u652F\u6301: ${validFormats.join(", ")}` });
|
|
982
1323
|
return;
|
|
983
1324
|
}
|
|
984
|
-
setConfig("llm_provider",
|
|
1325
|
+
setConfig("llm_provider", normalized);
|
|
985
1326
|
}
|
|
986
1327
|
if (api_key !== void 0) {
|
|
987
1328
|
if (api_key === "") {
|
|
@@ -1008,6 +1349,295 @@ router5.put("/", (req, res) => {
|
|
|
1008
1349
|
});
|
|
1009
1350
|
var config_default = router5;
|
|
1010
1351
|
|
|
1352
|
+
// src/routes/teams.ts
|
|
1353
|
+
import { Router as Router6 } from "express";
|
|
1354
|
+
var router6 = Router6();
|
|
1355
|
+
router6.get("/", (_req, res) => {
|
|
1356
|
+
const teams = listTeams();
|
|
1357
|
+
const teamsWithMembers = teams.map((t) => {
|
|
1358
|
+
const full = getTeamWithMembers(t.team_id);
|
|
1359
|
+
return full || { ...t, members: [] };
|
|
1360
|
+
});
|
|
1361
|
+
res.json({ teams: teamsWithMembers });
|
|
1362
|
+
});
|
|
1363
|
+
router6.get("/:id", (req, res) => {
|
|
1364
|
+
const team = getTeamWithMembers(req.params.id);
|
|
1365
|
+
if (!team) {
|
|
1366
|
+
res.status(404).json({ error: `\u56E2\u961F\u4E0D\u5B58\u5728: ${req.params.id}` });
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
res.json(team);
|
|
1370
|
+
});
|
|
1371
|
+
router6.post("/", (req, res) => {
|
|
1372
|
+
const { name, description } = req.body;
|
|
1373
|
+
if (!name || typeof name !== "string") {
|
|
1374
|
+
res.status(400).json({ error: "name is required" });
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
const team = createTeam({
|
|
1378
|
+
team_id: generateId("team"),
|
|
1379
|
+
name,
|
|
1380
|
+
description: description || ""
|
|
1381
|
+
});
|
|
1382
|
+
res.status(201).json(team);
|
|
1383
|
+
});
|
|
1384
|
+
router6.delete("/:id", (req, res) => {
|
|
1385
|
+
const deleted = deleteTeam(req.params.id);
|
|
1386
|
+
if (!deleted) {
|
|
1387
|
+
res.status(404).json({ error: `\u56E2\u961F\u4E0D\u5B58\u5728: ${req.params.id}` });
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
res.json({ deleted: req.params.id });
|
|
1391
|
+
});
|
|
1392
|
+
router6.post("/:id/members", (req, res) => {
|
|
1393
|
+
const { agent_id, relationship } = req.body;
|
|
1394
|
+
if (!agent_id || typeof agent_id !== "string") {
|
|
1395
|
+
res.status(400).json({ error: "agent_id is required" });
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const agent = getAgent(agent_id);
|
|
1399
|
+
if (!agent) {
|
|
1400
|
+
res.status(404).json({ error: `Agent \u4E0D\u5B58\u5728: ${agent_id}` });
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
try {
|
|
1404
|
+
addTeamMember(req.params.id, agent_id, relationship || "");
|
|
1405
|
+
res.status(201).json({ team_id: req.params.id, agent_id, relationship: relationship || "" });
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1408
|
+
res.status(400).json({ error: message });
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
router6.delete("/:id/members/:agent_id", (req, res) => {
|
|
1412
|
+
const removed = removeTeamMember(req.params.id, req.params.agent_id);
|
|
1413
|
+
if (!removed) {
|
|
1414
|
+
res.status(404).json({ error: "\u6210\u5458\u4E0D\u5728\u8BE5\u56E2\u961F\u4E2D" });
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
res.json({ removed: req.params.agent_id, team_id: req.params.id });
|
|
1418
|
+
});
|
|
1419
|
+
router6.put("/:id/members/:agent_id", (req, res) => {
|
|
1420
|
+
const { relationship } = req.body;
|
|
1421
|
+
if (relationship === void 0 || typeof relationship !== "string") {
|
|
1422
|
+
res.status(400).json({ error: "relationship is required" });
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const updated = updateTeamMemberRelationship(req.params.id, req.params.agent_id, relationship);
|
|
1426
|
+
if (!updated) {
|
|
1427
|
+
res.status(404).json({ error: "\u6210\u5458\u4E0D\u5728\u8BE5\u56E2\u961F\u4E2D" });
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
res.json({ team_id: req.params.id, agent_id: req.params.agent_id, relationship });
|
|
1431
|
+
});
|
|
1432
|
+
var teams_default = router6;
|
|
1433
|
+
|
|
1434
|
+
// src/routes/evaluations.ts
|
|
1435
|
+
import { Router as Router7 } from "express";
|
|
1436
|
+
|
|
1437
|
+
// src/services/evaluator.ts
|
|
1438
|
+
function generateEvalId() {
|
|
1439
|
+
return "eval_" + Math.random().toString(36).substring(2, 10);
|
|
1440
|
+
}
|
|
1441
|
+
function buildEvalPrompt(originalPrompt, ratingSystem, tasks) {
|
|
1442
|
+
const ratings = RATING_SYSTEMS[ratingSystem];
|
|
1443
|
+
const systemLabels = {
|
|
1444
|
+
ali: "\u963F\u91CC\u7EE9\u6548\u4F53\u7CFB\uFF083.25=\u4E0D\u5408\u683C, 3.5=\u57FA\u672C\u8FBE\u6807, 3.5+=\u8FBE\u6807, 3.75=\u4F18\u79C0, 4.0=\u5353\u8D8A\uFF09",
|
|
1445
|
+
letter: "SABCD \u7B49\u7EA7\uFF08S=\u5353\u8D8A, A=\u4F18\u79C0, B=\u8FBE\u6807, C=\u5F85\u6539\u8FDB, D=\u4E0D\u5408\u683C\uFF09",
|
|
1446
|
+
em: "EM/MM \u4F53\u7CFB\uFF08EM+=\u8FDC\u8D85\u9884\u671F, EM=\u8D85\u51FA\u9884\u671F, MM+=\u8FBE\u6807, MM=\u57FA\u672C\u8FBE\u6807, MM-=\u4E0D\u8FBE\u6807\uFF09"
|
|
1447
|
+
};
|
|
1448
|
+
let prompt = `\u539F\u59CB\u9700\u6C42: ${originalPrompt}
|
|
1449
|
+
|
|
1450
|
+
`;
|
|
1451
|
+
prompt += `\u8BC4\u5206\u4F53\u7CFB: ${systemLabels[ratingSystem]}
|
|
1452
|
+
`;
|
|
1453
|
+
prompt += `\u53EF\u9009\u8BC4\u5206: ${ratings.join(" / ")}
|
|
1454
|
+
|
|
1455
|
+
`;
|
|
1456
|
+
for (const t of tasks) {
|
|
1457
|
+
let resultText = "";
|
|
1458
|
+
if (t.result_data) {
|
|
1459
|
+
if (typeof t.result_data === "string") {
|
|
1460
|
+
resultText = t.result_data;
|
|
1461
|
+
} else if (typeof t.result_data === "object") {
|
|
1462
|
+
const rd = t.result_data;
|
|
1463
|
+
resultText = rd.text || JSON.stringify(rd, null, 2);
|
|
1464
|
+
} else {
|
|
1465
|
+
resultText = String(t.result_data);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
prompt += `--- \u4EFB\u52A1 ${t.trace_id} (\u6743\u91CD: ${t.weight}) ---
|
|
1469
|
+
`;
|
|
1470
|
+
prompt += `\u6267\u884C\u8005: ${t.assignee_name} (${t.assignee_id})
|
|
1471
|
+
`;
|
|
1472
|
+
prompt += `\u4EFB\u52A1: ${t.todo_description}
|
|
1473
|
+
`;
|
|
1474
|
+
prompt += `\u4EA4\u4ED8\u7269:
|
|
1475
|
+
${resultText}
|
|
1476
|
+
|
|
1477
|
+
`;
|
|
1478
|
+
}
|
|
1479
|
+
prompt += `\u8BF7\u4E3A\u6BCF\u4E2A\u6267\u884C\u8005\u7684\u6BCF\u4E2A\u4EFB\u52A1\u5355\u72EC\u8BC4\u5206\u3002\u4E25\u683C\u8F93\u51FA JSON \u6570\u7EC4\uFF08\u4E0D\u8981\u8F93\u51FA\u5176\u4ED6\u5185\u5BB9\uFF09\uFF1A
|
|
1480
|
+
\`\`\`json
|
|
1481
|
+
[
|
|
1482
|
+
{
|
|
1483
|
+
"agent_id": "\u6267\u884C\u8005ID",
|
|
1484
|
+
"trace_id": "\u4EFB\u52A1\u8FFD\u8E2A\u7801",
|
|
1485
|
+
"rating": "\u4ECE ${ratings.join("/")} \u4E2D\u9009\u4E00\u4E2A",
|
|
1486
|
+
"comment": "\u4E00\u53E5\u8BDD\u8BC4\u8BED\uFF0C\u8BF4\u660E\u7ED9\u6B64\u8BC4\u5206\u7684\u539F\u56E0"
|
|
1487
|
+
}
|
|
1488
|
+
]
|
|
1489
|
+
\`\`\``;
|
|
1490
|
+
return prompt;
|
|
1491
|
+
}
|
|
1492
|
+
function extractJson2(raw) {
|
|
1493
|
+
const codeBlockMatch = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
1494
|
+
if (codeBlockMatch) {
|
|
1495
|
+
return codeBlockMatch[1].trim();
|
|
1496
|
+
}
|
|
1497
|
+
const arrayMatch = raw.match(/\[[\s\S]*\]/);
|
|
1498
|
+
if (arrayMatch) {
|
|
1499
|
+
return arrayMatch[0];
|
|
1500
|
+
}
|
|
1501
|
+
return raw.trim();
|
|
1502
|
+
}
|
|
1503
|
+
async function generateEvaluations(request, provider, db2) {
|
|
1504
|
+
const conn = db2 ?? getDb();
|
|
1505
|
+
const job = getJobWithTasks(request.job_id, conn);
|
|
1506
|
+
if (!job) {
|
|
1507
|
+
throw new Error(`Job not found: ${request.job_id}`);
|
|
1508
|
+
}
|
|
1509
|
+
if (!isJobComplete(request.job_id, conn)) {
|
|
1510
|
+
const resolved = job.tasks.filter((t) => t.status === "RESOLVED").length;
|
|
1511
|
+
throw new Error(
|
|
1512
|
+
`Job \u5C1A\u672A\u5168\u90E8\u5B8C\u6210: ${resolved}/${job.tasks.length} \u4E2A\u4EFB\u52A1\u5DF2\u4EA4\u4ED8`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
deleteEvaluationsByJob(request.job_id, conn);
|
|
1516
|
+
const llm = provider ?? createLlmProvider();
|
|
1517
|
+
const tasksWithInfo = job.tasks.map((t) => {
|
|
1518
|
+
const agent = getAgent(t.assignee_id, conn);
|
|
1519
|
+
return {
|
|
1520
|
+
trace_id: t.trace_id,
|
|
1521
|
+
assignee_id: t.assignee_id,
|
|
1522
|
+
assignee_name: agent?.name || t.assignee_id,
|
|
1523
|
+
todo_description: t.todo_description,
|
|
1524
|
+
result_data: t.result_data,
|
|
1525
|
+
weight: request.task_weights?.[t.trace_id] ?? 1
|
|
1526
|
+
};
|
|
1527
|
+
});
|
|
1528
|
+
const response = await llm.complete({
|
|
1529
|
+
messages: [
|
|
1530
|
+
{
|
|
1531
|
+
role: "system",
|
|
1532
|
+
content: "\u4F60\u662F\u4E00\u4E2A\u5BA2\u89C2\u516C\u6B63\u7684\u7EE9\u6548\u8BC4\u4F30\u4E13\u5BB6\u3002\u6839\u636E\u4EFB\u52A1\u5B8C\u6210\u8D28\u91CF\u548C\u4EA4\u4ED8\u7269\u5185\u5BB9\u8FDB\u884C\u8BC4\u5206\u3002"
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
role: "user",
|
|
1536
|
+
content: buildEvalPrompt(job.original_prompt, request.rating_system, tasksWithInfo)
|
|
1537
|
+
}
|
|
1538
|
+
],
|
|
1539
|
+
temperature: 0.3,
|
|
1540
|
+
max_tokens: 4096
|
|
1541
|
+
});
|
|
1542
|
+
const jsonStr = extractJson2(response.content);
|
|
1543
|
+
let parsed;
|
|
1544
|
+
try {
|
|
1545
|
+
parsed = JSON.parse(jsonStr);
|
|
1546
|
+
} catch {
|
|
1547
|
+
throw new Error("AI \u8FD4\u56DE\u7684\u7EE9\u6548\u8BC4\u4EF7\u65E0\u6CD5\u89E3\u6790\u4E3A JSON\uFF0C\u8BF7\u91CD\u8BD5");
|
|
1548
|
+
}
|
|
1549
|
+
if (!Array.isArray(parsed)) {
|
|
1550
|
+
throw new Error("AI \u8FD4\u56DE\u7684\u4E0D\u662F\u6709\u6548\u7684\u8BC4\u4EF7\u6570\u7EC4");
|
|
1551
|
+
}
|
|
1552
|
+
const evaluations = [];
|
|
1553
|
+
for (const item of parsed) {
|
|
1554
|
+
const agentId = String(item.agent_id || "");
|
|
1555
|
+
const traceId = String(item.trace_id || "");
|
|
1556
|
+
let rating = String(item.rating || "");
|
|
1557
|
+
const comment = String(item.comment || "");
|
|
1558
|
+
if (!validateRating(request.rating_system, rating)) {
|
|
1559
|
+
const ratings = RATING_SYSTEMS[request.rating_system];
|
|
1560
|
+
rating = ratings[Math.floor(ratings.length / 2)];
|
|
1561
|
+
}
|
|
1562
|
+
const weight = request.task_weights?.[traceId] ?? 1;
|
|
1563
|
+
const evaluation = createEvaluation({
|
|
1564
|
+
eval_id: generateEvalId(),
|
|
1565
|
+
job_id: request.job_id,
|
|
1566
|
+
agent_id: agentId,
|
|
1567
|
+
trace_id: traceId,
|
|
1568
|
+
rating_system: request.rating_system,
|
|
1569
|
+
rating,
|
|
1570
|
+
weight,
|
|
1571
|
+
comment
|
|
1572
|
+
}, conn);
|
|
1573
|
+
evaluations.push(evaluation);
|
|
1574
|
+
}
|
|
1575
|
+
return {
|
|
1576
|
+
job_id: request.job_id,
|
|
1577
|
+
evaluations,
|
|
1578
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
function getPerformanceDashboard(agentId, db2) {
|
|
1582
|
+
const conn = db2 ?? getDb();
|
|
1583
|
+
if (agentId) {
|
|
1584
|
+
return { evaluations: listEvaluationsByAgent(agentId, conn) };
|
|
1585
|
+
}
|
|
1586
|
+
const rows = conn.prepare(
|
|
1587
|
+
`SELECT e.*, j.original_prompt, t.todo_description, a.name AS agent_name
|
|
1588
|
+
FROM evaluations e
|
|
1589
|
+
LEFT JOIN jobs j ON e.job_id = j.job_id
|
|
1590
|
+
LEFT JOIN tasks t ON e.trace_id = t.trace_id
|
|
1591
|
+
LEFT JOIN agents a ON e.agent_id = a.agent_id
|
|
1592
|
+
ORDER BY e.created_at DESC
|
|
1593
|
+
LIMIT 50`
|
|
1594
|
+
).all();
|
|
1595
|
+
return { evaluations: rows };
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/routes/evaluations.ts
|
|
1599
|
+
var router7 = Router7();
|
|
1600
|
+
router7.post("/generate", async (req, res) => {
|
|
1601
|
+
const { job_id, rating_system, task_weights } = req.body;
|
|
1602
|
+
if (!job_id || typeof job_id !== "string") {
|
|
1603
|
+
res.status(400).json({ error: "job_id is required" });
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
const validSystems = ["ali", "letter", "em"];
|
|
1607
|
+
if (!rating_system || !validSystems.includes(rating_system)) {
|
|
1608
|
+
res.status(400).json({
|
|
1609
|
+
error: `rating_system is required. Must be one of: ${validSystems.join(", ")}`
|
|
1610
|
+
});
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
try {
|
|
1614
|
+
const result = await generateEvaluations({
|
|
1615
|
+
job_id,
|
|
1616
|
+
rating_system,
|
|
1617
|
+
task_weights
|
|
1618
|
+
});
|
|
1619
|
+
res.json(result);
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1622
|
+
const status = message.includes("API Key") || message.includes("API key") ? 503 : 400;
|
|
1623
|
+
res.status(status).json({ error: message });
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
router7.get("/job/:job_id", (req, res) => {
|
|
1627
|
+
const evaluations = listEvaluationsByJob(req.params.job_id);
|
|
1628
|
+
res.json({ evaluations });
|
|
1629
|
+
});
|
|
1630
|
+
router7.get("/agent/:agent_id", (req, res) => {
|
|
1631
|
+
const evaluations = listEvaluationsByAgent(req.params.agent_id);
|
|
1632
|
+
res.json({ evaluations });
|
|
1633
|
+
});
|
|
1634
|
+
router7.get("/dashboard", (req, res) => {
|
|
1635
|
+
const agentId = req.query.agent_id;
|
|
1636
|
+
const dashboard = getPerformanceDashboard(agentId);
|
|
1637
|
+
res.json(dashboard);
|
|
1638
|
+
});
|
|
1639
|
+
var evaluations_default = router7;
|
|
1640
|
+
|
|
1011
1641
|
// src/dashboard.ts
|
|
1012
1642
|
function getDashboardHtml() {
|
|
1013
1643
|
return `<!DOCTYPE html>
|
|
@@ -1164,6 +1794,23 @@ main{padding:24px 32px;max-width:1200px}
|
|
|
1164
1794
|
.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}
|
|
1165
1795
|
.task-row .fg{margin-bottom:10px}
|
|
1166
1796
|
.task-row .fg:last-child{margin-bottom:0}
|
|
1797
|
+
/* Markdown Editor */
|
|
1798
|
+
.md-editor{position:relative}
|
|
1799
|
+
.md-editor textarea{min-height:120px;resize:vertical;font-family:var(--font-mono);font-size:12px;width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px 12px;color:var(--text);outline:none;transition:border-color .15s}
|
|
1800
|
+
.md-editor textarea:focus{border-color:var(--accent)}
|
|
1801
|
+
.md-expand-btn{position:absolute;top:6px;right:6px;background:var(--surface-hover);border:1px solid var(--border);color:var(--text-dim);border-radius:4px;width:24px;height:24px;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:2;transition:all .15s;line-height:1}
|
|
1802
|
+
.md-expand-btn:hover{color:var(--accent);border-color:var(--accent)}
|
|
1803
|
+
.md-editor.fullscreen{position:fixed;inset:20px;z-index:200;background:var(--surface);border-radius:var(--radius);padding:16px;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.7)}
|
|
1804
|
+
.md-editor.fullscreen textarea{flex:1;min-height:unset;height:100%;resize:none;font-size:14px}
|
|
1805
|
+
.md-editor.fullscreen .md-expand-btn{top:22px;right:22px}
|
|
1806
|
+
.md-fullscreen-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:199}
|
|
1807
|
+
/* Team badge */
|
|
1808
|
+
.team-badge{display:inline-block;background:rgba(168,85,247,.12);border:1px solid rgba(168,85,247,.25);border-radius:4px;padding:1px 8px;font-size:10px;color:var(--purple);margin-right:4px;margin-bottom:2px}
|
|
1809
|
+
/* Evaluation */
|
|
1810
|
+
.eval-badge{display:inline-block;padding:3px 10px;border-radius:12px;font-weight:700;font-size:13px;font-family:var(--font-mono)}
|
|
1811
|
+
.eval-badge.top{background:rgba(34,197,94,.15);color:var(--green)}
|
|
1812
|
+
.eval-badge.mid{background:rgba(234,179,8,.15);color:var(--yellow)}
|
|
1813
|
+
.eval-badge.low{background:rgba(239,68,68,.15);color:var(--red)}
|
|
1167
1814
|
</style>
|
|
1168
1815
|
</head>
|
|
1169
1816
|
<body>
|
|
@@ -1193,8 +1840,25 @@ function toast(msg,ok){
|
|
|
1193
1840
|
setTimeout(()=>t.remove(),3500);
|
|
1194
1841
|
}
|
|
1195
1842
|
let cachedAgents=[];
|
|
1843
|
+
let cachedTeams=[];
|
|
1196
1844
|
let currentPlan=null;
|
|
1197
1845
|
let selectedAgentIds=new Set();
|
|
1846
|
+
let selectedTeamId='';
|
|
1847
|
+
|
|
1848
|
+
window.toggleFullscreen=function(btn){
|
|
1849
|
+
const editor=btn.closest('.md-editor');
|
|
1850
|
+
if(editor.classList.contains('fullscreen')){
|
|
1851
|
+
editor.classList.remove('fullscreen');
|
|
1852
|
+
const bd=document.querySelector('.md-fullscreen-backdrop');
|
|
1853
|
+
if(bd)bd.remove();
|
|
1854
|
+
}else{
|
|
1855
|
+
const bd=document.createElement('div');bd.className='md-fullscreen-backdrop';
|
|
1856
|
+
bd.onclick=()=>{editor.classList.remove('fullscreen');bd.remove()};
|
|
1857
|
+
document.body.appendChild(bd);
|
|
1858
|
+
editor.classList.add('fullscreen');
|
|
1859
|
+
const ta=editor.querySelector('textarea');if(ta)ta.focus();
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1198
1862
|
|
|
1199
1863
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
1200
1864
|
// DEMO SCENARIOS
|
|
@@ -1204,6 +1868,10 @@ const DEMOS={
|
|
|
1204
1868
|
emoji:'🐉',title:'\u4E09\u56FD\u8700\u6C49',role:'\u4F60\u662F\u5218\u5907',
|
|
1205
1869
|
desc:'\u6843\u56ED\u7ED3\u4E49\uFF0C\u4E09\u987E\u8305\u5E90\u3002\u4F5C\u4E3A\u8700\u6C49\u4E4B\u4E3B\uFF0C\u7EDF\u9886\u4E94\u864E\u4E0A\u5C06\u548C\u5367\u9F99\u51E4\u96CF\uFF0C\u9010\u9E7F\u4E2D\u539F\u3002',
|
|
1206
1870
|
prompt:'\u5317\u4F10\u4E2D\u539F\uFF0C\u5175\u5206\u4E09\u8DEF\uFF0C\u9700\u8981\u653B\u57CE\u3001\u65AD\u7CAE\u548C\u5916\u4EA4\u4E09\u7BA1\u9F50\u4E0B',
|
|
1871
|
+
teams:[
|
|
1872
|
+
{name:'\u4E94\u864E\u4E0A\u5C06',description:'\u8700\u6C49\u4E94\u5927\u731B\u5C06',members:['\u5173\u7FBD','\u5F20\u98DE','\u8D75\u4E91','\u9EC4\u5FE0','\u9A6C\u8D85'],relationships:{'\u5173\u7FBD':'\u4E49\u5F1F\u517C\u519B\u56E2\u7EDF\u5E05','\u5F20\u98DE':'\u4E49\u5F1F\u517C\u5148\u950B\u5927\u5C06','\u8D75\u4E91':'\u5FC3\u8179\u7231\u5C06','\u9EC4\u5FE0':'\u8001\u5F53\u76CA\u58EE\u7684\u731B\u5C06','\u9A6C\u8D85':'\u5F52\u964D\u7684\u897F\u51C9\u864E\u5C06'}},
|
|
1873
|
+
{name:'\u8C0B\u58EB\u56E2',description:'\u8FD0\u7B79\u5E37\u5E44\u7684\u667A\u56CA\u56E2',members:['\u8BF8\u845B\u4EAE','\u5E9E\u7EDF'],relationships:{'\u8BF8\u845B\u4EAE':'\u9996\u5E2D\u519B\u5E08\uFF0C\u5982\u9C7C\u5F97\u6C34','\u5E9E\u7EDF':'\u526F\u519B\u5E08\uFF0C\u5947\u8C0B\u767E\u51FA'}}
|
|
1874
|
+
],
|
|
1207
1875
|
agents:[
|
|
1208
1876
|
{name:'\u5173\u7FBD',capabilities:['\u6B66\u827A','\u7EDF\u5175','\u9547\u5B88\u8981\u5730','\u6C34\u519B\u6307\u6325'],relationship:'\u4E49\u5F1F\uFF0C\u6843\u56ED\u7ED3\u4E49\u4E8C\u5F1F\uFF0C\u6700\u4FE1\u4EFB\u7684\u5144\u5F1F\u548C\u5927\u5C06'},
|
|
1209
1877
|
{name:'\u5F20\u98DE',capabilities:['\u6B66\u827A','\u5148\u950B\u7A81\u51FB','\u9A91\u5175\u6307\u6325','\u5A01\u6151\u654C\u519B'],relationship:'\u4E49\u5F1F\uFF0C\u6843\u56ED\u7ED3\u4E49\u4E09\u5F1F\uFF0C\u6027\u5982\u70C8\u706B\u4F46\u5FE0\u5FC3\u803F\u803F'},
|
|
@@ -1218,6 +1886,12 @@ const DEMOS={
|
|
|
1218
1886
|
emoji:'💻',title:'\u4E92\u8054\u7F51\u5927\u5382',role:'\u4F60\u662F\u6280\u672F\u603B\u76D1',
|
|
1219
1887
|
desc:'\u5E26\u9886\u4E00\u652F\u5168\u6808\u56E2\u961F\uFF0C\u4ECE\u524D\u7AEF\u5230\u8FD0\u7EF4\u4E00\u5E94\u4FF1\u5168\u3002\u5E94\u5BF9\u9AD8\u5E76\u53D1\u3001\u641E AI\u3001\u4E0A\u7EBF\u65B0\u7CFB\u7EDF\u3002',
|
|
1220
1888
|
prompt:'\u4E0A\u7EBF\u4E00\u4E2A AI \u667A\u80FD\u5BA2\u670D\u7CFB\u7EDF\uFF0C\u5305\u62EC\u524D\u7AEF\u754C\u9762\u3001\u540E\u7AEF API\u3001\u63A8\u8350\u7B97\u6CD5\u3001\u538B\u529B\u6D4B\u8BD5\u548C\u7070\u5EA6\u53D1\u5E03\u65B9\u6848',
|
|
1889
|
+
teams:[
|
|
1890
|
+
{name:'\u524D\u7AEF\u7EC4',description:'\u8D1F\u8D23\u6240\u6709\u524D\u7AEF\u9875\u9762\u5F00\u53D1',members:['\u524D\u7AEF\u8001\u674E','\u8BBE\u8BA1\u5E08\u5C0F\u6797'],relationships:{'\u524D\u7AEF\u8001\u674E':'\u524D\u7AEFTL\uFF0C\u6838\u5FC3\u9AA8\u5E72','\u8BBE\u8BA1\u5E08\u5C0F\u6797':'\u8D44\u6DF1\u8BBE\u8BA1\u5E08'}},
|
|
1891
|
+
{name:'\u540E\u7AEF\u7EC4',description:'\u540E\u7AEF\u67B6\u6784\u4E0E\u670D\u52A1\u5F00\u53D1',members:['\u540E\u7AEF\u5927\u738B','\u7B97\u6CD5\u5C0F\u9648'],relationships:{'\u540E\u7AEF\u5927\u738B':'\u67B6\u6784\u5E08\uFF0C\u6280\u672F\u51B3\u7B56\u8005','\u7B97\u6CD5\u5C0F\u9648':'\u7B97\u6CD5\u5DE5\u7A0B\u5E08\uFF0C\u9700\u8981\u6307\u5BFC'}},
|
|
1892
|
+
{name:'\u4EA7\u54C1\u7EC4',description:'\u4EA7\u54C1\u9700\u6C42\u4E0E\u9879\u76EE\u7BA1\u7406',members:['\u4EA7\u54C1\u7ECF\u7406 Amy'],relationships:{'\u4EA7\u54C1\u7ECF\u7406 Amy':'\u4EA7\u54C1\u8D1F\u8D23\u4EBA'}},
|
|
1893
|
+
{name:'\u8D28\u91CF\u7EC4',description:'\u6D4B\u8BD5\u4E0E\u8FD0\u7EF4\u4FDD\u969C',members:['\u6D4B\u8BD5\u8D1F\u8D23\u4EBA\u8001\u8D75','\u8FD0\u7EF4 DevOps \u963F\u6770'],relationships:{'\u6D4B\u8BD5\u8D1F\u8D23\u4EBA\u8001\u8D75':'\u8D28\u91CF\u628A\u5173\u4EBA','\u8FD0\u7EF4 DevOps \u963F\u6770':'SRE\u8D1F\u8D23\u4EBA'}}
|
|
1894
|
+
],
|
|
1221
1895
|
agents:[
|
|
1222
1896
|
{name:'\u524D\u7AEF\u8001\u674E',capabilities:['React','TypeScript','Next.js','\u79FB\u52A8\u7AEF\u9002\u914D','\u6027\u80FD\u4F18\u5316'],relationship:'P7 \u524D\u7AEF TL\uFF0C\u8DDF\u4E86\u4F60\u4E09\u5E74\uFF0C\u6280\u672F\u8FC7\u786C\u4F46\u6700\u8FD1\u6709\u70B9\u5026\u6020'},
|
|
1223
1897
|
{name:'\u540E\u7AEF\u5927\u738B',capabilities:['Java','Go','\u5FAE\u670D\u52A1','\u6570\u636E\u5E93\u8BBE\u8BA1','\u9AD8\u5E76\u53D1\u67B6\u6784'],relationship:'P8 \u540E\u7AEF\u67B6\u6784\u5E08\uFF0C\u6280\u672F\u5927\u62FF\uFF0C\u8BF4\u8BDD\u76F4\u6765\u76F4\u53BB'},
|
|
@@ -1232,6 +1906,11 @@ const DEMOS={
|
|
|
1232
1906
|
emoji:'🇺🇸',title:'\u7F8E\u56FD\u653F\u5E9C',role:'\u4F60\u662F\u7279\u6717\u666E (POTUS)',
|
|
1233
1907
|
desc:'Make the executive branch great again! \u7BA1\u7406\u4F60\u7684\u6838\u5FC3\u5185\u9601\u6210\u5458\uFF0C\u63A8\u884C\u653F\u7B56\u8BAE\u7A0B\u3002',
|
|
1234
1908
|
prompt:'\u5236\u5B9A\u4E00\u4E2A\u8BA9\u7F8E\u56FD\u5236\u9020\u4E1A\u56DE\u6D41\u7684\u7EFC\u5408\u8BA1\u5212\uFF0C\u9700\u8981\u5173\u7A0E\u653F\u7B56\u3001\u51CF\u7A0E\u65B9\u6848\u3001\u80FD\u6E90\u4FDD\u969C\u3001\u8FB9\u5883\u5B89\u5168\u914D\u5408\u548C\u653F\u5E9C\u6548\u7387\u4F18\u5316',
|
|
1909
|
+
teams:[
|
|
1910
|
+
{name:'\u7ECF\u6D4E\u5B89\u5168\u56E2\u961F',description:'\u7ECF\u6D4E\u653F\u7B56\u4E0E\u8D22\u653F\u7BA1\u7406',members:['Scott Bessent','Elon Musk'],relationships:{'Scott Bessent':'\u8D22\u653F\u90E8\u957F\uFF0C\u9996\u5E2D\u7ECF\u6D4E\u987E\u95EE','Elon Musk':'DOGE \u8D1F\u8D23\u4EBA\uFF0C\u6548\u7387\u6539\u9769\u63A8\u52A8\u8005'}},
|
|
1911
|
+
{name:'\u56FD\u9632\u5916\u4EA4\u56E2\u961F',description:'\u56FD\u9632\u4E0E\u5916\u4EA4\u4E8B\u52A1',members:['Marco Rubio','Pete Hegseth','Tulsi Gabbard'],relationships:{'Marco Rubio':'\u56FD\u52A1\u537F\uFF0C\u5916\u4EA4\u603B\u7BA1','Pete Hegseth':'\u56FD\u9632\u90E8\u957F\uFF0C\u519B\u4E8B\u4E8B\u52A1','Tulsi Gabbard':'\u60C5\u62A5\u603B\u76D1\uFF0C\u5B89\u5168\u8BC4\u4F30'}},
|
|
1912
|
+
{name:'\u56FD\u5185\u4E8B\u52A1\u56E2\u961F',description:'\u56FD\u5185\u5B89\u5168\u4E0E\u516C\u5171\u670D\u52A1',members:['Kristi Noem','Robert F. Kennedy Jr.'],relationships:{'Kristi Noem':'\u56FD\u571F\u5B89\u5168\u90E8\u957F\uFF0C\u8FB9\u5883\u5F3A\u786C\u6D3E','Robert F. Kennedy Jr.':'\u536B\u751F\u90E8\u957F\uFF0C\u533B\u7597\u6539\u9769'}}
|
|
1913
|
+
],
|
|
1235
1914
|
agents:[
|
|
1236
1915
|
{name:'Elon Musk',capabilities:['\u653F\u5E9C\u6548\u7387','\u6210\u672C\u524A\u51CF','\u79D1\u6280\u521B\u65B0','SpaceX','Tesla','\u793E\u4EA4\u5A92\u4F53'],relationship:'DOGE \u8D1F\u8D23\u4EBA\uFF0C\u4E16\u754C\u9996\u5BCC\uFF0CTwitter/X \u8001\u677F\uFF0C\u6700\u5177\u5F71\u54CD\u529B\u7684\u76DF\u53CB'},
|
|
1237
1916
|
{name:'Marco Rubio',capabilities:['\u5916\u4EA4\u653F\u7B56','\u62C9\u7F8E\u4E8B\u52A1','\u56FD\u9645\u8C08\u5224','\u5236\u88C1\u653F\u7B56','\u56FD\u5BB6\u5B89\u5168'],relationship:'\u56FD\u52A1\u537F\uFF0C\u4F5B\u7F57\u91CC\u8FBE\u53C2\u8BAE\u5458\uFF0C\u66FE\u7ECF\u7684\u7ADE\u9009\u5BF9\u624B\u53D8\u5FE0\u5B9E\u652F\u6301\u8005'},
|
|
@@ -1274,12 +1953,31 @@ window.loadDemo=async function(key){
|
|
|
1274
1953
|
}
|
|
1275
1954
|
let ok=0;
|
|
1276
1955
|
const demoAgentIds=[];
|
|
1956
|
+
const agentNameToId={};
|
|
1277
1957
|
for(const a of demo.agents){
|
|
1278
1958
|
try{
|
|
1279
1959
|
const r=await fetch(API+'/nodes',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:a.name,capabilities:a.capabilities,relationship:a.relationship})});
|
|
1280
|
-
if(r.ok){ok++;const d=await r.json();demoAgentIds.push(d.agent_id);}
|
|
1960
|
+
if(r.ok){ok++;const d=await r.json();demoAgentIds.push(d.agent_id);agentNameToId[a.name]=d.agent_id;}
|
|
1281
1961
|
}catch{}
|
|
1282
1962
|
}
|
|
1963
|
+
// Create demo teams
|
|
1964
|
+
if(demo.teams){
|
|
1965
|
+
for(const tm of demo.teams){
|
|
1966
|
+
try{
|
|
1967
|
+
const tr=await fetch(API+'/teams',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:tm.name,description:tm.description||''})});
|
|
1968
|
+
if(tr.ok){
|
|
1969
|
+
const td=await tr.json();
|
|
1970
|
+
for(const mName of tm.members){
|
|
1971
|
+
const agentId=agentNameToId[mName];
|
|
1972
|
+
if(agentId){
|
|
1973
|
+
const rel=tm.relationships?tm.relationships[mName]||'':'';
|
|
1974
|
+
await fetch(API+'/teams/'+td.team_id+'/members',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({agent_id:agentId,relationship:rel})});
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
}catch{}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1283
1981
|
toast(demo.title+' \u573A\u666F\u5DF2\u52A0\u8F7D\uFF01'+ok+'/'+demo.agents.length+' \u4E2A\u8282\u70B9\u6CE8\u518C\u6210\u529F',true);
|
|
1284
1982
|
// Switch to pipeline and open AI planning with suggested prompt
|
|
1285
1983
|
load('fleet');
|
|
@@ -1324,6 +2022,8 @@ async function loadFleet(el){
|
|
|
1324
2022
|
const r=await fetch(API+'/nodes/status');
|
|
1325
2023
|
const d=await r.json();
|
|
1326
2024
|
cachedAgents=d.agents||[];
|
|
2025
|
+
// Also load teams
|
|
2026
|
+
try{const tr=await fetch(API+'/teams');const td=await tr.json();cachedTeams=td.teams||[]}catch{cachedTeams=[]}
|
|
1327
2027
|
let h='<div class="section-hd"><h2>\u78B3\u57FA\u7B97\u529B\u6C60</h2><div style="display:flex;gap:6px"><button class="btn btn-ghost btn-sm" onclick="showDemoSelector()">🎮 Demo</button><button class="btn btn-primary btn-sm" onclick="showAddAgent()">+ \u6DFB\u52A0\u8282\u70B9</button></div></div>';
|
|
1328
2028
|
h+='<div class="stats">';
|
|
1329
2029
|
h+='<div class="stat"><div class="stat-val">'+d.total+'</div><div class="stat-lbl">\u603B\u8BA1</div></div>';
|
|
@@ -1342,6 +2042,9 @@ async function loadFleet(el){
|
|
|
1342
2042
|
h+='<div class="card"><div class="agent-header"><span class="dot '+a.status+'"></span><span class="agent-name">'+esc(a.name)+'</span></div>';
|
|
1343
2043
|
h+='<div class="agent-id">'+a.agent_id+'</div>';
|
|
1344
2044
|
if(a.relationship)h+='<div style="font-size:11px;color:var(--purple);margin-bottom:4px">👥 '+esc(a.relationship)+'</div>';
|
|
2045
|
+
// Team badges
|
|
2046
|
+
const agentTeams=cachedTeams.filter(tm=>tm.members&&tm.members.some(m=>m.agent_id===a.agent_id));
|
|
2047
|
+
if(agentTeams.length){h+='<div style="margin-bottom:4px">';for(const tm of agentTeams)h+='<span class="team-badge">'+esc(tm.name)+'</span>';h+='</div>'}
|
|
1345
2048
|
h+='<div class="caps">';for(const c of a.capabilities)h+='<span class="cap">'+esc(c)+'</span>';h+='</div>';
|
|
1346
2049
|
h+='<div class="agent-meta"><span>\u4EFB\u52A1: '+a.active_task_count+'</span>';
|
|
1347
2050
|
if(a.avg_delivery_hours!==null)h+='<span>\u5E73\u5747\u4EA4\u4ED8: '+a.avg_delivery_hours+'h</span>';
|
|
@@ -1354,6 +2057,23 @@ async function loadFleet(el){
|
|
|
1354
2057
|
h+='</div></div>';
|
|
1355
2058
|
}
|
|
1356
2059
|
h+='</div>';
|
|
2060
|
+
// Teams section
|
|
2061
|
+
h+='<div style="margin-top:32px"><div class="section-hd"><h2>\u56E2\u961F\u7BA1\u7406</h2><button class="btn btn-primary btn-sm" onclick="showCreateTeam()">+ \u521B\u5EFA\u56E2\u961F</button></div>';
|
|
2062
|
+
if(cachedTeams.length){
|
|
2063
|
+
h+='<div class="grid">';
|
|
2064
|
+
for(const tm of cachedTeams){
|
|
2065
|
+
const memberCount=tm.members?tm.members.length:0;
|
|
2066
|
+
h+='<div class="card" style="cursor:pointer" onclick="showTeamDetail(\\''+tm.team_id+'\\')">';
|
|
2067
|
+
h+='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span style="font-size:20px">👥</span><span class="agent-name">'+esc(tm.name)+'</span></div>';
|
|
2068
|
+
if(tm.description)h+='<div style="font-size:12px;color:var(--text-dim);margin-bottom:8px">'+esc(tm.description)+'</div>';
|
|
2069
|
+
h+='<div style="font-size:11px;color:var(--accent)">'+memberCount+' \u4E2A\u6210\u5458</div>';
|
|
2070
|
+
h+='</div>';
|
|
2071
|
+
}
|
|
2072
|
+
h+='</div>';
|
|
2073
|
+
}else{
|
|
2074
|
+
h+='<div style="font-size:12px;color:var(--text-dim);margin-top:8px">\u6682\u65E0\u56E2\u961F\u3002\u521B\u5EFA\u56E2\u961F\u540E\u53EF\u6309\u56E2\u961F\u5206\u914D\u4EFB\u52A1\u3002</div>';
|
|
2075
|
+
}
|
|
2076
|
+
h+='</div>';
|
|
1357
2077
|
el.innerHTML=h;
|
|
1358
2078
|
}catch(e){el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u5931\u8D25: '+e.message+'</p></div>'}
|
|
1359
2079
|
}
|
|
@@ -1399,6 +2119,95 @@ window.deleteAgent=async function(id){
|
|
|
1399
2119
|
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1400
2120
|
};
|
|
1401
2121
|
|
|
2122
|
+
// \u2500\u2500\u2500 Team Management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2123
|
+
window.showCreateTeam=function(){
|
|
2124
|
+
const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
|
|
2125
|
+
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
|
|
2126
|
+
let membersHtml='<div id="team-members-list">';
|
|
2127
|
+
for(const a of cachedAgents){
|
|
2128
|
+
membersHtml+='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">';
|
|
2129
|
+
membersHtml+='<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px"><input type="checkbox" class="tm-check" value="'+a.agent_id+'"/><span class="dot '+a.status+'" style="width:8px;height:8px"></span>'+esc(a.name)+'</label>';
|
|
2130
|
+
membersHtml+='<input class="tm-rel" data-agent="'+a.agent_id+'" placeholder="\u56E2\u961F\u4E2D\u7684\u5173\u7CFB" style="flex:1;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11px;color:var(--text);outline:none"/>';
|
|
2131
|
+
membersHtml+='</div>';
|
|
2132
|
+
}
|
|
2133
|
+
membersHtml+='</div>';
|
|
2134
|
+
ov.innerHTML='<div class="form-card"><h3>+ \u521B\u5EFA\u56E2\u961F</h3>'
|
|
2135
|
+
+'<div class="fg"><label>\u56E2\u961F\u540D\u79F0</label><input id="tm-name" placeholder="\u4F8B: \u524D\u7AEF\u7EC4"/></div>'
|
|
2136
|
+
+'<div class="fg"><label>\u56E2\u961F\u63CF\u8FF0 <span style="color:var(--text-dim);font-weight:400">(\u53EF\u9009)</span></label><input id="tm-desc" placeholder="\u4F8B: \u8D1F\u8D23\u6240\u6709\u524D\u7AEF\u9875\u9762\u5F00\u53D1"/></div>'
|
|
2137
|
+
+'<div class="fg"><label>\u9009\u62E9\u6210\u5458\u53CA\u56E2\u961F\u5173\u7CFB</label>'+membersHtml+'</div>'
|
|
2138
|
+
+'<div class="btn-group"><button class="btn btn-primary" onclick="submitTeam()">\u521B\u5EFA\u56E2\u961F</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div></div>';
|
|
2139
|
+
document.body.appendChild(ov);
|
|
2140
|
+
document.getElementById('tm-name').focus();
|
|
2141
|
+
};
|
|
2142
|
+
window.submitTeam=async function(){
|
|
2143
|
+
const name=document.getElementById('tm-name').value.trim();
|
|
2144
|
+
const desc=document.getElementById('tm-desc').value.trim();
|
|
2145
|
+
if(!name){toast('\u8BF7\u8F93\u5165\u56E2\u961F\u540D\u79F0',false);return}
|
|
2146
|
+
try{
|
|
2147
|
+
const r=await fetch(API+'/teams',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,description:desc})});
|
|
2148
|
+
const d=await r.json();
|
|
2149
|
+
if(!r.ok){toast(d.error||'\u521B\u5EFA\u5931\u8D25',false);return}
|
|
2150
|
+
const checks=document.querySelectorAll('.tm-check:checked');
|
|
2151
|
+
for(const chk of checks){
|
|
2152
|
+
const agentId=chk.value;
|
|
2153
|
+
const relInput=document.querySelector('.tm-rel[data-agent="'+agentId+'"]');
|
|
2154
|
+
const rel=relInput?relInput.value.trim():'';
|
|
2155
|
+
await fetch(API+'/teams/'+d.team_id+'/members',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({agent_id:agentId,relationship:rel})});
|
|
2156
|
+
}
|
|
2157
|
+
toast('\u56E2\u961F\u300C'+name+'\u300D\u521B\u5EFA\u6210\u529F\uFF01',true);
|
|
2158
|
+
document.getElementById('overlay').remove();
|
|
2159
|
+
load('fleet');
|
|
2160
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
2161
|
+
};
|
|
2162
|
+
window.showTeamDetail=async function(teamId){
|
|
2163
|
+
const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
|
|
2164
|
+
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
|
|
2165
|
+
ov.innerHTML='<div class="form-card"><h3>\u56E2\u961F\u8BE6\u60C5</h3><div class="spinner-wrap"><div class="spinner"></div></div></div>';
|
|
2166
|
+
document.body.appendChild(ov);
|
|
2167
|
+
try{
|
|
2168
|
+
const r=await fetch(API+'/teams/'+teamId);
|
|
2169
|
+
const tm=await r.json();
|
|
2170
|
+
if(!r.ok){toast(tm.error||'\u52A0\u8F7D\u5931\u8D25',false);ov.remove();return}
|
|
2171
|
+
const fc=ov.querySelector('.form-card');
|
|
2172
|
+
let h='<h3>'+esc(tm.name)+'</h3>';
|
|
2173
|
+
if(tm.description)h+='<div style="font-size:12px;color:var(--text-dim);margin-bottom:16px">'+esc(tm.description)+'</div>';
|
|
2174
|
+
h+='<div style="font-size:11px;color:var(--text-dim);margin-bottom:12px;font-family:var(--font-mono)">'+tm.team_id+'</div>';
|
|
2175
|
+
if(tm.members&&tm.members.length){
|
|
2176
|
+
h+='<div style="font-size:13px;font-weight:600;margin-bottom:8px">\u6210\u5458 ('+tm.members.length+')</div>';
|
|
2177
|
+
for(const m of tm.members){
|
|
2178
|
+
h+='<div style="display:flex;align-items:center;gap:8px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:8px;margin-bottom:6px">';
|
|
2179
|
+
h+='<span class="dot '+(m.agent_status||'IDLE')+'" style="width:8px;height:8px"></span>';
|
|
2180
|
+
h+='<span style="font-size:13px;font-weight:500">'+esc(m.agent_name||m.agent_id)+'</span>';
|
|
2181
|
+
if(m.relationship)h+='<span style="font-size:11px;color:var(--purple);margin-left:auto">'+esc(m.relationship)+'</span>';
|
|
2182
|
+
h+='<button style="margin-left:'+(m.relationship?'8px':'auto')+';background:none;border:none;color:var(--red);cursor:pointer;font-size:14px" onclick="removeTeamMember(\\''+teamId+'\\',\\''+m.agent_id+'\\')">×</button>';
|
|
2183
|
+
h+='</div>';
|
|
2184
|
+
}
|
|
2185
|
+
}else{
|
|
2186
|
+
h+='<div style="font-size:12px;color:var(--text-dim)">\u6682\u65E0\u6210\u5458</div>';
|
|
2187
|
+
}
|
|
2188
|
+
h+='<div class="btn-group"><button class="btn btn-danger btn-sm" onclick="deleteTeam(\\''+teamId+'\\')">\u5220\u9664\u56E2\u961F</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u5173\u95ED</button></div>';
|
|
2189
|
+
fc.innerHTML=h;
|
|
2190
|
+
}catch{toast('\u52A0\u8F7D\u5931\u8D25',false);ov.remove()}
|
|
2191
|
+
};
|
|
2192
|
+
window.removeTeamMember=async function(teamId,agentId){
|
|
2193
|
+
try{
|
|
2194
|
+
await fetch(API+'/teams/'+teamId+'/members/'+agentId,{method:'DELETE'});
|
|
2195
|
+
toast('\u6210\u5458\u5DF2\u79FB\u9664',true);
|
|
2196
|
+
document.getElementById('overlay').remove();
|
|
2197
|
+
showTeamDetail(teamId);
|
|
2198
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
2199
|
+
};
|
|
2200
|
+
window.deleteTeam=async function(teamId){
|
|
2201
|
+
if(!confirm('\u786E\u5B9A\u5220\u9664\u6B64\u56E2\u961F\uFF1F'))return;
|
|
2202
|
+
try{
|
|
2203
|
+
const r=await fetch(API+'/teams/'+teamId,{method:'DELETE'});
|
|
2204
|
+
if(!r.ok){toast('\u5220\u9664\u5931\u8D25',false);return}
|
|
2205
|
+
toast('\u56E2\u961F\u5DF2\u5220\u9664',true);
|
|
2206
|
+
document.getElementById('overlay').remove();
|
|
2207
|
+
load('fleet');
|
|
2208
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
2209
|
+
};
|
|
2210
|
+
|
|
1402
2211
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
1403
2212
|
// PIPELINE
|
|
1404
2213
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
@@ -1430,7 +2239,7 @@ async function loadPipeline(el){
|
|
|
1430
2239
|
allTasks.push(...j.tasks);
|
|
1431
2240
|
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>';
|
|
1432
2241
|
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>';
|
|
1433
|
-
if(pct===100)h+='<div style="margin-bottom:12px"><button class="btn btn-green btn-sm" onclick="reviewJob(\\''+j.job_id+'\\')">AI \u805A\u5408\u5BA1\u67E5</button></div>';
|
|
2242
|
+
if(pct===100)h+='<div style="margin-bottom:12px;display:flex;gap:6px;flex-wrap:wrap"><button class="btn btn-green btn-sm" onclick="reviewJob(\\''+j.job_id+'\\')">AI \u805A\u5408\u5BA1\u67E5</button><button class="btn btn-primary btn-sm" onclick="showEvalDialog(\\''+j.job_id+'\\')">📊 \u751F\u6210\u7EE9\u6548\u8BC4\u4EF7</button></div>';
|
|
1434
2243
|
h+='<div class="kanban"><div class="lane"><div class="lane-hd y">\u5DF2\u5206\u53D1 ('+dispatched.length+')</div>'+dispatched.map(tcard).join('')+'</div>';
|
|
1435
2244
|
h+='<div class="lane"><div class="lane-hd r">\u5DF2\u8D85\u65F6 ('+overdue.length+')</div>'+overdue.map(tcard).join('')+'</div>';
|
|
1436
2245
|
h+='<div class="lane"><div class="lane-hd g">\u5DF2\u4EA4\u4ED8 ('+done.length+')</div>'+done.map(tcard).join('')+'</div></div></div>';
|
|
@@ -1488,9 +2297,24 @@ function renderPlanStep1(ov){
|
|
|
1488
2297
|
const el=document.getElementById('agent-chip-area');
|
|
1489
2298
|
if(el)el.innerHTML=renderChips(chipFilter);
|
|
1490
2299
|
};
|
|
2300
|
+
window._selectTeam=function(teamId){
|
|
2301
|
+
selectedTeamId=teamId;
|
|
2302
|
+
if(teamId){
|
|
2303
|
+
const tm=cachedTeams.find(t=>t.team_id===teamId);
|
|
2304
|
+
if(tm&&tm.members){
|
|
2305
|
+
selectedAgentIds=new Set(tm.members.map(m=>m.agent_id));
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
const el=document.getElementById('agent-chip-area');
|
|
2309
|
+
if(el)el.innerHTML=renderChips(chipFilter);
|
|
2310
|
+
// Re-render to show team hint
|
|
2311
|
+
renderPlanStep1(document.getElementById('overlay'));
|
|
2312
|
+
};
|
|
1491
2313
|
|
|
1492
2314
|
ov.innerHTML='<div class="form-card"><h3>AI \u667A\u80FD\u89C4\u5212</h3>'
|
|
1493
|
-
+'<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">'+esc(pendingDemoPrompt)+'</textarea></div>'
|
|
2315
|
+
+'<div class="fg"><label>\u8F93\u5165\u4F60\u7684\u9700\u6C42</label><div class="md-editor"><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">'+esc(pendingDemoPrompt)+'</textarea><button class="md-expand-btn" onclick="toggleFullscreen(this)" title="\u5C55\u5F00/\u6536\u8D77">⛶</button></div></div>'
|
|
2316
|
+
+'<div class="fg"><label>\u6309\u56E2\u961F\u9009\u62E9 <span style="color:var(--text-dim);font-weight:400">(\u53EF\u9009)</span></label><select id="plan-team" onchange="window._selectTeam(this.value)"><option value="">-- \u4E0D\u6309\u56E2\u961F --</option>'+cachedTeams.map(t=>'<option value="'+t.team_id+'"'+(selectedTeamId===t.team_id?' selected':'')+'>'+esc(t.name)+'</option>').join('')+'</select>'
|
|
2317
|
+
+(selectedTeamId?'<div class="hint" style="color:var(--accent)">👥 \u4F7F\u7528\u56E2\u961F\u5173\u7CFB\u4E0A\u4E0B\u6587</div>':'')+'</div>'
|
|
1494
2318
|
+'<div class="fg"><label>\u9009\u62E9\u53C2\u4E0E\u7684\u78B3\u57FA\u8282\u70B9 <span style="color:var(--text-dim);font-weight:400">(\u9ED8\u8BA4\u9009\u4E2D\u7A7A\u95F2\u8282\u70B9)</span></label>'
|
|
1495
2319
|
+'<div id="agent-chip-area">'+renderChips('all')+'</div></div>'
|
|
1496
2320
|
+'<div class="btn-group">'
|
|
@@ -1511,7 +2335,9 @@ window.planWithAI=async function(){
|
|
|
1511
2335
|
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>';
|
|
1512
2336
|
|
|
1513
2337
|
try{
|
|
1514
|
-
const
|
|
2338
|
+
const reqBody={prompt,agent_ids:Array.from(selectedAgentIds)};
|
|
2339
|
+
if(selectedTeamId)reqBody.team_id=selectedTeamId;
|
|
2340
|
+
const r=await fetch(API+'/jobs/plan',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(reqBody)});
|
|
1515
2341
|
const d=await r.json();
|
|
1516
2342
|
if(!r.ok){
|
|
1517
2343
|
toast(d.error||'\u89C4\u5212\u5931\u8D25',false);
|
|
@@ -1624,18 +2450,59 @@ window.reviewJob=async function(jobId){
|
|
|
1624
2450
|
ov.innerHTML='<div class="form-card"><h3>AI \u805A\u5408\u5BA1\u67E5</h3><div class="spinner-wrap"><div class="spinner"></div><div class="spinner-text">AI \u6B63\u5728\u5BA1\u67E5\u6240\u6709\u4EA4\u4ED8\u7269...</div></div></div>';
|
|
1625
2451
|
document.body.appendChild(ov);
|
|
1626
2452
|
try{
|
|
1627
|
-
const r=await fetch(API+'/jobs/'+jobId+'/review',{method:'POST'});
|
|
2453
|
+
const r=await fetch(API+'/jobs/'+jobId+'/review',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})});
|
|
1628
2454
|
const d=await r.json();
|
|
1629
2455
|
if(!r.ok){toast(d.error||'\u5BA1\u67E5\u5931\u8D25',false);ov.remove();return}
|
|
1630
2456
|
const fc=ov.querySelector('.form-card');
|
|
1631
2457
|
fc.innerHTML='<h3>AI \u805A\u5408\u5BA1\u67E5</h3>'
|
|
1632
2458
|
+'<div style="font-size:12px;color:var(--text-dim);margin-bottom:12px">\u9700\u6C42: '+esc(d.original_prompt)+'</div>'
|
|
1633
|
-
+'<div class="result-display" style="max-height:400px">'+esc(d.review).replace(/\\n/g,'<br>')+'</div>'
|
|
2459
|
+
+'<div class="md-editor"><div class="result-display" style="max-height:400px">'+esc(d.review).replace(/\\n/g,'<br>')+'</div></div>'
|
|
1634
2460
|
+'<div style="font-size:10px;color:var(--text-dim);margin-top:8px;font-family:var(--font-mono)">\u5BA1\u67E5\u65F6\u95F4: '+new Date(d.reviewed_at).toLocaleString()+'</div>'
|
|
1635
2461
|
+'<div class="btn-group"><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u5173\u95ED</button></div>';
|
|
1636
2462
|
}catch(e){toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);ov.remove()}
|
|
1637
2463
|
};
|
|
1638
2464
|
|
|
2465
|
+
window.showEvalDialog=function(jobId){
|
|
2466
|
+
const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
|
|
2467
|
+
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
|
|
2468
|
+
ov.innerHTML='<div class="form-card"><h3>📊 \u751F\u6210\u7EE9\u6548\u8BC4\u4EF7</h3>'
|
|
2469
|
+
+'<div class="fg"><label>\u8BC4\u5206\u4F53\u7CFB</label><select id="eval-system">'
|
|
2470
|
+
+'<option value="ali">\u963F\u91CC\u7EE9\u6548 (3.25 / 3.5 / 3.5+ / 3.75 / 4.0)</option>'
|
|
2471
|
+
+'<option value="letter">SABCD \u7B49\u7EA7 (S / A / B / C / D)</option>'
|
|
2472
|
+
+'<option value="em">EM/MM \u4F53\u7CFB (EM+ / EM / MM+ / MM / MM-)</option>'
|
|
2473
|
+
+'</select></div>'
|
|
2474
|
+
+'<div class="btn-group"><button class="btn btn-primary" onclick="generateEval(\\''+jobId+'\\')">\u751F\u6210\u8BC4\u4EF7</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div></div>';
|
|
2475
|
+
document.body.appendChild(ov);
|
|
2476
|
+
};
|
|
2477
|
+
window.generateEval=async function(jobId){
|
|
2478
|
+
const ratingSystem=document.getElementById('eval-system').value;
|
|
2479
|
+
const ov=document.getElementById('overlay');
|
|
2480
|
+
const fc=ov.querySelector('.form-card');
|
|
2481
|
+
fc.innerHTML='<h3>📊 \u751F\u6210\u7EE9\u6548\u8BC4\u4EF7</h3><div class="spinner-wrap"><div class="spinner"></div><div class="spinner-text">AI \u6B63\u5728\u8BC4\u4F30\u6BCF\u4E2A\u78B3\u57FA\u8282\u70B9\u7684\u8868\u73B0...</div></div>';
|
|
2482
|
+
try{
|
|
2483
|
+
const r=await fetch(API+'/evaluations/generate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({job_id:jobId,rating_system:ratingSystem})});
|
|
2484
|
+
const d=await r.json();
|
|
2485
|
+
if(!r.ok){toast(d.error||'\u8BC4\u4EF7\u751F\u6210\u5931\u8D25',false);ov.remove();return}
|
|
2486
|
+
let h='<h3>📊 \u7EE9\u6548\u8BC4\u4EF7\u7ED3\u679C</h3>';
|
|
2487
|
+
h+='<div style="font-size:11px;color:var(--text-dim);margin-bottom:16px;font-family:var(--font-mono)">\u751F\u6210\u65F6\u95F4: '+new Date(d.generated_at).toLocaleString()+'</div>';
|
|
2488
|
+
const ratingTiers={ali:['4.0','3.75'],letter:['S','A'],em:['EM+','EM']};
|
|
2489
|
+
const lowTiers={ali:['3.25'],letter:['D'],em:['MM-']};
|
|
2490
|
+
for(const ev of d.evaluations){
|
|
2491
|
+
const agent=cachedAgents.find(a=>a.agent_id===ev.agent_id);
|
|
2492
|
+
const isTop=(ratingTiers[ratingSystem]||[]).includes(ev.rating);
|
|
2493
|
+
const isLow=(lowTiers[ratingSystem]||[]).includes(ev.rating);
|
|
2494
|
+
const tier=isTop?'top':isLow?'low':'mid';
|
|
2495
|
+
h+='<div style="padding:12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;margin-bottom:8px">';
|
|
2496
|
+
h+='<div style="display:flex;align-items:center;gap:10px;margin-bottom:6px"><span style="font-weight:600;font-size:13px">'+esc(agent?agent.name:ev.agent_id)+'</span><span class="eval-badge '+tier+'">'+esc(ev.rating)+'</span></div>';
|
|
2497
|
+
h+='<div style="font-size:12px;color:var(--text-dim)">'+esc(ev.comment)+'</div>';
|
|
2498
|
+
h+='<div style="font-size:10px;color:var(--text-dim);margin-top:4px;font-family:var(--font-mono)">\u4EFB\u52A1: '+ev.trace_id+'</div>';
|
|
2499
|
+
h+='</div>';
|
|
2500
|
+
}
|
|
2501
|
+
h+='<div class="btn-group"><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u5173\u95ED</button></div>';
|
|
2502
|
+
fc.innerHTML=h;
|
|
2503
|
+
}catch(e){toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);ov.remove()}
|
|
2504
|
+
};
|
|
2505
|
+
|
|
1639
2506
|
// \u2500\u2500\u2500 Task Detail Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1640
2507
|
window.showTaskDetail=function(traceId){
|
|
1641
2508
|
const t=allTasks.find(x=>x.trace_id===traceId);
|
|
@@ -1656,18 +2523,17 @@ window.showTaskDetail=function(traceId){
|
|
|
1656
2523
|
h+='<div class="detail-row"><div class="detail-label">\u622A\u6B62\u65F6\u95F4</div><div class="detail-value" style="font-family:var(--font-mono)">'+new Date(t.deadline).toLocaleString()+'</div></div>';
|
|
1657
2524
|
|
|
1658
2525
|
if(t.status==='RESOLVED'&&t.result_data){
|
|
1659
|
-
// Show result
|
|
2526
|
+
// Show result + reject option (manager can reject after reviewing)
|
|
1660
2527
|
let resultText='';
|
|
1661
2528
|
if(typeof t.result_data==='object'&&t.result_data){resultText=t.result_data.text||JSON.stringify(t.result_data,null,2)}else{resultText=String(t.result_data||'')}
|
|
1662
|
-
h+='<div class="detail-row"><div class="detail-label">\u4EA4\u4ED8\u7ED3\u679C</div><div class="result-display">'+esc(resultText)+'</div></div>';
|
|
1663
|
-
h+='<div class="btn-group"><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u5173\u95ED</button></div>';
|
|
2529
|
+
h+='<div class="detail-row"><div class="detail-label">\u4EA4\u4ED8\u7ED3\u679C</div><div class="md-editor"><div class="result-display">'+esc(resultText)+'</div></div></div>';
|
|
2530
|
+
h+='<div class="btn-group"><button class="btn btn-danger btn-sm" onclick="taskReject(\\''+t.trace_id+'\\')">\u6253\u56DE\u91CD\u505A (Reject)</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u5173\u95ED</button></div>';
|
|
1664
2531
|
}else{
|
|
1665
|
-
// Input for resume
|
|
1666
|
-
h+='<div class="fg" style="margin-top:16px"><label>\u63D0\u4EA4 Human \u4EA4\u4ED8\u7269</label><textarea id="td-payload" rows="4" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001GitHub PR/Commit URL\u3001\u5DE5\u4F5C\u6C47\u62A5\u7B49..."></textarea><div class="hint">\u652F\u6301\u8D34 GitHub URL\uFF08PR\u3001Commit\u3001Issue\uFF09\uFF0CAI \u5BA1\u67E5\u65F6\u4F1A\u5206\u6790</div></div>';
|
|
2532
|
+
// Input for resume (no reject for unsubmitted tasks)
|
|
2533
|
+
h+='<div class="fg" style="margin-top:16px"><label>\u63D0\u4EA4 Human \u4EA4\u4ED8\u7269</label><div class="md-editor"><textarea id="td-payload" rows="4" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001GitHub PR/Commit URL\u3001\u5DE5\u4F5C\u6C47\u62A5\u7B49..."></textarea><button class="md-expand-btn" onclick="toggleFullscreen(this)" title="\u5C55\u5F00/\u6536\u8D77">⛶</button></div><div class="hint">\u652F\u6301\u8D34 GitHub URL\uFF08PR\u3001Commit\u3001Issue\uFF09\uFF0CAI \u5BA1\u67E5\u65F6\u4F1A\u5206\u6790</div></div>';
|
|
1667
2534
|
h+='<div class="btn-group">';
|
|
1668
2535
|
h+='<button class="btn btn-primary btn-sm" onclick="simulateDelivery(\\''+t.trace_id+'\\')">🤖 \u6A21\u62DF\u4EA4\u4ED8</button>';
|
|
1669
2536
|
h+='<button class="btn btn-green" onclick="taskResume(\\''+t.trace_id+'\\')">\u63D0\u4EA4\u4EA4\u4ED8 (Resume)</button>';
|
|
1670
|
-
h+='<button class="btn btn-danger" onclick="taskReject(\\''+t.trace_id+'\\')">\u6253\u56DE\u91CD\u505A (Reject)</button>';
|
|
1671
2537
|
h+='<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>';
|
|
1672
2538
|
h+='</div>';
|
|
1673
2539
|
}
|
|
@@ -1723,7 +2589,7 @@ function loadTerminal(el){
|
|
|
1723
2589
|
el.innerHTML='<div class="section-hd"><h2>I/O \u4EA4\u4ED8\u7EC8\u7AEF</h2></div>'
|
|
1724
2590
|
+'<div class="form-card" style="margin-top:12px"><h3>> \u63D0\u4EA4\u78B3\u57FA\u8282\u70B9\u4EA7\u51FA</h3>'
|
|
1725
2591
|
+'<div class="fg"><label>Trace ID (\u8FFD\u8E2A\u7801)</label><input id="t-tid" placeholder="\u4F8B: TK-9527"/></div>'
|
|
1726
|
-
+'<div class="fg"><label>\u4EA4\u4ED8\u8F7D\u8377</label><textarea id="t-payload" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001GitHub PR/Commit URL\u3001\u5DE5\u4F5C\u6C47\u62A5\u7B49..."></textarea><div class="hint">\u652F\u6301\u8D34 GitHub URL\uFF08PR\u3001Commit\u3001Issue\uFF09\uFF0CAI \u5BA1\u67E5\u65F6\u4F1A\u5206\u6790</div></div>'
|
|
2592
|
+
+'<div class="fg"><label>\u4EA4\u4ED8\u8F7D\u8377</label><div class="md-editor"><textarea id="t-payload" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001GitHub PR/Commit URL\u3001\u5DE5\u4F5C\u6C47\u62A5\u7B49..."></textarea><button class="md-expand-btn" onclick="toggleFullscreen(this)" title="\u5C55\u5F00/\u6536\u8D77">⛶</button></div><div class="hint">\u652F\u6301\u8D34 GitHub URL\uFF08PR\u3001Commit\u3001Issue\uFF09\uFF0CAI \u5BA1\u67E5\u65F6\u4F1A\u5206\u6790</div></div>'
|
|
1727
2593
|
+'<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>';
|
|
1728
2594
|
}
|
|
1729
2595
|
window.doResume=async function(){
|
|
@@ -1772,8 +2638,8 @@ window.showSettings=async function(){
|
|
|
1772
2638
|
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>';
|
|
1773
2639
|
}
|
|
1774
2640
|
fc.innerHTML='<h3>LLM \u8BBE\u7F6E</h3>'+statusHtml
|
|
1775
|
-
+'<div class="fg"><label
|
|
1776
|
-
+'<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">
|
|
2641
|
+
+'<div class="fg"><label>API \u683C\u5F0F</label><select id="cfg-provider"><option value="anthropic"'+(cfg.provider==='anthropic'||cfg.provider==='claude'?' selected':'')+'>Anthropic Messages API</option><option value="openai"'+(cfg.provider==='openai'?' selected':'')+'>OpenAI Chat Completions API</option><option value="responses"'+(cfg.provider==='responses'?' selected':'')+'>OpenAI Responses API</option></select><div class="hint">\u9009\u62E9 API \u683C\u5F0F\u3002\u81EA\u5B9A\u4E49 Base URL \u53EF\u8FDE\u63A5\u4EFB\u4F55\u517C\u5BB9\u670D\u52A1\u3002</div></div>'
|
|
2642
|
+
+'<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">Anthropic: sk-ant-... | OpenAI: sk-...</div></div>'
|
|
1777
2643
|
+'<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>'
|
|
1778
2644
|
+'<div class="fg"><label>API Base URL <span style="color:var(--text-dim);font-weight:400">(\u53EF\u9009\uFF0C\u7559\u7A7A\u7528\u5B98\u65B9\u5730\u5740)</span></label><input id="cfg-baseurl" value="'+esc(cfg.base_url||'')+'" placeholder="\u4F8B: https://your-proxy.com"/><div class="hint">\u79C1\u6709\u90E8\u7F72: \u586B\u5199\u4F60\u7684\u6A21\u578B\u670D\u52A1\u5730\u5740\uFF0C\u5982 vLLM / Ollama / Azure \u7B49</div></div>'
|
|
1779
2645
|
+'<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>';
|
|
@@ -1839,6 +2705,8 @@ function createServer(port = 2026) {
|
|
|
1839
2705
|
app.use("/api/v1/tasks", tasks_default);
|
|
1840
2706
|
app.use("/api/v1/jobs", sync_default);
|
|
1841
2707
|
app.use("/api/v1/config", config_default);
|
|
2708
|
+
app.use("/api/v1/teams", teams_default);
|
|
2709
|
+
app.use("/api/v1/evaluations", evaluations_default);
|
|
1842
2710
|
const dashboardHtml = getDashboardHtml();
|
|
1843
2711
|
app.get("/", (_req, res) => {
|
|
1844
2712
|
res.type("html").send(dashboardHtml);
|