@chrysb/alphaclaw 0.4.1-beta.0 → 0.4.1-beta.2
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/lib/public/css/explorer.css +156 -4
- package/lib/public/js/components/file-tree.js +533 -48
- package/lib/public/js/components/file-viewer/index.js +2 -8
- package/lib/public/js/components/google/index.js +83 -48
- package/lib/public/js/components/icons.js +26 -0
- package/lib/public/js/components/onboarding/use-welcome-codex.js +129 -0
- package/lib/public/js/components/onboarding/use-welcome-pairing.js +74 -0
- package/lib/public/js/components/onboarding/use-welcome-storage.js +60 -0
- package/lib/public/js/components/sidebar.js +1 -1
- package/lib/public/js/components/telegram-workspace/onboarding.js +1 -1
- package/lib/public/js/components/webhooks.js +2 -2
- package/lib/public/js/components/welcome.js +57 -210
- package/lib/public/js/lib/api.js +27 -0
- package/lib/public/js/lib/browse-file-policies.js +3 -1
- package/lib/public/shared/browse-file-policies.json +2 -1
- package/lib/server/constants.js +0 -1
- package/lib/server/gmail-serve.js +1 -1
- package/lib/server/gmail-watch.js +8 -28
- package/lib/server/gog-skill.js +169 -0
- package/lib/server/onboarding/openclaw.js +9 -1
- package/lib/server/routes/browse/index.js +123 -6
- package/lib/server/routes/browse/path-utils.js +3 -1
- package/lib/server/routes/google.js +4 -0
- package/lib/server/routes/webhooks.js +3 -5
- package/lib/server/webhooks.js +1 -1
- package/lib/server.js +2 -0
- package/lib/setup/skills/gog-cli/calendar.md +63 -0
- package/lib/setup/skills/gog-cli/contacts.md +30 -0
- package/lib/setup/skills/gog-cli/docs.md +46 -0
- package/lib/setup/skills/gog-cli/drive.md +48 -0
- package/lib/setup/skills/gog-cli/gmail.md +64 -0
- package/lib/setup/skills/gog-cli/meet.md +6 -0
- package/lib/setup/skills/gog-cli/sheets.md +43 -0
- package/lib/setup/skills/gog-cli/tasks.md +32 -0
- package/package.json +2 -2
|
@@ -32,7 +32,7 @@ export const FileViewer = ({
|
|
|
32
32
|
onRequestEdit,
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
if (!state.hasSelectedPath) {
|
|
35
|
+
if (!state.hasSelectedPath || state.isFolderPath) {
|
|
36
36
|
return html`
|
|
37
37
|
<div class="file-viewer-empty">
|
|
38
38
|
<div class="file-viewer-empty-mark">[ ]</div>
|
|
@@ -99,13 +99,7 @@ export const FileViewer = ({
|
|
|
99
99
|
`
|
|
100
100
|
: state.error
|
|
101
101
|
? html`<div class="file-viewer-state file-viewer-state-error">${state.error}</div>`
|
|
102
|
-
: state.
|
|
103
|
-
? html`
|
|
104
|
-
<div class="file-viewer-state">
|
|
105
|
-
Folder selected. Choose a file from this folder in the tree.
|
|
106
|
-
</div>
|
|
107
|
-
`
|
|
108
|
-
: state.isImageFile || state.isAudioFile
|
|
102
|
+
: state.isImageFile || state.isAudioFile
|
|
109
103
|
? html`
|
|
110
104
|
<${MediaPreview}
|
|
111
105
|
isImageFile=${state.isImageFile}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
} from "https://esm.sh/preact/hooks";
|
|
3
8
|
import htm from "https://esm.sh/htm";
|
|
4
9
|
import {
|
|
5
10
|
checkGoogleApis,
|
|
@@ -34,12 +39,8 @@ export const Google = ({
|
|
|
34
39
|
onRestartRequired = () => {},
|
|
35
40
|
onOpenGmailWebhook = () => {},
|
|
36
41
|
}) => {
|
|
37
|
-
const {
|
|
38
|
-
|
|
39
|
-
loading,
|
|
40
|
-
hasCompanyCredentials,
|
|
41
|
-
refreshAccounts,
|
|
42
|
-
} = useGoogleAccounts({ gatewayStatus });
|
|
42
|
+
const { accounts, loading, hasCompanyCredentials, refreshAccounts } =
|
|
43
|
+
useGoogleAccounts({ gatewayStatus });
|
|
43
44
|
const [expandedAccountId, setExpandedAccountId] = useState("");
|
|
44
45
|
const [scopesByAccountId, setScopesByAccountId] = useState({});
|
|
45
46
|
const [savedScopesByAccountId, setSavedScopesByAccountId] = useState({});
|
|
@@ -90,12 +91,16 @@ export const Google = ({
|
|
|
90
91
|
);
|
|
91
92
|
|
|
92
93
|
const ensureScopesForAccount = useCallback((account) => {
|
|
93
|
-
const nextScopes =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
const nextScopes =
|
|
95
|
+
Array.isArray(account.activeScopes) && account.activeScopes.length
|
|
96
|
+
? account.activeScopes
|
|
97
|
+
: Array.isArray(account.services) && account.services.length
|
|
98
|
+
? account.services
|
|
99
|
+
: getDefaultScopes();
|
|
100
|
+
setSavedScopesByAccountId((prev) => ({
|
|
101
|
+
...prev,
|
|
102
|
+
[account.id]: [...nextScopes],
|
|
103
|
+
}));
|
|
99
104
|
setScopesByAccountId((prev) => {
|
|
100
105
|
const current = prev[account.id];
|
|
101
106
|
if (!current || !hasScopesChanged(current, nextScopes)) {
|
|
@@ -125,7 +130,10 @@ export const Google = ({
|
|
|
125
130
|
(accountId) => {
|
|
126
131
|
const account = getAccountById(accountId);
|
|
127
132
|
if (!account) return;
|
|
128
|
-
const scopes =
|
|
133
|
+
const scopes =
|
|
134
|
+
scopesByAccountId[accountId] ||
|
|
135
|
+
account.activeScopes ||
|
|
136
|
+
getDefaultScopes();
|
|
129
137
|
if (!scopes.length) {
|
|
130
138
|
window.alert("Select at least one service");
|
|
131
139
|
return;
|
|
@@ -160,7 +168,10 @@ export const Google = ({
|
|
|
160
168
|
try {
|
|
161
169
|
const data = await checkGoogleApis(accountId);
|
|
162
170
|
if (data.results) {
|
|
163
|
-
setApiStatusByAccountId((prev) => ({
|
|
171
|
+
setApiStatusByAccountId((prev) => ({
|
|
172
|
+
...prev,
|
|
173
|
+
[accountId]: data.results,
|
|
174
|
+
}));
|
|
164
175
|
}
|
|
165
176
|
} finally {
|
|
166
177
|
setCheckingByAccountId((prev) => {
|
|
@@ -184,7 +195,10 @@ export const Google = ({
|
|
|
184
195
|
await handleCheckApis(accountId);
|
|
185
196
|
}
|
|
186
197
|
} else if (event.data?.google === "error") {
|
|
187
|
-
showToast(
|
|
198
|
+
showToast(
|
|
199
|
+
`✗ Google auth failed: ${event.data.message || "unknown"}`,
|
|
200
|
+
"error",
|
|
201
|
+
);
|
|
188
202
|
}
|
|
189
203
|
};
|
|
190
204
|
window.addEventListener("message", handler);
|
|
@@ -249,6 +263,9 @@ export const Google = ({
|
|
|
249
263
|
};
|
|
250
264
|
|
|
251
265
|
const handleCredentialsSaved = async (account) => {
|
|
266
|
+
if (account?.id) {
|
|
267
|
+
setExpandedAccountId(account.id);
|
|
268
|
+
}
|
|
252
269
|
await refreshAccounts();
|
|
253
270
|
if (account?.id) startAuth(account.id);
|
|
254
271
|
};
|
|
@@ -267,6 +284,9 @@ export const Google = ({
|
|
|
267
284
|
return;
|
|
268
285
|
}
|
|
269
286
|
setAddCompanyModalOpen(false);
|
|
287
|
+
if (data.accountId) {
|
|
288
|
+
setExpandedAccountId(data.accountId);
|
|
289
|
+
}
|
|
270
290
|
await refreshAccounts();
|
|
271
291
|
if (data.accountId) startAuth(data.accountId);
|
|
272
292
|
} finally {
|
|
@@ -304,7 +324,7 @@ export const Google = ({
|
|
|
304
324
|
const account = getAccountById(accountId);
|
|
305
325
|
if (!account) return;
|
|
306
326
|
const personal = isPersonalAccount(account);
|
|
307
|
-
const client = personal ? "personal" :
|
|
327
|
+
const client = personal ? "personal" : account.client || "default";
|
|
308
328
|
let credentialValues = {};
|
|
309
329
|
try {
|
|
310
330
|
const credentialResponse = await fetchGoogleCredentials({
|
|
@@ -390,22 +410,23 @@ export const Google = ({
|
|
|
390
410
|
};
|
|
391
411
|
|
|
392
412
|
const renderEmptyState = () => html`
|
|
393
|
-
<div class="text-center space-y-2
|
|
413
|
+
<div class="text-center space-y-2 pt-3">
|
|
394
414
|
<div class="rounded-lg border border-border bg-black/20 px-3 py-5">
|
|
395
|
-
<div class="flex flex-col items-center justify-center gap-
|
|
415
|
+
<div class="flex flex-col items-center justify-center gap-3">
|
|
396
416
|
<img
|
|
397
417
|
src=${kGoogleIconPath}
|
|
398
418
|
alt="Google logo"
|
|
399
|
-
class="h-
|
|
419
|
+
class="h-5 w-5 shrink-0"
|
|
400
420
|
loading="lazy"
|
|
401
421
|
decoding="async"
|
|
402
422
|
/>
|
|
403
423
|
<p class="text-xs text-gray-500">
|
|
404
|
-
Connect Gmail, Calendar, Contacts, Drive, Sheets, Tasks, Docs, and
|
|
424
|
+
Connect Gmail, Calendar, Contacts, Drive, Sheets, Tasks, Docs, and
|
|
425
|
+
Meet.
|
|
405
426
|
</p>
|
|
406
427
|
</div>
|
|
407
428
|
</div>
|
|
408
|
-
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
429
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 pt-2">
|
|
409
430
|
<${ActionButton}
|
|
410
431
|
onClick=${handleAddCompanyClick}
|
|
411
432
|
tone="primary"
|
|
@@ -469,34 +490,46 @@ export const Google = ({
|
|
|
469
490
|
`}
|
|
470
491
|
/>
|
|
471
492
|
${loading
|
|
472
|
-
? html`<div class="text-gray-500 text-sm text-center py-2">
|
|
493
|
+
? html`<div class="text-gray-500 text-sm text-center py-2">
|
|
494
|
+
Loading...
|
|
495
|
+
</div>`
|
|
473
496
|
: accounts.length
|
|
474
497
|
? html`
|
|
475
498
|
<div class="space-y-2 mt-3">
|
|
476
|
-
${accounts.map(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
499
|
+
${accounts.map(
|
|
500
|
+
(account) =>
|
|
501
|
+
html`<${GoogleAccountRow}
|
|
502
|
+
key=${account.id}
|
|
503
|
+
account=${account}
|
|
504
|
+
personal=${isPersonalAccount(account)}
|
|
505
|
+
expanded=${expandedAccountId === account.id}
|
|
506
|
+
onToggleExpanded=${(accountId) =>
|
|
507
|
+
setExpandedAccountId((prev) =>
|
|
508
|
+
prev === accountId ? "" : accountId,
|
|
509
|
+
)}
|
|
510
|
+
scopes=${scopesByAccountId[account.id] ||
|
|
511
|
+
account.activeScopes ||
|
|
512
|
+
getDefaultScopes()}
|
|
513
|
+
savedScopes=${savedScopesByAccountId[account.id] ||
|
|
514
|
+
account.activeScopes ||
|
|
515
|
+
getDefaultScopes()}
|
|
516
|
+
apiStatus=${apiStatusByAccountId[account.id] || {}}
|
|
517
|
+
checkingApis=${expandedAccountId === account.id &&
|
|
518
|
+
Boolean(checkingByAccountId[account.id])}
|
|
519
|
+
onToggleScope=${handleToggleScope}
|
|
520
|
+
onCheckApis=${handleCheckApis}
|
|
521
|
+
onUpdatePermissions=${(accountId) => startAuth(accountId)}
|
|
522
|
+
onEditCredentials=${handleEditCredentials}
|
|
523
|
+
onDisconnect=${(accountId) =>
|
|
524
|
+
setDisconnectAccountId(accountId)}
|
|
525
|
+
gmailWatchStatus=${watchByAccountId.get(account.id) ||
|
|
526
|
+
null}
|
|
527
|
+
gmailWatchBusy=${Boolean(busyByAccountId[account.id])}
|
|
528
|
+
onEnableGmailWatch=${handleEnableGmailWatch}
|
|
529
|
+
onDisableGmailWatch=${handleDisableGmailWatch}
|
|
530
|
+
onOpenGmailSetup=${openGmailSetupWizard}
|
|
531
|
+
onOpenGmailWebhook=${onOpenGmailWebhook}
|
|
532
|
+
/>`,
|
|
500
533
|
)}
|
|
501
534
|
</div>
|
|
502
535
|
`
|
|
@@ -528,7 +561,9 @@ export const Google = ({
|
|
|
528
561
|
visible=${gmailWizardState.visible}
|
|
529
562
|
account=${getAccountById(gmailWizardState.accountId)}
|
|
530
563
|
clientConfig=${clientConfigByClient.get(
|
|
531
|
-
String(
|
|
564
|
+
String(
|
|
565
|
+
getAccountById(gmailWizardState.accountId)?.client || "default",
|
|
566
|
+
).trim() || "default",
|
|
532
567
|
) || null}
|
|
533
568
|
saving=${savingClient || gmailLoading}
|
|
534
569
|
onClose=${closeGmailSetupWizard}
|
|
@@ -262,6 +262,32 @@ export const DeleteBinLineIcon = ({ className = "" }) => html`
|
|
|
262
262
|
</svg>
|
|
263
263
|
`;
|
|
264
264
|
|
|
265
|
+
export const FileAddLineIcon = ({ className = "" }) => html`
|
|
266
|
+
<svg
|
|
267
|
+
class=${className}
|
|
268
|
+
viewBox="0 0 24 24"
|
|
269
|
+
fill="currentColor"
|
|
270
|
+
aria-hidden="true"
|
|
271
|
+
>
|
|
272
|
+
<path
|
|
273
|
+
d="M15 4H5V20H19V8H15V4ZM3 2.9918C3 2.44405 3.44749 2 3.9985 2H16L20.9997 7L21 20.9925C21 21.5489 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918ZM11 11V8H13V11H16V13H13V16H11V13H8V11H11Z"
|
|
274
|
+
/>
|
|
275
|
+
</svg>
|
|
276
|
+
`;
|
|
277
|
+
|
|
278
|
+
export const FolderAddLineIcon = ({ className = "" }) => html`
|
|
279
|
+
<svg
|
|
280
|
+
class=${className}
|
|
281
|
+
viewBox="0 0 24 24"
|
|
282
|
+
fill="currentColor"
|
|
283
|
+
aria-hidden="true"
|
|
284
|
+
>
|
|
285
|
+
<path
|
|
286
|
+
d="M12.4142 5H21C21.5523 5 22 5.44772 22 6V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H10.4142L12.4142 5ZM4 5V19H20V7H11.5858L9.58579 5H4ZM11 12V9H13V12H16V14H13V17H11V14H8V12H11Z"
|
|
287
|
+
/>
|
|
288
|
+
</svg>
|
|
289
|
+
`;
|
|
290
|
+
|
|
265
291
|
export const RestartLineIcon = ({ className = "" }) => html`
|
|
266
292
|
<svg
|
|
267
293
|
class=${className}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "https://esm.sh/preact/hooks";
|
|
2
|
+
import {
|
|
3
|
+
disconnectCodex,
|
|
4
|
+
exchangeCodexOAuth,
|
|
5
|
+
fetchCodexStatus,
|
|
6
|
+
} from "../../lib/api.js";
|
|
7
|
+
|
|
8
|
+
export const useWelcomeCodex = ({ setFormError } = {}) => {
|
|
9
|
+
const [codexStatus, setCodexStatus] = useState({ connected: false });
|
|
10
|
+
const [codexLoading, setCodexLoading] = useState(true);
|
|
11
|
+
const [codexManualInput, setCodexManualInput] = useState("");
|
|
12
|
+
const [codexExchanging, setCodexExchanging] = useState(false);
|
|
13
|
+
const [codexAuthStarted, setCodexAuthStarted] = useState(false);
|
|
14
|
+
const [codexAuthWaiting, setCodexAuthWaiting] = useState(false);
|
|
15
|
+
const codexPopupPollRef = useRef(null);
|
|
16
|
+
|
|
17
|
+
const refreshCodexStatus = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const status = await fetchCodexStatus();
|
|
20
|
+
setCodexStatus(status);
|
|
21
|
+
if (status?.connected) {
|
|
22
|
+
setCodexAuthStarted(false);
|
|
23
|
+
setCodexAuthWaiting(false);
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
setCodexStatus({ connected: false });
|
|
27
|
+
} finally {
|
|
28
|
+
setCodexLoading(false);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
refreshCodexStatus();
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const onMessage = async (e) => {
|
|
38
|
+
if (e.data?.codex === "success") {
|
|
39
|
+
await refreshCodexStatus();
|
|
40
|
+
}
|
|
41
|
+
if (e.data?.codex === "error") {
|
|
42
|
+
setFormError(`Codex auth failed: ${e.data.message || "unknown error"}`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
window.addEventListener("message", onMessage);
|
|
46
|
+
return () => window.removeEventListener("message", onMessage);
|
|
47
|
+
}, [setFormError]);
|
|
48
|
+
|
|
49
|
+
useEffect(
|
|
50
|
+
() => () => {
|
|
51
|
+
if (codexPopupPollRef.current) {
|
|
52
|
+
clearInterval(codexPopupPollRef.current);
|
|
53
|
+
codexPopupPollRef.current = null;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const startCodexAuth = () => {
|
|
60
|
+
if (codexStatus.connected) return;
|
|
61
|
+
setCodexAuthStarted(true);
|
|
62
|
+
setCodexAuthWaiting(true);
|
|
63
|
+
const authUrl = "/auth/codex/start";
|
|
64
|
+
const popup = window.open(
|
|
65
|
+
authUrl,
|
|
66
|
+
"codex-auth",
|
|
67
|
+
"popup=yes,width=640,height=780",
|
|
68
|
+
);
|
|
69
|
+
if (!popup || popup.closed) {
|
|
70
|
+
setCodexAuthWaiting(false);
|
|
71
|
+
window.location.href = authUrl;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (codexPopupPollRef.current) {
|
|
75
|
+
clearInterval(codexPopupPollRef.current);
|
|
76
|
+
}
|
|
77
|
+
codexPopupPollRef.current = setInterval(() => {
|
|
78
|
+
if (popup.closed) {
|
|
79
|
+
clearInterval(codexPopupPollRef.current);
|
|
80
|
+
codexPopupPollRef.current = null;
|
|
81
|
+
setCodexAuthWaiting(false);
|
|
82
|
+
}
|
|
83
|
+
}, 500);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const completeCodexAuth = async () => {
|
|
87
|
+
if (!codexManualInput.trim() || codexExchanging) return;
|
|
88
|
+
setCodexExchanging(true);
|
|
89
|
+
setFormError(null);
|
|
90
|
+
try {
|
|
91
|
+
const result = await exchangeCodexOAuth(codexManualInput.trim());
|
|
92
|
+
if (!result.ok)
|
|
93
|
+
throw new Error(result.error || "Codex OAuth exchange failed");
|
|
94
|
+
setCodexManualInput("");
|
|
95
|
+
setCodexAuthStarted(false);
|
|
96
|
+
setCodexAuthWaiting(false);
|
|
97
|
+
await refreshCodexStatus();
|
|
98
|
+
} catch (err) {
|
|
99
|
+
setFormError(err.message || "Codex OAuth exchange failed");
|
|
100
|
+
} finally {
|
|
101
|
+
setCodexExchanging(false);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleCodexDisconnect = async () => {
|
|
106
|
+
const result = await disconnectCodex();
|
|
107
|
+
if (!result.ok) {
|
|
108
|
+
setFormError(result.error || "Failed to disconnect Codex");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
setCodexAuthStarted(false);
|
|
112
|
+
setCodexAuthWaiting(false);
|
|
113
|
+
setCodexManualInput("");
|
|
114
|
+
await refreshCodexStatus();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
codexStatus,
|
|
119
|
+
codexLoading,
|
|
120
|
+
codexManualInput,
|
|
121
|
+
setCodexManualInput,
|
|
122
|
+
codexExchanging,
|
|
123
|
+
codexAuthStarted,
|
|
124
|
+
codexAuthWaiting,
|
|
125
|
+
startCodexAuth,
|
|
126
|
+
completeCodexAuth,
|
|
127
|
+
handleCodexDisconnect,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useEffect, useState } from "https://esm.sh/preact/hooks";
|
|
2
|
+
import { approvePairing, fetchPairings, fetchStatus, rejectPairing } from "../../lib/api.js";
|
|
3
|
+
import { usePolling } from "../../hooks/usePolling.js";
|
|
4
|
+
import { isChannelPaired } from "./pairing-utils.js";
|
|
5
|
+
|
|
6
|
+
export const useWelcomePairing = ({
|
|
7
|
+
isPairingStep = false,
|
|
8
|
+
selectedPairingChannel = "",
|
|
9
|
+
} = {}) => {
|
|
10
|
+
const [pairingError, setPairingError] = useState(null);
|
|
11
|
+
const [pairingComplete, setPairingComplete] = useState(false);
|
|
12
|
+
|
|
13
|
+
const pairingStatusPoll = usePolling(fetchStatus, 3000, {
|
|
14
|
+
enabled: isPairingStep,
|
|
15
|
+
});
|
|
16
|
+
const pairingRequestsPoll = usePolling(
|
|
17
|
+
async () => {
|
|
18
|
+
const payload = await fetchPairings();
|
|
19
|
+
const allPending = payload.pending || [];
|
|
20
|
+
return allPending.filter((p) => p.channel === selectedPairingChannel);
|
|
21
|
+
},
|
|
22
|
+
1000,
|
|
23
|
+
{ enabled: isPairingStep && !!selectedPairingChannel },
|
|
24
|
+
);
|
|
25
|
+
const pairingChannels = pairingStatusPoll.data?.channels || {};
|
|
26
|
+
const canFinishPairing = isChannelPaired(pairingChannels, selectedPairingChannel);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (isPairingStep && canFinishPairing) {
|
|
30
|
+
setPairingComplete(true);
|
|
31
|
+
}
|
|
32
|
+
}, [isPairingStep, canFinishPairing]);
|
|
33
|
+
|
|
34
|
+
const handlePairingApprove = async (id, channel) => {
|
|
35
|
+
try {
|
|
36
|
+
setPairingError(null);
|
|
37
|
+
const result = await approvePairing(id, channel);
|
|
38
|
+
if (!result.ok) throw new Error(result.error || "Could not approve pairing");
|
|
39
|
+
setPairingComplete(true);
|
|
40
|
+
pairingRequestsPoll.refresh();
|
|
41
|
+
pairingStatusPoll.refresh();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
setPairingError(err.message || "Could not approve pairing");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handlePairingReject = async (id, channel) => {
|
|
48
|
+
try {
|
|
49
|
+
setPairingError(null);
|
|
50
|
+
const result = await rejectPairing(id, channel);
|
|
51
|
+
if (!result.ok) throw new Error(result.error || "Could not reject pairing");
|
|
52
|
+
pairingRequestsPoll.refresh();
|
|
53
|
+
} catch (err) {
|
|
54
|
+
setPairingError(err.message || "Could not reject pairing");
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const resetPairingState = () => {
|
|
59
|
+
setPairingError(null);
|
|
60
|
+
setPairingComplete(false);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
pairingStatusPoll,
|
|
65
|
+
pairingRequestsPoll,
|
|
66
|
+
pairingChannels,
|
|
67
|
+
canFinishPairing,
|
|
68
|
+
pairingError,
|
|
69
|
+
pairingComplete,
|
|
70
|
+
handlePairingApprove,
|
|
71
|
+
handlePairingReject,
|
|
72
|
+
resetPairingState,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useEffect, useState } from "https://esm.sh/preact/hooks";
|
|
2
|
+
|
|
3
|
+
export const kOnboardingStorageKey = "openclaw_setup";
|
|
4
|
+
export const kOnboardingStepKey = "_step";
|
|
5
|
+
export const kPairingChannelKey = "_pairingChannel";
|
|
6
|
+
export const kOnboardingSetupErrorKey = "_lastSetupError";
|
|
7
|
+
|
|
8
|
+
const loadInitialSetupState = () => {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(localStorage.getItem(kOnboardingStorageKey) || "{}");
|
|
11
|
+
} catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const useWelcomeStorage = ({
|
|
17
|
+
kSetupStepIndex,
|
|
18
|
+
kPairingStepIndex,
|
|
19
|
+
} = {}) => {
|
|
20
|
+
const [initialSetupState] = useState(loadInitialSetupState);
|
|
21
|
+
const [vals, setVals] = useState(() => ({ ...initialSetupState }));
|
|
22
|
+
const [setupError, setSetupError] = useState(null);
|
|
23
|
+
const initialSetupError = String(
|
|
24
|
+
initialSetupState?.[kOnboardingSetupErrorKey] || "",
|
|
25
|
+
).trim();
|
|
26
|
+
const shouldRecoverFromSetupState = !!initialSetupError;
|
|
27
|
+
const [step, setStep] = useState(() => {
|
|
28
|
+
const parsedStep = Number.parseInt(
|
|
29
|
+
String(initialSetupState?.[kOnboardingStepKey] || ""),
|
|
30
|
+
10,
|
|
31
|
+
);
|
|
32
|
+
if (!Number.isFinite(parsedStep)) return 0;
|
|
33
|
+
const clampedStep = Math.max(0, Math.min(kPairingStepIndex, parsedStep));
|
|
34
|
+
if (clampedStep === kSetupStepIndex && shouldRecoverFromSetupState) return 0;
|
|
35
|
+
return clampedStep;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
localStorage.setItem(
|
|
40
|
+
kOnboardingStorageKey,
|
|
41
|
+
JSON.stringify({
|
|
42
|
+
...vals,
|
|
43
|
+
[kOnboardingStepKey]: step,
|
|
44
|
+
...(setupError ? { [kOnboardingSetupErrorKey]: setupError } : {}),
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
}, [vals, step, setupError]);
|
|
48
|
+
|
|
49
|
+
const setValue = (key, value) => setVals((prev) => ({ ...prev, [key]: value }));
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
vals,
|
|
53
|
+
setVals,
|
|
54
|
+
setValue,
|
|
55
|
+
step,
|
|
56
|
+
setStep,
|
|
57
|
+
setupError,
|
|
58
|
+
setSetupError,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
@@ -12,7 +12,7 @@ const kBrowseBottomPanelUiSettingKey = "browseBottomPanelHeightPx";
|
|
|
12
12
|
const kBrowsePanelMinHeightPx = 120;
|
|
13
13
|
const kBrowseBottomMinHeightPx = 120;
|
|
14
14
|
const kBrowseResizerHeightPx = 6;
|
|
15
|
-
const kDefaultBrowseBottomPanelHeightPx =
|
|
15
|
+
const kDefaultBrowseBottomPanelHeightPx = 260;
|
|
16
16
|
|
|
17
17
|
const readStoredBrowseBottomPanelHeight = () => {
|
|
18
18
|
try {
|
|
@@ -116,7 +116,7 @@ export const CreateGroupStep = ({ onNext, onBack }) => html`
|
|
|
116
116
|
<p class="text-xs font-medium text-gray-300">Create the group</p>
|
|
117
117
|
<ol class="text-xs text-gray-400 space-y-2 list-decimal list-inside">
|
|
118
118
|
<li>
|
|
119
|
-
Open Telegram and create a
|
|
119
|
+
Open Telegram and create a${" "}
|
|
120
120
|
<span class="text-gray-300">new group</span>
|
|
121
121
|
</li>
|
|
122
122
|
<li>
|
|
@@ -232,7 +232,7 @@ export const Webhooks = ({
|
|
|
232
232
|
selectedWebhook?.fullUrl || `.../hooks/${selectedHookName}`;
|
|
233
233
|
const webhookUrlWithQueryToken =
|
|
234
234
|
selectedWebhook?.queryStringUrl ||
|
|
235
|
-
`${webhookUrl}${webhookUrl.includes("?") ? "&" : "?"}token=<
|
|
235
|
+
`${webhookUrl}${webhookUrl.includes("?") ? "&" : "?"}token=<WEBHOOK_TOKEN>`;
|
|
236
236
|
const derivedTokenFromQuery = (() => {
|
|
237
237
|
try {
|
|
238
238
|
const parsed = new URL(webhookUrlWithQueryToken);
|
|
@@ -245,7 +245,7 @@ export const Webhooks = ({
|
|
|
245
245
|
selectedWebhook?.authHeaderValue ||
|
|
246
246
|
(derivedTokenFromQuery
|
|
247
247
|
? `Authorization: Bearer ${derivedTokenFromQuery}`
|
|
248
|
-
: "Authorization: Bearer <
|
|
248
|
+
: "Authorization: Bearer <WEBHOOK_TOKEN>");
|
|
249
249
|
const bearerTokenValue = authHeaderValue.startsWith("Authorization: ")
|
|
250
250
|
? authHeaderValue.slice("Authorization: ".length)
|
|
251
251
|
: authHeaderValue;
|