@chrysb/alphaclaw 0.3.3 → 0.3.4

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.
Files changed (31) hide show
  1. package/bin/alphaclaw.js +18 -0
  2. package/lib/plugin/usage-tracker/index.js +308 -0
  3. package/lib/plugin/usage-tracker/openclaw.plugin.json +8 -0
  4. package/lib/public/css/explorer.css +51 -1
  5. package/lib/public/css/shell.css +3 -1
  6. package/lib/public/css/theme.css +35 -0
  7. package/lib/public/js/app.js +73 -24
  8. package/lib/public/js/components/file-tree.js +231 -28
  9. package/lib/public/js/components/file-viewer.js +193 -20
  10. package/lib/public/js/components/segmented-control.js +33 -0
  11. package/lib/public/js/components/sidebar.js +14 -32
  12. package/lib/public/js/components/telegram-workspace/index.js +353 -0
  13. package/lib/public/js/components/telegram-workspace/manage.js +397 -0
  14. package/lib/public/js/components/telegram-workspace/onboarding.js +616 -0
  15. package/lib/public/js/components/usage-tab.js +528 -0
  16. package/lib/public/js/components/watchdog-tab.js +1 -1
  17. package/lib/public/js/lib/api.js +25 -1
  18. package/lib/public/js/lib/telegram-api.js +78 -0
  19. package/lib/public/js/lib/ui-settings.js +38 -0
  20. package/lib/public/setup.html +34 -30
  21. package/lib/server/alphaclaw-version.js +3 -3
  22. package/lib/server/constants.js +1 -0
  23. package/lib/server/onboarding/openclaw.js +15 -0
  24. package/lib/server/routes/auth.js +5 -1
  25. package/lib/server/routes/telegram.js +185 -60
  26. package/lib/server/routes/usage.js +133 -0
  27. package/lib/server/usage-db.js +570 -0
  28. package/lib/server.js +21 -1
  29. package/lib/setup/core-prompts/AGENTS.md +0 -101
  30. package/package.json +1 -1
  31. package/lib/public/js/components/telegram-workspace.js +0 -1365
@@ -1,1365 +0,0 @@
1
- import { h } from "https://esm.sh/preact";
2
- import { useState, useEffect } from "https://esm.sh/preact/hooks";
3
- import htm from "https://esm.sh/htm";
4
- import { Badge } from "./badge.js";
5
- import { showToast } from "./toast.js";
6
- import { ActionButton } from "./action-button.js";
7
-
8
- const html = htm.bind(h);
9
-
10
- const authFetch = async (url, opts = {}) => {
11
- const res = await fetch(url, opts);
12
- if (res.status === 401) {
13
- window.location.href = "/setup";
14
- throw new Error("Unauthorized");
15
- }
16
- return res;
17
- };
18
- const api = {
19
- verifyBot: async () => {
20
- const res = await authFetch("/api/telegram/bot");
21
- return res.json();
22
- },
23
- workspace: async () => {
24
- const res = await authFetch("/api/telegram/workspace");
25
- return res.json();
26
- },
27
- resetWorkspace: async () => {
28
- const res = await authFetch("/api/telegram/workspace/reset", {
29
- method: "POST",
30
- });
31
- return res.json();
32
- },
33
- verifyGroup: async (groupId) => {
34
- const res = await authFetch("/api/telegram/groups/verify", {
35
- method: "POST",
36
- headers: { "Content-Type": "application/json" },
37
- body: JSON.stringify({ groupId }),
38
- });
39
- return res.json();
40
- },
41
- listTopics: async (groupId) => {
42
- const res = await authFetch(
43
- `/api/telegram/groups/${encodeURIComponent(groupId)}/topics`,
44
- );
45
- return res.json();
46
- },
47
- createTopicsBulk: async (groupId, topics) => {
48
- const res = await authFetch(
49
- `/api/telegram/groups/${encodeURIComponent(groupId)}/topics/bulk`,
50
- {
51
- method: "POST",
52
- headers: { "Content-Type": "application/json" },
53
- body: JSON.stringify({ topics }),
54
- },
55
- );
56
- return res.json();
57
- },
58
- deleteTopic: async (groupId, topicId) => {
59
- const res = await authFetch(
60
- `/api/telegram/groups/${encodeURIComponent(groupId)}/topics/${topicId}`,
61
- { method: "DELETE" },
62
- );
63
- return res.json();
64
- },
65
- updateTopic: async (groupId, topicId, payload) => {
66
- const res = await authFetch(
67
- `/api/telegram/groups/${encodeURIComponent(groupId)}/topics/${encodeURIComponent(topicId)}`,
68
- {
69
- method: "PUT",
70
- headers: { "Content-Type": "application/json" },
71
- body: JSON.stringify(payload),
72
- },
73
- );
74
- return res.json();
75
- },
76
- configureGroup: async (groupId, payload) => {
77
- const res = await authFetch(
78
- `/api/telegram/groups/${encodeURIComponent(groupId)}/configure`,
79
- {
80
- method: "POST",
81
- headers: { "Content-Type": "application/json" },
82
- body: JSON.stringify(payload),
83
- },
84
- );
85
- return res.json();
86
- },
87
- };
88
-
89
- const kSteps = [
90
- { id: "verify-bot", label: "Verify Bot" },
91
- { id: "create-group", label: "Create Group" },
92
- { id: "add-bot", label: "Add Bot" },
93
- { id: "topics", label: "Topics" },
94
- { id: "summary", label: "Summary" },
95
- ];
96
-
97
- const kTelegramWorkspaceStorageKey = "telegram-workspace-state-v1";
98
- const kTelegramWorkspaceCacheKey = "telegram-workspace-cache-v1";
99
- const loadTelegramWorkspaceState = () => {
100
- try {
101
- const raw = window.localStorage.getItem(kTelegramWorkspaceStorageKey);
102
- if (!raw) return {};
103
- const parsed = JSON.parse(raw);
104
- return parsed && typeof parsed === "object" ? parsed : {};
105
- } catch {
106
- return {};
107
- }
108
- };
109
- const loadTelegramWorkspaceCache = () => {
110
- try {
111
- const raw = window.localStorage.getItem(kTelegramWorkspaceCacheKey);
112
- if (!raw) return null;
113
- const parsed = JSON.parse(raw);
114
- const data = parsed?.data;
115
- if (!data || typeof data !== "object") return null;
116
- return data;
117
- } catch {
118
- return null;
119
- }
120
- };
121
- const saveTelegramWorkspaceCache = (data) => {
122
- try {
123
- window.localStorage.setItem(
124
- kTelegramWorkspaceCacheKey,
125
- JSON.stringify({ cachedAt: Date.now(), data }),
126
- );
127
- } catch {}
128
- };
129
-
130
- const StepIndicator = ({ currentStep }) => html`
131
- <div class="flex items-center gap-1 mb-6">
132
- ${kSteps.map(
133
- (s, i) => html`
134
- <div
135
- class="h-1 flex-1 rounded-full transition-colors ${i <= currentStep
136
- ? "bg-accent"
137
- : "bg-border"}"
138
- style=${i <= currentStep ? "background: var(--accent)" : ""}
139
- />
140
- `,
141
- )}
142
- </div>
143
- `;
144
-
145
- const BackButton = ({ onBack }) => html`
146
- <button
147
- onclick=${onBack}
148
- class="flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-300 transition-colors mb-4"
149
- >
150
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
151
- <path
152
- d="M10.354 3.354a.5.5 0 00-.708-.708l-5 5a.5.5 0 000 .708l5 5a.5.5 0 00.708-.708L5.707 8l4.647-4.646z"
153
- />
154
- </svg>
155
- Back
156
- </button>
157
- `;
158
-
159
- // Step 1: Verify Bot
160
- const VerifyBotStep = ({ botInfo, setBotInfo, onNext }) => {
161
- const [loading, setLoading] = useState(false);
162
- const [error, setError] = useState(null);
163
-
164
- const verify = async () => {
165
- setLoading(true);
166
- setError(null);
167
- try {
168
- const data = await api.verifyBot();
169
- if (!data.ok) throw new Error(data.error);
170
- setBotInfo(data.bot);
171
- } catch (e) {
172
- setError(e.message);
173
- }
174
- setLoading(false);
175
- };
176
-
177
- useEffect(() => {
178
- if (!botInfo) verify();
179
- }, []);
180
-
181
- return html`
182
- <div class="space-y-4">
183
- <h3 class="text-sm font-semibold">Verify Bot Setup</h3>
184
-
185
- ${botInfo &&
186
- html`
187
- <div class="bg-black/20 border border-border rounded-lg p-3">
188
- <div class="flex items-center gap-2">
189
- <span class="text-sm text-gray-300 font-medium">@${botInfo.username}</span>
190
- <${Badge} tone="success">Connected</${Badge}>
191
- </div>
192
- <p class="text-xs text-gray-500 mt-1">${botInfo.first_name}</p>
193
- </div>
194
- `}
195
- ${error &&
196
- html`
197
- <div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
198
- <p class="text-sm text-red-400">${error}</p>
199
- </div>
200
- `}
201
- ${!botInfo &&
202
- !loading &&
203
- !error &&
204
- html` <p class="text-sm text-gray-400">Checking bot token...</p> `}
205
-
206
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
207
- <p class="text-xs font-medium text-gray-300">
208
- Before continuing, configure BotFather:
209
- </p>
210
- <ol class="text-xs text-gray-400 space-y-1.5 list-decimal list-inside">
211
- <li>
212
- Open <span class="text-gray-300">@BotFather</span> in Telegram
213
- </li>
214
- <li>
215
- Send <code class="bg-black/40 px-1 rounded">/mybots</code> and
216
- select your bot
217
- </li>
218
- <li>
219
- Go to <span class="text-gray-300">Bot Settings</span> >
220
- <span class="text-gray-300">Group Privacy</span>
221
- </li>
222
- <li>Turn it <span class="text-yellow-400 font-medium">OFF</span></li>
223
- </ol>
224
- </div>
225
-
226
- <div class="grid grid-cols-2 gap-2">
227
- <div />
228
- <button
229
- onclick=${onNext}
230
- disabled=${!botInfo}
231
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan ${!botInfo
232
- ? "opacity-50 cursor-not-allowed"
233
- : ""}"
234
- >
235
- Next
236
- </button>
237
- </div>
238
- </div>
239
- `;
240
- };
241
-
242
- // Step 2: Create Group
243
- const CreateGroupStep = ({ onNext, onBack }) => html`
244
- <div class="space-y-4">
245
- <h3 class="text-sm font-semibold">Create a Telegram Group</h3>
246
-
247
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
248
- <p class="text-xs font-medium text-gray-300">Create the group</p>
249
- <ol class="text-xs text-gray-400 space-y-2 list-decimal list-inside">
250
- <li>
251
- Open Telegram and create a
252
- <span class="text-gray-300">new group</span>
253
- </li>
254
- <li>
255
- Search for and add <span class="text-gray-300">your bot</span> as a
256
- member
257
- </li>
258
- <li>
259
- Hit <span class="text-gray-300">Next</span>, give the group a name
260
- (e.g. "My Workspace"), and create it
261
- </li>
262
- </ol>
263
- </div>
264
-
265
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
266
- <p class="text-xs font-medium text-gray-300">Enable topics</p>
267
- <ol class="text-xs text-gray-400 space-y-2 list-decimal list-inside">
268
- <li>Tap the group name at the top to open settings</li>
269
- <li>
270
- Tap <span class="text-gray-300">Edit</span> (pencil icon), scroll to
271
- <span class="text-gray-300"> Topics</span>, toggle it
272
- <span class="text-yellow-400 font-medium"> ON</span>
273
- </li>
274
- </ol>
275
- </div>
276
-
277
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
278
- <p class="text-xs font-medium text-gray-300">Make the bot an admin</p>
279
- <ol class="text-xs text-gray-400 space-y-2 list-decimal list-inside">
280
- <li>Go to <span class="text-gray-300">Members</span>, tap your bot</li>
281
- <li>
282
- Promote it to <span class="text-yellow-400 font-medium">Admin</span>
283
- </li>
284
- <li>
285
- Make sure
286
- <span class="text-yellow-400 font-medium"> Manage Topics </span>
287
- permission is enabled
288
- </li>
289
- </ol>
290
- </div>
291
-
292
- <p class="text-xs text-gray-500">
293
- Once all three steps are done, continue to verify the setup.
294
- </p>
295
-
296
- <div class="grid grid-cols-2 gap-2">
297
- <button
298
- onclick=${onBack}
299
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all border border-border text-gray-300 hover:border-gray-500"
300
- >
301
- Back
302
- </button>
303
- <button
304
- onclick=${onNext}
305
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"
306
- >
307
- Next
308
- </button>
309
- </div>
310
- </div>
311
- `;
312
-
313
- // Step 3: Add Bot to Group / Verify Group
314
- const AddBotStep = ({
315
- groupId,
316
- setGroupId,
317
- groupInfo,
318
- setGroupInfo,
319
- userId,
320
- setUserId,
321
- verifyGroupError,
322
- setVerifyGroupError,
323
- onNext,
324
- onBack,
325
- }) => {
326
- const [input, setInput] = useState(groupId || "");
327
- const [loading, setLoading] = useState(false);
328
- const [saving, setSaving] = useState(false);
329
- const verifyWarnings = groupInfo
330
- ? [
331
- ...(!groupInfo.chat?.isForum
332
- ? ["Topics are OFF. Enable Topics in Telegram group settings."]
333
- : []),
334
- ...(!groupInfo.bot?.isAdmin
335
- ? ["Bot is not an admin. Promote it to admin in group members."]
336
- : []),
337
- ...(!groupInfo.bot?.canManageTopics
338
- ? [
339
- "Bot is missing Manage Topics permission. Enable it in admin permissions.",
340
- ]
341
- : []),
342
- ]
343
- : [];
344
-
345
- const verify = async () => {
346
- const id = input.trim();
347
- if (!id) return;
348
- setLoading(true);
349
- setVerifyGroupError(null);
350
- try {
351
- const data = await api.verifyGroup(id);
352
- if (!data.ok) throw new Error(data.error);
353
- setGroupId(id);
354
- setGroupInfo(data);
355
- if (!String(userId || "").trim() && data.suggestedUserId) {
356
- setUserId(String(data.suggestedUserId));
357
- }
358
- } catch (e) {
359
- setVerifyGroupError(e.message);
360
- setGroupInfo(null);
361
- }
362
- setLoading(false);
363
- };
364
- const canContinue = !!(
365
- groupInfo &&
366
- groupInfo.chat?.isForum &&
367
- groupInfo.bot?.isAdmin &&
368
- groupInfo.bot?.canManageTopics
369
- );
370
- const continueWithConfig = async () => {
371
- if (!canContinue || saving) return;
372
- setVerifyGroupError(null);
373
- setSaving(true);
374
- try {
375
- const userIdValue = String(userId || "").trim();
376
- const data = await api.configureGroup(groupId, {
377
- ...(userIdValue ? { userId: userIdValue } : {}),
378
- groupName: groupInfo?.chat?.title || groupId,
379
- requireMention: false,
380
- });
381
- if (!data?.ok)
382
- throw new Error(data?.error || "Failed to configure Telegram group");
383
- if (data.userId) setUserId(String(data.userId));
384
- onNext();
385
- } catch (e) {
386
- setVerifyGroupError(e.message);
387
- }
388
- setSaving(false);
389
- };
390
-
391
- return html`
392
- <div class="space-y-4">
393
- <h3 class="text-sm font-semibold">Verify Group</h3>
394
-
395
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
396
- <p class="text-xs text-gray-400">To get your group chat ID:</p>
397
- <ol class="text-xs text-gray-400 space-y-1 list-decimal list-inside">
398
- <li>
399
- Invite <span class="text-gray-300">@myidbot</span> to your group
400
- </li>
401
- <li>
402
- Send <code class="bg-black/40 px-1 rounded">/getgroupid</code>
403
- </li>
404
- <li>
405
- Copy the ID (starts with
406
- <code class="bg-black/40 px-1 rounded">-100</code>)
407
- </li>
408
- </ol>
409
- </div>
410
-
411
- <div class="flex gap-2">
412
- <input
413
- type="text"
414
- value=${input}
415
- onInput=${(e) => setInput(e.target.value)}
416
- placeholder="-100XXXXXXXXXX"
417
- class="flex-1 bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500"
418
- />
419
- <${ActionButton}
420
- onClick=${verify}
421
- disabled=${!input.trim() || loading}
422
- loading=${loading}
423
- tone="secondary"
424
- size="md"
425
- idleLabel="Verify"
426
- loadingMode="inline"
427
- className="rounded-lg"
428
- />
429
- </div>
430
-
431
- ${verifyGroupError &&
432
- html`
433
- <div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
434
- <p class="text-sm text-red-400">${verifyGroupError}</p>
435
- </div>
436
- `}
437
- ${groupInfo &&
438
- html`
439
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
440
- <div class="flex items-center gap-2">
441
- <span class="text-sm text-gray-300 font-medium">${groupInfo.chat.title}</span>
442
- <${Badge} tone="success">Verified</${Badge}>
443
- </div>
444
- <div class="flex gap-3 text-xs text-gray-500">
445
- <span>Topics: ${groupInfo.chat.isForum ? "ON" : "OFF"}</span>
446
- <span>Bot: ${groupInfo.bot.status}</span>
447
- </div>
448
- </div>
449
- `}
450
- ${groupInfo &&
451
- verifyWarnings.length === 0 &&
452
- html`
453
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
454
- <p class="text-xs text-gray-500">Your Telegram User ID</p>
455
- <input
456
- type="text"
457
- value=${userId}
458
- onInput=${(e) => setUserId(e.target.value)}
459
- placeholder="e.g. 123456789"
460
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500"
461
- />
462
- <p class="text-xs text-gray-500">
463
- Auto-filled from Telegram admins. Edit if needed.
464
- </p>
465
- </div>
466
- `}
467
- ${verifyWarnings.length > 0 &&
468
- html`
469
- <div
470
- class="bg-red-500/10 border border-red-500/20 rounded-lg p-3 space-y-3"
471
- >
472
- <p class="text-xs font-medium text-red-300">
473
- Fix these before continuing:
474
- </p>
475
- <ul class="text-xs text-red-200 space-y-1 list-disc list-inside">
476
- ${verifyWarnings.map((message) => html`<li>${message}</li>`)}
477
- </ul>
478
- <p class="text-xs text-red-300 ">Once fixed, hit Verify again.</p>
479
- </div>
480
- `}
481
-
482
- <div class="grid grid-cols-2 gap-2">
483
- <button
484
- onclick=${onBack}
485
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all border border-border text-gray-300 hover:border-gray-500"
486
- >
487
- Back
488
- </button>
489
- <button
490
- onclick=${continueWithConfig}
491
- disabled=${!canContinue || saving}
492
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan ${!canContinue ||
493
- saving
494
- ? "opacity-50 cursor-not-allowed"
495
- : ""}"
496
- >
497
- ${saving ? "Saving..." : "Next"}
498
- </button>
499
- </div>
500
- </div>
501
- `;
502
- };
503
-
504
- // Step 4: Create Topics
505
- const TopicsStep = ({ groupId, topics, setTopics, onNext, onBack }) => {
506
- const [newTopicName, setNewTopicName] = useState("");
507
- const [newTopicInstructions, setNewTopicInstructions] = useState("");
508
- const [creating, setCreating] = useState(false);
509
- const [error, setError] = useState(null);
510
- const [deleting, setDeleting] = useState(null);
511
-
512
- const loadTopics = async () => {
513
- const data = await api.listTopics(groupId);
514
- if (data.ok) setTopics(data.topics);
515
- };
516
-
517
- useEffect(() => {
518
- loadTopics();
519
- }, [groupId]);
520
-
521
- const createSingle = async () => {
522
- const name = newTopicName.trim();
523
- const systemInstructions = newTopicInstructions.trim();
524
- if (!name) return;
525
- setCreating(true);
526
- setError(null);
527
- try {
528
- const data = await api.createTopicsBulk(groupId, [
529
- { name, ...(systemInstructions ? { systemInstructions } : {}) },
530
- ]);
531
- if (!data.ok)
532
- throw new Error(data.results?.[0]?.error || "Failed to create topic");
533
- const failed = data.results.filter((r) => !r.ok);
534
- if (failed.length > 0) throw new Error(failed[0].error);
535
- setNewTopicName("");
536
- setNewTopicInstructions("");
537
- await loadTopics();
538
- showToast(`Created topic: ${name}`, "success");
539
- } catch (e) {
540
- setError(e.message);
541
- }
542
- setCreating(false);
543
- };
544
-
545
- const handleDelete = async (topicId, topicName) => {
546
- setDeleting(topicId);
547
- try {
548
- const data = await api.deleteTopic(groupId, topicId);
549
- if (!data.ok) throw new Error(data.error);
550
- await loadTopics();
551
- if (data.removedFromRegistryOnly) {
552
- showToast(`Removed stale topic from registry: ${topicName}`, "success");
553
- } else {
554
- showToast(`Deleted topic: ${topicName}`, "success");
555
- }
556
- } catch (e) {
557
- showToast(`Failed to delete: ${e.message}`, "error");
558
- }
559
- setDeleting(null);
560
- };
561
-
562
- const topicEntries = Object.entries(topics || {});
563
-
564
- return html`
565
- <div class="space-y-4">
566
- <h3 class="text-sm font-semibold">Create Topics</h3>
567
-
568
- ${topicEntries.length > 0 &&
569
- html`
570
- <div
571
- class="bg-black/20 border border-border rounded-lg overflow-hidden"
572
- >
573
- <table class="w-full text-xs">
574
- <thead>
575
- <tr class="border-b border-border">
576
- <th class="text-left px-3 py-2 text-gray-500 font-medium">
577
- Topic
578
- </th>
579
- <th class="text-left px-3 py-2 text-gray-500 font-medium">
580
- Thread ID
581
- </th>
582
- <th class="px-3 py-2 w-8" />
583
- </tr>
584
- </thead>
585
- <tbody>
586
- ${topicEntries.map(
587
- ([id, t]) => html`
588
- <tr class="border-b border-border last:border-0">
589
- <td class="px-3 py-2 text-gray-300">${t.name}</td>
590
- <td class="px-3 py-2 text-gray-500 font-mono">${id}</td>
591
- <td class="px-3 py-2">
592
- <button
593
- onclick=${() => handleDelete(id, t.name)}
594
- disabled=${deleting === id}
595
- class="text-gray-600 hover:text-red-400 transition-colors ${deleting ===
596
- id
597
- ? "opacity-50"
598
- : ""}"
599
- title="Delete topic"
600
- >
601
- <svg
602
- width="14"
603
- height="14"
604
- viewBox="0 0 16 16"
605
- fill="currentColor"
606
- >
607
- <path
608
- d="M4.646 4.646a.5.5 0 01.708 0L8 7.293l2.646-2.647a.5.5 0 01.708.708L8.707 8l2.647 2.646a.5.5 0 01-.708.708L8 8.707l-2.646 2.647a.5.5 0 01-.708-.708L7.293 8 4.646 5.354a.5.5 0 010-.708z"
609
- />
610
- </svg>
611
- </button>
612
- </td>
613
- </tr>
614
- `,
615
- )}
616
- </tbody>
617
- </table>
618
- </div>
619
- `}
620
-
621
- <div class="space-y-2">
622
- <label class="text-xs text-gray-500">Add a topic</label>
623
- <div class="space-y-2">
624
- <div class="flex gap-2">
625
- <input
626
- type="text"
627
- value=${newTopicName}
628
- onInput=${(e) => setNewTopicName(e.target.value)}
629
- onKeyDown=${(e) => {
630
- if (e.key === "Enter") createSingle();
631
- }}
632
- placeholder="Topic name"
633
- class="flex-1 bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500"
634
- />
635
- </div>
636
- <textarea
637
- value=${newTopicInstructions}
638
- onInput=${(e) => setNewTopicInstructions(e.target.value)}
639
- placeholder="System instructions (optional)"
640
- rows="4"
641
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500 resize-y"
642
- />
643
- <div class="flex justify-end">
644
- <${ActionButton}
645
- onClick=${createSingle}
646
- disabled=${creating || !newTopicName.trim()}
647
- loading=${creating}
648
- tone="secondary"
649
- size="lg"
650
- idleLabel="Add"
651
- loadingMode="inline"
652
- className="min-w-[88px]"
653
- />
654
- </div>
655
- </div>
656
- </div>
657
- <div class="border-t border-white/10 pt-2" />
658
-
659
- ${error &&
660
- html`
661
- <div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
662
- <p class="text-sm text-red-400">${error}</p>
663
- </div>
664
- `}
665
-
666
- <div class="grid grid-cols-2 gap-2">
667
- <button
668
- onclick=${onBack}
669
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all border border-border text-gray-300 hover:border-gray-500"
670
- >
671
- Back
672
- </button>
673
- <button
674
- onclick=${onNext}
675
- disabled=${topicEntries.length === 0}
676
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"
677
- >
678
- Next
679
- </button>
680
- </div>
681
- </div>
682
- `;
683
- };
684
-
685
- // Step 5: Summary
686
- const SummaryStep = ({ groupId, groupInfo, topics, onBack, onDone }) => {
687
- return html`
688
- <div class="space-y-4">
689
- <div class="max-w-xl mx-auto text-center space-y-10 mt-10">
690
- <p class="text-sm font-medium text-green-300">🎉 Setup complete</p>
691
- <p class="text-xs text-gray-400">
692
- The topic registry has been injected into
693
- <code class="bg-black/40 px-1 rounded">TOOLS.md</code> so your agent
694
- knows which thread ID maps to which topic name.
695
- </p>
696
-
697
- <div class="bg-black/20 border border-border rounded-lg p-3">
698
- <p class="text-xs text-gray-500">
699
- If you used <span class="text-gray-300">@myidbot</span> to find IDs,
700
- you can remove it from the group now.
701
- </p>
702
- </div>
703
- </div>
704
-
705
- <div class="grid grid-cols-2 gap-2">
706
- <button
707
- onclick=${onBack}
708
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all border border-border text-gray-300 hover:border-gray-500"
709
- >
710
- Back
711
- </button>
712
- <button
713
- onclick=${onDone}
714
- class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"
715
- >
716
- Done
717
- </button>
718
- </div>
719
- </div>
720
- `;
721
- };
722
-
723
- const ManageTelegramWorkspace = ({
724
- groupId,
725
- groupName,
726
- initialTopics,
727
- configAgentMaxConcurrent,
728
- configSubagentMaxConcurrent,
729
- debugEnabled,
730
- onResetOnboarding,
731
- }) => {
732
- const [topics, setTopics] = useState(initialTopics || {});
733
- const [newTopicName, setNewTopicName] = useState("");
734
- const [newTopicInstructions, setNewTopicInstructions] = useState("");
735
- const [showCreateTopic, setShowCreateTopic] = useState(false);
736
- const [creating, setCreating] = useState(false);
737
- const [deleting, setDeleting] = useState(null);
738
- const [editingTopicId, setEditingTopicId] = useState("");
739
- const [editingTopicName, setEditingTopicName] = useState("");
740
- const [editingTopicInstructions, setEditingTopicInstructions] = useState("");
741
- const [renamingTopicId, setRenamingTopicId] = useState("");
742
- const [error, setError] = useState(null);
743
-
744
- const loadTopics = async () => {
745
- const data = await api.listTopics(groupId);
746
- if (data.ok) setTopics(data.topics || {});
747
- };
748
-
749
- useEffect(() => {
750
- loadTopics();
751
- }, [groupId]);
752
- useEffect(() => {
753
- if (initialTopics && Object.keys(initialTopics).length > 0) {
754
- setTopics(initialTopics);
755
- }
756
- }, [initialTopics]);
757
-
758
- const createSingle = async () => {
759
- const name = newTopicName.trim();
760
- const systemInstructions = newTopicInstructions.trim();
761
- if (!name) return;
762
- setCreating(true);
763
- setError(null);
764
- try {
765
- const data = await api.createTopicsBulk(groupId, [
766
- { name, ...(systemInstructions ? { systemInstructions } : {}) },
767
- ]);
768
- if (!data.ok)
769
- throw new Error(data.results?.[0]?.error || "Failed to create topic");
770
- const failed = data.results.filter((r) => !r.ok);
771
- if (failed.length > 0) throw new Error(failed[0].error);
772
- setNewTopicName("");
773
- setNewTopicInstructions("");
774
- setShowCreateTopic(false);
775
- await loadTopics();
776
- showToast(`Created topic: ${name}`, "success");
777
- } catch (e) {
778
- setError(e.message);
779
- }
780
- setCreating(false);
781
- };
782
-
783
- const handleDelete = async (topicId, topicName) => {
784
- setDeleting(topicId);
785
- try {
786
- const data = await api.deleteTopic(groupId, topicId);
787
- if (!data.ok) throw new Error(data.error);
788
- await loadTopics();
789
- if (data.removedFromRegistryOnly) {
790
- showToast(`Removed stale topic from registry: ${topicName}`, "success");
791
- } else {
792
- showToast(`Deleted topic: ${topicName}`, "success");
793
- }
794
- } catch (e) {
795
- showToast(`Failed to delete: ${e.message}`, "error");
796
- }
797
- setDeleting(null);
798
- };
799
-
800
- const startRename = (topicId, topicName, topicInstructions = "") => {
801
- setEditingTopicId(String(topicId));
802
- setEditingTopicName(String(topicName || ""));
803
- setEditingTopicInstructions(String(topicInstructions || ""));
804
- };
805
-
806
- const cancelRename = () => {
807
- setEditingTopicId("");
808
- setEditingTopicName("");
809
- setEditingTopicInstructions("");
810
- };
811
-
812
- const saveRename = async (topicId) => {
813
- const nextName = editingTopicName.trim();
814
- const nextSystemInstructions = editingTopicInstructions.trim();
815
- if (!nextName) {
816
- setError("Topic name is required");
817
- return;
818
- }
819
- setRenamingTopicId(String(topicId));
820
- setError(null);
821
- try {
822
- const data = await api.updateTopic(groupId, topicId, {
823
- name: nextName,
824
- systemInstructions: nextSystemInstructions,
825
- });
826
- if (!data.ok) throw new Error(data.error || "Failed to rename topic");
827
- await loadTopics();
828
- showToast(`Renamed topic: ${nextName}`, "success");
829
- cancelRename();
830
- } catch (e) {
831
- setError(e.message);
832
- }
833
- setRenamingTopicId("");
834
- };
835
-
836
- const topicEntries = Object.entries(topics || {});
837
- const topicCount = topicEntries.length;
838
- const computedMaxConcurrent = Math.max(topicCount * 3, 8);
839
- const computedSubagentMaxConcurrent = Math.max(computedMaxConcurrent - 2, 4);
840
- const maxConcurrent = Number.isFinite(configAgentMaxConcurrent)
841
- ? configAgentMaxConcurrent
842
- : computedMaxConcurrent;
843
- const subagentMaxConcurrent = Number.isFinite(configSubagentMaxConcurrent)
844
- ? configSubagentMaxConcurrent
845
- : computedSubagentMaxConcurrent;
846
-
847
- return html`
848
- <div class="space-y-4">
849
- ${debugEnabled &&
850
- html`
851
- <div class="flex justify-end">
852
- <button
853
- onclick=${onResetOnboarding}
854
- class="text-xs px-3 py-1.5 rounded-lg border border-border text-gray-400 hover:text-gray-200 hover:border-gray-500 transition-colors"
855
- >
856
- Reset onboarding
857
- </button>
858
- </div>
859
- `}
860
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-1">
861
- <p class="text-sm text-gray-300 font-medium">${groupName || groupId}</p>
862
- <p class="text-xs text-gray-500 font-mono">${groupId}</p>
863
- </div>
864
-
865
- <div class="space-y-2">
866
- <h2 class="card-label mb-3">Existing Topics</h2>
867
- ${topicEntries.length > 0
868
- ? html`
869
- <div
870
- class="bg-black/20 border border-border rounded-lg overflow-hidden"
871
- >
872
- <table class="w-full text-xs table-fixed">
873
- <thead>
874
- <tr class="border-b border-border">
875
- <th class="text-left px-3 py-2 text-gray-500 font-medium">
876
- Topic
877
- </th>
878
- <th
879
- class="text-left px-3 py-2 text-gray-500 font-medium w-36"
880
- >
881
- Thread ID
882
- </th>
883
- <th class="px-3 py-2 w-28" />
884
- </tr>
885
- </thead>
886
- <tbody>
887
- ${topicEntries.map(
888
- ([id, topic]) => html`
889
- ${editingTopicId === String(id)
890
- ? html`
891
- <tr
892
- class="border-b border-border last:border-0 align-top"
893
- >
894
- <td class="px-3 py-2" colspan="3">
895
- <div class="space-y-2">
896
- <input
897
- type="text"
898
- value=${editingTopicName}
899
- onInput=${(e) =>
900
- setEditingTopicName(e.target.value)}
901
- onKeyDown=${(e) => {
902
- if (e.key === "Enter") saveRename(id);
903
- if (e.key === "Escape") cancelRename();
904
- }}
905
- class="w-full bg-black/30 border border-border rounded-lg px-2 py-1.5 text-xs text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500"
906
- />
907
- <textarea
908
- value=${editingTopicInstructions}
909
- onInput=${(e) =>
910
- setEditingTopicInstructions(
911
- e.target.value,
912
- )}
913
- placeholder="System instructions (optional)"
914
- rows="6"
915
- class="w-full bg-black/30 border border-border rounded-lg px-2 py-1.5 text-xs text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500 resize-y"
916
- />
917
- <div class="flex items-center gap-2">
918
- <button
919
- onclick=${() => saveRename(id)}
920
- disabled=${renamingTopicId ===
921
- String(id)}
922
- class="text-xs px-2 py-1 rounded transition-all ac-btn-cyan ${renamingTopicId ===
923
- String(id)
924
- ? "opacity-50 cursor-not-allowed"
925
- : ""}"
926
- >
927
- Save
928
- </button>
929
- <button
930
- onclick=${cancelRename}
931
- class="text-xs px-2 py-1 rounded border border-border text-gray-400 hover:text-gray-200 hover:border-gray-500"
932
- >
933
- Cancel
934
- </button>
935
- </div>
936
- </div>
937
- </td>
938
- </tr>
939
- `
940
- : html`
941
- <tr
942
- class="border-b border-border last:border-0 align-middle"
943
- >
944
- <td class="px-3 py-2 text-gray-300">
945
- <div class="flex items-center gap-2">
946
- <span>${topic.name}</span>
947
- <button
948
- onclick=${() =>
949
- startRename(
950
- id,
951
- topic.name,
952
- topic.systemInstructions,
953
- )}
954
- class="inline-flex items-center justify-center text-white/80 hover:text-white transition-colors"
955
- title="Edit topic"
956
- aria-label="Rename topic"
957
- >
958
- <svg
959
- width="14"
960
- height="14"
961
- viewBox="0 0 16 16"
962
- fill="currentColor"
963
- aria-hidden="true"
964
- >
965
- <path
966
- d="M11.854 1.146a.5.5 0 00-.708 0L3 9.293V13h3.707l8.146-8.146a.5.5 0 000-.708l-3-3zM3.5 12.5v-2.793l7-7L13.793 6l-7 7H3.5z"
967
- />
968
- </svg>
969
- </button>
970
- </div>
971
- ${topic.systemInstructions &&
972
- html`
973
- <p
974
- class="text-[11px] text-gray-500 mt-1 line-clamp-1"
975
- >
976
- ${topic.systemInstructions}
977
- </p>
978
- `}
979
- </td>
980
- <td
981
- class="px-3 py-2 text-gray-500 font-mono w-36"
982
- >
983
- ${id}
984
- </td>
985
- <td class="px-3 py-2">
986
- <div
987
- class="flex items-center gap-2 justify-end"
988
- >
989
- <button
990
- onclick=${() =>
991
- handleDelete(id, topic.name)}
992
- disabled=${deleting === id}
993
- class="text-xs px-2 py-1 rounded border border-border text-gray-500 hover:text-red-300 hover:border-red-500 ${deleting ===
994
- id
995
- ? "opacity-50 cursor-not-allowed"
996
- : ""}"
997
- title="Delete topic"
998
- >
999
- Delete
1000
- </button>
1001
- </div>
1002
- </td>
1003
- </tr>
1004
- `}
1005
- `,
1006
- )}
1007
- </tbody>
1008
- </table>
1009
- </div>
1010
- `
1011
- : html`<p class="text-xs text-gray-500">No topics yet.</p>`}
1012
- </div>
1013
-
1014
- ${showCreateTopic &&
1015
- html`
1016
- <div class="space-y-2 bg-black/20 border border-border rounded-lg p-3">
1017
- <label class="text-xs text-gray-500">Create new topic</label>
1018
- <div class="space-y-2">
1019
- <input
1020
- type="text"
1021
- value=${newTopicName}
1022
- onInput=${(e) => setNewTopicName(e.target.value)}
1023
- onKeyDown=${(e) => {
1024
- if (e.key === "Enter") createSingle();
1025
- }}
1026
- placeholder="Topic name"
1027
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500"
1028
- />
1029
- <textarea
1030
- value=${newTopicInstructions}
1031
- onInput=${(e) => setNewTopicInstructions(e.target.value)}
1032
- placeholder="System instructions (optional)"
1033
- rows="5"
1034
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500 resize-y"
1035
- />
1036
- <div class="flex justify-end">
1037
- <${ActionButton}
1038
- onClick=${createSingle}
1039
- disabled=${creating || !newTopicName.trim()}
1040
- loading=${creating}
1041
- tone="secondary"
1042
- size="lg"
1043
- idleLabel="Add topic"
1044
- loadingLabel="Creating..."
1045
- />
1046
- </div>
1047
- </div>
1048
- </div>
1049
- `}
1050
- ${error &&
1051
- html`
1052
- <div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
1053
- <p class="text-sm text-red-400">${error}</p>
1054
- </div>
1055
- `}
1056
-
1057
- <div class="flex items-center justify-start">
1058
- <button
1059
- onclick=${() => setShowCreateTopic((v) => !v)}
1060
- class="${showCreateTopic
1061
- ? "w-auto text-sm font-medium px-4 py-2 rounded-xl transition-all border border-border text-gray-300 hover:border-gray-500"
1062
- : "w-auto text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"}"
1063
- >
1064
- ${showCreateTopic ? "Close create topic" : "Create topic"}
1065
- </button>
1066
- </div>
1067
-
1068
- <div class="border-t border-white/10" />
1069
-
1070
- <p class="text-xs text-gray-500">
1071
- Concurrency is auto-scaled to support your group:
1072
- <span class="text-gray-300"> agent ${maxConcurrent}</span>,
1073
- <span class="text-gray-300"> subagent ${subagentMaxConcurrent}</span>
1074
- <span class="text-gray-600"> (${topicCount} topics)</span>.
1075
- </p>
1076
- <p class="text-[11px] text-gray-500">
1077
- This registry can drift if topics are created, renamed, or removed
1078
- outside this page. Your agent will update the registry if it notices a
1079
- discrepancy.
1080
- </p>
1081
- </div>
1082
- `;
1083
- };
1084
-
1085
- export const TelegramWorkspace = ({ onBack }) => {
1086
- const initialState = loadTelegramWorkspaceState();
1087
- const cachedWorkspace = loadTelegramWorkspaceCache();
1088
- const [step, setStep] = useState(() => {
1089
- const value = Number.parseInt(String(initialState.step ?? 0), 10);
1090
- if (!Number.isFinite(value)) return 0;
1091
- return Math.min(Math.max(value, 0), kSteps.length - 1);
1092
- });
1093
- const [botInfo, setBotInfo] = useState(initialState.botInfo || null);
1094
- const [groupId, setGroupId] = useState(initialState.groupId || "");
1095
- const [groupInfo, setGroupInfo] = useState(initialState.groupInfo || null);
1096
- const [verifyGroupError, setVerifyGroupError] = useState(
1097
- initialState.verifyGroupError || null,
1098
- );
1099
- const [allowUserId, setAllowUserId] = useState(
1100
- initialState.allowUserId || "",
1101
- );
1102
- const [topics, setTopics] = useState(initialState.topics || {});
1103
- const [workspaceConfig, setWorkspaceConfig] = useState(() => ({
1104
- ready: !!cachedWorkspace,
1105
- configured: !!cachedWorkspace?.configured,
1106
- groupId: cachedWorkspace?.groupId || "",
1107
- groupName: cachedWorkspace?.groupName || "",
1108
- topics: cachedWorkspace?.topics || {},
1109
- debugEnabled: !!cachedWorkspace?.debugEnabled,
1110
- concurrency: cachedWorkspace?.concurrency || {
1111
- agentMaxConcurrent: null,
1112
- subagentMaxConcurrent: null,
1113
- },
1114
- }));
1115
-
1116
- const goNext = () => setStep((s) => Math.min(kSteps.length - 1, s + 1));
1117
- const goBack = () => setStep((s) => Math.max(0, s - 1));
1118
- const resetOnboarding = async () => {
1119
- try {
1120
- const data = await api.resetWorkspace();
1121
- if (!data.ok) throw new Error(data.error || "Failed to reset onboarding");
1122
- try {
1123
- window.localStorage.removeItem(kTelegramWorkspaceStorageKey);
1124
- window.localStorage.removeItem(kTelegramWorkspaceCacheKey);
1125
- } catch {}
1126
- setStep(0);
1127
- setBotInfo(null);
1128
- setGroupId("");
1129
- setGroupInfo(null);
1130
- setVerifyGroupError(null);
1131
- setAllowUserId("");
1132
- setTopics({});
1133
- setWorkspaceConfig({
1134
- ready: true,
1135
- configured: false,
1136
- groupId: "",
1137
- groupName: "",
1138
- topics: {},
1139
- debugEnabled: !!workspaceConfig?.debugEnabled,
1140
- concurrency: { agentMaxConcurrent: null, subagentMaxConcurrent: null },
1141
- });
1142
- showToast("Telegram onboarding reset", "success");
1143
- } catch (e) {
1144
- showToast(e.message || "Failed to reset onboarding", "error");
1145
- }
1146
- };
1147
- const handleDone = () => {
1148
- try {
1149
- window.localStorage.removeItem(kTelegramWorkspaceStorageKey);
1150
- window.localStorage.setItem(
1151
- kTelegramWorkspaceCacheKey,
1152
- JSON.stringify({
1153
- cachedAt: Date.now(),
1154
- data: {
1155
- ready: true,
1156
- configured: true,
1157
- groupId,
1158
- groupName: groupInfo?.chat?.title || groupId,
1159
- topics: topics || {},
1160
- debugEnabled: !!workspaceConfig?.debugEnabled,
1161
- concurrency: workspaceConfig?.concurrency || {
1162
- agentMaxConcurrent: null,
1163
- subagentMaxConcurrent: null,
1164
- },
1165
- },
1166
- }),
1167
- );
1168
- } catch {}
1169
- window.location.reload();
1170
- };
1171
-
1172
- useEffect(() => {
1173
- try {
1174
- window.localStorage.setItem(
1175
- kTelegramWorkspaceStorageKey,
1176
- JSON.stringify({
1177
- step,
1178
- botInfo,
1179
- groupId,
1180
- groupInfo,
1181
- verifyGroupError,
1182
- allowUserId,
1183
- topics,
1184
- }),
1185
- );
1186
- } catch {}
1187
- }, [
1188
- step,
1189
- botInfo,
1190
- groupId,
1191
- groupInfo,
1192
- verifyGroupError,
1193
- allowUserId,
1194
- topics,
1195
- ]);
1196
-
1197
- useEffect(() => {
1198
- let active = true;
1199
- const bootstrapWorkspace = async () => {
1200
- try {
1201
- const data = await api.workspace();
1202
- if (!active || !data?.ok) return;
1203
- if (!data.configured || !data.groupId) {
1204
- const nextConfig = {
1205
- ready: true,
1206
- configured: false,
1207
- groupId: "",
1208
- groupName: "",
1209
- topics: {},
1210
- debugEnabled: !!data?.debugEnabled,
1211
- concurrency: {
1212
- agentMaxConcurrent: null,
1213
- subagentMaxConcurrent: null,
1214
- },
1215
- };
1216
- setWorkspaceConfig(nextConfig);
1217
- saveTelegramWorkspaceCache(nextConfig);
1218
- return;
1219
- }
1220
- const nextConfig = {
1221
- ready: true,
1222
- configured: true,
1223
- groupId: data.groupId,
1224
- groupName: data.groupName || data.groupId,
1225
- topics: data.topics || {},
1226
- debugEnabled: !!data.debugEnabled,
1227
- concurrency: data.concurrency || {
1228
- agentMaxConcurrent: null,
1229
- subagentMaxConcurrent: null,
1230
- },
1231
- };
1232
- setWorkspaceConfig(nextConfig);
1233
- saveTelegramWorkspaceCache(nextConfig);
1234
- setGroupId(data.groupId);
1235
- setTopics(data.topics || {});
1236
- setGroupInfo({
1237
- chat: {
1238
- id: data.groupId,
1239
- title: data.groupName || data.groupId,
1240
- isForum: true,
1241
- },
1242
- bot: {
1243
- status: "administrator",
1244
- isAdmin: true,
1245
- canManageTopics: true,
1246
- },
1247
- });
1248
- setVerifyGroupError(null);
1249
- setAllowUserId("");
1250
- setStep((currentStep) => (currentStep < 3 ? 3 : currentStep));
1251
- } catch {}
1252
- };
1253
- bootstrapWorkspace();
1254
- return () => {
1255
- active = false;
1256
- };
1257
- }, []);
1258
-
1259
- return html`
1260
- <div class="space-y-4">
1261
- <${BackButton} onBack=${onBack} />
1262
- <div class="bg-surface border border-border rounded-xl p-4">
1263
- ${!workspaceConfig.ready
1264
- ? html`
1265
- <div class="min-h-[220px] flex items-center justify-center">
1266
- <p class="text-sm text-gray-500">Loading workspace...</p>
1267
- </div>
1268
- `
1269
- : workspaceConfig.configured
1270
- ? html`
1271
- <div class="flex items-center justify-between mb-4">
1272
- <div class="flex items-center gap-2">
1273
- <img
1274
- src="/assets/icons/telegram.svg"
1275
- alt=""
1276
- class="w-5 h-5"
1277
- />
1278
- <h2 class="font-semibold text-sm">
1279
- Manage Telegram Workspace
1280
- </h2>
1281
- </div>
1282
- </div>
1283
- <${ManageTelegramWorkspace}
1284
- groupId=${workspaceConfig.groupId}
1285
- groupName=${workspaceConfig.groupName}
1286
- initialTopics=${workspaceConfig.topics}
1287
- configAgentMaxConcurrent=${workspaceConfig.concurrency
1288
- ?.agentMaxConcurrent}
1289
- configSubagentMaxConcurrent=${workspaceConfig.concurrency
1290
- ?.subagentMaxConcurrent}
1291
- debugEnabled=${workspaceConfig.debugEnabled}
1292
- onResetOnboarding=${resetOnboarding}
1293
- />
1294
- `
1295
- : html`
1296
- <div class="flex items-center justify-between mb-4">
1297
- <div class="flex items-center gap-2">
1298
- <img
1299
- src="/assets/icons/telegram.svg"
1300
- alt=""
1301
- class="w-5 h-5"
1302
- />
1303
- <h2 class="font-semibold text-sm">
1304
- Set Up Telegram Workspace
1305
- </h2>
1306
- </div>
1307
- <span class="text-xs text-gray-500"
1308
- >Step ${step + 1} of ${kSteps.length}</span
1309
- >
1310
- </div>
1311
-
1312
- <${StepIndicator} currentStep=${step} />
1313
-
1314
- ${step === 0 &&
1315
- html`
1316
- <${VerifyBotStep}
1317
- botInfo=${botInfo}
1318
- setBotInfo=${setBotInfo}
1319
- onNext=${goNext}
1320
- />
1321
- `}
1322
- ${step === 1 &&
1323
- html`
1324
- <${CreateGroupStep} onNext=${goNext} onBack=${goBack} />
1325
- `}
1326
- ${step === 2 &&
1327
- html`
1328
- <${AddBotStep}
1329
- groupId=${groupId}
1330
- setGroupId=${setGroupId}
1331
- groupInfo=${groupInfo}
1332
- setGroupInfo=${setGroupInfo}
1333
- userId=${allowUserId}
1334
- setUserId=${setAllowUserId}
1335
- verifyGroupError=${verifyGroupError}
1336
- setVerifyGroupError=${setVerifyGroupError}
1337
- onNext=${goNext}
1338
- onBack=${goBack}
1339
- />
1340
- `}
1341
- ${step === 3 &&
1342
- html`
1343
- <${TopicsStep}
1344
- groupId=${groupId}
1345
- topics=${topics}
1346
- setTopics=${setTopics}
1347
- onNext=${goNext}
1348
- onBack=${goBack}
1349
- />
1350
- `}
1351
- ${step === 4 &&
1352
- html`
1353
- <${SummaryStep}
1354
- groupId=${groupId}
1355
- groupInfo=${groupInfo}
1356
- topics=${topics}
1357
- onBack=${goBack}
1358
- onDone=${handleDone}
1359
- />
1360
- `}
1361
- `}
1362
- </div>
1363
- </div>
1364
- `;
1365
- };