thepopebot 1.2.76-beta.2 → 1.2.76-beta.21
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 +3 -3
- package/api/CLAUDE.md +11 -4
- package/api/index.js +56 -18
- package/bin/CLAUDE.md +7 -4
- package/bin/cli.js +25 -45
- package/config/CLAUDE.md +23 -4
- package/drizzle/0021_coding_agent_workspace.sql +1 -0
- package/drizzle/0022_organic_apocalypse.sql +16 -0
- package/drizzle/0023_needy_ender_wiggin.sql +1 -0
- package/drizzle/meta/0021_snapshot.json +639 -0
- package/drizzle/meta/0022_snapshot.json +743 -0
- package/drizzle/meta/0023_snapshot.json +750 -0
- package/drizzle/meta/_journal.json +21 -0
- package/lib/CLAUDE.md +2 -2
- package/lib/actions.js +9 -1
- package/lib/ai/CLAUDE.md +72 -57
- package/lib/ai/helper-llm.js +108 -0
- package/lib/ai/index.js +308 -438
- package/lib/ai/line-mappers.js +42 -24
- package/lib/ai/scope.js +26 -0
- package/lib/ai/sdk-adapters/CLAUDE.md +114 -0
- package/lib/ai/sdk-adapters/claude-code.js +120 -8
- package/lib/ai/system-prompt.js +34 -0
- package/lib/ai/workspace-setup.js +19 -35
- package/lib/channels/CLAUDE.md +14 -4
- package/lib/channels/base.js +6 -2
- package/lib/channels/commands/index.js +42 -0
- package/lib/channels/commands/session.js +53 -0
- package/lib/channels/commands/verify.js +18 -0
- package/lib/channels/telegram.js +79 -28
- package/lib/chat/CLAUDE.md +4 -4
- package/lib/chat/actions.js +270 -49
- package/lib/chat/api.js +185 -31
- package/lib/chat/components/CLAUDE.md +6 -2
- package/lib/chat/components/chat-input.js +77 -47
- package/lib/chat/components/chat-input.jsx +77 -40
- package/lib/chat/components/chat-page.js +2 -0
- package/lib/chat/components/chat-page.jsx +3 -0
- package/lib/chat/components/chat.js +62 -14
- package/lib/chat/components/chat.jsx +68 -10
- package/lib/chat/components/code-mode-toggle.js +141 -22
- package/lib/chat/components/code-mode-toggle.jsx +129 -20
- package/lib/chat/components/containers-page.js +58 -40
- package/lib/chat/components/containers-page.jsx +64 -25
- package/lib/chat/components/crons-page.js +17 -3
- package/lib/chat/components/crons-page.jsx +34 -6
- package/lib/chat/components/index.js +2 -2
- package/lib/chat/components/message.js +18 -3
- package/lib/chat/components/message.jsx +18 -3
- package/lib/chat/components/profile-page.js +182 -4
- package/lib/chat/components/profile-page.jsx +196 -1
- package/lib/chat/components/scope-picker.js +21 -0
- package/lib/chat/components/scope-picker.jsx +27 -0
- package/lib/chat/components/settings-chat-page.js +11 -11
- package/lib/chat/components/settings-chat-page.jsx +14 -18
- package/lib/chat/components/settings-coding-agents-page.js +110 -16
- package/lib/chat/components/settings-coding-agents-page.jsx +87 -3
- package/lib/chat/components/settings-github-page.js +5 -0
- package/lib/chat/components/settings-github-page.jsx +5 -0
- package/lib/chat/components/settings-layout.js +3 -3
- package/lib/chat/components/settings-layout.jsx +3 -3
- package/lib/chat/components/settings-secrets-layout.js +1 -2
- package/lib/chat/components/settings-secrets-layout.jsx +1 -2
- package/lib/chat/components/settings-secrets-page.js +180 -75
- package/lib/chat/components/settings-secrets-page.jsx +212 -66
- package/lib/chat/components/triggers-page.js +17 -3
- package/lib/chat/components/triggers-page.jsx +34 -6
- package/lib/chat/components/ui/combobox.js +18 -2
- package/lib/chat/components/ui/combobox.jsx +17 -1
- package/lib/chat/components/ui/dropdown-menu.js +23 -2
- package/lib/chat/components/ui/dropdown-menu.jsx +27 -2
- package/lib/chat/telegram-profile.js +33 -0
- package/lib/cluster/CLAUDE.md +9 -3
- package/lib/code/CLAUDE.md +11 -3
- package/lib/code/actions.js +47 -8
- package/lib/code/terminal-view.js +31 -21
- package/lib/code/terminal-view.jsx +32 -23
- package/lib/config.js +15 -4
- package/lib/containers/CLAUDE.md +16 -6
- package/lib/db/CLAUDE.md +5 -2
- package/lib/db/chats.js +9 -17
- package/lib/db/code-workspaces.js +8 -3
- package/lib/db/config.js +0 -1
- package/lib/db/index.js +12 -0
- package/lib/db/schema.js +24 -1
- package/lib/db/user-channels.js +129 -0
- package/lib/llm-providers.js +8 -0
- package/lib/maintenance.js +31 -21
- package/lib/tools/CLAUDE.md +12 -3
- package/lib/tools/assemblyai.js +17 -0
- package/lib/tools/create-agent-job.js +12 -8
- package/lib/tools/docker.js +34 -10
- package/lib/tools/github.js +34 -0
- package/lib/tools/telegram.js +106 -0
- package/lib/utils/render-md.js +44 -18
- package/package.json +8 -8
- package/setup/CLAUDE.md +11 -5
- package/setup/lib/providers.mjs +2 -1
- package/setup/lib/targets.mjs +13 -16
- package/setup/lib/telegram.mjs +8 -69
- package/templates/.env.example +0 -7
- package/templates/.github/workflows/rebuild-event-handler.yml +1 -1
- package/templates/.gitignore.template +1 -3
- package/templates/CLAUDE.md +1 -1
- package/templates/CLAUDE.md.template +29 -7
- package/templates/agent-job/CLAUDE.md.template +5 -3
- package/templates/agent-job/CRONS.json +16 -0
- package/templates/agent-job/SYSTEM.md +16 -11
- package/templates/agents/CLAUDE.md.template +17 -17
- package/templates/coding-workspace/CLAUDE.md.template +7 -0
- package/templates/data/CLAUDE.md.template +1 -1
- package/templates/docker-compose.custom.yml +1 -0
- package/templates/docker-compose.yml +1 -0
- package/templates/event-handler/CLAUDE.md.template +79 -0
- package/templates/event-handler/TRIGGERS.json +18 -2
- package/templates/skills/CLAUDE.md.template +20 -22
- package/templates/skills/{library/agent-job-secrets → agent-job-secrets}/SKILL.md +2 -2
- package/lib/ai/agent.js +0 -65
- package/lib/ai/async-channel.js +0 -51
- package/lib/ai/model.js +0 -130
- package/lib/ai/tools.js +0 -164
- package/lib/tools/openai.js +0 -37
- package/setup/lib/telegram-verify.mjs +0 -63
- package/setup/setup-telegram.mjs +0 -260
- package/templates/agent-job/SOUL.md +0 -17
- /package/templates/{skills/active/.gitkeep → coding-workspace/SYSTEM.md} +0 -0
- /package/templates/skills/{library/agent-job-secrets → agent-job-secrets}/agent-job-secrets.js +0 -0
- /package/templates/skills/{library/playwright-cli → playwright-cli}/SKILL.md +0 -0
|
@@ -3,7 +3,17 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
4
|
import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, PlusIcon } from "./icons.js";
|
|
5
5
|
import { SecretRow, EmptyState, formatDate, timeAgo } from "./settings-shared.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createNewApiKey,
|
|
8
|
+
getApiKeys,
|
|
9
|
+
deleteApiKey,
|
|
10
|
+
getApiKeySettings,
|
|
11
|
+
updateApiKeySetting,
|
|
12
|
+
regenerateWebhookSecret,
|
|
13
|
+
getTelegramStatus,
|
|
14
|
+
validateTelegramToken,
|
|
15
|
+
registerTelegramWebhook
|
|
16
|
+
} from "../actions.js";
|
|
7
17
|
function ApiKeysListPage() {
|
|
8
18
|
const [keys, setKeys] = useState([]);
|
|
9
19
|
const [loading, setLoading] = useState(true);
|
|
@@ -250,98 +260,193 @@ function ApiKeysVoicePage() {
|
|
|
250
260
|
) })
|
|
251
261
|
] });
|
|
252
262
|
}
|
|
263
|
+
function StepIndicator({ n, state }) {
|
|
264
|
+
const cls = state === "done" ? "bg-green-500 text-white border-green-500" : state === "active" ? "border-foreground text-foreground" : "border-border text-muted-foreground";
|
|
265
|
+
return /* @__PURE__ */ jsx("div", { className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full border text-xs font-medium ${cls}`, children: state === "done" ? /* @__PURE__ */ jsx(CheckIcon, { className: "h-3 w-3" }) : n });
|
|
266
|
+
}
|
|
253
267
|
function ApiKeysTelegramPage() {
|
|
254
|
-
const [
|
|
268
|
+
const [status, setStatus] = useState(null);
|
|
255
269
|
const [loading, setLoading] = useState(true);
|
|
256
|
-
const [
|
|
257
|
-
const [
|
|
258
|
-
const [
|
|
259
|
-
const
|
|
270
|
+
const [tokenInput, setTokenInput] = useState("");
|
|
271
|
+
const [tokenEditing, setTokenEditing] = useState(false);
|
|
272
|
+
const [tokenSaving, setTokenSaving] = useState(false);
|
|
273
|
+
const [tokenError, setTokenError] = useState(null);
|
|
274
|
+
const [webhookSaving, setWebhookSaving] = useState(false);
|
|
275
|
+
const [webhookError, setWebhookError] = useState(null);
|
|
276
|
+
const loadStatus = async () => {
|
|
260
277
|
try {
|
|
261
|
-
const result = await
|
|
262
|
-
|
|
263
|
-
setChatId(result.telegramChatId || "");
|
|
264
|
-
} catch {
|
|
278
|
+
const result = await getTelegramStatus();
|
|
279
|
+
setStatus(result);
|
|
265
280
|
} finally {
|
|
266
281
|
setLoading(false);
|
|
267
282
|
}
|
|
268
283
|
};
|
|
269
284
|
useEffect(() => {
|
|
270
|
-
|
|
285
|
+
loadStatus();
|
|
271
286
|
}, []);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
if (loading) {
|
|
288
|
+
return /* @__PURE__ */ jsx("div", { className: "h-48 animate-pulse rounded-md bg-border/50" });
|
|
289
|
+
}
|
|
290
|
+
const step1Done = !!status.botInfo;
|
|
291
|
+
const step2Done = step1Done && status.webhookInfo?.url;
|
|
292
|
+
const handleSaveToken = async () => {
|
|
293
|
+
setTokenSaving(true);
|
|
294
|
+
setTokenError(null);
|
|
295
|
+
const validation = await validateTelegramToken(tokenInput.trim());
|
|
296
|
+
if (!validation.valid) {
|
|
297
|
+
setTokenError(validation.error || "Invalid token");
|
|
298
|
+
setTokenSaving(false);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const saveResult = await updateApiKeySetting("TELEGRAM_BOT_TOKEN", tokenInput.trim());
|
|
302
|
+
if (saveResult?.error) {
|
|
303
|
+
setTokenError(saveResult.error);
|
|
304
|
+
setTokenSaving(false);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
setTokenInput("");
|
|
308
|
+
setTokenEditing(false);
|
|
309
|
+
await loadStatus();
|
|
310
|
+
setTokenSaving(false);
|
|
278
311
|
};
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
await
|
|
282
|
-
await
|
|
283
|
-
|
|
312
|
+
const handleClearToken = async () => {
|
|
313
|
+
setTokenSaving(true);
|
|
314
|
+
await updateApiKeySetting("TELEGRAM_BOT_TOKEN", "");
|
|
315
|
+
await loadStatus();
|
|
316
|
+
setTokenSaving(false);
|
|
284
317
|
};
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
318
|
+
const handleRegisterWebhook = async () => {
|
|
319
|
+
setWebhookSaving(true);
|
|
320
|
+
setWebhookError(null);
|
|
321
|
+
const result = await registerTelegramWebhook();
|
|
322
|
+
if (result?.error) setWebhookError(result.error);
|
|
323
|
+
await loadStatus();
|
|
324
|
+
setWebhookSaving(false);
|
|
289
325
|
};
|
|
290
|
-
if (loading) {
|
|
291
|
-
return /* @__PURE__ */ jsx("div", { className: "h-24 animate-pulse rounded-md bg-border/50" });
|
|
292
|
-
}
|
|
293
326
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
294
327
|
/* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
|
|
295
328
|
/* @__PURE__ */ jsx("h2", { className: "text-base font-medium", children: "Telegram" }),
|
|
296
329
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Connect a Telegram bot to receive and send messages through your agent." })
|
|
297
330
|
] }),
|
|
298
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
299
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
300
|
-
/* @__PURE__ */ jsx(
|
|
301
|
-
|
|
302
|
-
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
331
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
332
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
333
|
+
/* @__PURE__ */ jsx(StepIndicator, { n: 1, state: step1Done ? "done" : "active" }),
|
|
334
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
335
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
336
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
337
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Bot Token" }),
|
|
338
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
|
|
339
|
+
"Create a bot with",
|
|
340
|
+
" ",
|
|
341
|
+
/* @__PURE__ */ jsx(
|
|
342
|
+
"a",
|
|
343
|
+
{
|
|
344
|
+
href: "https://t.me/BotFather",
|
|
345
|
+
target: "_blank",
|
|
346
|
+
rel: "noopener noreferrer",
|
|
347
|
+
className: "underline hover:text-foreground",
|
|
348
|
+
children: "@BotFather"
|
|
349
|
+
}
|
|
350
|
+
),
|
|
351
|
+
" ",
|
|
352
|
+
"and paste the token below."
|
|
353
|
+
] })
|
|
354
|
+
] }),
|
|
355
|
+
step1Done && !tokenEditing && /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
|
|
356
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm font-medium", children: [
|
|
357
|
+
"@",
|
|
358
|
+
status.botInfo.username
|
|
359
|
+
] }),
|
|
360
|
+
/* @__PURE__ */ jsx(
|
|
361
|
+
"button",
|
|
362
|
+
{
|
|
363
|
+
onClick: () => setTokenEditing(true),
|
|
364
|
+
className: "text-xs text-muted-foreground hover:text-foreground underline transition-colors",
|
|
365
|
+
children: "Change"
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
] })
|
|
369
|
+
] }),
|
|
370
|
+
(!step1Done || tokenEditing) && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-col gap-2", children: [
|
|
371
|
+
/* @__PURE__ */ jsx(
|
|
372
|
+
"input",
|
|
373
|
+
{
|
|
374
|
+
type: "password",
|
|
375
|
+
value: tokenInput,
|
|
376
|
+
onChange: (e) => setTokenInput(e.target.value),
|
|
377
|
+
placeholder: "123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
|
|
378
|
+
className: "rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground",
|
|
379
|
+
onKeyDown: (e) => e.key === "Enter" && tokenInput.trim() && handleSaveToken()
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
tokenError && /* @__PURE__ */ jsx("div", { className: "text-xs text-destructive", children: tokenError }),
|
|
383
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
384
|
+
/* @__PURE__ */ jsx(
|
|
385
|
+
"button",
|
|
386
|
+
{
|
|
387
|
+
onClick: handleSaveToken,
|
|
388
|
+
disabled: tokenSaving || !tokenInput.trim(),
|
|
389
|
+
className: "rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors",
|
|
390
|
+
children: tokenSaving ? "Validating..." : "Validate & Save"
|
|
391
|
+
}
|
|
392
|
+
),
|
|
393
|
+
tokenEditing && /* @__PURE__ */ jsx(
|
|
394
|
+
"button",
|
|
395
|
+
{
|
|
396
|
+
onClick: () => {
|
|
397
|
+
setTokenEditing(false);
|
|
398
|
+
setTokenInput("");
|
|
399
|
+
setTokenError(null);
|
|
400
|
+
},
|
|
401
|
+
className: "rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
|
|
402
|
+
children: "Cancel"
|
|
403
|
+
}
|
|
404
|
+
),
|
|
405
|
+
step1Done && tokenEditing && /* @__PURE__ */ jsx(
|
|
406
|
+
"button",
|
|
407
|
+
{
|
|
408
|
+
onClick: handleClearToken,
|
|
409
|
+
className: "ml-auto rounded-md border border-destructive text-destructive px-2.5 py-1.5 text-xs font-medium hover:bg-destructive/10 transition-colors",
|
|
410
|
+
children: "Remove"
|
|
411
|
+
}
|
|
412
|
+
)
|
|
413
|
+
] })
|
|
414
|
+
] })
|
|
343
415
|
] })
|
|
344
|
-
] })
|
|
416
|
+
] }) }),
|
|
417
|
+
/* @__PURE__ */ jsx("div", { className: `rounded-lg border bg-card p-4 ${!step1Done ? "opacity-50 pointer-events-none" : ""}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
418
|
+
/* @__PURE__ */ jsx(StepIndicator, { n: 2, state: step2Done ? "done" : step1Done ? "active" : "pending" }),
|
|
419
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
420
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between gap-2", children: /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
421
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Webhook" }),
|
|
422
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Register your public URL with Telegram so it can deliver messages to your bot." }),
|
|
423
|
+
step2Done && /* @__PURE__ */ jsxs("div", { className: "mt-2 text-xs text-muted-foreground truncate", children: [
|
|
424
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: status.webhookInfo.url }),
|
|
425
|
+
status.webhookInfo.pendingUpdates > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-yellow-500", children: [
|
|
426
|
+
"(",
|
|
427
|
+
status.webhookInfo.pendingUpdates,
|
|
428
|
+
" pending)"
|
|
429
|
+
] }),
|
|
430
|
+
status.webhookInfo.lastErrorMessage && /* @__PURE__ */ jsxs("div", { className: "mt-1 text-destructive", children: [
|
|
431
|
+
"Last error: ",
|
|
432
|
+
status.webhookInfo.lastErrorMessage
|
|
433
|
+
] })
|
|
434
|
+
] })
|
|
435
|
+
] }) }),
|
|
436
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
|
|
437
|
+
webhookError && /* @__PURE__ */ jsx("div", { className: "text-xs text-destructive mb-2", children: webhookError }),
|
|
438
|
+
/* @__PURE__ */ jsx(
|
|
439
|
+
"button",
|
|
440
|
+
{
|
|
441
|
+
onClick: handleRegisterWebhook,
|
|
442
|
+
disabled: !step1Done || webhookSaving,
|
|
443
|
+
className: "rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors",
|
|
444
|
+
children: webhookSaving ? "Registering..." : step2Done ? "Re-register Webhook" : "Register Webhook"
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
] })
|
|
448
|
+
] })
|
|
449
|
+
] }) })
|
|
345
450
|
] })
|
|
346
451
|
] });
|
|
347
452
|
}
|
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, PlusIcon } from './icons.js';
|
|
5
5
|
import { SecretRow, EmptyState, formatDate, timeAgo } from './settings-shared.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createNewApiKey,
|
|
8
|
+
getApiKeys,
|
|
9
|
+
deleteApiKey,
|
|
10
|
+
getApiKeySettings,
|
|
11
|
+
updateApiKeySetting,
|
|
12
|
+
regenerateWebhookSecret,
|
|
13
|
+
getTelegramStatus,
|
|
14
|
+
validateTelegramToken,
|
|
15
|
+
registerTelegramWebhook,
|
|
16
|
+
} from '../actions.js';
|
|
7
17
|
|
|
8
18
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
19
|
// Keys sub-tab — Multiple named API keys
|
|
@@ -288,100 +298,236 @@ export function ApiKeysVoicePage() {
|
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
291
|
-
// Telegram sub-tab —
|
|
301
|
+
// Telegram sub-tab — Guided setup (bot token → webhook → chat verification)
|
|
292
302
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
293
303
|
|
|
304
|
+
function StepIndicator({ n, state }) {
|
|
305
|
+
// state: 'done' | 'active' | 'pending'
|
|
306
|
+
const cls =
|
|
307
|
+
state === 'done'
|
|
308
|
+
? 'bg-green-500 text-white border-green-500'
|
|
309
|
+
: state === 'active'
|
|
310
|
+
? 'border-foreground text-foreground'
|
|
311
|
+
: 'border-border text-muted-foreground';
|
|
312
|
+
return (
|
|
313
|
+
<div className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full border text-xs font-medium ${cls}`}>
|
|
314
|
+
{state === 'done' ? <CheckIcon className="h-3 w-3" /> : n}
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
294
319
|
export function ApiKeysTelegramPage() {
|
|
295
|
-
const [
|
|
320
|
+
const [status, setStatus] = useState(null);
|
|
296
321
|
const [loading, setLoading] = useState(true);
|
|
297
|
-
const [chatId, setChatId] = useState('');
|
|
298
|
-
const [savingChatId, setSavingChatId] = useState(false);
|
|
299
|
-
const [saving, setSaving] = useState(false);
|
|
300
322
|
|
|
301
|
-
|
|
323
|
+
// Step 1 — bot token
|
|
324
|
+
const [tokenInput, setTokenInput] = useState('');
|
|
325
|
+
const [tokenEditing, setTokenEditing] = useState(false);
|
|
326
|
+
const [tokenSaving, setTokenSaving] = useState(false);
|
|
327
|
+
const [tokenError, setTokenError] = useState(null);
|
|
328
|
+
|
|
329
|
+
// Step 2 — webhook
|
|
330
|
+
const [webhookSaving, setWebhookSaving] = useState(false);
|
|
331
|
+
const [webhookError, setWebhookError] = useState(null);
|
|
332
|
+
|
|
333
|
+
const loadStatus = async () => {
|
|
302
334
|
try {
|
|
303
|
-
const result = await
|
|
304
|
-
|
|
305
|
-
setChatId(result.telegramChatId || '');
|
|
306
|
-
} catch {
|
|
307
|
-
// ignore
|
|
335
|
+
const result = await getTelegramStatus();
|
|
336
|
+
setStatus(result);
|
|
308
337
|
} finally {
|
|
309
338
|
setLoading(false);
|
|
310
339
|
}
|
|
311
340
|
};
|
|
312
341
|
|
|
313
342
|
useEffect(() => {
|
|
314
|
-
|
|
343
|
+
loadStatus();
|
|
315
344
|
}, []);
|
|
316
345
|
|
|
317
|
-
|
|
346
|
+
if (loading) {
|
|
347
|
+
return <div className="h-48 animate-pulse rounded-md bg-border/50" />;
|
|
348
|
+
}
|
|
318
349
|
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
350
|
+
const step1Done = !!status.botInfo;
|
|
351
|
+
const step2Done = step1Done && status.webhookInfo?.url;
|
|
352
|
+
|
|
353
|
+
// Step 1 handlers
|
|
354
|
+
const handleSaveToken = async () => {
|
|
355
|
+
setTokenSaving(true);
|
|
356
|
+
setTokenError(null);
|
|
357
|
+
const validation = await validateTelegramToken(tokenInput.trim());
|
|
358
|
+
if (!validation.valid) {
|
|
359
|
+
setTokenError(validation.error || 'Invalid token');
|
|
360
|
+
setTokenSaving(false);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const saveResult = await updateApiKeySetting('TELEGRAM_BOT_TOKEN', tokenInput.trim());
|
|
364
|
+
if (saveResult?.error) {
|
|
365
|
+
setTokenError(saveResult.error);
|
|
366
|
+
setTokenSaving(false);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
setTokenInput('');
|
|
370
|
+
setTokenEditing(false);
|
|
371
|
+
await loadStatus();
|
|
372
|
+
setTokenSaving(false);
|
|
324
373
|
};
|
|
325
374
|
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
await
|
|
329
|
-
await
|
|
330
|
-
|
|
375
|
+
const handleClearToken = async () => {
|
|
376
|
+
setTokenSaving(true);
|
|
377
|
+
await updateApiKeySetting('TELEGRAM_BOT_TOKEN', '');
|
|
378
|
+
await loadStatus();
|
|
379
|
+
setTokenSaving(false);
|
|
331
380
|
};
|
|
332
381
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
382
|
+
// Step 2 handlers
|
|
383
|
+
const handleRegisterWebhook = async () => {
|
|
384
|
+
setWebhookSaving(true);
|
|
385
|
+
setWebhookError(null);
|
|
386
|
+
const result = await registerTelegramWebhook();
|
|
387
|
+
if (result?.error) setWebhookError(result.error);
|
|
388
|
+
await loadStatus();
|
|
389
|
+
setWebhookSaving(false);
|
|
337
390
|
};
|
|
338
391
|
|
|
339
|
-
if (loading) {
|
|
340
|
-
return <div className="h-24 animate-pulse rounded-md bg-border/50" />;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
392
|
return (
|
|
344
393
|
<div>
|
|
345
394
|
<div className="mb-4">
|
|
346
395
|
<h2 className="text-base font-medium">Telegram</h2>
|
|
347
|
-
<p className="text-sm text-muted-foreground">
|
|
396
|
+
<p className="text-sm text-muted-foreground">
|
|
397
|
+
Connect a Telegram bot to receive and send messages through your agent.
|
|
398
|
+
</p>
|
|
348
399
|
</div>
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
400
|
+
|
|
401
|
+
<div className="space-y-3">
|
|
402
|
+
{/* ─── Step 1: Bot Token ─── */}
|
|
403
|
+
<div className="rounded-lg border bg-card p-4">
|
|
404
|
+
<div className="flex items-start gap-3">
|
|
405
|
+
<StepIndicator n={1} state={step1Done ? 'done' : 'active'} />
|
|
406
|
+
<div className="flex-1 min-w-0">
|
|
407
|
+
<div className="flex items-center justify-between gap-2">
|
|
408
|
+
<div>
|
|
409
|
+
<h3 className="text-sm font-medium">Bot Token</h3>
|
|
410
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
411
|
+
Create a bot with{' '}
|
|
412
|
+
<a
|
|
413
|
+
href="https://t.me/BotFather"
|
|
414
|
+
target="_blank"
|
|
415
|
+
rel="noopener noreferrer"
|
|
416
|
+
className="underline hover:text-foreground"
|
|
417
|
+
>
|
|
418
|
+
@BotFather
|
|
419
|
+
</a>{' '}
|
|
420
|
+
and paste the token below.
|
|
421
|
+
</p>
|
|
422
|
+
</div>
|
|
423
|
+
{step1Done && !tokenEditing && (
|
|
424
|
+
<div className="shrink-0 text-right">
|
|
425
|
+
<div className="text-sm font-medium">@{status.botInfo.username}</div>
|
|
426
|
+
<button
|
|
427
|
+
onClick={() => setTokenEditing(true)}
|
|
428
|
+
className="text-xs text-muted-foreground hover:text-foreground underline transition-colors"
|
|
429
|
+
>
|
|
430
|
+
Change
|
|
431
|
+
</button>
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
{(!step1Done || tokenEditing) && (
|
|
437
|
+
<div className="mt-3 flex flex-col gap-2">
|
|
438
|
+
<input
|
|
439
|
+
type="password"
|
|
440
|
+
value={tokenInput}
|
|
441
|
+
onChange={(e) => setTokenInput(e.target.value)}
|
|
442
|
+
placeholder="123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
|
443
|
+
className="rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
|
|
444
|
+
onKeyDown={(e) => e.key === 'Enter' && tokenInput.trim() && handleSaveToken()}
|
|
445
|
+
/>
|
|
446
|
+
{tokenError && <div className="text-xs text-destructive">{tokenError}</div>}
|
|
447
|
+
<div className="flex items-center gap-2">
|
|
448
|
+
<button
|
|
449
|
+
onClick={handleSaveToken}
|
|
450
|
+
disabled={tokenSaving || !tokenInput.trim()}
|
|
451
|
+
className="rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors"
|
|
452
|
+
>
|
|
453
|
+
{tokenSaving ? 'Validating...' : 'Validate & Save'}
|
|
454
|
+
</button>
|
|
455
|
+
{tokenEditing && (
|
|
456
|
+
<button
|
|
457
|
+
onClick={() => {
|
|
458
|
+
setTokenEditing(false);
|
|
459
|
+
setTokenInput('');
|
|
460
|
+
setTokenError(null);
|
|
461
|
+
}}
|
|
462
|
+
className="rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
463
|
+
>
|
|
464
|
+
Cancel
|
|
465
|
+
</button>
|
|
466
|
+
)}
|
|
467
|
+
{step1Done && tokenEditing && (
|
|
468
|
+
<button
|
|
469
|
+
onClick={handleClearToken}
|
|
470
|
+
className="ml-auto rounded-md border border-destructive text-destructive px-2.5 py-1.5 text-xs font-medium hover:bg-destructive/10 transition-colors"
|
|
471
|
+
>
|
|
472
|
+
Remove
|
|
473
|
+
</button>
|
|
474
|
+
)}
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
)}
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
364
480
|
</div>
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
481
|
+
|
|
482
|
+
{/* ─── Step 2: Webhook ─── */}
|
|
483
|
+
<div className={`rounded-lg border bg-card p-4 ${!step1Done ? 'opacity-50 pointer-events-none' : ''}`}>
|
|
484
|
+
<div className="flex items-start gap-3">
|
|
485
|
+
<StepIndicator n={2} state={step2Done ? 'done' : step1Done ? 'active' : 'pending'} />
|
|
486
|
+
<div className="flex-1 min-w-0">
|
|
487
|
+
<div className="flex items-center justify-between gap-2">
|
|
488
|
+
<div className="min-w-0">
|
|
489
|
+
<h3 className="text-sm font-medium">Webhook</h3>
|
|
490
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
491
|
+
Register your public URL with Telegram so it can deliver messages to your bot.
|
|
492
|
+
</p>
|
|
493
|
+
{step2Done && (
|
|
494
|
+
<div className="mt-2 text-xs text-muted-foreground truncate">
|
|
495
|
+
<span className="font-mono">{status.webhookInfo.url}</span>
|
|
496
|
+
{status.webhookInfo.pendingUpdates > 0 && (
|
|
497
|
+
<span className="ml-2 text-yellow-500">
|
|
498
|
+
({status.webhookInfo.pendingUpdates} pending)
|
|
499
|
+
</span>
|
|
500
|
+
)}
|
|
501
|
+
{status.webhookInfo.lastErrorMessage && (
|
|
502
|
+
<div className="mt-1 text-destructive">
|
|
503
|
+
Last error: {status.webhookInfo.lastErrorMessage}
|
|
504
|
+
</div>
|
|
505
|
+
)}
|
|
506
|
+
</div>
|
|
507
|
+
)}
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<div className="mt-3">
|
|
512
|
+
{webhookError && (
|
|
513
|
+
<div className="text-xs text-destructive mb-2">{webhookError}</div>
|
|
514
|
+
)}
|
|
515
|
+
<button
|
|
516
|
+
onClick={handleRegisterWebhook}
|
|
517
|
+
disabled={!step1Done || webhookSaving}
|
|
518
|
+
className="rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors"
|
|
519
|
+
>
|
|
520
|
+
{webhookSaving
|
|
521
|
+
? 'Registering...'
|
|
522
|
+
: step2Done
|
|
523
|
+
? 'Re-register Webhook'
|
|
524
|
+
: 'Register Webhook'}
|
|
525
|
+
</button>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
383
528
|
</div>
|
|
384
529
|
</div>
|
|
530
|
+
|
|
385
531
|
</div>
|
|
386
532
|
</div>
|
|
387
533
|
);
|
|
@@ -40,9 +40,19 @@ function ActionCard({ action, index }) {
|
|
|
40
40
|
] }),
|
|
41
41
|
type === "agent" && action.job && /* @__PURE__ */ jsxs("div", { children: [
|
|
42
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.
|
|
44
|
-
/* @__PURE__ */
|
|
45
|
-
|
|
43
|
+
(action.agent_backend || action.llm_model || action.scope) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2 flex-wrap", children: [
|
|
44
|
+
action.agent_backend && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
45
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Agent:" }),
|
|
46
|
+
/* @__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.agent_backend })
|
|
47
|
+
] }),
|
|
48
|
+
action.llm_model && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
49
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Model:" }),
|
|
50
|
+
/* @__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_model })
|
|
51
|
+
] }),
|
|
52
|
+
action.scope && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
53
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Scope:" }),
|
|
54
|
+
/* @__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-mono", children: action.scope })
|
|
55
|
+
] })
|
|
46
56
|
] })
|
|
47
57
|
] }),
|
|
48
58
|
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 }),
|
|
@@ -54,6 +64,10 @@ function ActionCard({ action, index }) {
|
|
|
54
64
|
action.vars && Object.keys(action.vars).length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
55
65
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Variables" }),
|
|
56
66
|
/* @__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) })
|
|
67
|
+
] }),
|
|
68
|
+
action.headers && Object.keys(action.headers).length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
69
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Headers" }),
|
|
70
|
+
/* @__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.headers, null, 2) })
|
|
57
71
|
] })
|
|
58
72
|
] })
|
|
59
73
|
] });
|