@harbinger-ai/harbinger 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +406 -0
- package/agents/README.md +76 -0
- package/agents/_template/CONFIG.yaml +7 -0
- package/agents/_template/HEARTBEAT.md +59 -0
- package/agents/_template/IDENTITY.md +4 -0
- package/agents/_template/SKILLS.md +1 -0
- package/agents/_template/SOUL.md +25 -0
- package/agents/_template/TOOLS.md +3 -0
- package/agents/binary-reverser/CONFIG.yaml +21 -0
- package/agents/binary-reverser/HEARTBEAT.md +65 -0
- package/agents/binary-reverser/IDENTITY.md +1 -0
- package/agents/binary-reverser/SKILLS.md +1 -0
- package/agents/binary-reverser/SOUL.md +23 -0
- package/agents/binary-reverser/TOOLS.md +99 -0
- package/agents/browser-agent/CONFIG.yaml +20 -0
- package/agents/browser-agent/HEARTBEAT.md +79 -0
- package/agents/browser-agent/IDENTITY.md +5 -0
- package/agents/browser-agent/SKILLS.md +86 -0
- package/agents/browser-agent/SOUL.md +23 -0
- package/agents/browser-agent/TOOLS.md +186 -0
- package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
- package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
- package/agents/cloud-infiltrator/IDENTITY.md +1 -0
- package/agents/cloud-infiltrator/SKILLS.md +1 -0
- package/agents/cloud-infiltrator/SOUL.md +23 -0
- package/agents/cloud-infiltrator/TOOLS.md +68 -0
- package/agents/coding-assistant/CONFIG.yaml +22 -0
- package/agents/coding-assistant/HEARTBEAT.md +57 -0
- package/agents/coding-assistant/IDENTITY.md +5 -0
- package/agents/coding-assistant/SKILLS.md +69 -0
- package/agents/coding-assistant/SOUL.md +60 -0
- package/agents/coding-assistant/TOOLS.md +168 -0
- package/agents/learning-agent/CONFIG.yaml +21 -0
- package/agents/learning-agent/HEARTBEAT.md +63 -0
- package/agents/learning-agent/IDENTITY.md +5 -0
- package/agents/learning-agent/SKILLS.md +86 -0
- package/agents/learning-agent/SOUL.md +77 -0
- package/agents/learning-agent/TOOLS.md +145 -0
- package/agents/maintainer/CONFIG.yaml +31 -0
- package/agents/maintainer/HEARTBEAT.md +28 -0
- package/agents/maintainer/IDENTITY.md +33 -0
- package/agents/maintainer/SKILLS.md +24 -0
- package/agents/maintainer/SOUL.md +61 -0
- package/agents/maintainer/TOOLS.md +29 -0
- package/agents/maintainer/lib/engine.js +279 -0
- package/agents/maintainer/lib/safe-fixer.js +183 -0
- package/agents/morning-brief/CONFIG.yaml +22 -0
- package/agents/morning-brief/HEARTBEAT.md +60 -0
- package/agents/morning-brief/IDENTITY.md +5 -0
- package/agents/morning-brief/SKILLS.md +56 -0
- package/agents/morning-brief/SOUL.md +64 -0
- package/agents/morning-brief/TOOLS.md +112 -0
- package/agents/osint-detective/CONFIG.yaml +24 -0
- package/agents/osint-detective/HEARTBEAT.md +66 -0
- package/agents/osint-detective/IDENTITY.md +1 -0
- package/agents/osint-detective/SKILLS.md +1 -0
- package/agents/osint-detective/SOUL.md +23 -0
- package/agents/osint-detective/TOOLS.md +81 -0
- package/agents/recon-scout/CONFIG.yaml +22 -0
- package/agents/recon-scout/HEARTBEAT.md +79 -0
- package/agents/recon-scout/IDENTITY.md +1 -0
- package/agents/recon-scout/SKILLS.md +1 -0
- package/agents/recon-scout/SOUL.md +23 -0
- package/agents/recon-scout/TOOLS.md +93 -0
- package/agents/report-writer/CONFIG.yaml +21 -0
- package/agents/report-writer/HEARTBEAT.md +63 -0
- package/agents/report-writer/IDENTITY.md +1 -0
- package/agents/report-writer/SKILLS.md +1 -0
- package/agents/report-writer/SOUL.md +23 -0
- package/agents/report-writer/TOOLS.md +69 -0
- package/agents/shared/README.md +13 -0
- package/agents/web-hacker/CONFIG.yaml +24 -0
- package/agents/web-hacker/HEARTBEAT.md +78 -0
- package/agents/web-hacker/IDENTITY.md +1 -0
- package/agents/web-hacker/SKILLS.md +1 -0
- package/agents/web-hacker/SOUL.md +23 -0
- package/agents/web-hacker/TOOLS.md +86 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +274 -0
- package/bin/cli.js +620 -0
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +24 -0
- package/config/instrumentation.js +93 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_bounty_and_registry.sql +82 -0
- package/drizzle/0002_sync_columns.sql +7 -0
- package/drizzle/0003_graceful_bloodscream.sql +86 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0003_snapshot.json +878 -0
- package/drizzle/meta/_journal.json +34 -0
- package/drizzle/relations.ts +3 -0
- package/drizzle/schema.ts +145 -0
- package/lib/actions.js +47 -0
- package/lib/agents.js +166 -0
- package/lib/ai/agent.js +96 -0
- package/lib/ai/autonomous-engine.js +261 -0
- package/lib/ai/index.js +359 -0
- package/lib/ai/model-router.js +254 -0
- package/lib/ai/model.js +73 -0
- package/lib/ai/tools.js +84 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +53 -0
- package/lib/bounty/actions.js +119 -0
- package/lib/bounty/findings.js +64 -0
- package/lib/bounty/programs.js +34 -0
- package/lib/bounty/sync-targets.js +267 -0
- package/lib/bounty/targets.js +33 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +288 -0
- package/lib/chat/api.js +135 -0
- package/lib/chat/components/app-sidebar.js +237 -0
- package/lib/chat/components/app-sidebar.jsx +289 -0
- package/lib/chat/components/chat-header.js +27 -0
- package/lib/chat/components/chat-header.jsx +37 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +228 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +81 -0
- package/lib/chat/components/chat-page.jsx +100 -0
- package/lib/chat/components/chat.js +150 -0
- package/lib/chat/components/chat.jsx +182 -0
- package/lib/chat/components/chats-page.js +302 -0
- package/lib/chat/components/chats-page.jsx +330 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/enhanced-tool-call.js +103 -0
- package/lib/chat/components/enhanced-tool-call.jsx +139 -0
- package/lib/chat/components/findings-page.js +175 -0
- package/lib/chat/components/findings-page.jsx +214 -0
- package/lib/chat/components/greeting.js +22 -0
- package/lib/chat/components/greeting.jsx +26 -0
- package/lib/chat/components/icons.js +777 -0
- package/lib/chat/components/icons.jsx +741 -0
- package/lib/chat/components/index.js +26 -0
- package/lib/chat/components/mcp-page.js +260 -0
- package/lib/chat/components/mcp-page.jsx +355 -0
- package/lib/chat/components/message.js +289 -0
- package/lib/chat/components/message.jsx +315 -0
- package/lib/chat/components/messages.js +66 -0
- package/lib/chat/components/messages.jsx +77 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/registry-page.js +222 -0
- package/lib/chat/components/registry-page.jsx +255 -0
- package/lib/chat/components/settings-layout.js +40 -0
- package/lib/chat/components/settings-layout.jsx +54 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +132 -0
- package/lib/chat/components/sidebar-history-item.jsx +113 -0
- package/lib/chat/components/sidebar-history.js +115 -0
- package/lib/chat/components/sidebar-history.jsx +157 -0
- package/lib/chat/components/sidebar-user-nav.js +63 -0
- package/lib/chat/components/sidebar-user-nav.jsx +73 -0
- package/lib/chat/components/status-bar.js +39 -0
- package/lib/chat/components/status-bar.jsx +51 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/targets-page.js +376 -0
- package/lib/chat/components/targets-page.jsx +389 -0
- package/lib/chat/components/tool-call.js +86 -0
- package/lib/chat/components/tool-call.jsx +104 -0
- package/lib/chat/components/tool-panel.js +107 -0
- package/lib/chat/components/tool-panel.jsx +145 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/rename-dialog.js +74 -0
- package/lib/chat/components/ui/rename-dialog.jsx +72 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +145 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +145 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/mcp/actions.js +104 -0
- package/lib/mcp/client.js +79 -0
- package/lib/mcp/handler.js +57 -0
- package/lib/mcp/server.js +165 -0
- package/lib/paths.js +46 -0
- package/lib/registry/actions.js +164 -0
- package/lib/registry/catalog.js +137 -0
- package/lib/registry/tools.js +71 -0
- package/lib/tools/create-job.js +99 -0
- package/lib/tools/github.js +217 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +118 -0
- package/lib/utils/render-md.js +102 -0
- package/package.json +103 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +48 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-telegram.mjs +264 -0
- package/setup/setup.mjs +842 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +63 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/build-image.yml +36 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +307 -0
- package/templates/app/api/[...thepopebot]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/findings/page.js +7 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +19 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/mcp/page.js +5 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/targets/page.js +7 -0
- package/templates/app/toolbox/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/AGENT.md +34 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/EVENT_HANDLER.md +224 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/MCP_SERVERS.json +1 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
- package/templates/config/SOUL.md +17 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker/job-claude-code/Dockerfile +34 -0
- package/templates/docker/job-claude-code/entrypoint.sh +139 -0
- package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
- package/templates/docker-compose.yml +63 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +1 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
5
|
+
import { cn } from "../utils.js";
|
|
6
|
+
import { SpinnerIcon, CheckIcon, XIcon } from "./icons.js";
|
|
7
|
+
function getToolDisplayName(toolName) {
|
|
8
|
+
const names = {
|
|
9
|
+
create_job: "Create Job",
|
|
10
|
+
get_job_status: "Check Job Status",
|
|
11
|
+
get_system_technical_specs: "Read Tech Docs",
|
|
12
|
+
get_skill_building_guide: "Read Skill Docs"
|
|
13
|
+
};
|
|
14
|
+
return names[toolName] || toolName.replace(/_/g, " ");
|
|
15
|
+
}
|
|
16
|
+
function formatContent(content) {
|
|
17
|
+
if (content == null) return null;
|
|
18
|
+
if (typeof content === "string") {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(JSON.parse(content), null, 2);
|
|
21
|
+
} catch {
|
|
22
|
+
return content.length > 200 ? content.slice(0, 200) + "..." : content;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const str = JSON.stringify(content, null, 2);
|
|
26
|
+
return str.length > 200 ? str.slice(0, 200) + "..." : str;
|
|
27
|
+
}
|
|
28
|
+
function ToolItem({ part }) {
|
|
29
|
+
const toolName = part.toolName || (part.type?.startsWith("tool-") ? part.type.slice(5) : "tool");
|
|
30
|
+
const displayName = getToolDisplayName(toolName);
|
|
31
|
+
const state = part.state || "input-available";
|
|
32
|
+
const isRunning = state === "input-streaming" || state === "input-available";
|
|
33
|
+
const isDone = state === "output-available";
|
|
34
|
+
const isError = state === "output-error";
|
|
35
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-white/[0.06] bg-black/20 p-2.5", children: [
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [
|
|
37
|
+
/* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: "text-[--cyan] shrink-0", children: /* @__PURE__ */ jsx("path", { d: "M13 2 3 14h9l-1 10 10-12h-9l1-10z" }) }),
|
|
38
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] font-medium text-foreground/80 truncate flex-1", children: displayName }),
|
|
39
|
+
isRunning && /* @__PURE__ */ jsx(SpinnerIcon, { size: 10, className: "text-[--cyan]" }),
|
|
40
|
+
isDone && /* @__PURE__ */ jsx(CheckIcon, { size: 10, className: "text-[--success]" }),
|
|
41
|
+
isError && /* @__PURE__ */ jsx(XIcon, { size: 10, className: "text-[--destructive]" })
|
|
42
|
+
] }),
|
|
43
|
+
isDone && part.output != null && /* @__PURE__ */ jsx("pre", { className: "font-mono text-[10px] text-muted-foreground whitespace-pre-wrap break-all max-h-20 overflow-hidden", children: formatContent(part.output) }),
|
|
44
|
+
isError && part.output != null && /* @__PURE__ */ jsx("pre", { className: "font-mono text-[10px] text-[--destructive]/80 whitespace-pre-wrap break-all max-h-20 overflow-hidden", children: formatContent(part.output) })
|
|
45
|
+
] });
|
|
46
|
+
}
|
|
47
|
+
function ToolPanel({ messages, show }) {
|
|
48
|
+
const toolParts = useMemo(() => {
|
|
49
|
+
const parts = [];
|
|
50
|
+
for (const msg of messages) {
|
|
51
|
+
if (!msg.parts) continue;
|
|
52
|
+
for (const part of msg.parts) {
|
|
53
|
+
if (part.type?.startsWith("tool-")) {
|
|
54
|
+
parts.push(part);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return parts;
|
|
59
|
+
}, [messages]);
|
|
60
|
+
const activeParts = toolParts.filter((p) => {
|
|
61
|
+
const s = p.state || "input-available";
|
|
62
|
+
return s === "input-streaming" || s === "input-available";
|
|
63
|
+
});
|
|
64
|
+
const completedParts = toolParts.filter((p) => {
|
|
65
|
+
const s = p.state || "input-available";
|
|
66
|
+
return s === "output-available" || s === "output-error";
|
|
67
|
+
});
|
|
68
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { children: show && /* @__PURE__ */ jsxs(
|
|
69
|
+
motion.div,
|
|
70
|
+
{
|
|
71
|
+
initial: { width: 0, opacity: 0 },
|
|
72
|
+
animate: { width: 300, opacity: 1 },
|
|
73
|
+
exit: { width: 0, opacity: 0 },
|
|
74
|
+
transition: { duration: 0.25, ease: "easeInOut" },
|
|
75
|
+
className: "hidden md:flex flex-col h-full border-l border-white/[0.06] bg-[--card]/50 backdrop-blur-sm overflow-hidden shrink-0",
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ jsx("div", { className: "p-3 border-b border-white/[0.06]", children: /* @__PURE__ */ jsx("h3", { className: "font-mono text-[10px] uppercase tracking-wider text-muted-foreground", children: "Tool Activity" }) }),
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-3 space-y-3 scrollbar-thin", children: [
|
|
79
|
+
activeParts.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("div", { className: "font-mono text-[9px] uppercase tracking-wider text-[--cyan] mb-2 flex items-center gap-1.5", children: [
|
|
81
|
+
/* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-[--cyan] animate-pulse" }),
|
|
82
|
+
"Running (",
|
|
83
|
+
activeParts.length,
|
|
84
|
+
")"
|
|
85
|
+
] }),
|
|
86
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: activeParts.map((p, i) => /* @__PURE__ */ jsx(ToolItem, { part: p }, p.toolCallId || `active-${i}`)) })
|
|
87
|
+
] }),
|
|
88
|
+
completedParts.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
89
|
+
/* @__PURE__ */ jsxs("div", { className: "font-mono text-[9px] uppercase tracking-wider text-muted-foreground mb-2", children: [
|
|
90
|
+
"Recent (",
|
|
91
|
+
completedParts.length,
|
|
92
|
+
")"
|
|
93
|
+
] }),
|
|
94
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: completedParts.slice(-10).reverse().map((p, i) => /* @__PURE__ */ jsx(ToolItem, { part: p }, p.toolCallId || `done-${i}`)) })
|
|
95
|
+
] }),
|
|
96
|
+
toolParts.length === 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
|
|
97
|
+
/* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: "text-muted-foreground/40 mb-2", children: /* @__PURE__ */ jsx("path", { d: "M13 2 3 14h9l-1 10 10-12h-9l1-10z" }) }),
|
|
98
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/40", children: "No tool activity yet" })
|
|
99
|
+
] })
|
|
100
|
+
] })
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
) });
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
ToolPanel
|
|
107
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
5
|
+
import { cn } from '../utils.js';
|
|
6
|
+
import { SpinnerIcon, CheckIcon, XIcon } from './icons.js';
|
|
7
|
+
|
|
8
|
+
function getToolDisplayName(toolName) {
|
|
9
|
+
const names = {
|
|
10
|
+
create_job: 'Create Job',
|
|
11
|
+
get_job_status: 'Check Job Status',
|
|
12
|
+
get_system_technical_specs: 'Read Tech Docs',
|
|
13
|
+
get_skill_building_guide: 'Read Skill Docs',
|
|
14
|
+
};
|
|
15
|
+
return names[toolName] || toolName.replace(/_/g, ' ');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatContent(content) {
|
|
19
|
+
if (content == null) return null;
|
|
20
|
+
if (typeof content === 'string') {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(JSON.parse(content), null, 2);
|
|
23
|
+
} catch {
|
|
24
|
+
return content.length > 200 ? content.slice(0, 200) + '...' : content;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const str = JSON.stringify(content, null, 2);
|
|
28
|
+
return str.length > 200 ? str.slice(0, 200) + '...' : str;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ToolItem({ part }) {
|
|
32
|
+
const toolName = part.toolName || (part.type?.startsWith('tool-') ? part.type.slice(5) : 'tool');
|
|
33
|
+
const displayName = getToolDisplayName(toolName);
|
|
34
|
+
const state = part.state || 'input-available';
|
|
35
|
+
const isRunning = state === 'input-streaming' || state === 'input-available';
|
|
36
|
+
const isDone = state === 'output-available';
|
|
37
|
+
const isError = state === 'output-error';
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="rounded-md border border-white/[0.06] bg-black/20 p-2.5">
|
|
41
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
42
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className="text-[--cyan] shrink-0">
|
|
43
|
+
<path d="M13 2 3 14h9l-1 10 10-12h-9l1-10z" />
|
|
44
|
+
</svg>
|
|
45
|
+
<span className="font-mono text-[11px] font-medium text-foreground/80 truncate flex-1">{displayName}</span>
|
|
46
|
+
{isRunning && <SpinnerIcon size={10} className="text-[--cyan]" />}
|
|
47
|
+
{isDone && <CheckIcon size={10} className="text-[--success]" />}
|
|
48
|
+
{isError && <XIcon size={10} className="text-[--destructive]" />}
|
|
49
|
+
</div>
|
|
50
|
+
{isDone && part.output != null && (
|
|
51
|
+
<pre className="font-mono text-[10px] text-muted-foreground whitespace-pre-wrap break-all max-h-20 overflow-hidden">
|
|
52
|
+
{formatContent(part.output)}
|
|
53
|
+
</pre>
|
|
54
|
+
)}
|
|
55
|
+
{isError && part.output != null && (
|
|
56
|
+
<pre className="font-mono text-[10px] text-[--destructive]/80 whitespace-pre-wrap break-all max-h-20 overflow-hidden">
|
|
57
|
+
{formatContent(part.output)}
|
|
58
|
+
</pre>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function ToolPanel({ messages, show }) {
|
|
65
|
+
// Extract all tool parts from messages (client-side only, no backend changes)
|
|
66
|
+
const toolParts = useMemo(() => {
|
|
67
|
+
const parts = [];
|
|
68
|
+
for (const msg of messages) {
|
|
69
|
+
if (!msg.parts) continue;
|
|
70
|
+
for (const part of msg.parts) {
|
|
71
|
+
if (part.type?.startsWith('tool-')) {
|
|
72
|
+
parts.push(part);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return parts;
|
|
77
|
+
}, [messages]);
|
|
78
|
+
|
|
79
|
+
const activeParts = toolParts.filter(p => {
|
|
80
|
+
const s = p.state || 'input-available';
|
|
81
|
+
return s === 'input-streaming' || s === 'input-available';
|
|
82
|
+
});
|
|
83
|
+
const completedParts = toolParts.filter(p => {
|
|
84
|
+
const s = p.state || 'input-available';
|
|
85
|
+
return s === 'output-available' || s === 'output-error';
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<AnimatePresence>
|
|
90
|
+
{show && (
|
|
91
|
+
<motion.div
|
|
92
|
+
initial={{ width: 0, opacity: 0 }}
|
|
93
|
+
animate={{ width: 300, opacity: 1 }}
|
|
94
|
+
exit={{ width: 0, opacity: 0 }}
|
|
95
|
+
transition={{ duration: 0.25, ease: 'easeInOut' }}
|
|
96
|
+
className="hidden md:flex flex-col h-full border-l border-white/[0.06] bg-[--card]/50 backdrop-blur-sm overflow-hidden shrink-0"
|
|
97
|
+
>
|
|
98
|
+
<div className="p-3 border-b border-white/[0.06]">
|
|
99
|
+
<h3 className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground">Tool Activity</h3>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="flex-1 overflow-y-auto p-3 space-y-3 scrollbar-thin">
|
|
103
|
+
{/* Active tools */}
|
|
104
|
+
{activeParts.length > 0 && (
|
|
105
|
+
<div>
|
|
106
|
+
<div className="font-mono text-[9px] uppercase tracking-wider text-[--cyan] mb-2 flex items-center gap-1.5">
|
|
107
|
+
<div className="w-1.5 h-1.5 rounded-full bg-[--cyan] animate-pulse" />
|
|
108
|
+
Running ({activeParts.length})
|
|
109
|
+
</div>
|
|
110
|
+
<div className="space-y-2">
|
|
111
|
+
{activeParts.map((p, i) => (
|
|
112
|
+
<ToolItem key={p.toolCallId || `active-${i}`} part={p} />
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{/* Completed tools */}
|
|
119
|
+
{completedParts.length > 0 && (
|
|
120
|
+
<div>
|
|
121
|
+
<div className="font-mono text-[9px] uppercase tracking-wider text-muted-foreground mb-2">
|
|
122
|
+
Recent ({completedParts.length})
|
|
123
|
+
</div>
|
|
124
|
+
<div className="space-y-2">
|
|
125
|
+
{completedParts.slice(-10).reverse().map((p, i) => (
|
|
126
|
+
<ToolItem key={p.toolCallId || `done-${i}`} part={p} />
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
{toolParts.length === 0 && (
|
|
133
|
+
<div className="flex flex-col items-center justify-center py-8 text-center">
|
|
134
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className="text-muted-foreground/40 mb-2">
|
|
135
|
+
<path d="M13 2 3 14h9l-1 10 10-12h-9l1-10z" />
|
|
136
|
+
</svg>
|
|
137
|
+
<span className="font-mono text-[10px] text-muted-foreground/40">No tool activity yet</span>
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
</motion.div>
|
|
142
|
+
)}
|
|
143
|
+
</AnimatePresence>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { ZapIcon, ChevronDownIcon } from "./icons.js";
|
|
5
|
+
import { getSwarmConfig } from "../actions.js";
|
|
6
|
+
const typeBadgeStyles = {
|
|
7
|
+
agent: "bg-purple-500/10 text-purple-500",
|
|
8
|
+
command: "bg-blue-500/10 text-blue-500",
|
|
9
|
+
webhook: "bg-orange-500/10 text-orange-500"
|
|
10
|
+
};
|
|
11
|
+
const typeOrder = { agent: 0, command: 1, webhook: 2 };
|
|
12
|
+
function sortByType(items) {
|
|
13
|
+
return [...items].sort((a, b) => {
|
|
14
|
+
const actions_a = a.actions || [];
|
|
15
|
+
const actions_b = b.actions || [];
|
|
16
|
+
const ta = typeOrder[actions_a[0]?.type || "agent"] ?? 99;
|
|
17
|
+
const tb = typeOrder[actions_b[0]?.type || "agent"] ?? 99;
|
|
18
|
+
return ta - tb;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function GroupHeader({ label, count }) {
|
|
22
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2 pb-1", children: [
|
|
23
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: label }),
|
|
24
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
25
|
+
"(",
|
|
26
|
+
count,
|
|
27
|
+
")"
|
|
28
|
+
] })
|
|
29
|
+
] });
|
|
30
|
+
}
|
|
31
|
+
function ActionCard({ action, index }) {
|
|
32
|
+
const type = action.type || "agent";
|
|
33
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-background p-3", children: [
|
|
34
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
|
|
35
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground font-medium", children: [
|
|
36
|
+
"Action ",
|
|
37
|
+
index + 1
|
|
38
|
+
] }),
|
|
39
|
+
/* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${typeBadgeStyles[type] || typeBadgeStyles.agent}`, children: type })
|
|
40
|
+
] }),
|
|
41
|
+
type === "agent" && action.job && /* @__PURE__ */ jsxs("div", { children: [
|
|
42
|
+
/* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48", children: action.job }),
|
|
43
|
+
(action.llm_provider || action.llm_model) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
|
|
44
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "LLM:" }),
|
|
45
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-purple-500/10 text-purple-500 px-2 py-0.5 text-[10px] font-medium", children: [action.llm_provider, action.llm_model].filter(Boolean).join(" / ") })
|
|
46
|
+
] })
|
|
47
|
+
] }),
|
|
48
|
+
type === "command" && action.command && /* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48", children: action.command }),
|
|
49
|
+
type === "webhook" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
50
|
+
/* @__PURE__ */ jsxs("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto", children: [
|
|
51
|
+
action.method && action.method !== "POST" ? `${action.method} ` : "",
|
|
52
|
+
action.url
|
|
53
|
+
] }),
|
|
54
|
+
action.vars && Object.keys(action.vars).length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
55
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Variables" }),
|
|
56
|
+
/* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48", children: JSON.stringify(action.vars, null, 2) })
|
|
57
|
+
] })
|
|
58
|
+
] })
|
|
59
|
+
] });
|
|
60
|
+
}
|
|
61
|
+
function TriggerCard({ trigger }) {
|
|
62
|
+
const [expanded, setExpanded] = useState(false);
|
|
63
|
+
const disabled = trigger.enabled === false;
|
|
64
|
+
const actions = trigger.actions || [];
|
|
65
|
+
const actionTypes = actions.map((a) => a.type || "agent").filter((v, i, arr) => arr.indexOf(v) === i);
|
|
66
|
+
return /* @__PURE__ */ jsxs(
|
|
67
|
+
"div",
|
|
68
|
+
{
|
|
69
|
+
className: `rounded-lg border bg-card transition-opacity ${disabled ? "opacity-60" : ""}`,
|
|
70
|
+
children: [
|
|
71
|
+
/* @__PURE__ */ jsxs(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
onClick: () => setExpanded(!expanded),
|
|
75
|
+
className: "flex items-center gap-3 w-full text-left p-4 hover:bg-accent/50 rounded-lg",
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(ZapIcon, { size: 16 }) }),
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
79
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: trigger.name }),
|
|
80
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
|
|
81
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: trigger.watch_path }),
|
|
82
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1.5 text-border", children: "|" }),
|
|
83
|
+
actions.length,
|
|
84
|
+
" action",
|
|
85
|
+
actions.length !== 1 ? "s" : "",
|
|
86
|
+
actionTypes.length > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-1", children: [
|
|
87
|
+
"(",
|
|
88
|
+
actionTypes.join(", "),
|
|
89
|
+
")"
|
|
90
|
+
] })
|
|
91
|
+
] })
|
|
92
|
+
] }),
|
|
93
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
"span",
|
|
96
|
+
{
|
|
97
|
+
className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${disabled ? "bg-muted text-muted-foreground" : "bg-green-500/10 text-green-500"}`,
|
|
98
|
+
children: disabled ? "disabled" : "enabled"
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
/* @__PURE__ */ jsx("span", { className: `transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
|
|
102
|
+
] })
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
expanded && /* @__PURE__ */ jsx("div", { className: "border-t px-4 py-3 flex flex-col gap-2", children: actions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No actions defined." }) : actions.map((action, i) => /* @__PURE__ */ jsx(ActionCard, { action, index: i }, i)) })
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function TriggersPage() {
|
|
112
|
+
const [triggers, setTriggers] = useState([]);
|
|
113
|
+
const [loading, setLoading] = useState(true);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
getSwarmConfig().then((data) => {
|
|
116
|
+
if (data?.triggers) setTriggers(data.triggers);
|
|
117
|
+
}).catch(() => {
|
|
118
|
+
}).finally(() => setLoading(false));
|
|
119
|
+
}, []);
|
|
120
|
+
const enabled = sortByType(triggers.filter((t) => t.enabled !== false));
|
|
121
|
+
const disabled = sortByType(triggers.filter((t) => t.enabled === false));
|
|
122
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
123
|
+
!loading && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-4", children: [
|
|
124
|
+
triggers.length,
|
|
125
|
+
" trigger",
|
|
126
|
+
triggers.length !== 1 ? "s" : "",
|
|
127
|
+
" configured, ",
|
|
128
|
+
enabled.length,
|
|
129
|
+
" enabled"
|
|
130
|
+
] }),
|
|
131
|
+
loading ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-20 animate-pulse rounded-lg bg-border/50" }, i)) }) : triggers.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(ZapIcon, { size: 24 }) }),
|
|
133
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No triggers configured" }),
|
|
134
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground max-w-sm", children: [
|
|
135
|
+
"Add webhook triggers by editing ",
|
|
136
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "config/TRIGGERS.json" }),
|
|
137
|
+
" in your project."
|
|
138
|
+
] })
|
|
139
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
140
|
+
enabled.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
141
|
+
/* @__PURE__ */ jsx(GroupHeader, { label: "Enabled", count: enabled.length }),
|
|
142
|
+
enabled.map((trigger, i) => /* @__PURE__ */ jsx(TriggerCard, { trigger }, `enabled-${i}`))
|
|
143
|
+
] }),
|
|
144
|
+
disabled.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
145
|
+
/* @__PURE__ */ jsx(GroupHeader, { label: "Disabled", count: disabled.length }),
|
|
146
|
+
disabled.map((trigger, i) => /* @__PURE__ */ jsx(TriggerCard, { trigger }, `disabled-${i}`))
|
|
147
|
+
] })
|
|
148
|
+
] })
|
|
149
|
+
] });
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
TriggersPage
|
|
153
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { ZapIcon, ChevronDownIcon } from './icons.js';
|
|
5
|
+
import { getSwarmConfig } from '../actions.js';
|
|
6
|
+
|
|
7
|
+
const typeBadgeStyles = {
|
|
8
|
+
agent: 'bg-purple-500/10 text-purple-500',
|
|
9
|
+
command: 'bg-blue-500/10 text-blue-500',
|
|
10
|
+
webhook: 'bg-orange-500/10 text-orange-500',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const typeOrder = { agent: 0, command: 1, webhook: 2 };
|
|
14
|
+
|
|
15
|
+
function sortByType(items) {
|
|
16
|
+
return [...items].sort((a, b) => {
|
|
17
|
+
const actions_a = a.actions || [];
|
|
18
|
+
const actions_b = b.actions || [];
|
|
19
|
+
const ta = typeOrder[(actions_a[0]?.type) || 'agent'] ?? 99;
|
|
20
|
+
const tb = typeOrder[(actions_b[0]?.type) || 'agent'] ?? 99;
|
|
21
|
+
return ta - tb;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
// Group Header
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function GroupHeader({ label, count }) {
|
|
30
|
+
return (
|
|
31
|
+
<div className="flex items-center gap-2 pt-2 pb-1">
|
|
32
|
+
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">{label}</span>
|
|
33
|
+
<span className="text-xs text-muted-foreground">({count})</span>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
// Action Card (nested inside trigger)
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function ActionCard({ action, index }) {
|
|
43
|
+
const type = action.type || 'agent';
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="rounded-md border bg-background p-3">
|
|
47
|
+
<div className="flex items-center gap-2 mb-2">
|
|
48
|
+
<span className="text-xs text-muted-foreground font-medium">Action {index + 1}</span>
|
|
49
|
+
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${typeBadgeStyles[type] || typeBadgeStyles.agent}`}>
|
|
50
|
+
{type}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
{type === 'agent' && action.job && (
|
|
54
|
+
<div>
|
|
55
|
+
<pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
|
|
56
|
+
{action.job}
|
|
57
|
+
</pre>
|
|
58
|
+
{(action.llm_provider || action.llm_model) && (
|
|
59
|
+
<div className="flex items-center gap-2 mt-2">
|
|
60
|
+
<span className="text-xs font-medium text-muted-foreground">LLM:</span>
|
|
61
|
+
<span className="inline-flex items-center rounded-full bg-purple-500/10 text-purple-500 px-2 py-0.5 text-[10px] font-medium">
|
|
62
|
+
{[action.llm_provider, action.llm_model].filter(Boolean).join(' / ')}
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
{type === 'command' && action.command && (
|
|
69
|
+
<pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
|
|
70
|
+
{action.command}
|
|
71
|
+
</pre>
|
|
72
|
+
)}
|
|
73
|
+
{type === 'webhook' && (
|
|
74
|
+
<div className="flex flex-col gap-2">
|
|
75
|
+
<pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto">
|
|
76
|
+
{action.method && action.method !== 'POST' ? `${action.method} ` : ''}{action.url}
|
|
77
|
+
</pre>
|
|
78
|
+
{action.vars && Object.keys(action.vars).length > 0 && (
|
|
79
|
+
<div>
|
|
80
|
+
<p className="text-xs font-medium text-muted-foreground mb-1">Variables</p>
|
|
81
|
+
<pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
|
|
82
|
+
{JSON.stringify(action.vars, null, 2)}
|
|
83
|
+
</pre>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
93
|
+
// Trigger Card
|
|
94
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
function TriggerCard({ trigger }) {
|
|
97
|
+
const [expanded, setExpanded] = useState(false);
|
|
98
|
+
const disabled = trigger.enabled === false;
|
|
99
|
+
const actions = trigger.actions || [];
|
|
100
|
+
const actionTypes = actions
|
|
101
|
+
.map((a) => a.type || 'agent')
|
|
102
|
+
.filter((v, i, arr) => arr.indexOf(v) === i);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
className={`rounded-lg border bg-card transition-opacity ${disabled ? 'opacity-60' : ''}`}
|
|
107
|
+
>
|
|
108
|
+
<button
|
|
109
|
+
onClick={() => setExpanded(!expanded)}
|
|
110
|
+
className="flex items-center gap-3 w-full text-left p-4 hover:bg-accent/50 rounded-lg"
|
|
111
|
+
>
|
|
112
|
+
<div className="shrink-0 rounded-md bg-muted p-2">
|
|
113
|
+
<ZapIcon size={16} />
|
|
114
|
+
</div>
|
|
115
|
+
<div className="flex-1 min-w-0">
|
|
116
|
+
<p className="text-sm font-medium truncate">{trigger.name}</p>
|
|
117
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
118
|
+
<span className="font-mono">{trigger.watch_path}</span>
|
|
119
|
+
<span className="mx-1.5 text-border">|</span>
|
|
120
|
+
{actions.length} action{actions.length !== 1 ? 's' : ''}
|
|
121
|
+
{actionTypes.length > 0 && (
|
|
122
|
+
<span className="ml-1">({actionTypes.join(', ')})</span>
|
|
123
|
+
)}
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
127
|
+
<span
|
|
128
|
+
className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${
|
|
129
|
+
disabled ? 'bg-muted text-muted-foreground' : 'bg-green-500/10 text-green-500'
|
|
130
|
+
}`}
|
|
131
|
+
>
|
|
132
|
+
{disabled ? 'disabled' : 'enabled'}
|
|
133
|
+
</span>
|
|
134
|
+
<span className={`transition-transform ${expanded ? 'rotate-180' : ''}`}>
|
|
135
|
+
<ChevronDownIcon size={14} />
|
|
136
|
+
</span>
|
|
137
|
+
</div>
|
|
138
|
+
</button>
|
|
139
|
+
|
|
140
|
+
{expanded && (
|
|
141
|
+
<div className="border-t px-4 py-3 flex flex-col gap-2">
|
|
142
|
+
{actions.length === 0 ? (
|
|
143
|
+
<p className="text-xs text-muted-foreground">No actions defined.</p>
|
|
144
|
+
) : (
|
|
145
|
+
actions.map((action, i) => (
|
|
146
|
+
<ActionCard key={i} action={action} index={i} />
|
|
147
|
+
))
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
156
|
+
// Main Page
|
|
157
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
export function TriggersPage() {
|
|
160
|
+
const [triggers, setTriggers] = useState([]);
|
|
161
|
+
const [loading, setLoading] = useState(true);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
getSwarmConfig()
|
|
165
|
+
.then((data) => {
|
|
166
|
+
if (data?.triggers) setTriggers(data.triggers);
|
|
167
|
+
})
|
|
168
|
+
.catch(() => {})
|
|
169
|
+
.finally(() => setLoading(false));
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
const enabled = sortByType(triggers.filter((t) => t.enabled !== false));
|
|
173
|
+
const disabled = sortByType(triggers.filter((t) => t.enabled === false));
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<>
|
|
177
|
+
{!loading && (
|
|
178
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
179
|
+
{triggers.length} trigger{triggers.length !== 1 ? 's' : ''} configured, {enabled.length} enabled
|
|
180
|
+
</p>
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{loading ? (
|
|
184
|
+
<div className="flex flex-col gap-3">
|
|
185
|
+
{[...Array(3)].map((_, i) => (
|
|
186
|
+
<div key={i} className="h-20 animate-pulse rounded-lg bg-border/50" />
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
) : triggers.length === 0 ? (
|
|
190
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
191
|
+
<div className="rounded-full bg-muted p-4 mb-4">
|
|
192
|
+
<ZapIcon size={24} />
|
|
193
|
+
</div>
|
|
194
|
+
<p className="text-sm font-medium mb-1">No triggers configured</p>
|
|
195
|
+
<p className="text-xs text-muted-foreground max-w-sm">
|
|
196
|
+
Add webhook triggers by editing <span className="font-mono">config/TRIGGERS.json</span> in your project.
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
) : (
|
|
200
|
+
<div className="flex flex-col gap-3">
|
|
201
|
+
{enabled.length > 0 && (
|
|
202
|
+
<>
|
|
203
|
+
<GroupHeader label="Enabled" count={enabled.length} />
|
|
204
|
+
{enabled.map((trigger, i) => (
|
|
205
|
+
<TriggerCard key={`enabled-${i}`} trigger={trigger} />
|
|
206
|
+
))}
|
|
207
|
+
</>
|
|
208
|
+
)}
|
|
209
|
+
{disabled.length > 0 && (
|
|
210
|
+
<>
|
|
211
|
+
<GroupHeader label="Disabled" count={disabled.length} />
|
|
212
|
+
{disabled.map((trigger, i) => (
|
|
213
|
+
<TriggerCard key={`disabled-${i}`} trigger={trigger} />
|
|
214
|
+
))}
|
|
215
|
+
</>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
</>
|
|
220
|
+
);
|
|
221
|
+
}
|