myaiforone 1.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 +113 -0
- package/agents/_template/CLAUDE.md +18 -0
- package/agents/_template/agent.json +7 -0
- package/agents/platform/agentcreator/CLAUDE.md +300 -0
- package/agents/platform/appcreator/CLAUDE.md +158 -0
- package/agents/platform/gym/CLAUDE.md +486 -0
- package/agents/platform/gym/agent.json +40 -0
- package/agents/platform/gym/programs/agent-building/program.json +160 -0
- package/agents/platform/gym/programs/automations-mastery/program.json +129 -0
- package/agents/platform/gym/programs/getting-started/program.json +124 -0
- package/agents/platform/gym/programs/mcp-integrations/program.json +116 -0
- package/agents/platform/gym/programs/multi-model-strategy/program.json +115 -0
- package/agents/platform/gym/programs/prompt-engineering/program.json +136 -0
- package/agents/platform/gym/souls/alex.md +12 -0
- package/agents/platform/gym/souls/jordan.md +12 -0
- package/agents/platform/gym/souls/morgan.md +12 -0
- package/agents/platform/gym/souls/riley.md +12 -0
- package/agents/platform/gym/souls/sam.md +12 -0
- package/agents/platform/hub/CLAUDE.md +372 -0
- package/agents/platform/promptcreator/CLAUDE.md +130 -0
- package/agents/platform/skillcreator/CLAUDE.md +163 -0
- package/bin/cli.js +566 -0
- package/config.example.json +310 -0
- package/dist/agent-registry.d.ts +32 -0
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js +144 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/channels/discord.d.ts +17 -0
- package/dist/channels/discord.d.ts.map +1 -0
- package/dist/channels/discord.js +114 -0
- package/dist/channels/discord.js.map +1 -0
- package/dist/channels/imessage.d.ts +23 -0
- package/dist/channels/imessage.d.ts.map +1 -0
- package/dist/channels/imessage.js +214 -0
- package/dist/channels/imessage.js.map +1 -0
- package/dist/channels/slack.d.ts +19 -0
- package/dist/channels/slack.d.ts.map +1 -0
- package/dist/channels/slack.js +167 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/telegram.d.ts +19 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +274 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +44 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +18 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +23 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +189 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/config.d.ts +134 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +127 -0
- package/dist/config.js.map +1 -0
- package/dist/cron.d.ts +8 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +35 -0
- package/dist/cron.js.map +1 -0
- package/dist/decrypt-keys.d.ts +7 -0
- package/dist/decrypt-keys.d.ts.map +1 -0
- package/dist/decrypt-keys.js +53 -0
- package/dist/decrypt-keys.js.map +1 -0
- package/dist/encrypt-keys.d.ts +8 -0
- package/dist/encrypt-keys.d.ts.map +1 -0
- package/dist/encrypt-keys.js +62 -0
- package/dist/encrypt-keys.js.map +1 -0
- package/dist/executor.d.ts +31 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +2009 -0
- package/dist/executor.js.map +1 -0
- package/dist/gemini-executor.d.ts +27 -0
- package/dist/gemini-executor.d.ts.map +1 -0
- package/dist/gemini-executor.js +160 -0
- package/dist/gemini-executor.js.map +1 -0
- package/dist/goals.d.ts +24 -0
- package/dist/goals.d.ts.map +1 -0
- package/dist/goals.js +189 -0
- package/dist/goals.js.map +1 -0
- package/dist/gym/activity-digest.d.ts +30 -0
- package/dist/gym/activity-digest.d.ts.map +1 -0
- package/dist/gym/activity-digest.js +506 -0
- package/dist/gym/activity-digest.js.map +1 -0
- package/dist/gym/dimension-scorer.d.ts +76 -0
- package/dist/gym/dimension-scorer.d.ts.map +1 -0
- package/dist/gym/dimension-scorer.js +236 -0
- package/dist/gym/dimension-scorer.js.map +1 -0
- package/dist/gym/gym-router.d.ts +7 -0
- package/dist/gym/gym-router.d.ts.map +1 -0
- package/dist/gym/gym-router.js +718 -0
- package/dist/gym/gym-router.js.map +1 -0
- package/dist/gym/index.d.ts +11 -0
- package/dist/gym/index.d.ts.map +1 -0
- package/dist/gym/index.js +11 -0
- package/dist/gym/index.js.map +1 -0
- package/dist/heartbeat.d.ts +21 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +163 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +254 -0
- package/dist/index.js.map +1 -0
- package/dist/keystore.d.ts +22 -0
- package/dist/keystore.d.ts.map +1 -0
- package/dist/keystore.js +178 -0
- package/dist/keystore.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +45 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/daily.d.ts +22 -0
- package/dist/memory/daily.d.ts.map +1 -0
- package/dist/memory/daily.js +82 -0
- package/dist/memory/daily.js.map +1 -0
- package/dist/memory/embeddings.d.ts +15 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +154 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/index.d.ts +32 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +159 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/search.d.ts +21 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +77 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +23 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +144 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/ollama-executor.d.ts +17 -0
- package/dist/ollama-executor.d.ts.map +1 -0
- package/dist/ollama-executor.js +112 -0
- package/dist/ollama-executor.js.map +1 -0
- package/dist/openai-executor.d.ts +38 -0
- package/dist/openai-executor.d.ts.map +1 -0
- package/dist/openai-executor.js +197 -0
- package/dist/openai-executor.js.map +1 -0
- package/dist/router.d.ts +11 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +185 -0
- package/dist/router.js.map +1 -0
- package/dist/test-message.d.ts +2 -0
- package/dist/test-message.d.ts.map +1 -0
- package/dist/test-message.js +60 -0
- package/dist/test-message.js.map +1 -0
- package/dist/utils/imsg-db-reader.d.ts +24 -0
- package/dist/utils/imsg-db-reader.d.ts.map +1 -0
- package/dist/utils/imsg-db-reader.js +92 -0
- package/dist/utils/imsg-db-reader.js.map +1 -0
- package/dist/utils/imsg-rpc.d.ts +25 -0
- package/dist/utils/imsg-rpc.d.ts.map +1 -0
- package/dist/utils/imsg-rpc.js +149 -0
- package/dist/utils/imsg-rpc.js.map +1 -0
- package/dist/utils/message-formatter.d.ts +3 -0
- package/dist/utils/message-formatter.d.ts.map +1 -0
- package/dist/utils/message-formatter.js +69 -0
- package/dist/utils/message-formatter.js.map +1 -0
- package/dist/web-ui.d.ts +12 -0
- package/dist/web-ui.d.ts.map +1 -0
- package/dist/web-ui.js +5784 -0
- package/dist/web-ui.js.map +1 -0
- package/dist/whatsapp-chats.d.ts +2 -0
- package/dist/whatsapp-chats.d.ts.map +1 -0
- package/dist/whatsapp-chats.js +76 -0
- package/dist/whatsapp-chats.js.map +1 -0
- package/dist/whatsapp-login.d.ts +2 -0
- package/dist/whatsapp-login.d.ts.map +1 -0
- package/dist/whatsapp-login.js +90 -0
- package/dist/whatsapp-login.js.map +1 -0
- package/dist/wiki-sync.d.ts +21 -0
- package/dist/wiki-sync.d.ts.map +1 -0
- package/dist/wiki-sync.js +147 -0
- package/dist/wiki-sync.js.map +1 -0
- package/docs/AddNewAgentGuide.md +100 -0
- package/docs/AddNewMcpGuide.md +72 -0
- package/docs/Architecture.md +795 -0
- package/docs/CLAUDE-AI-SETUP.md +166 -0
- package/docs/Setup.md +297 -0
- package/docs/ai-gym-architecture.md +1040 -0
- package/docs/ai-gym-build-plan.md +343 -0
- package/docs/ai-gym-onboarding.md +122 -0
- package/docs/appcreator_plan.md +348 -0
- package/docs/platform-mcp-audit.md +320 -0
- package/docs/server-deployment-plan.md +503 -0
- package/docs/superpowers/plans/2026-03-25-marketplace.md +1281 -0
- package/docs/superpowers/specs/2026-03-25-marketplace-design.md +287 -0
- package/docs/user-guide.md +2016 -0
- package/mcp-catalog.json +628 -0
- package/package.json +63 -0
- package/public/MyAIforOne-logomark-512.svg +16 -0
- package/public/MyAIforOne-logomark-transparent.svg +15 -0
- package/public/activity.html +314 -0
- package/public/admin.html +1674 -0
- package/public/agent-dashboard.html +670 -0
- package/public/api-docs.html +1106 -0
- package/public/automations.html +722 -0
- package/public/canvas.css +223 -0
- package/public/canvas.js +588 -0
- package/public/changelog.html +231 -0
- package/public/gym.html +2766 -0
- package/public/home.html +1930 -0
- package/public/index.html +2809 -0
- package/public/lab.html +1643 -0
- package/public/library.html +1442 -0
- package/public/marketplace.html +1101 -0
- package/public/mcp-docs.html +441 -0
- package/public/mini.html +390 -0
- package/public/monitor.html +584 -0
- package/public/org.html +4304 -0
- package/public/projects.html +734 -0
- package/public/settings.html +645 -0
- package/public/tasks.html +932 -0
- package/public/trainers/alex.svg +12 -0
- package/public/trainers/jordan.svg +12 -0
- package/public/trainers/morgan.svg +12 -0
- package/public/trainers/riley.svg +12 -0
- package/public/trainers/sam.svg +12 -0
- package/public/user-guide.html +218 -0
- package/registry/agents.json +3 -0
- package/registry/apps.json +20 -0
- package/registry/installed-drafts.json +3 -0
- package/registry/mcps.json +1084 -0
- package/registry/prompts/personal/mcp-test-prompt.md +6 -0
- package/registry/prompts/personal/memory-recall.md +6 -0
- package/registry/prompts/platform/brainstorm.md +15 -0
- package/registry/prompts/platform/code-review.md +16 -0
- package/registry/prompts/platform/explain.md +16 -0
- package/registry/prompts.json +58 -0
- package/registry/skills/external/brainstorming.md +5 -0
- package/registry/skills/external/code-review.md +40 -0
- package/registry/skills/external/frontend-patterns.md +642 -0
- package/registry/skills/external/frontend-slides.md +184 -0
- package/registry/skills/external/systematic-debugging.md +5 -0
- package/registry/skills/external/tdd.md +328 -0
- package/registry/skills/external/verification-before-completion.md +5 -0
- package/registry/skills/external/writing-plans.md +5 -0
- package/registry/skills/platform/ai41_app_build.md +930 -0
- package/registry/skills/platform/ai41_app_deploy.md +168 -0
- package/registry/skills/platform/ai41_app_orchestrator.md +239 -0
- package/registry/skills/platform/ai41_app_patterns.md +359 -0
- package/registry/skills/platform/ai41_app_register.md +85 -0
- package/registry/skills/platform/ai41_app_scaffold.md +421 -0
- package/registry/skills/platform/ai41_app_verify.md +107 -0
- package/registry/skills/platform/opProjectCreate.md +239 -0
- package/registry/skills/platform/op_devbrowser.md +136 -0
- package/registry/skills/platform/sop_brandguidelines.md +103 -0
- package/registry/skills/platform/sop_docx.md +117 -0
- package/registry/skills/platform/sop_frontenddesign.md +44 -0
- package/registry/skills/platform/sop_frontenddesign_v2.md +659 -0
- package/registry/skills/platform/sop_mcpbuilder.md +133 -0
- package/registry/skills/platform/sop_pdf.md +172 -0
- package/registry/skills/platform/sop_pptx.md +133 -0
- package/registry/skills/platform/sop_skillcreator.md +104 -0
- package/registry/skills/platform/sop_themefactory.md +128 -0
- package/registry/skills/platform/sop_webapptesting.md +75 -0
- package/registry/skills/platform/sop_webartifactsbuilder.md +97 -0
- package/registry/skills/platform/sop_xlsx.md +134 -0
- package/registry/skills.json +1055 -0
- package/scripts/discover-chats.sh +11 -0
- package/scripts/install-service-windows.ps1 +87 -0
- package/scripts/install-service.sh +52 -0
- package/scripts/seed-registry.ts +195 -0
- package/scripts/test-send.sh +5 -0
- package/scripts/tray-indicator.ps1 +35 -0
- package/scripts/uninstall-service-windows.ps1 +23 -0
- package/scripts/uninstall-service.sh +15 -0
- package/scripts/xbar-myagent.5s.sh +32 -0
- package/server/mcp-server/dist/index.d.ts +11 -0
- package/server/mcp-server/dist/index.js +1332 -0
- package/server/mcp-server/dist/lib/api-client.d.ts +165 -0
- package/server/mcp-server/dist/lib/api-client.js +241 -0
- package/server/mcp-server/index.ts +1545 -0
- package/server/mcp-server/lib/api-client.ts +366 -0
- package/server/mcp-server/tsconfig.json +14 -0
- package/src/agent-registry.ts +180 -0
- package/src/channels/discord.ts +129 -0
- package/src/channels/imessage.ts +261 -0
- package/src/channels/slack.ts +208 -0
- package/src/channels/telegram.ts +307 -0
- package/src/channels/types.ts +62 -0
- package/src/channels/whatsapp.ts +227 -0
- package/src/config.ts +281 -0
- package/src/cron.ts +43 -0
- package/src/decrypt-keys.ts +60 -0
- package/src/encrypt-keys.ts +70 -0
- package/src/executor.ts +2190 -0
- package/src/gemini-executor.ts +212 -0
- package/src/goals.ts +240 -0
- package/src/gym/activity-digest.ts +546 -0
- package/src/gym/dimension-scorer.ts +297 -0
- package/src/gym/gym-router.ts +801 -0
- package/src/gym/index.ts +19 -0
- package/src/heartbeat.ts +220 -0
- package/src/index.ts +275 -0
- package/src/keystore.ts +190 -0
- package/src/logger.ts +51 -0
- package/src/memory/daily.ts +101 -0
- package/src/memory/embeddings.ts +185 -0
- package/src/memory/index.ts +218 -0
- package/src/memory/search.ts +124 -0
- package/src/memory/store.ts +189 -0
- package/src/ollama-executor.ts +126 -0
- package/src/openai-executor.ts +259 -0
- package/src/router.ts +230 -0
- package/src/test-message.ts +72 -0
- package/src/utils/imsg-db-reader.ts +109 -0
- package/src/utils/imsg-rpc.ts +178 -0
- package/src/utils/message-formatter.ts +90 -0
- package/src/web-ui.ts +5778 -0
- package/src/whatsapp-chats.ts +91 -0
- package/src/whatsapp-login.ts +110 -0
- package/src/wiki-sync.ts +199 -0
- package/tsconfig.json +19 -0
package/public/org.html
ADDED
|
@@ -0,0 +1,4304 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>MyAIforOne — Org</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
10
|
+
|
|
11
|
+
:root{
|
|
12
|
+
--bg-deep:#060a13;
|
|
13
|
+
--bg-surface:rgba(12,18,33,0.92);
|
|
14
|
+
--bg-card:rgba(16,22,40,0.85);
|
|
15
|
+
--bg-input:rgba(0,0,0,0.35);
|
|
16
|
+
--border-dim:rgba(56,189,248,0.08);
|
|
17
|
+
--border-glow:rgba(56,189,248,0.18);
|
|
18
|
+
--border-active:rgba(56,189,248,0.45);
|
|
19
|
+
--text-primary:rgba(255,255,255,0.92);
|
|
20
|
+
--text-secondary:rgba(255,255,255,0.68);
|
|
21
|
+
--text-muted:rgba(148,163,184,0.55);
|
|
22
|
+
--accent:#22d3ee;
|
|
23
|
+
--accent-soft:#38bdf8;
|
|
24
|
+
--accent-bg:rgba(6,182,212,0.15);
|
|
25
|
+
--accent-glow:rgba(34,211,238,0.12);
|
|
26
|
+
--purple:rgba(139,92,246,0.7);
|
|
27
|
+
--purple-bg:rgba(139,92,246,0.12);
|
|
28
|
+
--green:#4ade80;
|
|
29
|
+
--green-bg:rgba(74,222,128,0.1);
|
|
30
|
+
--amber:#fbbf24;
|
|
31
|
+
--amber-bg:rgba(251,191,36,0.1);
|
|
32
|
+
--shadow:0 2px 12px rgba(0,0,0,0.3);
|
|
33
|
+
--shadow-glow:0 0 20px rgba(34,211,238,0.08);
|
|
34
|
+
--radius:12px;
|
|
35
|
+
--font-sans:'DM Sans',system-ui,sans-serif;
|
|
36
|
+
--font-mono:'IBM Plex Mono',monospace;
|
|
37
|
+
--font-display:'Syne',sans-serif;
|
|
38
|
+
--line-color:rgba(56,189,248,0.12);
|
|
39
|
+
--line-glow:rgba(34,211,238,0.25);
|
|
40
|
+
}
|
|
41
|
+
[data-theme="light"]{
|
|
42
|
+
--bg-deep:#f4f6f9;
|
|
43
|
+
--bg-surface:rgba(255,255,255,0.95);
|
|
44
|
+
--bg-card:rgba(255,255,255,0.9);
|
|
45
|
+
--bg-input:rgba(0,0,0,0.04);
|
|
46
|
+
--border-dim:rgba(0,0,0,0.08);
|
|
47
|
+
--border-glow:rgba(14,116,144,0.18);
|
|
48
|
+
--border-active:rgba(14,116,144,0.45);
|
|
49
|
+
--text-primary:rgba(15,23,42,0.92);
|
|
50
|
+
--text-secondary:rgba(51,65,85,0.8);
|
|
51
|
+
--text-muted:rgba(100,116,139,0.6);
|
|
52
|
+
--accent:#0891b2;
|
|
53
|
+
--accent-soft:#0e7490;
|
|
54
|
+
--accent-bg:rgba(14,116,144,0.08);
|
|
55
|
+
--accent-glow:rgba(14,116,144,0.06);
|
|
56
|
+
--purple:rgba(109,40,217,0.75);
|
|
57
|
+
--purple-bg:rgba(139,92,246,0.08);
|
|
58
|
+
--green:#16a34a;
|
|
59
|
+
--green-bg:rgba(22,163,74,0.08);
|
|
60
|
+
--amber:#d97706;
|
|
61
|
+
--amber-bg:rgba(217,119,6,0.08);
|
|
62
|
+
--shadow:0 1px 8px rgba(0,0,0,0.06);
|
|
63
|
+
--shadow-glow:none;
|
|
64
|
+
--line-color:rgba(14,116,144,0.12);
|
|
65
|
+
--line-glow:rgba(14,116,144,0.2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
html,body{
|
|
69
|
+
width:100%;height:100%;overflow:hidden;
|
|
70
|
+
background:var(--bg-deep);font-family:var(--font-sans);color:var(--text-primary);
|
|
71
|
+
transition:background .3s,color .3s;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ─── Top Bar ─────────────────────────────────────────────────── */
|
|
75
|
+
.topbar{
|
|
76
|
+
height:56px;display:flex;align-items:center;padding:0 24px;
|
|
77
|
+
background:var(--bg-surface);border-bottom:1px solid var(--border-dim);
|
|
78
|
+
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
|
|
79
|
+
position:fixed;top:0;left:0;right:0;z-index:100;
|
|
80
|
+
overflow:hidden;max-width:100vw;
|
|
81
|
+
}
|
|
82
|
+
.topbar-logo{display:flex;align-items:center;gap:10px;margin-right:32px}
|
|
83
|
+
.logo-mark{width:28px;height:28px;border-radius:8px;object-fit:contain}
|
|
84
|
+
.logo-text{font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--accent)}
|
|
85
|
+
|
|
86
|
+
.tab-group{display:flex;gap:0;flex-shrink:1;min-width:0;overflow-x:auto}
|
|
87
|
+
.tab-btn{
|
|
88
|
+
font-family:var(--font-sans);font-size:13px;font-weight:600;
|
|
89
|
+
color:var(--text-muted);background:none;border:none;
|
|
90
|
+
padding:16px 14px;cursor:pointer;position:relative;transition:color .2s;
|
|
91
|
+
text-decoration:none;white-space:nowrap;flex-shrink:0;
|
|
92
|
+
}
|
|
93
|
+
.tab-btn:hover{color:var(--text-secondary)}
|
|
94
|
+
.tab-btn.active{color:var(--accent)}
|
|
95
|
+
.tab-btn.active::after{
|
|
96
|
+
content:'';position:absolute;bottom:0;left:12px;right:12px;
|
|
97
|
+
height:2px;background:var(--accent);border-radius:1px;
|
|
98
|
+
}
|
|
99
|
+
.sub-nav{position:fixed;top:56px;left:0;right:0;z-index:99;height:36px;display:flex;align-items:center;gap:2px;padding:0 24px;background:var(--bg-surface);border-bottom:1px solid var(--border-dim);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}
|
|
100
|
+
.sub-nav-link{font-family:var(--font-sans);font-size:12px;font-weight:500;color:var(--text-muted);text-decoration:none;padding:4px 10px;border-radius:5px;transition:color .2s}
|
|
101
|
+
.sub-nav-link:hover{color:var(--text-secondary)}
|
|
102
|
+
.sub-nav-link.active{color:var(--accent)}
|
|
103
|
+
|
|
104
|
+
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:8px;flex-shrink:0}
|
|
105
|
+
.gym-nav-btn{font-family:var(--font-sans);font-size:12px;font-weight:700;color:#fff;background:var(--accent);border:none;border-radius:8px;padding:6px 14px;cursor:pointer;text-decoration:none;letter-spacing:.02em;transition:opacity .2s,transform .2s;white-space:nowrap}
|
|
106
|
+
.gym-nav-btn:hover{opacity:.88;transform:translateY(-1px)}
|
|
107
|
+
.topbar-control{
|
|
108
|
+
font-family:var(--font-sans);font-size:11px;font-weight:500;
|
|
109
|
+
padding:6px 12px;border-radius:8px;
|
|
110
|
+
border:1px solid var(--border-dim);background:var(--bg-input);
|
|
111
|
+
color:var(--text-primary);outline:none;cursor:pointer;
|
|
112
|
+
transition:border-color .2s;height:32px;box-sizing:border-box;
|
|
113
|
+
}
|
|
114
|
+
.topbar-control:hover,.topbar-control:focus{border-color:var(--border-glow)}
|
|
115
|
+
.org-select{
|
|
116
|
+
font-family:var(--font-sans);font-size:11px;font-weight:500;
|
|
117
|
+
padding:6px 12px;border-radius:8px;
|
|
118
|
+
border:1px solid var(--border-dim);background:var(--bg-input);
|
|
119
|
+
color:var(--text-primary);outline:none;cursor:pointer;
|
|
120
|
+
height:32px;box-sizing:border-box;transition:border-color .2s;
|
|
121
|
+
}
|
|
122
|
+
.org-select:hover,.org-select:focus{border-color:var(--border-glow)}
|
|
123
|
+
.add-agent-btn-top{
|
|
124
|
+
font-family:var(--font-sans);font-size:12px;font-weight:600;
|
|
125
|
+
padding:7px 16px;border-radius:8px;cursor:pointer;
|
|
126
|
+
border:1px dashed var(--border-glow);background:transparent;
|
|
127
|
+
color:var(--text-muted);transition:all .2s;
|
|
128
|
+
display:flex;align-items:center;gap:6px;
|
|
129
|
+
}
|
|
130
|
+
.add-agent-btn-top:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
|
|
131
|
+
.theme-toggle,.gear-btn{
|
|
132
|
+
width:34px;height:34px;border-radius:8px;border:1px solid var(--border-dim);
|
|
133
|
+
background:transparent;color:var(--text-muted);cursor:pointer;
|
|
134
|
+
display:flex;align-items:center;justify-content:center;font-size:16px;
|
|
135
|
+
transition:all .2s;
|
|
136
|
+
text-decoration:none;}
|
|
137
|
+
.theme-toggle:hover,.gear-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
138
|
+
.docs-dropdown{position:relative;display:inline-block}
|
|
139
|
+
.docs-menu{display:none;position:absolute;top:40px;right:0;min-width:160px;background:var(--bg-surface);border:1px solid var(--border-glow);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.4);z-index:200;padding:4px;backdrop-filter:blur(20px)}
|
|
140
|
+
.docs-menu.open{display:block}
|
|
141
|
+
.docs-menu a{display:flex;align-items:center;gap:8px;padding:7px 12px;font-size:12px;font-weight:500;color:var(--text-secondary);text-decoration:none;border-radius:6px;transition:all .15s}
|
|
142
|
+
.docs-menu a:hover{background:var(--accent-bg);color:var(--accent)}
|
|
143
|
+
.docs-menu a .dm-icon{font-size:14px;width:18px;text-align:center}
|
|
144
|
+
/* Tooltip for icon buttons */
|
|
145
|
+
.gear-btn,.theme-toggle,.select-mode-btn,.hide-names-btn{position:relative}
|
|
146
|
+
.gear-btn::after,.theme-toggle::after{
|
|
147
|
+
content:attr(title);position:absolute;top:calc(100% + 6px);left:50%;transform:translateX(-50%);
|
|
148
|
+
background:var(--bg-card);color:var(--text);border:1px solid var(--border-dim);
|
|
149
|
+
padding:4px 10px;border-radius:6px;font-size:11px;white-space:nowrap;
|
|
150
|
+
pointer-events:none;opacity:0;transition:opacity .15s;z-index:100;
|
|
151
|
+
font-family:var(--font-mono);
|
|
152
|
+
}
|
|
153
|
+
.gear-btn:hover::after,.theme-toggle:hover::after{opacity:1}
|
|
154
|
+
|
|
155
|
+
.hidden{display:none!important}
|
|
156
|
+
|
|
157
|
+
/* ─── Sub-nav agent controls ─────────────────────────────────── */
|
|
158
|
+
.sub-nav .agent-controls{display:flex;align-items:center;gap:6px;margin-left:auto}
|
|
159
|
+
.sub-nav .agent-controls .topbar-control{height:26px;font-size:10px;padding:4px 8px}
|
|
160
|
+
.sub-nav .agent-controls .class-dropdown-btn{font-size:10px;padding:4px 8px;height:26px}
|
|
161
|
+
.sub-nav .agent-controls .org-select{height:26px;font-size:10px;padding:2px 8px}
|
|
162
|
+
.sub-nav .agent-controls .account-select{height:26px;font-size:10px;padding:2px 8px}
|
|
163
|
+
.sub-nav .agent-controls .add-agent-btn-top{font-size:10px;padding:4px 10px}
|
|
164
|
+
.sub-nav .agent-controls button{font-size:10px;padding:3px 8px}
|
|
165
|
+
|
|
166
|
+
/* ─── Canvas ──────────────────────────────────────────────────── */
|
|
167
|
+
.canvas{
|
|
168
|
+
position:fixed;top:92px;left:0;right:0;bottom:0;
|
|
169
|
+
overflow:auto;padding:40px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ─── Function Box (department grouping) ─────────────────────── */
|
|
173
|
+
.org-grid{
|
|
174
|
+
display:flex;flex-wrap:wrap;justify-content:center;gap:32px;padding:20px;
|
|
175
|
+
}
|
|
176
|
+
.function-box{
|
|
177
|
+
background:var(--bg-card);border:1px solid var(--border-glow);
|
|
178
|
+
border-radius:16px;padding:0;min-width:280px;max-width:520px;
|
|
179
|
+
backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
|
|
180
|
+
box-shadow:var(--shadow);overflow:hidden;
|
|
181
|
+
transition:all .3s;
|
|
182
|
+
}
|
|
183
|
+
.function-box:hover{
|
|
184
|
+
box-shadow:var(--shadow),var(--shadow-glow);
|
|
185
|
+
}
|
|
186
|
+
.function-header{
|
|
187
|
+
padding:14px 20px;border-bottom:1px solid var(--border-dim);
|
|
188
|
+
display:flex;align-items:center;gap:10px;
|
|
189
|
+
}
|
|
190
|
+
.function-header-icon{
|
|
191
|
+
width:28px;height:28px;border-radius:7px;
|
|
192
|
+
background:var(--purple-bg);border:1px solid var(--purple);
|
|
193
|
+
display:flex;align-items:center;justify-content:center;
|
|
194
|
+
font-family:var(--font-mono);font-size:11px;font-weight:600;color:var(--purple);
|
|
195
|
+
}
|
|
196
|
+
.function-header-name{
|
|
197
|
+
font-family:var(--font-display);font-size:13px;font-weight:700;
|
|
198
|
+
text-transform:uppercase;letter-spacing:.06em;color:var(--purple);
|
|
199
|
+
}
|
|
200
|
+
.function-header-count{
|
|
201
|
+
margin-left:auto;font-family:var(--font-mono);font-size:10px;color:var(--text-muted);
|
|
202
|
+
}
|
|
203
|
+
.function-body{padding:16px}
|
|
204
|
+
.function-body .agent-node{margin-bottom:12px}
|
|
205
|
+
.function-body .agent-node:last-child{margin-bottom:0}
|
|
206
|
+
.function-body .agent-node.is-lead{
|
|
207
|
+
border-color:var(--accent);
|
|
208
|
+
}
|
|
209
|
+
.function-body .agent-node.is-lead .node-title::after{
|
|
210
|
+
content:' ★';color:var(--amber);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Connector between function boxes */
|
|
214
|
+
.inter-box-connectors{
|
|
215
|
+
width:100%;position:relative;display:flex;justify-content:center;
|
|
216
|
+
margin:8px 0;
|
|
217
|
+
}
|
|
218
|
+
.connector-label{
|
|
219
|
+
font-family:var(--font-mono);font-size:9px;color:var(--text-muted);
|
|
220
|
+
background:var(--bg-deep);padding:2px 10px;border-radius:4px;
|
|
221
|
+
border:1px solid var(--border-dim);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* ─── Agent Node ──────────────────────────────────────────────── */
|
|
225
|
+
.agent-node{
|
|
226
|
+
padding:16px;position:relative;
|
|
227
|
+
background:var(--bg-surface);border:1px solid var(--border-dim);
|
|
228
|
+
border-radius:12px;
|
|
229
|
+
box-shadow:var(--shadow);cursor:pointer;
|
|
230
|
+
transition:all .3s;display:flex;align-items:center;gap:14px;
|
|
231
|
+
}
|
|
232
|
+
.agent-node:hover{
|
|
233
|
+
border-color:var(--border-active);
|
|
234
|
+
box-shadow:0 4px 24px rgba(0,0,0,0.4),0 0 24px rgba(34,211,238,0.08);
|
|
235
|
+
transform:translateY(-1px);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.node-avatar{
|
|
239
|
+
width:42px;height:42px;border-radius:12px;flex-shrink:0;
|
|
240
|
+
background:var(--accent-bg);border:2px solid var(--accent);
|
|
241
|
+
display:flex;align-items:center;justify-content:center;
|
|
242
|
+
font-family:var(--font-mono);font-size:14px;font-weight:600;color:var(--accent);
|
|
243
|
+
position:relative;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Heartbeat pulse for active agents — heart, top-right */
|
|
247
|
+
.node-avatar.active::before{
|
|
248
|
+
content:'❤';position:absolute;top:-8px;right:-8px;
|
|
249
|
+
font-size:14px;line-height:1;
|
|
250
|
+
color:var(--green);opacity:0;
|
|
251
|
+
filter:drop-shadow(0 0 4px rgba(74,222,128,0.5));
|
|
252
|
+
animation:heartbeat 1.5s ease-in-out infinite;
|
|
253
|
+
}
|
|
254
|
+
@keyframes heartbeat{
|
|
255
|
+
0%{transform:scale(1);opacity:.3}
|
|
256
|
+
15%{transform:scale(1.3);opacity:1}
|
|
257
|
+
30%{transform:scale(1);opacity:.3}
|
|
258
|
+
45%{transform:scale(1.2);opacity:.8}
|
|
259
|
+
60%{transform:scale(1);opacity:.3}
|
|
260
|
+
100%{opacity:.3}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Spinning circle for agents with active automations (goals/schedules) */
|
|
264
|
+
.node-avatar.automated::after{
|
|
265
|
+
content:'';position:absolute;top:-10px;right:6px;
|
|
266
|
+
width:12px;height:12px;
|
|
267
|
+
border:2px solid transparent;
|
|
268
|
+
border-top:2px solid var(--green);
|
|
269
|
+
border-right:2px solid var(--green);
|
|
270
|
+
border-radius:50%;
|
|
271
|
+
animation:automationSpin 1.2s linear infinite;
|
|
272
|
+
filter:drop-shadow(0 0 4px rgba(74,222,128,0.6));
|
|
273
|
+
}
|
|
274
|
+
@keyframes automationSpin{
|
|
275
|
+
0%{transform:rotate(0deg)}
|
|
276
|
+
100%{transform:rotate(360deg)}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.node-info{flex:1;min-width:0}
|
|
280
|
+
.node-title{
|
|
281
|
+
font-family:var(--font-display);font-size:10px;font-weight:700;
|
|
282
|
+
text-transform:uppercase;letter-spacing:.1em;color:var(--accent);
|
|
283
|
+
margin-bottom:1px;
|
|
284
|
+
}
|
|
285
|
+
.node-name{font-family:var(--font-display);font-size:14px;font-weight:700;margin-bottom:1px}
|
|
286
|
+
.node-alias{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);margin-bottom:4px}
|
|
287
|
+
/* ─── Org Title Banner ────────────────────────────────────────── */
|
|
288
|
+
.org-title-banner{
|
|
289
|
+
font-family:var(--font-display);font-size:15px;font-weight:700;
|
|
290
|
+
text-align:left;padding:20px 20px 8px;
|
|
291
|
+
color:var(--accent);letter-spacing:.04em;
|
|
292
|
+
border-bottom:1px solid var(--border-glow);
|
|
293
|
+
margin:0 20px 16px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* ─── Connector line between hierarchy levels ─────────────────── */
|
|
297
|
+
.level-connector{
|
|
298
|
+
width:2px;height:40px;margin:0 auto;position:relative;
|
|
299
|
+
background:var(--line-color);
|
|
300
|
+
overflow:hidden;
|
|
301
|
+
}
|
|
302
|
+
.level-connector::after{
|
|
303
|
+
content:'';position:absolute;top:-100%;left:0;width:100%;height:100%;
|
|
304
|
+
background:linear-gradient(to bottom,transparent,var(--line-glow),transparent);
|
|
305
|
+
animation:flowDown 2s ease-in-out infinite;
|
|
306
|
+
}
|
|
307
|
+
@keyframes flowDown{
|
|
308
|
+
0%{top:-100%}
|
|
309
|
+
100%{top:100%}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.node-func-tag{
|
|
313
|
+
font-family:var(--font-mono);font-size:9px;font-weight:500;
|
|
314
|
+
padding:2px 8px;border-radius:4px;
|
|
315
|
+
background:var(--purple-bg);color:var(--purple);
|
|
316
|
+
border:1px solid rgba(139,92,246,0.15);
|
|
317
|
+
}
|
|
318
|
+
.node-stats{
|
|
319
|
+
display:flex;gap:10px;margin-top:4px;
|
|
320
|
+
font-family:var(--font-mono);font-size:9px;color:var(--text-muted);
|
|
321
|
+
}
|
|
322
|
+
.node-actions{
|
|
323
|
+
display:flex;flex-direction:column;gap:4px;
|
|
324
|
+
opacity:0;transition:opacity .2s;flex-shrink:0;
|
|
325
|
+
}
|
|
326
|
+
.agent-node:hover .node-actions{opacity:1}
|
|
327
|
+
.node-btn{
|
|
328
|
+
font-family:var(--font-mono);font-size:10px;font-weight:500;
|
|
329
|
+
padding:5px 12px;border-radius:6px;cursor:pointer;
|
|
330
|
+
border:1px solid var(--border-dim);background:transparent;
|
|
331
|
+
color:var(--text-muted);transition:all .2s;white-space:nowrap;
|
|
332
|
+
}
|
|
333
|
+
.node-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
|
|
334
|
+
|
|
335
|
+
/* ─── Flat Grid (All Agents view) ────────────────────────────── */
|
|
336
|
+
.flat-grid{
|
|
337
|
+
display:flex;flex-wrap:wrap;justify-content:center;gap:20px;padding:20px;
|
|
338
|
+
}
|
|
339
|
+
.flat-grid .agent-node{
|
|
340
|
+
width:300px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* ─── View Toggle ─────────────────────────────────────────────── */
|
|
344
|
+
.view-toggle{display:flex;gap:2px;background:var(--bg-input);border-radius:6px;padding:2px;border:1px solid var(--border-dim)}
|
|
345
|
+
.view-toggle-btn{
|
|
346
|
+
width:26px;height:22px;border:none;background:transparent;color:var(--text-muted);
|
|
347
|
+
cursor:pointer;border-radius:4px;display:flex;align-items:center;justify-content:center;
|
|
348
|
+
font-size:12px;transition:all .15s;padding:0;
|
|
349
|
+
}
|
|
350
|
+
.view-toggle-btn:hover{color:var(--text-secondary)}
|
|
351
|
+
.view-toggle-btn.active{background:var(--accent-bg);color:var(--accent);border:1px solid rgba(34,211,238,0.2)}
|
|
352
|
+
|
|
353
|
+
/* ─── Compact Grid ────────────────────────────────────────────── */
|
|
354
|
+
.compact-grid{
|
|
355
|
+
display:flex;flex-wrap:wrap;gap:8px;padding:20px;
|
|
356
|
+
}
|
|
357
|
+
.compact-node{
|
|
358
|
+
display:flex;align-items:center;gap:10px;padding:8px 12px;
|
|
359
|
+
background:var(--bg-surface);border:1px solid var(--border-dim);
|
|
360
|
+
border-radius:8px;cursor:pointer;transition:all .2s;
|
|
361
|
+
width:240px;position:relative;
|
|
362
|
+
}
|
|
363
|
+
.compact-node:hover{border-color:var(--border-active);box-shadow:0 2px 12px rgba(0,0,0,0.2)}
|
|
364
|
+
.compact-node .node-avatar{width:28px;height:28px;border-radius:8px;font-size:10px;flex-shrink:0}
|
|
365
|
+
.compact-node .compact-info{flex:1;min-width:0}
|
|
366
|
+
.compact-node .compact-name{font-family:var(--font-sans);font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
367
|
+
.compact-node .compact-alias{font-family:var(--font-mono);font-size:9px;color:var(--text-muted)}
|
|
368
|
+
.compact-node .compact-status{width:6px;height:6px;border-radius:50%;flex-shrink:0}
|
|
369
|
+
.compact-node .compact-status.active{background:var(--green)}
|
|
370
|
+
.compact-node .compact-status.inactive{background:var(--text-muted);opacity:.4}
|
|
371
|
+
.compact-node .compact-menu{
|
|
372
|
+
opacity:0;transition:opacity .15s;font-size:14px;color:var(--text-muted);
|
|
373
|
+
cursor:pointer;padding:2px 4px;border:none;background:none;
|
|
374
|
+
}
|
|
375
|
+
.compact-node:hover .compact-menu{opacity:1}
|
|
376
|
+
.compact-node .select-check{top:4px;left:4px;width:16px;height:16px;border-radius:3px}
|
|
377
|
+
.compact-node .select-check:checked::after{font-size:11px}
|
|
378
|
+
|
|
379
|
+
/* Compact context menu */
|
|
380
|
+
.compact-ctx{position:absolute;top:100%;right:0;z-index:200;min-width:120px;background:var(--bg-surface);border:1px solid var(--border-glow);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.4);padding:4px;display:none}
|
|
381
|
+
.compact-ctx.open{display:block}
|
|
382
|
+
.compact-ctx a{display:block;padding:6px 10px;font-size:11px;font-weight:500;color:var(--text-secondary);text-decoration:none;border-radius:5px;cursor:pointer}
|
|
383
|
+
.compact-ctx a:hover{background:var(--accent-bg);color:var(--accent)}
|
|
384
|
+
|
|
385
|
+
/* ─── List/Table View ─────────────────────────────────────────── */
|
|
386
|
+
.list-view{padding:20px}
|
|
387
|
+
.list-table{width:100%;border-collapse:separate;border-spacing:0}
|
|
388
|
+
.list-table th{
|
|
389
|
+
position:sticky;top:0;z-index:10;
|
|
390
|
+
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
391
|
+
text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);
|
|
392
|
+
padding:8px 12px;text-align:left;cursor:pointer;user-select:none;
|
|
393
|
+
background:var(--bg-deep);border-bottom:1px solid var(--border-glow);
|
|
394
|
+
white-space:nowrap;transition:color .15s;
|
|
395
|
+
}
|
|
396
|
+
.list-table th:hover{color:var(--accent)}
|
|
397
|
+
.list-table th .sort-arrow{font-size:9px;margin-left:4px;opacity:.4}
|
|
398
|
+
.list-table th.sorted .sort-arrow{opacity:1;color:var(--accent)}
|
|
399
|
+
.list-table td{
|
|
400
|
+
font-size:12px;padding:8px 12px;
|
|
401
|
+
border-bottom:1px solid var(--border-dim);
|
|
402
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px;
|
|
403
|
+
}
|
|
404
|
+
.list-table tr{cursor:pointer;transition:background .15s}
|
|
405
|
+
.list-table tr:hover td{background:var(--accent-bg)}
|
|
406
|
+
.list-table tr .row-avatar{
|
|
407
|
+
width:24px;height:24px;border-radius:6px;font-size:9px;
|
|
408
|
+
background:var(--accent-bg);border:1px solid var(--accent);
|
|
409
|
+
display:inline-flex;align-items:center;justify-content:center;
|
|
410
|
+
font-family:var(--font-mono);font-weight:600;color:var(--accent);
|
|
411
|
+
vertical-align:middle;margin-right:8px;
|
|
412
|
+
}
|
|
413
|
+
.list-table tr .row-status{
|
|
414
|
+
display:inline-block;width:7px;height:7px;border-radius:50%;vertical-align:middle;
|
|
415
|
+
}
|
|
416
|
+
.list-table tr .row-status.active{background:var(--green)}
|
|
417
|
+
.list-table tr .row-status.inactive{background:var(--text-muted);opacity:.3}
|
|
418
|
+
.list-table tr .select-check{position:static;display:none;width:16px;height:16px;vertical-align:middle;border-radius:3px}
|
|
419
|
+
.list-table tr .select-check:checked::after{font-size:11px}
|
|
420
|
+
.select-mode .list-table tr .select-check{display:inline-block}
|
|
421
|
+
|
|
422
|
+
/* ─── Bench ──────────────────────────────────────────────────── */
|
|
423
|
+
.bench{
|
|
424
|
+
margin-top:48px;padding-top:24px;
|
|
425
|
+
border-top:1px solid var(--border-dim);text-align:center;
|
|
426
|
+
}
|
|
427
|
+
.bench-label{
|
|
428
|
+
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
429
|
+
text-transform:uppercase;letter-spacing:.14em;color:var(--text-muted);
|
|
430
|
+
margin-bottom:16px;
|
|
431
|
+
}
|
|
432
|
+
.bench-grid{display:flex;flex-wrap:wrap;justify-content:center;gap:16px}
|
|
433
|
+
.bench-grid .agent-node{width:280px}
|
|
434
|
+
|
|
435
|
+
/* ─── Empty State ─────────────────────────────────────────────── */
|
|
436
|
+
.empty-org{
|
|
437
|
+
text-align:center;padding:80px 20px;color:var(--text-muted);
|
|
438
|
+
}
|
|
439
|
+
.empty-org-icon{font-size:48px;opacity:.3;margin-bottom:12px}
|
|
440
|
+
.empty-org-text{font-size:14px;font-weight:500;margin-bottom:4px}
|
|
441
|
+
.empty-org-sub{font-size:12px;opacity:.6}
|
|
442
|
+
|
|
443
|
+
/* ─── Hide Names (presentation mode) ────────────────────────── */
|
|
444
|
+
.name-blurred{filter:blur(8px);-webkit-filter:blur(8px);transition:filter .2s}
|
|
445
|
+
.class-dropdown{position:relative;display:inline-block}
|
|
446
|
+
.class-dropdown-btn{padding:6px 12px;border-radius:8px;border:1px solid var(--border-dim);background:var(--bg-input);color:var(--text-primary);font-family:var(--font-sans);font-size:11px;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:4px;white-space:nowrap;height:32px;box-sizing:border-box;transition:border-color .2s}
|
|
447
|
+
.class-dropdown-btn:hover{border-color:var(--border-glow)}
|
|
448
|
+
.class-dropdown-menu{display:none;position:absolute;top:100%;left:0;margin-top:4px;padding:6px 0;background:var(--bg-card);border:1px solid var(--border-dim);border-radius:8px;box-shadow:var(--shadow);z-index:100;min-width:150px}
|
|
449
|
+
.class-dropdown-menu.open{display:block}
|
|
450
|
+
.class-dropdown-item{display:flex;align-items:center;gap:8px;padding:6px 14px;font-size:11px;cursor:pointer;color:var(--text-muted);transition:all .15s}
|
|
451
|
+
.class-dropdown-item:hover{background:var(--bg-surface);color:var(--text-primary)}
|
|
452
|
+
.class-dropdown-item .check{width:14px;font-size:12px;color:var(--accent)}
|
|
453
|
+
.hide-names-btn{
|
|
454
|
+
margin-left:auto;font-family:var(--font-mono);font-size:11px;font-weight:500;
|
|
455
|
+
padding:4px 10px;border-radius:6px;cursor:pointer;
|
|
456
|
+
border:1px solid var(--border-dim);background:transparent;
|
|
457
|
+
color:var(--text-muted);transition:all .2s;display:flex;align-items:center;gap:5px;
|
|
458
|
+
white-space:nowrap;
|
|
459
|
+
}
|
|
460
|
+
.hide-names-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
461
|
+
.hide-names-btn.active{border-color:var(--amber);color:var(--amber);background:var(--amber-bg)}
|
|
462
|
+
|
|
463
|
+
/* ─── Account Badge ──────────────────────────────────────────── */
|
|
464
|
+
.account-badge{
|
|
465
|
+
display:inline-block;font-family:var(--font-mono);font-size:9px;font-weight:600;
|
|
466
|
+
padding:1px 6px;border-radius:4px;white-space:nowrap;line-height:1.4;
|
|
467
|
+
border:1px solid;opacity:.85;
|
|
468
|
+
}
|
|
469
|
+
.agent-node > .account-badge,
|
|
470
|
+
.compact-node > .account-badge{
|
|
471
|
+
position:absolute;top:6px;right:6px;font-size:8px;padding:0 5px;opacity:.7;
|
|
472
|
+
}
|
|
473
|
+
.agent-node:hover > .account-badge,
|
|
474
|
+
.compact-node:hover > .account-badge{opacity:1}
|
|
475
|
+
.account-badge-0{color:#22d3ee;border-color:rgba(34,211,238,0.3);background:rgba(34,211,238,0.1)}
|
|
476
|
+
.account-badge-1{color:#a78bfa;border-color:rgba(167,139,250,0.3);background:rgba(167,139,250,0.1)}
|
|
477
|
+
.account-badge-2{color:#4ade80;border-color:rgba(74,222,128,0.3);background:rgba(74,222,128,0.1)}
|
|
478
|
+
.account-badge-3{color:#fb923c;border-color:rgba(251,146,60,0.3);background:rgba(251,146,60,0.1)}
|
|
479
|
+
.account-badge-4{color:#f472b6;border-color:rgba(244,114,182,0.3);background:rgba(244,114,182,0.1)}
|
|
480
|
+
.account-badge-5{color:#fbbf24;border-color:rgba(251,191,36,0.3);background:rgba(251,191,36,0.1)}
|
|
481
|
+
[data-theme="light"] .account-badge-0{color:#0891b2;border-color:rgba(8,145,178,0.3);background:rgba(8,145,178,0.08)}
|
|
482
|
+
[data-theme="light"] .account-badge-1{color:#7c3aed;border-color:rgba(124,58,237,0.3);background:rgba(124,58,237,0.08)}
|
|
483
|
+
[data-theme="light"] .account-badge-2{color:#16a34a;border-color:rgba(22,163,74,0.3);background:rgba(22,163,74,0.08)}
|
|
484
|
+
[data-theme="light"] .account-badge-3{color:#ea580c;border-color:rgba(234,88,12,0.3);background:rgba(234,88,12,0.08)}
|
|
485
|
+
[data-theme="light"] .account-badge-4{color:#db2777;border-color:rgba(219,39,119,0.3);background:rgba(219,39,119,0.08)}
|
|
486
|
+
[data-theme="light"] .account-badge-5{color:#d97706;border-color:rgba(217,119,6,0.3);background:rgba(217,119,6,0.08)}
|
|
487
|
+
.account-select{
|
|
488
|
+
height:32px;font-family:var(--font-sans);font-size:11px;font-weight:500;
|
|
489
|
+
padding:2px 8px;border-radius:8px;border:1px solid var(--border-dim);
|
|
490
|
+
background:var(--bg-input);color:var(--text-primary);cursor:pointer;
|
|
491
|
+
box-sizing:border-box;transition:border-color .2s;
|
|
492
|
+
}
|
|
493
|
+
.account-select:hover{border-color:var(--border-glow)}
|
|
494
|
+
.account-group-banner{
|
|
495
|
+
padding:8px 24px;font-family:var(--font-display);font-size:14px;font-weight:700;
|
|
496
|
+
color:var(--text-secondary);display:flex;align-items:center;gap:10px;
|
|
497
|
+
margin-top:16px;
|
|
498
|
+
}
|
|
499
|
+
.account-group-dot{width:8px;height:8px;border-radius:50%;display:inline-block}
|
|
500
|
+
|
|
501
|
+
/* ─── Modal ───────────────────────────────────────────────────── */
|
|
502
|
+
.modal-overlay{
|
|
503
|
+
position:fixed;inset:0;z-index:1000;
|
|
504
|
+
background:rgba(0,0,0,0.6);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
|
|
505
|
+
display:none;align-items:center;justify-content:center;
|
|
506
|
+
animation:fadeIn .2s ease-out;
|
|
507
|
+
}
|
|
508
|
+
.modal-overlay.show{display:flex}
|
|
509
|
+
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
|
510
|
+
|
|
511
|
+
.modal{
|
|
512
|
+
width:580px;max-width:94vw;max-height:88vh;overflow-y:auto;
|
|
513
|
+
background:var(--bg-surface);border:1px solid var(--border-glow);
|
|
514
|
+
border-radius:16px;padding:0;
|
|
515
|
+
box-shadow:0 8px 40px rgba(0,0,0,0.4),0 0 60px rgba(34,211,238,0.06);
|
|
516
|
+
animation:modalIn .25s ease-out;
|
|
517
|
+
}
|
|
518
|
+
@keyframes modalIn{from{opacity:0;transform:scale(.96) translateY(8px)}to{opacity:1;transform:none}}
|
|
519
|
+
|
|
520
|
+
.modal-header{
|
|
521
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
522
|
+
padding:20px 24px 16px;border-bottom:1px solid var(--border-dim);
|
|
523
|
+
}
|
|
524
|
+
.modal-title{font-family:var(--font-display);font-size:18px;font-weight:700}
|
|
525
|
+
.modal-close{
|
|
526
|
+
width:32px;height:32px;border-radius:8px;border:1px solid var(--border-dim);
|
|
527
|
+
background:transparent;color:var(--text-muted);cursor:pointer;
|
|
528
|
+
display:flex;align-items:center;justify-content:center;font-size:16px;
|
|
529
|
+
transition:all .2s;
|
|
530
|
+
}
|
|
531
|
+
.modal-close:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
532
|
+
|
|
533
|
+
/* Modal tabs */
|
|
534
|
+
.modal-tabs{
|
|
535
|
+
display:flex;border-bottom:1px solid var(--border-dim);padding:0 24px;
|
|
536
|
+
}
|
|
537
|
+
.modal-tab{
|
|
538
|
+
font-family:var(--font-sans);font-size:13px;font-weight:600;
|
|
539
|
+
color:var(--text-muted);background:none;border:none;
|
|
540
|
+
padding:12px 16px;cursor:pointer;position:relative;transition:color .2s;
|
|
541
|
+
}
|
|
542
|
+
.modal-tab:hover{color:var(--text-secondary)}
|
|
543
|
+
.modal-tab.active{color:var(--accent)}
|
|
544
|
+
.modal-tab.active::after{
|
|
545
|
+
content:'';position:absolute;bottom:-1px;left:8px;right:8px;
|
|
546
|
+
height:2px;background:var(--accent);border-radius:1px;
|
|
547
|
+
}
|
|
548
|
+
.modal-tab-content{display:none}
|
|
549
|
+
.modal-tab-content.active{display:block}
|
|
550
|
+
|
|
551
|
+
.modal-body{padding:20px 24px}
|
|
552
|
+
|
|
553
|
+
.form-group{margin-bottom:16px}
|
|
554
|
+
.form-label{
|
|
555
|
+
display:block;font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
556
|
+
text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);
|
|
557
|
+
margin-bottom:6px;
|
|
558
|
+
}
|
|
559
|
+
.form-input{
|
|
560
|
+
width:100%;padding:10px 14px;border-radius:10px;
|
|
561
|
+
border:1px solid var(--border-dim);background:var(--bg-input);
|
|
562
|
+
color:var(--text-primary);font-family:var(--font-sans);font-size:13px;
|
|
563
|
+
outline:none;transition:border-color .2s;
|
|
564
|
+
}
|
|
565
|
+
.form-input:focus{border-color:var(--accent)}
|
|
566
|
+
.form-input::placeholder{color:var(--text-muted)}
|
|
567
|
+
.form-input:read-only{opacity:.6;cursor:not-allowed}
|
|
568
|
+
.form-hint{font-size:11px;color:var(--text-muted);margin-top:4px}
|
|
569
|
+
|
|
570
|
+
.form-row{display:flex;gap:12px}
|
|
571
|
+
.form-row .form-group{flex:1}
|
|
572
|
+
|
|
573
|
+
.form-section{
|
|
574
|
+
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
575
|
+
text-transform:uppercase;letter-spacing:.12em;color:var(--accent);
|
|
576
|
+
margin:20px 0 12px;padding-bottom:6px;
|
|
577
|
+
border-bottom:1px solid var(--border-dim);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.checkbox-group{display:flex;flex-wrap:wrap;gap:6px}
|
|
581
|
+
.checkbox-pill{
|
|
582
|
+
font-family:var(--font-mono);font-size:10px;font-weight:500;
|
|
583
|
+
padding:5px 10px;border-radius:6px;cursor:pointer;
|
|
584
|
+
border:1px solid var(--border-dim);background:transparent;
|
|
585
|
+
color:var(--text-muted);transition:all .2s;user-select:none;
|
|
586
|
+
}
|
|
587
|
+
.checkbox-pill:hover{border-color:var(--border-glow);color:var(--text-secondary)}
|
|
588
|
+
.checkbox-pill.checked{
|
|
589
|
+
border-color:var(--accent);background:var(--accent-bg);color:var(--accent);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.modal-footer{
|
|
593
|
+
display:flex;justify-content:space-between;align-items:center;
|
|
594
|
+
padding:16px 24px 20px;border-top:1px solid var(--border-dim);
|
|
595
|
+
}
|
|
596
|
+
.modal-footer-left{display:flex;flex-direction:column;gap:4px}
|
|
597
|
+
.modal-footer-hint{font-size:11px;color:var(--text-muted)}
|
|
598
|
+
.btn-delete{
|
|
599
|
+
padding:6px 14px;border-radius:8px;border:1px solid rgba(239,68,68,0.3);
|
|
600
|
+
background:rgba(239,68,68,0.08);color:#ef4444;cursor:pointer;
|
|
601
|
+
font-family:var(--font-sans);font-size:11px;font-weight:600;
|
|
602
|
+
transition:all .2s;align-self:flex-start;
|
|
603
|
+
}
|
|
604
|
+
.btn-delete:hover{background:rgba(239,68,68,0.15);border-color:rgba(239,68,68,0.5)}
|
|
605
|
+
/* Bulk action bar */
|
|
606
|
+
.bulk-bar{
|
|
607
|
+
position:fixed;bottom:0;left:0;right:0;z-index:1500;
|
|
608
|
+
background:var(--bg-card);border-top:2px solid var(--accent);
|
|
609
|
+
padding:12px 24px;display:flex;align-items:center;gap:12px;
|
|
610
|
+
box-shadow:0 -4px 24px rgba(0,0,0,0.4);
|
|
611
|
+
}
|
|
612
|
+
.bulk-bar.hidden{display:none}
|
|
613
|
+
.bulk-bar #bulkCount{font-weight:600;color:var(--accent);min-width:100px}
|
|
614
|
+
.bulk-btn{padding:6px 16px;border-radius:6px;cursor:pointer;font-size:12px;border:1px solid var(--border-dim);background:var(--bg-surface);color:var(--text)}
|
|
615
|
+
.bulk-btn:hover{border-color:var(--accent)}
|
|
616
|
+
.bulk-delete{background:#dc2626;color:#fff;border-color:#dc2626}
|
|
617
|
+
.bulk-delete:hover{background:#b91c1c}
|
|
618
|
+
.bulk-cancel{background:transparent;color:var(--text-dim)}
|
|
619
|
+
/* Select mode checkbox on cards */
|
|
620
|
+
.select-check{
|
|
621
|
+
position:absolute;top:8px;left:8px;width:20px;height:20px;
|
|
622
|
+
border-radius:4px;border:2px solid var(--border-dim);background:var(--bg-surface);
|
|
623
|
+
cursor:pointer;display:none;z-index:10;
|
|
624
|
+
appearance:none;-webkit-appearance:none;outline:none;
|
|
625
|
+
}
|
|
626
|
+
.select-check:checked{background:var(--accent);border-color:var(--accent)}
|
|
627
|
+
.select-check:checked::after{content:'✓';color:#fff;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;height:100%}
|
|
628
|
+
.select-mode .select-check{display:block}
|
|
629
|
+
.select-mode .agent-node{user-select:none}
|
|
630
|
+
.select-mode .agent-node.selected{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent)}
|
|
631
|
+
.select-mode-btn.active{background:var(--accent)!important;color:#000!important;border-color:var(--accent)!important}
|
|
632
|
+
.delete-overlay{
|
|
633
|
+
position:fixed;top:0;left:0;right:0;bottom:0;
|
|
634
|
+
background:rgba(0,0,0,0.7);backdrop-filter:blur(4px);
|
|
635
|
+
display:flex;align-items:center;justify-content:center;z-index:2000;
|
|
636
|
+
}
|
|
637
|
+
.delete-overlay.hidden{display:none}
|
|
638
|
+
.delete-modal{
|
|
639
|
+
background:var(--bg-surface);border:1px solid rgba(239,68,68,0.3);
|
|
640
|
+
border-radius:16px;padding:32px;max-width:440px;width:90%;
|
|
641
|
+
box-shadow:0 8px 32px rgba(239,68,68,0.1);
|
|
642
|
+
}
|
|
643
|
+
.delete-warning-icon{font-size:48px;text-align:center;margin-bottom:8px}
|
|
644
|
+
.delete-title{font-size:18px;font-weight:700;text-align:center;color:#ef4444;margin-bottom:16px}
|
|
645
|
+
.delete-body{font-size:13px;color:var(--text-secondary);line-height:1.6}
|
|
646
|
+
.delete-body ul{margin:8px 0 12px 20px}
|
|
647
|
+
.delete-body li{margin:4px 0}
|
|
648
|
+
.delete-irreversible{color:#ef4444;font-weight:600;margin:12px 0}
|
|
649
|
+
.delete-alias-hint{
|
|
650
|
+
text-align:center;font-family:var(--font-mono);font-size:16px;font-weight:700;
|
|
651
|
+
color:var(--text-primary);margin:8px 0;padding:6px;
|
|
652
|
+
background:var(--bg-input);border-radius:6px;
|
|
653
|
+
}
|
|
654
|
+
.delete-input{
|
|
655
|
+
width:100%;padding:10px 14px;border:2px solid var(--border-dim);border-radius:8px;
|
|
656
|
+
background:var(--bg-input);color:var(--text-primary);
|
|
657
|
+
font-family:var(--font-mono);font-size:14px;text-align:center;
|
|
658
|
+
transition:border-color .2s;margin-top:8px;
|
|
659
|
+
}
|
|
660
|
+
.delete-input:focus{outline:none;border-color:rgba(239,68,68,0.5)}
|
|
661
|
+
.delete-actions{display:flex;gap:12px;margin-top:20px;justify-content:flex-end}
|
|
662
|
+
.btn-cancel{
|
|
663
|
+
padding:10px 20px;border-radius:10px;border:1px solid var(--border-dim);
|
|
664
|
+
background:transparent;color:var(--text-secondary);cursor:pointer;
|
|
665
|
+
font-family:var(--font-sans);font-size:13px;font-weight:500;
|
|
666
|
+
}
|
|
667
|
+
.btn-cancel:hover{border-color:var(--border-glow);color:var(--text-primary)}
|
|
668
|
+
.btn-confirm-delete{
|
|
669
|
+
padding:10px 20px;border-radius:10px;border:none;
|
|
670
|
+
background:#ef4444;color:#fff;cursor:pointer;
|
|
671
|
+
font-family:var(--font-sans);font-size:13px;font-weight:700;
|
|
672
|
+
transition:all .2s;opacity:0.4;
|
|
673
|
+
}
|
|
674
|
+
.btn-confirm-delete:disabled{cursor:not-allowed;opacity:0.4}
|
|
675
|
+
.btn-confirm-delete:not(:disabled){opacity:1}
|
|
676
|
+
.btn-confirm-delete:not(:disabled):hover{background:#dc2626}
|
|
677
|
+
.mcp-key-card{
|
|
678
|
+
display:flex;align-items:center;gap:10px;
|
|
679
|
+
padding:10px 14px;margin:6px 0;border-radius:10px;
|
|
680
|
+
background:var(--bg-input);border:1px solid var(--border-dim);
|
|
681
|
+
font-size:13px;transition:border-color .2s;
|
|
682
|
+
}
|
|
683
|
+
.mcp-key-card.connected{border-color:rgba(74,222,128,0.3)}
|
|
684
|
+
.mcp-key-name{font-weight:600;color:var(--text-primary);min-width:100px}
|
|
685
|
+
.mcp-key-status{font-size:11px;padding:2px 8px;border-radius:6px;font-weight:500}
|
|
686
|
+
.mcp-key-status.ok{background:var(--green-bg);color:var(--green)}
|
|
687
|
+
.mcp-key-status.missing{background:var(--amber-bg);color:var(--amber)}
|
|
688
|
+
.mcp-key-input{
|
|
689
|
+
flex:1;padding:6px 10px;border-radius:6px;border:1px solid var(--border-dim);
|
|
690
|
+
background:var(--bg-input);color:var(--text-primary);
|
|
691
|
+
font-family:var(--font-mono);font-size:11px;
|
|
692
|
+
}
|
|
693
|
+
.mcp-key-input:focus{outline:none;border-color:var(--accent)}
|
|
694
|
+
.mcp-key-btn{
|
|
695
|
+
padding:4px 12px;border-radius:6px;border:1px solid var(--border-glow);
|
|
696
|
+
background:var(--accent-bg);color:var(--accent);cursor:pointer;
|
|
697
|
+
font-size:11px;font-weight:600;white-space:nowrap;transition:all .2s;
|
|
698
|
+
}
|
|
699
|
+
.mcp-key-btn:hover{background:var(--accent);color:#000}
|
|
700
|
+
.mcp-key-btn.oauth{border-color:rgba(66,133,244,0.3);background:rgba(66,133,244,0.1);color:#4285F4}
|
|
701
|
+
.mcp-key-btn.oauth:hover{background:#4285F4;color:#fff}
|
|
702
|
+
.mcp-key-btn.disconnect{border-color:rgba(239,68,68,0.3);background:rgba(239,68,68,0.08);color:#ef4444;font-size:10px}
|
|
703
|
+
.mcp-key-btn.disconnect:hover{background:#ef4444;color:#fff}
|
|
704
|
+
.mcp-accordion{margin:4px 0;border-radius:10px;border:1px solid var(--border-dim);overflow:hidden;transition:border-color .2s}
|
|
705
|
+
.mcp-accordion:hover{border-color:var(--border-glow)}
|
|
706
|
+
.mcp-accordion-header{
|
|
707
|
+
display:flex;align-items:center;gap:8px;padding:10px 14px;cursor:pointer;
|
|
708
|
+
background:var(--bg-input);user-select:none;transition:background .2s;
|
|
709
|
+
}
|
|
710
|
+
.mcp-accordion-header:hover{background:rgba(139,92,246,0.05)}
|
|
711
|
+
.mcp-accordion-arrow{font-size:10px;color:var(--text-muted);transition:transform .2s;width:12px}
|
|
712
|
+
.mcp-accordion.open .mcp-accordion-arrow{transform:rotate(90deg)}
|
|
713
|
+
.mcp-accordion-name{font-weight:600;font-size:13px;color:var(--text-primary);flex:1}
|
|
714
|
+
.mcp-accordion-summary{font-size:11px;color:var(--text-muted);font-family:var(--font-mono)}
|
|
715
|
+
.mcp-accordion-body{display:none;padding:4px 14px 10px;background:var(--bg-deep)}
|
|
716
|
+
.mcp-accordion.open .mcp-accordion-body{display:block}
|
|
717
|
+
.mcp-category-label{
|
|
718
|
+
font-family:var(--font-mono);font-size:11px;font-weight:600;
|
|
719
|
+
color:var(--accent-soft);margin:10px 0 4px;padding:0 4px;
|
|
720
|
+
letter-spacing:0.03em;
|
|
721
|
+
}
|
|
722
|
+
.mcp-auth-modal{
|
|
723
|
+
width:420px;max-width:92vw;
|
|
724
|
+
background:var(--bg-surface);border:1px solid var(--border-glow);
|
|
725
|
+
border-radius:16px;padding:0;
|
|
726
|
+
box-shadow:0 8px 40px rgba(0,0,0,0.4),0 0 60px rgba(34,211,238,0.06);
|
|
727
|
+
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
|
|
728
|
+
animation:modalIn .25s ease-out;
|
|
729
|
+
}
|
|
730
|
+
.mcp-auth-modal .modal-header{
|
|
731
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
732
|
+
padding:20px 24px 16px;border-bottom:1px solid var(--border-dim);
|
|
733
|
+
}
|
|
734
|
+
.mcp-auth-modal .modal-title{font-family:var(--font-display);font-size:16px;font-weight:700}
|
|
735
|
+
.mcp-auth-modal .mcp-auth-body{padding:20px 24px}
|
|
736
|
+
.mcp-auth-modal .mcp-auth-body .form-group{margin-bottom:14px}
|
|
737
|
+
.mcp-auth-modal .mcp-auth-body .form-label{
|
|
738
|
+
display:block;font-size:12px;font-weight:600;color:var(--text-secondary);margin-bottom:6px;
|
|
739
|
+
}
|
|
740
|
+
.mcp-auth-modal .mcp-auth-body .form-input{
|
|
741
|
+
width:100%;padding:8px 12px;border-radius:8px;
|
|
742
|
+
border:1px solid var(--border-dim);background:var(--bg-input);
|
|
743
|
+
color:var(--text-primary);font-family:var(--font-mono);font-size:12px;
|
|
744
|
+
transition:border-color .2s;
|
|
745
|
+
}
|
|
746
|
+
.mcp-auth-modal .mcp-auth-body .form-input:focus{outline:none;border-color:var(--accent)}
|
|
747
|
+
.mcp-auth-modal .mcp-auth-actions{
|
|
748
|
+
display:flex;justify-content:flex-end;gap:10px;
|
|
749
|
+
padding:16px 24px;border-top:1px solid var(--border-dim);
|
|
750
|
+
}
|
|
751
|
+
.mcp-auth-modal .btn-cancel{
|
|
752
|
+
padding:8px 18px;border-radius:8px;border:1px solid var(--border-dim);
|
|
753
|
+
background:transparent;color:var(--text-secondary);cursor:pointer;
|
|
754
|
+
font-size:13px;font-weight:500;transition:all .2s;
|
|
755
|
+
}
|
|
756
|
+
.mcp-auth-modal .btn-cancel:hover{border-color:var(--border-glow);color:var(--text-primary)}
|
|
757
|
+
.mcp-auth-modal .btn-save{
|
|
758
|
+
padding:8px 18px;border-radius:8px;border:none;
|
|
759
|
+
background:var(--accent);color:#000;cursor:pointer;
|
|
760
|
+
font-size:13px;font-weight:600;transition:all .2s;
|
|
761
|
+
}
|
|
762
|
+
.mcp-auth-modal .btn-save:hover{filter:brightness(1.1)}
|
|
763
|
+
.mcp-auth-modal .btn-danger{
|
|
764
|
+
padding:8px 18px;border-radius:8px;border:none;
|
|
765
|
+
background:#ef4444;color:#fff;cursor:pointer;
|
|
766
|
+
font-size:13px;font-weight:600;transition:all .2s;
|
|
767
|
+
}
|
|
768
|
+
.mcp-auth-modal .btn-danger:hover{filter:brightness(1.1)}
|
|
769
|
+
.mcp-auth-modal .mcp-auth-hint{
|
|
770
|
+
font-size:11px;color:var(--text-muted);margin-top:4px;
|
|
771
|
+
}
|
|
772
|
+
.mcp-auth-modal .mcp-oauth-link{
|
|
773
|
+
display:inline-flex;align-items:center;gap:6px;
|
|
774
|
+
padding:6px 14px;border-radius:6px;
|
|
775
|
+
border:1px solid rgba(66,133,244,0.3);background:rgba(66,133,244,0.1);color:#4285F4;
|
|
776
|
+
cursor:pointer;font-size:12px;font-weight:600;text-decoration:none;transition:all .2s;
|
|
777
|
+
margin-bottom:12px;
|
|
778
|
+
}
|
|
779
|
+
.mcp-auth-modal .mcp-oauth-link:hover{background:#4285F4;color:#fff}
|
|
780
|
+
.btn-save{
|
|
781
|
+
padding:10px 24px;border-radius:10px;border:none;
|
|
782
|
+
background:var(--accent);color:#fff;cursor:pointer;
|
|
783
|
+
font-family:var(--font-sans);font-size:13px;font-weight:600;
|
|
784
|
+
transition:all .2s;
|
|
785
|
+
}
|
|
786
|
+
.btn-save:hover{filter:brightness(1.1);transform:scale(1.02)}
|
|
787
|
+
.btn-save:disabled{opacity:.5;cursor:not-allowed;transform:none}
|
|
788
|
+
|
|
789
|
+
/* Route & org entry rows */
|
|
790
|
+
.route-entry,.org-entry{
|
|
791
|
+
display:flex;gap:8px;align-items:center;margin-bottom:8px;
|
|
792
|
+
}
|
|
793
|
+
.route-entry select,.route-entry input,.org-entry input{
|
|
794
|
+
padding:8px 10px;border-radius:8px;
|
|
795
|
+
border:1px solid var(--border-dim);background:var(--bg-input);
|
|
796
|
+
color:var(--text-primary);font-family:var(--font-mono);font-size:12px;
|
|
797
|
+
outline:none;
|
|
798
|
+
}
|
|
799
|
+
.route-entry select{width:110px}
|
|
800
|
+
.route-entry input{flex:1}
|
|
801
|
+
.org-entry input{flex:1}
|
|
802
|
+
.entry-remove{
|
|
803
|
+
width:28px;height:28px;border-radius:6px;border:1px solid var(--border-dim);
|
|
804
|
+
background:transparent;color:var(--text-muted);cursor:pointer;flex-shrink:0;
|
|
805
|
+
display:flex;align-items:center;justify-content:center;font-size:14px;
|
|
806
|
+
}
|
|
807
|
+
.entry-remove:hover{border-color:rgba(239,68,68,.3);color:#ef4444}
|
|
808
|
+
.add-entry-btn{
|
|
809
|
+
font-family:var(--font-mono);font-size:11px;color:var(--accent);
|
|
810
|
+
background:none;border:none;cursor:pointer;padding:4px 0;
|
|
811
|
+
}
|
|
812
|
+
.add-entry-btn:hover{text-decoration:underline}
|
|
813
|
+
|
|
814
|
+
/* Mention checkbox */
|
|
815
|
+
.mention-cb{
|
|
816
|
+
display:flex;align-items:center;gap:4px;
|
|
817
|
+
font-family:var(--font-mono);font-size:10px;color:var(--text-muted);
|
|
818
|
+
flex-shrink:0;cursor:pointer;
|
|
819
|
+
}
|
|
820
|
+
.mention-cb input{cursor:pointer}
|
|
821
|
+
|
|
822
|
+
/* Schedule entry */
|
|
823
|
+
.schedule-entry{
|
|
824
|
+
padding:14px;margin-bottom:10px;
|
|
825
|
+
background:var(--bg-input);border:1px solid var(--border-dim);
|
|
826
|
+
border-radius:10px;position:relative;
|
|
827
|
+
}
|
|
828
|
+
.schedule-entry .entry-remove{position:absolute;top:10px;right:10px}
|
|
829
|
+
.schedule-row{display:flex;gap:8px;align-items:center;margin-bottom:8px;flex-wrap:wrap}
|
|
830
|
+
.schedule-select{
|
|
831
|
+
padding:7px 10px;border-radius:7px;
|
|
832
|
+
border:1px solid var(--border-dim);background:var(--bg-deep);
|
|
833
|
+
color:var(--text-primary);font-family:var(--font-mono);font-size:11px;
|
|
834
|
+
outline:none;cursor:pointer;
|
|
835
|
+
}
|
|
836
|
+
.schedule-label{font-family:var(--font-sans);font-size:12px;color:var(--text-secondary)}
|
|
837
|
+
.schedule-msg{
|
|
838
|
+
width:100%;padding:8px 12px;border-radius:8px;
|
|
839
|
+
border:1px solid var(--border-dim);background:var(--bg-deep);
|
|
840
|
+
color:var(--text-primary);font-family:var(--font-sans);font-size:12px;
|
|
841
|
+
outline:none;resize:none;min-height:36px;
|
|
842
|
+
}
|
|
843
|
+
.schedule-msg:focus,.schedule-select:focus{border-color:var(--accent)}
|
|
844
|
+
.schedule-preview{
|
|
845
|
+
font-family:var(--font-mono);font-size:10px;color:var(--text-muted);
|
|
846
|
+
margin-top:6px;
|
|
847
|
+
}
|
|
848
|
+
.no-schedules,.no-goals{
|
|
849
|
+
text-align:center;padding:24px;color:var(--text-muted);font-size:13px;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/* Goal entry */
|
|
853
|
+
.goal-entry{
|
|
854
|
+
padding:14px;margin-bottom:10px;
|
|
855
|
+
background:var(--bg-input);border:1px solid var(--border-dim);
|
|
856
|
+
border-radius:10px;position:relative;
|
|
857
|
+
}
|
|
858
|
+
.goal-entry .entry-remove{position:absolute;top:10px;right:10px}
|
|
859
|
+
.goal-row{display:flex;gap:8px;align-items:center;margin-bottom:8px;flex-wrap:wrap}
|
|
860
|
+
.goal-input{
|
|
861
|
+
width:100%;padding:8px 12px;border-radius:8px;
|
|
862
|
+
border:1px solid var(--border-dim);background:var(--bg-deep);
|
|
863
|
+
color:var(--text-primary);font-family:var(--font-sans);font-size:12px;
|
|
864
|
+
outline:none;box-sizing:border-box;
|
|
865
|
+
}
|
|
866
|
+
.goal-input:focus{border-color:var(--accent)}
|
|
867
|
+
.goal-input::placeholder{color:var(--text-muted)}
|
|
868
|
+
.goal-textarea{
|
|
869
|
+
width:100%;padding:8px 12px;border-radius:8px;
|
|
870
|
+
border:1px solid var(--border-dim);background:var(--bg-deep);
|
|
871
|
+
color:var(--text-primary);font-family:var(--font-sans);font-size:12px;
|
|
872
|
+
outline:none;resize:none;min-height:48px;
|
|
873
|
+
}
|
|
874
|
+
.goal-textarea:focus{border-color:var(--accent)}
|
|
875
|
+
.goal-textarea::placeholder{color:var(--text-muted)}
|
|
876
|
+
.goal-label{font-family:var(--font-mono);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);margin-bottom:4px;display:block}
|
|
877
|
+
.goal-toggle{
|
|
878
|
+
display:flex;align-items:center;gap:6px;cursor:pointer;
|
|
879
|
+
font-family:var(--font-mono);font-size:11px;color:var(--text-secondary);
|
|
880
|
+
}
|
|
881
|
+
.goal-toggle input{cursor:pointer}
|
|
882
|
+
.goal-select{
|
|
883
|
+
padding:7px 10px;border-radius:7px;
|
|
884
|
+
border:1px solid var(--border-dim);background:var(--bg-deep);
|
|
885
|
+
color:var(--text-primary);font-family:var(--font-mono);font-size:11px;
|
|
886
|
+
outline:none;cursor:pointer;
|
|
887
|
+
}
|
|
888
|
+
.goal-select:focus{border-color:var(--accent)}
|
|
889
|
+
|
|
890
|
+
/* ─── Channel Cards ──────────────────────────────────────────── */
|
|
891
|
+
.channels-grid{
|
|
892
|
+
display:flex;flex-wrap:wrap;justify-content:center;gap:24px;padding:20px;
|
|
893
|
+
}
|
|
894
|
+
.channel-card{
|
|
895
|
+
width:480px;max-width:94vw;
|
|
896
|
+
background:var(--bg-card);border:1px solid var(--border-glow);
|
|
897
|
+
border-radius:16px;overflow:hidden;
|
|
898
|
+
backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
|
|
899
|
+
box-shadow:var(--shadow);transition:all .3s;
|
|
900
|
+
}
|
|
901
|
+
.channel-card:hover{box-shadow:var(--shadow),var(--shadow-glow)}
|
|
902
|
+
.channel-card-header{
|
|
903
|
+
display:flex;align-items:center;gap:12px;
|
|
904
|
+
padding:16px 20px;border-bottom:1px solid var(--border-dim);
|
|
905
|
+
}
|
|
906
|
+
.channel-card-icon{
|
|
907
|
+
width:36px;height:36px;border-radius:10px;
|
|
908
|
+
background:var(--accent-bg);border:1px solid var(--accent);
|
|
909
|
+
display:flex;align-items:center;justify-content:center;
|
|
910
|
+
font-size:16px;flex-shrink:0;
|
|
911
|
+
}
|
|
912
|
+
.channel-card-name{
|
|
913
|
+
font-family:var(--font-display);font-size:15px;font-weight:700;
|
|
914
|
+
text-transform:capitalize;
|
|
915
|
+
}
|
|
916
|
+
.channel-status-pill{
|
|
917
|
+
margin-left:auto;font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
918
|
+
padding:3px 10px;border-radius:20px;text-transform:uppercase;letter-spacing:.06em;
|
|
919
|
+
}
|
|
920
|
+
.channel-status-pill.enabled{background:var(--green-bg);color:var(--green);border:1px solid rgba(74,222,128,0.2)}
|
|
921
|
+
.channel-status-pill.disabled{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(251,191,36,0.2)}
|
|
922
|
+
|
|
923
|
+
.channel-card-body{padding:16px 20px}
|
|
924
|
+
.channel-setting-row{
|
|
925
|
+
display:flex;align-items:center;gap:10px;margin-bottom:10px;
|
|
926
|
+
}
|
|
927
|
+
.channel-setting-label{
|
|
928
|
+
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
929
|
+
text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);
|
|
930
|
+
min-width:100px;flex-shrink:0;
|
|
931
|
+
}
|
|
932
|
+
.channel-setting-input{
|
|
933
|
+
padding:6px 10px;border-radius:8px;
|
|
934
|
+
border:1px solid var(--border-dim);background:var(--bg-input);
|
|
935
|
+
color:var(--text-primary);font-family:var(--font-mono);font-size:12px;
|
|
936
|
+
outline:none;transition:border-color .2s;
|
|
937
|
+
}
|
|
938
|
+
.channel-setting-input:focus{border-color:var(--accent)}
|
|
939
|
+
.channel-setting-select{
|
|
940
|
+
padding:6px 10px;border-radius:8px;
|
|
941
|
+
border:1px solid var(--border-dim);background:var(--bg-input);
|
|
942
|
+
color:var(--text-primary);font-family:var(--font-mono);font-size:12px;
|
|
943
|
+
outline:none;cursor:pointer;
|
|
944
|
+
}
|
|
945
|
+
.channel-save-btn{
|
|
946
|
+
font-family:var(--font-sans);font-size:11px;font-weight:600;
|
|
947
|
+
padding:6px 16px;border-radius:8px;border:none;
|
|
948
|
+
background:var(--accent);color:#fff;cursor:pointer;
|
|
949
|
+
transition:all .2s;margin-top:4px;
|
|
950
|
+
}
|
|
951
|
+
.channel-save-btn:hover{filter:brightness(1.1)}
|
|
952
|
+
.channel-save-btn:disabled{opacity:.5;cursor:not-allowed}
|
|
953
|
+
|
|
954
|
+
.channel-section-title{
|
|
955
|
+
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
956
|
+
text-transform:uppercase;letter-spacing:.1em;color:var(--accent);
|
|
957
|
+
margin:14px 0 8px;padding-bottom:4px;
|
|
958
|
+
border-bottom:1px solid var(--border-dim);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.channel-agent-row{
|
|
962
|
+
display:flex;align-items:center;gap:8px;
|
|
963
|
+
padding:6px 0;font-size:12px;
|
|
964
|
+
}
|
|
965
|
+
.channel-agent-name{
|
|
966
|
+
font-family:var(--font-sans);font-weight:600;color:var(--text-primary);
|
|
967
|
+
}
|
|
968
|
+
.channel-agent-alias{
|
|
969
|
+
font-family:var(--font-mono);font-size:10px;color:var(--text-muted);
|
|
970
|
+
}
|
|
971
|
+
.channel-agent-chatid{
|
|
972
|
+
font-family:var(--font-mono);font-size:10px;color:var(--text-secondary);
|
|
973
|
+
margin-left:auto;
|
|
974
|
+
}
|
|
975
|
+
.channel-agent-remove{
|
|
976
|
+
width:22px;height:22px;border-radius:5px;border:1px solid var(--border-dim);
|
|
977
|
+
background:transparent;color:var(--text-muted);cursor:pointer;flex-shrink:0;
|
|
978
|
+
display:flex;align-items:center;justify-content:center;font-size:12px;
|
|
979
|
+
transition:all .2s;
|
|
980
|
+
}
|
|
981
|
+
.channel-agent-remove:hover{border-color:rgba(239,68,68,.3);color:#ef4444}
|
|
982
|
+
|
|
983
|
+
.channel-add-row{
|
|
984
|
+
display:flex;gap:6px;align-items:center;margin-top:8px;
|
|
985
|
+
}
|
|
986
|
+
.channel-add-select,.channel-add-input{
|
|
987
|
+
padding:6px 8px;border-radius:7px;
|
|
988
|
+
border:1px solid var(--border-dim);background:var(--bg-input);
|
|
989
|
+
color:var(--text-primary);font-family:var(--font-mono);font-size:11px;
|
|
990
|
+
outline:none;
|
|
991
|
+
}
|
|
992
|
+
.channel-add-select{width:140px}
|
|
993
|
+
.channel-add-input{flex:1;min-width:80px}
|
|
994
|
+
.channel-add-btn{
|
|
995
|
+
font-family:var(--font-mono);font-size:11px;color:var(--accent);
|
|
996
|
+
background:none;border:1px solid var(--accent);border-radius:7px;
|
|
997
|
+
padding:5px 10px;cursor:pointer;white-space:nowrap;transition:all .2s;
|
|
998
|
+
}
|
|
999
|
+
.channel-add-btn:hover{background:var(--accent-bg)}
|
|
1000
|
+
|
|
1001
|
+
.monitored-ids{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}
|
|
1002
|
+
.monitored-pill{
|
|
1003
|
+
font-family:var(--font-mono);font-size:10px;
|
|
1004
|
+
padding:3px 8px;border-radius:5px;
|
|
1005
|
+
background:var(--bg-input);border:1px solid var(--border-dim);
|
|
1006
|
+
color:var(--text-secondary);display:flex;align-items:center;gap:4px;
|
|
1007
|
+
}
|
|
1008
|
+
.monitored-pill button{
|
|
1009
|
+
background:none;border:none;color:var(--text-muted);cursor:pointer;
|
|
1010
|
+
font-size:11px;padding:0 2px;transition:color .2s;
|
|
1011
|
+
}
|
|
1012
|
+
.monitored-pill button:hover{color:#ef4444}
|
|
1013
|
+
.channel-restart-note{
|
|
1014
|
+
font-family:var(--font-mono);font-size:9px;color:var(--text-muted);
|
|
1015
|
+
margin-top:8px;opacity:.7;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/* ─── Scrollbar ───────────────────────────────────────────────── */
|
|
1019
|
+
::-webkit-scrollbar{width:6px}
|
|
1020
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
1021
|
+
::-webkit-scrollbar-thumb{background:var(--border-glow);border-radius:3px}
|
|
1022
|
+
</style>
|
|
1023
|
+
</head>
|
|
1024
|
+
<body>
|
|
1025
|
+
|
|
1026
|
+
<div class="topbar">
|
|
1027
|
+
<a href="/" style="display:flex;align-items:center;gap:10px;margin-right:28px;text-decoration:none">
|
|
1028
|
+
<img class="logo-mark" src="/MyAIforOne-logomark-transparent.svg" alt="MyAIforOne">
|
|
1029
|
+
<span class="logo-text">MyAIforOne</span>
|
|
1030
|
+
</a>
|
|
1031
|
+
<div class="tab-group">
|
|
1032
|
+
<a class="tab-btn" href="/">Home</a>
|
|
1033
|
+
<a class="tab-btn" href="/org" id="tabAgents">Agents</a>
|
|
1034
|
+
<a class="tab-btn" href="/ui">Chat</a>
|
|
1035
|
+
<a class="tab-btn" href="/library" id="tabLibrary">Library</a>
|
|
1036
|
+
<a class="tab-btn" href="/lab">Lab</a>
|
|
1037
|
+
</div>
|
|
1038
|
+
<div class="topbar-right">
|
|
1039
|
+
<a class="gym-nav-btn gym-tab-link" href="/gym" style="display:none">Gym</a>
|
|
1040
|
+
<a href="/marketplace" class="gear-btn" title="Marketplace" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Marketplace</a>
|
|
1041
|
+
<a href="/monitor" class="gear-btn" title="Monitor" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Monitor</a>
|
|
1042
|
+
<a href="/admin" class="gear-btn" title="Admin" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Admin</a>
|
|
1043
|
+
<a href="/user-guide" class="gear-btn" title="User Guide" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">User Guide</a>
|
|
1044
|
+
<button onclick="window.open('/mini','minibar','width=440,height=460,resizable=yes,scrollbars=no')" title="Open Mini Bar" style="display:flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:8px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;font-size:15px;transition:all .2s" onmouseover="this.style.borderColor='var(--border-glow)'" onmouseout="this.style.borderColor='var(--border-dim)'">⊡</button>
|
|
1045
|
+
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme">☀</button>
|
|
1046
|
+
</div>
|
|
1047
|
+
</div>
|
|
1048
|
+
|
|
1049
|
+
<div class="sub-nav" id="subNav">
|
|
1050
|
+
<!-- sub-nav links injected by JS, agent controls appended after -->
|
|
1051
|
+
</div>
|
|
1052
|
+
|
|
1053
|
+
<div class="canvas" id="canvas"></div>
|
|
1054
|
+
|
|
1055
|
+
<!-- Agent Modal (create + edit) -->
|
|
1056
|
+
<div class="modal-overlay" id="agentModal">
|
|
1057
|
+
<div class="modal">
|
|
1058
|
+
<div class="modal-header">
|
|
1059
|
+
<div class="modal-title" id="modalTitle">New Agent</div>
|
|
1060
|
+
<button class="modal-close" onclick="closeModal()">✕</button>
|
|
1061
|
+
</div>
|
|
1062
|
+
<div class="modal-tabs">
|
|
1063
|
+
<button class="modal-tab active" onclick="switchModalTab('overview',this)">Overview</button>
|
|
1064
|
+
<button class="modal-tab" onclick="switchModalTab('skills',this)">Skills</button>
|
|
1065
|
+
<button class="modal-tab" onclick="switchModalTab('mcps',this)">MCPs</button>
|
|
1066
|
+
<button class="modal-tab" onclick="switchModalTab('schedules',this)">Schedules</button>
|
|
1067
|
+
<button class="modal-tab" onclick="switchModalTab('goals',this)">Goals</button>
|
|
1068
|
+
<button class="modal-tab" onclick="switchModalTab('config',this)">Config</button>
|
|
1069
|
+
<button class="modal-tab gym-only-tab" style="display:none" onclick="switchModalTab('gym-trainer',this)">Trainer</button>
|
|
1070
|
+
<button class="modal-tab gym-only-tab" style="display:none" onclick="switchModalTab('gym-learner',this)">Learner</button>
|
|
1071
|
+
<button class="modal-tab gym-only-tab" style="display:none" onclick="switchModalTab('gym-dimensions',this)">Dimensions</button>
|
|
1072
|
+
<button class="modal-tab gym-only-tab" style="display:none" onclick="switchModalTab('gym-programs',this)">Programs</button>
|
|
1073
|
+
</div>
|
|
1074
|
+
|
|
1075
|
+
<!-- Overview Tab -->
|
|
1076
|
+
<div class="modal-tab-content active" id="tab-overview">
|
|
1077
|
+
<div class="modal-body">
|
|
1078
|
+
<div class="form-row">
|
|
1079
|
+
<div class="form-group">
|
|
1080
|
+
<label class="form-label">Agent ID</label>
|
|
1081
|
+
<input class="form-input" id="f-agentId" placeholder="my-agent" oninput="autoAlias()">
|
|
1082
|
+
<div class="form-hint">Lowercase, hyphens only.</div>
|
|
1083
|
+
</div>
|
|
1084
|
+
<div class="form-group">
|
|
1085
|
+
<label class="form-label">Mention Alias</label>
|
|
1086
|
+
<input class="form-input" id="f-alias" placeholder="@myagent">
|
|
1087
|
+
</div>
|
|
1088
|
+
</div>
|
|
1089
|
+
|
|
1090
|
+
<div class="form-group">
|
|
1091
|
+
<label class="form-label">Name</label>
|
|
1092
|
+
<input class="form-input" id="f-name" placeholder="My Agent">
|
|
1093
|
+
</div>
|
|
1094
|
+
|
|
1095
|
+
<div class="form-group">
|
|
1096
|
+
<label class="form-label">Description</label>
|
|
1097
|
+
<input class="form-input" id="f-desc" placeholder="What does this agent do?">
|
|
1098
|
+
</div>
|
|
1099
|
+
|
|
1100
|
+
<div class="form-group">
|
|
1101
|
+
<label class="form-label">Instructions (CLAUDE.md)</label>
|
|
1102
|
+
<textarea class="form-input" id="f-instructions" rows="6" placeholder="System prompt / instructions for this agent..." style="font-family:var(--font-mono);font-size:12px;resize:vertical;min-height:200px"></textarea>
|
|
1103
|
+
<div class="form-hint">Agent system prompt. Written to the agent's CLAUDE.md file.</div>
|
|
1104
|
+
</div>
|
|
1105
|
+
|
|
1106
|
+
<div class="form-group">
|
|
1107
|
+
<label class="form-label">Agent Class</label>
|
|
1108
|
+
<select class="form-input" id="f-agentClass">
|
|
1109
|
+
<option value="standard">Standard</option>
|
|
1110
|
+
<option value="builder">Builder</option>
|
|
1111
|
+
<option value="platform">Platform</option>
|
|
1112
|
+
<option value="gym">Gym</option>
|
|
1113
|
+
</select>
|
|
1114
|
+
<div class="form-hint">Standard (default), Builder (app developer agents), Platform (Lab creators), Gym (training coach).</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
|
|
1117
|
+
<div class="form-group">
|
|
1118
|
+
<label class="form-label">Executor</label>
|
|
1119
|
+
<select class="form-input" id="f-executor">
|
|
1120
|
+
<option value="">Platform Default</option>
|
|
1121
|
+
<option value="claude">Claude</option>
|
|
1122
|
+
</select>
|
|
1123
|
+
<div class="form-hint">Override the platform default executor. Supports Claude, Ollama, OpenAI, Grok, Gemini, Groq, Together, Mistral. Requires multi-model enabled + API key in Admin → Settings.</div>
|
|
1124
|
+
</div>
|
|
1125
|
+
|
|
1126
|
+
<div class="form-section">Organization</div>
|
|
1127
|
+
<div id="orgEntries"></div>
|
|
1128
|
+
<button class="add-entry-btn" onclick="addOrgEntry()">+ Add Org Entry</button>
|
|
1129
|
+
|
|
1130
|
+
<!-- Heartbeat (collapsed by default) -->
|
|
1131
|
+
<div class="form-section" style="margin-top:24px;cursor:pointer;user-select:none;display:flex;align-items:center;gap:8px" onclick="document.getElementById('heartbeat-collapse').classList.toggle('hidden');this.querySelector('.collapse-arrow').textContent=document.getElementById('heartbeat-collapse').classList.contains('hidden')?'▸':'▾'">
|
|
1132
|
+
<span class="collapse-arrow" style="font-size:11px;color:var(--text-muted)">▸</span>Heartbeat
|
|
1133
|
+
<div class="checkbox-pill" id="f-heartbeatEnabled" onclick="event.stopPropagation();togglePill(this)" style="margin-left:auto;font-size:10px">Enabled</div>
|
|
1134
|
+
</div>
|
|
1135
|
+
<div id="heartbeat-collapse" class="hidden">
|
|
1136
|
+
<div class="form-group">
|
|
1137
|
+
<label class="form-label">Heartbeat Instructions</label>
|
|
1138
|
+
<textarea class="form-input" id="f-heartbeatInstructions" rows="4" style="font-family:var(--font-mono);font-size:12px;resize:vertical;min-height:80px">Check my tasks and work on the highest priority item. If no tasks are pending, report status.</textarea>
|
|
1139
|
+
<div class="form-hint">What the agent does during a heartbeat check.</div>
|
|
1140
|
+
</div>
|
|
1141
|
+
<div class="form-group">
|
|
1142
|
+
<label class="form-label">Schedule</label>
|
|
1143
|
+
<div id="heartbeatScheduleWrap" class="schedule-entry" style="border:none;padding:0;margin:0;background:none">
|
|
1144
|
+
<div class="schedule-row" style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
|
1145
|
+
<span class="schedule-label">Every</span>
|
|
1146
|
+
<select class="schedule-select" id="hb-frequency" onchange="updateHeartbeatFields()">
|
|
1147
|
+
<option value="">Off (manual only)</option>
|
|
1148
|
+
<option value="15min">15 Minutes</option>
|
|
1149
|
+
<option value="30min">30 Minutes</option>
|
|
1150
|
+
<option value="hour">Hour</option>
|
|
1151
|
+
<option value="day">Day</option>
|
|
1152
|
+
<option value="weekdays">Weekday (Mon-Fri)</option>
|
|
1153
|
+
<option value="week">Week</option>
|
|
1154
|
+
</select>
|
|
1155
|
+
<span class="schedule-label" id="hb-day-label" style="display:none">on</span>
|
|
1156
|
+
<select class="schedule-select" id="hb-days" style="display:none" onchange="updateHeartbeatFields()">
|
|
1157
|
+
<option value="1">Monday</option>
|
|
1158
|
+
<option value="2">Tuesday</option>
|
|
1159
|
+
<option value="3">Wednesday</option>
|
|
1160
|
+
<option value="4">Thursday</option>
|
|
1161
|
+
<option value="5">Friday</option>
|
|
1162
|
+
<option value="6">Saturday</option>
|
|
1163
|
+
<option value="0">Sunday</option>
|
|
1164
|
+
</select>
|
|
1165
|
+
<span class="schedule-label" id="hb-time-label" style="display:none">at</span>
|
|
1166
|
+
<select class="schedule-select" id="hb-hour" style="display:none">
|
|
1167
|
+
<option value="12">12</option><option value="1">1</option><option value="2">2</option><option value="3">3</option>
|
|
1168
|
+
<option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option>
|
|
1169
|
+
<option value="8">8</option><option value="9" selected>9</option><option value="10">10</option><option value="11">11</option>
|
|
1170
|
+
</select>
|
|
1171
|
+
<span class="schedule-label" id="hb-colon" style="display:none">:</span>
|
|
1172
|
+
<select class="schedule-select" id="hb-min" style="display:none">
|
|
1173
|
+
<option value="0">00</option><option value="15">15</option><option value="30">30</option><option value="45">45</option>
|
|
1174
|
+
</select>
|
|
1175
|
+
<select class="schedule-select" id="hb-ampm" style="display:none">
|
|
1176
|
+
<option value="AM">AM</option><option value="PM">PM</option>
|
|
1177
|
+
</select>
|
|
1178
|
+
</div>
|
|
1179
|
+
</div>
|
|
1180
|
+
<div class="form-hint">Leave "Off" for manual-only heartbeats via the Dashboard.</div>
|
|
1181
|
+
</div>
|
|
1182
|
+
</div>
|
|
1183
|
+
|
|
1184
|
+
<!-- Wiki Sync (collapsed by default) -->
|
|
1185
|
+
<div class="form-section" style="margin-top:24px;cursor:pointer;user-select:none;display:flex;align-items:center;gap:8px" onclick="document.getElementById('wikisync-collapse').classList.toggle('hidden');this.querySelector('.collapse-arrow').textContent=document.getElementById('wikisync-collapse').classList.contains('hidden')?'▸':'▾'">
|
|
1186
|
+
<span class="collapse-arrow" style="font-size:11px;color:var(--text-muted)">▸</span>Wiki Sync
|
|
1187
|
+
<div class="checkbox-pill" id="f-wikiSyncEnabled" onclick="event.stopPropagation();togglePill(this)" style="margin-left:auto;font-size:10px">Enabled</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
<div id="wikisync-collapse" class="hidden">
|
|
1190
|
+
<div class="form-group">
|
|
1191
|
+
<label class="form-label">Wiki Sync Instructions</label>
|
|
1192
|
+
<textarea class="form-input" id="f-wikiSyncInstructions" rows="4" style="font-family:var(--font-mono);font-size:12px;resize:vertical;min-height:80px">Review learned.md for new facts. Cross-check against context.md. Merge verified facts. Flag contradictions but don't overwrite — leave a note for human review.</textarea>
|
|
1193
|
+
<div class="form-hint">What the agent does during a wiki sync (merging learned.md → context.md).</div>
|
|
1194
|
+
</div>
|
|
1195
|
+
<div class="form-group">
|
|
1196
|
+
<label class="form-label">Schedule</label>
|
|
1197
|
+
<div id="wikiSyncScheduleWrap" class="schedule-entry" style="border:none;padding:0;margin:0;background:none">
|
|
1198
|
+
<div class="schedule-row" style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
|
1199
|
+
<span class="schedule-label">Every</span>
|
|
1200
|
+
<select class="schedule-select" id="ws-frequency" onchange="updateWikiSyncFields()">
|
|
1201
|
+
<option value="">Off (manual only)</option>
|
|
1202
|
+
<option value="15min">15 Minutes</option>
|
|
1203
|
+
<option value="30min">30 Minutes</option>
|
|
1204
|
+
<option value="hour">Hour</option>
|
|
1205
|
+
<option value="day" selected>Day</option>
|
|
1206
|
+
<option value="weekdays">Weekday (Mon-Fri)</option>
|
|
1207
|
+
<option value="week">Week</option>
|
|
1208
|
+
</select>
|
|
1209
|
+
<span class="schedule-label" id="ws-day-label" style="display:none">on</span>
|
|
1210
|
+
<select class="schedule-select" id="ws-days" style="display:none" onchange="updateWikiSyncFields()">
|
|
1211
|
+
<option value="1">Monday</option>
|
|
1212
|
+
<option value="2">Tuesday</option>
|
|
1213
|
+
<option value="3">Wednesday</option>
|
|
1214
|
+
<option value="4">Thursday</option>
|
|
1215
|
+
<option value="5">Friday</option>
|
|
1216
|
+
<option value="6">Saturday</option>
|
|
1217
|
+
<option value="0">Sunday</option>
|
|
1218
|
+
</select>
|
|
1219
|
+
<span class="schedule-label" id="ws-time-label">at</span>
|
|
1220
|
+
<select class="schedule-select" id="ws-hour">
|
|
1221
|
+
<option value="12">12</option><option value="1">1</option><option value="2">2</option><option value="3">3</option>
|
|
1222
|
+
<option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option>
|
|
1223
|
+
<option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="11">11</option>
|
|
1224
|
+
</select>
|
|
1225
|
+
<span class="schedule-label" id="ws-colon">:</span>
|
|
1226
|
+
<select class="schedule-select" id="ws-min">
|
|
1227
|
+
<option value="0" selected>00</option><option value="15">15</option><option value="30">30</option><option value="45">45</option>
|
|
1228
|
+
</select>
|
|
1229
|
+
<select class="schedule-select" id="ws-ampm">
|
|
1230
|
+
<option value="AM" selected>AM</option><option value="PM">PM</option>
|
|
1231
|
+
</select>
|
|
1232
|
+
</div>
|
|
1233
|
+
</div>
|
|
1234
|
+
<div class="form-hint">Leave "Off" for manual-only wiki sync via the Dashboard.</div>
|
|
1235
|
+
</div>
|
|
1236
|
+
</div>
|
|
1237
|
+
</div>
|
|
1238
|
+
</div>
|
|
1239
|
+
|
|
1240
|
+
<!-- Skills Tab -->
|
|
1241
|
+
<div class="modal-tab-content" id="tab-skills">
|
|
1242
|
+
<div class="modal-body">
|
|
1243
|
+
<div class="form-group" id="defaultSkillsSection" style="display:none">
|
|
1244
|
+
<label class="form-label" style="color:var(--text-muted);font-size:11px">Global Default Skills <span style="font-weight:400;opacity:.7">(all agents inherit these)</span></label>
|
|
1245
|
+
<div id="defaultSkillsList" style="display:flex;flex-wrap:wrap;gap:4px;padding:4px 0"></div>
|
|
1246
|
+
</div>
|
|
1247
|
+
<div class="form-group">
|
|
1248
|
+
<label class="form-label">Skills</label>
|
|
1249
|
+
<div class="checkbox-group" id="f-skills"></div>
|
|
1250
|
+
<div class="form-hint" id="f-skills-hint">Loading installed skills...</div>
|
|
1251
|
+
<button type="button" class="btn btn-sm" onclick="promptCreateSkill()" style="margin-top:8px;font-size:11px;padding:4px 10px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;cursor:pointer;color:var(--text)">+ Add New Skill</button>
|
|
1252
|
+
</div>
|
|
1253
|
+
|
|
1254
|
+
<div class="form-section">Tools</div>
|
|
1255
|
+
<div class="form-group">
|
|
1256
|
+
<div class="checkbox-group" id="f-tools">
|
|
1257
|
+
<div class="checkbox-pill checked" data-val="Read" onclick="togglePill(this)">Read</div>
|
|
1258
|
+
<div class="checkbox-pill checked" data-val="Edit" onclick="togglePill(this)">Edit</div>
|
|
1259
|
+
<div class="checkbox-pill checked" data-val="Write" onclick="togglePill(this)">Write</div>
|
|
1260
|
+
<div class="checkbox-pill checked" data-val="Glob" onclick="togglePill(this)">Glob</div>
|
|
1261
|
+
<div class="checkbox-pill checked" data-val="Grep" onclick="togglePill(this)">Grep</div>
|
|
1262
|
+
<div class="checkbox-pill checked" data-val="Bash" onclick="togglePill(this)">Bash</div>
|
|
1263
|
+
<div class="checkbox-pill checked" data-val="WebFetch" onclick="togglePill(this)">WebFetch</div>
|
|
1264
|
+
<div class="checkbox-pill checked" data-val="WebSearch" onclick="togglePill(this)">WebSearch</div>
|
|
1265
|
+
</div>
|
|
1266
|
+
</div>
|
|
1267
|
+
</div>
|
|
1268
|
+
</div>
|
|
1269
|
+
|
|
1270
|
+
<!-- MCPs Tab -->
|
|
1271
|
+
<div class="modal-tab-content" id="tab-mcps">
|
|
1272
|
+
<div class="modal-body">
|
|
1273
|
+
<div class="form-section">Available MCPs</div>
|
|
1274
|
+
<div class="form-group">
|
|
1275
|
+
<div class="checkbox-group" id="f-mcps"></div>
|
|
1276
|
+
<div class="form-hint" id="f-mcps-hint">Loading available MCPs...</div>
|
|
1277
|
+
</div>
|
|
1278
|
+
|
|
1279
|
+
<div class="form-section" id="mcpKeysSection" style="display:none">MCP Connections</div>
|
|
1280
|
+
<div id="mcpKeysArea" style="display:none">
|
|
1281
|
+
<div class="form-hint">Connect API keys or authorize services for the selected MCPs.</div>
|
|
1282
|
+
<div id="mcpKeysList"></div>
|
|
1283
|
+
</div>
|
|
1284
|
+
</div>
|
|
1285
|
+
</div>
|
|
1286
|
+
<div class="modal-tab-content" id="tab-schedules">
|
|
1287
|
+
<div class="modal-body">
|
|
1288
|
+
<div class="form-section">Scheduled Messages</div>
|
|
1289
|
+
<div id="scheduleEntries">
|
|
1290
|
+
<div class="no-schedules" id="noSchedules">No scheduled messages yet</div>
|
|
1291
|
+
</div>
|
|
1292
|
+
<button class="add-entry-btn" onclick="addScheduleEntry()">+ Add Schedule</button>
|
|
1293
|
+
</div>
|
|
1294
|
+
</div>
|
|
1295
|
+
<div class="modal-tab-content" id="tab-goals">
|
|
1296
|
+
<div class="modal-body">
|
|
1297
|
+
<div id="goalEntries">
|
|
1298
|
+
<div class="no-goals" id="noGoals">No goals yet</div>
|
|
1299
|
+
</div>
|
|
1300
|
+
<button class="add-entry-btn" onclick="addGoalEntry()">+ Add Goal</button>
|
|
1301
|
+
</div>
|
|
1302
|
+
</div>
|
|
1303
|
+
|
|
1304
|
+
<!-- Config Tab -->
|
|
1305
|
+
<div class="modal-tab-content" id="tab-config">
|
|
1306
|
+
<div class="modal-body">
|
|
1307
|
+
<div class="form-group">
|
|
1308
|
+
<label class="form-label">Workspace</label>
|
|
1309
|
+
<input class="form-input" id="f-workspace" placeholder="~" value="~">
|
|
1310
|
+
<div class="form-hint">Directory Claude can access.</div>
|
|
1311
|
+
</div>
|
|
1312
|
+
|
|
1313
|
+
<div class="form-group">
|
|
1314
|
+
<label class="form-label">Agent Home</label>
|
|
1315
|
+
<input class="form-input" id="f-agentHome" readonly style="opacity:.6;cursor:not-allowed;font-family:var(--font-mono);font-size:12px">
|
|
1316
|
+
<div class="form-hint">Auto-derived directory where agent files live.</div>
|
|
1317
|
+
</div>
|
|
1318
|
+
|
|
1319
|
+
<div class="form-section">Session</div>
|
|
1320
|
+
<div class="form-group">
|
|
1321
|
+
<div class="checkbox-group">
|
|
1322
|
+
<div class="checkbox-pill checked" id="f-persistent" onclick="togglePill(this)">Persistent Session</div>
|
|
1323
|
+
<div class="checkbox-pill" id="f-streaming" onclick="togglePill(this)">Streaming</div>
|
|
1324
|
+
<div class="checkbox-pill checked" id="f-advancedMemory" onclick="togglePill(this)">Advanced Memory</div>
|
|
1325
|
+
<div class="checkbox-pill" id="f-wiki" onclick="toggleWikiPill(this)">Wiki Learning</div>
|
|
1326
|
+
<div class="checkbox-pill checked" id="f-autonomousCapable" onclick="togglePill(this)">Autonomous Capable</div>
|
|
1327
|
+
<div class="checkbox-pill" id="f-autoCommit" onclick="togglePill(this)">Auto Commit</div>
|
|
1328
|
+
</div>
|
|
1329
|
+
</div>
|
|
1330
|
+
|
|
1331
|
+
<div class="form-row">
|
|
1332
|
+
<div class="form-group">
|
|
1333
|
+
<label class="form-label">Timeout (seconds)</label>
|
|
1334
|
+
<input class="form-input" id="f-timeout" type="number" min="30" step="30" placeholder="14400" value="14400" style="font-family:var(--font-mono);font-size:12px">
|
|
1335
|
+
</div>
|
|
1336
|
+
<div class="form-group">
|
|
1337
|
+
<label class="form-label">Claude Account</label>
|
|
1338
|
+
<select class="form-input" id="f-claudeAccount">
|
|
1339
|
+
<option value="">Default</option>
|
|
1340
|
+
</select>
|
|
1341
|
+
<div class="form-hint">Which Anthropic account.</div>
|
|
1342
|
+
</div>
|
|
1343
|
+
</div>
|
|
1344
|
+
|
|
1345
|
+
<div class="form-section">Routes</div>
|
|
1346
|
+
<div id="routeEntries"></div>
|
|
1347
|
+
<button class="add-entry-btn" onclick="addRouteEntry()">+ Add Route</button>
|
|
1348
|
+
</div>
|
|
1349
|
+
</div>
|
|
1350
|
+
|
|
1351
|
+
<!-- Gym: Trainer Tab -->
|
|
1352
|
+
<div class="modal-tab-content" id="tab-gym-trainer">
|
|
1353
|
+
<div class="modal-body">
|
|
1354
|
+
<div class="form-section">Active Trainer</div>
|
|
1355
|
+
<p style="font-size:13px;color:var(--text-secondary);margin:0 0 16px">Choose the coaching personality for your gym agent. This changes how the coach interacts during training sessions.</p>
|
|
1356
|
+
<div id="gymTrainerGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px"></div>
|
|
1357
|
+
</div>
|
|
1358
|
+
</div>
|
|
1359
|
+
|
|
1360
|
+
<!-- Gym: Learner Tab -->
|
|
1361
|
+
<div class="modal-tab-content" id="tab-gym-learner">
|
|
1362
|
+
<div class="modal-body">
|
|
1363
|
+
<div class="form-section">Learner Profile</div>
|
|
1364
|
+
<p style="font-size:13px;color:var(--text-secondary);margin:0 0 16px">The coach builds this profile during onboarding and updates it as you train. You can edit fields directly.</p>
|
|
1365
|
+
<div class="form-group">
|
|
1366
|
+
<label class="form-label">Identity</label>
|
|
1367
|
+
<textarea class="form-input" id="gym-learner-identity" rows="4" style="font-family:var(--font-mono);font-size:12px;resize:vertical" placeholder="Built during onboarding..."></textarea>
|
|
1368
|
+
</div>
|
|
1369
|
+
<div class="form-group">
|
|
1370
|
+
<label class="form-label">Goals</label>
|
|
1371
|
+
<textarea class="form-input" id="gym-learner-goals" rows="3" style="font-family:var(--font-mono);font-size:12px;resize:vertical" placeholder="What the learner wants to achieve..."></textarea>
|
|
1372
|
+
</div>
|
|
1373
|
+
<div class="form-row">
|
|
1374
|
+
<div class="form-group">
|
|
1375
|
+
<label class="form-label">Current Streak</label>
|
|
1376
|
+
<input class="form-input" id="gym-learner-streak" type="number" min="0" readonly style="opacity:.7;cursor:not-allowed;font-family:var(--font-mono);font-size:12px">
|
|
1377
|
+
</div>
|
|
1378
|
+
<div class="form-group">
|
|
1379
|
+
<label class="form-label">Longest Streak</label>
|
|
1380
|
+
<input class="form-input" id="gym-learner-longestStreak" type="number" min="0" readonly style="opacity:.7;cursor:not-allowed;font-family:var(--font-mono);font-size:12px">
|
|
1381
|
+
</div>
|
|
1382
|
+
</div>
|
|
1383
|
+
<div class="form-section" style="margin-top:16px">Strengths</div>
|
|
1384
|
+
<div id="gym-learner-strengths" style="font-size:13px;color:var(--text-secondary)"></div>
|
|
1385
|
+
<div class="form-section" style="margin-top:16px">Struggles</div>
|
|
1386
|
+
<div id="gym-learner-struggles" style="font-size:13px;color:var(--text-secondary)"></div>
|
|
1387
|
+
<div style="margin-top:20px;display:flex;gap:8px">
|
|
1388
|
+
<button class="btn-save" onclick="saveGymLearnerProfile()" style="font-size:13px;padding:8px 16px">Save Profile</button>
|
|
1389
|
+
</div>
|
|
1390
|
+
</div>
|
|
1391
|
+
</div>
|
|
1392
|
+
|
|
1393
|
+
<!-- Gym: Dimensions Tab -->
|
|
1394
|
+
<div class="modal-tab-content" id="tab-gym-dimensions">
|
|
1395
|
+
<div class="modal-body">
|
|
1396
|
+
<div class="form-section">Skill Dimensions</div>
|
|
1397
|
+
<p style="font-size:13px;color:var(--text-secondary);margin:0 0 16px">Current scores across the 5 skill dimensions. Updated by the coach as you progress.</p>
|
|
1398
|
+
<div id="gymDimensionBars" style="display:flex;flex-direction:column;gap:12px"></div>
|
|
1399
|
+
<div class="form-section" style="margin-top:24px">History</div>
|
|
1400
|
+
<div id="gymDimensionHistory" style="font-size:13px;color:var(--text-secondary)"></div>
|
|
1401
|
+
</div>
|
|
1402
|
+
</div>
|
|
1403
|
+
|
|
1404
|
+
<!-- Gym: Programs Tab -->
|
|
1405
|
+
<div class="modal-tab-content" id="tab-gym-programs">
|
|
1406
|
+
<div class="modal-body">
|
|
1407
|
+
<div class="form-section">Programs</div>
|
|
1408
|
+
<p style="font-size:13px;color:var(--text-secondary);margin:0 0 16px">Training programs available to the gym. Shows enrollment and completion status.</p>
|
|
1409
|
+
<div id="gymProgramList" style="display:flex;flex-direction:column;gap:10px"></div>
|
|
1410
|
+
</div>
|
|
1411
|
+
</div>
|
|
1412
|
+
|
|
1413
|
+
<div class="modal-footer">
|
|
1414
|
+
<div class="modal-footer-left">
|
|
1415
|
+
<button class="btn-delete hidden" id="deleteBtn" onclick="showDeleteConfirm()">Delete Agent</button>
|
|
1416
|
+
<div class="modal-footer-hint" id="modalHint">Agent files created in ~/Desktop/MyAIforOne Drive/PersonalAgents/</div>
|
|
1417
|
+
</div>
|
|
1418
|
+
<button class="btn-save" id="saveBtn" onclick="saveAgent()">Create Agent</button>
|
|
1419
|
+
</div>
|
|
1420
|
+
</div>
|
|
1421
|
+
</div>
|
|
1422
|
+
|
|
1423
|
+
<!-- Bulk Action Bar (floating bottom) -->
|
|
1424
|
+
<div class="bulk-bar hidden" id="bulkBar">
|
|
1425
|
+
<span id="bulkCount">0 selected</span>
|
|
1426
|
+
<button class="bulk-btn bulk-select-all" onclick="bulkSelectAll()">Select All</button>
|
|
1427
|
+
<button class="bulk-btn bulk-deselect" onclick="bulkDeselectAll()">Deselect All</button>
|
|
1428
|
+
<button class="bulk-btn bulk-delete" onclick="bulkDelete()">Delete Selected</button>
|
|
1429
|
+
<button class="bulk-btn bulk-cancel" onclick="toggleSelectMode()">Cancel</button>
|
|
1430
|
+
</div>
|
|
1431
|
+
|
|
1432
|
+
<!-- Delete Confirmation Modal -->
|
|
1433
|
+
<div class="delete-overlay hidden" id="deleteOverlay" onclick="hideDeleteConfirm()">
|
|
1434
|
+
<div class="delete-modal" onclick="event.stopPropagation()">
|
|
1435
|
+
<div class="delete-warning-icon">⚠</div>
|
|
1436
|
+
<div class="delete-title">Delete Agent Permanently</div>
|
|
1437
|
+
<div class="delete-body">
|
|
1438
|
+
<p>This will <strong>permanently delete</strong> this agent including:</p>
|
|
1439
|
+
<ul>
|
|
1440
|
+
<li>All conversation history and memory</li>
|
|
1441
|
+
<li>System prompt (CLAUDE.md)</li>
|
|
1442
|
+
<li>All files in the agent's home directory</li>
|
|
1443
|
+
<li>MCP keys and skills</li>
|
|
1444
|
+
<li>Config entry and routes</li>
|
|
1445
|
+
</ul>
|
|
1446
|
+
<p class="delete-irreversible">This action cannot be undone.</p>
|
|
1447
|
+
<p>Type the agent's alias to confirm:</p>
|
|
1448
|
+
<div class="delete-alias-hint" id="deleteAliasHint"></div>
|
|
1449
|
+
<input type="text" class="delete-input" id="deleteConfirmInput" placeholder="@alias" oninput="checkDeleteInput()">
|
|
1450
|
+
</div>
|
|
1451
|
+
<div class="delete-actions">
|
|
1452
|
+
<button class="btn-cancel" onclick="hideDeleteConfirm()">Cancel</button>
|
|
1453
|
+
<button class="btn-confirm-delete" id="confirmDeleteBtn" onclick="executeDelete()" disabled>Delete Forever</button>
|
|
1454
|
+
</div>
|
|
1455
|
+
</div>
|
|
1456
|
+
</div>
|
|
1457
|
+
|
|
1458
|
+
<!-- MCP Auth Modal (reusable for add account, confirm disconnect/remove) -->
|
|
1459
|
+
<div class="modal-overlay" id="mcpAuthModal">
|
|
1460
|
+
<div class="mcp-auth-modal" onclick="event.stopPropagation()">
|
|
1461
|
+
<div class="modal-header">
|
|
1462
|
+
<div class="modal-title" id="mcpAuthTitle">Add Account</div>
|
|
1463
|
+
<button class="modal-close" onclick="closeMcpAuthModal()">✕</button>
|
|
1464
|
+
</div>
|
|
1465
|
+
<div class="mcp-auth-body" id="mcpAuthBody"></div>
|
|
1466
|
+
<div class="mcp-auth-actions" id="mcpAuthActions"></div>
|
|
1467
|
+
</div>
|
|
1468
|
+
</div>
|
|
1469
|
+
|
|
1470
|
+
<!-- Datalists for org autocomplete -->
|
|
1471
|
+
<datalist id="dl-orgs"></datalist>
|
|
1472
|
+
<datalist id="dl-functions"></datalist>
|
|
1473
|
+
<datalist id="dl-titles"></datalist>
|
|
1474
|
+
<datalist id="dl-aliases"></datalist>
|
|
1475
|
+
|
|
1476
|
+
<script>
|
|
1477
|
+
let agents = [];
|
|
1478
|
+
let visibleClasses = (function() {
|
|
1479
|
+
try { const s = JSON.parse(localStorage.getItem('orgVisibleClasses')); if (Array.isArray(s)) return new Set(s); } catch {}
|
|
1480
|
+
return new Set(['standard', 'platform']);
|
|
1481
|
+
})();
|
|
1482
|
+
let viewMode = localStorage.getItem('orgViewMode') || 'grid'; // 'grid' | 'compact' | 'list'
|
|
1483
|
+
let orgs = [];
|
|
1484
|
+
let availableMcps = [];
|
|
1485
|
+
let availableSkills = []; // { id, name, installed, isPlatformDefault }
|
|
1486
|
+
let availableAccounts = [];
|
|
1487
|
+
let channelsData = [];
|
|
1488
|
+
let editingAgentId = null; // null = create mode, string = edit mode
|
|
1489
|
+
|
|
1490
|
+
// ─── Theme ─────────────────────────────────────────────────────
|
|
1491
|
+
function toggleTheme() {
|
|
1492
|
+
const html = document.documentElement;
|
|
1493
|
+
const current = html.getAttribute('data-theme');
|
|
1494
|
+
const next = current === 'light' ? '' : 'light';
|
|
1495
|
+
html.setAttribute('data-theme', next);
|
|
1496
|
+
document.querySelector('.theme-toggle').textContent = next === 'light' ? '\u{1F319}' : '\u2600';
|
|
1497
|
+
localStorage.setItem('theme', next);
|
|
1498
|
+
}
|
|
1499
|
+
(function() {
|
|
1500
|
+
const saved = localStorage.getItem('theme');
|
|
1501
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
1502
|
+
const theme = saved !== null ? saved : (prefersDark ? '' : 'light');
|
|
1503
|
+
if (theme) document.documentElement.setAttribute('data-theme', theme);
|
|
1504
|
+
document.querySelector('.theme-toggle').textContent = theme === 'light' ? '\u{1F319}' : '\u2600';
|
|
1505
|
+
})();
|
|
1506
|
+
|
|
1507
|
+
// ─── Hidden Orgs (presentation mode) ────────────────────────────
|
|
1508
|
+
function getHiddenOrgs() {
|
|
1509
|
+
try { return JSON.parse(localStorage.getItem('hiddenOrgs') || '[]'); } catch { return []; }
|
|
1510
|
+
}
|
|
1511
|
+
function setHiddenOrgs(arr) {
|
|
1512
|
+
localStorage.setItem('hiddenOrgs', JSON.stringify(arr));
|
|
1513
|
+
}
|
|
1514
|
+
function toggleHideOrg(orgName) {
|
|
1515
|
+
const hidden = getHiddenOrgs();
|
|
1516
|
+
const idx = hidden.indexOf(orgName);
|
|
1517
|
+
if (idx >= 0) hidden.splice(idx, 1); else hidden.push(orgName);
|
|
1518
|
+
setHiddenOrgs(hidden);
|
|
1519
|
+
renderOrg();
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Auto-open agent modal from URL params (e.g. /org?edit=agentId&tab=goals)
|
|
1523
|
+
// When embedded in an iframe, hide background and notify parent on close
|
|
1524
|
+
(function() {
|
|
1525
|
+
const params = new URLSearchParams(window.location.search);
|
|
1526
|
+
const editId = params.get('edit');
|
|
1527
|
+
const tab = params.get('tab');
|
|
1528
|
+
const isEmbed = window !== window.parent;
|
|
1529
|
+
if (editId) {
|
|
1530
|
+
// Hide background content when embedded so only the modal shows
|
|
1531
|
+
if (isEmbed) {
|
|
1532
|
+
document.documentElement.style.background = 'transparent';
|
|
1533
|
+
document.body.style.background = 'transparent';
|
|
1534
|
+
for (const sel of ['.topbar', '.sub-nav', '#canvas', '#bulkBar']) {
|
|
1535
|
+
document.querySelector(sel)?.style.setProperty('display','none');
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
setTimeout(() => {
|
|
1539
|
+
openModal(editId).then(() => {
|
|
1540
|
+
if (tab) {
|
|
1541
|
+
const tabBtn = [...document.querySelectorAll('.modal-tab')].find(t => t.textContent.toLowerCase() === tab);
|
|
1542
|
+
if (tabBtn) switchModalTab(tab, tabBtn);
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
}, 500);
|
|
1546
|
+
}
|
|
1547
|
+
})();
|
|
1548
|
+
function isOrgHidden(orgName) {
|
|
1549
|
+
return getHiddenOrgs().includes(orgName);
|
|
1550
|
+
}
|
|
1551
|
+
function toggleHideAll() {
|
|
1552
|
+
const allOrgs = [...new Set(agents.flatMap(a => (a.org || []).map(o => o.organization)).filter(Boolean))];
|
|
1553
|
+
const hidden = getHiddenOrgs();
|
|
1554
|
+
const allHidden = allOrgs.every(o => hidden.includes(o));
|
|
1555
|
+
if (allHidden) {
|
|
1556
|
+
// Unhide all
|
|
1557
|
+
setHiddenOrgs([]);
|
|
1558
|
+
} else {
|
|
1559
|
+
// Hide all
|
|
1560
|
+
setHiddenOrgs(allOrgs);
|
|
1561
|
+
}
|
|
1562
|
+
renderOrg();
|
|
1563
|
+
updateHideAllBtn();
|
|
1564
|
+
}
|
|
1565
|
+
function updateHideAllBtn() {
|
|
1566
|
+
const btn = document.getElementById('hideAllBtn');
|
|
1567
|
+
if (!btn) return;
|
|
1568
|
+
const allOrgs = [...new Set(agents.flatMap(a => (a.org || []).map(o => o.organization)).filter(Boolean))];
|
|
1569
|
+
const hidden = getHiddenOrgs();
|
|
1570
|
+
const allHidden = allOrgs.length > 0 && allOrgs.every(o => hidden.includes(o));
|
|
1571
|
+
btn.className = 'hide-names-btn' + (allHidden ? ' active' : '');
|
|
1572
|
+
btn.innerHTML = allHidden ? '🚫 Show All' : '👁 Hide All';
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// ─── Data ──────────────────────────────────────────────────────
|
|
1576
|
+
async function fetchData() {
|
|
1577
|
+
const [dashRes, chRes] = await Promise.all([
|
|
1578
|
+
fetch('/api/dashboard'),
|
|
1579
|
+
fetch('/api/channels'),
|
|
1580
|
+
]);
|
|
1581
|
+
const data = await dashRes.json();
|
|
1582
|
+
agents = data.agents;
|
|
1583
|
+
agents.sort((a, b) => (a.name || a.id || '').localeCompare(b.name || b.id || ''));
|
|
1584
|
+
availableAccounts = data.claudeAccounts || [];
|
|
1585
|
+
|
|
1586
|
+
try { channelsData = (await chRes.json()).channels || []; } catch { channelsData = []; }
|
|
1587
|
+
|
|
1588
|
+
// Collect unique orgs
|
|
1589
|
+
const orgSet = new Set();
|
|
1590
|
+
orgSet.add('All Agents');
|
|
1591
|
+
for (const a of agents) {
|
|
1592
|
+
for (const o of (a.org || [])) {
|
|
1593
|
+
orgSet.add(o.organization);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
orgs = [...orgSet];
|
|
1597
|
+
|
|
1598
|
+
// Populate dropdown — add "By Account" grouping option
|
|
1599
|
+
const select = document.getElementById('orgSelect');
|
|
1600
|
+
const prev = select.value;
|
|
1601
|
+
select.innerHTML = orgs.map(o => `<option value="${o}">${o}</option>`).join('') + '<option value="__byAccount__">── By Account ──</option>';
|
|
1602
|
+
if (prev && (orgs.includes(prev) || prev === '__byAccount__')) select.value = prev;
|
|
1603
|
+
|
|
1604
|
+
// Populate account filter
|
|
1605
|
+
populateAccountFilter();
|
|
1606
|
+
|
|
1607
|
+
// Build datalists
|
|
1608
|
+
buildDataLists();
|
|
1609
|
+
renderOrg();
|
|
1610
|
+
updateHideAllBtn();
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function buildDataLists() {
|
|
1614
|
+
const orgNames = new Set();
|
|
1615
|
+
const funcNames = new Set();
|
|
1616
|
+
const titleNames = new Set();
|
|
1617
|
+
const aliasNames = new Set();
|
|
1618
|
+
for (const a of agents) {
|
|
1619
|
+
for (const al of (a.aliases || [])) aliasNames.add(al);
|
|
1620
|
+
for (const o of (a.org || [])) {
|
|
1621
|
+
orgNames.add(o.organization);
|
|
1622
|
+
if (o.function) funcNames.add(o.function);
|
|
1623
|
+
if (o.title) titleNames.add(o.title);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
document.getElementById('dl-orgs').innerHTML = [...orgNames].map(v => `<option value="${escapeHtml(v)}">`).join('');
|
|
1627
|
+
document.getElementById('dl-functions').innerHTML = [...funcNames].map(v => `<option value="${escapeHtml(v)}">`).join('');
|
|
1628
|
+
document.getElementById('dl-titles').innerHTML = [...titleNames].map(v => `<option value="${escapeHtml(v)}">`).join('');
|
|
1629
|
+
document.getElementById('dl-aliases').innerHTML = [...aliasNames].map(v => `<option value="${escapeHtml(v)}">`).join('');
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// ─── Render ────────────────────────────────────────────────────
|
|
1633
|
+
let searchQuery = '';
|
|
1634
|
+
let accountFilter = 'all'; // 'all' or account name
|
|
1635
|
+
|
|
1636
|
+
// ─── Account helpers ────────────────────────────────────────────
|
|
1637
|
+
const ACCOUNT_COLORS = ['0','1','2','3','4','5'];
|
|
1638
|
+
function accountColorClass(accountName) {
|
|
1639
|
+
if (!accountName) return 'account-badge-0';
|
|
1640
|
+
let hash = 0;
|
|
1641
|
+
for (let i = 0; i < accountName.length; i++) hash = ((hash << 5) - hash) + accountName.charCodeAt(i);
|
|
1642
|
+
return 'account-badge-' + ACCOUNT_COLORS[Math.abs(hash) % ACCOUNT_COLORS.length];
|
|
1643
|
+
}
|
|
1644
|
+
function renderAccountBadge(agent) {
|
|
1645
|
+
const acct = agent.claudeAccount || 'default';
|
|
1646
|
+
return `<span class="account-badge ${accountColorClass(acct)}">${escapeHtml(acct)}</span>`;
|
|
1647
|
+
}
|
|
1648
|
+
function matchesAccountFilter(agent) {
|
|
1649
|
+
if (accountFilter === 'all') return true;
|
|
1650
|
+
const acct = agent.claudeAccount || 'default';
|
|
1651
|
+
return acct === accountFilter;
|
|
1652
|
+
}
|
|
1653
|
+
function setAccountFilter(val) {
|
|
1654
|
+
accountFilter = val;
|
|
1655
|
+
renderOrg();
|
|
1656
|
+
}
|
|
1657
|
+
function populateAccountFilter() {
|
|
1658
|
+
const sel = document.getElementById('accountFilter');
|
|
1659
|
+
if (!sel) return;
|
|
1660
|
+
const accts = new Set(['default']);
|
|
1661
|
+
for (const a of agents) { if (a.claudeAccount) accts.add(a.claudeAccount); }
|
|
1662
|
+
const sorted = [...accts].sort();
|
|
1663
|
+
const prev = sel.value;
|
|
1664
|
+
sel.innerHTML = '<option value="all">All Accounts</option>' + sorted.map(a => `<option value="${a}">${escapeHtml(a)}</option>`).join('');
|
|
1665
|
+
if (prev && (prev === 'all' || sorted.includes(prev))) sel.value = prev;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function toggleClassDropdown() {
|
|
1669
|
+
document.getElementById('classDropdownMenu').classList.toggle('open');
|
|
1670
|
+
}
|
|
1671
|
+
document.addEventListener('click', e => {
|
|
1672
|
+
const dd = document.getElementById('classDropdown');
|
|
1673
|
+
if (dd && !dd.contains(e.target)) {
|
|
1674
|
+
const menu = document.getElementById('classDropdownMenu');
|
|
1675
|
+
if (menu) menu.classList.remove('open');
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
function toggleClassFilter(cls) {
|
|
1680
|
+
if (visibleClasses.has(cls)) visibleClasses.delete(cls);
|
|
1681
|
+
else visibleClasses.add(cls);
|
|
1682
|
+
localStorage.setItem('orgVisibleClasses', JSON.stringify([...visibleClasses]));
|
|
1683
|
+
updateClassDropdownLabel();
|
|
1684
|
+
renderOrg();
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
function updateClassDropdownLabel() {
|
|
1688
|
+
['standard','builder','platform'].forEach(cls => {
|
|
1689
|
+
const chk = document.getElementById('chk-' + cls);
|
|
1690
|
+
if (chk) chk.textContent = visibleClasses.has(cls) ? '✓' : '';
|
|
1691
|
+
});
|
|
1692
|
+
const btn = document.getElementById('classDropdownBtn');
|
|
1693
|
+
const labels = [];
|
|
1694
|
+
if (visibleClasses.has('standard')) labels.push('Standard');
|
|
1695
|
+
if (visibleClasses.has('builder')) labels.push('Builder');
|
|
1696
|
+
if (visibleClasses.has('platform')) labels.push('Platform');
|
|
1697
|
+
btn.textContent = (labels.length ? labels.join(', ') : 'None') + ' ▾';
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function handleSearch() {
|
|
1701
|
+
searchQuery = (document.getElementById('agentSearch')?.value || '').trim().toLowerCase();
|
|
1702
|
+
if (searchQuery) {
|
|
1703
|
+
// Switch to All Agents view when searching
|
|
1704
|
+
document.getElementById('orgSelect').value = 'All Agents';
|
|
1705
|
+
}
|
|
1706
|
+
renderOrg();
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function matchesSearch(agent) {
|
|
1710
|
+
if (!searchQuery) return true;
|
|
1711
|
+
const q = searchQuery;
|
|
1712
|
+
const name = (agent.name || '').toLowerCase();
|
|
1713
|
+
const alias = ((agent.aliases?.[0] || agent.mentionAliases?.[0]) || '').toLowerCase();
|
|
1714
|
+
const title = ((agent.orgData?.title) || (agent.org?.[0]?.title) || '').toLowerCase();
|
|
1715
|
+
const desc = (agent.description || '').toLowerCase();
|
|
1716
|
+
return name.includes(q) || alias.includes(q) || title.includes(q) || desc.includes(q);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function renderOrg() {
|
|
1720
|
+
if (isChannelsPage) {
|
|
1721
|
+
const canvas = document.getElementById('canvas');
|
|
1722
|
+
if (canvas) renderChannelsPanel(canvas);
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
const selected = document.getElementById('orgSelect').value;
|
|
1726
|
+
const canvas = document.getElementById('canvas');
|
|
1727
|
+
|
|
1728
|
+
if (selected === 'All Agents') {
|
|
1729
|
+
renderFlatGrid(canvas);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
if (selected === '__byAccount__') {
|
|
1734
|
+
renderByAccount(canvas);
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// Find agents in this org
|
|
1739
|
+
const nodesInOrg = [];
|
|
1740
|
+
for (const a of agents) {
|
|
1741
|
+
const ac = a.agentClass || 'standard';
|
|
1742
|
+
if (!visibleClasses.has(ac)) continue;
|
|
1743
|
+
if (!matchesSearch(a)) continue;
|
|
1744
|
+
if (!matchesAccountFilter(a)) continue;
|
|
1745
|
+
const orgEntry = (a.org || []).find(o => o.organization === selected);
|
|
1746
|
+
if (orgEntry) {
|
|
1747
|
+
nodesInOrg.push({ ...a, orgData: orgEntry });
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
if (nodesInOrg.length === 0) {
|
|
1752
|
+
canvas.innerHTML = `<div class="empty-org"><div class="empty-org-icon">◇</div><div class="empty-org-text">No agents in ${escapeHtml(selected)}</div></div>`;
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
const selHidden = isOrgHidden(selected);
|
|
1757
|
+
|
|
1758
|
+
// Compact view for single org
|
|
1759
|
+
if (viewMode === 'compact') {
|
|
1760
|
+
let html = renderOrgBanner(selected, selHidden);
|
|
1761
|
+
html += '<div class="compact-grid">';
|
|
1762
|
+
for (const a of nodesInOrg.sort((a,b)=>(a.name||a.id||'').localeCompare(b.name||b.id||''))) {
|
|
1763
|
+
html += renderCompactNode(a, selHidden);
|
|
1764
|
+
}
|
|
1765
|
+
html += '</div>';
|
|
1766
|
+
canvas.innerHTML = html;
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// List view for single org
|
|
1771
|
+
if (viewMode === 'list') {
|
|
1772
|
+
// Temporarily override getFilteredAgents result to just this org
|
|
1773
|
+
const fakeGroups = {}; fakeGroups[selected] = nodesInOrg;
|
|
1774
|
+
const origFn = window.getFilteredAgents;
|
|
1775
|
+
window.getFilteredAgents = () => ({ orgGroups: fakeGroups, noOrg: [] });
|
|
1776
|
+
renderListView(canvas);
|
|
1777
|
+
window.getFilteredAgents = origFn;
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// Grid view: tree hierarchy
|
|
1782
|
+
const aliasesInOrg = new Set(nodesInOrg.flatMap(n => n.aliases || []));
|
|
1783
|
+
const roots = nodesInOrg.filter(n => !n.orgData.reportsTo || !aliasesInOrg.has(n.orgData.reportsTo));
|
|
1784
|
+
|
|
1785
|
+
const childMap = {};
|
|
1786
|
+
for (const n of nodesInOrg) {
|
|
1787
|
+
if (n.orgData.reportsTo && aliasesInOrg.has(n.orgData.reportsTo)) {
|
|
1788
|
+
if (!childMap[n.orgData.reportsTo]) childMap[n.orgData.reportsTo] = [];
|
|
1789
|
+
childMap[n.orgData.reportsTo].push(n);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
const levels = [];
|
|
1794
|
+
let currentLevel = roots;
|
|
1795
|
+
const visited = new Set();
|
|
1796
|
+
while (currentLevel.length > 0) {
|
|
1797
|
+
levels.push(currentLevel);
|
|
1798
|
+
for (const n of currentLevel) visited.add(n.id);
|
|
1799
|
+
const nextLevel = [];
|
|
1800
|
+
for (const node of currentLevel) {
|
|
1801
|
+
for (const alias of (node.aliases || [])) {
|
|
1802
|
+
if (childMap[alias]) {
|
|
1803
|
+
for (const child of childMap[alias]) {
|
|
1804
|
+
if (!visited.has(child.id)) nextLevel.push(child);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
currentLevel = nextLevel;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
let html = renderOrgBanner(selected, selHidden);
|
|
1813
|
+
html += '<div style="display:flex;flex-direction:column;align-items:center;gap:0;padding:0 20px 20px">';
|
|
1814
|
+
for (let i = 0; i < levels.length; i++) {
|
|
1815
|
+
if (i > 0) html += '<div class="level-connector"></div>';
|
|
1816
|
+
html += '<div style="display:flex;justify-content:center;gap:24px;flex-wrap:wrap">';
|
|
1817
|
+
for (const node of levels[i]) {
|
|
1818
|
+
html += renderNode(node, false, i === 0, selHidden);
|
|
1819
|
+
}
|
|
1820
|
+
html += '</div>';
|
|
1821
|
+
}
|
|
1822
|
+
html += '</div>';
|
|
1823
|
+
|
|
1824
|
+
canvas.innerHTML = html;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function renderFlatGrid(canvas) {
|
|
1828
|
+
// Delegate to compact or list view if active
|
|
1829
|
+
if (viewMode === 'compact') return renderCompactGrid(canvas);
|
|
1830
|
+
if (viewMode === 'list') return renderListView(canvas);
|
|
1831
|
+
|
|
1832
|
+
const { orgGroups, noOrg } = getFilteredAgents();
|
|
1833
|
+
|
|
1834
|
+
let html = '';
|
|
1835
|
+
for (const orgName of Object.keys(orgGroups).sort()) {
|
|
1836
|
+
const oHidden = isOrgHidden(orgName);
|
|
1837
|
+
html += renderOrgBanner(orgName, oHidden);
|
|
1838
|
+
html += '<div class="flat-grid">';
|
|
1839
|
+
for (const a of [...orgGroups[orgName]].sort((a,b)=>(a.name||a.id||'').localeCompare(b.name||b.id||''))) {
|
|
1840
|
+
html += renderNode(a, false, false, oHidden);
|
|
1841
|
+
}
|
|
1842
|
+
html += '</div>';
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
noOrg.sort((a, b) => (a.name || a.id || '').localeCompare(b.name || b.id || ''));
|
|
1846
|
+
if (noOrg.length > 0) {
|
|
1847
|
+
html += '<div class="org-title-banner" style="opacity:.5">Unassigned</div>';
|
|
1848
|
+
html += '<div class="flat-grid">';
|
|
1849
|
+
for (const a of noOrg) {
|
|
1850
|
+
html += renderNode(a);
|
|
1851
|
+
}
|
|
1852
|
+
html += '</div>';
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
canvas.innerHTML = html;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
function renderNode(a, dimmed, isLead, blurNames) {
|
|
1859
|
+
const initials = (a.aliases?.[0] || a.id).replace('@','').slice(0,2).toUpperCase();
|
|
1860
|
+
const orgData = a.orgData || (a.org && a.org[0]);
|
|
1861
|
+
const title = orgData?.title || '';
|
|
1862
|
+
const func = orgData?.function || '';
|
|
1863
|
+
const isActive = a.sessionActive;
|
|
1864
|
+
const isRecent = a.lastMessage !== 'never' && (Date.now() - new Date(a.lastMessage).getTime()) < 3600000;
|
|
1865
|
+
const hasAutomation = (a.activeGoals || 0) + (a.activeCron || 0) > 0;
|
|
1866
|
+
const opacity = dimmed ? 'opacity:.5;' : '';
|
|
1867
|
+
const leadClass = isLead ? ' is-lead' : '';
|
|
1868
|
+
const blurClass = blurNames ? ' name-blurred' : '';
|
|
1869
|
+
|
|
1870
|
+
return `<div class="agent-node${leadClass}" style="${opacity};width:300px" data-agent-id="${a.id}" data-agent-alias="${a.aliases?.[0] || a.id}" onclick="if(selectMode){toggleAgentSelect('${a.id}');return}">
|
|
1871
|
+
<input type="checkbox" class="select-check" data-id="${a.id}" onclick="event.stopPropagation();toggleAgentSelect('${a.id}')" ${selectedAgents.has(a.id)?'checked':''}>
|
|
1872
|
+
${renderAccountBadge(a)}
|
|
1873
|
+
<div class="node-avatar ${(isActive || isRecent) ? 'active' : ''}${hasAutomation ? ' automated' : ''}">${initials}</div>
|
|
1874
|
+
<div class="node-info">
|
|
1875
|
+
${title ? `<div class="node-title">${escapeHtml(title)}</div>` : ''}
|
|
1876
|
+
<div class="node-name${blurClass}">${escapeHtml(a.name)}</div>
|
|
1877
|
+
<div class="node-alias${blurClass}">${a.aliases?.[0] || a.id}</div>
|
|
1878
|
+
${func ? `<div style="margin-top:4px"><span class="node-func-tag">${escapeHtml(func)}</span></div>` : ''}
|
|
1879
|
+
<div class="node-stats">
|
|
1880
|
+
<span>${a.messageCount} msgs</span>
|
|
1881
|
+
<span>${a.persistent ? 'persistent' : 'single-shot'}</span>
|
|
1882
|
+
</div>
|
|
1883
|
+
</div>
|
|
1884
|
+
<div class="node-actions">
|
|
1885
|
+
<button class="node-btn" onclick="event.stopPropagation();window.location='/ui#${a.id}'">Chat</button>
|
|
1886
|
+
<button class="node-btn" onclick="event.stopPropagation();window.location='/agent-dashboard?id=${a.id}'">Dashboard</button>
|
|
1887
|
+
<button class="node-btn" onclick="event.stopPropagation();openModal('${a.id}')">Config</button>
|
|
1888
|
+
</div>
|
|
1889
|
+
</div>`;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// ─── View Mode ──────────────────────────────────────────────────
|
|
1893
|
+
function setViewMode(mode) {
|
|
1894
|
+
viewMode = mode;
|
|
1895
|
+
localStorage.setItem('orgViewMode', mode);
|
|
1896
|
+
document.querySelectorAll('.view-toggle-btn').forEach(b => b.classList.toggle('active', b.dataset.view === mode));
|
|
1897
|
+
renderOrg();
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// ─── Compact Node ───────────────────────────────────────────────
|
|
1901
|
+
function renderCompactNode(a, blurNames) {
|
|
1902
|
+
const initials = (a.aliases?.[0] || a.id).replace('@','').slice(0,2).toUpperCase();
|
|
1903
|
+
const isActive = a.sessionActive || (a.lastMessage !== 'never' && (Date.now() - new Date(a.lastMessage).getTime()) < 3600000);
|
|
1904
|
+
const blurClass = blurNames ? ' name-blurred' : '';
|
|
1905
|
+
return `<div class="compact-node" data-agent-id="${a.id}" onclick="if(selectMode){toggleAgentSelect('${a.id}');return}openModal('${a.id}')">
|
|
1906
|
+
<input type="checkbox" class="select-check" data-id="${a.id}" onclick="event.stopPropagation();toggleAgentSelect('${a.id}')" ${selectedAgents.has(a.id)?'checked':''}>
|
|
1907
|
+
${renderAccountBadge(a)}
|
|
1908
|
+
<div class="node-avatar ${isActive ? 'active' : ''}" style="width:28px;height:28px;border-radius:8px;font-size:10px">${initials}</div>
|
|
1909
|
+
<div class="compact-info">
|
|
1910
|
+
<div class="compact-name${blurClass}">${escapeHtml(a.name)}</div>
|
|
1911
|
+
<div class="compact-alias${blurClass}">${a.aliases?.[0] || a.id}</div>
|
|
1912
|
+
</div>
|
|
1913
|
+
<span class="compact-status ${isActive ? 'active' : 'inactive'}"></span>
|
|
1914
|
+
<button class="compact-menu" onclick="event.stopPropagation();toggleCompactMenu(this)" title="Actions">⋯</button>
|
|
1915
|
+
<div class="compact-ctx">
|
|
1916
|
+
<a onclick="event.stopPropagation();window.location='/ui#${a.id}'">Chat</a>
|
|
1917
|
+
<a onclick="event.stopPropagation();window.location='/agent-dashboard?id=${a.id}'">Dashboard</a>
|
|
1918
|
+
<a onclick="event.stopPropagation();openModal('${a.id}')">Config</a>
|
|
1919
|
+
</div>
|
|
1920
|
+
</div>`;
|
|
1921
|
+
}
|
|
1922
|
+
function toggleCompactMenu(btn) {
|
|
1923
|
+
const ctx = btn.nextElementSibling;
|
|
1924
|
+
document.querySelectorAll('.compact-ctx.open').forEach(m => { if (m !== ctx) m.classList.remove('open'); });
|
|
1925
|
+
ctx.classList.toggle('open');
|
|
1926
|
+
}
|
|
1927
|
+
document.addEventListener('click', e => {
|
|
1928
|
+
if (!e.target.closest('.compact-menu') && !e.target.closest('.compact-ctx')) {
|
|
1929
|
+
document.querySelectorAll('.compact-ctx.open').forEach(m => m.classList.remove('open'));
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
|
|
1933
|
+
// ─── Compact Grid Render ────────────────────────────────────────
|
|
1934
|
+
function renderCompactGrid(canvas) {
|
|
1935
|
+
const { orgGroups, noOrg } = getFilteredAgents();
|
|
1936
|
+
let html = '';
|
|
1937
|
+
for (const orgName of Object.keys(orgGroups).sort()) {
|
|
1938
|
+
const oHidden = isOrgHidden(orgName);
|
|
1939
|
+
html += renderOrgBanner(orgName, oHidden);
|
|
1940
|
+
html += '<div class="compact-grid">';
|
|
1941
|
+
for (const a of orgGroups[orgName].sort((a,b)=>(a.name||a.id||'').localeCompare(b.name||b.id||''))) {
|
|
1942
|
+
html += renderCompactNode(a, oHidden);
|
|
1943
|
+
}
|
|
1944
|
+
html += '</div>';
|
|
1945
|
+
}
|
|
1946
|
+
if (noOrg.length > 0) {
|
|
1947
|
+
html += '<div class="org-title-banner" style="opacity:.5">Unassigned</div>';
|
|
1948
|
+
html += '<div class="compact-grid">';
|
|
1949
|
+
for (const a of noOrg.sort((a,b)=>(a.name||a.id||'').localeCompare(b.name||b.id||''))) {
|
|
1950
|
+
html += renderCompactNode(a, false);
|
|
1951
|
+
}
|
|
1952
|
+
html += '</div>';
|
|
1953
|
+
}
|
|
1954
|
+
canvas.innerHTML = html;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// ─── Group by Account Render ────────────────────────────────────
|
|
1958
|
+
function renderByAccount(canvas) {
|
|
1959
|
+
// Group agents by account
|
|
1960
|
+
const accountGroups = {};
|
|
1961
|
+
for (const a of agents) {
|
|
1962
|
+
const ac = a.agentClass || 'standard';
|
|
1963
|
+
if (!visibleClasses.has(ac)) continue;
|
|
1964
|
+
if (!matchesSearch(a)) continue;
|
|
1965
|
+
if (!matchesAccountFilter(a)) continue;
|
|
1966
|
+
const acct = a.claudeAccount || 'default';
|
|
1967
|
+
if (!accountGroups[acct]) accountGroups[acct] = [];
|
|
1968
|
+
accountGroups[acct].push(a);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
if (viewMode === 'list') {
|
|
1972
|
+
// For list view, just render the standard list (already filtered)
|
|
1973
|
+
renderListView(canvas);
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
let html = '';
|
|
1978
|
+
for (const acctName of Object.keys(accountGroups).sort()) {
|
|
1979
|
+
const colorCls = accountColorClass(acctName);
|
|
1980
|
+
const dotColor = document.querySelector(`.${colorCls}`)?.style?.color || 'var(--accent)';
|
|
1981
|
+
html += `<div class="account-group-banner"><span class="account-badge ${colorCls}" style="font-size:11px;padding:2px 10px">${escapeHtml(acctName)}</span><span style="font-size:12px;color:var(--text-muted)">${accountGroups[acctName].length} agent${accountGroups[acctName].length !== 1 ? 's' : ''}</span></div>`;
|
|
1982
|
+
|
|
1983
|
+
if (viewMode === 'compact') {
|
|
1984
|
+
html += '<div class="compact-grid">';
|
|
1985
|
+
for (const a of accountGroups[acctName].sort((a,b)=>(a.name||a.id||'').localeCompare(b.name||b.id||''))) {
|
|
1986
|
+
html += renderCompactNode(a, false);
|
|
1987
|
+
}
|
|
1988
|
+
html += '</div>';
|
|
1989
|
+
} else {
|
|
1990
|
+
html += '<div class="flat-grid">';
|
|
1991
|
+
for (const a of accountGroups[acctName].sort((a,b)=>(a.name||a.id||'').localeCompare(b.name||b.id||''))) {
|
|
1992
|
+
html += renderNode(a, false, false, false);
|
|
1993
|
+
}
|
|
1994
|
+
html += '</div>';
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
if (Object.keys(accountGroups).length === 0) {
|
|
1999
|
+
html = '<div class="empty-org"><div class="empty-org-icon">◇</div><div class="empty-org-text">No agents match current filters</div></div>';
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
canvas.innerHTML = html;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
// ─── Relative Time Helper ───────────────────────────────────────
|
|
2006
|
+
function formatRelativeTime(ts) {
|
|
2007
|
+
if (!ts || ts === 'never') return 'never';
|
|
2008
|
+
const diff = Date.now() - new Date(ts).getTime();
|
|
2009
|
+
if (diff < 0) return 'just now';
|
|
2010
|
+
const mins = Math.floor(diff / 60000);
|
|
2011
|
+
if (mins < 1) return 'just now';
|
|
2012
|
+
if (mins < 60) return mins + 'm ago';
|
|
2013
|
+
const hrs = Math.floor(mins / 60);
|
|
2014
|
+
if (hrs < 24) return hrs + 'h ago';
|
|
2015
|
+
const days = Math.floor(hrs / 24);
|
|
2016
|
+
if (days < 30) return days + 'd ago';
|
|
2017
|
+
return Math.floor(days / 30) + 'mo ago';
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// ─── List/Table Render ──────────────────────────────────────────
|
|
2021
|
+
let listSortCol = 'lastActive';
|
|
2022
|
+
let listSortAsc = false;
|
|
2023
|
+
|
|
2024
|
+
function sortList(col) {
|
|
2025
|
+
if (listSortCol === col) listSortAsc = !listSortAsc;
|
|
2026
|
+
else { listSortCol = col; listSortAsc = true; }
|
|
2027
|
+
renderOrg();
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
function renderListView(canvas) {
|
|
2031
|
+
const { orgGroups, noOrg } = getFilteredAgents();
|
|
2032
|
+
// Flatten all agents into one list
|
|
2033
|
+
const all = [];
|
|
2034
|
+
for (const orgName of Object.keys(orgGroups)) {
|
|
2035
|
+
for (const a of orgGroups[orgName]) {
|
|
2036
|
+
all.push({ ...a, _org: orgName });
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
for (const a of noOrg) all.push({ ...a, _org: '' });
|
|
2040
|
+
|
|
2041
|
+
// Sort
|
|
2042
|
+
all.sort((a, b) => {
|
|
2043
|
+
let va, vb;
|
|
2044
|
+
switch (listSortCol) {
|
|
2045
|
+
case 'name': va = (a.name||a.id||'').toLowerCase(); vb = (b.name||b.id||'').toLowerCase(); break;
|
|
2046
|
+
case 'alias': va = (a.aliases?.[0]||a.id||'').toLowerCase(); vb = (b.aliases?.[0]||b.id||'').toLowerCase(); break;
|
|
2047
|
+
case 'org': va = (a._org||'').toLowerCase(); vb = (b._org||'').toLowerCase(); break;
|
|
2048
|
+
case 'dept': va = (a.orgData?.function||a.org?.[0]?.function||'').toLowerCase(); vb = (b.orgData?.function||b.org?.[0]?.function||'').toLowerCase(); break;
|
|
2049
|
+
case 'title': va = (a.orgData?.title||a.org?.[0]?.title||'').toLowerCase(); vb = (b.orgData?.title||b.org?.[0]?.title||'').toLowerCase(); break;
|
|
2050
|
+
case 'class': va = (a.agentClass||'standard').toLowerCase(); vb = (b.agentClass||'standard').toLowerCase(); break;
|
|
2051
|
+
case 'account': va = (a.claudeAccount||'default').toLowerCase(); vb = (b.claudeAccount||'default').toLowerCase(); break;
|
|
2052
|
+
case 'status': va = a.sessionActive ? 0 : 1; vb = b.sessionActive ? 0 : 1; break;
|
|
2053
|
+
case 'msgs': va = a.messageCount||0; vb = b.messageCount||0; break;
|
|
2054
|
+
case 'lastActive': va = a.lastMessage === 'never' ? 0 : new Date(a.lastMessage).getTime(); vb = b.lastMessage === 'never' ? 0 : new Date(b.lastMessage).getTime(); break;
|
|
2055
|
+
default: va = ''; vb = '';
|
|
2056
|
+
}
|
|
2057
|
+
if (va < vb) return listSortAsc ? -1 : 1;
|
|
2058
|
+
if (va > vb) return listSortAsc ? 1 : -1;
|
|
2059
|
+
return 0;
|
|
2060
|
+
});
|
|
2061
|
+
|
|
2062
|
+
const arrow = col => `<span class="sort-arrow">${listSortCol === col ? (listSortAsc ? '▲' : '▼') : '⇅'}</span>`;
|
|
2063
|
+
const sorted = col => listSortCol === col ? ' sorted' : '';
|
|
2064
|
+
|
|
2065
|
+
let html = '<div class="list-view"><table class="list-table"><thead><tr>';
|
|
2066
|
+
html += `<th class="${sorted('name')}" onclick="sortList('name')">Name ${arrow('name')}</th>`;
|
|
2067
|
+
html += `<th class="${sorted('alias')}" onclick="sortList('alias')">Alias ${arrow('alias')}</th>`;
|
|
2068
|
+
html += `<th class="${sorted('org')}" onclick="sortList('org')">Org ${arrow('org')}</th>`;
|
|
2069
|
+
html += `<th class="${sorted('dept')}" onclick="sortList('dept')">Dept ${arrow('dept')}</th>`;
|
|
2070
|
+
html += `<th class="${sorted('title')}" onclick="sortList('title')">Title ${arrow('title')}</th>`;
|
|
2071
|
+
html += `<th class="${sorted('class')}" onclick="sortList('class')">Class ${arrow('class')}</th>`;
|
|
2072
|
+
html += `<th class="${sorted('account')}" onclick="sortList('account')">Account ${arrow('account')}</th>`;
|
|
2073
|
+
html += `<th class="${sorted('status')}" onclick="sortList('status')">Status ${arrow('status')}</th>`;
|
|
2074
|
+
html += `<th class="${sorted('lastActive')}" onclick="sortList('lastActive')">Last Active ${arrow('lastActive')}</th>`;
|
|
2075
|
+
html += `<th class="${sorted('msgs')}" onclick="sortList('msgs')">Msgs ${arrow('msgs')}</th>`;
|
|
2076
|
+
html += '</tr></thead><tbody>';
|
|
2077
|
+
|
|
2078
|
+
for (const a of all) {
|
|
2079
|
+
const initials = (a.aliases?.[0] || a.id).replace('@','').slice(0,2).toUpperCase();
|
|
2080
|
+
const isActive = a.sessionActive || (a.lastMessage !== 'never' && (Date.now() - new Date(a.lastMessage).getTime()) < 3600000);
|
|
2081
|
+
const dept = a.orgData?.function || a.org?.[0]?.function || '';
|
|
2082
|
+
const title = a.orgData?.title || a.org?.[0]?.title || '';
|
|
2083
|
+
const cls = a.agentClass || 'standard';
|
|
2084
|
+
html += `<tr data-agent-id="${a.id}" onclick="if(selectMode){toggleAgentSelect('${a.id}');return}openModal('${a.id}')">`;
|
|
2085
|
+
html += `<td><input type="checkbox" class="select-check" data-id="${a.id}" onclick="event.stopPropagation();toggleAgentSelect('${a.id}')" ${selectedAgents.has(a.id)?'checked':''}><span class="row-avatar">${initials}</span>${escapeHtml(a.name)}</td>`;
|
|
2086
|
+
html += `<td style="font-family:var(--font-mono);font-size:11px;color:var(--text-muted)">${a.aliases?.[0] || a.id}</td>`;
|
|
2087
|
+
html += `<td>${escapeHtml(a._org)}</td>`;
|
|
2088
|
+
html += `<td>${escapeHtml(dept)}</td>`;
|
|
2089
|
+
html += `<td>${escapeHtml(title)}</td>`;
|
|
2090
|
+
html += `<td><span style="font-family:var(--font-mono);font-size:10px;padding:2px 6px;border-radius:4px;background:var(--accent-bg);color:var(--accent)">${cls}</span></td>`;
|
|
2091
|
+
html += `<td>${renderAccountBadge(a)}</td>`;
|
|
2092
|
+
html += `<td><span class="row-status ${isActive ? 'active' : 'inactive'}"></span> ${isActive ? 'Active' : 'Idle'}</td>`;
|
|
2093
|
+
html += `<td style="font-family:var(--font-mono);font-size:11px;color:var(--text-muted)">${formatRelativeTime(a.lastMessage)}</td>`;
|
|
2094
|
+
html += `<td style="font-family:var(--font-mono);font-size:11px">${a.messageCount||0}</td>`;
|
|
2095
|
+
html += '</tr>';
|
|
2096
|
+
}
|
|
2097
|
+
html += '</tbody></table></div>';
|
|
2098
|
+
canvas.innerHTML = html;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
// ─── Shared: get filtered agents grouped by org ─────────────────
|
|
2102
|
+
function getFilteredAgents() {
|
|
2103
|
+
const orgGroups = {};
|
|
2104
|
+
const noOrg = [];
|
|
2105
|
+
for (const a of agents) {
|
|
2106
|
+
const ac = a.agentClass || 'standard';
|
|
2107
|
+
if (!visibleClasses.has(ac)) continue;
|
|
2108
|
+
if (!matchesSearch(a)) continue;
|
|
2109
|
+
if (!matchesAccountFilter(a)) continue;
|
|
2110
|
+
if (!a.org || a.org.length === 0) {
|
|
2111
|
+
noOrg.push(a);
|
|
2112
|
+
} else {
|
|
2113
|
+
const orgsAdded = new Set();
|
|
2114
|
+
for (const o of a.org) {
|
|
2115
|
+
if (!orgsAdded.has(o.organization)) {
|
|
2116
|
+
const merged = { ...a, orgData: o };
|
|
2117
|
+
if (!matchesSearch(merged)) continue;
|
|
2118
|
+
if (!orgGroups[o.organization]) orgGroups[o.organization] = [];
|
|
2119
|
+
orgGroups[o.organization].push(merged);
|
|
2120
|
+
orgsAdded.add(o.organization);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return { orgGroups, noOrg };
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
function renderOrgBanner(orgName, oHidden) {
|
|
2129
|
+
const oBtnClass = oHidden ? ' active' : '';
|
|
2130
|
+
const oBtnIcon = oHidden ? '🚫' : '👁';
|
|
2131
|
+
const oBtnLabel = oHidden ? 'Hidden' : 'Hide Names';
|
|
2132
|
+
return `<div class="org-title-banner" style="display:flex;align-items:center"><span class="${oHidden ? 'name-blurred' : ''}">${escapeHtml(orgName)}</span><button class="hide-names-btn${oBtnClass}" onclick="toggleHideOrg('${escapeHtml(orgName).replace(/'/g,"\\'")}')" title="Toggle name blur for presentations">${oBtnIcon} ${oBtnLabel}</button></div>`;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
function escapeHtml(s) {
|
|
2136
|
+
return (s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
// ─── Modal Tabs ────────────────────────────────────────────────
|
|
2140
|
+
function switchModalTab(tabId, btn) {
|
|
2141
|
+
document.querySelectorAll('.modal-tab').forEach(t => t.classList.remove('active'));
|
|
2142
|
+
document.querySelectorAll('.modal-tab-content').forEach(t => t.classList.remove('active'));
|
|
2143
|
+
btn.classList.add('active');
|
|
2144
|
+
document.getElementById('tab-' + tabId).classList.add('active');
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
// ─── Modal ─────────────────────────────────────────────────────
|
|
2148
|
+
function togglePill(el) {
|
|
2149
|
+
el.classList.toggle('checked');
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
async function loadExecutorOptions(currentValue) {
|
|
2153
|
+
const sel = document.getElementById('f-executor');
|
|
2154
|
+
if (!sel) return;
|
|
2155
|
+
// Keep first two static options (Platform Default, Claude)
|
|
2156
|
+
while (sel.options.length > 2) sel.remove(2);
|
|
2157
|
+
try {
|
|
2158
|
+
const r = await fetch('/api/config/service');
|
|
2159
|
+
const svc = await r.json();
|
|
2160
|
+
if (svc.multiModelEnabled) {
|
|
2161
|
+
// Cloud providers — add popular models for each configured provider
|
|
2162
|
+
const pk = svc.providerKeys || {};
|
|
2163
|
+
const cloudModels = [
|
|
2164
|
+
{ key: 'openai', prefix: 'openai', label: 'OpenAI', models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'o3-mini'] },
|
|
2165
|
+
{ key: 'xai', prefix: 'grok', label: 'Grok', models: ['grok-3', 'grok-3-mini', 'grok-2'] },
|
|
2166
|
+
{ key: 'google', prefix: 'gemini', label: 'Gemini', models: ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.0-flash'] },
|
|
2167
|
+
{ key: 'groq', prefix: 'groq', label: 'Groq', models: ['llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'mixtral-8x7b-32768'] },
|
|
2168
|
+
{ key: 'together', prefix: 'together', label: 'Together', models: ['meta-llama/Llama-3.3-70B-Instruct', 'mistralai/Mixtral-8x22B-Instruct-v0.1'] },
|
|
2169
|
+
{ key: 'mistral', prefix: 'mistral', label: 'Mistral', models: ['mistral-large-latest', 'mistral-small-latest', 'codestral-latest'] },
|
|
2170
|
+
];
|
|
2171
|
+
for (const provider of cloudModels) {
|
|
2172
|
+
if (pk[provider.key]) {
|
|
2173
|
+
const group = document.createElement('optgroup');
|
|
2174
|
+
group.label = provider.label;
|
|
2175
|
+
for (const model of provider.models) {
|
|
2176
|
+
const opt = document.createElement('option');
|
|
2177
|
+
opt.value = provider.prefix + ':' + model;
|
|
2178
|
+
opt.textContent = model;
|
|
2179
|
+
group.appendChild(opt);
|
|
2180
|
+
}
|
|
2181
|
+
sel.appendChild(group);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
// Fetch available Ollama models
|
|
2185
|
+
try {
|
|
2186
|
+
const ollamaUrl = (svc.ollamaBaseUrl || 'http://localhost:11434') + '/api/tags';
|
|
2187
|
+
const mr = await fetch('/api/ollama-proxy?url=' + encodeURIComponent(ollamaUrl));
|
|
2188
|
+
if (mr.ok) {
|
|
2189
|
+
const data = await mr.json();
|
|
2190
|
+
const models = data.models || [];
|
|
2191
|
+
if (models.length > 0) {
|
|
2192
|
+
const group = document.createElement('optgroup');
|
|
2193
|
+
group.label = 'Ollama (local)';
|
|
2194
|
+
models.forEach(m => {
|
|
2195
|
+
const opt = document.createElement('option');
|
|
2196
|
+
opt.value = 'ollama:' + m.name;
|
|
2197
|
+
opt.textContent = m.name;
|
|
2198
|
+
group.appendChild(opt);
|
|
2199
|
+
});
|
|
2200
|
+
sel.appendChild(group);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
} catch(e) { /* Ollama not reachable */ }
|
|
2204
|
+
}
|
|
2205
|
+
} catch(e) { /* service config fetch failed */ }
|
|
2206
|
+
// If current value is not in the list, add it so it's not lost
|
|
2207
|
+
if (currentValue && currentValue !== 'claude' && !sel.querySelector(`option[value="${currentValue}"]`)) {
|
|
2208
|
+
const opt = document.createElement('option');
|
|
2209
|
+
opt.value = currentValue;
|
|
2210
|
+
opt.textContent = currentValue;
|
|
2211
|
+
sel.appendChild(opt);
|
|
2212
|
+
}
|
|
2213
|
+
sel.value = currentValue || '';
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
function autoAlias() {
|
|
2217
|
+
if (editingAgentId) return; // don't auto-alias in edit mode
|
|
2218
|
+
const id = document.getElementById('f-agentId')?.value || ''.trim();
|
|
2219
|
+
const aliasEl = document.getElementById('f-alias');
|
|
2220
|
+
if (!aliasEl.value || aliasEl.value === '@' + aliasEl.dataset.auto) {
|
|
2221
|
+
const clean = id.replace(/-/g, '');
|
|
2222
|
+
aliasEl.value = clean ? '@' + clean : '';
|
|
2223
|
+
aliasEl.dataset.auto = clean;
|
|
2224
|
+
}
|
|
2225
|
+
// Update agentHome preview
|
|
2226
|
+
const homeEl = document.getElementById('f-agentHome');
|
|
2227
|
+
homeEl.value = id ? '~/Desktop/MyAIforOne Drive/PersonalAgents/' + id : '~/Desktop/MyAIforOne Drive/PersonalAgents/(agent-id)';
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
async function openModal(agentId) {
|
|
2231
|
+
editingAgentId = agentId;
|
|
2232
|
+
|
|
2233
|
+
// Fetch MCPs
|
|
2234
|
+
try {
|
|
2235
|
+
const res = await fetch('/api/mcps');
|
|
2236
|
+
const data = await res.json();
|
|
2237
|
+
availableMcps = data.mcps || [];
|
|
2238
|
+
} catch { availableMcps = []; }
|
|
2239
|
+
|
|
2240
|
+
// Fetch installed skills from marketplace
|
|
2241
|
+
try {
|
|
2242
|
+
const res = await fetch('/api/marketplace/skills');
|
|
2243
|
+
const data = await res.json();
|
|
2244
|
+
availableSkills = (data.items || []).filter(s => s.installed);
|
|
2245
|
+
} catch { availableSkills = []; }
|
|
2246
|
+
|
|
2247
|
+
// Populate Claude Account dropdown
|
|
2248
|
+
const accountSelect = document.getElementById('f-claudeAccount');
|
|
2249
|
+
accountSelect.innerHTML = '<option value="">Default</option>' +
|
|
2250
|
+
availableAccounts.map(a => `<option value="${a}">${a}</option>`).join('');
|
|
2251
|
+
|
|
2252
|
+
const idField = document.getElementById('f-agentId');
|
|
2253
|
+
const aliasField = document.getElementById('f-alias');
|
|
2254
|
+
const nameField = document.getElementById('f-name');
|
|
2255
|
+
const descField = document.getElementById('f-desc');
|
|
2256
|
+
const instrField = document.getElementById('f-instructions');
|
|
2257
|
+
const wsField = document.getElementById('f-workspace');
|
|
2258
|
+
const homeField = document.getElementById('f-agentHome');
|
|
2259
|
+
const persistPill = document.getElementById('f-persistent');
|
|
2260
|
+
const streamPill = document.getElementById('f-streaming');
|
|
2261
|
+
|
|
2262
|
+
if (agentId) {
|
|
2263
|
+
// Edit mode
|
|
2264
|
+
const a = agents.find(x => x.id === agentId);
|
|
2265
|
+
if (!a) return;
|
|
2266
|
+
document.getElementById('modalTitle').textContent = 'Edit Agent';
|
|
2267
|
+
document.getElementById('saveBtn').textContent = 'Save Changes';
|
|
2268
|
+
document.getElementById('modalHint').textContent = 'Changes will rebuild the project.';
|
|
2269
|
+
document.getElementById('deleteBtn').classList.remove('hidden');
|
|
2270
|
+
document.getElementById('deleteBtn').dataset.agentId = agentId;
|
|
2271
|
+
document.getElementById('deleteBtn').dataset.alias = a.aliases?.[0] || agentId;
|
|
2272
|
+
|
|
2273
|
+
idField.value = agentId;
|
|
2274
|
+
idField.readOnly = true;
|
|
2275
|
+
aliasField.value = a.aliases?.[0] || '';
|
|
2276
|
+
nameField.value = a.name;
|
|
2277
|
+
descField.value = a.description || '';
|
|
2278
|
+
wsField.value = a.workspace || '~';
|
|
2279
|
+
homeField.value = a.agentHome || '';
|
|
2280
|
+
|
|
2281
|
+
// Load CLAUDE.md instructions
|
|
2282
|
+
instrField.value = 'Loading...';
|
|
2283
|
+
try {
|
|
2284
|
+
const instrRes = await fetch(`/api/agents/${agentId}/instructions`);
|
|
2285
|
+
const instrData = await instrRes.json();
|
|
2286
|
+
instrField.value = instrData.instructions || '';
|
|
2287
|
+
// Load heartbeat.md if present (keep default text if no saved instructions)
|
|
2288
|
+
if (instrData.heartbeatInstructions) document.getElementById('f-heartbeatInstructions').value = instrData.heartbeatInstructions;
|
|
2289
|
+
} catch { instrField.value = ''; }
|
|
2290
|
+
|
|
2291
|
+
// Load heartbeat cron schedule if present
|
|
2292
|
+
const hbCron = (a.cron || []).find(c => c.message === '[HEARTBEAT]');
|
|
2293
|
+
loadHeartbeatSchedule(hbCron?.schedule || '');
|
|
2294
|
+
const hbEnabledPill = document.getElementById('f-heartbeatEnabled');
|
|
2295
|
+
if (hbCron?.enabled !== false) hbEnabledPill.classList.add('checked'); else hbEnabledPill.classList.remove('checked');
|
|
2296
|
+
|
|
2297
|
+
const memPill = document.getElementById('f-advancedMemory');
|
|
2298
|
+
const autoPill = document.getElementById('f-autonomousCapable');
|
|
2299
|
+
if (a.persistent) persistPill.classList.add('checked'); else persistPill.classList.remove('checked');
|
|
2300
|
+
if (a.streaming) streamPill.classList.add('checked'); else streamPill.classList.remove('checked');
|
|
2301
|
+
if (a.advancedMemory) memPill.classList.add('checked'); else memPill.classList.remove('checked');
|
|
2302
|
+
const wikiPill = document.getElementById('f-wiki');
|
|
2303
|
+
if (a.wiki) wikiPill.classList.add('checked'); else wikiPill.classList.remove('checked');
|
|
2304
|
+
const wikiSyncEnabledPill = document.getElementById('f-wikiSyncEnabled');
|
|
2305
|
+
if (a.wikiSync?.enabled) wikiSyncEnabledPill.classList.add('checked'); else wikiSyncEnabledPill.classList.remove('checked');
|
|
2306
|
+
if (a.wikiSync?.instructions) document.getElementById('f-wikiSyncInstructions').value = a.wikiSync.instructions;
|
|
2307
|
+
setWikiSyncFromCron(a.wikiSync?.schedule || '0 0 * * *');
|
|
2308
|
+
if (a.autonomousCapable !== false) autoPill.classList.add('checked'); else autoPill.classList.remove('checked');
|
|
2309
|
+
|
|
2310
|
+
// Claude Account
|
|
2311
|
+
accountSelect.value = a.claudeAccount || '';
|
|
2312
|
+
|
|
2313
|
+
// Agent Class
|
|
2314
|
+
const agentClassEl = document.getElementById('f-agentClass');
|
|
2315
|
+
if (agentClassEl) agentClassEl.value = a.agentClass || 'standard';
|
|
2316
|
+
|
|
2317
|
+
// Show/hide gym-specific tabs
|
|
2318
|
+
const isGym = (a.agentClass || 'standard') === 'gym';
|
|
2319
|
+
document.querySelectorAll('.gym-only-tab').forEach(el => {
|
|
2320
|
+
el.style.display = isGym ? '' : 'none';
|
|
2321
|
+
});
|
|
2322
|
+
if (isGym) loadGymTabData();
|
|
2323
|
+
|
|
2324
|
+
// Executor
|
|
2325
|
+
loadExecutorOptions(a.executor || '');
|
|
2326
|
+
|
|
2327
|
+
// Auto Commit
|
|
2328
|
+
const commitPill = document.getElementById('f-autoCommit');
|
|
2329
|
+
if (a.autoCommit) commitPill.classList.add('checked'); else commitPill.classList.remove('checked');
|
|
2330
|
+
|
|
2331
|
+
// Timeout
|
|
2332
|
+
const timeoutEl = document.getElementById('f-timeout');
|
|
2333
|
+
if (timeoutEl) timeoutEl.value = Math.round((a.timeout || 120000) / 1000);
|
|
2334
|
+
|
|
2335
|
+
// Skills
|
|
2336
|
+
renderSkillPills(a.skills || []);
|
|
2337
|
+
|
|
2338
|
+
// Tools
|
|
2339
|
+
const agentTools = a.tools || [];
|
|
2340
|
+
document.querySelectorAll('#f-tools .checkbox-pill').forEach(p => {
|
|
2341
|
+
if (agentTools.includes(p.dataset.val)) p.classList.add('checked');
|
|
2342
|
+
else p.classList.remove('checked');
|
|
2343
|
+
});
|
|
2344
|
+
|
|
2345
|
+
// MCPs
|
|
2346
|
+
renderMcpPills(a.mcps || []);
|
|
2347
|
+
loadMcpKeys(agentId, a.mcps || []);
|
|
2348
|
+
|
|
2349
|
+
// Org entries
|
|
2350
|
+
const orgContainer = document.getElementById('orgEntries');
|
|
2351
|
+
orgContainer.innerHTML = '';
|
|
2352
|
+
for (const o of (a.org || [])) {
|
|
2353
|
+
addOrgEntry(o.organization, o.function, o.title, o.reportsTo);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
// Route entries
|
|
2357
|
+
const routeContainer = document.getElementById('routeEntries');
|
|
2358
|
+
routeContainer.innerHTML = '';
|
|
2359
|
+
for (const r of (a.routes || [])) {
|
|
2360
|
+
const parts = r.split(':');
|
|
2361
|
+
addRouteEntry(parts[0], parts.slice(1).join(':'));
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// Schedule entries
|
|
2365
|
+
const schedContainer = document.getElementById('scheduleEntries');
|
|
2366
|
+
schedContainer.innerHTML = '<div class="no-schedules" id="noSchedules" style="display:none">No scheduled messages yet</div>';
|
|
2367
|
+
const cronJobs = a.cron || [];
|
|
2368
|
+
if (cronJobs.length > 0) {
|
|
2369
|
+
for (let ci = 0; ci < cronJobs.length; ci++) {
|
|
2370
|
+
const job = cronJobs[ci];
|
|
2371
|
+
const parsed = cronToHuman(job.schedule);
|
|
2372
|
+
addScheduleEntry(parsed.frequency, parsed.days, parsed.time, job.message, job.channel, job.chatId, parsed.min, ci, job.enabled);
|
|
2373
|
+
}
|
|
2374
|
+
} else {
|
|
2375
|
+
document.getElementById('noSchedules').style.display = '';
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// Goal entries
|
|
2379
|
+
const goalContainer = document.getElementById('goalEntries');
|
|
2380
|
+
goalContainer.innerHTML = '<div class="no-goals" id="noGoals" style="display:none">No goals yet</div>';
|
|
2381
|
+
const agentGoals = a.goals || [];
|
|
2382
|
+
if (agentGoals.length > 0) {
|
|
2383
|
+
for (const g of agentGoals) {
|
|
2384
|
+
addGoalEntry(g);
|
|
2385
|
+
}
|
|
2386
|
+
} else {
|
|
2387
|
+
document.getElementById('noGoals').style.display = '';
|
|
2388
|
+
}
|
|
2389
|
+
} else {
|
|
2390
|
+
// Create mode
|
|
2391
|
+
document.getElementById('modalTitle').textContent = 'Create New Agent';
|
|
2392
|
+
document.getElementById('saveBtn').textContent = 'Create Agent';
|
|
2393
|
+
document.getElementById('modalHint').textContent = 'Agent files created in ~/Desktop/MyAIforOne Drive/PersonalAgents/';
|
|
2394
|
+
document.getElementById('deleteBtn').classList.add('hidden');
|
|
2395
|
+
document.querySelectorAll('.gym-only-tab').forEach(el => el.style.display = 'none');
|
|
2396
|
+
|
|
2397
|
+
idField.value = '';
|
|
2398
|
+
idField.readOnly = false;
|
|
2399
|
+
aliasField.value = '';
|
|
2400
|
+
aliasField.dataset.auto = '';
|
|
2401
|
+
nameField.value = '';
|
|
2402
|
+
descField.value = '';
|
|
2403
|
+
instrField.value = '# Agent Name\n\nDescription of what this agent does.\n\n## Identity\n- Mention alias: @alias\n- Respond when mentioned\n\n## Guidelines\n- Keep responses concise\n- If a task requires multiple steps, summarize what you did\n- If you need clarification, ask\n';
|
|
2404
|
+
wsField.value = '~';
|
|
2405
|
+
homeField.value = '~/Desktop/MyAIforOne Drive/PersonalAgents/(agent-id)';
|
|
2406
|
+
persistPill.classList.add('checked');
|
|
2407
|
+
streamPill.classList.remove('checked');
|
|
2408
|
+
document.getElementById('f-advancedMemory')?.classList.add('checked');
|
|
2409
|
+
document.getElementById('f-wiki')?.classList.remove('checked');
|
|
2410
|
+
document.getElementById('f-autonomousCapable')?.classList.add('checked');
|
|
2411
|
+
document.getElementById('f-autoCommit')?.classList.remove('checked');
|
|
2412
|
+
document.getElementById('f-timeout').value = '14400';
|
|
2413
|
+
renderSkillPills([]);
|
|
2414
|
+
document.getElementById('f-heartbeatInstructions').value = 'Check my tasks and work on the highest priority item. If no tasks are pending, report status.';
|
|
2415
|
+
loadHeartbeatSchedule('');
|
|
2416
|
+
document.getElementById('f-heartbeatEnabled').classList.remove('checked');
|
|
2417
|
+
document.getElementById('heartbeat-collapse')?.classList.add('hidden');
|
|
2418
|
+
document.getElementById('f-wikiSyncEnabled')?.classList.remove('checked');
|
|
2419
|
+
document.getElementById('f-wikiSyncInstructions').value = 'Review learned.md for new facts. Cross-check against context.md. Merge verified facts. Flag contradictions but don\'t overwrite — leave a note for human review.';
|
|
2420
|
+
setWikiSyncFromCron('0 0 * * *');
|
|
2421
|
+
document.getElementById('wikisync-collapse')?.classList.add('hidden');
|
|
2422
|
+
|
|
2423
|
+
// Executor
|
|
2424
|
+
loadExecutorOptions('');
|
|
2425
|
+
|
|
2426
|
+
// Reset tools
|
|
2427
|
+
document.querySelectorAll('#f-tools .checkbox-pill').forEach(p => p.classList.add('checked'));
|
|
2428
|
+
|
|
2429
|
+
// Claude Account
|
|
2430
|
+
accountSelect.value = '';
|
|
2431
|
+
|
|
2432
|
+
// MCPs
|
|
2433
|
+
renderMcpPills([]);
|
|
2434
|
+
document.getElementById('mcpKeysSection').style.display = 'none';
|
|
2435
|
+
document.getElementById('mcpKeysArea').style.display = 'none';
|
|
2436
|
+
|
|
2437
|
+
// Clear org, routes, schedules & goals
|
|
2438
|
+
document.getElementById('orgEntries').innerHTML = '';
|
|
2439
|
+
document.getElementById('routeEntries').innerHTML = '';
|
|
2440
|
+
document.getElementById('scheduleEntries').innerHTML = '<div class="no-schedules" id="noSchedules">No scheduled messages yet</div>';
|
|
2441
|
+
document.getElementById('goalEntries').innerHTML = '<div class="no-goals" id="noGoals">No goals yet</div>';
|
|
2442
|
+
addRouteEntry();
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
// Reset to Overview tab
|
|
2446
|
+
document.querySelectorAll('.modal-tab').forEach((t, i) => t.classList.toggle('active', i === 0));
|
|
2447
|
+
document.querySelectorAll('.modal-tab-content').forEach((t, i) => t.classList.toggle('active', i === 0));
|
|
2448
|
+
|
|
2449
|
+
document.getElementById('agentModal').classList.add('show');
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
function renderMcpPills(selectedMcps) {
|
|
2453
|
+
const mcpGroup = document.getElementById('f-mcps');
|
|
2454
|
+
const mcpHint = document.getElementById('f-mcps-hint');
|
|
2455
|
+
if (availableMcps.length > 0) {
|
|
2456
|
+
mcpGroup.innerHTML = availableMcps.map(m => {
|
|
2457
|
+
const checked = selectedMcps.includes(m) ? ' checked' : '';
|
|
2458
|
+
return `<div class="checkbox-pill${checked}" data-val="${m}" onclick="toggleMcpPill(this)">${m}</div>`;
|
|
2459
|
+
}).join('');
|
|
2460
|
+
mcpHint.textContent = 'Select which MCP servers this agent can use.';
|
|
2461
|
+
} else {
|
|
2462
|
+
mcpGroup.innerHTML = '';
|
|
2463
|
+
mcpHint.textContent = 'No MCPs registered. Add them in config.json.';
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
function toggleMcpPill(el) {
|
|
2468
|
+
togglePill(el);
|
|
2469
|
+
// Refresh the connections section with currently selected MCPs
|
|
2470
|
+
const selectedMcps = getSelectedMcps();
|
|
2471
|
+
const agentId = editingAgentId;
|
|
2472
|
+
if (agentId) {
|
|
2473
|
+
loadMcpKeys(agentId, selectedMcps);
|
|
2474
|
+
} else {
|
|
2475
|
+
// Create mode — show connections section with placeholder
|
|
2476
|
+
showCreateModeConnections(selectedMcps);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
function getSelectedMcps() {
|
|
2481
|
+
return [...document.querySelectorAll('#f-mcps .checkbox-pill.checked')].map(p => p.dataset.val);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
function showCreateModeConnections(selectedMcps) {
|
|
2485
|
+
const section = document.getElementById('mcpKeysSection');
|
|
2486
|
+
const area = document.getElementById('mcpKeysArea');
|
|
2487
|
+
const list = document.getElementById('mcpKeysList');
|
|
2488
|
+
if (!selectedMcps.length) {
|
|
2489
|
+
section.style.display = 'none';
|
|
2490
|
+
area.style.display = 'none';
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
section.style.display = '';
|
|
2494
|
+
area.style.display = '';
|
|
2495
|
+
list.innerHTML = '<div style="font-size:12px;color:var(--text-muted);padding:8px 0">Save the agent first, then connect credentials from the Config modal.</div>';
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
// ─── Skills Pills ─────────────────────────────────────────────
|
|
2499
|
+
function renderSkillPills(selectedSkills) {
|
|
2500
|
+
const group = document.getElementById('f-skills');
|
|
2501
|
+
const hint = document.getElementById('f-skills-hint');
|
|
2502
|
+
const defaultsSection = document.getElementById('defaultSkillsSection');
|
|
2503
|
+
const defaultsList = document.getElementById('defaultSkillsList');
|
|
2504
|
+
|
|
2505
|
+
// Show global default skills as read-only pills
|
|
2506
|
+
const defaults = availableSkills.filter(s => s.isPlatformDefault);
|
|
2507
|
+
if (defaults.length > 0) {
|
|
2508
|
+
defaultsSection.style.display = '';
|
|
2509
|
+
defaultsList.innerHTML = defaults.map(s =>
|
|
2510
|
+
`<span style="display:inline-block;padding:2px 8px;font-size:11px;font-family:var(--font-mono);background:var(--bg-secondary);border:1px solid var(--border);border-radius:4px;color:var(--text-muted)">${s.name || s.id}</span>`
|
|
2511
|
+
).join('');
|
|
2512
|
+
} else {
|
|
2513
|
+
defaultsSection.style.display = 'none';
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
// Show non-default installed skills as selectable pills
|
|
2517
|
+
const assignable = availableSkills.filter(s => !s.isPlatformDefault);
|
|
2518
|
+
if (assignable.length > 0) {
|
|
2519
|
+
group.innerHTML = assignable.map(s => {
|
|
2520
|
+
const checked = selectedSkills.includes(s.id) ? ' checked' : '';
|
|
2521
|
+
const label = s.name || s.id;
|
|
2522
|
+
return `<div class="checkbox-pill${checked}" data-val="${s.id}" onclick="togglePill(this)" title="${s.description || ''}">${label}</div>`;
|
|
2523
|
+
}).join('');
|
|
2524
|
+
hint.textContent = 'Select which skills this agent can use (beyond global defaults).';
|
|
2525
|
+
} else {
|
|
2526
|
+
group.innerHTML = '';
|
|
2527
|
+
hint.textContent = 'No additional skills available. Install skills from the Marketplace.';
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
function getSelectedSkills() {
|
|
2532
|
+
return [...document.querySelectorAll('#f-skills .checkbox-pill.checked')].map(p => p.dataset.val);
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
function promptCreateSkill() {
|
|
2536
|
+
const confirmed = confirm(
|
|
2537
|
+
'This will close the agent editor and take you to the Skill Lab to create a new skill.\n\n' +
|
|
2538
|
+
'Any unsaved changes to this agent will be lost.\n\n' +
|
|
2539
|
+
'Continue?'
|
|
2540
|
+
);
|
|
2541
|
+
if (confirmed) {
|
|
2542
|
+
document.getElementById('editModal').style.display = 'none';
|
|
2543
|
+
window.location.href = '/marketplace?tab=skills&action=create';
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// ─── MCP Keys / Connections ─────────────────────────────────
|
|
2548
|
+
let mcpCatalog = {};
|
|
2549
|
+
|
|
2550
|
+
async function loadMcpKeys(agentId, agentMcps) {
|
|
2551
|
+
const section = document.getElementById('mcpKeysSection');
|
|
2552
|
+
const area = document.getElementById('mcpKeysArea');
|
|
2553
|
+
const list = document.getElementById('mcpKeysList');
|
|
2554
|
+
|
|
2555
|
+
// Always show section — show helpful message when no MCPs selected
|
|
2556
|
+
section.style.display = '';
|
|
2557
|
+
area.style.display = '';
|
|
2558
|
+
|
|
2559
|
+
if (!agentMcps.length) {
|
|
2560
|
+
list.innerHTML = '<div style="font-size:12px;color:var(--text-muted);padding:8px 0">Select MCPs above to configure connections.</div>';
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
list.innerHTML = '<div style="font-size:12px;color:var(--text-muted)">Loading...</div>';
|
|
2564
|
+
|
|
2565
|
+
try {
|
|
2566
|
+
const [catalogRes, keysRes, connRes] = await Promise.all([
|
|
2567
|
+
fetch('/api/mcp-catalog'),
|
|
2568
|
+
fetch(`/api/agents/${agentId}/mcp-keys`),
|
|
2569
|
+
fetch(`/api/agents/${agentId}/mcp-connections`),
|
|
2570
|
+
]);
|
|
2571
|
+
const catalogData = await catalogRes.json();
|
|
2572
|
+
const keysData = await keysRes.json();
|
|
2573
|
+
const connData = await connRes.json();
|
|
2574
|
+
mcpCatalog = catalogData.mcps || {};
|
|
2575
|
+
const configured = keysData.configured || {};
|
|
2576
|
+
const connections = connData.connections || {};
|
|
2577
|
+
|
|
2578
|
+
// Group named connections by base MCP
|
|
2579
|
+
const connectionsByBase = {};
|
|
2580
|
+
for (const [name, info] of Object.entries(connections)) {
|
|
2581
|
+
const base = info.baseMcp;
|
|
2582
|
+
if (!connectionsByBase[base]) connectionsByBase[base] = [];
|
|
2583
|
+
connectionsByBase[base].push({ name, ...info });
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// Filter to base MCPs only (not named instances like gmail-work)
|
|
2587
|
+
const baseMcps = agentMcps.filter(m => !Object.keys(connections).includes(m));
|
|
2588
|
+
|
|
2589
|
+
// Build card data for each MCP
|
|
2590
|
+
const mcpCards = baseMcps.map(mcpName => {
|
|
2591
|
+
const cat = mcpCatalog[mcpName];
|
|
2592
|
+
const isConfigured = !!configured[mcpName];
|
|
2593
|
+
const requiredKeys = cat?.requiredKeys || [];
|
|
2594
|
+
const isOAuth = cat?.oauth || mcpName.startsWith('gmail') || mcpName.startsWith('google') || mcpName.startsWith('youtube');
|
|
2595
|
+
const label = cat?.name || mcpName;
|
|
2596
|
+
const hint = requiredKeys[0]?.hint || '';
|
|
2597
|
+
const envVar = requiredKeys[0]?.env || mcpName.toUpperCase().replace(/-/g, '_') + '_API_KEY';
|
|
2598
|
+
const keyLabel = requiredKeys[0]?.label || 'API Key';
|
|
2599
|
+
const namedConns = connectionsByBase[mcpName] || [];
|
|
2600
|
+
const noKeyRequired = requiredKeys.length === 0 && !isOAuth;
|
|
2601
|
+
const hasAnyConnection = isConfigured || namedConns.some(c => !!configured[c.name]);
|
|
2602
|
+
return { mcpName, cat, isConfigured, requiredKeys, isOAuth, label, hint, envVar, keyLabel, namedConns, noKeyRequired, hasAnyConnection };
|
|
2603
|
+
});
|
|
2604
|
+
|
|
2605
|
+
const connectedMcps = mcpCards.filter(m => m.hasAnyConnection || m.noKeyRequired);
|
|
2606
|
+
const notConnectedMcps = mcpCards.filter(m => !m.hasAnyConnection && !m.noKeyRequired);
|
|
2607
|
+
|
|
2608
|
+
function renderMcpAccordion(m) {
|
|
2609
|
+
const connCount = m.namedConns.length + (m.isConfigured ? 1 : 0);
|
|
2610
|
+
const isReady = m.noKeyRequired || m.isConfigured || connCount > 0;
|
|
2611
|
+
const statusDot = isReady ? '🟢' : '🔴';
|
|
2612
|
+
let summary = '';
|
|
2613
|
+
if (m.noKeyRequired) summary = 'Ready';
|
|
2614
|
+
else if (connCount > 0) summary = `${connCount} account${connCount > 1 ? 's' : ''}`;
|
|
2615
|
+
else summary = 'Not connected';
|
|
2616
|
+
|
|
2617
|
+
const uid = 'mcp-acc-' + m.mcpName.replace(/[^a-z0-9]/gi, '');
|
|
2618
|
+
|
|
2619
|
+
let innerHtml = '';
|
|
2620
|
+
|
|
2621
|
+
// Base MCP key (for non-OAuth, non-noKey MCPs)
|
|
2622
|
+
if (!m.noKeyRequired && !m.isOAuth) {
|
|
2623
|
+
if (m.isConfigured) {
|
|
2624
|
+
innerHtml += `<div class="mcp-key-card connected">
|
|
2625
|
+
<span class="mcp-key-name">Default Key</span>
|
|
2626
|
+
<span class="mcp-key-status ok">Connected</span>
|
|
2627
|
+
<button class="mcp-key-btn disconnect" onclick="event.stopPropagation();disconnectMcpKey('${agentId}','${m.mcpName}')">Disconnect</button>
|
|
2628
|
+
</div>`;
|
|
2629
|
+
} else if (connCount === 0) {
|
|
2630
|
+
innerHtml += `<div class="mcp-key-card">
|
|
2631
|
+
<input class="mcp-key-input" id="mcpkey-${m.mcpName}" placeholder="${escapeHtml(m.keyLabel)}" title="${escapeHtml(m.hint)}">
|
|
2632
|
+
<button class="mcp-key-btn" onclick="event.stopPropagation();saveMcpKey('${agentId}','${m.mcpName}','${m.envVar}')">Save</button>
|
|
2633
|
+
</div>`;
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
// Named connections
|
|
2638
|
+
for (const conn of m.namedConns) {
|
|
2639
|
+
const connConfigured = !!configured[conn.name];
|
|
2640
|
+
innerHtml += `<div class="mcp-key-card ${connConfigured ? 'connected' : ''}">
|
|
2641
|
+
<span class="mcp-key-name">${escapeHtml(conn.label)}</span>
|
|
2642
|
+
<span style="font-size:10px;color:var(--text-muted)">${escapeHtml(conn.description || '')}</span>
|
|
2643
|
+
<span class="mcp-key-status ${connConfigured ? 'ok' : 'missing'}">${connConfigured ? 'Connected' : 'Pending'}</span>
|
|
2644
|
+
<button class="mcp-key-btn disconnect" onclick="event.stopPropagation();deleteConnection('${agentId}','${conn.name}')">Remove</button>
|
|
2645
|
+
</div>`;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// Add Account button (for MCPs that need keys)
|
|
2649
|
+
if (m.requiredKeys.length > 0 || m.isOAuth) {
|
|
2650
|
+
innerHtml += `<div style="margin:4px 0">
|
|
2651
|
+
<button class="mcp-key-btn" style="font-size:10px" onclick="event.stopPropagation();showAddAccount('${agentId}','${m.mcpName}','${m.isOAuth}')">+ Add Account</button>
|
|
2652
|
+
</div>`;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
return `<div class="mcp-accordion" id="${uid}">
|
|
2656
|
+
<div class="mcp-accordion-header" onclick="this.parentElement.classList.toggle('open')">
|
|
2657
|
+
<span class="mcp-accordion-arrow">▸</span>
|
|
2658
|
+
<span class="mcp-accordion-name">${escapeHtml(m.label)}</span>
|
|
2659
|
+
<span class="mcp-accordion-summary">${statusDot} ${summary}</span>
|
|
2660
|
+
</div>
|
|
2661
|
+
<div class="mcp-accordion-body">${innerHtml}</div>
|
|
2662
|
+
</div>`;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
const allMcps = [...connectedMcps, ...notConnectedMcps].sort((a, b) => a.label.localeCompare(b.label));
|
|
2666
|
+
let html = '';
|
|
2667
|
+
if (allMcps.length > 0) {
|
|
2668
|
+
html = allMcps.map(renderMcpAccordion).join('');
|
|
2669
|
+
} else {
|
|
2670
|
+
html = '<div style="font-size:12px;color:var(--text-muted);padding:8px 0">All selected MCPs are ready (no keys required).</div>';
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
list.innerHTML = html;
|
|
2674
|
+
} catch (err) {
|
|
2675
|
+
list.innerHTML = '<div style="font-size:12px;color:var(--amber)">Failed to load MCP keys</div>';
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
function openMcpAuthModal(title, bodyHtml, actionsHtml) {
|
|
2680
|
+
document.getElementById('mcpAuthTitle').textContent = title;
|
|
2681
|
+
document.getElementById('mcpAuthBody').innerHTML = bodyHtml;
|
|
2682
|
+
document.getElementById('mcpAuthActions').innerHTML = actionsHtml;
|
|
2683
|
+
document.getElementById('mcpAuthModal').classList.add('show');
|
|
2684
|
+
}
|
|
2685
|
+
function closeMcpAuthModal() {
|
|
2686
|
+
document.getElementById('mcpAuthModal').classList.remove('show');
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
function showAddAccount(agentId, baseMcp, isOAuth) {
|
|
2690
|
+
const cat = mcpCatalog[baseMcp];
|
|
2691
|
+
const isOAuthBool = isOAuth === 'true' || isOAuth === true;
|
|
2692
|
+
const keyLabel = cat?.requiredKeys?.[0]?.label || 'API Key';
|
|
2693
|
+
const hint = cat?.requiredKeys?.[0]?.hint || '';
|
|
2694
|
+
const mcpBaseUrl = isOAuthBool ? (cat?.url?.replace('/mcp', '') || '') : '';
|
|
2695
|
+
const authUrl = isOAuthBool ? (cat?.authUrl || (mcpBaseUrl ? mcpBaseUrl + '/authorize' : '')) : '';
|
|
2696
|
+
const displayName = cat?.name || baseMcp;
|
|
2697
|
+
|
|
2698
|
+
let bodyHtml = `
|
|
2699
|
+
<div class="form-group">
|
|
2700
|
+
<label class="form-label">Account Label</label>
|
|
2701
|
+
<input class="form-input" id="mcpAuthLabel" placeholder='e.g. "Work", "Personal", "Client - Acme"'>
|
|
2702
|
+
</div>
|
|
2703
|
+
<div class="form-group">
|
|
2704
|
+
<label class="form-label">Description</label>
|
|
2705
|
+
<input class="form-input" id="mcpAuthDesc" placeholder="e.g. email address or account name">
|
|
2706
|
+
</div>`;
|
|
2707
|
+
if (isOAuthBool && authUrl) {
|
|
2708
|
+
bodyHtml += `
|
|
2709
|
+
<div class="form-group">
|
|
2710
|
+
<a class="mcp-oauth-link" href="${escapeHtml(authUrl)}" target="_blank" rel="noopener">Authorize ${escapeHtml(displayName)} →</a>
|
|
2711
|
+
<div class="mcp-auth-hint">Click above to authorize, then paste the refresh token below.</div>
|
|
2712
|
+
</div>`;
|
|
2713
|
+
}
|
|
2714
|
+
bodyHtml += `
|
|
2715
|
+
<div class="form-group">
|
|
2716
|
+
<label class="form-label">${isOAuthBool ? 'Refresh Token' : escapeHtml(keyLabel)}</label>
|
|
2717
|
+
<input class="form-input" id="mcpAuthToken" placeholder="${isOAuthBool ? 'Paste refresh token' : escapeHtml(keyLabel)}" ${hint ? 'title="' + escapeHtml(hint) + '"' : ''}>
|
|
2718
|
+
${hint ? '<div class="mcp-auth-hint">' + escapeHtml(hint) + '</div>' : ''}
|
|
2719
|
+
</div>`;
|
|
2720
|
+
|
|
2721
|
+
const actionsHtml = `
|
|
2722
|
+
<button class="btn-cancel" onclick="closeMcpAuthModal()">Cancel</button>
|
|
2723
|
+
<button class="btn-save" onclick="submitAddAccount('${agentId}','${baseMcp}')">Save</button>`;
|
|
2724
|
+
|
|
2725
|
+
openMcpAuthModal('Add Account — ' + displayName, bodyHtml, actionsHtml);
|
|
2726
|
+
setTimeout(() => { const el = document.getElementById('mcpAuthLabel'); if (el) el.focus(); }, 100);
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
function submitAddAccount(agentId, baseMcp) {
|
|
2730
|
+
const label = document.getElementById('mcpAuthLabel')?.value?.trim();
|
|
2731
|
+
const description = document.getElementById('mcpAuthDesc')?.value?.trim() || '';
|
|
2732
|
+
const token = document.getElementById('mcpAuthToken')?.value?.trim();
|
|
2733
|
+
if (!label) { document.getElementById('mcpAuthLabel').style.borderColor = '#ef4444'; return; }
|
|
2734
|
+
if (!token) { document.getElementById('mcpAuthToken').style.borderColor = '#ef4444'; return; }
|
|
2735
|
+
closeMcpAuthModal();
|
|
2736
|
+
createConnection(agentId, baseMcp, label, description, token);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
async function createConnection(agentId, baseMcp, label, description, value) {
|
|
2740
|
+
const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
2741
|
+
const envVar = (baseMcp.toUpperCase().replace(/-/g, '_') + '_' + slug.toUpperCase().replace(/-/g, '_')).replace(/__+/g, '_');
|
|
2742
|
+
|
|
2743
|
+
try {
|
|
2744
|
+
const res = await fetch(`/api/agents/${agentId}/mcp-connections`, {
|
|
2745
|
+
method: 'POST',
|
|
2746
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2747
|
+
body: JSON.stringify({ baseMcp, label, envVar, value, description }),
|
|
2748
|
+
});
|
|
2749
|
+
const data = await res.json();
|
|
2750
|
+
if (data.ok) {
|
|
2751
|
+
const a = agents.find(x => x.id === agentId);
|
|
2752
|
+
loadMcpKeys(agentId, a?.mcps || []);
|
|
2753
|
+
} else {
|
|
2754
|
+
alert('Failed: ' + (data.error || 'Unknown error'));
|
|
2755
|
+
}
|
|
2756
|
+
} catch (err) {
|
|
2757
|
+
alert('Failed: ' + err.message);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
function deleteConnection(agentId, instanceName) {
|
|
2762
|
+
const bodyHtml = `
|
|
2763
|
+
<div style="font-size:13px;color:var(--text-secondary);line-height:1.5">
|
|
2764
|
+
Remove account <strong style="color:var(--text-primary)">${escapeHtml(instanceName)}</strong>?
|
|
2765
|
+
<div style="margin-top:8px;color:var(--text-muted);font-size:12px">This deletes the saved credentials and removes the connection.</div>
|
|
2766
|
+
</div>`;
|
|
2767
|
+
const actionsHtml = `
|
|
2768
|
+
<button class="btn-cancel" onclick="closeMcpAuthModal()">Cancel</button>
|
|
2769
|
+
<button class="btn-danger" onclick="executeDeleteConnection('${agentId}','${instanceName}')">Remove</button>`;
|
|
2770
|
+
openMcpAuthModal('Remove Connection', bodyHtml, actionsHtml);
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
async function executeDeleteConnection(agentId, instanceName) {
|
|
2774
|
+
closeMcpAuthModal();
|
|
2775
|
+
try {
|
|
2776
|
+
await fetch(`/api/agents/${agentId}/mcp-connections/${instanceName}`, { method: 'DELETE' });
|
|
2777
|
+
const a = agents.find(x => x.id === agentId);
|
|
2778
|
+
if (a) a.mcps = a.mcps.filter(m => m !== instanceName);
|
|
2779
|
+
loadMcpKeys(agentId, getSelectedMcps());
|
|
2780
|
+
} catch (err) {
|
|
2781
|
+
openMcpAuthModal('Error', `<div style="color:#ef4444;font-size:13px">${escapeHtml(err.message)}</div>`,
|
|
2782
|
+
'<button class="btn-cancel" onclick="closeMcpAuthModal()">Close</button>');
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
async function saveMcpKey(agentId, mcpName, envVar) {
|
|
2787
|
+
const input = document.getElementById('mcpkey-' + mcpName);
|
|
2788
|
+
if (!input || !input.value.trim()) {
|
|
2789
|
+
alert('Please enter a key or token');
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
try {
|
|
2793
|
+
const res = await fetch(`/api/agents/${agentId}/mcp-keys`, {
|
|
2794
|
+
method: 'POST',
|
|
2795
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2796
|
+
body: JSON.stringify({ mcpName, envVar, value: input.value.trim() }),
|
|
2797
|
+
});
|
|
2798
|
+
const data = await res.json();
|
|
2799
|
+
if (data.ok) {
|
|
2800
|
+
input.value = '';
|
|
2801
|
+
// Reload using currently selected MCPs from UI (not stale in-memory list)
|
|
2802
|
+
loadMcpKeys(agentId, getSelectedMcps());
|
|
2803
|
+
} else {
|
|
2804
|
+
alert('Failed: ' + (data.error || 'Unknown error'));
|
|
2805
|
+
}
|
|
2806
|
+
} catch (err) {
|
|
2807
|
+
alert('Failed: ' + err.message);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
function disconnectMcpKey(agentId, mcpName) {
|
|
2812
|
+
const displayName = mcpCatalog[mcpName]?.name || mcpName;
|
|
2813
|
+
const bodyHtml = `
|
|
2814
|
+
<div style="font-size:13px;color:var(--text-secondary);line-height:1.5">
|
|
2815
|
+
Disconnect <strong style="color:var(--text-primary)">${escapeHtml(displayName)}</strong>?
|
|
2816
|
+
<div style="margin-top:8px;color:var(--text-muted);font-size:12px">This removes the saved API key/token for this MCP.</div>
|
|
2817
|
+
</div>`;
|
|
2818
|
+
const actionsHtml = `
|
|
2819
|
+
<button class="btn-cancel" onclick="closeMcpAuthModal()">Cancel</button>
|
|
2820
|
+
<button class="btn-danger" onclick="executeDisconnectMcp('${agentId}','${mcpName}')">Disconnect</button>`;
|
|
2821
|
+
openMcpAuthModal('Disconnect MCP', bodyHtml, actionsHtml);
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
async function executeDisconnectMcp(agentId, mcpName) {
|
|
2825
|
+
closeMcpAuthModal();
|
|
2826
|
+
try {
|
|
2827
|
+
await fetch(`/api/agents/${agentId}/mcp-keys/${mcpName}`, { method: 'DELETE' });
|
|
2828
|
+
loadMcpKeys(agentId, getSelectedMcps());
|
|
2829
|
+
} catch (err) {
|
|
2830
|
+
openMcpAuthModal('Error', `<div style="color:#ef4444;font-size:13px">${escapeHtml(err.message)}</div>`,
|
|
2831
|
+
'<button class="btn-cancel" onclick="closeMcpAuthModal()">Close</button>');
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
function closeModal() {
|
|
2836
|
+
document.getElementById('agentModal').classList.remove('show');
|
|
2837
|
+
editingAgentId = null;
|
|
2838
|
+
// If embedded in an iframe (e.g. Home config overlay), tell parent to close
|
|
2839
|
+
if (window !== window.parent) {
|
|
2840
|
+
window.parent.postMessage({ type: 'configModalClosed' }, '*');
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
function addOrgEntry(org, func, title, reportsTo) {
|
|
2845
|
+
const container = document.getElementById('orgEntries');
|
|
2846
|
+
const div = document.createElement('div');
|
|
2847
|
+
div.className = 'org-entry';
|
|
2848
|
+
div.innerHTML = `
|
|
2849
|
+
<input placeholder="Organization" value="${escapeHtml(org||'')}" list="dl-orgs" style="flex:1">
|
|
2850
|
+
<input placeholder="Function" value="${escapeHtml(func||'')}" list="dl-functions" style="flex:1">
|
|
2851
|
+
<input placeholder="Title" value="${escapeHtml(title||'')}" list="dl-titles" style="flex:1">
|
|
2852
|
+
<input placeholder="Reports To" value="${escapeHtml(reportsTo||'')}" list="dl-aliases" style="flex:0.7">
|
|
2853
|
+
<button class="entry-remove" onclick="this.parentElement.remove()">×</button>
|
|
2854
|
+
`;
|
|
2855
|
+
container.appendChild(div);
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
function addRouteEntry(channel, chatId) {
|
|
2859
|
+
const container = document.getElementById('routeEntries');
|
|
2860
|
+
const channels = agents.length > 0
|
|
2861
|
+
? [...new Set(agents.flatMap(a => (a.routes||[]).map(r => r.split(':')[0])))]
|
|
2862
|
+
: ['telegram', 'slack', 'imessage'];
|
|
2863
|
+
|
|
2864
|
+
const div = document.createElement('div');
|
|
2865
|
+
div.className = 'route-entry';
|
|
2866
|
+
div.innerHTML = `
|
|
2867
|
+
<select>
|
|
2868
|
+
${channels.map(c => `<option value="${c}" ${c===channel?'selected':''}>${c}</option>`).join('')}
|
|
2869
|
+
</select>
|
|
2870
|
+
<input placeholder="Chat/Channel ID" value="${escapeHtml(chatId||'')}" />
|
|
2871
|
+
<label class="mention-cb"><input type="checkbox" checked> @mention</label>
|
|
2872
|
+
<button class="entry-remove" onclick="this.parentElement.remove()">×</button>
|
|
2873
|
+
`;
|
|
2874
|
+
container.appendChild(div);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
// ─── Schedule Entries ──────────────────────────────────────────
|
|
2878
|
+
function addScheduleEntry(frequency, days, time, message, channel, chatId, minute, cronIndex, cronEnabled) {
|
|
2879
|
+
const container = document.getElementById('scheduleEntries');
|
|
2880
|
+
const noMsg = document.getElementById('noSchedules');
|
|
2881
|
+
if (noMsg) noMsg.style.display = 'none';
|
|
2882
|
+
|
|
2883
|
+
const channels = agents.length > 0
|
|
2884
|
+
? [...new Set(agents.flatMap(a => (a.routes||[]).map(r => r.split(':')[0])))]
|
|
2885
|
+
: ['telegram', 'slack', 'imessage'];
|
|
2886
|
+
|
|
2887
|
+
const div = document.createElement('div');
|
|
2888
|
+
div.className = 'schedule-entry';
|
|
2889
|
+
div.innerHTML = `
|
|
2890
|
+
<button class="entry-remove" onclick="removeSchedule(this)">×</button>
|
|
2891
|
+
<div class="schedule-row">
|
|
2892
|
+
<span class="schedule-label">Every</span>
|
|
2893
|
+
<select class="schedule-select" data-field="frequency" onchange="updateSchedulePreview(this.closest('.schedule-entry'))">
|
|
2894
|
+
<option value="day" ${frequency==='day'?'selected':''}>Day</option>
|
|
2895
|
+
<option value="weekdays" ${frequency==='weekdays'?'selected':''}>Weekday (Mon-Fri)</option>
|
|
2896
|
+
<option value="week" ${frequency==='week'?'selected':''}>Week</option>
|
|
2897
|
+
<option value="hour" ${frequency==='hour'?'selected':''}>Hour</option>
|
|
2898
|
+
<option value="30min" ${frequency==='30min'?'selected':''}>30 Minutes</option>
|
|
2899
|
+
<option value="15min" ${frequency==='15min'?'selected':''}>15 Minutes</option>
|
|
2900
|
+
</select>
|
|
2901
|
+
<span class="schedule-label day-label" style="${['day','weekdays','hour','30min','15min'].includes(frequency||'day')?'display:none':''}">on</span>
|
|
2902
|
+
<select class="schedule-select day-select" data-field="days" style="${frequency!=='week'?'display:none':''}" onchange="updateSchedulePreview(this.closest('.schedule-entry'))">
|
|
2903
|
+
<option value="1" ${days==='1'?'selected':''}>Monday</option>
|
|
2904
|
+
<option value="2" ${days==='2'?'selected':''}>Tuesday</option>
|
|
2905
|
+
<option value="3" ${days==='3'?'selected':''}>Wednesday</option>
|
|
2906
|
+
<option value="4" ${days==='4'?'selected':''}>Thursday</option>
|
|
2907
|
+
<option value="5" ${days==='5'?'selected':''}>Friday</option>
|
|
2908
|
+
<option value="6" ${days==='6'?'selected':''}>Saturday</option>
|
|
2909
|
+
<option value="0" ${days==='0'?'selected':''}>Sunday</option>
|
|
2910
|
+
</select>
|
|
2911
|
+
<span class="schedule-label time-label" style="${['hour','30min','15min'].includes(frequency||'')?'display:none':''}">at</span>
|
|
2912
|
+
${(function() {
|
|
2913
|
+
const h24 = parseInt(time || '9');
|
|
2914
|
+
const isPM = h24 >= 12;
|
|
2915
|
+
const h12 = h24 === 0 ? 12 : h24 > 12 ? h24 - 12 : h24;
|
|
2916
|
+
const m = minute || '0';
|
|
2917
|
+
const hide = ['hour','30min','15min'].includes(frequency||'');
|
|
2918
|
+
const hourOpts = Array.from({length:12}, (_,i) => {
|
|
2919
|
+
const hr = i === 0 ? 12 : i;
|
|
2920
|
+
return '<option value="'+hr+'" '+(hr===h12?'selected':'')+'>'+hr+'</option>';
|
|
2921
|
+
}).join('');
|
|
2922
|
+
const minOpts = ['0','15','30','45'].map(v =>
|
|
2923
|
+
'<option value="'+v+'" '+(v===m?'selected':'')+'>'+(v==='0'?'00':v)+'</option>'
|
|
2924
|
+
).join('');
|
|
2925
|
+
return '<select class="schedule-select" data-field="timeHour" style="'+(hide?'display:none':'')+'" onchange="updateSchedulePreview(this.closest(\'.schedule-entry\'))">'+hourOpts+'</select>'
|
|
2926
|
+
+ '<span class="schedule-label time-label" style="'+(hide?'display:none':'')+'">:</span>'
|
|
2927
|
+
+ '<select class="schedule-select" data-field="timeMin" style="'+(hide?'display:none':'')+'" onchange="updateSchedulePreview(this.closest(\'.schedule-entry\'))">'+minOpts+'</select>'
|
|
2928
|
+
+ '<select class="schedule-select" data-field="timeAmpm" style="'+(hide?'display:none':'')+'" onchange="updateSchedulePreview(this.closest(\'.schedule-entry\'))">'
|
|
2929
|
+
+ '<option value="AM" '+(isPM?'':'selected')+'>AM</option>'
|
|
2930
|
+
+ '<option value="PM" '+(isPM?'selected':'')+'>PM</option></select>';
|
|
2931
|
+
})()}
|
|
2932
|
+
</div>
|
|
2933
|
+
<div class="schedule-row">
|
|
2934
|
+
<span class="schedule-label">Message:</span>
|
|
2935
|
+
</div>
|
|
2936
|
+
<textarea class="schedule-msg" placeholder="What should the agent do?">${escapeHtml(message||'')}</textarea>
|
|
2937
|
+
<div class="schedule-row" style="margin-top:8px">
|
|
2938
|
+
<span class="schedule-label">Reply on:</span>
|
|
2939
|
+
<select class="schedule-select" data-field="channel">
|
|
2940
|
+
${channels.map(c => `<option value="${c}" ${c===channel?'selected':''}>${c}</option>`).join('')}
|
|
2941
|
+
</select>
|
|
2942
|
+
<input class="schedule-select" style="flex:1;min-width:100px" placeholder="Chat/Channel ID" value="${escapeHtml(chatId||'')}" data-field="chatId">
|
|
2943
|
+
</div>
|
|
2944
|
+
<div class="schedule-preview" id="sched-preview"></div>
|
|
2945
|
+
${cronIndex !== undefined && editingAgentId ? `<div style="display:flex;gap:8px;justify-content:flex-end;padding-top:6px;border-top:1px solid var(--border-dim);margin-top:8px">
|
|
2946
|
+
<button class="mcp-key-btn${cronEnabled === false ? ' disconnect' : ''}" style="font-size:10px" onclick="toggleCron(${cronIndex})">${cronEnabled === false ? 'Resume' : 'Pause'}</button>
|
|
2947
|
+
<button class="mcp-key-btn oauth" style="font-size:11px" onclick="triggerCron(${cronIndex})">Trigger Now</button>
|
|
2948
|
+
</div>` : ''}
|
|
2949
|
+
`;
|
|
2950
|
+
container.appendChild(div);
|
|
2951
|
+
|
|
2952
|
+
// Show/hide day & time based on frequency
|
|
2953
|
+
const freqSel = div.querySelector('[data-field="frequency"]');
|
|
2954
|
+
freqSel.addEventListener('change', () => toggleScheduleFields(div));
|
|
2955
|
+
toggleScheduleFields(div);
|
|
2956
|
+
updateSchedulePreview(div);
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
// ─── Heartbeat schedule helpers ──────────────────────────────
|
|
2960
|
+
function updateHeartbeatFields() {
|
|
2961
|
+
const freq = document.getElementById('hb-frequency').value;
|
|
2962
|
+
const showDay = freq === 'week';
|
|
2963
|
+
const showTime = freq && !['hour','30min','15min'].includes(freq);
|
|
2964
|
+
|
|
2965
|
+
document.getElementById('hb-day-label').style.display = showDay ? '' : 'none';
|
|
2966
|
+
document.getElementById('hb-days').style.display = showDay ? '' : 'none';
|
|
2967
|
+
document.getElementById('hb-time-label').style.display = showTime ? '' : 'none';
|
|
2968
|
+
document.getElementById('hb-hour').style.display = showTime ? '' : 'none';
|
|
2969
|
+
document.getElementById('hb-colon').style.display = showTime ? '' : 'none';
|
|
2970
|
+
document.getElementById('hb-min').style.display = showTime ? '' : 'none';
|
|
2971
|
+
document.getElementById('hb-ampm').style.display = showTime ? '' : 'none';
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
|
|
2975
|
+
function loadHeartbeatSchedule(cronStr) {
|
|
2976
|
+
if (!cronStr) {
|
|
2977
|
+
document.getElementById('hb-frequency').value = '';
|
|
2978
|
+
updateHeartbeatFields();
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2981
|
+
const parsed = cronToHuman(cronStr);
|
|
2982
|
+
document.getElementById('hb-frequency').value = parsed.frequency;
|
|
2983
|
+
|
|
2984
|
+
if (parsed.frequency === 'week') {
|
|
2985
|
+
document.getElementById('hb-days').value = parsed.days || '1';
|
|
2986
|
+
}
|
|
2987
|
+
if (!['hour','30min','15min'].includes(parsed.frequency)) {
|
|
2988
|
+
const h24 = parseInt(parsed.time || '9');
|
|
2989
|
+
const isPM = h24 >= 12;
|
|
2990
|
+
const h12 = h24 === 0 ? 12 : h24 > 12 ? h24 - 12 : h24;
|
|
2991
|
+
document.getElementById('hb-hour').value = String(h12);
|
|
2992
|
+
document.getElementById('hb-min').value = parsed.min || '0';
|
|
2993
|
+
document.getElementById('hb-ampm').value = isPM ? 'PM' : 'AM';
|
|
2994
|
+
}
|
|
2995
|
+
updateHeartbeatFields();
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
function toggleScheduleFields(entry) {
|
|
2999
|
+
const freq = entry.querySelector('[data-field="frequency"]')?.value || 'day';
|
|
3000
|
+
const showDay = freq === 'week';
|
|
3001
|
+
const showTime = !['hour', '30min', '15min'].includes(freq);
|
|
3002
|
+
|
|
3003
|
+
entry.querySelector('.day-label').style.display = showDay ? '' : 'none';
|
|
3004
|
+
entry.querySelector('.day-select').style.display = showDay ? '' : 'none';
|
|
3005
|
+
entry.querySelectorAll('.time-label').forEach(el => el.style.display = showTime ? '' : 'none');
|
|
3006
|
+
entry.querySelectorAll('[data-field="timeHour"],[data-field="timeMin"],[data-field="timeAmpm"]').forEach(el => el.style.display = showTime ? '' : 'none');
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
function updateSchedulePreview(entry) {
|
|
3010
|
+
const cron = buildCronFromEntry(entry);
|
|
3011
|
+
const preview = entry.querySelector('.schedule-preview');
|
|
3012
|
+
if (preview) preview.textContent = 'Cron: ' + cron;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
function buildCronFromEntry(entry) {
|
|
3016
|
+
const freq = entry.querySelector('[data-field="frequency"]')?.value || 'day';
|
|
3017
|
+
const day = entry.querySelector('[data-field="days"]')?.value || '1';
|
|
3018
|
+
let hr = parseInt(entry.querySelector('[data-field="timeHour"]')?.value || '9');
|
|
3019
|
+
const min = entry.querySelector('[data-field="timeMin"]')?.value || '0';
|
|
3020
|
+
const ampm = entry.querySelector('[data-field="timeAmpm"]')?.value || 'AM';
|
|
3021
|
+
|
|
3022
|
+
// Convert 12hr to 24hr
|
|
3023
|
+
if (ampm === 'PM' && hr !== 12) hr += 12;
|
|
3024
|
+
if (ampm === 'AM' && hr === 12) hr = 0;
|
|
3025
|
+
|
|
3026
|
+
switch(freq) {
|
|
3027
|
+
case 'day': return `${min} ${hr} * * *`;
|
|
3028
|
+
case 'weekdays': return `${min} ${hr} * * 1-5`;
|
|
3029
|
+
case 'week': return `${min} ${hr} * * ${day}`;
|
|
3030
|
+
case 'hour': return '0 * * * *';
|
|
3031
|
+
case '30min': return '*/30 * * * *';
|
|
3032
|
+
case '15min': return '*/15 * * * *';
|
|
3033
|
+
default: return `${min} ${hr} * * *`;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
function removeSchedule(btn) {
|
|
3038
|
+
btn.closest('.schedule-entry').remove();
|
|
3039
|
+
const container = document.getElementById('scheduleEntries');
|
|
3040
|
+
if (!container.querySelector('.schedule-entry')) {
|
|
3041
|
+
document.getElementById('noSchedules').style.display = '';
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
// ─── Heartbeat schedule picker ──────────────────────────────────
|
|
3046
|
+
function updateHeartbeatFields() {
|
|
3047
|
+
const freq = document.getElementById('hb-frequency').value;
|
|
3048
|
+
const showDay = freq === 'week';
|
|
3049
|
+
const showTime = !['', 'hour', '30min', '15min'].includes(freq);
|
|
3050
|
+
document.getElementById('hb-day-label').style.display = showDay ? '' : 'none';
|
|
3051
|
+
document.getElementById('hb-days').style.display = showDay ? '' : 'none';
|
|
3052
|
+
document.getElementById('hb-time-label').style.display = showTime ? '' : 'none';
|
|
3053
|
+
document.getElementById('hb-hour').style.display = showTime ? '' : 'none';
|
|
3054
|
+
document.getElementById('hb-colon').style.display = showTime ? '' : 'none';
|
|
3055
|
+
document.getElementById('hb-min').style.display = showTime ? '' : 'none';
|
|
3056
|
+
document.getElementById('hb-ampm').style.display = showTime ? '' : 'none';
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
function buildHeartbeatCron() {
|
|
3060
|
+
const freq = document.getElementById('hb-frequency').value;
|
|
3061
|
+
if (!freq) return '';
|
|
3062
|
+
const day = document.getElementById('hb-days').value || '1';
|
|
3063
|
+
let hr = parseInt(document.getElementById('hb-hour').value || '9');
|
|
3064
|
+
const min = document.getElementById('hb-min').value || '0';
|
|
3065
|
+
const ampm = document.getElementById('hb-ampm').value || 'AM';
|
|
3066
|
+
if (ampm === 'PM' && hr !== 12) hr += 12;
|
|
3067
|
+
if (ampm === 'AM' && hr === 12) hr = 0;
|
|
3068
|
+
switch (freq) {
|
|
3069
|
+
case 'day': return `${min} ${hr} * * *`;
|
|
3070
|
+
case 'weekdays': return `${min} ${hr} * * 1-5`;
|
|
3071
|
+
case 'week': return `${min} ${hr} * * ${day}`;
|
|
3072
|
+
case 'hour': return '0 * * * *';
|
|
3073
|
+
case '30min': return '*/30 * * * *';
|
|
3074
|
+
case '15min': return '*/15 * * * *';
|
|
3075
|
+
default: return '';
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
function setHeartbeatFromCron(cron) {
|
|
3080
|
+
if (!cron) {
|
|
3081
|
+
document.getElementById('hb-frequency').value = '';
|
|
3082
|
+
updateHeartbeatFields();
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
const parsed = cronToHuman(cron);
|
|
3086
|
+
document.getElementById('hb-frequency').value = parsed.frequency;
|
|
3087
|
+
document.getElementById('hb-days').value = parsed.days || '1';
|
|
3088
|
+
const h24 = parseInt(parsed.time || '9');
|
|
3089
|
+
const isPM = h24 >= 12;
|
|
3090
|
+
const h12 = h24 === 0 ? 12 : h24 > 12 ? h24 - 12 : h24;
|
|
3091
|
+
document.getElementById('hb-hour').value = String(h12);
|
|
3092
|
+
document.getElementById('hb-min').value = parsed.min || '0';
|
|
3093
|
+
document.getElementById('hb-ampm').value = isPM ? 'PM' : 'AM';
|
|
3094
|
+
updateHeartbeatFields();
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
// ─── Wiki Learning toggle with cost warning ────────────────────
|
|
3098
|
+
function toggleWikiPill(el) {
|
|
3099
|
+
const isCurrentlyOn = el.classList.contains('checked');
|
|
3100
|
+
if (isCurrentlyOn) {
|
|
3101
|
+
// Turning off — no warning needed
|
|
3102
|
+
el.classList.remove('checked');
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
// Turning on — show cost warning
|
|
3106
|
+
if (confirm('Wiki Learning adds extra token usage to every conversation as the agent evaluates what it learned and writes to learned.md.\n\nThis increases API costs per message. Only enable for agents that accumulate knowledge over time (e.g., concierge, project manager, research agent).\n\nEnable Wiki Learning?')) {
|
|
3107
|
+
el.classList.add('checked');
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// ─── Wiki Sync schedule picker ──────────────────────────────────
|
|
3112
|
+
function updateWikiSyncFields() {
|
|
3113
|
+
const freq = document.getElementById('ws-frequency').value;
|
|
3114
|
+
const showDay = freq === 'week';
|
|
3115
|
+
const showTime = !['', 'hour', '30min', '15min'].includes(freq);
|
|
3116
|
+
document.getElementById('ws-day-label').style.display = showDay ? '' : 'none';
|
|
3117
|
+
document.getElementById('ws-days').style.display = showDay ? '' : 'none';
|
|
3118
|
+
document.getElementById('ws-time-label').style.display = showTime ? '' : 'none';
|
|
3119
|
+
document.getElementById('ws-hour').style.display = showTime ? '' : 'none';
|
|
3120
|
+
document.getElementById('ws-colon').style.display = showTime ? '' : 'none';
|
|
3121
|
+
document.getElementById('ws-min').style.display = showTime ? '' : 'none';
|
|
3122
|
+
document.getElementById('ws-ampm').style.display = showTime ? '' : 'none';
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
function buildWikiSyncCron() {
|
|
3126
|
+
const freq = document.getElementById('ws-frequency').value;
|
|
3127
|
+
if (!freq) return '';
|
|
3128
|
+
const day = document.getElementById('ws-days').value || '1';
|
|
3129
|
+
let hr = parseInt(document.getElementById('ws-hour').value || '12');
|
|
3130
|
+
const min = document.getElementById('ws-min').value || '0';
|
|
3131
|
+
const ampm = document.getElementById('ws-ampm').value || 'AM';
|
|
3132
|
+
if (ampm === 'PM' && hr !== 12) hr += 12;
|
|
3133
|
+
if (ampm === 'AM' && hr === 12) hr = 0;
|
|
3134
|
+
switch (freq) {
|
|
3135
|
+
case 'day': return `${min} ${hr} * * *`;
|
|
3136
|
+
case 'weekdays': return `${min} ${hr} * * 1-5`;
|
|
3137
|
+
case 'week': return `${min} ${hr} * * ${day}`;
|
|
3138
|
+
case 'hour': return '0 * * * *';
|
|
3139
|
+
case '30min': return '*/30 * * * *';
|
|
3140
|
+
case '15min': return '*/15 * * * *';
|
|
3141
|
+
default: return '';
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
function setWikiSyncFromCron(cron) {
|
|
3146
|
+
if (!cron) {
|
|
3147
|
+
document.getElementById('ws-frequency').value = '';
|
|
3148
|
+
updateWikiSyncFields();
|
|
3149
|
+
return;
|
|
3150
|
+
}
|
|
3151
|
+
const parsed = cronToHuman(cron);
|
|
3152
|
+
document.getElementById('ws-frequency').value = parsed.frequency;
|
|
3153
|
+
document.getElementById('ws-days').value = parsed.days || '1';
|
|
3154
|
+
const h24 = parseInt(parsed.time || '12');
|
|
3155
|
+
const isPM = h24 >= 12;
|
|
3156
|
+
const h12 = h24 === 0 ? 12 : h24 > 12 ? h24 - 12 : h24;
|
|
3157
|
+
document.getElementById('ws-hour').value = String(h12);
|
|
3158
|
+
document.getElementById('ws-min').value = parsed.min || '0';
|
|
3159
|
+
document.getElementById('ws-ampm').value = isPM ? 'PM' : 'AM';
|
|
3160
|
+
updateWikiSyncFields();
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
function cronToHuman(cron) {
|
|
3164
|
+
const parts = cron.split(' ');
|
|
3165
|
+
if (parts.length !== 5) return { frequency: 'day', days: '1', time: '9' };
|
|
3166
|
+
const [min, hour, , , dow] = parts;
|
|
3167
|
+
|
|
3168
|
+
if (min === '*/15') return { frequency: '15min', days: '1', time: '9' };
|
|
3169
|
+
if (min === '*/30') return { frequency: '30min', days: '1', time: '9' };
|
|
3170
|
+
if (hour === '*') return { frequency: 'hour', days: '1', time: '9' };
|
|
3171
|
+
|
|
3172
|
+
// Convert 24hr back to hour for the 12hr select
|
|
3173
|
+
// We store the 24hr value in time — addScheduleEntry uses it to set ampm/hour
|
|
3174
|
+
if (dow === '1-5') return { frequency: 'weekdays', days: '1', time: hour, min: min };
|
|
3175
|
+
if (dow === '*') return { frequency: 'day', days: '1', time: hour, min: min };
|
|
3176
|
+
return { frequency: 'week', days: dow, time: hour, min: min };
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
function getSchedulesFromForm() {
|
|
3180
|
+
const entries = document.querySelectorAll('.schedule-entry');
|
|
3181
|
+
return [...entries].map(entry => {
|
|
3182
|
+
const cron = buildCronFromEntry(entry);
|
|
3183
|
+
const msgEl = entry.querySelector('.schedule-msg');
|
|
3184
|
+
const chEl = entry.querySelector('[data-field="channel"]');
|
|
3185
|
+
const cidEl = entry.querySelector('[data-field="chatId"]');
|
|
3186
|
+
if (!msgEl || !chEl || !cidEl) return null;
|
|
3187
|
+
const message = msgEl.value.trim();
|
|
3188
|
+
const channel = chEl.value;
|
|
3189
|
+
const chatId = cidEl.value.trim();
|
|
3190
|
+
return (message && chatId) ? { schedule: cron, message, channel, chatId } : null;
|
|
3191
|
+
}).filter(Boolean);
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
// ─── Goal Entries ────────────────────────────────────────────
|
|
3195
|
+
function addGoalEntry(goal) {
|
|
3196
|
+
const container = document.getElementById('goalEntries');
|
|
3197
|
+
const noMsg = document.getElementById('noGoals');
|
|
3198
|
+
if (noMsg) noMsg.style.display = 'none';
|
|
3199
|
+
|
|
3200
|
+
const channels = agents.length > 0
|
|
3201
|
+
? [...new Set(agents.flatMap(a => (a.routes||[]).map(r => r.split(':')[0])))]
|
|
3202
|
+
: ['telegram', 'slack', 'imessage'];
|
|
3203
|
+
|
|
3204
|
+
const g = goal || {};
|
|
3205
|
+
const enabled = g.enabled !== false;
|
|
3206
|
+
const id = g.id || '';
|
|
3207
|
+
const desc = g.description || '';
|
|
3208
|
+
const criteria = g.successCriteria || '';
|
|
3209
|
+
const instructions = g.instructions || '';
|
|
3210
|
+
const budget = g.budget && g.budget.maxDailyUsd != null ? g.budget.maxDailyUsd : '';
|
|
3211
|
+
// Support multi-channel reportTo
|
|
3212
|
+
const reportTargets = g.reportTo
|
|
3213
|
+
? (Array.isArray(g.reportTo) ? g.reportTo : [g.reportTo])
|
|
3214
|
+
: [];
|
|
3215
|
+
const reportChannel = reportTargets[0] ? reportTargets[0].split(':')[0] : '';
|
|
3216
|
+
const reportChatId = reportTargets[0] ? reportTargets[0].split(':').slice(1).join(':') : '';
|
|
3217
|
+
|
|
3218
|
+
// Parse heartbeat cron
|
|
3219
|
+
const hb = g.heartbeat ? cronToHuman(g.heartbeat) : { frequency: 'day', days: '1', time: '9', min: '0' };
|
|
3220
|
+
|
|
3221
|
+
const div = document.createElement('div');
|
|
3222
|
+
div.className = 'goal-entry';
|
|
3223
|
+
div.innerHTML = `
|
|
3224
|
+
<button class="entry-remove" onclick="removeGoal(this)">×</button>
|
|
3225
|
+
<div class="goal-row">
|
|
3226
|
+
<label class="goal-toggle"><input type="checkbox" data-field="goalEnabled" ${enabled ? 'checked' : ''}> Enabled</label>
|
|
3227
|
+
</div>
|
|
3228
|
+
<div class="goal-row">
|
|
3229
|
+
<div style="flex:1">
|
|
3230
|
+
<label class="goal-label">Description</label>
|
|
3231
|
+
<input class="goal-input" data-field="goalDesc" placeholder="What is this agent responsible for" value="${escapeHtml(desc)}" oninput="autoGoalId(this.closest('.goal-entry'))">
|
|
3232
|
+
</div>
|
|
3233
|
+
</div>
|
|
3234
|
+
<div class="goal-row">
|
|
3235
|
+
<div style="flex:1">
|
|
3236
|
+
<label class="goal-label">ID</label>
|
|
3237
|
+
<input class="goal-input" data-field="goalId" placeholder="auto-generated-from-description" value="${escapeHtml(id)}" style="font-family:var(--font-mono);font-size:11px">
|
|
3238
|
+
</div>
|
|
3239
|
+
</div>
|
|
3240
|
+
<div class="goal-row">
|
|
3241
|
+
<div style="flex:1">
|
|
3242
|
+
<label class="goal-label">Success Criteria</label>
|
|
3243
|
+
<input class="goal-input" data-field="goalCriteria" placeholder="How do we know it's done" value="${escapeHtml(criteria)}">
|
|
3244
|
+
</div>
|
|
3245
|
+
</div>
|
|
3246
|
+
<div class="goal-row">
|
|
3247
|
+
<div style="flex:1">
|
|
3248
|
+
<label class="goal-label">Instructions (optional)</label>
|
|
3249
|
+
<textarea class="goal-textarea" data-field="goalInstructions" placeholder="Step by step guidance">${escapeHtml(instructions)}</textarea>
|
|
3250
|
+
</div>
|
|
3251
|
+
</div>
|
|
3252
|
+
<div class="goal-row">
|
|
3253
|
+
<div style="flex:1">
|
|
3254
|
+
<label class="goal-label">Heartbeat</label>
|
|
3255
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
|
3256
|
+
<span class="schedule-label">Every</span>
|
|
3257
|
+
<select class="goal-select" data-field="goalFreq" onchange="toggleGoalHeartbeatFields(this.closest('.goal-entry'))">
|
|
3258
|
+
<option value="day" ${hb.frequency==='day'?'selected':''}>Day</option>
|
|
3259
|
+
<option value="weekdays" ${hb.frequency==='weekdays'?'selected':''}>Weekday (Mon-Fri)</option>
|
|
3260
|
+
<option value="week" ${hb.frequency==='week'?'selected':''}>Week</option>
|
|
3261
|
+
<option value="hour" ${hb.frequency==='hour'?'selected':''}>Hour</option>
|
|
3262
|
+
</select>
|
|
3263
|
+
<span class="schedule-label goal-day-label" style="${hb.frequency!=='week'?'display:none':''}">on</span>
|
|
3264
|
+
<select class="goal-select goal-day-select" data-field="goalDays" style="${hb.frequency!=='week'?'display:none':''}">
|
|
3265
|
+
<option value="1" ${hb.days==='1'?'selected':''}>Monday</option>
|
|
3266
|
+
<option value="2" ${hb.days==='2'?'selected':''}>Tuesday</option>
|
|
3267
|
+
<option value="3" ${hb.days==='3'?'selected':''}>Wednesday</option>
|
|
3268
|
+
<option value="4" ${hb.days==='4'?'selected':''}>Thursday</option>
|
|
3269
|
+
<option value="5" ${hb.days==='5'?'selected':''}>Friday</option>
|
|
3270
|
+
<option value="6" ${hb.days==='6'?'selected':''}>Saturday</option>
|
|
3271
|
+
<option value="0" ${hb.days==='0'?'selected':''}>Sunday</option>
|
|
3272
|
+
</select>
|
|
3273
|
+
${(function() {
|
|
3274
|
+
const h24 = parseInt(hb.time || '9');
|
|
3275
|
+
const isPM = h24 >= 12;
|
|
3276
|
+
const h12 = h24 === 0 ? 12 : h24 > 12 ? h24 - 12 : h24;
|
|
3277
|
+
const m = hb.min || '0';
|
|
3278
|
+
const hide = hb.frequency === 'hour';
|
|
3279
|
+
const hourOpts = Array.from({length:12}, (_,i) => {
|
|
3280
|
+
const hr = i === 0 ? 12 : i;
|
|
3281
|
+
return '<option value="'+hr+'" '+(hr===h12?'selected':'')+'>'+hr+'</option>';
|
|
3282
|
+
}).join('');
|
|
3283
|
+
const minOpts = ['0','15','30','45'].map(v =>
|
|
3284
|
+
'<option value="'+v+'" '+(v===m?'selected':'')+'>'+(v==='0'?'00':v)+'</option>'
|
|
3285
|
+
).join('');
|
|
3286
|
+
return '<span class="schedule-label goal-time-label" style="'+(hide?'display:none':'')+'">at</span>'
|
|
3287
|
+
+ '<select class="goal-select" data-field="goalTimeHour" style="'+(hide?'display:none':'')+'">'+hourOpts+'</select>'
|
|
3288
|
+
+ '<span class="schedule-label goal-time-label" style="'+(hide?'display:none':'')+'">:</span>'
|
|
3289
|
+
+ '<select class="goal-select" data-field="goalTimeMin" style="'+(hide?'display:none':'')+'">'+minOpts+'</select>'
|
|
3290
|
+
+ '<select class="goal-select" data-field="goalTimeAmpm" style="'+(hide?'display:none':'')+'">'
|
|
3291
|
+
+ '<option value="AM" '+(isPM?'':'selected')+'>AM</option>'
|
|
3292
|
+
+ '<option value="PM" '+(isPM?'selected':'')+'>PM</option></select>';
|
|
3293
|
+
})()}
|
|
3294
|
+
</div>
|
|
3295
|
+
</div>
|
|
3296
|
+
</div>
|
|
3297
|
+
<div class="goal-row">
|
|
3298
|
+
<div style="flex:0.4">
|
|
3299
|
+
<label class="goal-label">Budget ($/day, optional)</label>
|
|
3300
|
+
<input class="goal-input" data-field="goalBudget" type="number" step="0.01" min="0" placeholder="5.00" value="${budget}">
|
|
3301
|
+
</div>
|
|
3302
|
+
<div style="flex:0.6">
|
|
3303
|
+
<label class="goal-label">Report To</label>
|
|
3304
|
+
<div class="goal-report-targets" data-field="goalReportTargets">
|
|
3305
|
+
${reportTargets.length > 0 ? reportTargets.map((t, i) => {
|
|
3306
|
+
const ch = t.split(':')[0];
|
|
3307
|
+
const cid = t.split(':').slice(1).join(':');
|
|
3308
|
+
return `<div style="display:flex;gap:6px;margin-bottom:4px" class="report-target-row">
|
|
3309
|
+
<select class="goal-select" data-field="goalReportChannel" style="width:100px">
|
|
3310
|
+
<option value="">None</option>
|
|
3311
|
+
${channels.map(c => '<option value="'+c+'" '+(c===ch?'selected':'')+'>'+c+'</option>').join('')}
|
|
3312
|
+
</select>
|
|
3313
|
+
<input class="goal-input" data-field="goalReportChatId" placeholder="Chat/Channel ID" value="${escapeHtml(cid)}" style="flex:1">
|
|
3314
|
+
${i > 0 ? '<button class="entry-remove" style="position:static;font-size:14px;width:24px;height:24px" onclick="this.parentElement.remove()">×</button>' : ''}
|
|
3315
|
+
</div>`;
|
|
3316
|
+
}).join('') : `<div style="display:flex;gap:6px;margin-bottom:4px" class="report-target-row">
|
|
3317
|
+
<select class="goal-select" data-field="goalReportChannel" style="width:100px">
|
|
3318
|
+
<option value="">None</option>
|
|
3319
|
+
${channels.map(c => '<option value="'+c+'" '+(c===reportChannel?'selected':'')+'>'+c+'</option>').join('')}
|
|
3320
|
+
</select>
|
|
3321
|
+
<input class="goal-input" data-field="goalReportChatId" placeholder="Chat/Channel ID" value="${escapeHtml(reportChatId)}" style="flex:1">
|
|
3322
|
+
</div>`}
|
|
3323
|
+
</div>
|
|
3324
|
+
<button class="mcp-key-btn" style="font-size:10px;margin-top:2px" onclick="addReportTarget(this)">+ Add Channel</button>
|
|
3325
|
+
</div>
|
|
3326
|
+
</div>
|
|
3327
|
+
${id && editingAgentId ? `<div class="goal-row" style="justify-content:flex-end;gap:8px;padding-top:4px;border-top:1px solid var(--border-dim)">
|
|
3328
|
+
<button class="mcp-key-btn${enabled ? '' : ' disconnect'}" style="font-size:10px" onclick="toggleGoal('${id}')">${enabled ? 'Pause' : 'Resume'}</button>
|
|
3329
|
+
<button class="mcp-key-btn oauth" style="font-size:11px" onclick="triggerGoal('${id}')">Trigger Now</button>
|
|
3330
|
+
</div>` : ''}
|
|
3331
|
+
`;
|
|
3332
|
+
container.appendChild(div);
|
|
3333
|
+
toggleGoalHeartbeatFields(div);
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
function toggleGoalHeartbeatFields(entry) {
|
|
3337
|
+
const freq = entry.querySelector('[data-field="goalFreq"]')?.value || '';
|
|
3338
|
+
const showDay = freq === 'week';
|
|
3339
|
+
const showTime = freq !== 'hour';
|
|
3340
|
+
entry.querySelector('.goal-day-label').style.display = showDay ? '' : 'none';
|
|
3341
|
+
entry.querySelector('.goal-day-select').style.display = showDay ? '' : 'none';
|
|
3342
|
+
entry.querySelectorAll('.goal-time-label').forEach(el => el.style.display = showTime ? '' : 'none');
|
|
3343
|
+
entry.querySelectorAll('[data-field="goalTimeHour"],[data-field="goalTimeMin"],[data-field="goalTimeAmpm"]').forEach(el => el.style.display = showTime ? '' : 'none');
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
function autoGoalId(entry) {
|
|
3347
|
+
const desc = entry.querySelector('[data-field="goalDesc"]').value.trim();
|
|
3348
|
+
const idField = entry.querySelector('[data-field="goalId"]');
|
|
3349
|
+
// Only auto-fill if id is empty or was previously auto-generated
|
|
3350
|
+
if (!idField.dataset.manual) {
|
|
3351
|
+
const kebab = desc.toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, 50);
|
|
3352
|
+
idField.value = kebab;
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
async function triggerGoal(goalId) {
|
|
3357
|
+
if (!editingAgentId) return;
|
|
3358
|
+
if (!confirm(`Trigger goal "${goalId}" now? It will run in the background and report to configured channels.`)) return;
|
|
3359
|
+
try {
|
|
3360
|
+
const res = await fetch(`/api/agents/${editingAgentId}/goals/${goalId}/trigger`, { method: 'POST' });
|
|
3361
|
+
const data = await res.json();
|
|
3362
|
+
if (data.ok) {
|
|
3363
|
+
alert(`Goal "${goalId}" triggered! Running in background. Check configured report channels for results.`);
|
|
3364
|
+
} else {
|
|
3365
|
+
alert('Failed: ' + (data.error || 'Unknown error'));
|
|
3366
|
+
}
|
|
3367
|
+
} catch (err) {
|
|
3368
|
+
alert('Failed: ' + err.message);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
async function toggleGoal(goalId) {
|
|
3373
|
+
if (!editingAgentId) return;
|
|
3374
|
+
try {
|
|
3375
|
+
const res = await fetch(`/api/agents/${editingAgentId}/goals/${goalId}/toggle`, { method: 'POST' });
|
|
3376
|
+
const data = await res.json();
|
|
3377
|
+
if (data.ok) {
|
|
3378
|
+
// Reload goals to reflect new state
|
|
3379
|
+
await fetchData();
|
|
3380
|
+
const a = agents.find(x => x.id === editingAgentId);
|
|
3381
|
+
if (a) {
|
|
3382
|
+
const goalContainer = document.getElementById('goalEntries');
|
|
3383
|
+
goalContainer.innerHTML = '<div class="no-goals" id="noGoals" style="display:none">No goals yet</div>';
|
|
3384
|
+
for (const g of (a.goals || [])) { addGoalEntry(g); }
|
|
3385
|
+
}
|
|
3386
|
+
} else {
|
|
3387
|
+
alert('Failed: ' + (data.error || 'Unknown error'));
|
|
3388
|
+
}
|
|
3389
|
+
} catch (err) {
|
|
3390
|
+
alert('Failed: ' + err.message);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
async function toggleCron(index) {
|
|
3395
|
+
if (!editingAgentId) return;
|
|
3396
|
+
try {
|
|
3397
|
+
const res = await fetch(`/api/agents/${editingAgentId}/cron/${index}/toggle`, { method: 'POST' });
|
|
3398
|
+
const data = await res.json();
|
|
3399
|
+
if (data.ok) {
|
|
3400
|
+
await fetchData();
|
|
3401
|
+
const a = agents.find(x => x.id === editingAgentId);
|
|
3402
|
+
if (a) {
|
|
3403
|
+
const schedContainer = document.getElementById('scheduleEntries');
|
|
3404
|
+
schedContainer.innerHTML = '<div class="no-schedules" id="noSchedules" style="display:none">No scheduled messages yet</div>';
|
|
3405
|
+
const cronJobs = a.cron || [];
|
|
3406
|
+
if (cronJobs.length > 0) {
|
|
3407
|
+
for (let ci = 0; ci < cronJobs.length; ci++) {
|
|
3408
|
+
const job = cronJobs[ci];
|
|
3409
|
+
const parsed = cronToHuman(job.schedule);
|
|
3410
|
+
addScheduleEntry(parsed.frequency, parsed.days, parsed.time, job.message, job.channel, job.chatId, parsed.min, ci, job.enabled);
|
|
3411
|
+
}
|
|
3412
|
+
} else {
|
|
3413
|
+
document.getElementById('noSchedules').style.display = '';
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
} else {
|
|
3417
|
+
alert('Failed: ' + (data.error || 'Unknown error'));
|
|
3418
|
+
}
|
|
3419
|
+
} catch (err) {
|
|
3420
|
+
alert('Failed: ' + err.message);
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
async function triggerCron(index) {
|
|
3425
|
+
if (!editingAgentId) return;
|
|
3426
|
+
if (!confirm('Trigger this schedule now? It will run in the background.')) return;
|
|
3427
|
+
try {
|
|
3428
|
+
const res = await fetch(`/api/agents/${editingAgentId}/cron/${index}/trigger`, { method: 'POST' });
|
|
3429
|
+
const data = await res.json();
|
|
3430
|
+
if (data.ok) {
|
|
3431
|
+
alert('Schedule triggered! Running in background.');
|
|
3432
|
+
} else {
|
|
3433
|
+
alert('Failed: ' + (data.error || 'Unknown error'));
|
|
3434
|
+
}
|
|
3435
|
+
} catch (err) {
|
|
3436
|
+
alert('Failed: ' + err.message);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
function addReportTarget(btn) {
|
|
3441
|
+
const targetsDiv = btn.previousElementSibling;
|
|
3442
|
+
const channels = agents.length > 0
|
|
3443
|
+
? [...new Set(agents.flatMap(a => (a.routes||[]).map(r => r.split(':')[0])))]
|
|
3444
|
+
: ['telegram', 'slack', 'imessage'];
|
|
3445
|
+
const row = document.createElement('div');
|
|
3446
|
+
row.className = 'report-target-row';
|
|
3447
|
+
row.style.cssText = 'display:flex;gap:6px;margin-bottom:4px';
|
|
3448
|
+
row.innerHTML = `
|
|
3449
|
+
<select class="goal-select" data-field="goalReportChannel" style="width:100px">
|
|
3450
|
+
<option value="">None</option>
|
|
3451
|
+
${channels.map(c => '<option value="'+c+'">'+c+'</option>').join('')}
|
|
3452
|
+
</select>
|
|
3453
|
+
<input class="goal-input" data-field="goalReportChatId" placeholder="Chat/Channel ID" style="flex:1">
|
|
3454
|
+
<button class="entry-remove" style="position:static;font-size:14px;width:24px;height:24px" onclick="this.parentElement.remove()">×</button>
|
|
3455
|
+
`;
|
|
3456
|
+
targetsDiv.appendChild(row);
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
function removeGoal(btn) {
|
|
3460
|
+
btn.closest('.goal-entry').remove();
|
|
3461
|
+
const container = document.getElementById('goalEntries');
|
|
3462
|
+
if (!container.querySelector('.goal-entry')) {
|
|
3463
|
+
document.getElementById('noGoals').style.display = '';
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
function buildGoalCron(entry) {
|
|
3468
|
+
const freq = entry.querySelector('[data-field="goalFreq"]')?.value || 'day';
|
|
3469
|
+
const day = entry.querySelector('[data-field="goalDays"]')?.value || '1';
|
|
3470
|
+
let hr = parseInt(entry.querySelector('[data-field="goalTimeHour"]')?.value || '9');
|
|
3471
|
+
const min = entry.querySelector('[data-field="goalTimeMin"]')?.value || '0';
|
|
3472
|
+
const ampm = entry.querySelector('[data-field="goalTimeAmpm"]')?.value || 'AM';
|
|
3473
|
+
if (ampm === 'PM' && hr !== 12) hr += 12;
|
|
3474
|
+
if (ampm === 'AM' && hr === 12) hr = 0;
|
|
3475
|
+
switch(freq) {
|
|
3476
|
+
case 'day': return `${min} ${hr} * * *`;
|
|
3477
|
+
case 'weekdays': return `${min} ${hr} * * 1-5`;
|
|
3478
|
+
case 'week': return `${min} ${hr} * * ${day}`;
|
|
3479
|
+
case 'hour': return '0 * * * *';
|
|
3480
|
+
default: return `${min} ${hr} * * *`;
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
function getGoalsFromForm() {
|
|
3485
|
+
const entries = document.querySelectorAll('.goal-entry');
|
|
3486
|
+
return [...entries].map(entry => {
|
|
3487
|
+
const idEl = entry.querySelector('[data-field="goalId"]');
|
|
3488
|
+
const enabledEl = entry.querySelector('[data-field="goalEnabled"]');
|
|
3489
|
+
const descEl = entry.querySelector('[data-field="goalDesc"]');
|
|
3490
|
+
if (!idEl || !descEl) return null;
|
|
3491
|
+
const id = idEl.value.trim();
|
|
3492
|
+
const enabled = enabledEl?.checked ?? true;
|
|
3493
|
+
const description = descEl.value.trim();
|
|
3494
|
+
const successCriteria = entry.querySelector('[data-field="goalCriteria"]')?.value?.trim() || '';
|
|
3495
|
+
const instructions = entry.querySelector('[data-field="goalInstructions"]')?.value?.trim() || '';
|
|
3496
|
+
const heartbeat = buildGoalCron(entry);
|
|
3497
|
+
const budgetVal = entry.querySelector('[data-field="goalBudget"]').value.trim();
|
|
3498
|
+
// Collect all report targets
|
|
3499
|
+
const reportRows = entry.querySelectorAll('.report-target-row');
|
|
3500
|
+
const reportTargets = [...reportRows].map(row => {
|
|
3501
|
+
const ch = row.querySelector('[data-field="goalReportChannel"]')?.value || '';
|
|
3502
|
+
const cid = row.querySelector('[data-field="goalReportChatId"]')?.value?.trim() || '';
|
|
3503
|
+
return ch && cid ? ch + ':' + cid : null;
|
|
3504
|
+
}).filter(Boolean);
|
|
3505
|
+
|
|
3506
|
+
if (!description || !id) return null;
|
|
3507
|
+
const goal = { id, enabled, description, heartbeat };
|
|
3508
|
+
if (successCriteria) goal.successCriteria = successCriteria;
|
|
3509
|
+
if (instructions) goal.instructions = instructions;
|
|
3510
|
+
if (budgetVal) goal.budget = { maxDailyUsd: parseFloat(budgetVal) };
|
|
3511
|
+
if (reportTargets.length === 1) goal.reportTo = reportTargets[0];
|
|
3512
|
+
else if (reportTargets.length > 1) goal.reportTo = reportTargets;
|
|
3513
|
+
return goal;
|
|
3514
|
+
}).filter(Boolean);
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
// ─── Gym Tab Functions ──────────────────────────────────────────
|
|
3518
|
+
|
|
3519
|
+
const GYM_TRAINERS = [
|
|
3520
|
+
{ id: 'alex', name: 'Alex', initials: 'AX', color: '#22d3ee', style: 'Collaborative' },
|
|
3521
|
+
{ id: 'jordan', name: 'Jordan', initials: 'JD', color: '#f97316', style: 'Direct' },
|
|
3522
|
+
{ id: 'morgan', name: 'Morgan', initials: 'MG', color: '#a78bfa', style: 'Thoughtful' },
|
|
3523
|
+
{ id: 'riley', name: 'Riley', initials: 'RL', color: '#ef4444', style: 'Challenging' },
|
|
3524
|
+
{ id: 'sam', name: 'Sam', initials: 'SM', color: '#4ade80', style: 'Patient' }
|
|
3525
|
+
];
|
|
3526
|
+
|
|
3527
|
+
const GYM_DIMENSIONS = [
|
|
3528
|
+
{ key: 'application', label: 'Application', color: '#22d3ee' },
|
|
3529
|
+
{ key: 'communication', label: 'Communication', color: '#f97316' },
|
|
3530
|
+
{ key: 'knowledge', label: 'Knowledge', color: '#a78bfa' },
|
|
3531
|
+
{ key: 'orchestration', label: 'Orchestration', color: '#ef4444' },
|
|
3532
|
+
{ key: 'craft', label: 'Craft', color: '#4ade80' }
|
|
3533
|
+
];
|
|
3534
|
+
|
|
3535
|
+
let gymProfile = null;
|
|
3536
|
+
|
|
3537
|
+
async function loadGymTabData() {
|
|
3538
|
+
try {
|
|
3539
|
+
const [profileRes, progRes, programsRes, histRes] = await Promise.all([
|
|
3540
|
+
fetch('/api/gym/learner-profile'),
|
|
3541
|
+
fetch('/api/gym/progress'),
|
|
3542
|
+
fetch('/api/gym/programs'),
|
|
3543
|
+
fetch('/api/gym/dimensions/history')
|
|
3544
|
+
]);
|
|
3545
|
+
gymProfile = await profileRes.json();
|
|
3546
|
+
const progress = await progRes.json();
|
|
3547
|
+
const programs = await programsRes.json();
|
|
3548
|
+
const history = await histRes.json();
|
|
3549
|
+
|
|
3550
|
+
renderGymTrainerTab(gymProfile);
|
|
3551
|
+
renderGymLearnerTab(gymProfile);
|
|
3552
|
+
renderGymDimensionsTab(gymProfile, history);
|
|
3553
|
+
renderGymProgramsTab(programs, progress);
|
|
3554
|
+
} catch (e) {
|
|
3555
|
+
console.warn('Failed to load gym data:', e);
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
function renderGymTrainerTab(profile) {
|
|
3560
|
+
const grid = document.getElementById('gymTrainerGrid');
|
|
3561
|
+
const selected = profile.selectedTrainer || 'alex';
|
|
3562
|
+
grid.innerHTML = GYM_TRAINERS.map(t => `
|
|
3563
|
+
<div class="gym-trainer-card ${t.id === selected ? 'selected' : ''}" data-trainer="${t.id}" onclick="selectGymTrainer('${t.id}')"
|
|
3564
|
+
style="padding:16px;border-radius:12px;border:2px solid ${t.id === selected ? t.color : 'var(--border-dim)'};
|
|
3565
|
+
background:${t.id === selected ? t.color + '10' : 'var(--bg-surface)'};cursor:pointer;transition:all .2s;text-align:center">
|
|
3566
|
+
<div style="width:48px;height:48px;border-radius:50%;background:${t.color}20;color:${t.color};
|
|
3567
|
+
display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;margin:0 auto 8px">
|
|
3568
|
+
${t.initials}
|
|
3569
|
+
</div>
|
|
3570
|
+
<div style="font-weight:600;color:var(--text-primary);font-size:14px">${t.name}</div>
|
|
3571
|
+
<div style="font-size:12px;color:var(--text-muted);margin-top:2px">${t.style}</div>
|
|
3572
|
+
${t.id === selected ? '<div style="font-size:11px;color:' + t.color + ';margin-top:6px;font-weight:600">Active</div>' : ''}
|
|
3573
|
+
</div>
|
|
3574
|
+
`).join('');
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
async function selectGymTrainer(trainerId) {
|
|
3578
|
+
try {
|
|
3579
|
+
await fetch('/api/gym/learner-profile', {
|
|
3580
|
+
method: 'PUT',
|
|
3581
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3582
|
+
body: JSON.stringify({ selectedTrainer: trainerId })
|
|
3583
|
+
});
|
|
3584
|
+
if (gymProfile) gymProfile.selectedTrainer = trainerId;
|
|
3585
|
+
renderGymTrainerTab(gymProfile || { selectedTrainer: trainerId });
|
|
3586
|
+
} catch (e) {
|
|
3587
|
+
console.warn('Failed to switch trainer:', e);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
function renderGymLearnerTab(profile) {
|
|
3592
|
+
const identityEl = document.getElementById('gym-learner-identity');
|
|
3593
|
+
const goalsEl = document.getElementById('gym-learner-goals');
|
|
3594
|
+
const streakEl = document.getElementById('gym-learner-streak');
|
|
3595
|
+
const longestEl = document.getElementById('gym-learner-longestStreak');
|
|
3596
|
+
const strengthsEl = document.getElementById('gym-learner-strengths');
|
|
3597
|
+
const strugglesEl = document.getElementById('gym-learner-struggles');
|
|
3598
|
+
|
|
3599
|
+
const identity = profile.identity || {};
|
|
3600
|
+
identityEl.value = typeof identity === 'string' ? identity : JSON.stringify(identity, null, 2);
|
|
3601
|
+
|
|
3602
|
+
const goals = profile.goals || profile.identity?.goals || [];
|
|
3603
|
+
goalsEl.value = Array.isArray(goals) ? goals.join('\n') : (typeof goals === 'string' ? goals : JSON.stringify(goals, null, 2));
|
|
3604
|
+
|
|
3605
|
+
streakEl.value = profile.streak?.current || 0;
|
|
3606
|
+
longestEl.value = profile.streak?.longest || 0;
|
|
3607
|
+
|
|
3608
|
+
const strengths = profile.patterns?.strengths || [];
|
|
3609
|
+
strengthsEl.innerHTML = strengths.length > 0
|
|
3610
|
+
? strengths.map(s => `<span style="display:inline-block;padding:4px 10px;border-radius:8px;background:var(--bg-deep);border:1px solid var(--border-dim);margin:2px;font-size:12px">${s}</span>`).join('')
|
|
3611
|
+
: '<span style="color:var(--text-muted);font-size:12px">None identified yet</span>';
|
|
3612
|
+
|
|
3613
|
+
const struggles = profile.patterns?.struggles || [];
|
|
3614
|
+
strugglesEl.innerHTML = struggles.length > 0
|
|
3615
|
+
? struggles.map(s => `<span style="display:inline-block;padding:4px 10px;border-radius:8px;background:var(--bg-deep);border:1px solid var(--border-dim);margin:2px;font-size:12px">${s}</span>`).join('')
|
|
3616
|
+
: '<span style="color:var(--text-muted);font-size:12px">None identified yet</span>';
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
async function saveGymLearnerProfile() {
|
|
3620
|
+
try {
|
|
3621
|
+
const identityRaw = document.getElementById('gym-learner-identity').value;
|
|
3622
|
+
const goalsRaw = document.getElementById('gym-learner-goals').value;
|
|
3623
|
+
|
|
3624
|
+
let identity;
|
|
3625
|
+
try { identity = JSON.parse(identityRaw); } catch { identity = identityRaw; }
|
|
3626
|
+
|
|
3627
|
+
const goals = goalsRaw.split('\n').map(g => g.trim()).filter(Boolean);
|
|
3628
|
+
|
|
3629
|
+
await fetch('/api/gym/learner-profile', {
|
|
3630
|
+
method: 'PUT',
|
|
3631
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3632
|
+
body: JSON.stringify({ identity, goals })
|
|
3633
|
+
});
|
|
3634
|
+
|
|
3635
|
+
// Refresh
|
|
3636
|
+
const res = await fetch('/api/gym/learner-profile');
|
|
3637
|
+
gymProfile = await res.json();
|
|
3638
|
+
renderGymLearnerTab(gymProfile);
|
|
3639
|
+
} catch (e) {
|
|
3640
|
+
console.warn('Failed to save learner profile:', e);
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
function renderGymDimensionsTab(profile, history) {
|
|
3645
|
+
const barsEl = document.getElementById('gymDimensionBars');
|
|
3646
|
+
const dims = profile.dimensions || {};
|
|
3647
|
+
|
|
3648
|
+
barsEl.innerHTML = GYM_DIMENSIONS.map(d => {
|
|
3649
|
+
const dim = dims[d.key] || {};
|
|
3650
|
+
const score = dim.score || 0;
|
|
3651
|
+
const pct = Math.round((score / 10) * 100);
|
|
3652
|
+
const trend = dim.trend || 'stable';
|
|
3653
|
+
const trendIcon = trend === 'improving' ? '↑' : trend === 'declining' ? '↓' : '•';
|
|
3654
|
+
const trendColor = trend === 'improving' ? '#4ade80' : trend === 'declining' ? '#ef4444' : 'var(--text-muted)';
|
|
3655
|
+
return `
|
|
3656
|
+
<div style="display:flex;align-items:center;gap:12px">
|
|
3657
|
+
<div style="width:110px;font-size:13px;font-weight:500;color:var(--text-primary)">${d.label}</div>
|
|
3658
|
+
<div style="flex:1;height:8px;border-radius:4px;background:var(--bg-deep);overflow:hidden">
|
|
3659
|
+
<div style="width:${pct}%;height:100%;border-radius:4px;background:${d.color};transition:width .4s ease"></div>
|
|
3660
|
+
</div>
|
|
3661
|
+
<div style="width:40px;text-align:right;font-size:13px;font-weight:600;color:var(--text-primary)">${score}/10</div>
|
|
3662
|
+
<div style="width:20px;text-align:center;font-size:14px;color:${trendColor}" title="${trend}">${trendIcon}</div>
|
|
3663
|
+
</div>
|
|
3664
|
+
`;
|
|
3665
|
+
}).join('');
|
|
3666
|
+
|
|
3667
|
+
// History
|
|
3668
|
+
const histEl = document.getElementById('gymDimensionHistory');
|
|
3669
|
+
const snapshots = Array.isArray(history) ? history : [];
|
|
3670
|
+
if (snapshots.length === 0) {
|
|
3671
|
+
histEl.innerHTML = '<span style="color:var(--text-muted);font-size:12px">No snapshots yet. The coach takes snapshots as you progress.</span>';
|
|
3672
|
+
} else {
|
|
3673
|
+
const recent = snapshots.slice(-5).reverse();
|
|
3674
|
+
histEl.innerHTML = `
|
|
3675
|
+
<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
3676
|
+
<thead><tr style="border-bottom:1px solid var(--border-dim)">
|
|
3677
|
+
<th style="text-align:left;padding:6px 8px;color:var(--text-muted);font-weight:500">Date</th>
|
|
3678
|
+
${GYM_DIMENSIONS.map(d => `<th style="text-align:center;padding:6px 4px;color:${d.color};font-weight:500">${d.label.slice(0, 4)}</th>`).join('')}
|
|
3679
|
+
</tr></thead>
|
|
3680
|
+
<tbody>
|
|
3681
|
+
${recent.map(s => `
|
|
3682
|
+
<tr style="border-bottom:1px solid var(--border-dim)">
|
|
3683
|
+
<td style="padding:6px 8px;color:var(--text-secondary)">${s.date || '—'}</td>
|
|
3684
|
+
${GYM_DIMENSIONS.map(d => `<td style="text-align:center;padding:6px 4px;color:var(--text-primary)">${s.dimensions?.[d.key] ?? '—'}</td>`).join('')}
|
|
3685
|
+
</tr>
|
|
3686
|
+
`).join('')}
|
|
3687
|
+
</tbody>
|
|
3688
|
+
</table>
|
|
3689
|
+
`;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
function renderGymProgramsTab(programs, progress) {
|
|
3694
|
+
const listEl = document.getElementById('gymProgramList');
|
|
3695
|
+
const progs = Array.isArray(programs) ? programs : [];
|
|
3696
|
+
const progressData = progress?.programs || {};
|
|
3697
|
+
|
|
3698
|
+
if (progs.length === 0) {
|
|
3699
|
+
listEl.innerHTML = '<span style="color:var(--text-muted);font-size:13px">No programs found.</span>';
|
|
3700
|
+
return;
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
listEl.innerHTML = progs.map(p => {
|
|
3704
|
+
const prog = progressData[p.slug || p.id] || {};
|
|
3705
|
+
const completedSteps = prog.completedSteps?.length || 0;
|
|
3706
|
+
const totalSteps = (p.modules || []).reduce((sum, m) => sum + (m.steps?.length || 0), 0);
|
|
3707
|
+
const isStarted = !!prog.startedAt;
|
|
3708
|
+
const pct = totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0;
|
|
3709
|
+
|
|
3710
|
+
const diffColors = { beginner: '#4ade80', intermediate: '#f97316', advanced: '#ef4444' };
|
|
3711
|
+
const diffColor = diffColors[p.difficulty] || 'var(--text-muted)';
|
|
3712
|
+
|
|
3713
|
+
return `
|
|
3714
|
+
<div style="padding:14px 16px;border-radius:12px;border:1px solid var(--border-dim);background:var(--bg-surface)">
|
|
3715
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px">
|
|
3716
|
+
<div>
|
|
3717
|
+
<div style="font-weight:600;font-size:14px;color:var(--text-primary)">${p.title || p.id}</div>
|
|
3718
|
+
<div style="font-size:12px;color:var(--text-muted);margin-top:2px">
|
|
3719
|
+
<span style="color:${diffColor}">${p.difficulty || 'unknown'}</span>
|
|
3720
|
+
${p.estimatedTime ? ' · ' + p.estimatedTime : ''}
|
|
3721
|
+
${(p.dimensions || []).length > 0 ? ' · ' + p.dimensions.join(', ') : ''}
|
|
3722
|
+
</div>
|
|
3723
|
+
</div>
|
|
3724
|
+
<div style="text-align:right;min-width:80px">
|
|
3725
|
+
${isStarted ? `<div style="font-size:13px;font-weight:600;color:var(--text-primary)">${pct}%</div>` : '<div style="font-size:12px;color:var(--text-muted)">Not started</div>'}
|
|
3726
|
+
</div>
|
|
3727
|
+
</div>
|
|
3728
|
+
${isStarted ? `
|
|
3729
|
+
<div style="margin-top:8px;height:4px;border-radius:2px;background:var(--bg-deep);overflow:hidden">
|
|
3730
|
+
<div style="width:${pct}%;height:100%;border-radius:2px;background:${pct === 100 ? '#4ade80' : 'var(--accent-primary)'};transition:width .3s"></div>
|
|
3731
|
+
</div>
|
|
3732
|
+
<div style="font-size:11px;color:var(--text-muted);margin-top:4px">${completedSteps}/${totalSteps} steps${prog.currentModule ? ' · Current: ' + prog.currentModule : ''}</div>
|
|
3733
|
+
` : ''}
|
|
3734
|
+
${p.description ? `<div style="font-size:12px;color:var(--text-secondary);margin-top:6px;line-height:1.4">${p.description.length > 120 ? p.description.slice(0, 120) + '...' : p.description}</div>` : ''}
|
|
3735
|
+
</div>
|
|
3736
|
+
`;
|
|
3737
|
+
}).join('');
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
async function saveAgent() {
|
|
3741
|
+
const btn = document.getElementById('saveBtn');
|
|
3742
|
+
btn.disabled = true;
|
|
3743
|
+
const originalText = btn.textContent;
|
|
3744
|
+
btn.textContent = 'Saving...';
|
|
3745
|
+
|
|
3746
|
+
const agentId = (document.getElementById('f-agentId')?.value || '').trim();
|
|
3747
|
+
const alias = document.getElementById('f-alias')?.value || ''.trim();
|
|
3748
|
+
const name = document.getElementById('f-name')?.value || ''.trim();
|
|
3749
|
+
const description = document.getElementById('f-desc')?.value || ''.trim();
|
|
3750
|
+
const instructions = document.getElementById('f-instructions')?.value || '';
|
|
3751
|
+
const workspace = document.getElementById('f-workspace')?.value || ''.trim() || '~';
|
|
3752
|
+
const persistent = document.getElementById('f-persistent')?.classList.contains('checked');
|
|
3753
|
+
const streaming = document.getElementById('f-streaming')?.classList.contains('checked');
|
|
3754
|
+
const advancedMemory = document.getElementById('f-advancedMemory')?.classList?.contains('checked') ?? true;
|
|
3755
|
+
const wiki = document.getElementById('f-wiki')?.classList?.contains('checked') ?? false;
|
|
3756
|
+
const wikiSyncEnabled = document.getElementById('f-wikiSyncEnabled')?.classList?.contains('checked') ?? false;
|
|
3757
|
+
const wikiSyncSchedule = buildWikiSyncCron() || '0 0 * * *';
|
|
3758
|
+
const wikiSyncInstructions = document.getElementById('f-wikiSyncInstructions')?.value?.trim() || '';
|
|
3759
|
+
const wikiSync = { enabled: wikiSyncEnabled, schedule: wikiSyncSchedule, instructions: wikiSyncInstructions };
|
|
3760
|
+
const autonomousCapable = document.getElementById('f-autonomousCapable')?.classList?.contains('checked') ?? true;
|
|
3761
|
+
const autoCommit = document.getElementById('f-autoCommit')?.classList?.contains('checked') ?? false;
|
|
3762
|
+
const timeoutSec = parseInt(document.getElementById('f-timeout')?.value || '14400') || 14400;
|
|
3763
|
+
const timeout = timeoutSec * 1000;
|
|
3764
|
+
const claudeAccount = document.getElementById('f-claudeAccount')?.value || undefined;
|
|
3765
|
+
const agentClass = document.getElementById('f-agentClass')?.value || 'standard';
|
|
3766
|
+
const executor = document.getElementById('f-executor')?.value || undefined;
|
|
3767
|
+
const skills = getSelectedSkills();
|
|
3768
|
+
const agentSkills = [];
|
|
3769
|
+
const heartbeatInstructions = document.getElementById('f-heartbeatInstructions')?.value?.trim() || undefined;
|
|
3770
|
+
const heartbeatCron = buildHeartbeatCron() || undefined;
|
|
3771
|
+
const heartbeatEnabled = document.getElementById('f-heartbeatEnabled')?.classList?.contains('checked') ?? false;
|
|
3772
|
+
|
|
3773
|
+
const tools = [...document.querySelectorAll('#f-tools .checkbox-pill.checked')]
|
|
3774
|
+
.map(el => el.dataset.val).filter(Boolean);
|
|
3775
|
+
|
|
3776
|
+
const mcps = [...document.querySelectorAll('#f-mcps .checkbox-pill.checked')]
|
|
3777
|
+
.map(el => el.dataset.val).filter(Boolean);
|
|
3778
|
+
|
|
3779
|
+
// Org entries
|
|
3780
|
+
const orgEls = document.querySelectorAll('.org-entry');
|
|
3781
|
+
const org = [...orgEls].map(el => {
|
|
3782
|
+
const inputs = el.querySelectorAll('input');
|
|
3783
|
+
if (!inputs[0]) return null;
|
|
3784
|
+
const entry = {
|
|
3785
|
+
organization: inputs[0]?.value?.trim() || '',
|
|
3786
|
+
function: inputs[1]?.value?.trim() || '',
|
|
3787
|
+
title: inputs[2]?.value?.trim() || '',
|
|
3788
|
+
};
|
|
3789
|
+
const rt = inputs[3]?.value?.trim() || '';
|
|
3790
|
+
if (rt) entry.reportsTo = rt;
|
|
3791
|
+
return entry;
|
|
3792
|
+
}).filter(o => o.organization);
|
|
3793
|
+
|
|
3794
|
+
// Routes
|
|
3795
|
+
const routeEls = document.querySelectorAll('.route-entry');
|
|
3796
|
+
const routes = [...routeEls].map(el => {
|
|
3797
|
+
const selEl = el.querySelector('select');
|
|
3798
|
+
const idEl = el.querySelector('input[placeholder="Chat/Channel ID"]');
|
|
3799
|
+
if (!selEl || !idEl) return null;
|
|
3800
|
+
const channel = selEl.value;
|
|
3801
|
+
const chatId = idEl.value.trim();
|
|
3802
|
+
const requireMention = el.querySelector('.mention-cb input')?.checked ?? true;
|
|
3803
|
+
return chatId ? { channel, chatId, requireMention } : null;
|
|
3804
|
+
}).filter(Boolean);
|
|
3805
|
+
|
|
3806
|
+
if (!agentId || !name || !alias) {
|
|
3807
|
+
alert('Agent ID, Name, and Alias are required.');
|
|
3808
|
+
btn.disabled = false;
|
|
3809
|
+
btn.textContent = originalText;
|
|
3810
|
+
return;
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
try {
|
|
3814
|
+
let url, method;
|
|
3815
|
+
if (editingAgentId) {
|
|
3816
|
+
url = `/api/agents/${editingAgentId}`;
|
|
3817
|
+
method = 'PUT';
|
|
3818
|
+
} else {
|
|
3819
|
+
url = '/api/agents';
|
|
3820
|
+
method = 'POST';
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
const cron = getSchedulesFromForm();
|
|
3824
|
+
const goals = getGoalsFromForm();
|
|
3825
|
+
|
|
3826
|
+
const res = await fetch(url, {
|
|
3827
|
+
method,
|
|
3828
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3829
|
+
body: JSON.stringify({ agentId, name, description, alias, workspace, persistent, streaming, advancedMemory, wiki, wikiSync, autonomousCapable, autoCommit, timeout, tools, mcps, skills: skills.length > 0 ? skills : undefined, agentSkills: agentSkills.length > 0 ? agentSkills : undefined, routes, org, cron, goals, instructions: instructions || undefined, claudeAccount, agentClass, executor, heartbeatInstructions, heartbeatCron, heartbeatEnabled }),
|
|
3830
|
+
});
|
|
3831
|
+
const data = await res.json();
|
|
3832
|
+
|
|
3833
|
+
if (data.ok) {
|
|
3834
|
+
closeModal();
|
|
3835
|
+
await fetchData();
|
|
3836
|
+
} else {
|
|
3837
|
+
alert('Error: ' + (data.error || 'Unknown error'));
|
|
3838
|
+
}
|
|
3839
|
+
} catch (err) {
|
|
3840
|
+
alert('Error: ' + err.message);
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
btn.disabled = false;
|
|
3844
|
+
btn.textContent = originalText;
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
// Close modal on overlay click
|
|
3848
|
+
document.getElementById('agentModal').addEventListener('click', function(e) {
|
|
3849
|
+
if (e.target === this) closeModal();
|
|
3850
|
+
});
|
|
3851
|
+
|
|
3852
|
+
document.getElementById('mcpAuthModal').addEventListener('click', function(e) {
|
|
3853
|
+
if (e.target === this) closeMcpAuthModal();
|
|
3854
|
+
});
|
|
3855
|
+
|
|
3856
|
+
document.addEventListener('keydown', e => {
|
|
3857
|
+
if (e.key === 'Escape') {
|
|
3858
|
+
if (document.getElementById('mcpAuthModal').classList.contains('show')) {
|
|
3859
|
+
closeMcpAuthModal();
|
|
3860
|
+
} else if (!document.getElementById('deleteOverlay').classList.contains('hidden')) {
|
|
3861
|
+
hideDeleteConfirm();
|
|
3862
|
+
} else {
|
|
3863
|
+
closeModal();
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
});
|
|
3867
|
+
|
|
3868
|
+
// ─── Delete Agent ───────────────────────────────────────────────
|
|
3869
|
+
let deleteTargetId = null;
|
|
3870
|
+
let deleteTargetAlias = null;
|
|
3871
|
+
|
|
3872
|
+
function showDeleteConfirm() {
|
|
3873
|
+
const btn = document.getElementById('deleteBtn');
|
|
3874
|
+
deleteTargetId = btn.dataset.agentId;
|
|
3875
|
+
deleteTargetAlias = btn.dataset.alias;
|
|
3876
|
+
document.getElementById('deleteAliasHint').textContent = deleteTargetAlias;
|
|
3877
|
+
document.getElementById('deleteConfirmInput').value = '';
|
|
3878
|
+
document.getElementById('confirmDeleteBtn').disabled = true;
|
|
3879
|
+
document.getElementById('deleteOverlay').classList.remove('hidden');
|
|
3880
|
+
setTimeout(() => document.getElementById('deleteConfirmInput').focus(), 100);
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
function hideDeleteConfirm() {
|
|
3884
|
+
document.getElementById('deleteOverlay').classList.add('hidden');
|
|
3885
|
+
deleteTargetId = null;
|
|
3886
|
+
deleteTargetAlias = null;
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
// ─── Multi-select & Bulk Delete ─────────────────────────────────
|
|
3890
|
+
let selectMode = false;
|
|
3891
|
+
const selectedAgents = new Set();
|
|
3892
|
+
|
|
3893
|
+
function toggleSelectMode() {
|
|
3894
|
+
selectMode = !selectMode;
|
|
3895
|
+
const btn = document.getElementById('selectModeBtn');
|
|
3896
|
+
const canvas = document.getElementById('canvas');
|
|
3897
|
+
const bar = document.getElementById('bulkBar');
|
|
3898
|
+
btn.classList.toggle('active', selectMode);
|
|
3899
|
+
canvas.classList.toggle('select-mode', selectMode);
|
|
3900
|
+
bar.classList.toggle('hidden', !selectMode);
|
|
3901
|
+
if (!selectMode) { selectedAgents.clear(); updateBulkCount(); }
|
|
3902
|
+
// Update checkboxes
|
|
3903
|
+
document.querySelectorAll('.select-check').forEach(cb => { cb.checked = selectedAgents.has(cb.dataset.id); });
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
function toggleAgentSelect(id) {
|
|
3907
|
+
if (selectedAgents.has(id)) selectedAgents.delete(id); else selectedAgents.add(id);
|
|
3908
|
+
const cb = document.querySelector(`.select-check[data-id="${id}"]`);
|
|
3909
|
+
if (cb) cb.checked = selectedAgents.has(id);
|
|
3910
|
+
const node = document.querySelector(`.agent-node[data-agent-id="${id}"]`);
|
|
3911
|
+
if (node) node.classList.toggle('selected', selectedAgents.has(id));
|
|
3912
|
+
updateBulkCount();
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
function bulkSelectAll() {
|
|
3916
|
+
document.querySelectorAll('.agent-node[data-agent-id]').forEach(n => {
|
|
3917
|
+
const id = n.dataset.agentId;
|
|
3918
|
+
selectedAgents.add(id);
|
|
3919
|
+
n.classList.add('selected');
|
|
3920
|
+
const cb = n.querySelector('.select-check');
|
|
3921
|
+
if (cb) cb.checked = true;
|
|
3922
|
+
});
|
|
3923
|
+
updateBulkCount();
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
function bulkDeselectAll() {
|
|
3927
|
+
selectedAgents.clear();
|
|
3928
|
+
document.querySelectorAll('.agent-node.selected').forEach(n => n.classList.remove('selected'));
|
|
3929
|
+
document.querySelectorAll('.select-check').forEach(cb => { cb.checked = false; });
|
|
3930
|
+
updateBulkCount();
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
function updateBulkCount() {
|
|
3934
|
+
document.getElementById('bulkCount').textContent = `${selectedAgents.size} selected`;
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
async function bulkDelete() {
|
|
3938
|
+
if (selectedAgents.size === 0) return;
|
|
3939
|
+
const ids = [...selectedAgents];
|
|
3940
|
+
const names = ids.map(id => {
|
|
3941
|
+
const node = document.querySelector(`.agent-node[data-agent-id="${id}"]`);
|
|
3942
|
+
return node?.dataset.agentAlias || id;
|
|
3943
|
+
});
|
|
3944
|
+
const confirmed = confirm(`Permanently delete ${ids.length} agent(s)?\n\n${names.join('\n')}\n\nThis cannot be undone.`);
|
|
3945
|
+
if (!confirmed) return;
|
|
3946
|
+
|
|
3947
|
+
// Disable buttons, show progress
|
|
3948
|
+
const bar = document.getElementById('bulkBar');
|
|
3949
|
+
const countEl = document.getElementById('bulkCount');
|
|
3950
|
+
bar.querySelectorAll('.bulk-btn').forEach(b => { b.disabled = true; b.style.opacity = '0.5'; });
|
|
3951
|
+
countEl.style.minWidth = '200px';
|
|
3952
|
+
|
|
3953
|
+
let deleted = 0, failed = 0;
|
|
3954
|
+
const total = ids.length;
|
|
3955
|
+
for (const id of ids) {
|
|
3956
|
+
const alias = document.querySelector(`.agent-node[data-agent-id="${id}"]`)?.dataset.agentAlias || id;
|
|
3957
|
+
countEl.textContent = `Deleting ${deleted + failed + 1}/${total}...`;
|
|
3958
|
+
// Fade out the card being deleted
|
|
3959
|
+
const card = document.querySelector(`.agent-node[data-agent-id="${id}"]`);
|
|
3960
|
+
if (card) { card.style.transition = 'opacity 0.3s'; card.style.opacity = '0.2'; }
|
|
3961
|
+
try {
|
|
3962
|
+
const res = await fetch(`/api/agents/${id}`, {
|
|
3963
|
+
method: 'DELETE',
|
|
3964
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3965
|
+
body: JSON.stringify({ confirmAlias: alias }),
|
|
3966
|
+
});
|
|
3967
|
+
const data = await res.json();
|
|
3968
|
+
if (data.ok) { deleted++; if (card) card.remove(); } else failed++;
|
|
3969
|
+
} catch { failed++; }
|
|
3970
|
+
}
|
|
3971
|
+
countEl.textContent = `Done — ${deleted} deleted${failed ? `, ${failed} failed` : ''}`;
|
|
3972
|
+
selectedAgents.clear();
|
|
3973
|
+
await fetchData();
|
|
3974
|
+
setTimeout(() => toggleSelectMode(), 1500);
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
function checkDeleteInput() {
|
|
3978
|
+
const val = document.getElementById('deleteConfirmInput').value.trim();
|
|
3979
|
+
document.getElementById('confirmDeleteBtn').disabled = (val !== deleteTargetAlias);
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
async function executeDelete() {
|
|
3983
|
+
if (!deleteTargetId || !deleteTargetAlias) return;
|
|
3984
|
+
const btn = document.getElementById('confirmDeleteBtn');
|
|
3985
|
+
btn.textContent = 'Deleting...';
|
|
3986
|
+
btn.disabled = true;
|
|
3987
|
+
|
|
3988
|
+
try {
|
|
3989
|
+
const res = await fetch(`/api/agents/${deleteTargetId}`, {
|
|
3990
|
+
method: 'DELETE',
|
|
3991
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3992
|
+
body: JSON.stringify({ confirmAlias: deleteTargetAlias }),
|
|
3993
|
+
});
|
|
3994
|
+
const data = await res.json();
|
|
3995
|
+
if (data.ok) {
|
|
3996
|
+
hideDeleteConfirm();
|
|
3997
|
+
closeModal();
|
|
3998
|
+
await fetchData();
|
|
3999
|
+
alert(`Agent "${deleteTargetAlias}" has been permanently deleted.`);
|
|
4000
|
+
} else {
|
|
4001
|
+
alert(`Delete failed: ${data.error}`);
|
|
4002
|
+
btn.textContent = 'Delete Forever';
|
|
4003
|
+
btn.disabled = false;
|
|
4004
|
+
}
|
|
4005
|
+
} catch (err) {
|
|
4006
|
+
alert(`Delete failed: ${err.message}`);
|
|
4007
|
+
btn.textContent = 'Delete Forever';
|
|
4008
|
+
btn.disabled = false;
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
// ─── Channels Panel ────────────────────────────────────────────
|
|
4013
|
+
const channelIcons = {
|
|
4014
|
+
telegram: '\u{1F4AC}',
|
|
4015
|
+
slack: '\u{1F4E2}',
|
|
4016
|
+
imessage: '\u{1F4F1}',
|
|
4017
|
+
whatsapp: '\u{1F4F2}',
|
|
4018
|
+
};
|
|
4019
|
+
|
|
4020
|
+
function renderChannelsPanel(canvas) {
|
|
4021
|
+
if (!channelsData || channelsData.length === 0) {
|
|
4022
|
+
canvas.innerHTML = '<div class="empty-org"><div class="empty-org-icon">📡</div><div class="empty-org-text">No channels configured</div></div>';
|
|
4023
|
+
return;
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
let html = '<div class="org-title-banner">Channels</div>';
|
|
4027
|
+
html += '<div class="channels-grid">';
|
|
4028
|
+
|
|
4029
|
+
for (const ch of channelsData) {
|
|
4030
|
+
const icon = channelIcons[ch.name] || '\u{1F517}';
|
|
4031
|
+
const statusClass = ch.enabled ? 'enabled' : 'disabled';
|
|
4032
|
+
const statusText = ch.enabled ? 'Connected' : 'Disabled';
|
|
4033
|
+
const timeoutMin = Math.round((ch.stickyTimeoutMs || 300000) / 60000);
|
|
4034
|
+
|
|
4035
|
+
html += `<div class="channel-card" id="ch-card-${ch.name}">
|
|
4036
|
+
<div class="channel-card-header">
|
|
4037
|
+
<div class="channel-card-icon">${icon}</div>
|
|
4038
|
+
<div class="channel-card-name">${escapeHtml(ch.name)}</div>
|
|
4039
|
+
<span class="channel-status-pill ${statusClass}">${statusText}</span>
|
|
4040
|
+
</div>
|
|
4041
|
+
<div class="channel-card-body">
|
|
4042
|
+
<div class="channel-setting-row">
|
|
4043
|
+
<span class="channel-setting-label">Sticky Mode</span>
|
|
4044
|
+
<select class="channel-setting-select" id="ch-sticky-${ch.name}" onchange="togglePrefixVisibility('${ch.name}')">
|
|
4045
|
+
<option value="none" ${ch.stickyRouting==='none'?'selected':''}>None</option>
|
|
4046
|
+
<option value="sticky" ${ch.stickyRouting==='sticky'?'selected':''}>Sticky</option>
|
|
4047
|
+
<option value="prefix" ${ch.stickyRouting==='prefix'?'selected':''}>Prefix</option>
|
|
4048
|
+
</select>
|
|
4049
|
+
</div>
|
|
4050
|
+
<div class="channel-setting-row" id="ch-prefix-row-${ch.name}" style="${ch.stickyRouting==='prefix'?'':'display:none'}">
|
|
4051
|
+
<span class="channel-setting-label">Prefix</span>
|
|
4052
|
+
<input class="channel-setting-input" id="ch-prefix-${ch.name}" value="${escapeHtml(ch.stickyPrefix || '!')}" style="width:60px">
|
|
4053
|
+
</div>
|
|
4054
|
+
<div class="channel-setting-row">
|
|
4055
|
+
<span class="channel-setting-label">Timeout (min)</span>
|
|
4056
|
+
<input class="channel-setting-input" id="ch-timeout-${ch.name}" type="number" min="1" value="${timeoutMin}" style="width:80px">
|
|
4057
|
+
</div>
|
|
4058
|
+
<button class="channel-save-btn" onclick="saveChannelSettings('${ch.name}')">Save Settings</button>
|
|
4059
|
+
<div class="channel-restart-note">Changes require service restart to take effect</div>`;
|
|
4060
|
+
|
|
4061
|
+
// iMessage monitored chat IDs
|
|
4062
|
+
if (ch.name === 'imessage' && ch.monitoredChatIds) {
|
|
4063
|
+
html += `<div class="channel-section-title">Monitored Chat IDs</div>
|
|
4064
|
+
<div class="monitored-ids" id="ch-monitored-${ch.name}">`;
|
|
4065
|
+
for (const mid of ch.monitoredChatIds) {
|
|
4066
|
+
html += `<div class="monitored-pill">${mid}<button onclick="removeMonitoredId(${mid})">×</button></div>`;
|
|
4067
|
+
}
|
|
4068
|
+
html += `</div>
|
|
4069
|
+
<div class="channel-add-row" style="margin-top:6px">
|
|
4070
|
+
<input class="channel-add-input" id="ch-addmon-${ch.name}" type="number" placeholder="Chat ID (number)" style="width:140px">
|
|
4071
|
+
<button class="channel-add-btn" onclick="addMonitoredId('${ch.name}')">+ Add</button>
|
|
4072
|
+
</div>`;
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
// Agents grouped by chat ID with filter dropdown
|
|
4076
|
+
const chatGroups = {};
|
|
4077
|
+
for (const ag of ch.agents) {
|
|
4078
|
+
if (!chatGroups[ag.chatId]) chatGroups[ag.chatId] = [];
|
|
4079
|
+
chatGroups[ag.chatId].push(ag);
|
|
4080
|
+
}
|
|
4081
|
+
const chatIds = Object.keys(chatGroups).sort();
|
|
4082
|
+
html += `<div class="channel-section-title" style="display:flex;align-items:center;gap:8px">
|
|
4083
|
+
Chat Groups
|
|
4084
|
+
<select class="channel-setting-select" id="ch-chatfilter-${ch.name}" onchange="filterChatGroup('${ch.name}')" style="font-size:11px;padding:2px 6px">
|
|
4085
|
+
<option value="">All (${chatIds.length} chats, ${ch.agents.length} routes)</option>
|
|
4086
|
+
${chatIds.map(cid => `<option value="${cid}">Chat ${escapeHtml(cid)} (${chatGroups[cid].length})</option>`).join('')}
|
|
4087
|
+
</select>
|
|
4088
|
+
</div>`;
|
|
4089
|
+
if (chatIds.length === 0) {
|
|
4090
|
+
html += '<div style="font-size:12px;color:var(--text-muted);padding:4px 0">No agents on this channel</div>';
|
|
4091
|
+
} else {
|
|
4092
|
+
for (const cid of chatIds) {
|
|
4093
|
+
const agentsInChat = chatGroups[cid];
|
|
4094
|
+
html += `<div class="ch-chatgroup-${ch.name}" data-chatid="${escapeHtml(cid)}" style="margin:8px 0 4px;padding:6px 10px;background:rgba(139,92,246,0.05);border:1px solid var(--border-dim);border-radius:8px">
|
|
4095
|
+
<div style="font-family:var(--font-mono);font-size:11px;font-weight:600;color:var(--accent);margin-bottom:6px">Chat ${escapeHtml(cid)} <span style="font-weight:400;color:var(--text-muted)">(${agentsInChat.length} agent${agentsInChat.length>1?'s':''})</span></div>`;
|
|
4096
|
+
for (const ag of agentsInChat) {
|
|
4097
|
+
html += `<div class="channel-agent-row">
|
|
4098
|
+
<span class="channel-agent-name">${escapeHtml(ag.agentName)}</span>
|
|
4099
|
+
<span class="channel-agent-alias">${escapeHtml(ag.alias)}</span>
|
|
4100
|
+
<button class="channel-agent-remove" onclick="removeChannelAgent('${ch.name}','${escapeHtml(ag.agentId)}','${escapeHtml(ag.chatId)}')" title="Remove route">×</button>
|
|
4101
|
+
</div>`;
|
|
4102
|
+
}
|
|
4103
|
+
html += `</div>`;
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4107
|
+
// Add agent
|
|
4108
|
+
const agentIdsOnChannel = new Set(ch.agents.map(a => a.agentId));
|
|
4109
|
+
const availableAgents = agents.filter(a => !agentIdsOnChannel.has(a.id));
|
|
4110
|
+
html += `<div class="channel-add-row">
|
|
4111
|
+
<select class="channel-add-select" id="ch-addagent-${ch.name}">
|
|
4112
|
+
${availableAgents.length === 0 ? '<option value="">All agents added</option>' :
|
|
4113
|
+
'<option value="">Select agent...</option>' + availableAgents.map(a => `<option value="${a.id}">${escapeHtml(a.name)} (${a.aliases?.[0] || a.id})</option>`).join('')}
|
|
4114
|
+
</select>
|
|
4115
|
+
<input class="channel-add-input" id="ch-addchatid-${ch.name}" placeholder="Chat/Channel ID">
|
|
4116
|
+
<button class="channel-add-btn" onclick="addChannelAgent('${ch.name}')" ${availableAgents.length===0?'disabled':''}>+ Add Agent</button>
|
|
4117
|
+
</div>`;
|
|
4118
|
+
|
|
4119
|
+
html += '</div></div>';
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4122
|
+
html += '</div>';
|
|
4123
|
+
canvas.innerHTML = html;
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
function togglePrefixVisibility(channelName) {
|
|
4127
|
+
const mode = document.getElementById('ch-sticky-' + channelName).value;
|
|
4128
|
+
document.getElementById('ch-prefix-row-' + channelName).style.display = mode === 'prefix' ? '' : 'none';
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
async function saveChannelSettings(channelName) {
|
|
4132
|
+
const mode = document.getElementById('ch-sticky-' + channelName).value;
|
|
4133
|
+
const prefix = document.getElementById('ch-prefix-' + channelName)?.value || '!';
|
|
4134
|
+
const timeoutMin = parseInt(document.getElementById('ch-timeout-' + channelName).value) || 5;
|
|
4135
|
+
const timeoutMs = timeoutMin * 60000;
|
|
4136
|
+
|
|
4137
|
+
try {
|
|
4138
|
+
const res = await fetch('/api/channels/' + channelName, {
|
|
4139
|
+
method: 'PUT',
|
|
4140
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4141
|
+
body: JSON.stringify({ stickyRouting: mode, stickyPrefix: prefix, stickyTimeoutMs: timeoutMs }),
|
|
4142
|
+
});
|
|
4143
|
+
const data = await res.json();
|
|
4144
|
+
if (data.ok) {
|
|
4145
|
+
// Update local data
|
|
4146
|
+
const ch = channelsData.find(c => c.name === channelName);
|
|
4147
|
+
if (ch) { ch.stickyRouting = mode; ch.stickyPrefix = prefix; ch.stickyTimeoutMs = timeoutMs; }
|
|
4148
|
+
alert('Settings saved. Restart service for full effect.');
|
|
4149
|
+
} else {
|
|
4150
|
+
alert('Error: ' + (data.error || 'Unknown'));
|
|
4151
|
+
}
|
|
4152
|
+
} catch (err) {
|
|
4153
|
+
alert('Error: ' + err.message);
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
async function addChannelAgent(channelName) {
|
|
4158
|
+
const selectEl = document.getElementById('ch-addagent-' + channelName);
|
|
4159
|
+
const chatIdEl = document.getElementById('ch-addchatid-' + channelName);
|
|
4160
|
+
const agentId = selectEl.value;
|
|
4161
|
+
const chatId = chatIdEl.value.trim();
|
|
4162
|
+
|
|
4163
|
+
if (!agentId) { alert('Select an agent'); return; }
|
|
4164
|
+
if (!chatId) { alert('Enter a chat/channel ID'); return; }
|
|
4165
|
+
|
|
4166
|
+
try {
|
|
4167
|
+
const res = await fetch('/api/channels/' + channelName + '/agents', {
|
|
4168
|
+
method: 'POST',
|
|
4169
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4170
|
+
body: JSON.stringify({ agentId, chatId, requireMention: true }),
|
|
4171
|
+
});
|
|
4172
|
+
const data = await res.json();
|
|
4173
|
+
if (data.ok) {
|
|
4174
|
+
await fetchData();
|
|
4175
|
+
} else {
|
|
4176
|
+
alert('Error: ' + (data.error || 'Unknown'));
|
|
4177
|
+
}
|
|
4178
|
+
} catch (err) {
|
|
4179
|
+
alert('Error: ' + err.message);
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
function filterChatGroup(channelName) {
|
|
4184
|
+
const filter = document.getElementById('ch-chatfilter-' + channelName)?.value || '';
|
|
4185
|
+
const groups = document.querySelectorAll('.ch-chatgroup-' + channelName);
|
|
4186
|
+
for (const g of groups) {
|
|
4187
|
+
g.style.display = (!filter || g.dataset.chatid === filter) ? '' : 'none';
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
|
|
4191
|
+
async function removeChannelAgent(channelName, agentId, chatId) {
|
|
4192
|
+
if (!confirm('Remove ' + agentId + ' from ' + channelName + ':' + chatId + '?')) return;
|
|
4193
|
+
|
|
4194
|
+
try {
|
|
4195
|
+
const res = await fetch('/api/channels/' + channelName + '/agents/' + agentId, {
|
|
4196
|
+
method: 'DELETE',
|
|
4197
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4198
|
+
body: JSON.stringify({ chatId }),
|
|
4199
|
+
});
|
|
4200
|
+
const data = await res.json();
|
|
4201
|
+
if (data.ok) {
|
|
4202
|
+
await fetchData();
|
|
4203
|
+
} else {
|
|
4204
|
+
alert('Error: ' + (data.error || 'Unknown'));
|
|
4205
|
+
}
|
|
4206
|
+
} catch (err) {
|
|
4207
|
+
alert('Error: ' + err.message);
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
async function addMonitoredId(channelName) {
|
|
4212
|
+
const input = document.getElementById('ch-addmon-' + channelName);
|
|
4213
|
+
const chatId = parseInt(input.value);
|
|
4214
|
+
if (isNaN(chatId)) { alert('Enter a valid numeric chat ID'); return; }
|
|
4215
|
+
|
|
4216
|
+
try {
|
|
4217
|
+
const res = await fetch('/api/channels/' + channelName + '/monitored', {
|
|
4218
|
+
method: 'POST',
|
|
4219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4220
|
+
body: JSON.stringify({ chatId }),
|
|
4221
|
+
});
|
|
4222
|
+
const data = await res.json();
|
|
4223
|
+
if (data.ok) {
|
|
4224
|
+
input.value = '';
|
|
4225
|
+
await fetchData();
|
|
4226
|
+
} else {
|
|
4227
|
+
alert('Error: ' + (data.error || 'Unknown'));
|
|
4228
|
+
}
|
|
4229
|
+
} catch (err) {
|
|
4230
|
+
alert('Error: ' + err.message);
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
async function removeMonitoredId(chatId) {
|
|
4235
|
+
if (!confirm('Remove monitored chat ID ' + chatId + '?')) return;
|
|
4236
|
+
|
|
4237
|
+
try {
|
|
4238
|
+
const res = await fetch('/api/channels/imessage/monitored', {
|
|
4239
|
+
method: 'DELETE',
|
|
4240
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4241
|
+
body: JSON.stringify({ chatId }),
|
|
4242
|
+
});
|
|
4243
|
+
const data = await res.json();
|
|
4244
|
+
if (data.ok) {
|
|
4245
|
+
await fetchData();
|
|
4246
|
+
} else {
|
|
4247
|
+
alert('Error: ' + (data.error || 'Unknown'));
|
|
4248
|
+
}
|
|
4249
|
+
} catch (err) {
|
|
4250
|
+
alert('Error: ' + err.message);
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
|
|
4254
|
+
// Set active tab based on URL
|
|
4255
|
+
const isChannelsPage = location.pathname === '/channels';
|
|
4256
|
+
document.getElementById('tabAgents')?.classList.add('active');
|
|
4257
|
+
|
|
4258
|
+
// Render contextual sub-nav (Agents section) with agent controls inline
|
|
4259
|
+
const subNav = document.getElementById('subNav');
|
|
4260
|
+
const agentsSubNav = `
|
|
4261
|
+
<a class="sub-nav-link active" href="/org">Teams</a>
|
|
4262
|
+
<a class="sub-nav-link" href="/tasks">Tasks</a>
|
|
4263
|
+
<a class="sub-nav-link" href="/projects">Projects</a>
|
|
4264
|
+
<a class="sub-nav-link" href="/automations">Automations</a>
|
|
4265
|
+
<div class="agent-controls">
|
|
4266
|
+
<div class="view-toggle" id="viewToggle">
|
|
4267
|
+
<button class="view-toggle-btn${viewMode==='grid'?' active':''}" data-view="grid" onclick="setViewMode('grid')" title="Grid view">▦</button>
|
|
4268
|
+
<button class="view-toggle-btn${viewMode==='compact'?' active':''}" data-view="compact" onclick="setViewMode('compact')" title="Compact view">▪▪</button>
|
|
4269
|
+
<button class="view-toggle-btn${viewMode==='list'?' active':''}" data-view="list" onclick="setViewMode('list')" title="List view">☰</button>
|
|
4270
|
+
</div>
|
|
4271
|
+
<input type="text" id="agentSearch" class="topbar-control" placeholder="Search..." oninput="handleSearch()" style="width:120px">
|
|
4272
|
+
<div class="class-dropdown" id="classDropdown">
|
|
4273
|
+
<button class="class-dropdown-btn" onclick="toggleClassDropdown()" id="classDropdownBtn">${[visibleClasses.has('standard')?'Standard':'',visibleClasses.has('builder')?'Builder':'',visibleClasses.has('platform')?'Platform':''].filter(Boolean).join(', ')||'None'} ▾</button>
|
|
4274
|
+
<div class="class-dropdown-menu" id="classDropdownMenu">
|
|
4275
|
+
<div class="class-dropdown-item" onclick="toggleClassFilter('standard')"><span class="check" id="chk-standard">${visibleClasses.has('standard')?'✓':' '}</span> Standard</div>
|
|
4276
|
+
<div class="class-dropdown-item" onclick="toggleClassFilter('builder')"><span class="check" id="chk-builder">${visibleClasses.has('builder')?'✓':' '}</span> Builder</div>
|
|
4277
|
+
<div class="class-dropdown-item" onclick="toggleClassFilter('platform')"><span class="check" id="chk-platform">${visibleClasses.has('platform')?'✓':' '}</span> Platform</div>
|
|
4278
|
+
</div>
|
|
4279
|
+
</div>
|
|
4280
|
+
<select class="org-select" id="orgSelect" onchange="renderOrg()"></select>
|
|
4281
|
+
<select class="account-select" id="accountFilter" onchange="setAccountFilter(this.value)"><option value="all">All Accounts</option></select>
|
|
4282
|
+
<button class="hide-names-btn" id="hideAllBtn" onclick="toggleHideAll()" title="Hide/show all agent names" style="font-size:10px;padding:3px 8px">👁 Hide</button>
|
|
4283
|
+
<button class="select-mode-btn" id="selectModeBtn" onclick="toggleSelectMode()" title="Multi-select agents" style="font-size:10px;padding:3px 8px;background:transparent;color:var(--text-dim);border:1px solid var(--border-dim);border-radius:5px;cursor:pointer">☑ Select</button>
|
|
4284
|
+
<button class="add-agent-btn-top" id="newAgentBtn" onclick="openModal(null)">+ New Agent</button>
|
|
4285
|
+
</div>
|
|
4286
|
+
`;
|
|
4287
|
+
subNav.innerHTML = agentsSubNav;
|
|
4288
|
+
|
|
4289
|
+
fetchData().then(() => {
|
|
4290
|
+
if (isChannelsPage) {
|
|
4291
|
+
// Auto-show channels panel
|
|
4292
|
+
const canvas = document.getElementById('orgCanvas');
|
|
4293
|
+
if (canvas) renderChannelsPanel(canvas);
|
|
4294
|
+
}
|
|
4295
|
+
});
|
|
4296
|
+
setInterval(fetchData, 30000);
|
|
4297
|
+
document.addEventListener('click', function(e){
|
|
4298
|
+
const dd = document.getElementById('docsDropdown');
|
|
4299
|
+
if(dd && !dd.contains(e.target)) document.getElementById('docsMenu')?.classList.remove('open');
|
|
4300
|
+
});
|
|
4301
|
+
</script>
|
|
4302
|
+
<script>(function(){fetch("/api/config/service").then(function(r){return r.json()}).then(function(d){if(d.gymEnabled)document.querySelectorAll(".gym-tab-link").forEach(function(el){el.style.display=""});if(d.gymOnlyMode){var keep=["/gym","/org","/admin"];document.querySelectorAll(".tab-group .tab-btn").forEach(function(el){var h=el.getAttribute("href");if(h&&keep.indexOf(h)===-1&&!el.classList.contains("gym-tab-link"))el.style.display="none"});document.querySelectorAll(".topbar-right .gear-btn").forEach(function(el){var h=el.getAttribute("href");if(h&&["/marketplace","/monitor","/user-guide"].indexOf(h)!==-1)el.style.display="none"})}}).catch(function(){})})()</script>
|
|
4303
|
+
</body>
|
|
4304
|
+
</html>
|