@chrysb/alphaclaw 0.6.2-beta.5 → 0.7.0-beta.0
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/agents.css +37 -13
- package/lib/public/css/cron.css +124 -41
- package/lib/public/css/shell.css +61 -2
- package/lib/public/css/theme.css +2 -1
- package/lib/public/js/app.js +41 -33
- package/lib/public/js/components/agents-tab/agent-detail-panel.js +61 -49
- package/lib/public/js/components/agents-tab/agent-overview/index.js +9 -0
- package/lib/public/js/components/agents-tab/agent-overview/tools-card.js +54 -0
- package/lib/public/js/components/cron-tab/cron-calendar.js +297 -203
- package/lib/public/js/components/cron-tab/cron-helpers.js +48 -0
- package/lib/public/js/components/cron-tab/cron-insights-panel.js +294 -0
- package/lib/public/js/components/cron-tab/cron-job-detail.js +38 -363
- package/lib/public/js/components/cron-tab/cron-job-settings-card.js +233 -0
- package/lib/public/js/components/cron-tab/cron-overview.js +40 -19
- package/lib/public/js/components/cron-tab/cron-prompt-editor.js +173 -0
- package/lib/public/js/components/cron-tab/cron-run-history-panel.js +74 -62
- package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +24 -24
- package/lib/public/js/components/cron-tab/index.js +170 -78
- package/lib/public/js/components/envars.js +187 -46
- package/lib/public/js/components/file-viewer/editor-surface.js +5 -1
- package/lib/public/js/components/file-viewer/use-editor-line-number-sync.js +36 -0
- package/lib/public/js/components/file-viewer/use-file-viewer.js +7 -23
- package/lib/public/js/components/file-viewer/utils.js +1 -5
- package/lib/public/js/components/models-tab/index.js +137 -133
- package/lib/public/js/components/models-tab/provider-auth-card.js +8 -1
- package/lib/public/js/components/models-tab/use-models.js +35 -8
- package/lib/public/js/components/onboarding/welcome-pairing-step.js +88 -59
- package/lib/public/js/components/pane-shell.js +27 -0
- package/lib/public/js/components/routes/envars-route.js +1 -3
- package/lib/public/js/components/routes/models-route.js +1 -3
- package/lib/public/js/lib/app-navigation.js +1 -1
- package/lib/server/cost-utils.js +2 -2
- package/package.json +1 -1
|
@@ -32,6 +32,7 @@ import { useFileDiff } from "./use-file-diff.js";
|
|
|
32
32
|
import { useFileViewerDraftSync } from "./use-file-viewer-draft-sync.js";
|
|
33
33
|
import { useFileViewerHotkeys } from "./use-file-viewer-hotkeys.js";
|
|
34
34
|
import { useEditorSelectionRestore } from "./use-editor-selection-restore.js";
|
|
35
|
+
import { useEditorLineNumberSync } from "./use-editor-line-number-sync.js";
|
|
35
36
|
|
|
36
37
|
export const useFileViewer = ({
|
|
37
38
|
filePath = "",
|
|
@@ -190,29 +191,12 @@ export const useFileViewer = ({
|
|
|
190
191
|
[parsedFrontmatter.body, isMarkdownFile],
|
|
191
192
|
);
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const numberRow = numberRows[index];
|
|
200
|
-
const highlightRow = highlightRows[index];
|
|
201
|
-
if (!numberRow || !highlightRow) continue;
|
|
202
|
-
numberRow.style.height = `${highlightRow.offsetHeight}px`;
|
|
203
|
-
}
|
|
204
|
-
}, [shouldUseHighlightedEditor, viewMode]);
|
|
205
|
-
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
syncEditorLineNumberHeights();
|
|
208
|
-
}, [content, syncEditorLineNumberHeights]);
|
|
209
|
-
|
|
210
|
-
useEffect(() => {
|
|
211
|
-
if (!shouldUseHighlightedEditor || viewMode !== "edit") return () => {};
|
|
212
|
-
const onResize = () => syncEditorLineNumberHeights();
|
|
213
|
-
window.addEventListener("resize", onResize);
|
|
214
|
-
return () => window.removeEventListener("resize", onResize);
|
|
215
|
-
}, [shouldUseHighlightedEditor, viewMode, syncEditorLineNumberHeights]);
|
|
194
|
+
useEditorLineNumberSync({
|
|
195
|
+
enabled: shouldUseHighlightedEditor && viewMode === "edit",
|
|
196
|
+
syncKey: `${normalizedPath}:${renderContent.length}:${highlightedEditorLines.length}`,
|
|
197
|
+
editorLineNumberRowRefs,
|
|
198
|
+
editorHighlightLineRefs,
|
|
199
|
+
});
|
|
216
200
|
|
|
217
201
|
useEffect(() => {
|
|
218
202
|
if (!isMarkdownFile && viewMode !== "edit") {
|
|
@@ -13,11 +13,7 @@ export const clampSelectionIndex = (value, maxValue) => {
|
|
|
13
13
|
export const countTextLines = (content) => {
|
|
14
14
|
const text = String(content || "");
|
|
15
15
|
if (!text) return 1;
|
|
16
|
-
|
|
17
|
-
for (let index = 0; index < text.length; index += 1) {
|
|
18
|
-
if (text.charCodeAt(index) === 10) lineCount += 1;
|
|
19
|
-
}
|
|
20
|
-
return lineCount;
|
|
16
|
+
return text.split(/\r\n|\r|\n/).length;
|
|
21
17
|
};
|
|
22
18
|
|
|
23
19
|
export const shouldUseSimpleEditorMode = ({
|
|
@@ -4,6 +4,8 @@ import htm from "https://esm.sh/htm";
|
|
|
4
4
|
import { PageHeader } from "../page-header.js";
|
|
5
5
|
import { LoadingSpinner } from "../loading-spinner.js";
|
|
6
6
|
import { ActionButton } from "../action-button.js";
|
|
7
|
+
import { PopActions } from "../pop-actions.js";
|
|
8
|
+
import { PaneShell } from "../pane-shell.js";
|
|
7
9
|
import { Badge } from "../badge.js";
|
|
8
10
|
import { useModels } from "./use-models.js";
|
|
9
11
|
import {
|
|
@@ -123,155 +125,157 @@ export const Models = ({ onRestartRequired = () => {}, agentId, embedded = false
|
|
|
123
125
|
);
|
|
124
126
|
|
|
125
127
|
const headerActions = html`
|
|
126
|
-
<${
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
128
|
+
<${PopActions} visible=${isDirty}>
|
|
129
|
+
<${ActionButton}
|
|
130
|
+
onClick=${cancelChanges}
|
|
131
|
+
disabled=${saving}
|
|
132
|
+
tone="secondary"
|
|
133
|
+
size="sm"
|
|
134
|
+
idleLabel="Cancel"
|
|
135
|
+
className="text-xs"
|
|
136
|
+
/>
|
|
137
|
+
<${ActionButton}
|
|
138
|
+
onClick=${saveAll}
|
|
139
|
+
disabled=${saving}
|
|
140
|
+
loading=${saving}
|
|
141
|
+
loadingMode="inline"
|
|
142
|
+
tone="primary"
|
|
143
|
+
size="sm"
|
|
144
|
+
idleLabel="Save changes"
|
|
145
|
+
loadingLabel="Saving…"
|
|
146
|
+
className="text-xs"
|
|
147
|
+
/>
|
|
148
|
+
</${PopActions}>
|
|
144
149
|
`;
|
|
145
150
|
|
|
146
151
|
if (!ready) {
|
|
147
|
-
|
|
148
|
-
<div class="
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
title="Models"
|
|
153
|
-
actions=${html`
|
|
154
|
-
<${ActionButton}
|
|
155
|
-
disabled=${true}
|
|
156
|
-
tone="primary"
|
|
157
|
-
size="sm"
|
|
158
|
-
idleLabel="Save changes"
|
|
159
|
-
className="transition-all"
|
|
160
|
-
/>
|
|
161
|
-
`}
|
|
162
|
-
/>
|
|
163
|
-
`
|
|
164
|
-
: null}
|
|
165
|
-
<div class="bg-surface border border-border rounded-xl p-4">
|
|
166
|
-
<div class="flex items-center gap-2 text-sm text-gray-400">
|
|
167
|
-
<${LoadingSpinner} className="h-4 w-4" />
|
|
168
|
-
Loading model settings...
|
|
169
|
-
</div>
|
|
152
|
+
const loadingBody = html`
|
|
153
|
+
<div class="bg-surface border border-border rounded-xl p-4">
|
|
154
|
+
<div class="flex items-center gap-2 text-sm text-gray-400">
|
|
155
|
+
<${LoadingSpinner} className="h-4 w-4" />
|
|
156
|
+
Loading model settings...
|
|
170
157
|
</div>
|
|
171
158
|
</div>
|
|
172
159
|
`;
|
|
160
|
+
if (embedded) return loadingBody;
|
|
161
|
+
return html`
|
|
162
|
+
<${PaneShell}
|
|
163
|
+
header=${html`<${PageHeader} title="Models" />`}
|
|
164
|
+
>
|
|
165
|
+
${loadingBody}
|
|
166
|
+
</${PaneShell}>
|
|
167
|
+
`;
|
|
173
168
|
}
|
|
174
169
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
170
|
+
const bodyContent = html`
|
|
171
|
+
<!-- Configured Models -->
|
|
172
|
+
<div class="bg-surface border border-border rounded-xl p-4 space-y-3">
|
|
173
|
+
<h2 class="card-label">Available Models</h2>
|
|
174
|
+
|
|
175
|
+
${configuredModelEntries.length === 0
|
|
176
|
+
? html`<p class="text-xs text-gray-500">
|
|
177
|
+
No models configured. Add a model below.
|
|
178
|
+
</p>`
|
|
179
179
|
: html`
|
|
180
|
-
<div class="
|
|
181
|
-
${
|
|
180
|
+
<div class="space-y-1">
|
|
181
|
+
${configuredModelEntries.map(
|
|
182
|
+
(entry) => html`
|
|
183
|
+
<div
|
|
184
|
+
class="flex items-center justify-between py-1.5 px-2 rounded-lg hover:bg-white/5"
|
|
185
|
+
>
|
|
186
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
187
|
+
<span class="text-sm text-gray-200 truncate"
|
|
188
|
+
>${entry.label}</span
|
|
189
|
+
>
|
|
190
|
+
${entry.isPrimary
|
|
191
|
+
? html`<${Badge} tone="cyan">Primary</${Badge}>`
|
|
192
|
+
: entry.hasAuth
|
|
193
|
+
? html`
|
|
194
|
+
<button
|
|
195
|
+
onclick=${() => setPrimaryModel(entry.key)}
|
|
196
|
+
class="text-xs px-2 py-0.5 rounded-full text-gray-500 hover:text-gray-300 hover:bg-white/5"
|
|
197
|
+
>
|
|
198
|
+
Set primary
|
|
199
|
+
</button>
|
|
200
|
+
`
|
|
201
|
+
: html`<${Badge} tone="warning">Needs auth</${Badge}>`}
|
|
202
|
+
</div>
|
|
203
|
+
<button
|
|
204
|
+
onclick=${() => removeModel(entry.key)}
|
|
205
|
+
class="text-xs text-gray-600 hover:text-red-400 shrink-0 px-1"
|
|
206
|
+
>
|
|
207
|
+
Remove
|
|
208
|
+
</button>
|
|
209
|
+
</div>
|
|
210
|
+
`,
|
|
211
|
+
)}
|
|
182
212
|
</div>
|
|
183
213
|
`}
|
|
184
214
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
215
|
+
<div class="space-y-2">
|
|
216
|
+
<${SearchableModelPicker}
|
|
217
|
+
options=${pickerModels}
|
|
218
|
+
popularModels=${popularPickerModels}
|
|
219
|
+
placeholder="Add model..."
|
|
220
|
+
onSelect=${(modelKey) => {
|
|
221
|
+
addModel(modelKey);
|
|
222
|
+
if (!primary) setPrimaryModel(modelKey);
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
</div>
|
|
188
226
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
<div
|
|
198
|
-
class="flex items-center justify-between py-1.5 px-2 rounded-lg hover:bg-white/5"
|
|
199
|
-
>
|
|
200
|
-
<div class="flex items-center gap-2 min-w-0">
|
|
201
|
-
<span class="text-sm text-gray-200 truncate"
|
|
202
|
-
>${entry.label}</span
|
|
203
|
-
>
|
|
204
|
-
${entry.isPrimary
|
|
205
|
-
? html`<${Badge} tone="cyan">Primary</${Badge}>`
|
|
206
|
-
: entry.hasAuth
|
|
207
|
-
? html`
|
|
208
|
-
<button
|
|
209
|
-
onclick=${() => setPrimaryModel(entry.key)}
|
|
210
|
-
class="text-xs px-2 py-0.5 rounded-full text-gray-500 hover:text-gray-300 hover:bg-white/5"
|
|
211
|
-
>
|
|
212
|
-
Set primary
|
|
213
|
-
</button>
|
|
214
|
-
`
|
|
215
|
-
: html`<${Badge} tone="warning">Needs auth</${Badge}>`}
|
|
216
|
-
</div>
|
|
217
|
-
<button
|
|
218
|
-
onclick=${() => removeModel(entry.key)}
|
|
219
|
-
class="text-xs text-gray-600 hover:text-red-400 shrink-0 px-1"
|
|
220
|
-
>
|
|
221
|
-
Remove
|
|
222
|
-
</button>
|
|
223
|
-
</div>
|
|
224
|
-
`,
|
|
225
|
-
)}
|
|
226
|
-
</div>
|
|
227
|
-
`}
|
|
227
|
+
${loading
|
|
228
|
+
? html`<p class="text-xs text-gray-600">
|
|
229
|
+
Loading model catalog...
|
|
230
|
+
</p>`
|
|
231
|
+
: error
|
|
232
|
+
? html`<p class="text-xs text-gray-600">${error}</p>`
|
|
233
|
+
: null}
|
|
234
|
+
</div>
|
|
228
235
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
<!-- Provider Auth -->
|
|
237
|
+
${sortedProviders.length > 0
|
|
238
|
+
? html`
|
|
239
|
+
<div class="space-y-3">
|
|
240
|
+
<h2 class="font-semibold text-base">
|
|
241
|
+
Provider Authentication
|
|
242
|
+
</h2>
|
|
243
|
+
${sortedProviders.map(
|
|
244
|
+
(provider) => html`
|
|
245
|
+
<${ProviderAuthCard}
|
|
246
|
+
provider=${provider}
|
|
247
|
+
authProfiles=${authProfiles}
|
|
248
|
+
authOrder=${authOrder}
|
|
249
|
+
codexStatus=${codexStatus}
|
|
250
|
+
onEditProfile=${editProfile}
|
|
251
|
+
onEditAuthOrder=${editAuthOrder}
|
|
252
|
+
getProfileValue=${getProfileValue}
|
|
253
|
+
getEffectiveOrder=${getEffectiveOrder}
|
|
254
|
+
onRefreshCodex=${refreshCodexStatus}
|
|
255
|
+
/>
|
|
256
|
+
`,
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
`
|
|
260
|
+
: null}
|
|
261
|
+
`;
|
|
240
262
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
263
|
+
if (embedded) {
|
|
264
|
+
return html`
|
|
265
|
+
<div class="space-y-4">
|
|
266
|
+
<div class="flex items-center justify-end gap-2">
|
|
267
|
+
${headerActions}
|
|
268
|
+
</div>
|
|
269
|
+
${bodyContent}
|
|
248
270
|
</div>
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
249
273
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
</h2>
|
|
257
|
-
${sortedProviders.map(
|
|
258
|
-
(provider) => html`
|
|
259
|
-
<${ProviderAuthCard}
|
|
260
|
-
provider=${provider}
|
|
261
|
-
authProfiles=${authProfiles}
|
|
262
|
-
authOrder=${authOrder}
|
|
263
|
-
codexStatus=${codexStatus}
|
|
264
|
-
onEditProfile=${editProfile}
|
|
265
|
-
onEditAuthOrder=${editAuthOrder}
|
|
266
|
-
getProfileValue=${getProfileValue}
|
|
267
|
-
getEffectiveOrder=${getEffectiveOrder}
|
|
268
|
-
onRefreshCodex=${refreshCodexStatus}
|
|
269
|
-
/>
|
|
270
|
-
`,
|
|
271
|
-
)}
|
|
272
|
-
</div>
|
|
273
|
-
`
|
|
274
|
-
: null}
|
|
275
|
-
</div>
|
|
274
|
+
return html`
|
|
275
|
+
<${PaneShell}
|
|
276
|
+
header=${html`<${PageHeader} title="Models" actions=${headerActions} />`}
|
|
277
|
+
>
|
|
278
|
+
${bodyContent}
|
|
279
|
+
</${PaneShell}>
|
|
276
280
|
`;
|
|
277
281
|
};
|
|
@@ -375,7 +375,14 @@ export const ProviderAuthCard = ({
|
|
|
375
375
|
};
|
|
376
376
|
if (currentValue?.expires) cred.expires = currentValue.expires;
|
|
377
377
|
onEditProfile(profileId, cred);
|
|
378
|
-
|
|
378
|
+
const savedProfile =
|
|
379
|
+
authProfiles.find((p) => p.id === profileId) || null;
|
|
380
|
+
const isReverted =
|
|
381
|
+
getCredentialValue(cred) ===
|
|
382
|
+
getCredentialValue(savedProfile);
|
|
383
|
+
if (isReverted && hasMultipleModes) {
|
|
384
|
+
onEditAuthOrder(provider, savedOrder);
|
|
385
|
+
} else if (hasMultipleModes && newVal && !isActive) {
|
|
379
386
|
handleSetActive(mode);
|
|
380
387
|
}
|
|
381
388
|
}}
|
|
@@ -92,10 +92,13 @@ export const useModels = (agentId) => {
|
|
|
92
92
|
refresh();
|
|
93
93
|
}, [agentId]);
|
|
94
94
|
|
|
95
|
+
const stableStringify = (obj) =>
|
|
96
|
+
JSON.stringify(Object.keys(obj).sort().reduce((acc, k) => { acc[k] = obj[k]; return acc; }, {}));
|
|
97
|
+
|
|
95
98
|
const modelConfigDirty =
|
|
96
99
|
primary !== savedPrimaryRef.current ||
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
stableStringify(configuredModels) !==
|
|
101
|
+
stableStringify(savedConfiguredRef.current);
|
|
99
102
|
|
|
100
103
|
const authDirty = (() => {
|
|
101
104
|
const hasProfileChanges = Object.entries(profileEdits).some(
|
|
@@ -155,13 +158,37 @@ export const useModels = (agentId) => {
|
|
|
155
158
|
[updateCache],
|
|
156
159
|
);
|
|
157
160
|
|
|
158
|
-
const editProfile = useCallback(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
const editProfile = useCallback(
|
|
162
|
+
(profileId, credential) => {
|
|
163
|
+
const existing = authProfiles.find((p) => p.id === profileId);
|
|
164
|
+
if (getCredentialValue(credential) === getCredentialValue(existing)) {
|
|
165
|
+
setProfileEdits((prev) => {
|
|
166
|
+
const next = { ...prev };
|
|
167
|
+
delete next[profileId];
|
|
168
|
+
return next;
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
setProfileEdits((prev) => ({ ...prev, [profileId]: credential }));
|
|
173
|
+
},
|
|
174
|
+
[authProfiles],
|
|
175
|
+
);
|
|
161
176
|
|
|
162
|
-
const editAuthOrder = useCallback(
|
|
163
|
-
|
|
164
|
-
|
|
177
|
+
const editAuthOrder = useCallback(
|
|
178
|
+
(provider, orderedIds) => {
|
|
179
|
+
const existing = authOrder[provider] || null;
|
|
180
|
+
if (JSON.stringify(orderedIds) === JSON.stringify(existing)) {
|
|
181
|
+
setOrderEdits((prev) => {
|
|
182
|
+
const next = { ...prev };
|
|
183
|
+
delete next[provider];
|
|
184
|
+
return next;
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
setOrderEdits((prev) => ({ ...prev, [provider]: orderedIds }));
|
|
189
|
+
},
|
|
190
|
+
[authOrder],
|
|
191
|
+
);
|
|
165
192
|
|
|
166
193
|
const getProfileValue = useCallback(
|
|
167
194
|
(profileId) => {
|
|
@@ -43,7 +43,9 @@ const PairingRow = ({ pairing, onApprove, onReject }) => {
|
|
|
43
43
|
<div class="font-medium text-sm">
|
|
44
44
|
${pairing.code || pairing.id || "Pending request"}
|
|
45
45
|
</div>
|
|
46
|
-
<span
|
|
46
|
+
<span
|
|
47
|
+
class="text-[11px] px-2 py-0.5 rounded-full border border-border text-gray-400"
|
|
48
|
+
>
|
|
47
49
|
Request
|
|
48
50
|
</span>
|
|
49
51
|
</div>
|
|
@@ -54,14 +56,18 @@ const PairingRow = ({ pairing, onApprove, onReject }) => {
|
|
|
54
56
|
<button
|
|
55
57
|
onclick=${handleApprove}
|
|
56
58
|
disabled=${!!busyAction}
|
|
57
|
-
class="ac-btn-green text-xs font-medium px-3 py-1.5 rounded-lg ${busyAction
|
|
59
|
+
class="ac-btn-green text-xs font-medium px-3 py-1.5 rounded-lg ${busyAction
|
|
60
|
+
? "opacity-50 cursor-not-allowed"
|
|
61
|
+
: ""}"
|
|
58
62
|
>
|
|
59
63
|
${busyAction === "approve" ? "Approving..." : "Approve"}
|
|
60
64
|
</button>
|
|
61
65
|
<button
|
|
62
66
|
onclick=${handleReject}
|
|
63
67
|
disabled=${!!busyAction}
|
|
64
|
-
class="ac-btn-secondary text-xs font-medium px-3 py-1.5 rounded-lg ${busyAction
|
|
68
|
+
class="ac-btn-secondary text-xs font-medium px-3 py-1.5 rounded-lg ${busyAction
|
|
69
|
+
? "opacity-50 cursor-not-allowed"
|
|
70
|
+
: ""}"
|
|
65
71
|
>
|
|
66
72
|
${busyAction === "reject" ? "Rejecting..." : "Reject"}
|
|
67
73
|
</button>
|
|
@@ -83,15 +89,20 @@ export const WelcomePairingStep = ({
|
|
|
83
89
|
onSkip,
|
|
84
90
|
}) => {
|
|
85
91
|
const channelMeta = kChannelMeta[channel] || {
|
|
86
|
-
label: channel
|
|
92
|
+
label: channel
|
|
93
|
+
? channel.charAt(0).toUpperCase() + channel.slice(1)
|
|
94
|
+
: "Channel",
|
|
87
95
|
iconSrc: "",
|
|
88
96
|
};
|
|
89
97
|
const channelInfo = channels?.[channel];
|
|
90
98
|
|
|
91
99
|
if (!channel) {
|
|
92
100
|
return html`
|
|
93
|
-
<div
|
|
94
|
-
|
|
101
|
+
<div
|
|
102
|
+
class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
|
|
103
|
+
>
|
|
104
|
+
Missing channel configuration. Go back and add a Telegram or Discord bot
|
|
105
|
+
token.
|
|
95
106
|
</div>
|
|
96
107
|
`;
|
|
97
108
|
}
|
|
@@ -101,12 +112,16 @@ export const WelcomePairingStep = ({
|
|
|
101
112
|
<div class="min-h-[300px] pb-6 px-6 flex flex-col">
|
|
102
113
|
<div class="flex-1 flex items-center justify-center text-center">
|
|
103
114
|
<div class="space-y-3 max-w-xl mx-auto">
|
|
104
|
-
<p class="text-sm font-medium text-green-300 mb-12"
|
|
115
|
+
<p class="text-sm font-medium text-green-300 mb-12">
|
|
116
|
+
🎉 Setup complete
|
|
117
|
+
</p>
|
|
105
118
|
<p class="text-xs text-gray-300">
|
|
106
|
-
Your ${channelMeta.label} channel is connected. You can switch to
|
|
119
|
+
Your ${channelMeta.label} channel is connected. You can switch to
|
|
120
|
+
${channelMeta.label} and start using your agent now.
|
|
107
121
|
</p>
|
|
108
122
|
<p class="text-xs text-gray-500 font-normal opacity-85">
|
|
109
|
-
Continue to the dashboard to explore extras like Google Workspace
|
|
123
|
+
Continue to the dashboard to explore extras like Google Workspace
|
|
124
|
+
and additional integrations.
|
|
110
125
|
</p>
|
|
111
126
|
</div>
|
|
112
127
|
</div>
|
|
@@ -124,60 +139,74 @@ export const WelcomePairingStep = ({
|
|
|
124
139
|
<div class="min-h-[300px] pb-6 flex flex-col gap-3">
|
|
125
140
|
<div class="flex items-center justify-end gap-2">
|
|
126
141
|
<${Badge} tone="warning"
|
|
127
|
-
>${
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
>${
|
|
143
|
+
loading
|
|
144
|
+
? "Checking..."
|
|
145
|
+
: pairings.length > 0
|
|
146
|
+
? "Pairing request detected"
|
|
147
|
+
: "Awaiting pairing"
|
|
148
|
+
}</${Badge}
|
|
132
149
|
>
|
|
133
150
|
</div>
|
|
134
151
|
|
|
135
|
-
${
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
152
|
+
${
|
|
153
|
+
pairings.length > 0
|
|
154
|
+
? html`<div class="flex-1 flex items-center">
|
|
155
|
+
<div class="w-full">
|
|
156
|
+
${pairings.map(
|
|
157
|
+
(pairing) =>
|
|
158
|
+
html`<${PairingRow}
|
|
159
|
+
key=${pairing.id}
|
|
160
|
+
pairing=${pairing}
|
|
161
|
+
onApprove=${onApprove}
|
|
162
|
+
onReject=${onReject}
|
|
163
|
+
/>`,
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
</div>`
|
|
167
|
+
: html`<div
|
|
168
|
+
class="flex-1 flex items-center justify-center text-center py-4"
|
|
169
|
+
>
|
|
170
|
+
<div class="space-y-4">
|
|
171
|
+
${channelMeta.iconSrc
|
|
172
|
+
? html`<img
|
|
173
|
+
src=${channelMeta.iconSrc}
|
|
174
|
+
alt=${channelMeta.label}
|
|
175
|
+
class="w-8 h-8 mx-auto rounded-md"
|
|
176
|
+
/>`
|
|
177
|
+
: null}
|
|
178
|
+
<p class="text-gray-300 text-sm">
|
|
179
|
+
Send a message to your ${channelMeta.label} bot
|
|
180
|
+
</p>
|
|
181
|
+
<p class="text-gray-600 text-xs">
|
|
182
|
+
The pairing request will appear here in 5-10 seconds
|
|
183
|
+
</p>
|
|
184
|
+
</div>
|
|
185
|
+
</div>`
|
|
186
|
+
}
|
|
166
187
|
|
|
167
|
-
${
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
${
|
|
189
|
+
error
|
|
190
|
+
? html`<div
|
|
191
|
+
class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
|
|
192
|
+
>
|
|
193
|
+
${error}
|
|
194
|
+
</div>`
|
|
195
|
+
: null
|
|
196
|
+
}
|
|
197
|
+
${
|
|
198
|
+
pairings.length === 0
|
|
199
|
+
? html`<div class="pt-3 text-center">
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
onclick=${onSkip}
|
|
203
|
+
class="ac-tip-link text-xs font-medium"
|
|
204
|
+
>
|
|
205
|
+
Skip pairing for now
|
|
206
|
+
</button>
|
|
207
|
+
</div>`
|
|
208
|
+
: null
|
|
209
|
+
}
|
|
181
210
|
</div>
|
|
182
211
|
`;
|
|
183
212
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
* Shared layout shell for pages that need a fixed header with a
|
|
8
|
+
* separately scrollable body. The header stays pinned at the top
|
|
9
|
+
* while body content scrolls underneath.
|
|
10
|
+
*
|
|
11
|
+
* @param {preact.ComponentChildren} props.header Content rendered in the fixed header area.
|
|
12
|
+
* @param {preact.ComponentChildren} props.children Content rendered in the scrollable body.
|
|
13
|
+
*/
|
|
14
|
+
export const PaneShell = ({ header, children }) => html`
|
|
15
|
+
<div class="ac-pane-shell">
|
|
16
|
+
<div class="ac-pane-header">
|
|
17
|
+
<div class="ac-pane-header-content">
|
|
18
|
+
${header}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="ac-pane-body">
|
|
22
|
+
<div class="ac-pane-body-content">
|
|
23
|
+
${children}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
`;
|