@chrysb/alphaclaw 0.4.1-beta.1 → 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.
@@ -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.isFolderPath
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}
@@ -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
+ };