@chrysb/alphaclaw 0.8.0 → 0.8.1-beta.1
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/js/app.js +100 -83
- package/lib/public/js/components/agents-tab/agent-pairing-section.js +47 -12
- package/lib/public/js/components/channels.js +14 -17
- package/lib/public/js/components/envars.js +42 -6
- package/lib/public/js/components/features.js +6 -12
- package/lib/public/js/components/general/use-general-tab.js +10 -5
- package/lib/public/js/components/google/use-gmail-watch.js +22 -18
- package/lib/public/js/components/google/use-google-accounts.js +23 -23
- package/lib/public/js/components/models-tab/use-models.js +20 -4
- package/lib/public/js/components/nodes-tab/connected-nodes/user-connected-nodes.js +2 -2
- package/lib/public/js/components/nodes-tab/use-nodes-tab.js +13 -9
- package/lib/public/js/components/routes/webhooks-route.js +1 -1
- package/lib/public/js/components/webhooks/create-webhook-modal/index.js +176 -0
- package/lib/public/js/components/webhooks/helpers.js +106 -0
- package/lib/public/js/components/webhooks/index.js +148 -0
- package/lib/public/js/components/webhooks/request-history/index.js +241 -0
- package/lib/public/js/components/webhooks/request-history/use-request-history.js +167 -0
- package/lib/public/js/components/webhooks/webhook-detail/index.js +374 -0
- package/lib/public/js/components/webhooks/webhook-detail/use-webhook-detail.js +261 -0
- package/lib/public/js/components/webhooks/webhook-list/index.js +96 -0
- package/lib/public/js/components/webhooks/webhook-list/use-webhook-list.js +30 -0
- package/lib/public/js/hooks/use-app-shell-controller.js +59 -6
- package/lib/public/js/hooks/use-cached-fetch.js +63 -0
- package/lib/public/js/hooks/usePolling.js +45 -7
- package/lib/public/js/lib/api-cache.js +88 -0
- package/lib/public/js/lib/api.js +64 -1
- package/lib/server/db/webhooks/index.js +144 -0
- package/lib/server/db/webhooks/schema.js +13 -0
- package/lib/server/init/register-server-routes.js +21 -0
- package/lib/server/oauth-callback-middleware.js +34 -0
- package/lib/server/routes/proxy.js +2 -0
- package/lib/server/routes/system.js +50 -2
- package/lib/server/routes/webhooks.js +126 -18
- package/lib/server/webhook-middleware.js +6 -1
- package/lib/server.js +12 -0
- package/package.json +1 -1
- package/lib/public/js/components/webhooks.js +0 -1259
package/lib/public/js/app.js
CHANGED
|
@@ -240,87 +240,105 @@ const App = () => {
|
|
|
240
240
|
<span style="color: var(--accent)">alpha</span>claw
|
|
241
241
|
</span>
|
|
242
242
|
</div>
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
243
|
+
${browseState.isBrowseRoute
|
|
244
|
+
? html`
|
|
245
|
+
<div class="app-content-pane browse-pane">
|
|
246
|
+
<${BrowseRoute}
|
|
247
|
+
activeBrowsePath=${browseState.activeBrowsePath}
|
|
248
|
+
browseView=${browseState.browseViewerMode}
|
|
249
|
+
lineTarget=${browseState.browseLineTarget}
|
|
250
|
+
lineEndTarget=${browseState.browseLineEndTarget}
|
|
251
|
+
selectedBrowsePath=${browseState.selectedBrowsePath}
|
|
252
|
+
onNavigateToBrowseFile=${browseActions.navigateToBrowseFile}
|
|
253
|
+
onEditSelectedBrowseFile=${() =>
|
|
254
|
+
setLocation(
|
|
255
|
+
browseActions.buildBrowseRoute(browseState.selectedBrowsePath, {
|
|
256
|
+
view: "edit",
|
|
257
|
+
}),
|
|
258
|
+
)}
|
|
259
|
+
onClearSelection=${() => {
|
|
260
|
+
browseActions.clearBrowsePreview();
|
|
261
|
+
setLocation("/browse");
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
`
|
|
266
|
+
: null}
|
|
267
|
+
${isAgentsRoute
|
|
268
|
+
? html`
|
|
269
|
+
<div class="app-content-pane agents-pane">
|
|
270
|
+
<${AgentsRoute}
|
|
271
|
+
agents=${agentsState.agents}
|
|
272
|
+
loading=${agentsState.loading}
|
|
273
|
+
saving=${agentsState.saving}
|
|
274
|
+
agentsActions=${agentsActions}
|
|
275
|
+
selectedAgentId=${selectedAgentId}
|
|
276
|
+
activeTab=${agentDetailTab}
|
|
277
|
+
onSelectAgent=${(agentId) =>
|
|
278
|
+
setLocation(`/agents/${encodeURIComponent(agentId)}`)}
|
|
279
|
+
onSelectTab=${(tab) => {
|
|
280
|
+
const safePath = tab && tab !== "overview"
|
|
281
|
+
? `/agents/${encodeURIComponent(selectedAgentId)}/${tab}`
|
|
282
|
+
: `/agents/${encodeURIComponent(selectedAgentId)}`;
|
|
283
|
+
setLocation(safePath);
|
|
284
|
+
}}
|
|
285
|
+
onNavigateToBrowseFile=${browseActions.navigateToBrowseFile}
|
|
286
|
+
onSetLocation=${setLocation}
|
|
287
|
+
/>
|
|
288
|
+
</div>
|
|
289
|
+
`
|
|
290
|
+
: null}
|
|
291
|
+
${isCronRoute
|
|
292
|
+
? html`
|
|
293
|
+
<div class="app-content-pane cron-pane">
|
|
294
|
+
<${CronRoute}
|
|
295
|
+
jobId=${selectedCronJobId}
|
|
296
|
+
onSetLocation=${setLocation}
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
`
|
|
300
|
+
: null}
|
|
301
|
+
${isEnvarsRoute
|
|
302
|
+
? html`
|
|
303
|
+
<div class="app-content-pane ac-fixed-header-pane">
|
|
304
|
+
<${EnvarsRoute}
|
|
305
|
+
onRestartRequired=${controllerActions.setRestartRequired}
|
|
306
|
+
/>
|
|
307
|
+
</div>
|
|
308
|
+
`
|
|
309
|
+
: null}
|
|
310
|
+
${isModelsRoute
|
|
311
|
+
? html`
|
|
312
|
+
<div class="app-content-pane ac-fixed-header-pane">
|
|
313
|
+
<${ModelsRoute}
|
|
314
|
+
onRestartRequired=${controllerActions.setRestartRequired}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
`
|
|
318
|
+
: null}
|
|
319
|
+
${isNodesRoute
|
|
320
|
+
? html`
|
|
321
|
+
<div class="app-content-pane">
|
|
322
|
+
<${NodesRoute}
|
|
323
|
+
onRestartRequired=${controllerActions.setRestartRequired}
|
|
324
|
+
/>
|
|
325
|
+
</div>
|
|
326
|
+
`
|
|
327
|
+
: null}
|
|
328
|
+
${browseState.isBrowseRoute ||
|
|
329
|
+
isAgentsRoute ||
|
|
330
|
+
isCronRoute ||
|
|
331
|
+
isEnvarsRoute ||
|
|
332
|
+
isModelsRoute ||
|
|
333
|
+
isNodesRoute
|
|
334
|
+
? null
|
|
335
|
+
: html`
|
|
336
|
+
<div
|
|
337
|
+
class="app-content-pane"
|
|
338
|
+
onscroll=${shellActions.handlePaneScroll}
|
|
339
|
+
>
|
|
320
340
|
<div class="max-w-2xl w-full mx-auto">
|
|
321
|
-
|
|
322
|
-
? html`
|
|
323
|
-
<${Switch}>
|
|
341
|
+
<${Switch}>
|
|
324
342
|
<${Route} path="/general">
|
|
325
343
|
<${GeneralRoute}
|
|
326
344
|
statusData=${controllerState.sharedStatus}
|
|
@@ -412,10 +430,9 @@ const App = () => {
|
|
|
412
430
|
<${RouteRedirect} to="/general" />
|
|
413
431
|
</${Route}>
|
|
414
432
|
</${Switch}>
|
|
415
|
-
`
|
|
416
|
-
: null}
|
|
417
433
|
</div>
|
|
418
|
-
|
|
434
|
+
</div>
|
|
435
|
+
`}
|
|
419
436
|
<${ToastContainer}
|
|
420
437
|
className="fixed top-4 right-4 z-[60] space-y-2 pointer-events-none"
|
|
421
438
|
/>
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
rejectPairing,
|
|
18
18
|
} from "../../lib/api.js";
|
|
19
19
|
import { showToast } from "../toast.js";
|
|
20
|
+
import { useCachedFetch } from "../../hooks/use-cached-fetch.js";
|
|
20
21
|
|
|
21
22
|
const html = htm.bind(h);
|
|
22
23
|
|
|
@@ -43,29 +44,60 @@ export const AgentPairingSection = ({ agent = {} }) => {
|
|
|
43
44
|
const pairingDelayedRefreshTimerRefs = useRef([]);
|
|
44
45
|
const agentId = String(agent?.id || "").trim();
|
|
45
46
|
const isDefaultAgent = !!agent?.default;
|
|
47
|
+
const {
|
|
48
|
+
data: bindingsPayload,
|
|
49
|
+
loading: bindingsLoading,
|
|
50
|
+
refresh: refreshBindingsPayload,
|
|
51
|
+
} = useCachedFetch(
|
|
52
|
+
`/api/agents/${encodeURIComponent(String(agentId || ""))}/bindings`,
|
|
53
|
+
() => fetchAgentBindings(agent.id),
|
|
54
|
+
{
|
|
55
|
+
enabled: Boolean(agentId),
|
|
56
|
+
maxAgeMs: 30000,
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
const {
|
|
60
|
+
data: channelsPayload,
|
|
61
|
+
loading: channelsLoading,
|
|
62
|
+
refresh: refreshChannelsPayload,
|
|
63
|
+
} = useCachedFetch("/api/channels/accounts", fetchChannelAccounts, {
|
|
64
|
+
maxAgeMs: 30000,
|
|
65
|
+
});
|
|
46
66
|
|
|
47
67
|
const loadBindings = useCallback(async () => {
|
|
48
68
|
setLoadingBindings(true);
|
|
49
69
|
try {
|
|
50
|
-
const [
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
const [nextBindingsPayload, nextChannelsPayload] = await Promise.all([
|
|
71
|
+
refreshBindingsPayload({ force: true }),
|
|
72
|
+
refreshChannelsPayload({ force: true }),
|
|
53
73
|
]);
|
|
54
|
-
setBindings(
|
|
55
|
-
|
|
74
|
+
setBindings(
|
|
75
|
+
Array.isArray(nextBindingsPayload?.bindings)
|
|
76
|
+
? nextBindingsPayload.bindings
|
|
77
|
+
: [],
|
|
78
|
+
);
|
|
79
|
+
setChannels(
|
|
80
|
+
Array.isArray(nextChannelsPayload?.channels)
|
|
81
|
+
? nextChannelsPayload.channels
|
|
82
|
+
: [],
|
|
83
|
+
);
|
|
56
84
|
} catch {
|
|
57
85
|
setBindings([]);
|
|
58
86
|
setChannels([]);
|
|
59
87
|
} finally {
|
|
60
88
|
setLoadingBindings(false);
|
|
61
89
|
}
|
|
62
|
-
}, [
|
|
90
|
+
}, [refreshBindingsPayload, refreshChannelsPayload]);
|
|
63
91
|
|
|
64
92
|
useEffect(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
93
|
+
setBindings(
|
|
94
|
+
Array.isArray(bindingsPayload?.bindings) ? bindingsPayload.bindings : [],
|
|
95
|
+
);
|
|
96
|
+
setChannels(
|
|
97
|
+
Array.isArray(channelsPayload?.channels) ? channelsPayload.channels : [],
|
|
98
|
+
);
|
|
99
|
+
setLoadingBindings(Boolean(bindingsLoading || channelsLoading));
|
|
100
|
+
}, [bindingsLoading, bindingsPayload, channelsLoading, channelsPayload]);
|
|
69
101
|
|
|
70
102
|
useEffect(() => {
|
|
71
103
|
const handleBindingsChanged = (event) => {
|
|
@@ -207,8 +239,11 @@ export const AgentPairingSection = ({ agent = {} }) => {
|
|
|
207
239
|
};
|
|
208
240
|
});
|
|
209
241
|
},
|
|
210
|
-
|
|
211
|
-
{
|
|
242
|
+
3000,
|
|
243
|
+
{
|
|
244
|
+
enabled: hasUnpaired && ownedAccounts.length > 0,
|
|
245
|
+
cacheKey: `/api/pairings?agent=${encodeURIComponent(agentId)}`,
|
|
246
|
+
},
|
|
212
247
|
);
|
|
213
248
|
|
|
214
249
|
const pending = pairingsPoll.data || [];
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
fetchChannelAccounts,
|
|
16
16
|
updateChannelAccount,
|
|
17
17
|
} from "../lib/api.js";
|
|
18
|
+
import { useCachedFetch } from "../hooks/use-cached-fetch.js";
|
|
18
19
|
import {
|
|
19
20
|
isImplicitDefaultAccount,
|
|
20
21
|
resolveChannelAccountLabel,
|
|
@@ -140,31 +141,27 @@ export const Channels = ({
|
|
|
140
141
|
onNavigate = () => {},
|
|
141
142
|
onRefreshStatuses = () => {},
|
|
142
143
|
}) => {
|
|
143
|
-
const [channelAccounts, setChannelAccounts] = useState([]);
|
|
144
|
-
const [loadingAccounts, setLoadingAccounts] = useState(true);
|
|
145
144
|
const [saving, setSaving] = useState(false);
|
|
146
145
|
const [createLoadingLabel, setCreateLoadingLabel] = useState("Creating...");
|
|
147
146
|
const [menuOpenId, setMenuOpenId] = useState("");
|
|
148
147
|
const [editingAccount, setEditingAccount] = useState(null);
|
|
149
148
|
const [deletingAccount, setDeletingAccount] = useState(null);
|
|
149
|
+
const {
|
|
150
|
+
data: channelAccountsPayload,
|
|
151
|
+
loading: loadingAccounts,
|
|
152
|
+
refresh: refreshChannelAccounts,
|
|
153
|
+
} = useCachedFetch("/api/channels/accounts", fetchChannelAccounts, {
|
|
154
|
+
maxAgeMs: 30000,
|
|
155
|
+
});
|
|
156
|
+
const channelAccounts = Array.isArray(channelAccountsPayload?.channels)
|
|
157
|
+
? channelAccountsPayload.channels
|
|
158
|
+
: [];
|
|
150
159
|
|
|
151
160
|
const loadChannelAccounts = useCallback(async () => {
|
|
152
|
-
setLoadingAccounts(true);
|
|
153
161
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
);
|
|
158
|
-
} catch {
|
|
159
|
-
setChannelAccounts([]);
|
|
160
|
-
} finally {
|
|
161
|
-
setLoadingAccounts(false);
|
|
162
|
-
}
|
|
163
|
-
}, []);
|
|
164
|
-
|
|
165
|
-
useEffect(() => {
|
|
166
|
-
loadChannelAccounts();
|
|
167
|
-
}, [loadChannelAccounts]);
|
|
162
|
+
await refreshChannelAccounts({ force: true });
|
|
163
|
+
} catch {}
|
|
164
|
+
}, [refreshChannelAccounts]);
|
|
168
165
|
|
|
169
166
|
|
|
170
167
|
const configuredChannelMap = useMemo(
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "https://esm.sh/preact/hooks";
|
|
8
8
|
import htm from "https://esm.sh/htm";
|
|
9
9
|
import { fetchEnvVars, saveEnvVars } from "../lib/api.js";
|
|
10
|
+
import { useCachedFetch } from "../hooks/use-cached-fetch.js";
|
|
10
11
|
import { showToast } from "./toast.js";
|
|
11
12
|
import { SecretInput } from "./secret-input.js";
|
|
12
13
|
import { PageHeader } from "./page-header.js";
|
|
@@ -315,24 +316,47 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
315
316
|
const [showAllAiKeys, setShowAllAiKeys] = useState(false);
|
|
316
317
|
const [newKey, setNewKey] = useState("");
|
|
317
318
|
const baselineSignatureRef = useRef("[]");
|
|
319
|
+
const {
|
|
320
|
+
data: envPayload,
|
|
321
|
+
error: envPayloadError,
|
|
322
|
+
loading: envPayloadLoading,
|
|
323
|
+
refresh: refreshEnvPayload,
|
|
324
|
+
} = useCachedFetch("/api/env", fetchEnvVars, {
|
|
325
|
+
maxAgeMs: 30000,
|
|
326
|
+
});
|
|
318
327
|
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
328
|
+
const applyEnvPayload = useCallback(
|
|
329
|
+
(data) => {
|
|
330
|
+
if (!data) return;
|
|
322
331
|
const nextVars = sortCustomVarsAlphabetically(data.vars || []);
|
|
323
332
|
baselineSignatureRef.current = getVarsSignature(nextVars);
|
|
324
333
|
setVars(nextVars);
|
|
325
334
|
setPendingCustomKeys([]);
|
|
326
335
|
setReservedKeys(new Set(data.reservedKeys || []));
|
|
327
336
|
onRestartRequired(!!data.restartRequired);
|
|
337
|
+
},
|
|
338
|
+
[onRestartRequired],
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const load = useCallback(async () => {
|
|
342
|
+
try {
|
|
343
|
+
const data = await refreshEnvPayload({ force: true });
|
|
344
|
+
applyEnvPayload(data);
|
|
328
345
|
} catch (err) {
|
|
329
346
|
console.error("Failed to load env vars:", err);
|
|
330
347
|
}
|
|
331
|
-
}, []);
|
|
348
|
+
}, [applyEnvPayload, refreshEnvPayload]);
|
|
332
349
|
|
|
333
350
|
useEffect(() => {
|
|
334
|
-
|
|
335
|
-
|
|
351
|
+
if (!envPayload) return;
|
|
352
|
+
if (dirty || saving) return;
|
|
353
|
+
applyEnvPayload(envPayload);
|
|
354
|
+
}, [applyEnvPayload, dirty, envPayload, saving]);
|
|
355
|
+
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
if (!envPayloadError) return;
|
|
358
|
+
console.error("Failed to load env vars:", envPayloadError);
|
|
359
|
+
}, [envPayloadError]);
|
|
336
360
|
|
|
337
361
|
useEffect(() => {
|
|
338
362
|
setDirty(getVarsSignature(vars) !== baselineSignatureRef.current);
|
|
@@ -592,6 +616,18 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
592
616
|
`;
|
|
593
617
|
};
|
|
594
618
|
|
|
619
|
+
if (envPayloadLoading && !vars.length) {
|
|
620
|
+
return html`
|
|
621
|
+
<${PaneShell}
|
|
622
|
+
header=${html`<${PageHeader} title="Envars" />`}
|
|
623
|
+
>
|
|
624
|
+
<div class="bg-surface border border-border rounded-xl p-4 text-sm text-gray-500">
|
|
625
|
+
Loading environment variables...
|
|
626
|
+
</div>
|
|
627
|
+
</${PaneShell}>
|
|
628
|
+
`;
|
|
629
|
+
}
|
|
630
|
+
|
|
595
631
|
return html`
|
|
596
632
|
<${PaneShell}
|
|
597
633
|
header=${html`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
|
-
import { useState, useEffect } from "https://esm.sh/preact/hooks";
|
|
3
2
|
import htm from "https://esm.sh/htm";
|
|
4
3
|
import { fetchEnvVars } from "../lib/api.js";
|
|
4
|
+
import { useCachedFetch } from "../hooks/use-cached-fetch.js";
|
|
5
5
|
import { Badge } from "./badge.js";
|
|
6
6
|
import {
|
|
7
7
|
kFeatureDefs,
|
|
@@ -23,17 +23,11 @@ const resolveFeatureStatus = (feature, envVars) => {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export const Features = ({ onSwitchTab }) => {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.then((data) => {
|
|
32
|
-
setEnvVars(data.vars || []);
|
|
33
|
-
setLoaded(true);
|
|
34
|
-
})
|
|
35
|
-
.catch(() => setLoaded(true));
|
|
36
|
-
}, []);
|
|
26
|
+
const { data, loading } = useCachedFetch("/api/env", fetchEnvVars, {
|
|
27
|
+
maxAgeMs: 30000,
|
|
28
|
+
});
|
|
29
|
+
const envVars = Array.isArray(data?.vars) ? data.vars : [];
|
|
30
|
+
const loaded = !loading;
|
|
37
31
|
|
|
38
32
|
if (!loaded) return null;
|
|
39
33
|
|
|
@@ -62,8 +62,11 @@ export const useGeneralTab = ({
|
|
|
62
62
|
const data = await fetchPairings();
|
|
63
63
|
return data.pending || [];
|
|
64
64
|
},
|
|
65
|
-
|
|
66
|
-
{
|
|
65
|
+
3000,
|
|
66
|
+
{
|
|
67
|
+
enabled: hasUnpaired && gatewayStatus === "running",
|
|
68
|
+
cacheKey: "/api/pairings",
|
|
69
|
+
},
|
|
67
70
|
);
|
|
68
71
|
const pending = pairingsPoll.data || [];
|
|
69
72
|
const shouldPollDevices =
|
|
@@ -75,14 +78,16 @@ export const useGeneralTab = ({
|
|
|
75
78
|
setCliAutoApproveComplete(data?.cliAutoApproveComplete === true);
|
|
76
79
|
return data.pending || [];
|
|
77
80
|
},
|
|
78
|
-
|
|
79
|
-
{
|
|
81
|
+
5000,
|
|
82
|
+
{
|
|
83
|
+
enabled: shouldPollDevices,
|
|
84
|
+
cacheKey: "/api/devices",
|
|
85
|
+
},
|
|
80
86
|
);
|
|
81
87
|
const devicePending = devicePoll.data || [];
|
|
82
88
|
|
|
83
89
|
useEffect(() => {
|
|
84
90
|
if (!isActive) return;
|
|
85
|
-
onRefreshStatuses();
|
|
86
91
|
pairingsPoll.refresh();
|
|
87
92
|
if (shouldPollDevices) {
|
|
88
93
|
devicePoll.refresh();
|
|
@@ -6,34 +6,38 @@ import {
|
|
|
6
6
|
startGmailWatch,
|
|
7
7
|
stopGmailWatch,
|
|
8
8
|
} from "../../lib/api.js";
|
|
9
|
+
import { useCachedFetch } from "../../hooks/use-cached-fetch.js";
|
|
9
10
|
|
|
10
11
|
export const useGmailWatch = ({ gatewayStatus, accounts = [] }) => {
|
|
11
|
-
const [loading, setLoading] = useState(true);
|
|
12
|
-
const [config, setConfig] = useState(null);
|
|
13
12
|
const [busyByAccountId, setBusyByAccountId] = useState({});
|
|
14
13
|
const [savingClient, setSavingClient] = useState(false);
|
|
14
|
+
const accountSignature = useMemo(
|
|
15
|
+
() =>
|
|
16
|
+
accounts
|
|
17
|
+
.map((entry) => String(entry?.id || "").trim())
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.sort()
|
|
20
|
+
.join("|"),
|
|
21
|
+
[accounts],
|
|
22
|
+
);
|
|
23
|
+
const {
|
|
24
|
+
data: config,
|
|
25
|
+
loading,
|
|
26
|
+
refresh: refreshCachedConfig,
|
|
27
|
+
} = useCachedFetch("/api/gmail/config", fetchGmailConfig, {
|
|
28
|
+
enabled: gatewayStatus === "running",
|
|
29
|
+
maxAgeMs: 30000,
|
|
30
|
+
});
|
|
15
31
|
|
|
16
32
|
const refresh = useCallback(async () => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const nextConfig = await fetchGmailConfig();
|
|
20
|
-
setConfig(nextConfig);
|
|
21
|
-
return nextConfig;
|
|
22
|
-
} finally {
|
|
23
|
-
setLoading(false);
|
|
24
|
-
}
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (gatewayStatus !== "running") return;
|
|
29
|
-
refresh();
|
|
30
|
-
}, [gatewayStatus, refresh]);
|
|
33
|
+
return refreshCachedConfig({ force: true });
|
|
34
|
+
}, [refreshCachedConfig]);
|
|
31
35
|
|
|
32
36
|
useEffect(() => {
|
|
33
37
|
if (gatewayStatus !== "running") return;
|
|
34
38
|
if (!accounts.length) return;
|
|
35
|
-
refresh();
|
|
36
|
-
}, [accounts, gatewayStatus, refresh]);
|
|
39
|
+
refresh().catch(() => {});
|
|
40
|
+
}, [accountSignature, accounts.length, gatewayStatus, refresh]);
|
|
37
41
|
|
|
38
42
|
const watchByAccountId = useMemo(() => {
|
|
39
43
|
const map = new Map();
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import { useCallback, useEffect,
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "https://esm.sh/preact/hooks";
|
|
2
2
|
import { fetchGoogleAccounts } from "../../lib/api.js";
|
|
3
|
+
import { useCachedFetch } from "../../hooks/use-cached-fetch.js";
|
|
3
4
|
|
|
4
5
|
export const useGoogleAccounts = ({ gatewayStatus }) => {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const hasRefreshedAfterGatewayRunningRef = useRef(false);
|
|
7
|
+
const { data, loading, refresh } = useCachedFetch(
|
|
8
|
+
"/api/google/accounts",
|
|
9
|
+
fetchGoogleAccounts,
|
|
10
|
+
{ maxAgeMs: 30000 },
|
|
11
|
+
);
|
|
9
12
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
setHasCompanyCredentials(Boolean(data.hasCompanyCredentials));
|
|
17
|
-
setHasPersonalCredentials(Boolean(data.hasPersonalCredentials));
|
|
18
|
-
}
|
|
19
|
-
} finally {
|
|
20
|
-
setLoading(false);
|
|
21
|
-
}
|
|
22
|
-
}, []);
|
|
13
|
+
const accounts = useMemo(
|
|
14
|
+
() => (Array.isArray(data?.accounts) ? data.accounts : []),
|
|
15
|
+
[data?.accounts],
|
|
16
|
+
);
|
|
17
|
+
const hasCompanyCredentials = Boolean(data?.hasCompanyCredentials);
|
|
18
|
+
const hasPersonalCredentials = Boolean(data?.hasPersonalCredentials);
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}, [
|
|
20
|
+
const refreshAccounts = useCallback(async () => {
|
|
21
|
+
return refresh({ force: true });
|
|
22
|
+
}, [refresh]);
|
|
27
23
|
|
|
28
24
|
useEffect(() => {
|
|
29
|
-
if (gatewayStatus
|
|
30
|
-
|
|
25
|
+
if (gatewayStatus !== "running") {
|
|
26
|
+
hasRefreshedAfterGatewayRunningRef.current = false;
|
|
27
|
+
return;
|
|
31
28
|
}
|
|
29
|
+
if (hasRefreshedAfterGatewayRunningRef.current) return;
|
|
30
|
+
hasRefreshedAfterGatewayRunningRef.current = true;
|
|
31
|
+
refreshAccounts().catch(() => {});
|
|
32
32
|
}, [gatewayStatus, refreshAccounts]);
|
|
33
33
|
|
|
34
34
|
return {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
disconnectCodex,
|
|
8
8
|
} from "../../lib/api.js";
|
|
9
9
|
import { showToast } from "../toast.js";
|
|
10
|
+
import { useCachedFetch } from "../../hooks/use-cached-fetch.js";
|
|
10
11
|
|
|
11
12
|
let kModelsTabCache = null;
|
|
12
13
|
const getCredentialValue = (value) =>
|
|
@@ -14,6 +15,7 @@ const getCredentialValue = (value) =>
|
|
|
14
15
|
|
|
15
16
|
export const useModels = (agentId) => {
|
|
16
17
|
const isScoped = !!agentId;
|
|
18
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
17
19
|
const useCache = !isScoped;
|
|
18
20
|
const [catalog, setCatalog] = useState(() => (useCache && kModelsTabCache?.catalog) || []);
|
|
19
21
|
const [primary, setPrimary] = useState(() => (useCache && kModelsTabCache?.primary) || "");
|
|
@@ -43,15 +45,29 @@ export const useModels = (agentId) => {
|
|
|
43
45
|
const updateCache = useCallback((patch) => {
|
|
44
46
|
if (!isScoped) kModelsTabCache = { ...(kModelsTabCache || {}), ...patch };
|
|
45
47
|
}, [isScoped]);
|
|
48
|
+
const modelsConfigCacheKey = normalizedAgentId
|
|
49
|
+
? `/api/models/config?agentId=${encodeURIComponent(normalizedAgentId)}`
|
|
50
|
+
: "/api/models/config";
|
|
51
|
+
const catalogFetchState = useCachedFetch("/api/models", fetchModels, {
|
|
52
|
+
maxAgeMs: 30000,
|
|
53
|
+
});
|
|
54
|
+
const configFetchState = useCachedFetch(
|
|
55
|
+
modelsConfigCacheKey,
|
|
56
|
+
() => fetchModelsConfig(isScoped ? { agentId } : undefined),
|
|
57
|
+
{ maxAgeMs: 30000 },
|
|
58
|
+
);
|
|
59
|
+
const codexFetchState = useCachedFetch("/api/codex/status", fetchCodexStatus, {
|
|
60
|
+
maxAgeMs: 15000,
|
|
61
|
+
});
|
|
46
62
|
|
|
47
63
|
const refresh = useCallback(async () => {
|
|
48
64
|
if (!ready) setLoading(true);
|
|
49
65
|
setError("");
|
|
50
66
|
try {
|
|
51
67
|
const [catalogResult, configResult, codex] = await Promise.all([
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
catalogFetchState.refresh({ force: true }),
|
|
69
|
+
configFetchState.refresh({ force: true }),
|
|
70
|
+
codexFetchState.refresh({ force: true }),
|
|
55
71
|
]);
|
|
56
72
|
const catalogModels = Array.isArray(catalogResult.models)
|
|
57
73
|
? catalogResult.models
|
|
@@ -86,7 +102,7 @@ export const useModels = (agentId) => {
|
|
|
86
102
|
setReady(true);
|
|
87
103
|
setLoading(false);
|
|
88
104
|
}
|
|
89
|
-
}, [ready, updateCache, agentId, isScoped]);
|
|
105
|
+
}, [catalogFetchState, codexFetchState, configFetchState, ready, updateCache, agentId, isScoped]);
|
|
90
106
|
|
|
91
107
|
useEffect(() => {
|
|
92
108
|
refresh();
|