@hienlh/ppm 0.9.40 → 0.9.41

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 (57) hide show
  1. package/CHANGELOG.md +3 -59
  2. package/dist/web/assets/browser-tab--V6I70pH.js +1 -0
  3. package/dist/web/assets/chat-tab-CrkhvVjF.js +10 -0
  4. package/dist/web/assets/code-editor-BfMyExLp.js +2 -0
  5. package/dist/web/assets/{database-viewer-RqbZkczM.js → database-viewer-CeRUrZKj.js} +1 -1
  6. package/dist/web/assets/{diff-viewer-C-6EcVDG.js → diff-viewer-D2p3WTMS.js} +1 -1
  7. package/dist/web/assets/{extension-webview-xZQrFpb0.js → extension-webview-DQWAHMlR.js} +1 -1
  8. package/dist/web/assets/git-graph-BWRMlCdK.js +1 -0
  9. package/dist/web/assets/index-C7esr4gM.css +2 -0
  10. package/dist/web/assets/index-DU6UVgQY.js +30 -0
  11. package/dist/web/assets/keybindings-store-BE2T8jM9.js +1 -0
  12. package/dist/web/assets/{markdown-renderer-CViGEOCg.js → markdown-renderer-C7lKs47M.js} +4 -4
  13. package/dist/web/assets/{postgres-viewer-DjRZKruo.js → postgres-viewer-Cr9jpBNd.js} +1 -1
  14. package/dist/web/assets/{settings-tab-DQ3eb1bU.js → settings-tab-DKy-YDg2.js} +1 -1
  15. package/dist/web/assets/{sqlite-viewer-B8OuhoEV.js → sqlite-viewer-9AmeF-Zs.js} +1 -1
  16. package/dist/web/assets/square-oPKIkJiw.js +1 -0
  17. package/dist/web/assets/{terminal-tab-D7K74k2B.js → terminal-tab-DFhB4Rxh.js} +1 -1
  18. package/dist/web/assets/{use-monaco-theme-BEWkUA66.js → use-monaco-theme-B7XLw-OX.js} +1 -1
  19. package/dist/web/index.html +2 -3
  20. package/dist/web/sw.js +1 -1
  21. package/docs/codebase-summary.md +3 -33
  22. package/docs/project-changelog.md +0 -47
  23. package/docs/project-roadmap.md +7 -14
  24. package/docs/system-architecture.md +2 -65
  25. package/package.json +1 -1
  26. package/src/server/index.ts +0 -7
  27. package/src/server/routes/settings.ts +17 -83
  28. package/src/services/config.service.ts +1 -1
  29. package/src/services/db.service.ts +1 -285
  30. package/src/services/git.service.ts +2 -2
  31. package/src/services/telegram-notification.service.ts +21 -44
  32. package/src/types/config.ts +1 -25
  33. package/src/web/components/browser/browser-tab.tsx +128 -97
  34. package/src/web/components/chat/chat-history-bar.tsx +3 -8
  35. package/src/web/components/layout/command-palette.tsx +1 -1
  36. package/src/web/components/settings/settings-tab.tsx +5 -10
  37. package/src/web/hooks/use-url-sync.ts +1 -1
  38. package/dist/web/assets/browser-tab-Dm65lWLO.js +0 -1
  39. package/dist/web/assets/chat-tab-rKXwCBEZ.js +0 -10
  40. package/dist/web/assets/code-editor-BD_hxR8Z.js +0 -2
  41. package/dist/web/assets/git-graph-Cndi59vr.js +0 -1
  42. package/dist/web/assets/index-BpOBp5oT.js +0 -30
  43. package/dist/web/assets/index-CcFDEPCo.css +0 -2
  44. package/dist/web/assets/keybindings-store-BE5y0cut.js +0 -1
  45. package/dist/web/assets/tab-store-BXMIUvsE.js +0 -1
  46. package/docs/streaming-input-guide.md +0 -267
  47. package/snapshot-state.md +0 -1526
  48. package/src/services/ppmbot/ppmbot-formatter.ts +0 -88
  49. package/src/services/ppmbot/ppmbot-memory.ts +0 -333
  50. package/src/services/ppmbot/ppmbot-service.ts +0 -545
  51. package/src/services/ppmbot/ppmbot-session.ts +0 -199
  52. package/src/services/ppmbot/ppmbot-streamer.ts +0 -288
  53. package/src/services/ppmbot/ppmbot-telegram.ts +0 -279
  54. package/src/types/ppmbot.ts +0 -103
  55. package/src/web/components/settings/ppmbot-settings-section.tsx +0 -355
  56. package/test-session-ops.mjs +0 -444
  57. package/test-tokens.mjs +0 -212
@@ -1,355 +0,0 @@
1
- import { useState, useEffect, useCallback, type ChangeEvent } from "react";
2
- import { Button } from "@/components/ui/button";
3
- import { Input } from "@/components/ui/input";
4
- import { Switch } from "@/components/ui/switch";
5
- import { api } from "@/lib/api-client";
6
- import { Trash2, CheckCircle, Clock, Send } from "lucide-react";
7
-
8
- interface PPMBotConfig {
9
- enabled: boolean;
10
- default_provider: string;
11
- default_project: string;
12
- system_prompt: string;
13
- show_tool_calls: boolean;
14
- show_thinking: boolean;
15
- permission_mode: string;
16
- debounce_ms: number;
17
- }
18
-
19
- interface TelegramConfig {
20
- bot_token: string;
21
- }
22
-
23
- interface PairedChat {
24
- id: number;
25
- telegram_chat_id: string;
26
- telegram_user_id: string | null;
27
- display_name: string | null;
28
- pairing_code: string | null;
29
- status: "pending" | "approved";
30
- created_at: number;
31
- approved_at: number | null;
32
- }
33
-
34
- export function PPMBotSettingsSection() {
35
- const [config, setConfig] = useState<PPMBotConfig | null>(null);
36
- const [saving, setSaving] = useState(false);
37
- const [status, setStatus] = useState<{ type: "ok" | "err"; msg: string } | null>(null);
38
-
39
- // Bot token (from telegram config)
40
- const [tokenInput, setTokenInput] = useState("");
41
- const [tokenConfigured, setTokenConfigured] = useState(false);
42
- const [tokenSaving, setTokenSaving] = useState(false);
43
-
44
- const [enabled, setEnabled] = useState(false);
45
- const [defaultProject, setDefaultProject] = useState("");
46
- const [systemPrompt, setSystemPrompt] = useState("");
47
- const [showToolCalls, setShowToolCalls] = useState(true);
48
- const [showThinking, setShowThinking] = useState(false);
49
- const [debounceMs, setDebounceMs] = useState(2000);
50
-
51
- const [pairedChats, setPairedChats] = useState<PairedChat[]>([]);
52
- const [approveCode, setApproveCode] = useState("");
53
- const [approving, setApproving] = useState(false);
54
- const [testing, setTesting] = useState(false);
55
-
56
- const fetchPairedChats = useCallback(async () => {
57
- try {
58
- const data = await api.get<PairedChat[]>("/api/settings/clawbot/paired");
59
- setPairedChats(data);
60
- } catch {}
61
- }, []);
62
-
63
- useEffect(() => {
64
- api.get<PPMBotConfig>("/api/settings/clawbot").then((data) => {
65
- setConfig(data);
66
- setEnabled(data.enabled);
67
- setDefaultProject(data.default_project);
68
- setSystemPrompt(data.system_prompt);
69
- setShowToolCalls(data.show_tool_calls);
70
- setShowThinking(data.show_thinking);
71
- setDebounceMs(data.debounce_ms);
72
- }).catch(() => {});
73
- api.get<TelegramConfig>("/api/settings/telegram").then((data) => {
74
- setTokenConfigured(!!data.bot_token);
75
- }).catch(() => {});
76
- fetchPairedChats();
77
- }, [fetchPairedChats]);
78
-
79
- const saveToken = async () => {
80
- if (!tokenInput.trim()) return;
81
- setTokenSaving(true);
82
- setStatus(null);
83
- try {
84
- await api.put<TelegramConfig>("/api/settings/telegram", { bot_token: tokenInput });
85
- setTokenConfigured(true);
86
- setTokenInput("");
87
- setStatus({ type: "ok", msg: "Bot token saved" });
88
- } catch (e) {
89
- setStatus({ type: "err", msg: (e as Error).message });
90
- } finally {
91
- setTokenSaving(false);
92
- }
93
- };
94
-
95
- const save = async () => {
96
- setSaving(true);
97
- setStatus(null);
98
- try {
99
- const body: Partial<PPMBotConfig> = {
100
- enabled,
101
- default_project: defaultProject.trim(),
102
- system_prompt: systemPrompt,
103
- show_tool_calls: showToolCalls,
104
- show_thinking: showThinking,
105
- debounce_ms: debounceMs,
106
- };
107
- const data = await api.put<PPMBotConfig>("/api/settings/clawbot", body);
108
- setConfig(data);
109
- setStatus({ type: "ok", msg: enabled ? "Saved — bot started" : "Saved — bot stopped" });
110
- } catch (e) {
111
- setStatus({ type: "err", msg: (e as Error).message });
112
- } finally {
113
- setSaving(false);
114
- }
115
- };
116
-
117
- const handleApprovePairing = async () => {
118
- if (!approveCode.trim()) return;
119
- setApproving(true);
120
- try {
121
- await api.post("/api/settings/clawbot/paired/approve", { code: approveCode.trim().toUpperCase() });
122
- setApproveCode("");
123
- await fetchPairedChats();
124
- setStatus({ type: "ok", msg: "Device approved" });
125
- } catch (e) {
126
- setStatus({ type: "err", msg: (e as Error).message });
127
- } finally {
128
- setApproving(false);
129
- }
130
- };
131
-
132
- const handleRevokePairing = async (chatId: string) => {
133
- try {
134
- await api.del(`/api/settings/clawbot/paired/${chatId}`);
135
- await fetchPairedChats();
136
- setStatus({ type: "ok", msg: "Device revoked" });
137
- } catch (e) {
138
- setStatus({ type: "err", msg: (e as Error).message });
139
- }
140
- };
141
-
142
- const handleTestNotification = async () => {
143
- setTesting(true);
144
- setStatus(null);
145
- try {
146
- await api.post("/api/settings/telegram/test", {});
147
- setStatus({ type: "ok", msg: "Test notification sent to all paired devices!" });
148
- } catch (e) {
149
- setStatus({ type: "err", msg: (e as Error).message });
150
- } finally {
151
- setTesting(false);
152
- }
153
- };
154
-
155
- if (!config) return <p className="text-xs text-muted-foreground">Loading...</p>;
156
-
157
- const approvedCount = pairedChats.filter((c) => c.status === "approved").length;
158
-
159
- return (
160
- <div className="space-y-4">
161
- {/* Bot Token */}
162
- <div className="space-y-1.5">
163
- <label className="text-[11px] text-muted-foreground">Telegram Bot Token</label>
164
- <div className="flex gap-1.5">
165
- <Input
166
- type="password"
167
- placeholder={tokenConfigured ? "•••••• (saved)" : "123456:ABC-DEF..."}
168
- value={tokenInput}
169
- onChange={(e) => setTokenInput(e.target.value)}
170
- className="h-7 text-xs flex-1"
171
- />
172
- <Button
173
- variant="outline"
174
- size="sm"
175
- className="h-7 text-xs shrink-0 cursor-pointer"
176
- disabled={tokenSaving || !tokenInput.trim()}
177
- onClick={saveToken}
178
- >
179
- {tokenSaving ? "..." : "Save"}
180
- </Button>
181
- </div>
182
- <p className="text-[10px] text-muted-foreground">
183
- Create a bot via <b>@BotFather</b> on Telegram. Used for both chat and notifications.
184
- </p>
185
- </div>
186
-
187
- {/* Enable/Disable */}
188
- <div className="flex items-center justify-between">
189
- <div>
190
- <p className="text-xs font-medium">Enable PPMBot</p>
191
- <p className="text-[10px] text-muted-foreground">
192
- Telegram bot that chats with your AI providers
193
- </p>
194
- </div>
195
- <Switch checked={enabled} onCheckedChange={setEnabled} />
196
- </div>
197
-
198
- {/* Paired Devices */}
199
- <div className="space-y-2">
200
- <p className="text-xs font-medium">Paired Devices</p>
201
- <p className="text-[10px] text-muted-foreground">
202
- Send any message to the bot on Telegram to get a pairing code. Enter it below to approve.
203
- Notifications are sent to all approved devices.
204
- </p>
205
-
206
- <div className="flex gap-2">
207
- <Input
208
- placeholder="Enter pairing code (e.g. A3K7WR)"
209
- value={approveCode}
210
- onChange={(e) => setApproveCode(e.target.value.toUpperCase())}
211
- className="h-8 text-xs font-mono tracking-wider uppercase"
212
- maxLength={6}
213
- />
214
- <Button
215
- variant="outline"
216
- size="sm"
217
- className="h-8 text-xs shrink-0 cursor-pointer"
218
- disabled={approving || approveCode.length < 6}
219
- onClick={handleApprovePairing}
220
- >
221
- {approving ? "..." : "Approve"}
222
- </Button>
223
- </div>
224
-
225
- {pairedChats.length === 0 ? (
226
- <p className="text-[10px] text-muted-foreground italic">No paired devices yet.</p>
227
- ) : (
228
- <div className="space-y-1">
229
- {pairedChats.map((chat) => (
230
- <div
231
- key={chat.id}
232
- className="flex items-center justify-between rounded-md border p-2"
233
- >
234
- <div className="flex items-center gap-2 min-w-0">
235
- {chat.status === "approved" ? (
236
- <CheckCircle className="size-3.5 text-green-500 shrink-0" />
237
- ) : (
238
- <Clock className="size-3.5 text-yellow-500 shrink-0" />
239
- )}
240
- <div className="min-w-0">
241
- <p className="text-xs truncate">
242
- {chat.display_name || `Chat ${chat.telegram_chat_id}`}
243
- </p>
244
- <p className="text-[10px] text-muted-foreground">
245
- {chat.status === "pending" && chat.pairing_code
246
- ? `Code: ${chat.pairing_code}`
247
- : chat.status}
248
- </p>
249
- </div>
250
- </div>
251
- <Button
252
- variant="ghost"
253
- size="sm"
254
- className="h-7 w-7 p-0 text-destructive hover:text-destructive cursor-pointer"
255
- onClick={() => handleRevokePairing(chat.telegram_chat_id)}
256
- >
257
- <Trash2 className="size-3.5" />
258
- </Button>
259
- </div>
260
- ))}
261
- </div>
262
- )}
263
-
264
- {/* Test notification button */}
265
- {tokenConfigured && approvedCount > 0 && (
266
- <Button
267
- variant="outline"
268
- size="sm"
269
- className="h-7 text-xs gap-1 w-full cursor-pointer"
270
- disabled={testing}
271
- onClick={handleTestNotification}
272
- >
273
- <Send className="size-3" />
274
- {testing ? "Sending..." : "Test Notification"}
275
- </Button>
276
- )}
277
- </div>
278
-
279
- {/* Default Project */}
280
- <div className="space-y-1.5">
281
- <label className="text-[11px] text-muted-foreground">Default Project</label>
282
- <Input
283
- placeholder="my-project"
284
- value={defaultProject}
285
- onChange={(e) => setDefaultProject(e.target.value)}
286
- className="h-7 text-xs"
287
- />
288
- <p className="text-[10px] text-muted-foreground">
289
- Project used when starting a new chat. Leave empty for default workspace (~/.ppm/bot/).
290
- </p>
291
- </div>
292
-
293
- {/* System Prompt */}
294
- <div className="space-y-1.5">
295
- <label className="text-[11px] text-muted-foreground">System Prompt</label>
296
- <textarea
297
- placeholder="You are a helpful assistant..."
298
- value={systemPrompt}
299
- onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setSystemPrompt(e.target.value)}
300
- className="flex w-full rounded-md border border-input bg-background px-3 py-2 text-xs min-h-[60px] resize-y ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
301
- rows={3}
302
- />
303
- <p className="text-[10px] text-muted-foreground">
304
- Custom personality/instructions prepended to each session.
305
- </p>
306
- </div>
307
-
308
- {/* Display Toggles */}
309
- <div className="space-y-2">
310
- <div className="flex items-center justify-between">
311
- <p className="text-xs">Show tool calls</p>
312
- <Switch checked={showToolCalls} onCheckedChange={setShowToolCalls} />
313
- </div>
314
- <div className="flex items-center justify-between">
315
- <p className="text-xs">Show thinking</p>
316
- <Switch checked={showThinking} onCheckedChange={setShowThinking} />
317
- </div>
318
- </div>
319
-
320
- {/* Debounce */}
321
- <div className="space-y-1.5">
322
- <label className="text-[11px] text-muted-foreground">Debounce (ms)</label>
323
- <Input
324
- type="number"
325
- min={0}
326
- max={30000}
327
- step={500}
328
- value={debounceMs}
329
- onChange={(e) => setDebounceMs(Number(e.target.value))}
330
- className="h-7 text-xs w-24"
331
- />
332
- <p className="text-[10px] text-muted-foreground">
333
- Merge rapid messages within this window. 0 = no debounce.
334
- </p>
335
- </div>
336
-
337
- {/* Save */}
338
- <Button
339
- variant="default"
340
- size="sm"
341
- className="h-8 text-xs w-full cursor-pointer"
342
- disabled={saving}
343
- onClick={save}
344
- >
345
- {saving ? "Saving..." : "Save"}
346
- </Button>
347
-
348
- {status && (
349
- <p className={`text-[11px] ${status.type === "ok" ? "text-green-600 dark:text-green-400" : "text-destructive"}`}>
350
- {status.msg}
351
- </p>
352
- )}
353
- </div>
354
- );
355
- }