@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.
- package/bin/alphaclaw.js +18 -0
- package/lib/plugin/usage-tracker/index.js +308 -0
- package/lib/plugin/usage-tracker/openclaw.plugin.json +8 -0
- package/lib/public/css/explorer.css +51 -1
- package/lib/public/css/shell.css +3 -1
- package/lib/public/css/theme.css +35 -0
- package/lib/public/js/app.js +73 -24
- package/lib/public/js/components/file-tree.js +231 -28
- package/lib/public/js/components/file-viewer.js +193 -20
- package/lib/public/js/components/segmented-control.js +33 -0
- package/lib/public/js/components/sidebar.js +14 -32
- package/lib/public/js/components/telegram-workspace/index.js +353 -0
- package/lib/public/js/components/telegram-workspace/manage.js +397 -0
- package/lib/public/js/components/telegram-workspace/onboarding.js +616 -0
- package/lib/public/js/components/usage-tab.js +528 -0
- package/lib/public/js/components/watchdog-tab.js +1 -1
- package/lib/public/js/lib/api.js +25 -1
- package/lib/public/js/lib/telegram-api.js +78 -0
- package/lib/public/js/lib/ui-settings.js +38 -0
- package/lib/public/setup.html +34 -30
- package/lib/server/alphaclaw-version.js +3 -3
- package/lib/server/constants.js +1 -0
- package/lib/server/onboarding/openclaw.js +15 -0
- package/lib/server/routes/auth.js +5 -1
- package/lib/server/routes/telegram.js +185 -60
- package/lib/server/routes/usage.js +133 -0
- package/lib/server/usage-db.js +570 -0
- package/lib/server.js +21 -1
- package/lib/setup/core-prompts/AGENTS.md +0 -101
- package/package.json +1 -1
- package/lib/public/js/components/telegram-workspace.js +0 -1365
|
@@ -0,0 +1,616 @@
|
|
|
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
|
+
import { ConfirmDialog } from "../confirm-dialog.js";
|
|
8
|
+
import * as api from "../../lib/telegram-api.js";
|
|
9
|
+
|
|
10
|
+
const html = htm.bind(h);
|
|
11
|
+
|
|
12
|
+
export const StepIndicator = ({ currentStep, steps }) => html`
|
|
13
|
+
<div class="flex items-center gap-1 mb-6">
|
|
14
|
+
${steps.map(
|
|
15
|
+
(s, i) => html`
|
|
16
|
+
<div
|
|
17
|
+
class="h-1 flex-1 rounded-full transition-colors ${i <= currentStep
|
|
18
|
+
? "bg-accent"
|
|
19
|
+
: "bg-border"}"
|
|
20
|
+
style=${i <= currentStep ? "background: var(--accent)" : ""}
|
|
21
|
+
/>
|
|
22
|
+
`,
|
|
23
|
+
)}
|
|
24
|
+
</div>
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
// Step 1: Verify Bot
|
|
28
|
+
export const VerifyBotStep = ({ botInfo, setBotInfo, onNext }) => {
|
|
29
|
+
const [loading, setLoading] = useState(false);
|
|
30
|
+
const [error, setError] = useState(null);
|
|
31
|
+
|
|
32
|
+
const verify = async () => {
|
|
33
|
+
setLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const data = await api.verifyBot();
|
|
37
|
+
if (!data.ok) throw new Error(data.error);
|
|
38
|
+
setBotInfo(data.bot);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
setError(e.message);
|
|
41
|
+
}
|
|
42
|
+
setLoading(false);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!botInfo) verify();
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
return html`
|
|
50
|
+
<div class="space-y-4">
|
|
51
|
+
<h3 class="text-sm font-semibold">Verify Bot Setup</h3>
|
|
52
|
+
|
|
53
|
+
${botInfo &&
|
|
54
|
+
html`
|
|
55
|
+
<div class="bg-black/20 border border-border rounded-lg p-3">
|
|
56
|
+
<div class="flex items-center gap-2">
|
|
57
|
+
<span class="text-sm text-gray-300 font-medium">@${botInfo.username}</span>
|
|
58
|
+
<${Badge} tone="success">Connected</${Badge}>
|
|
59
|
+
</div>
|
|
60
|
+
<p class="text-xs text-gray-500 mt-1">${botInfo.first_name}</p>
|
|
61
|
+
</div>
|
|
62
|
+
`}
|
|
63
|
+
${error &&
|
|
64
|
+
html`
|
|
65
|
+
<div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
|
|
66
|
+
<p class="text-sm text-red-400">${error}</p>
|
|
67
|
+
</div>
|
|
68
|
+
`}
|
|
69
|
+
${!botInfo &&
|
|
70
|
+
!loading &&
|
|
71
|
+
!error &&
|
|
72
|
+
html` <p class="text-sm text-gray-400">Checking bot token...</p> `}
|
|
73
|
+
|
|
74
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
|
|
75
|
+
<p class="text-xs font-medium text-gray-300">
|
|
76
|
+
Before continuing, configure BotFather:
|
|
77
|
+
</p>
|
|
78
|
+
<ol class="text-xs text-gray-400 space-y-1.5 list-decimal list-inside">
|
|
79
|
+
<li>
|
|
80
|
+
Open <span class="text-gray-300">@BotFather</span> in Telegram
|
|
81
|
+
</li>
|
|
82
|
+
<li>
|
|
83
|
+
Send <code class="bg-black/40 px-1 rounded">/mybots</code> and
|
|
84
|
+
select your bot
|
|
85
|
+
</li>
|
|
86
|
+
<li>
|
|
87
|
+
Go to <span class="text-gray-300">Bot Settings</span> >
|
|
88
|
+
<span class="text-gray-300">Group Privacy</span>
|
|
89
|
+
</li>
|
|
90
|
+
<li>Turn it <span class="text-yellow-400 font-medium">OFF</span></li>
|
|
91
|
+
</ol>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="grid grid-cols-2 gap-2">
|
|
95
|
+
<div />
|
|
96
|
+
<button
|
|
97
|
+
onclick=${onNext}
|
|
98
|
+
disabled=${!botInfo}
|
|
99
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan ${!botInfo
|
|
100
|
+
? "opacity-50 cursor-not-allowed"
|
|
101
|
+
: ""}"
|
|
102
|
+
>
|
|
103
|
+
Next
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
`;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Step 2: Create Group
|
|
111
|
+
export const CreateGroupStep = ({ onNext, onBack }) => html`
|
|
112
|
+
<div class="space-y-4">
|
|
113
|
+
<h3 class="text-sm font-semibold">Create a Telegram Group</h3>
|
|
114
|
+
|
|
115
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
|
|
116
|
+
<p class="text-xs font-medium text-gray-300">Create the group</p>
|
|
117
|
+
<ol class="text-xs text-gray-400 space-y-2 list-decimal list-inside">
|
|
118
|
+
<li>
|
|
119
|
+
Open Telegram and create a
|
|
120
|
+
<span class="text-gray-300">new group</span>
|
|
121
|
+
</li>
|
|
122
|
+
<li>
|
|
123
|
+
Search for and add <span class="text-gray-300">your bot</span> as a
|
|
124
|
+
member
|
|
125
|
+
</li>
|
|
126
|
+
<li>
|
|
127
|
+
Hit <span class="text-gray-300">Next</span>, give the group a name
|
|
128
|
+
(e.g. "My Workspace"), and create it
|
|
129
|
+
</li>
|
|
130
|
+
</ol>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
|
|
134
|
+
<p class="text-xs font-medium text-gray-300">Enable topics</p>
|
|
135
|
+
<ol class="text-xs text-gray-400 space-y-2 list-decimal list-inside">
|
|
136
|
+
<li>Tap the group name at the top to open settings</li>
|
|
137
|
+
<li>
|
|
138
|
+
Tap <span class="text-gray-300">Edit</span> (pencil icon), scroll to
|
|
139
|
+
<span class="text-gray-300"> Topics</span>, toggle it
|
|
140
|
+
<span class="text-yellow-400 font-medium"> ON</span>
|
|
141
|
+
</li>
|
|
142
|
+
</ol>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
|
|
146
|
+
<p class="text-xs font-medium text-gray-300">Make the bot an admin</p>
|
|
147
|
+
<ol class="text-xs text-gray-400 space-y-2 list-decimal list-inside">
|
|
148
|
+
<li>Go to <span class="text-gray-300">Members</span>, tap your bot</li>
|
|
149
|
+
<li>
|
|
150
|
+
Promote it to <span class="text-yellow-400 font-medium">Admin</span>
|
|
151
|
+
</li>
|
|
152
|
+
<li>
|
|
153
|
+
Make sure
|
|
154
|
+
<span class="text-yellow-400 font-medium"> Manage Topics </span>
|
|
155
|
+
permission is enabled
|
|
156
|
+
</li>
|
|
157
|
+
</ol>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<p class="text-xs text-gray-500">
|
|
161
|
+
Once all three steps are done, continue to verify the setup.
|
|
162
|
+
</p>
|
|
163
|
+
|
|
164
|
+
<div class="grid grid-cols-2 gap-2">
|
|
165
|
+
<button
|
|
166
|
+
onclick=${onBack}
|
|
167
|
+
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"
|
|
168
|
+
>
|
|
169
|
+
Back
|
|
170
|
+
</button>
|
|
171
|
+
<button
|
|
172
|
+
onclick=${onNext}
|
|
173
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"
|
|
174
|
+
>
|
|
175
|
+
Next
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
`;
|
|
180
|
+
|
|
181
|
+
// Step 3: Add Bot to Group / Verify Group
|
|
182
|
+
export const AddBotStep = ({
|
|
183
|
+
groupId,
|
|
184
|
+
setGroupId,
|
|
185
|
+
groupInfo,
|
|
186
|
+
setGroupInfo,
|
|
187
|
+
userId,
|
|
188
|
+
setUserId,
|
|
189
|
+
verifyGroupError,
|
|
190
|
+
setVerifyGroupError,
|
|
191
|
+
onNext,
|
|
192
|
+
onBack,
|
|
193
|
+
}) => {
|
|
194
|
+
const [input, setInput] = useState(groupId || "");
|
|
195
|
+
const [loading, setLoading] = useState(false);
|
|
196
|
+
const [saving, setSaving] = useState(false);
|
|
197
|
+
const verifyWarnings = groupInfo
|
|
198
|
+
? [
|
|
199
|
+
...(!groupInfo.chat?.isForum
|
|
200
|
+
? ["Topics are OFF. Enable Topics in Telegram group settings."]
|
|
201
|
+
: []),
|
|
202
|
+
...(!groupInfo.bot?.isAdmin
|
|
203
|
+
? ["Bot is not an admin. Promote it to admin in group members."]
|
|
204
|
+
: []),
|
|
205
|
+
...(!groupInfo.bot?.canManageTopics
|
|
206
|
+
? [
|
|
207
|
+
"Bot is missing Manage Topics permission. Enable it in admin permissions.",
|
|
208
|
+
]
|
|
209
|
+
: []),
|
|
210
|
+
]
|
|
211
|
+
: [];
|
|
212
|
+
|
|
213
|
+
const verify = async () => {
|
|
214
|
+
const id = input.trim();
|
|
215
|
+
if (!id) return;
|
|
216
|
+
setLoading(true);
|
|
217
|
+
setVerifyGroupError(null);
|
|
218
|
+
try {
|
|
219
|
+
const data = await api.verifyGroup(id);
|
|
220
|
+
if (!data.ok) throw new Error(data.error);
|
|
221
|
+
setGroupId(id);
|
|
222
|
+
setGroupInfo(data);
|
|
223
|
+
if (!String(userId || "").trim() && data.suggestedUserId) {
|
|
224
|
+
setUserId(String(data.suggestedUserId));
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
setVerifyGroupError(e.message);
|
|
228
|
+
setGroupInfo(null);
|
|
229
|
+
}
|
|
230
|
+
setLoading(false);
|
|
231
|
+
};
|
|
232
|
+
const canContinue = !!(
|
|
233
|
+
groupInfo &&
|
|
234
|
+
groupInfo.chat?.isForum &&
|
|
235
|
+
groupInfo.bot?.isAdmin &&
|
|
236
|
+
groupInfo.bot?.canManageTopics
|
|
237
|
+
);
|
|
238
|
+
const continueWithConfig = async () => {
|
|
239
|
+
if (!canContinue || saving) return;
|
|
240
|
+
setVerifyGroupError(null);
|
|
241
|
+
setSaving(true);
|
|
242
|
+
try {
|
|
243
|
+
const userIdValue = String(userId || "").trim();
|
|
244
|
+
const data = await api.configureGroup(groupId, {
|
|
245
|
+
...(userIdValue ? { userId: userIdValue } : {}),
|
|
246
|
+
groupName: groupInfo?.chat?.title || groupId,
|
|
247
|
+
requireMention: false,
|
|
248
|
+
});
|
|
249
|
+
if (!data?.ok)
|
|
250
|
+
throw new Error(data?.error || "Failed to configure Telegram group");
|
|
251
|
+
if (data.userId) setUserId(String(data.userId));
|
|
252
|
+
onNext();
|
|
253
|
+
} catch (e) {
|
|
254
|
+
setVerifyGroupError(e.message);
|
|
255
|
+
}
|
|
256
|
+
setSaving(false);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
return html`
|
|
260
|
+
<div class="space-y-4">
|
|
261
|
+
<h3 class="text-sm font-semibold">Verify Group</h3>
|
|
262
|
+
|
|
263
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
|
|
264
|
+
<p class="text-xs text-gray-400">To get your group chat ID:</p>
|
|
265
|
+
<ol class="text-xs text-gray-400 space-y-1 list-decimal list-inside">
|
|
266
|
+
<li>
|
|
267
|
+
Invite <span class="text-gray-300">@myidbot</span> to your group
|
|
268
|
+
</li>
|
|
269
|
+
<li>
|
|
270
|
+
Send <code class="bg-black/40 px-1 rounded">/getgroupid</code>
|
|
271
|
+
</li>
|
|
272
|
+
<li>
|
|
273
|
+
Copy the ID (starts with
|
|
274
|
+
<code class="bg-black/40 px-1 rounded">-100</code>)
|
|
275
|
+
</li>
|
|
276
|
+
</ol>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div class="flex gap-2">
|
|
280
|
+
<input
|
|
281
|
+
type="text"
|
|
282
|
+
value=${input}
|
|
283
|
+
onInput=${(e) => setInput(e.target.value)}
|
|
284
|
+
placeholder="-100XXXXXXXXXX"
|
|
285
|
+
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"
|
|
286
|
+
/>
|
|
287
|
+
<${ActionButton}
|
|
288
|
+
onClick=${verify}
|
|
289
|
+
disabled=${!input.trim() || loading}
|
|
290
|
+
loading=${loading}
|
|
291
|
+
tone="secondary"
|
|
292
|
+
size="md"
|
|
293
|
+
idleLabel="Verify"
|
|
294
|
+
loadingMode="inline"
|
|
295
|
+
className="rounded-lg"
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
${verifyGroupError &&
|
|
300
|
+
html`
|
|
301
|
+
<div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
|
|
302
|
+
<p class="text-sm text-red-400">${verifyGroupError}</p>
|
|
303
|
+
</div>
|
|
304
|
+
`}
|
|
305
|
+
${groupInfo &&
|
|
306
|
+
html`
|
|
307
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
|
|
308
|
+
<div class="flex items-center gap-2">
|
|
309
|
+
<span class="text-sm text-gray-300 font-medium">${groupInfo.chat.title}</span>
|
|
310
|
+
<${Badge} tone="success">Verified</${Badge}>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="flex gap-3 text-xs text-gray-500">
|
|
313
|
+
<span>Topics: ${groupInfo.chat.isForum ? "ON" : "OFF"}</span>
|
|
314
|
+
<span>Bot: ${groupInfo.bot.status}</span>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
`}
|
|
318
|
+
${groupInfo &&
|
|
319
|
+
verifyWarnings.length === 0 &&
|
|
320
|
+
html`
|
|
321
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
|
|
322
|
+
<p class="text-xs text-gray-500">Your Telegram User ID</p>
|
|
323
|
+
<input
|
|
324
|
+
type="text"
|
|
325
|
+
value=${userId}
|
|
326
|
+
onInput=${(e) => setUserId(e.target.value)}
|
|
327
|
+
placeholder="e.g. 123456789"
|
|
328
|
+
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"
|
|
329
|
+
/>
|
|
330
|
+
<p class="text-xs text-gray-500">
|
|
331
|
+
Auto-filled from Telegram admins. Edit if needed.
|
|
332
|
+
</p>
|
|
333
|
+
</div>
|
|
334
|
+
`}
|
|
335
|
+
${verifyWarnings.length > 0 &&
|
|
336
|
+
html`
|
|
337
|
+
<div
|
|
338
|
+
class="bg-red-500/10 border border-red-500/20 rounded-lg p-3 space-y-3"
|
|
339
|
+
>
|
|
340
|
+
<p class="text-xs font-medium text-red-300">
|
|
341
|
+
Fix these before continuing:
|
|
342
|
+
</p>
|
|
343
|
+
<ul class="text-xs text-red-200 space-y-1 list-disc list-inside">
|
|
344
|
+
${verifyWarnings.map((message) => html`<li>${message}</li>`)}
|
|
345
|
+
</ul>
|
|
346
|
+
<p class="text-xs text-red-300 ">Once fixed, hit Verify again.</p>
|
|
347
|
+
</div>
|
|
348
|
+
`}
|
|
349
|
+
|
|
350
|
+
<div class="grid grid-cols-2 gap-2">
|
|
351
|
+
<button
|
|
352
|
+
onclick=${onBack}
|
|
353
|
+
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"
|
|
354
|
+
>
|
|
355
|
+
Back
|
|
356
|
+
</button>
|
|
357
|
+
<button
|
|
358
|
+
onclick=${continueWithConfig}
|
|
359
|
+
disabled=${!canContinue || saving}
|
|
360
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan ${!canContinue ||
|
|
361
|
+
saving
|
|
362
|
+
? "opacity-50 cursor-not-allowed"
|
|
363
|
+
: ""}"
|
|
364
|
+
>
|
|
365
|
+
${saving ? "Saving..." : "Next"}
|
|
366
|
+
</button>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
`;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Step 4: Create Topics
|
|
373
|
+
export const TopicsStep = ({ groupId, topics, setTopics, onNext, onBack }) => {
|
|
374
|
+
const [newTopicName, setNewTopicName] = useState("");
|
|
375
|
+
const [newTopicInstructions, setNewTopicInstructions] = useState("");
|
|
376
|
+
const [creating, setCreating] = useState(false);
|
|
377
|
+
const [error, setError] = useState(null);
|
|
378
|
+
const [deleting, setDeleting] = useState(null);
|
|
379
|
+
const [deleteTopicConfirm, setDeleteTopicConfirm] = useState(null);
|
|
380
|
+
|
|
381
|
+
const loadTopics = async () => {
|
|
382
|
+
const data = await api.listTopics(groupId);
|
|
383
|
+
if (data.ok) setTopics(data.topics);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
loadTopics();
|
|
388
|
+
}, [groupId]);
|
|
389
|
+
|
|
390
|
+
const createSingle = async () => {
|
|
391
|
+
const name = newTopicName.trim();
|
|
392
|
+
const systemInstructions = newTopicInstructions.trim();
|
|
393
|
+
if (!name) return;
|
|
394
|
+
setCreating(true);
|
|
395
|
+
setError(null);
|
|
396
|
+
try {
|
|
397
|
+
const data = await api.createTopicsBulk(groupId, [
|
|
398
|
+
{ name, ...(systemInstructions ? { systemInstructions } : {}) },
|
|
399
|
+
]);
|
|
400
|
+
if (!data.ok)
|
|
401
|
+
throw new Error(data.results?.[0]?.error || "Failed to create topic");
|
|
402
|
+
const failed = data.results.filter((r) => !r.ok);
|
|
403
|
+
if (failed.length > 0) throw new Error(failed[0].error);
|
|
404
|
+
setNewTopicName("");
|
|
405
|
+
setNewTopicInstructions("");
|
|
406
|
+
await loadTopics();
|
|
407
|
+
showToast(`Created topic: ${name}`, "success");
|
|
408
|
+
} catch (e) {
|
|
409
|
+
setError(e.message);
|
|
410
|
+
}
|
|
411
|
+
setCreating(false);
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const handleDelete = async (topicId, topicName) => {
|
|
415
|
+
setDeleting(topicId);
|
|
416
|
+
try {
|
|
417
|
+
const data = await api.deleteTopic(groupId, topicId);
|
|
418
|
+
if (!data.ok) throw new Error(data.error);
|
|
419
|
+
await loadTopics();
|
|
420
|
+
if (data.removedFromRegistryOnly) {
|
|
421
|
+
showToast(`Removed stale topic from registry: ${topicName}`, "success");
|
|
422
|
+
} else {
|
|
423
|
+
showToast(`Deleted topic: ${topicName}`, "success");
|
|
424
|
+
}
|
|
425
|
+
} catch (e) {
|
|
426
|
+
showToast(`Failed to delete: ${e.message}`, "error");
|
|
427
|
+
}
|
|
428
|
+
setDeleting(null);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const topicEntries = Object.entries(topics || {});
|
|
432
|
+
|
|
433
|
+
return html`
|
|
434
|
+
<div class="space-y-4">
|
|
435
|
+
<h3 class="text-sm font-semibold">Create Topics</h3>
|
|
436
|
+
|
|
437
|
+
${topicEntries.length > 0 &&
|
|
438
|
+
html`
|
|
439
|
+
<div
|
|
440
|
+
class="bg-black/20 border border-border rounded-lg overflow-hidden"
|
|
441
|
+
>
|
|
442
|
+
<table class="w-full text-xs">
|
|
443
|
+
<thead>
|
|
444
|
+
<tr class="border-b border-border">
|
|
445
|
+
<th class="text-left px-3 py-2 text-gray-500 font-medium">
|
|
446
|
+
Topic
|
|
447
|
+
</th>
|
|
448
|
+
<th class="text-left px-3 py-2 text-gray-500 font-medium">
|
|
449
|
+
Thread ID
|
|
450
|
+
</th>
|
|
451
|
+
<th class="px-3 py-2 w-8" />
|
|
452
|
+
</tr>
|
|
453
|
+
</thead>
|
|
454
|
+
<tbody>
|
|
455
|
+
${topicEntries.map(
|
|
456
|
+
([id, t]) => html`
|
|
457
|
+
<tr class="border-b border-border last:border-0">
|
|
458
|
+
<td class="px-3 py-2 text-gray-300">${t.name}</td>
|
|
459
|
+
<td class="px-3 py-2 text-gray-500 font-mono">${id}</td>
|
|
460
|
+
<td class="px-3 py-2">
|
|
461
|
+
<button
|
|
462
|
+
onclick=${() =>
|
|
463
|
+
setDeleteTopicConfirm({
|
|
464
|
+
id: String(id),
|
|
465
|
+
name: String(t.name || ""),
|
|
466
|
+
})}
|
|
467
|
+
disabled=${deleting === id}
|
|
468
|
+
class="text-gray-600 hover:text-red-400 transition-colors ${deleting ===
|
|
469
|
+
id
|
|
470
|
+
? "opacity-50"
|
|
471
|
+
: ""}"
|
|
472
|
+
title="Delete topic"
|
|
473
|
+
>
|
|
474
|
+
<svg
|
|
475
|
+
width="14"
|
|
476
|
+
height="14"
|
|
477
|
+
viewBox="0 0 16 16"
|
|
478
|
+
fill="currentColor"
|
|
479
|
+
>
|
|
480
|
+
<path
|
|
481
|
+
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"
|
|
482
|
+
/>
|
|
483
|
+
</svg>
|
|
484
|
+
</button>
|
|
485
|
+
</td>
|
|
486
|
+
</tr>
|
|
487
|
+
`,
|
|
488
|
+
)}
|
|
489
|
+
</tbody>
|
|
490
|
+
</table>
|
|
491
|
+
</div>
|
|
492
|
+
`}
|
|
493
|
+
|
|
494
|
+
<div class="space-y-2">
|
|
495
|
+
<label class="text-xs text-gray-500">Add a topic</label>
|
|
496
|
+
<div class="space-y-2">
|
|
497
|
+
<div class="flex gap-2">
|
|
498
|
+
<input
|
|
499
|
+
type="text"
|
|
500
|
+
value=${newTopicName}
|
|
501
|
+
onInput=${(e) => setNewTopicName(e.target.value)}
|
|
502
|
+
onKeyDown=${(e) => {
|
|
503
|
+
if (e.key === "Enter") createSingle();
|
|
504
|
+
}}
|
|
505
|
+
placeholder="Topic name"
|
|
506
|
+
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"
|
|
507
|
+
/>
|
|
508
|
+
</div>
|
|
509
|
+
<textarea
|
|
510
|
+
value=${newTopicInstructions}
|
|
511
|
+
onInput=${(e) => setNewTopicInstructions(e.target.value)}
|
|
512
|
+
placeholder="System instructions (optional)"
|
|
513
|
+
rows="4"
|
|
514
|
+
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"
|
|
515
|
+
/>
|
|
516
|
+
<div class="flex justify-end">
|
|
517
|
+
<${ActionButton}
|
|
518
|
+
onClick=${createSingle}
|
|
519
|
+
disabled=${creating || !newTopicName.trim()}
|
|
520
|
+
loading=${creating}
|
|
521
|
+
tone="secondary"
|
|
522
|
+
size="lg"
|
|
523
|
+
idleLabel="Add"
|
|
524
|
+
loadingMode="inline"
|
|
525
|
+
className="min-w-[88px]"
|
|
526
|
+
/>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
<div class="border-t border-white/10 pt-2" />
|
|
531
|
+
|
|
532
|
+
${error &&
|
|
533
|
+
html`
|
|
534
|
+
<div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
|
|
535
|
+
<p class="text-sm text-red-400">${error}</p>
|
|
536
|
+
</div>
|
|
537
|
+
`}
|
|
538
|
+
|
|
539
|
+
<div class="grid grid-cols-2 gap-2">
|
|
540
|
+
<button
|
|
541
|
+
onclick=${onBack}
|
|
542
|
+
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"
|
|
543
|
+
>
|
|
544
|
+
Back
|
|
545
|
+
</button>
|
|
546
|
+
<button
|
|
547
|
+
onclick=${onNext}
|
|
548
|
+
disabled=${topicEntries.length === 0}
|
|
549
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"
|
|
550
|
+
>
|
|
551
|
+
Next
|
|
552
|
+
</button>
|
|
553
|
+
</div>
|
|
554
|
+
<${ConfirmDialog}
|
|
555
|
+
visible=${!!deleteTopicConfirm}
|
|
556
|
+
title="Delete topic?"
|
|
557
|
+
message=${deleteTopicConfirm
|
|
558
|
+
? `This will delete "${deleteTopicConfirm.name}" (thread ${deleteTopicConfirm.id}) from your Telegram workspace.`
|
|
559
|
+
: "This will delete this topic from your Telegram workspace."}
|
|
560
|
+
confirmLabel="Delete topic"
|
|
561
|
+
confirmLoadingLabel="Deleting..."
|
|
562
|
+
confirmTone="warning"
|
|
563
|
+
confirmLoading=${!!deleting}
|
|
564
|
+
cancelLabel="Cancel"
|
|
565
|
+
onCancel=${() => {
|
|
566
|
+
if (deleting) return;
|
|
567
|
+
setDeleteTopicConfirm(null);
|
|
568
|
+
}}
|
|
569
|
+
onConfirm=${async () => {
|
|
570
|
+
if (!deleteTopicConfirm) return;
|
|
571
|
+
const pendingDelete = deleteTopicConfirm;
|
|
572
|
+
setDeleteTopicConfirm(null);
|
|
573
|
+
await handleDelete(pendingDelete.id, pendingDelete.name);
|
|
574
|
+
}}
|
|
575
|
+
/>
|
|
576
|
+
</div>
|
|
577
|
+
`;
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// Step 5: Summary
|
|
581
|
+
export const SummaryStep = ({ groupId, groupInfo, topics, onBack, onDone }) => {
|
|
582
|
+
return html`
|
|
583
|
+
<div class="space-y-4">
|
|
584
|
+
<div class="max-w-xl mx-auto text-center space-y-10 mt-10">
|
|
585
|
+
<p class="text-sm font-medium text-green-300">🎉 Setup complete</p>
|
|
586
|
+
<p class="text-xs text-gray-400">
|
|
587
|
+
The topic registry has been injected into
|
|
588
|
+
<code class="bg-black/40 px-1 rounded">TOOLS.md</code> so your agent
|
|
589
|
+
knows which thread ID maps to which topic name.
|
|
590
|
+
</p>
|
|
591
|
+
|
|
592
|
+
<div class="bg-black/20 border border-border rounded-lg p-3">
|
|
593
|
+
<p class="text-xs text-gray-500">
|
|
594
|
+
If you used <span class="text-gray-300">@myidbot</span> to find IDs,
|
|
595
|
+
you can remove it from the group now.
|
|
596
|
+
</p>
|
|
597
|
+
</div>
|
|
598
|
+
</div>
|
|
599
|
+
|
|
600
|
+
<div class="grid grid-cols-2 gap-2">
|
|
601
|
+
<button
|
|
602
|
+
onclick=${onBack}
|
|
603
|
+
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"
|
|
604
|
+
>
|
|
605
|
+
Back
|
|
606
|
+
</button>
|
|
607
|
+
<button
|
|
608
|
+
onclick=${onDone}
|
|
609
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"
|
|
610
|
+
>
|
|
611
|
+
Done
|
|
612
|
+
</button>
|
|
613
|
+
</div>
|
|
614
|
+
</div>
|
|
615
|
+
`;
|
|
616
|
+
};
|