@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
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "https://esm.sh/preact/hooks";
|
|
3
9
|
import htm from "https://esm.sh/htm";
|
|
4
10
|
import { fetchBrowseTree } from "../lib/api.js";
|
|
5
11
|
import {
|
|
@@ -23,6 +29,7 @@ const html = htm.bind(h);
|
|
|
23
29
|
const kTreeIndentPx = 9;
|
|
24
30
|
const kFolderBasePaddingPx = 10;
|
|
25
31
|
const kFileBasePaddingPx = 14;
|
|
32
|
+
const kTreeRefreshIntervalMs = 5000;
|
|
26
33
|
const kCollapsedFoldersStorageKey = "alphaclaw.browse.collapsedFolders";
|
|
27
34
|
const kLegacyCollapsedFoldersStorageKey = "alphaclawBrowseCollapsedFolders";
|
|
28
35
|
|
|
@@ -48,6 +55,37 @@ const collectFolderPaths = (node, folderPaths) => {
|
|
|
48
55
|
);
|
|
49
56
|
};
|
|
50
57
|
|
|
58
|
+
const collectFilePaths = (node, filePaths) => {
|
|
59
|
+
if (!node) return;
|
|
60
|
+
if (node.type === "file") {
|
|
61
|
+
if (node.path) filePaths.push(node.path);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
(node.children || []).forEach((childNode) =>
|
|
65
|
+
collectFilePaths(childNode, filePaths),
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const filterTreeNode = (node, normalizedQuery) => {
|
|
70
|
+
if (!node) return null;
|
|
71
|
+
const query = String(normalizedQuery || "").trim().toLowerCase();
|
|
72
|
+
if (!query) return node;
|
|
73
|
+
const nodeName = String(node.name || "").toLowerCase();
|
|
74
|
+
const nodePath = String(node.path || "").toLowerCase();
|
|
75
|
+
const isDirectMatch = nodeName.includes(query) || nodePath.includes(query);
|
|
76
|
+
if (node.type === "file") {
|
|
77
|
+
return isDirectMatch ? node : null;
|
|
78
|
+
}
|
|
79
|
+
const filteredChildren = (node.children || [])
|
|
80
|
+
.map((childNode) => filterTreeNode(childNode, query))
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
if (!isDirectMatch && filteredChildren.length === 0) return null;
|
|
83
|
+
return {
|
|
84
|
+
...node,
|
|
85
|
+
children: filteredChildren,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
51
89
|
const getFileIconMeta = (fileName) => {
|
|
52
90
|
const normalizedName = String(fileName || "").toLowerCase();
|
|
53
91
|
if (normalizedName.endsWith(".md")) {
|
|
@@ -126,17 +164,20 @@ const TreeNode = ({
|
|
|
126
164
|
onSelectFile,
|
|
127
165
|
selectedPath = "",
|
|
128
166
|
draftPaths,
|
|
167
|
+
isSearchActive = false,
|
|
168
|
+
searchActivePath = "",
|
|
129
169
|
}) => {
|
|
130
170
|
if (!node) return null;
|
|
131
171
|
if (node.type === "file") {
|
|
132
172
|
const isActive = selectedPath === node.path;
|
|
173
|
+
const isSearchActiveNode = searchActivePath === node.path;
|
|
133
174
|
const hasDraft = draftPaths.has(node.path || "");
|
|
134
175
|
const fileIconMeta = getFileIconMeta(node.name);
|
|
135
176
|
const FileTypeIcon = fileIconMeta.icon;
|
|
136
177
|
return html`
|
|
137
178
|
<li class="tree-item">
|
|
138
179
|
<a
|
|
139
|
-
class=${isActive ? "active" : ""}
|
|
180
|
+
class=${`${isActive ? "active" : ""} ${isSearchActiveNode && !isActive ? "soft-active" : ""}`.trim()}
|
|
140
181
|
onclick=${() => onSelectFile(node.path)}
|
|
141
182
|
style=${{
|
|
142
183
|
paddingLeft: `${kFileBasePaddingPx + depth * kTreeIndentPx}px`,
|
|
@@ -152,7 +193,7 @@ const TreeNode = ({
|
|
|
152
193
|
}
|
|
153
194
|
|
|
154
195
|
const folderPath = node.path || "";
|
|
155
|
-
const isCollapsed = collapsedPaths.has(folderPath);
|
|
196
|
+
const isCollapsed = isSearchActive ? false : collapsedPaths.has(folderPath);
|
|
156
197
|
return html`
|
|
157
198
|
<li class="tree-item">
|
|
158
199
|
<div
|
|
@@ -178,6 +219,8 @@ const TreeNode = ({
|
|
|
178
219
|
onSelectFile=${onSelectFile}
|
|
179
220
|
selectedPath=${selectedPath}
|
|
180
221
|
draftPaths=${draftPaths}
|
|
222
|
+
isSearchActive=${isSearchActive}
|
|
223
|
+
searchActivePath=${searchActivePath}
|
|
181
224
|
/>
|
|
182
225
|
`,
|
|
183
226
|
)}
|
|
@@ -186,7 +229,11 @@ const TreeNode = ({
|
|
|
186
229
|
`;
|
|
187
230
|
};
|
|
188
231
|
|
|
189
|
-
export const FileTree = ({
|
|
232
|
+
export const FileTree = ({
|
|
233
|
+
onSelectFile = () => {},
|
|
234
|
+
selectedPath = "",
|
|
235
|
+
onPreviewFile = () => {},
|
|
236
|
+
}) => {
|
|
190
237
|
const [treeRoot, setTreeRoot] = useState(null);
|
|
191
238
|
const [loading, setLoading] = useState(true);
|
|
192
239
|
const [error, setError] = useState("");
|
|
@@ -194,38 +241,75 @@ export const FileTree = ({ onSelectFile = () => {}, selectedPath = "" }) => {
|
|
|
194
241
|
readStoredCollapsedPaths,
|
|
195
242
|
);
|
|
196
243
|
const [draftPaths, setDraftPaths] = useState(readStoredDraftPaths);
|
|
244
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
245
|
+
const [searchActivePath, setSearchActivePath] = useState("");
|
|
246
|
+
const searchInputRef = useRef(null);
|
|
247
|
+
const treeSignatureRef = useRef("");
|
|
197
248
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
const loadTree = useCallback(async ({ showLoading = false } = {}) => {
|
|
250
|
+
if (showLoading) setLoading(true);
|
|
251
|
+
if (showLoading) setError("");
|
|
252
|
+
try {
|
|
253
|
+
const data = await fetchBrowseTree();
|
|
254
|
+
const nextRoot = data.root || null;
|
|
255
|
+
const nextSignature = JSON.stringify(nextRoot || {});
|
|
256
|
+
if (treeSignatureRef.current !== nextSignature) {
|
|
257
|
+
treeSignatureRef.current = nextSignature;
|
|
258
|
+
setTreeRoot(nextRoot);
|
|
259
|
+
}
|
|
260
|
+
setCollapsedPaths((previousPaths) => {
|
|
261
|
+
if (previousPaths instanceof Set) return previousPaths;
|
|
262
|
+
const nextPaths = new Set();
|
|
263
|
+
collectFolderPaths(nextRoot, nextPaths);
|
|
264
|
+
return nextPaths;
|
|
265
|
+
});
|
|
266
|
+
if (showLoading) setError("");
|
|
267
|
+
} catch (loadError) {
|
|
268
|
+
if (showLoading) {
|
|
215
269
|
setError(loadError.message || "Could not load file tree");
|
|
216
|
-
} finally {
|
|
217
|
-
if (active) setLoading(false);
|
|
218
270
|
}
|
|
271
|
+
} finally {
|
|
272
|
+
if (showLoading) setLoading(false);
|
|
273
|
+
}
|
|
274
|
+
}, []);
|
|
275
|
+
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
loadTree({ showLoading: true });
|
|
278
|
+
}, [loadTree]);
|
|
279
|
+
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
const refreshTree = () => {
|
|
282
|
+
loadTree({ showLoading: false });
|
|
219
283
|
};
|
|
220
|
-
|
|
284
|
+
const refreshInterval = window.setInterval(
|
|
285
|
+
refreshTree,
|
|
286
|
+
kTreeRefreshIntervalMs,
|
|
287
|
+
);
|
|
288
|
+
window.addEventListener("alphaclaw:browse-file-saved", refreshTree);
|
|
289
|
+
window.addEventListener("alphaclaw:browse-tree-refresh", refreshTree);
|
|
221
290
|
return () => {
|
|
222
|
-
|
|
291
|
+
window.clearInterval(refreshInterval);
|
|
292
|
+
window.removeEventListener("alphaclaw:browse-file-saved", refreshTree);
|
|
293
|
+
window.removeEventListener("alphaclaw:browse-tree-refresh", refreshTree);
|
|
223
294
|
};
|
|
224
|
-
}, []);
|
|
295
|
+
}, [loadTree]);
|
|
225
296
|
|
|
226
|
-
const
|
|
297
|
+
const normalizedSearchQuery = String(searchQuery || "").trim().toLowerCase();
|
|
298
|
+
const rootChildren = useMemo(() => {
|
|
299
|
+
const children = treeRoot?.children || [];
|
|
300
|
+
if (!normalizedSearchQuery) return children;
|
|
301
|
+
return children
|
|
302
|
+
.map((node) => filterTreeNode(node, normalizedSearchQuery))
|
|
303
|
+
.filter(Boolean);
|
|
304
|
+
}, [treeRoot, normalizedSearchQuery]);
|
|
227
305
|
const safeCollapsedPaths =
|
|
228
306
|
collapsedPaths instanceof Set ? collapsedPaths : new Set();
|
|
307
|
+
const isSearchActive = normalizedSearchQuery.length > 0;
|
|
308
|
+
const filteredFilePaths = useMemo(() => {
|
|
309
|
+
const filePaths = [];
|
|
310
|
+
rootChildren.forEach((node) => collectFilePaths(node, filePaths));
|
|
311
|
+
return filePaths;
|
|
312
|
+
}, [rootChildren]);
|
|
229
313
|
|
|
230
314
|
useEffect(() => {
|
|
231
315
|
if (!(collapsedPaths instanceof Set)) return;
|
|
@@ -278,6 +362,39 @@ export const FileTree = ({ onSelectFile = () => {}, selectedPath = "" }) => {
|
|
|
278
362
|
};
|
|
279
363
|
}, []);
|
|
280
364
|
|
|
365
|
+
useEffect(() => {
|
|
366
|
+
const handleGlobalSearchShortcut = (event) => {
|
|
367
|
+
if (event.key !== "/") return;
|
|
368
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
369
|
+
const target = event.target;
|
|
370
|
+
const tagName = String(target?.tagName || "").toLowerCase();
|
|
371
|
+
const isTypingTarget =
|
|
372
|
+
tagName === "input" ||
|
|
373
|
+
tagName === "textarea" ||
|
|
374
|
+
tagName === "select" ||
|
|
375
|
+
target?.isContentEditable;
|
|
376
|
+
if (isTypingTarget && target !== searchInputRef.current) return;
|
|
377
|
+
event.preventDefault();
|
|
378
|
+
searchInputRef.current?.focus();
|
|
379
|
+
searchInputRef.current?.select();
|
|
380
|
+
};
|
|
381
|
+
window.addEventListener("keydown", handleGlobalSearchShortcut);
|
|
382
|
+
return () => {
|
|
383
|
+
window.removeEventListener("keydown", handleGlobalSearchShortcut);
|
|
384
|
+
};
|
|
385
|
+
}, []);
|
|
386
|
+
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (!isSearchActive) {
|
|
389
|
+
setSearchActivePath("");
|
|
390
|
+
onPreviewFile("");
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (searchActivePath && filteredFilePaths.includes(searchActivePath)) return;
|
|
394
|
+
setSearchActivePath("");
|
|
395
|
+
onPreviewFile("");
|
|
396
|
+
}, [isSearchActive, filteredFilePaths, searchActivePath, onPreviewFile]);
|
|
397
|
+
|
|
281
398
|
const toggleFolder = (folderPath) => {
|
|
282
399
|
setCollapsedPaths((previousPaths) => {
|
|
283
400
|
const nextPaths =
|
|
@@ -288,6 +405,58 @@ export const FileTree = ({ onSelectFile = () => {}, selectedPath = "" }) => {
|
|
|
288
405
|
});
|
|
289
406
|
};
|
|
290
407
|
|
|
408
|
+
const updateSearchQuery = (nextQuery) => {
|
|
409
|
+
setSearchQuery(nextQuery);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const clearSearch = () => {
|
|
413
|
+
setSearchQuery("");
|
|
414
|
+
setSearchActivePath("");
|
|
415
|
+
onPreviewFile("");
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const moveSearchSelection = (direction) => {
|
|
419
|
+
if (!filteredFilePaths.length) return;
|
|
420
|
+
const currentIndex = filteredFilePaths.indexOf(searchActivePath);
|
|
421
|
+
const delta = direction === "up" ? -1 : 1;
|
|
422
|
+
const baseIndex = currentIndex === -1 ? (direction === "up" ? 0 : -1) : currentIndex;
|
|
423
|
+
const nextIndex =
|
|
424
|
+
(baseIndex + delta + filteredFilePaths.length) % filteredFilePaths.length;
|
|
425
|
+
const nextPath = filteredFilePaths[nextIndex];
|
|
426
|
+
setSearchActivePath(nextPath);
|
|
427
|
+
onPreviewFile(nextPath);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const commitSearchSelection = () => {
|
|
431
|
+
const [singlePath = ""] = filteredFilePaths;
|
|
432
|
+
const targetPath = searchActivePath || (filteredFilePaths.length === 1 ? singlePath : "");
|
|
433
|
+
if (!targetPath) return;
|
|
434
|
+
onSelectFile(targetPath);
|
|
435
|
+
clearSearch();
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const onSearchKeyDown = (event) => {
|
|
439
|
+
if (event.key === "ArrowDown") {
|
|
440
|
+
event.preventDefault();
|
|
441
|
+
moveSearchSelection("down");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (event.key === "ArrowUp") {
|
|
445
|
+
event.preventDefault();
|
|
446
|
+
moveSearchSelection("up");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (event.key === "Enter") {
|
|
450
|
+
event.preventDefault();
|
|
451
|
+
commitSearchSelection();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (event.key === "Escape") {
|
|
455
|
+
event.preventDefault();
|
|
456
|
+
clearSearch();
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
291
460
|
if (loading) {
|
|
292
461
|
return html`<div class="file-tree-state">Loading files...</div>`;
|
|
293
462
|
}
|
|
@@ -297,11 +466,43 @@ export const FileTree = ({ onSelectFile = () => {}, selectedPath = "" }) => {
|
|
|
297
466
|
</div>`;
|
|
298
467
|
}
|
|
299
468
|
if (!rootChildren.length) {
|
|
300
|
-
return html
|
|
469
|
+
return html`
|
|
470
|
+
<div class="file-tree-wrap">
|
|
471
|
+
<div class="file-tree-search">
|
|
472
|
+
<input
|
|
473
|
+
class="file-tree-search-input"
|
|
474
|
+
type="text"
|
|
475
|
+
ref=${searchInputRef}
|
|
476
|
+
value=${searchQuery}
|
|
477
|
+
onInput=${(event) => updateSearchQuery(event.target.value)}
|
|
478
|
+
onKeyDown=${onSearchKeyDown}
|
|
479
|
+
placeholder="Search files..."
|
|
480
|
+
autocomplete="off"
|
|
481
|
+
spellcheck=${false}
|
|
482
|
+
/>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="file-tree-state">
|
|
485
|
+
${isSearchActive ? "No matching files." : "No files found."}
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
`;
|
|
301
489
|
}
|
|
302
490
|
|
|
303
491
|
return html`
|
|
304
492
|
<div class="file-tree-wrap">
|
|
493
|
+
<div class="file-tree-search">
|
|
494
|
+
<input
|
|
495
|
+
class="file-tree-search-input"
|
|
496
|
+
type="text"
|
|
497
|
+
ref=${searchInputRef}
|
|
498
|
+
value=${searchQuery}
|
|
499
|
+
onInput=${(event) => updateSearchQuery(event.target.value)}
|
|
500
|
+
onKeyDown=${onSearchKeyDown}
|
|
501
|
+
placeholder="Search files..."
|
|
502
|
+
autocomplete="off"
|
|
503
|
+
spellcheck=${false}
|
|
504
|
+
/>
|
|
505
|
+
</div>
|
|
305
506
|
<ul class="file-tree">
|
|
306
507
|
${rootChildren.map(
|
|
307
508
|
(node) => html`
|
|
@@ -313,6 +514,8 @@ export const FileTree = ({ onSelectFile = () => {}, selectedPath = "" }) => {
|
|
|
313
514
|
onSelectFile=${onSelectFile}
|
|
314
515
|
selectedPath=${selectedPath}
|
|
315
516
|
draftPaths=${draftPaths}
|
|
517
|
+
isSearchActive=${isSearchActive}
|
|
518
|
+
searchActivePath=${searchActivePath}
|
|
316
519
|
/>
|
|
317
520
|
`,
|
|
318
521
|
)}
|