@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
|
@@ -22,13 +22,18 @@ import {
|
|
|
22
22
|
writeStoredFileDraft,
|
|
23
23
|
} from "../lib/browse-draft-state.js";
|
|
24
24
|
import { ActionButton } from "./action-button.js";
|
|
25
|
+
import { LoadingSpinner } from "./loading-spinner.js";
|
|
26
|
+
import { SegmentedControl } from "./segmented-control.js";
|
|
25
27
|
import { SaveFillIcon } from "./icons.js";
|
|
26
28
|
import { showToast } from "./toast.js";
|
|
27
29
|
|
|
28
30
|
const html = htm.bind(h);
|
|
29
31
|
const kFileViewerModeStorageKey = "alphaclaw.browse.fileViewerMode";
|
|
30
32
|
const kLegacyFileViewerModeStorageKey = "alphaclawBrowseFileViewerMode";
|
|
33
|
+
const kEditorSelectionStorageKey = "alphaclaw.browse.editorSelectionByPath";
|
|
31
34
|
const kProtectedBrowsePaths = new Set(["openclaw.json", "devices/paired.json"]);
|
|
35
|
+
const kLoadingIndicatorDelayMs = 1000;
|
|
36
|
+
const kFileRefreshIntervalMs = 5000;
|
|
32
37
|
|
|
33
38
|
const parsePathSegments = (inputPath) =>
|
|
34
39
|
String(inputPath || "")
|
|
@@ -57,18 +62,70 @@ const readStoredFileViewerMode = () => {
|
|
|
57
62
|
}
|
|
58
63
|
};
|
|
59
64
|
|
|
65
|
+
const clampSelectionIndex = (value, maxValue) => {
|
|
66
|
+
const numericValue = Number.parseInt(String(value ?? ""), 10);
|
|
67
|
+
if (!Number.isFinite(numericValue)) return 0;
|
|
68
|
+
return Math.max(0, Math.min(maxValue, numericValue));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const readStoredEditorSelection = (filePath) => {
|
|
72
|
+
const safePath = String(filePath || "").trim();
|
|
73
|
+
if (!safePath) return null;
|
|
74
|
+
try {
|
|
75
|
+
const rawStorageValue = window.localStorage.getItem(kEditorSelectionStorageKey);
|
|
76
|
+
if (!rawStorageValue) return null;
|
|
77
|
+
const parsedStorageValue = JSON.parse(rawStorageValue);
|
|
78
|
+
if (!parsedStorageValue || typeof parsedStorageValue !== "object") return null;
|
|
79
|
+
const selection = parsedStorageValue[safePath];
|
|
80
|
+
if (!selection || typeof selection !== "object") return null;
|
|
81
|
+
return {
|
|
82
|
+
start: selection.start,
|
|
83
|
+
end: selection.end,
|
|
84
|
+
};
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
60
89
|
|
|
61
|
-
|
|
90
|
+
const writeStoredEditorSelection = (filePath, selection) => {
|
|
91
|
+
const safePath = String(filePath || "").trim();
|
|
92
|
+
if (!safePath || !selection || typeof selection !== "object") return;
|
|
93
|
+
try {
|
|
94
|
+
const rawStorageValue = window.localStorage.getItem(kEditorSelectionStorageKey);
|
|
95
|
+
const parsedStorageValue = rawStorageValue ? JSON.parse(rawStorageValue) : {};
|
|
96
|
+
const nextStorageValue =
|
|
97
|
+
parsedStorageValue && typeof parsedStorageValue === "object"
|
|
98
|
+
? parsedStorageValue
|
|
99
|
+
: {};
|
|
100
|
+
nextStorageValue[safePath] = {
|
|
101
|
+
start: selection.start,
|
|
102
|
+
end: selection.end,
|
|
103
|
+
};
|
|
104
|
+
window.localStorage.setItem(
|
|
105
|
+
kEditorSelectionStorageKey,
|
|
106
|
+
JSON.stringify(nextStorageValue),
|
|
107
|
+
);
|
|
108
|
+
} catch {}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
export const FileViewer = ({
|
|
113
|
+
filePath = "",
|
|
114
|
+
isPreviewOnly = false,
|
|
115
|
+
}) => {
|
|
62
116
|
const normalizedPath = String(filePath || "").trim();
|
|
63
117
|
const normalizedPolicyPath = normalizePolicyPath(normalizedPath);
|
|
64
118
|
const [content, setContent] = useState("");
|
|
65
119
|
const [initialContent, setInitialContent] = useState("");
|
|
66
120
|
const [viewMode, setViewMode] = useState(readStoredFileViewerMode);
|
|
67
121
|
const [loading, setLoading] = useState(false);
|
|
122
|
+
const [showDelayedLoadingSpinner, setShowDelayedLoadingSpinner] =
|
|
123
|
+
useState(false);
|
|
68
124
|
const [saving, setSaving] = useState(false);
|
|
69
125
|
const [error, setError] = useState("");
|
|
70
126
|
const [isFolderPath, setIsFolderPath] = useState(false);
|
|
71
127
|
const [frontmatterCollapsed, setFrontmatterCollapsed] = useState(false);
|
|
128
|
+
const [externalChangeNoticeShown, setExternalChangeNoticeShown] = useState(false);
|
|
72
129
|
const [protectedEditBypassPaths, setProtectedEditBypassPaths] = useState(
|
|
73
130
|
() => new Set(),
|
|
74
131
|
);
|
|
@@ -79,6 +136,8 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
79
136
|
const viewScrollRatioRef = useRef(0);
|
|
80
137
|
const isSyncingScrollRef = useRef(false);
|
|
81
138
|
const loadedFilePathRef = useRef("");
|
|
139
|
+
const restoredSelectionPathRef = useRef("");
|
|
140
|
+
const fileRefreshInFlightRef = useRef(false);
|
|
82
141
|
const editorLineNumberRowRefs = useRef([]);
|
|
83
142
|
const editorHighlightLineRefs = useRef([]);
|
|
84
143
|
|
|
@@ -87,7 +146,7 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
87
146
|
[normalizedPath],
|
|
88
147
|
);
|
|
89
148
|
const hasSelectedPath = normalizedPath.length > 0;
|
|
90
|
-
const canEditFile = hasSelectedPath && !isFolderPath;
|
|
149
|
+
const canEditFile = hasSelectedPath && !isFolderPath && !isPreviewOnly;
|
|
91
150
|
const isDirty = canEditFile && content !== initialContent;
|
|
92
151
|
const isProtectedFile =
|
|
93
152
|
canEditFile && kProtectedBrowsePaths.has(normalizedPolicyPath);
|
|
@@ -164,9 +223,21 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
164
223
|
} catch {}
|
|
165
224
|
}, [viewMode]);
|
|
166
225
|
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (!loading) {
|
|
228
|
+
setShowDelayedLoadingSpinner(false);
|
|
229
|
+
return () => {};
|
|
230
|
+
}
|
|
231
|
+
const timer = window.setTimeout(() => {
|
|
232
|
+
setShowDelayedLoadingSpinner(true);
|
|
233
|
+
}, kLoadingIndicatorDelayMs);
|
|
234
|
+
return () => window.clearTimeout(timer);
|
|
235
|
+
}, [loading]);
|
|
236
|
+
|
|
167
237
|
useEffect(() => {
|
|
168
238
|
let active = true;
|
|
169
239
|
loadedFilePathRef.current = "";
|
|
240
|
+
restoredSelectionPathRef.current = "";
|
|
170
241
|
if (!hasSelectedPath) {
|
|
171
242
|
setContent("");
|
|
172
243
|
setInitialContent("");
|
|
@@ -195,8 +266,10 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
195
266
|
{ dispatchEvent: (event) => window.dispatchEvent(event) },
|
|
196
267
|
);
|
|
197
268
|
setInitialContent(nextContent);
|
|
269
|
+
setExternalChangeNoticeShown(false);
|
|
198
270
|
viewScrollRatioRef.current = 0;
|
|
199
271
|
loadedFilePathRef.current = normalizedPath;
|
|
272
|
+
restoredSelectionPathRef.current = "";
|
|
200
273
|
} catch (loadError) {
|
|
201
274
|
if (!active) return;
|
|
202
275
|
const message = loadError.message || "Could not load file";
|
|
@@ -206,6 +279,7 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
206
279
|
setIsFolderPath(true);
|
|
207
280
|
setError("");
|
|
208
281
|
loadedFilePathRef.current = normalizedPath;
|
|
282
|
+
restoredSelectionPathRef.current = "";
|
|
209
283
|
return;
|
|
210
284
|
}
|
|
211
285
|
setError(message);
|
|
@@ -219,6 +293,60 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
219
293
|
};
|
|
220
294
|
}, [hasSelectedPath, normalizedPath]);
|
|
221
295
|
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
if (!hasSelectedPath || isFolderPath || !canEditFile) return () => {};
|
|
298
|
+
const refreshFromDisk = async () => {
|
|
299
|
+
if (loading || saving) return;
|
|
300
|
+
if (fileRefreshInFlightRef.current) return;
|
|
301
|
+
fileRefreshInFlightRef.current = true;
|
|
302
|
+
try {
|
|
303
|
+
const data = await fetchFileContent(normalizedPath);
|
|
304
|
+
const diskContent = data.content || "";
|
|
305
|
+
if (diskContent === initialContent) {
|
|
306
|
+
setExternalChangeNoticeShown(false);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// Auto-refresh only when editor has no unsaved work.
|
|
310
|
+
if (!isDirty) {
|
|
311
|
+
setContent(diskContent);
|
|
312
|
+
setInitialContent(diskContent);
|
|
313
|
+
clearStoredFileDraft(normalizedPath);
|
|
314
|
+
updateDraftIndex(normalizedPath, false, {
|
|
315
|
+
dispatchEvent: (event) => window.dispatchEvent(event),
|
|
316
|
+
});
|
|
317
|
+
setExternalChangeNoticeShown(false);
|
|
318
|
+
window.dispatchEvent(new CustomEvent("alphaclaw:browse-tree-refresh"));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (!externalChangeNoticeShown) {
|
|
322
|
+
showToast(
|
|
323
|
+
"This file changed on disk. Save to overwrite or reload by re-opening.",
|
|
324
|
+
"error",
|
|
325
|
+
);
|
|
326
|
+
setExternalChangeNoticeShown(true);
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
// Ignore transient refresh errors to avoid interrupting editing.
|
|
330
|
+
} finally {
|
|
331
|
+
fileRefreshInFlightRef.current = false;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const intervalId = window.setInterval(refreshFromDisk, kFileRefreshIntervalMs);
|
|
335
|
+
return () => {
|
|
336
|
+
window.clearInterval(intervalId);
|
|
337
|
+
};
|
|
338
|
+
}, [
|
|
339
|
+
hasSelectedPath,
|
|
340
|
+
isFolderPath,
|
|
341
|
+
canEditFile,
|
|
342
|
+
loading,
|
|
343
|
+
saving,
|
|
344
|
+
normalizedPath,
|
|
345
|
+
initialContent,
|
|
346
|
+
isDirty,
|
|
347
|
+
externalChangeNoticeShown,
|
|
348
|
+
]);
|
|
349
|
+
|
|
222
350
|
useEffect(() => {
|
|
223
351
|
if (loadedFilePathRef.current !== normalizedPath) return;
|
|
224
352
|
if (!canEditFile || !hasSelectedPath || loading) return;
|
|
@@ -242,6 +370,21 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
242
370
|
normalizedPath,
|
|
243
371
|
]);
|
|
244
372
|
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
if (!canEditFile || loading || !hasSelectedPath) return;
|
|
375
|
+
if (loadedFilePathRef.current !== normalizedPath) return;
|
|
376
|
+
if (restoredSelectionPathRef.current === normalizedPath) return;
|
|
377
|
+
const textareaElement = editorTextareaRef.current;
|
|
378
|
+
if (!textareaElement) return;
|
|
379
|
+
const storedSelection = readStoredEditorSelection(normalizedPath);
|
|
380
|
+
restoredSelectionPathRef.current = normalizedPath;
|
|
381
|
+
if (!storedSelection) return;
|
|
382
|
+
const maxIndex = String(content || "").length;
|
|
383
|
+
const start = clampSelectionIndex(storedSelection.start, maxIndex);
|
|
384
|
+
const end = clampSelectionIndex(storedSelection.end, maxIndex);
|
|
385
|
+
textareaElement.setSelectionRange(start, Math.max(start, end));
|
|
386
|
+
}, [canEditFile, loading, hasSelectedPath, normalizedPath, content]);
|
|
387
|
+
|
|
245
388
|
const handleSave = async () => {
|
|
246
389
|
if (!canEditFile || saving || !isDirty || isProtectedLocked) return;
|
|
247
390
|
setSaving(true);
|
|
@@ -249,6 +392,7 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
249
392
|
try {
|
|
250
393
|
const data = await saveFileContent(normalizedPath, content);
|
|
251
394
|
setInitialContent(content);
|
|
395
|
+
setExternalChangeNoticeShown(false);
|
|
252
396
|
clearStoredFileDraft(normalizedPath);
|
|
253
397
|
updateDraftIndex(normalizedPath, false, {
|
|
254
398
|
dispatchEvent: (event) => window.dispatchEvent(event),
|
|
@@ -282,9 +426,15 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
282
426
|
};
|
|
283
427
|
|
|
284
428
|
const handleContentInput = (event) => {
|
|
285
|
-
if (isProtectedLocked) return;
|
|
429
|
+
if (isProtectedLocked || isPreviewOnly) return;
|
|
286
430
|
const nextContent = event.target.value;
|
|
287
431
|
setContent(nextContent);
|
|
432
|
+
if (hasSelectedPath && canEditFile) {
|
|
433
|
+
writeStoredEditorSelection(normalizedPath, {
|
|
434
|
+
start: event.target.selectionStart,
|
|
435
|
+
end: event.target.selectionEnd,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
288
438
|
if (hasSelectedPath && canEditFile) {
|
|
289
439
|
writeStoredFileDraft(normalizedPath, nextContent);
|
|
290
440
|
updateDraftIndex(normalizedPath, nextContent !== initialContent, {
|
|
@@ -293,6 +443,16 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
293
443
|
}
|
|
294
444
|
};
|
|
295
445
|
|
|
446
|
+
const handleEditorSelectionChange = () => {
|
|
447
|
+
if (!hasSelectedPath || !canEditFile || loading) return;
|
|
448
|
+
const textareaElement = editorTextareaRef.current;
|
|
449
|
+
if (!textareaElement) return;
|
|
450
|
+
writeStoredEditorSelection(normalizedPath, {
|
|
451
|
+
start: textareaElement.selectionStart,
|
|
452
|
+
end: textareaElement.selectionEnd,
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
|
|
296
456
|
const getScrollRatio = (element) => {
|
|
297
457
|
if (!element) return 0;
|
|
298
458
|
const maxScrollTop = element.scrollHeight - element.clientHeight;
|
|
@@ -408,22 +568,20 @@ export const FileViewer = ({ filePath = "" }) => {
|
|
|
408
568
|
: null}
|
|
409
569
|
</div>
|
|
410
570
|
<div class="file-viewer-tabbar-spacer"></div>
|
|
571
|
+
${isPreviewOnly
|
|
572
|
+
? html`<div class="file-viewer-preview-pill">Preview</div>`
|
|
573
|
+
: null}
|
|
411
574
|
${isMarkdownFile &&
|
|
412
575
|
html`
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
onclick=${() => handleChangeViewMode("preview")}
|
|
423
|
-
>
|
|
424
|
-
preview
|
|
425
|
-
</button>
|
|
426
|
-
</div>
|
|
576
|
+
<${SegmentedControl}
|
|
577
|
+
className="mr-2.5"
|
|
578
|
+
options=${[
|
|
579
|
+
{ label: "edit", value: "edit" },
|
|
580
|
+
{ label: "preview", value: "preview" },
|
|
581
|
+
]}
|
|
582
|
+
value=${viewMode}
|
|
583
|
+
onChange=${handleChangeViewMode}
|
|
584
|
+
/>
|
|
427
585
|
`}
|
|
428
586
|
<${ActionButton}
|
|
429
587
|
onClick=${handleSave}
|
|
@@ -508,7 +666,13 @@ ${formattedValue}</pre
|
|
|
508
666
|
`
|
|
509
667
|
: null}
|
|
510
668
|
${loading
|
|
511
|
-
? html
|
|
669
|
+
? html`
|
|
670
|
+
<div class="file-viewer-loading-shell">
|
|
671
|
+
${showDelayedLoadingSpinner
|
|
672
|
+
? html`<${LoadingSpinner} className="h-4 w-4" />`
|
|
673
|
+
: null}
|
|
674
|
+
</div>
|
|
675
|
+
`
|
|
512
676
|
: error
|
|
513
677
|
? html`<div class="file-viewer-state file-viewer-state-error">
|
|
514
678
|
${error}
|
|
@@ -585,6 +749,9 @@ ${formattedValue}</pre
|
|
|
585
749
|
value=${content}
|
|
586
750
|
onInput=${handleContentInput}
|
|
587
751
|
onScroll=${handleEditorScroll}
|
|
752
|
+
onSelect=${handleEditorSelectionChange}
|
|
753
|
+
onKeyUp=${handleEditorSelectionChange}
|
|
754
|
+
onClick=${handleEditorSelectionChange}
|
|
588
755
|
spellcheck=${false}
|
|
589
756
|
autocorrect="off"
|
|
590
757
|
autocapitalize="off"
|
|
@@ -653,7 +820,10 @@ ${formattedValue}</pre
|
|
|
653
820
|
value=${content}
|
|
654
821
|
onInput=${handleContentInput}
|
|
655
822
|
onScroll=${handleEditorScroll}
|
|
656
|
-
|
|
823
|
+
onSelect=${handleEditorSelectionChange}
|
|
824
|
+
onKeyUp=${handleEditorSelectionChange}
|
|
825
|
+
onClick=${handleEditorSelectionChange}
|
|
826
|
+
readonly=${isProtectedLocked || isPreviewOnly}
|
|
657
827
|
spellcheck=${false}
|
|
658
828
|
autocorrect="off"
|
|
659
829
|
autocapitalize="off"
|
|
@@ -672,7 +842,10 @@ ${formattedValue}</pre
|
|
|
672
842
|
value=${content}
|
|
673
843
|
onInput=${handleContentInput}
|
|
674
844
|
onScroll=${handleEditorScroll}
|
|
675
|
-
|
|
845
|
+
onSelect=${handleEditorSelectionChange}
|
|
846
|
+
onKeyUp=${handleEditorSelectionChange}
|
|
847
|
+
onClick=${handleEditorSelectionChange}
|
|
848
|
+
readonly=${isProtectedLocked || isPreviewOnly}
|
|
676
849
|
spellcheck=${false}
|
|
677
850
|
autocorrect="off"
|
|
678
851
|
autocapitalize="off"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
|
|
4
|
+
const html = htm.bind(h);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reusable segmented control (pill toggle).
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} props
|
|
10
|
+
* @param {Array<{label:string, value:*}>} props.options
|
|
11
|
+
* @param {*} props.value Currently selected value.
|
|
12
|
+
* @param {Function} props.onChange Called with the new value on click.
|
|
13
|
+
* @param {string} [props.className] Extra classes on the wrapper.
|
|
14
|
+
*/
|
|
15
|
+
export const SegmentedControl = ({
|
|
16
|
+
options = [],
|
|
17
|
+
value,
|
|
18
|
+
onChange = () => {},
|
|
19
|
+
className = "",
|
|
20
|
+
}) => html`
|
|
21
|
+
<div class=${`ac-segmented-control ${className}`}>
|
|
22
|
+
${options.map(
|
|
23
|
+
(option) => html`
|
|
24
|
+
<button
|
|
25
|
+
class=${`ac-segmented-control-button ${option.value === value ? "active" : ""}`}
|
|
26
|
+
onclick=${() => onChange(option.value)}
|
|
27
|
+
>
|
|
28
|
+
${option.label}
|
|
29
|
+
</button>
|
|
30
|
+
`,
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
`;
|
|
@@ -5,37 +5,26 @@ import { HomeLineIcon, FolderLineIcon } from "./icons.js";
|
|
|
5
5
|
import { FileTree } from "./file-tree.js";
|
|
6
6
|
import { UpdateActionButton } from "./update-action-button.js";
|
|
7
7
|
import { SidebarGitPanel } from "./sidebar-git-panel.js";
|
|
8
|
+
import { readUiSettings, writeUiSettings } from "../lib/ui-settings.js";
|
|
8
9
|
|
|
9
10
|
const html = htm.bind(h);
|
|
10
|
-
const kUiSettingsStorageKey = "alphaclaw.uiSettings";
|
|
11
|
-
const kLegacyUiSettingsStorageKey = "alphaclawUiSettings";
|
|
12
11
|
const kBrowseBottomPanelUiSettingKey = "browseBottomPanelHeightPx";
|
|
13
12
|
const kBrowsePanelMinHeightPx = 120;
|
|
14
13
|
const kBrowseBottomMinHeightPx = 120;
|
|
15
14
|
const kBrowseResizerHeightPx = 6;
|
|
16
15
|
const kDefaultBrowseBottomPanelHeightPx = 160;
|
|
17
|
-
const kLegacyBrowsePanelUiStorageKey = "alphaclawBrowsePanelHeightPx";
|
|
18
16
|
|
|
19
17
|
const readStoredBrowseBottomPanelHeight = () => {
|
|
20
18
|
try {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
10,
|
|
29
|
-
);
|
|
30
|
-
if (Number.isFinite(fromSharedSettings) && fromSharedSettings > 0) {
|
|
31
|
-
return fromSharedSettings;
|
|
32
|
-
}
|
|
19
|
+
const settings = readUiSettings();
|
|
20
|
+
const fromSharedSettings = Number.parseInt(
|
|
21
|
+
String(settings?.[kBrowseBottomPanelUiSettingKey] || ""),
|
|
22
|
+
10,
|
|
23
|
+
);
|
|
24
|
+
if (Number.isFinite(fromSharedSettings) && fromSharedSettings > 0) {
|
|
25
|
+
return fromSharedSettings;
|
|
33
26
|
}
|
|
34
|
-
|
|
35
|
-
const parsedValue = Number.parseInt(String(legacyRawValue || ""), 10);
|
|
36
|
-
return Number.isFinite(parsedValue) && parsedValue > 0
|
|
37
|
-
? parsedValue
|
|
38
|
-
: kDefaultBrowseBottomPanelHeightPx;
|
|
27
|
+
return kDefaultBrowseBottomPanelHeightPx;
|
|
39
28
|
} catch {
|
|
40
29
|
return kDefaultBrowseBottomPanelHeightPx;
|
|
41
30
|
}
|
|
@@ -55,6 +44,7 @@ export const AppSidebar = ({
|
|
|
55
44
|
onSelectNavItem = () => {},
|
|
56
45
|
selectedBrowsePath = "",
|
|
57
46
|
onSelectBrowseFile = () => {},
|
|
47
|
+
onPreviewBrowseFile = () => {},
|
|
58
48
|
acHasUpdate = false,
|
|
59
49
|
acLatest = "",
|
|
60
50
|
acDismissed = false,
|
|
@@ -70,18 +60,9 @@ export const AppSidebar = ({
|
|
|
70
60
|
const [isResizingBrowsePanels, setIsResizingBrowsePanels] = useState(false);
|
|
71
61
|
|
|
72
62
|
useEffect(() => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
window.localStorage.getItem(kLegacyUiSettingsStorageKey);
|
|
77
|
-
const parsedSettings = rawSettings ? JSON.parse(rawSettings) : {};
|
|
78
|
-
const nextSettings =
|
|
79
|
-
parsedSettings && typeof parsedSettings === "object"
|
|
80
|
-
? { ...parsedSettings }
|
|
81
|
-
: {};
|
|
82
|
-
nextSettings[kBrowseBottomPanelUiSettingKey] = browseBottomPanelHeightPx;
|
|
83
|
-
window.localStorage.setItem(kUiSettingsStorageKey, JSON.stringify(nextSettings));
|
|
84
|
-
} catch {}
|
|
63
|
+
const settings = readUiSettings();
|
|
64
|
+
settings[kBrowseBottomPanelUiSettingKey] = browseBottomPanelHeightPx;
|
|
65
|
+
writeUiSettings(settings);
|
|
85
66
|
}, [browseBottomPanelHeightPx]);
|
|
86
67
|
|
|
87
68
|
const getClampedBrowseBottomPanelHeight = (value) => {
|
|
@@ -217,6 +198,7 @@ export const AppSidebar = ({
|
|
|
217
198
|
<${FileTree}
|
|
218
199
|
onSelectFile=${onSelectBrowseFile}
|
|
219
200
|
selectedPath=${selectedBrowsePath}
|
|
201
|
+
onPreviewFile=${onPreviewBrowseFile}
|
|
220
202
|
/>
|
|
221
203
|
</div>
|
|
222
204
|
<div
|