gigaclaw 1.4.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 +26 -0
- package/README.md +237 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +265 -0
- package/bin/cli.js +823 -0
- package/bin/local.sh +85 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +26 -0
- package/config/instrumentation.js +62 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_nostalgic_sersi.sql +11 -0
- package/drizzle/0002_black_daimon_hellstrom.sql +19 -0
- package/drizzle/0003_rename_code_workspaces.sql +5 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0001_snapshot.json +390 -0
- package/drizzle/meta/0002_snapshot.json +411 -0
- package/drizzle/meta/0003_snapshot.json +419 -0
- package/drizzle/meta/_journal.json +34 -0
- package/lib/actions.js +44 -0
- package/lib/ai/agent.js +86 -0
- package/lib/ai/index.js +342 -0
- package/lib/ai/model.js +180 -0
- package/lib/ai/tools.js +269 -0
- package/lib/ai/web-search.js +42 -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 +62 -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 +579 -0
- package/lib/chat/api.js +140 -0
- package/lib/chat/components/app-sidebar.js +213 -0
- package/lib/chat/components/app-sidebar.jsx +279 -0
- package/lib/chat/components/chat-header.js +192 -0
- package/lib/chat/components/chat-header.jsx +223 -0
- package/lib/chat/components/chat-input.js +236 -0
- package/lib/chat/components/chat-input.jsx +249 -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 +99 -0
- package/lib/chat/components/chat-page.jsx +121 -0
- package/lib/chat/components/chat.js +153 -0
- package/lib/chat/components/chat.jsx +199 -0
- package/lib/chat/components/chats-page.js +367 -0
- package/lib/chat/components/chats-page.jsx +394 -0
- package/lib/chat/components/code-mode-toggle.js +132 -0
- package/lib/chat/components/code-mode-toggle.jsx +163 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/greeting.js +11 -0
- package/lib/chat/components/greeting.jsx +16 -0
- package/lib/chat/components/icons.js +805 -0
- package/lib/chat/components/icons.jsx +751 -0
- package/lib/chat/components/index.js +20 -0
- package/lib/chat/components/message.js +363 -0
- package/lib/chat/components/message.jsx +422 -0
- package/lib/chat/components/messages.js +65 -0
- package/lib/chat/components/messages.jsx +74 -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/pull-requests-page.js +103 -0
- package/lib/chat/components/pull-requests-page.jsx +113 -0
- package/lib/chat/components/settings-layout.js +39 -0
- package/lib/chat/components/settings-layout.jsx +53 -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 +138 -0
- package/lib/chat/components/sidebar-history-item.jsx +119 -0
- package/lib/chat/components/sidebar-history.js +167 -0
- package/lib/chat/components/sidebar-history.jsx +220 -0
- package/lib/chat/components/sidebar-user-nav.js +61 -0
- package/lib/chat/components/sidebar-user-nav.jsx +77 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/tool-call.js +89 -0
- package/lib/chat/components/tool-call.jsx +107 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/combobox.js +98 -0
- package/lib/chat/components/ui/combobox.jsx +114 -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 +194 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +215 -0
- package/lib/chat/components/ui/rename-dialog.js +78 -0
- package/lib/chat/components/ui/rename-dialog.jsx +74 -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 +228 -0
- package/lib/chat/components/ui/sidebar.jsx +246 -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/code/actions.js +153 -0
- package/lib/code/code-page.js +22 -0
- package/lib/code/code-page.jsx +25 -0
- package/lib/code/index.js +1 -0
- package/lib/code/terminal-view.js +201 -0
- package/lib/code/terminal-view.jsx +224 -0
- package/lib/code/ws-proxy.js +80 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +168 -0
- package/lib/db/code-workspaces.js +110 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +66 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/paths.js +42 -0
- package/lib/tools/create-job.js +97 -0
- package/lib/tools/docker.js +146 -0
- package/lib/tools/github.js +271 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +104 -0
- package/lib/utils/render-md.js +111 -0
- package/package.json +118 -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 +105 -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-cloud.mjs +833 -0
- package/setup/setup-local.mjs +377 -0
- package/setup/setup-telegram.mjs +265 -0
- package/setup/setup.mjs +87 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +104 -0
- package/templates/.github/workflows/auto-merge.yml +117 -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 +308 -0
- package/templates/app/api/[...gigaclaw]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +9 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/code/[codeWorkspaceId]/page.js +9 -0
- package/templates/app/components/ascii-logo.jsx +12 -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/globals.css +90 -0
- package/templates/app/layout.js +33 -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/pull-requests/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/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/triggers/page.js +5 -0
- package/templates/config/CODE_PLANNING.md +14 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_AGENT.md +30 -0
- package/templates/config/JOB_PLANNING.md +240 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +96 -0
- package/templates/config/SOUL.md +48 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/config/WEB_SEARCH_AVAILABLE.md +5 -0
- package/templates/config/WEB_SEARCH_UNAVAILABLE.md +3 -0
- package/templates/docker/claude-code-job/Dockerfile +34 -0
- package/templates/docker/claude-code-job/entrypoint.sh +149 -0
- package/templates/docker/claude-code-workspace/.tmux.conf +5 -0
- package/templates/docker/claude-code-workspace/Dockerfile +61 -0
- package/templates/docker/claude-code-workspace/entrypoint.sh +51 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +7 -0
- package/templates/docker/pi-coding-agent-job/Dockerfile +51 -0
- package/templates/docker/pi-coding-agent-job/entrypoint.sh +164 -0
- package/templates/docker-compose.local.yml +78 -0
- package/templates/docker-compose.yml +64 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +23 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/public/favicon.ico +0 -0
- package/templates/server.js +25 -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/google-docs/SKILL.md +23 -0
- package/templates/skills/google-docs/create.sh +69 -0
- package/templates/skills/google-drive/SKILL.md +47 -0
- package/templates/skills/google-drive/delete.sh +47 -0
- package/templates/skills/google-drive/download.sh +50 -0
- package/templates/skills/google-drive/list.sh +41 -0
- package/templates/skills/google-drive/upload.sh +76 -0
- package/templates/skills/kie-ai/SKILL.md +38 -0
- package/templates/skills/kie-ai/generate-image.sh +77 -0
- package/templates/skills/kie-ai/generate-video.sh +69 -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
- package/templates/skills/youtube-transcript/SKILL.md +41 -0
- package/templates/skills/youtube-transcript/package-lock.json +24 -0
- package/templates/skills/youtube-transcript/package.json +8 -0
- package/templates/skills/youtube-transcript/transcript.js +84 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
4
|
+
import { ChevronDownIcon, CheckIcon, SearchIcon } from "../icons.js";
|
|
5
|
+
import { cn } from "../../utils.js";
|
|
6
|
+
function Combobox({ options = [], value, onChange, placeholder = "Select...", loading = false, disabled = false }) {
|
|
7
|
+
const [open, setOpen] = useState(false);
|
|
8
|
+
const [filter, setFilter] = useState("");
|
|
9
|
+
const ref = useRef(null);
|
|
10
|
+
const inputRef = useRef(null);
|
|
11
|
+
const selectedLabel = options.find((o) => o.value === value)?.label || "";
|
|
12
|
+
const filtered = filter ? options.filter((o) => o.label.toLowerCase().includes(filter.toLowerCase())) : options;
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!open) return;
|
|
15
|
+
const handleClick = (e) => {
|
|
16
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
17
|
+
setOpen(false);
|
|
18
|
+
setFilter("");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const handleEsc = (e) => {
|
|
22
|
+
if (e.key === "Escape") {
|
|
23
|
+
setOpen(false);
|
|
24
|
+
setFilter("");
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
setTimeout(() => document.addEventListener("click", handleClick), 0);
|
|
28
|
+
document.addEventListener("keydown", handleEsc);
|
|
29
|
+
return () => {
|
|
30
|
+
document.removeEventListener("click", handleClick);
|
|
31
|
+
document.removeEventListener("keydown", handleEsc);
|
|
32
|
+
};
|
|
33
|
+
}, [open]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (open) {
|
|
36
|
+
setTimeout(() => inputRef.current?.focus(), 0);
|
|
37
|
+
}
|
|
38
|
+
}, [open]);
|
|
39
|
+
const handleSelect = useCallback((val) => {
|
|
40
|
+
onChange(val);
|
|
41
|
+
setOpen(false);
|
|
42
|
+
setFilter("");
|
|
43
|
+
}, [onChange]);
|
|
44
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
45
|
+
/* @__PURE__ */ jsxs(
|
|
46
|
+
"button",
|
|
47
|
+
{
|
|
48
|
+
type: "button",
|
|
49
|
+
disabled,
|
|
50
|
+
onClick: () => !disabled && setOpen(!open),
|
|
51
|
+
className: cn(
|
|
52
|
+
"flex items-center gap-2 rounded-lg border px-3 py-1.5 text-sm transition-colors w-full",
|
|
53
|
+
disabled ? "border-border bg-muted text-muted-foreground cursor-not-allowed opacity-60" : "border-border bg-background text-foreground hover:bg-muted cursor-pointer"
|
|
54
|
+
),
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ jsx("span", { className: cn("flex-1 text-left truncate", !value && "text-muted-foreground"), children: value ? selectedLabel : placeholder }),
|
|
57
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { size: 14, className: cn("text-muted-foreground transition-transform shrink-0", open && "rotate-180") })
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
open && /* @__PURE__ */ jsxs("div", { className: "absolute z-50 mt-1 w-full min-w-[200px] overflow-hidden rounded-lg border border-border bg-background shadow-lg", children: [
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2", children: [
|
|
63
|
+
/* @__PURE__ */ jsx(SearchIcon, { size: 14, className: "text-muted-foreground shrink-0" }),
|
|
64
|
+
/* @__PURE__ */ jsx(
|
|
65
|
+
"input",
|
|
66
|
+
{
|
|
67
|
+
ref: inputRef,
|
|
68
|
+
type: "text",
|
|
69
|
+
value: filter,
|
|
70
|
+
onChange: (e) => setFilter(e.target.value),
|
|
71
|
+
placeholder: "Search...",
|
|
72
|
+
className: "flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none"
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
] }),
|
|
76
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-[200px] overflow-y-auto p-1", children: loading ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "Loading..." }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "No results" }) : filtered.map((opt) => /* @__PURE__ */ jsxs(
|
|
77
|
+
"button",
|
|
78
|
+
{
|
|
79
|
+
type: "button",
|
|
80
|
+
onClick: () => handleSelect(opt.value),
|
|
81
|
+
className: cn(
|
|
82
|
+
"flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-sm text-left transition-colors",
|
|
83
|
+
"hover:bg-muted",
|
|
84
|
+
opt.value === value && "bg-muted"
|
|
85
|
+
),
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: opt.label }),
|
|
88
|
+
opt.value === value && /* @__PURE__ */ jsx(CheckIcon, { size: 14, className: "text-foreground shrink-0" })
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
opt.value
|
|
92
|
+
)) })
|
|
93
|
+
] })
|
|
94
|
+
] });
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
Combobox
|
|
98
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
+
import { ChevronDownIcon, CheckIcon, SearchIcon } from '../icons.js';
|
|
5
|
+
import { cn } from '../../utils.js';
|
|
6
|
+
|
|
7
|
+
export function Combobox({ options = [], value, onChange, placeholder = 'Select...', loading = false, disabled = false }) {
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [filter, setFilter] = useState('');
|
|
10
|
+
const ref = useRef(null);
|
|
11
|
+
const inputRef = useRef(null);
|
|
12
|
+
|
|
13
|
+
const selectedLabel = options.find((o) => o.value === value)?.label || '';
|
|
14
|
+
|
|
15
|
+
const filtered = filter
|
|
16
|
+
? options.filter((o) => o.label.toLowerCase().includes(filter.toLowerCase()))
|
|
17
|
+
: options;
|
|
18
|
+
|
|
19
|
+
// Close on click outside
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!open) return;
|
|
22
|
+
const handleClick = (e) => {
|
|
23
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
24
|
+
setOpen(false);
|
|
25
|
+
setFilter('');
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const handleEsc = (e) => {
|
|
29
|
+
if (e.key === 'Escape') {
|
|
30
|
+
setOpen(false);
|
|
31
|
+
setFilter('');
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
setTimeout(() => document.addEventListener('click', handleClick), 0);
|
|
35
|
+
document.addEventListener('keydown', handleEsc);
|
|
36
|
+
return () => {
|
|
37
|
+
document.removeEventListener('click', handleClick);
|
|
38
|
+
document.removeEventListener('keydown', handleEsc);
|
|
39
|
+
};
|
|
40
|
+
}, [open]);
|
|
41
|
+
|
|
42
|
+
// Auto-focus search input when opened
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (open) {
|
|
45
|
+
setTimeout(() => inputRef.current?.focus(), 0);
|
|
46
|
+
}
|
|
47
|
+
}, [open]);
|
|
48
|
+
|
|
49
|
+
const handleSelect = useCallback((val) => {
|
|
50
|
+
onChange(val);
|
|
51
|
+
setOpen(false);
|
|
52
|
+
setFilter('');
|
|
53
|
+
}, [onChange]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div ref={ref} className="relative">
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
disabled={disabled}
|
|
60
|
+
onClick={() => !disabled && setOpen(!open)}
|
|
61
|
+
className={cn(
|
|
62
|
+
'flex items-center gap-2 rounded-lg border px-3 py-1.5 text-sm transition-colors w-full',
|
|
63
|
+
disabled
|
|
64
|
+
? 'border-border bg-muted text-muted-foreground cursor-not-allowed opacity-60'
|
|
65
|
+
: 'border-border bg-background text-foreground hover:bg-muted cursor-pointer'
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
<span className={cn('flex-1 text-left truncate', !value && 'text-muted-foreground')}>
|
|
69
|
+
{value ? selectedLabel : placeholder}
|
|
70
|
+
</span>
|
|
71
|
+
<ChevronDownIcon size={14} className={cn('text-muted-foreground transition-transform shrink-0', open && 'rotate-180')} />
|
|
72
|
+
</button>
|
|
73
|
+
|
|
74
|
+
{open && (
|
|
75
|
+
<div className="absolute z-50 mt-1 w-full min-w-[200px] overflow-hidden rounded-lg border border-border bg-background shadow-lg">
|
|
76
|
+
<div className="flex items-center gap-2 border-b border-border px-3 py-2">
|
|
77
|
+
<SearchIcon size={14} className="text-muted-foreground shrink-0" />
|
|
78
|
+
<input
|
|
79
|
+
ref={inputRef}
|
|
80
|
+
type="text"
|
|
81
|
+
value={filter}
|
|
82
|
+
onChange={(e) => setFilter(e.target.value)}
|
|
83
|
+
placeholder="Search..."
|
|
84
|
+
className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="max-h-[200px] overflow-y-auto p-1">
|
|
88
|
+
{loading ? (
|
|
89
|
+
<div className="px-3 py-2 text-sm text-muted-foreground">Loading...</div>
|
|
90
|
+
) : filtered.length === 0 ? (
|
|
91
|
+
<div className="px-3 py-2 text-sm text-muted-foreground">No results</div>
|
|
92
|
+
) : (
|
|
93
|
+
filtered.map((opt) => (
|
|
94
|
+
<button
|
|
95
|
+
key={opt.value}
|
|
96
|
+
type="button"
|
|
97
|
+
onClick={() => handleSelect(opt.value)}
|
|
98
|
+
className={cn(
|
|
99
|
+
'flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-sm text-left transition-colors',
|
|
100
|
+
'hover:bg-muted',
|
|
101
|
+
opt.value === value && 'bg-muted'
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
<span className="flex-1 truncate">{opt.label}</span>
|
|
105
|
+
{opt.value === value && <CheckIcon size={14} className="text-foreground shrink-0" />}
|
|
106
|
+
</button>
|
|
107
|
+
))
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { cn } from "../../utils.js";
|
|
5
|
+
function ConfirmDialog({ open, onConfirm, onCancel, title, description, confirmLabel = "Delete", cancelLabel = "Cancel", variant = "destructive" }) {
|
|
6
|
+
const cancelRef = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (open && cancelRef.current) {
|
|
9
|
+
cancelRef.current.focus();
|
|
10
|
+
}
|
|
11
|
+
}, [open]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!open) return;
|
|
14
|
+
const handleEsc = (e) => {
|
|
15
|
+
if (e.key === "Escape") onCancel();
|
|
16
|
+
};
|
|
17
|
+
document.addEventListener("keydown", handleEsc);
|
|
18
|
+
return () => document.removeEventListener("keydown", handleEsc);
|
|
19
|
+
}, [open, onCancel]);
|
|
20
|
+
if (!open) return null;
|
|
21
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
22
|
+
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black/50", onClick: onCancel }),
|
|
23
|
+
/* @__PURE__ */ jsxs("div", { className: "relative z-50 w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg", onClick: (e) => e.stopPropagation(), children: [
|
|
24
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: title }),
|
|
25
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: description }),
|
|
26
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
27
|
+
/* @__PURE__ */ jsx(
|
|
28
|
+
"button",
|
|
29
|
+
{
|
|
30
|
+
ref: cancelRef,
|
|
31
|
+
onClick: onCancel,
|
|
32
|
+
className: "rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted",
|
|
33
|
+
children: cancelLabel
|
|
34
|
+
}
|
|
35
|
+
),
|
|
36
|
+
/* @__PURE__ */ jsx(
|
|
37
|
+
"button",
|
|
38
|
+
{
|
|
39
|
+
onClick: onConfirm,
|
|
40
|
+
className: cn(
|
|
41
|
+
"rounded-md px-3 py-1.5 text-sm font-medium text-white",
|
|
42
|
+
variant === "destructive" ? "bg-destructive hover:bg-destructive/90" : "bg-foreground hover:bg-foreground/90"
|
|
43
|
+
),
|
|
44
|
+
children: confirmLabel
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
] })
|
|
48
|
+
] })
|
|
49
|
+
] });
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
ConfirmDialog
|
|
53
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { cn } from '../../utils.js';
|
|
5
|
+
|
|
6
|
+
export function ConfirmDialog({ open, onConfirm, onCancel, title, description, confirmLabel = 'Delete', cancelLabel = 'Cancel', variant = 'destructive' }) {
|
|
7
|
+
const cancelRef = useRef(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (open && cancelRef.current) {
|
|
11
|
+
cancelRef.current.focus();
|
|
12
|
+
}
|
|
13
|
+
}, [open]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!open) return;
|
|
17
|
+
const handleEsc = (e) => {
|
|
18
|
+
if (e.key === 'Escape') onCancel();
|
|
19
|
+
};
|
|
20
|
+
document.addEventListener('keydown', handleEsc);
|
|
21
|
+
return () => document.removeEventListener('keydown', handleEsc);
|
|
22
|
+
}, [open, onCancel]);
|
|
23
|
+
|
|
24
|
+
if (!open) return null;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
28
|
+
<div className="fixed inset-0 bg-black/50" onClick={onCancel} />
|
|
29
|
+
<div className="relative z-50 w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg" onClick={(e) => e.stopPropagation()}>
|
|
30
|
+
<h3 className="text-lg font-semibold">{title}</h3>
|
|
31
|
+
{description && (
|
|
32
|
+
<p className="mt-2 text-sm text-muted-foreground">{description}</p>
|
|
33
|
+
)}
|
|
34
|
+
<div className="mt-4 flex justify-end gap-2">
|
|
35
|
+
<button
|
|
36
|
+
ref={cancelRef}
|
|
37
|
+
onClick={onCancel}
|
|
38
|
+
className="rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted"
|
|
39
|
+
>
|
|
40
|
+
{cancelLabel}
|
|
41
|
+
</button>
|
|
42
|
+
<button
|
|
43
|
+
onClick={onConfirm}
|
|
44
|
+
className={cn(
|
|
45
|
+
'rounded-md px-3 py-1.5 text-sm font-medium text-white',
|
|
46
|
+
variant === 'destructive'
|
|
47
|
+
? 'bg-destructive hover:bg-destructive/90'
|
|
48
|
+
: 'bg-foreground hover:bg-foreground/90'
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
51
|
+
{confirmLabel}
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|