@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.
- package/CHANGELOG.md +3 -59
- package/dist/web/assets/browser-tab--V6I70pH.js +1 -0
- package/dist/web/assets/chat-tab-CrkhvVjF.js +10 -0
- package/dist/web/assets/code-editor-BfMyExLp.js +2 -0
- package/dist/web/assets/{database-viewer-RqbZkczM.js → database-viewer-CeRUrZKj.js} +1 -1
- package/dist/web/assets/{diff-viewer-C-6EcVDG.js → diff-viewer-D2p3WTMS.js} +1 -1
- package/dist/web/assets/{extension-webview-xZQrFpb0.js → extension-webview-DQWAHMlR.js} +1 -1
- package/dist/web/assets/git-graph-BWRMlCdK.js +1 -0
- package/dist/web/assets/index-C7esr4gM.css +2 -0
- package/dist/web/assets/index-DU6UVgQY.js +30 -0
- package/dist/web/assets/keybindings-store-BE2T8jM9.js +1 -0
- package/dist/web/assets/{markdown-renderer-CViGEOCg.js → markdown-renderer-C7lKs47M.js} +4 -4
- package/dist/web/assets/{postgres-viewer-DjRZKruo.js → postgres-viewer-Cr9jpBNd.js} +1 -1
- package/dist/web/assets/{settings-tab-DQ3eb1bU.js → settings-tab-DKy-YDg2.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-B8OuhoEV.js → sqlite-viewer-9AmeF-Zs.js} +1 -1
- package/dist/web/assets/square-oPKIkJiw.js +1 -0
- package/dist/web/assets/{terminal-tab-D7K74k2B.js → terminal-tab-DFhB4Rxh.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BEWkUA66.js → use-monaco-theme-B7XLw-OX.js} +1 -1
- package/dist/web/index.html +2 -3
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +3 -33
- package/docs/project-changelog.md +0 -47
- package/docs/project-roadmap.md +7 -14
- package/docs/system-architecture.md +2 -65
- package/package.json +1 -1
- package/src/server/index.ts +0 -7
- package/src/server/routes/settings.ts +17 -83
- package/src/services/config.service.ts +1 -1
- package/src/services/db.service.ts +1 -285
- package/src/services/git.service.ts +2 -2
- package/src/services/telegram-notification.service.ts +21 -44
- package/src/types/config.ts +1 -25
- package/src/web/components/browser/browser-tab.tsx +128 -97
- package/src/web/components/chat/chat-history-bar.tsx +3 -8
- package/src/web/components/layout/command-palette.tsx +1 -1
- package/src/web/components/settings/settings-tab.tsx +5 -10
- package/src/web/hooks/use-url-sync.ts +1 -1
- package/dist/web/assets/browser-tab-Dm65lWLO.js +0 -1
- package/dist/web/assets/chat-tab-rKXwCBEZ.js +0 -10
- package/dist/web/assets/code-editor-BD_hxR8Z.js +0 -2
- package/dist/web/assets/git-graph-Cndi59vr.js +0 -1
- package/dist/web/assets/index-BpOBp5oT.js +0 -30
- package/dist/web/assets/index-CcFDEPCo.css +0 -2
- package/dist/web/assets/keybindings-store-BE5y0cut.js +0 -1
- package/dist/web/assets/tab-store-BXMIUvsE.js +0 -1
- package/docs/streaming-input-guide.md +0 -267
- package/snapshot-state.md +0 -1526
- package/src/services/ppmbot/ppmbot-formatter.ts +0 -88
- package/src/services/ppmbot/ppmbot-memory.ts +0 -333
- package/src/services/ppmbot/ppmbot-service.ts +0 -545
- package/src/services/ppmbot/ppmbot-session.ts +0 -199
- package/src/services/ppmbot/ppmbot-streamer.ts +0 -288
- package/src/services/ppmbot/ppmbot-telegram.ts +0 -279
- package/src/types/ppmbot.ts +0 -103
- package/src/web/components/settings/ppmbot-settings-section.tsx +0 -355
- package/test-session-ops.mjs +0 -444
- 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
|
-
}
|