@chrysb/alphaclaw 0.6.2-beta.6 → 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 +35 -4
- 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 +13 -42
- package/lib/public/js/components/cron-tab/cron-helpers.js +48 -0
- package/lib/public/js/components/cron-tab/cron-insights-panel.js +8 -39
- package/lib/public/js/components/cron-tab/cron-run-history-panel.js +8 -9
- package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +4 -22
- package/lib/public/js/components/envars.js +187 -46
- 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/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
|
@@ -8,8 +8,13 @@ import {
|
|
|
8
8
|
import htm from "https://esm.sh/htm";
|
|
9
9
|
import { SegmentedControl } from "../segmented-control.js";
|
|
10
10
|
import { Tooltip } from "../tooltip.js";
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
formatCost,
|
|
13
|
+
formatCronScheduleLabel,
|
|
14
|
+
formatTokenCount,
|
|
15
|
+
getCronRunEstimatedCost,
|
|
16
|
+
getCronRunTotalTokens,
|
|
17
|
+
} from "./cron-helpers.js";
|
|
13
18
|
import { readUiSettings, updateUiSettings } from "../../lib/ui-settings.js";
|
|
14
19
|
import {
|
|
15
20
|
classifyRepeatingJobs,
|
|
@@ -104,40 +109,6 @@ const formatUpcomingTime = (timestampMs) => {
|
|
|
104
109
|
});
|
|
105
110
|
};
|
|
106
111
|
|
|
107
|
-
const getRunTotalTokens = (entry = {}) => {
|
|
108
|
-
const usage = entry?.usage || {};
|
|
109
|
-
const candidates = [
|
|
110
|
-
usage?.total_tokens,
|
|
111
|
-
usage?.totalTokens,
|
|
112
|
-
entry?.total_tokens,
|
|
113
|
-
entry?.totalTokens,
|
|
114
|
-
];
|
|
115
|
-
for (const candidate of candidates) {
|
|
116
|
-
const numericValue = Number(candidate);
|
|
117
|
-
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
118
|
-
}
|
|
119
|
-
return 0;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const getRunEstimatedCost = (entry = {}) => {
|
|
123
|
-
const usage = entry?.usage || {};
|
|
124
|
-
const candidates = [
|
|
125
|
-
entry?.estimatedCost,
|
|
126
|
-
entry?.estimated_cost,
|
|
127
|
-
usage?.estimatedCost,
|
|
128
|
-
usage?.estimated_cost,
|
|
129
|
-
usage?.totalCost,
|
|
130
|
-
usage?.total_cost,
|
|
131
|
-
usage?.costUsd,
|
|
132
|
-
usage?.cost,
|
|
133
|
-
];
|
|
134
|
-
for (const candidate of candidates) {
|
|
135
|
-
const numericValue = Number(candidate);
|
|
136
|
-
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
137
|
-
}
|
|
138
|
-
return null;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
112
|
const buildRunSummaryByJobId = ({
|
|
142
113
|
runsByJobId = {},
|
|
143
114
|
nowMs = Date.now(),
|
|
@@ -158,11 +129,11 @@ const buildRunSummaryByJobId = ({
|
|
|
158
129
|
});
|
|
159
130
|
const runCount = recentEntries.length;
|
|
160
131
|
const totalTokens = recentEntries.reduce(
|
|
161
|
-
(sum, entry) => sum + Number(
|
|
132
|
+
(sum, entry) => sum + Number(getCronRunTotalTokens(entry) || 0),
|
|
162
133
|
0,
|
|
163
134
|
);
|
|
164
135
|
const totalCost = recentEntries.reduce((sum, entry) => {
|
|
165
|
-
const cost =
|
|
136
|
+
const cost = getCronRunEstimatedCost(entry);
|
|
166
137
|
return sum + Number(cost == null ? 0 : cost);
|
|
167
138
|
}, 0);
|
|
168
139
|
accumulator[String(jobId || "")] = {
|
|
@@ -274,8 +245,8 @@ const buildJobTooltipText = ({
|
|
|
274
245
|
const runCount7d = Number(runSummary7d?.runCount || 0);
|
|
275
246
|
const avgTokensPerRun7d = Number(runSummary7d?.avgTokensPerRun || 0);
|
|
276
247
|
const avgCostPerRun7d = Number(runSummary7d?.avgCostPerRun || 0);
|
|
277
|
-
const slotRunTokens =
|
|
278
|
-
const slotRunCost =
|
|
248
|
+
const slotRunTokens = getCronRunTotalTokens(slotRun || {});
|
|
249
|
+
const slotRunCost = getCronRunEstimatedCost(slotRun || {});
|
|
279
250
|
const slotRunStatus = String(slotRun?.status || "")
|
|
280
251
|
.trim()
|
|
281
252
|
.toLowerCase();
|
|
@@ -428,7 +399,7 @@ export const CronCalendar = ({
|
|
|
428
399
|
if (!job || job.enabled === false) return;
|
|
429
400
|
const isPastSlot = Number(slot?.scheduledAtMs || 0) <= nowMs;
|
|
430
401
|
if (isPastSlot) {
|
|
431
|
-
const slotRunTokens =
|
|
402
|
+
const slotRunTokens = getCronRunTotalTokens(runBySlotKey[slot.key] || {});
|
|
432
403
|
if (slotRunTokens > 0) values.push(slotRunTokens);
|
|
433
404
|
return;
|
|
434
405
|
}
|
|
@@ -460,7 +431,7 @@ export const CronCalendar = ({
|
|
|
460
431
|
const enabled = job?.enabled !== false;
|
|
461
432
|
const isPastSlot = Number(slot?.scheduledAtMs || 0) <= nowMs;
|
|
462
433
|
if (isPastSlot) {
|
|
463
|
-
const slotRunTokens =
|
|
434
|
+
const slotRunTokens = getCronRunTotalTokens(
|
|
464
435
|
runBySlotKey[String(slot?.key || "")] || {},
|
|
465
436
|
);
|
|
466
437
|
return classifyTokenTier({
|
|
@@ -276,6 +276,54 @@ export const getCronJobHealthClassName = (health = "") => {
|
|
|
276
276
|
|
|
277
277
|
export const formatTokenCount = (value) => formatInteger(Number(value || 0));
|
|
278
278
|
export const formatCost = (value) => formatUsd(Number(value || 0));
|
|
279
|
+
export const getCronRunTotalTokens = (entry = {}) => {
|
|
280
|
+
const usage = entry?.usage || {};
|
|
281
|
+
const componentCandidates = [
|
|
282
|
+
usage?.input_tokens,
|
|
283
|
+
usage?.inputTokens,
|
|
284
|
+
usage?.output_tokens,
|
|
285
|
+
usage?.outputTokens,
|
|
286
|
+
usage?.cache_read_tokens,
|
|
287
|
+
usage?.cacheReadTokens,
|
|
288
|
+
usage?.cache_write_tokens,
|
|
289
|
+
usage?.cacheWriteTokens,
|
|
290
|
+
];
|
|
291
|
+
const componentTotal = componentCandidates.reduce((sum, candidate) => {
|
|
292
|
+
const numericValue = Number(candidate);
|
|
293
|
+
if (!Number.isFinite(numericValue) || numericValue < 0) return sum;
|
|
294
|
+
return sum + numericValue;
|
|
295
|
+
}, 0);
|
|
296
|
+
if (componentTotal > 0) return componentTotal;
|
|
297
|
+
const totalCandidates = [
|
|
298
|
+
usage?.total_tokens,
|
|
299
|
+
usage?.totalTokens,
|
|
300
|
+
entry?.total_tokens,
|
|
301
|
+
entry?.totalTokens,
|
|
302
|
+
];
|
|
303
|
+
for (const candidate of totalCandidates) {
|
|
304
|
+
const numericValue = Number(candidate);
|
|
305
|
+
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
306
|
+
}
|
|
307
|
+
return 0;
|
|
308
|
+
};
|
|
309
|
+
export const getCronRunEstimatedCost = (entry = {}) => {
|
|
310
|
+
const usage = entry?.usage || {};
|
|
311
|
+
const candidates = [
|
|
312
|
+
entry?.estimatedCost,
|
|
313
|
+
entry?.estimated_cost,
|
|
314
|
+
usage?.estimatedCost,
|
|
315
|
+
usage?.estimated_cost,
|
|
316
|
+
usage?.totalCost,
|
|
317
|
+
usage?.total_cost,
|
|
318
|
+
usage?.costUsd,
|
|
319
|
+
usage?.cost,
|
|
320
|
+
];
|
|
321
|
+
for (const candidate of candidates) {
|
|
322
|
+
const numericValue = Number(candidate);
|
|
323
|
+
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
};
|
|
279
327
|
const hasHeartbeatOnlySummary = (job = {}) => {
|
|
280
328
|
const state = job?.state || {};
|
|
281
329
|
try {
|
|
@@ -3,7 +3,12 @@ import { useMemo, useState } from "https://esm.sh/preact/hooks";
|
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import { SegmentedControl } from "../segmented-control.js";
|
|
5
5
|
import { Badge } from "../badge.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
formatCost,
|
|
8
|
+
formatTokenCount,
|
|
9
|
+
getCronRunEstimatedCost,
|
|
10
|
+
getCronRunTotalTokens,
|
|
11
|
+
} from "./cron-helpers.js";
|
|
7
12
|
|
|
8
13
|
const html = htm.bind(h);
|
|
9
14
|
|
|
@@ -35,40 +40,6 @@ const formatRunCountLabel = (count = 0) => {
|
|
|
35
40
|
return `${countLabel} ${safeCount === 1 ? "run" : "runs"}`;
|
|
36
41
|
};
|
|
37
42
|
|
|
38
|
-
const getRunTotalTokens = (entry = {}) => {
|
|
39
|
-
const usage = entry?.usage || {};
|
|
40
|
-
const candidates = [
|
|
41
|
-
usage?.total_tokens,
|
|
42
|
-
usage?.totalTokens,
|
|
43
|
-
entry?.total_tokens,
|
|
44
|
-
entry?.totalTokens,
|
|
45
|
-
];
|
|
46
|
-
for (const candidate of candidates) {
|
|
47
|
-
const numericValue = Number(candidate);
|
|
48
|
-
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
49
|
-
}
|
|
50
|
-
return 0;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const getRunEstimatedCost = (entry = {}) => {
|
|
54
|
-
const usage = entry?.usage || {};
|
|
55
|
-
const candidates = [
|
|
56
|
-
entry?.estimatedCost,
|
|
57
|
-
entry?.estimated_cost,
|
|
58
|
-
usage?.estimatedCost,
|
|
59
|
-
usage?.estimated_cost,
|
|
60
|
-
usage?.totalCost,
|
|
61
|
-
usage?.total_cost,
|
|
62
|
-
usage?.costUsd,
|
|
63
|
-
usage?.cost,
|
|
64
|
-
];
|
|
65
|
-
for (const candidate of candidates) {
|
|
66
|
-
const numericValue = Number(candidate);
|
|
67
|
-
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
43
|
const readDeliveryMode = (job = null) =>
|
|
73
44
|
String(job?.delivery?.mode || job?.deliveryMode || "none")
|
|
74
45
|
.trim()
|
|
@@ -137,10 +108,8 @@ const buildInsightMetrics = ({
|
|
|
137
108
|
return;
|
|
138
109
|
}
|
|
139
110
|
metricsByJobId[jobId].runCount += 1;
|
|
140
|
-
metricsByJobId[jobId].totalTokens += Number(
|
|
141
|
-
|
|
142
|
-
);
|
|
143
|
-
const estimatedCost = getRunEstimatedCost(entry);
|
|
111
|
+
metricsByJobId[jobId].totalTokens += Number(getCronRunTotalTokens(entry) || 0);
|
|
112
|
+
const estimatedCost = getCronRunEstimatedCost(entry);
|
|
144
113
|
if (estimatedCost != null) {
|
|
145
114
|
metricsByJobId[jobId].hasCostData = true;
|
|
146
115
|
metricsByJobId[jobId].totalCost += Number(estimatedCost || 0);
|
|
@@ -5,7 +5,12 @@ import {
|
|
|
5
5
|
formatDurationCompactMs,
|
|
6
6
|
formatLocaleDateTimeWithTodayTime,
|
|
7
7
|
} from "../../lib/format.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
formatCost,
|
|
10
|
+
formatTokenCount,
|
|
11
|
+
getCronRunEstimatedCost,
|
|
12
|
+
getCronRunTotalTokens,
|
|
13
|
+
} from "./cron-helpers.js";
|
|
9
14
|
|
|
10
15
|
const html = htm.bind(h);
|
|
11
16
|
const runStatusClassName = (status = "") => {
|
|
@@ -19,10 +24,6 @@ const runStatusClassName = (status = "") => {
|
|
|
19
24
|
};
|
|
20
25
|
const runDeliveryLabel = (run) =>
|
|
21
26
|
String(run?.deliveryStatus || "not-requested");
|
|
22
|
-
const getRunEstimatedCost = (runEntry = {}) => {
|
|
23
|
-
const parsed = Number(runEntry?.estimatedCost);
|
|
24
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
25
|
-
};
|
|
26
27
|
const formatOverviewTimestamp = (timestampMs) =>
|
|
27
28
|
formatLocaleDateTimeWithTodayTime(timestampMs, {
|
|
28
29
|
fallback: "—",
|
|
@@ -100,10 +101,8 @@ const renderEntryRow = ({
|
|
|
100
101
|
const runOutputTokens = Number(
|
|
101
102
|
runUsage?.output_tokens ?? runUsage?.outputTokens ?? 0,
|
|
102
103
|
);
|
|
103
|
-
const runTokens =
|
|
104
|
-
|
|
105
|
-
);
|
|
106
|
-
const runEstimatedCost = getRunEstimatedCost(runEntry);
|
|
104
|
+
const runTokens = getCronRunTotalTokens(runEntry);
|
|
105
|
+
const runEstimatedCost = getCronRunEstimatedCost(runEntry);
|
|
107
106
|
const runTitle = String(runEntry?.jobName || "").trim();
|
|
108
107
|
const hasRunTitle = runTitle.length > 0;
|
|
109
108
|
const isDetail = variant === "detail";
|
|
@@ -2,7 +2,7 @@ import { h } from "https://esm.sh/preact";
|
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "https://esm.sh/preact/hooks";
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import { SegmentedControl } from "../segmented-control.js";
|
|
5
|
-
import { formatCost } from "./cron-helpers.js";
|
|
5
|
+
import { formatCost, getCronRunEstimatedCost } from "./cron-helpers.js";
|
|
6
6
|
|
|
7
7
|
const html = htm.bind(h);
|
|
8
8
|
|
|
@@ -64,25 +64,6 @@ const getBucketConfig = (range = kRange7d) => {
|
|
|
64
64
|
};
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
const getEstimatedCostForEntry = (entry = {}) => {
|
|
68
|
-
const usage = entry?.usage || {};
|
|
69
|
-
const candidates = [
|
|
70
|
-
entry?.estimatedCost,
|
|
71
|
-
entry?.estimated_cost,
|
|
72
|
-
usage?.estimatedCost,
|
|
73
|
-
usage?.estimated_cost,
|
|
74
|
-
usage?.totalCost,
|
|
75
|
-
usage?.total_cost,
|
|
76
|
-
usage?.costUsd,
|
|
77
|
-
usage?.cost,
|
|
78
|
-
];
|
|
79
|
-
for (const candidate of candidates) {
|
|
80
|
-
const numericValue = Number(candidate);
|
|
81
|
-
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
82
|
-
}
|
|
83
|
-
return null;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
67
|
const buildTrendData = ({ bulkRunsByJobId = {}, nowMs = Date.now(), range = kRange7d } = {}) => {
|
|
87
68
|
const config = getBucketConfig(range);
|
|
88
69
|
const safeNowMs = Number.isFinite(Number(nowMs)) ? Number(nowMs) : Date.now();
|
|
@@ -129,7 +110,7 @@ const buildTrendData = ({ bulkRunsByJobId = {}, nowMs = Date.now(), range = kRan
|
|
|
129
110
|
if (!Number.isFinite(Number(bucketIndex))) return;
|
|
130
111
|
if (bucketIndex < 0 || bucketIndex >= config.bucketCount) return;
|
|
131
112
|
points[bucketIndex][status] += 1;
|
|
132
|
-
const estimatedCost =
|
|
113
|
+
const estimatedCost = getCronRunEstimatedCost(entry);
|
|
133
114
|
if (estimatedCost != null) {
|
|
134
115
|
points[bucketIndex].totalCost += estimatedCost;
|
|
135
116
|
points[bucketIndex].costCount += 1;
|
|
@@ -305,6 +286,7 @@ export const CronRunsTrendCard = ({
|
|
|
305
286
|
},
|
|
306
287
|
plugins: {
|
|
307
288
|
legend: {
|
|
289
|
+
position: "bottom",
|
|
308
290
|
labels: {
|
|
309
291
|
color: "rgba(209,213,219,1)",
|
|
310
292
|
boxWidth: 10,
|
|
@@ -339,7 +321,7 @@ export const CronRunsTrendCard = ({
|
|
|
339
321
|
return html`
|
|
340
322
|
<section class="bg-surface border border-border rounded-xl p-4 space-y-3">
|
|
341
323
|
<div class="flex items-center justify-between gap-2">
|
|
342
|
-
<h3 class="card-label cron-calendar-title">Run
|
|
324
|
+
<h3 class="card-label cron-calendar-title">Run Outcomes</h3>
|
|
343
325
|
<${SegmentedControl}
|
|
344
326
|
options=${kRanges}
|
|
345
327
|
value=${range}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useState,
|
|
4
|
+
useEffect,
|
|
5
|
+
useCallback,
|
|
6
|
+
useRef,
|
|
7
|
+
} from "https://esm.sh/preact/hooks";
|
|
3
8
|
import htm from "https://esm.sh/htm";
|
|
4
9
|
import { fetchEnvVars, saveEnvVars } from "../lib/api.js";
|
|
5
10
|
import { showToast } from "./toast.js";
|
|
6
11
|
import { SecretInput } from "./secret-input.js";
|
|
7
12
|
import { PageHeader } from "./page-header.js";
|
|
8
13
|
import { ActionButton } from "./action-button.js";
|
|
14
|
+
import { PopActions } from "./pop-actions.js";
|
|
15
|
+
import { PaneShell } from "./pane-shell.js";
|
|
9
16
|
import {
|
|
10
17
|
Brain2LineIcon,
|
|
11
18
|
ChatVoiceLineIcon,
|
|
@@ -44,7 +51,11 @@ const kFeatureIconByName = {
|
|
|
44
51
|
label: "Speech to text",
|
|
45
52
|
},
|
|
46
53
|
};
|
|
47
|
-
const normalizeEnvVarKey = (raw) =>
|
|
54
|
+
const normalizeEnvVarKey = (raw) =>
|
|
55
|
+
raw
|
|
56
|
+
.trim()
|
|
57
|
+
.toUpperCase()
|
|
58
|
+
.replace(/[^A-Z0-9_]/g, "_");
|
|
48
59
|
const kManagedChannelTokenPattern =
|
|
49
60
|
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN)(?:_[A-Z0-9_]+)?$/;
|
|
50
61
|
const stripSurroundingQuotes = (raw) => {
|
|
@@ -59,7 +70,11 @@ const stripSurroundingQuotes = (raw) => {
|
|
|
59
70
|
return value;
|
|
60
71
|
};
|
|
61
72
|
const isManagedChannelTokenKey = (key = "") =>
|
|
62
|
-
kManagedChannelTokenPattern.test(
|
|
73
|
+
kManagedChannelTokenPattern.test(
|
|
74
|
+
String(key || "")
|
|
75
|
+
.trim()
|
|
76
|
+
.toUpperCase(),
|
|
77
|
+
);
|
|
63
78
|
const getVarsSignature = (items) =>
|
|
64
79
|
JSON.stringify(
|
|
65
80
|
(items || [])
|
|
@@ -85,19 +100,119 @@ const sortCustomVarsAlphabetically = (items) => {
|
|
|
85
100
|
};
|
|
86
101
|
|
|
87
102
|
const kHintByKey = {
|
|
88
|
-
ANTHROPIC_API_KEY: html`from
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
ANTHROPIC_API_KEY: html`from${" "}
|
|
104
|
+
<a
|
|
105
|
+
href="https://console.anthropic.com"
|
|
106
|
+
target="_blank"
|
|
107
|
+
class="hover:underline"
|
|
108
|
+
style="color: var(--accent-link)"
|
|
109
|
+
>console.anthropic.com</a
|
|
110
|
+
>`,
|
|
111
|
+
ANTHROPIC_TOKEN: html`from
|
|
112
|
+
<code class="text-xs bg-black/30 px-1 rounded">claude setup-token</code>`,
|
|
113
|
+
OPENAI_API_KEY: html`from${" "}
|
|
114
|
+
<a
|
|
115
|
+
href="https://platform.openai.com"
|
|
116
|
+
target="_blank"
|
|
117
|
+
class="hover:underline"
|
|
118
|
+
style="color: var(--accent-link)"
|
|
119
|
+
>platform.openai.com</a
|
|
120
|
+
>`,
|
|
121
|
+
GEMINI_API_KEY: html`from${" "}
|
|
122
|
+
<a
|
|
123
|
+
href="https://aistudio.google.com"
|
|
124
|
+
target="_blank"
|
|
125
|
+
class="hover:underline"
|
|
126
|
+
style="color: var(--accent-link)"
|
|
127
|
+
>aistudio.google.com</a
|
|
128
|
+
>`,
|
|
129
|
+
ELEVENLABS_API_KEY: html`from${" "}
|
|
130
|
+
<a
|
|
131
|
+
href="https://elevenlabs.io"
|
|
132
|
+
target="_blank"
|
|
133
|
+
class="hover:underline"
|
|
134
|
+
style="color: var(--accent-link)"
|
|
135
|
+
>elevenlabs.io</a
|
|
136
|
+
>${" "} · ${" "}
|
|
137
|
+
<code class="text-xs bg-black/30 px-1 rounded">XI_API_KEY</code> also
|
|
138
|
+
supported`,
|
|
139
|
+
GITHUB_WORKSPACE_REPO: html`use
|
|
140
|
+
<code class="text-xs bg-black/30 px-1 rounded">owner/repo</code> or
|
|
141
|
+
<code class="text-xs bg-black/30 px-1 rounded"
|
|
142
|
+
>https://github.com/owner/repo</code
|
|
143
|
+
>`,
|
|
144
|
+
TELEGRAM_BOT_TOKEN: html`from${" "}
|
|
145
|
+
<a
|
|
146
|
+
href="https://t.me/BotFather"
|
|
147
|
+
target="_blank"
|
|
148
|
+
class="hover:underline"
|
|
149
|
+
style="color: var(--accent-link)"
|
|
150
|
+
>@BotFather</a
|
|
151
|
+
>
|
|
152
|
+
·
|
|
153
|
+
<a
|
|
154
|
+
href="https://docs.openclaw.ai/channels/telegram"
|
|
155
|
+
target="_blank"
|
|
156
|
+
class="hover:underline"
|
|
157
|
+
style="color: var(--accent-link)"
|
|
158
|
+
>full guide</a
|
|
159
|
+
>`,
|
|
160
|
+
DISCORD_BOT_TOKEN: html`from${" "}
|
|
161
|
+
<a
|
|
162
|
+
href="https://discord.com/developers/applications"
|
|
163
|
+
target="_blank"
|
|
164
|
+
class="hover:underline"
|
|
165
|
+
style="color: var(--accent-link)"
|
|
166
|
+
>developer portal</a
|
|
167
|
+
>
|
|
168
|
+
·
|
|
169
|
+
<a
|
|
170
|
+
href="https://docs.openclaw.ai/channels/discord"
|
|
171
|
+
target="_blank"
|
|
172
|
+
class="hover:underline"
|
|
173
|
+
style="color: var(--accent-link)"
|
|
174
|
+
>full guide</a
|
|
175
|
+
>`,
|
|
176
|
+
MISTRAL_API_KEY: html`from${" "}
|
|
177
|
+
<a
|
|
178
|
+
href="https://console.mistral.ai"
|
|
179
|
+
target="_blank"
|
|
180
|
+
class="hover:underline"
|
|
181
|
+
style="color: var(--accent-link)"
|
|
182
|
+
>console.mistral.ai</a
|
|
183
|
+
>`,
|
|
184
|
+
VOYAGE_API_KEY: html`from${" "}
|
|
185
|
+
<a
|
|
186
|
+
href="https://dash.voyageai.com"
|
|
187
|
+
target="_blank"
|
|
188
|
+
class="hover:underline"
|
|
189
|
+
style="color: var(--accent-link)"
|
|
190
|
+
>dash.voyageai.com</a
|
|
191
|
+
>`,
|
|
192
|
+
GROQ_API_KEY: html`from${" "}
|
|
193
|
+
<a
|
|
194
|
+
href="https://console.groq.com"
|
|
195
|
+
target="_blank"
|
|
196
|
+
class="hover:underline"
|
|
197
|
+
style="color: var(--accent-link)"
|
|
198
|
+
>console.groq.com</a
|
|
199
|
+
>`,
|
|
200
|
+
DEEPGRAM_API_KEY: html`from${" "}
|
|
201
|
+
<a
|
|
202
|
+
href="https://console.deepgram.com"
|
|
203
|
+
target="_blank"
|
|
204
|
+
class="hover:underline"
|
|
205
|
+
style="color: var(--accent-link)"
|
|
206
|
+
>console.deepgram.com</a
|
|
207
|
+
>`,
|
|
208
|
+
BRAVE_API_KEY: html`from${" "}
|
|
209
|
+
<a
|
|
210
|
+
href="https://brave.com/search/api/"
|
|
211
|
+
target="_blank"
|
|
212
|
+
class="hover:underline"
|
|
213
|
+
style="color: var(--accent-link)"
|
|
214
|
+
>brave.com/search/api</a
|
|
215
|
+
>${" "} — free tier available`,
|
|
101
216
|
};
|
|
102
217
|
|
|
103
218
|
const getHintContent = (envVar) => kHintByKey[envVar.key] || envVar.hint || "";
|
|
@@ -157,7 +272,8 @@ const EnvRow = ({ envVar, onChange, onDelete, disabled }) => {
|
|
|
157
272
|
? html`
|
|
158
273
|
<div class="flex items-center gap-2 mt-1 pl-3.5">
|
|
159
274
|
${featureIcons.map(
|
|
160
|
-
(feature) =>
|
|
275
|
+
(feature) =>
|
|
276
|
+
html`<${FeatureIcon} key=${feature} feature=${feature} />`,
|
|
161
277
|
)}
|
|
162
278
|
</div>
|
|
163
279
|
`
|
|
@@ -183,9 +299,7 @@ const EnvRow = ({ envVar, onChange, onDelete, disabled }) => {
|
|
|
183
299
|
</button>`
|
|
184
300
|
: null}
|
|
185
301
|
</div>
|
|
186
|
-
${hint
|
|
187
|
-
? html`<p class="text-xs text-gray-600 mt-1">${hint}</p>`
|
|
188
|
-
: null}
|
|
302
|
+
${hint ? html`<p class="text-xs text-gray-600 mt-1">${hint}</p>` : null}
|
|
189
303
|
</div>
|
|
190
304
|
</div>
|
|
191
305
|
`;
|
|
@@ -230,7 +344,9 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
230
344
|
|
|
231
345
|
const handleDelete = (key) => {
|
|
232
346
|
setVars((prev) => prev.filter((v) => v.key !== key));
|
|
233
|
-
setPendingCustomKeys((prev) =>
|
|
347
|
+
setPendingCustomKeys((prev) =>
|
|
348
|
+
prev.filter((pendingKey) => pendingKey !== key),
|
|
349
|
+
);
|
|
234
350
|
};
|
|
235
351
|
|
|
236
352
|
const handleSave = async () => {
|
|
@@ -348,7 +464,10 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
348
464
|
);
|
|
349
465
|
}
|
|
350
466
|
if (added) {
|
|
351
|
-
showToast(
|
|
467
|
+
showToast(
|
|
468
|
+
`Added ${added} variable${added !== 1 ? "s" : ""}`,
|
|
469
|
+
"success",
|
|
470
|
+
);
|
|
352
471
|
}
|
|
353
472
|
return;
|
|
354
473
|
}
|
|
@@ -410,7 +529,9 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
410
529
|
const nonPending = grouped.custom
|
|
411
530
|
.filter((item) => !pending.has(item.key))
|
|
412
531
|
.sort((a, b) => String(a?.key || "").localeCompare(String(b?.key || "")));
|
|
413
|
-
const pendingAtBottom = grouped.custom.filter((item) =>
|
|
532
|
+
const pendingAtBottom = grouped.custom.filter((item) =>
|
|
533
|
+
pending.has(item.key),
|
|
534
|
+
);
|
|
414
535
|
grouped.custom = [...nonPending, ...pendingAtBottom];
|
|
415
536
|
}
|
|
416
537
|
const aiSplit = splitAiVars(grouped.ai || []);
|
|
@@ -454,7 +575,9 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
454
575
|
`
|
|
455
576
|
: null}
|
|
456
577
|
${expanded
|
|
457
|
-
? html`<div class="divide-y divide-border border-t border-border"
|
|
578
|
+
? html`<div class="divide-y divide-border border-t border-border">
|
|
579
|
+
${renderEnvRows(hidden)}
|
|
580
|
+
</div>`
|
|
458
581
|
: null}
|
|
459
582
|
</div>
|
|
460
583
|
`;
|
|
@@ -470,33 +593,52 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
470
593
|
};
|
|
471
594
|
|
|
472
595
|
return html`
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
596
|
+
<${PaneShell}
|
|
597
|
+
header=${html`
|
|
598
|
+
<${PageHeader}
|
|
599
|
+
title="Envars"
|
|
600
|
+
actions=${html`
|
|
601
|
+
<${PopActions} visible=${dirty}>
|
|
602
|
+
<${ActionButton}
|
|
603
|
+
onClick=${load}
|
|
604
|
+
disabled=${saving}
|
|
605
|
+
tone="secondary"
|
|
606
|
+
size="sm"
|
|
607
|
+
idleLabel="Cancel"
|
|
608
|
+
className="text-xs"
|
|
609
|
+
/>
|
|
610
|
+
<${ActionButton}
|
|
611
|
+
onClick=${handleSave}
|
|
612
|
+
disabled=${saving}
|
|
613
|
+
loading=${saving}
|
|
614
|
+
loadingMode="inline"
|
|
615
|
+
tone="primary"
|
|
616
|
+
size="sm"
|
|
617
|
+
idleLabel="Save changes"
|
|
618
|
+
loadingLabel="Saving…"
|
|
619
|
+
className="text-xs"
|
|
620
|
+
/>
|
|
621
|
+
</${PopActions}>
|
|
622
|
+
`}
|
|
623
|
+
/>
|
|
624
|
+
`}
|
|
625
|
+
>
|
|
490
626
|
${kGroupOrder
|
|
491
627
|
.filter((g) => grouped[g]?.length)
|
|
492
628
|
.map((g) => renderGroupCard(g))}
|
|
493
629
|
|
|
494
|
-
<div
|
|
630
|
+
<div
|
|
631
|
+
class="bg-surface border border-border rounded-xl overflow-hidden"
|
|
632
|
+
>
|
|
495
633
|
<div class="flex items-center justify-between px-4 pt-3 pb-2">
|
|
496
634
|
<h3 class="card-label text-xs">Add Variable</h3>
|
|
497
|
-
<span class="text-xs" style="color: var(--text-dim)"
|
|
635
|
+
<span class="text-xs" style="color: var(--text-dim)"
|
|
636
|
+
>Paste KEY=VALUE or multiple lines</span
|
|
637
|
+
>
|
|
498
638
|
</div>
|
|
499
|
-
<div
|
|
639
|
+
<div
|
|
640
|
+
class="flex items-start gap-4 px-4 py-3 border-t border-border"
|
|
641
|
+
>
|
|
500
642
|
<div class="shrink-0" style="width: 200px">
|
|
501
643
|
<input
|
|
502
644
|
type="text"
|
|
@@ -527,7 +669,6 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
527
669
|
</div>
|
|
528
670
|
</div>
|
|
529
671
|
</div>
|
|
530
|
-
|
|
531
|
-
</div>
|
|
672
|
+
</${PaneShell}>
|
|
532
673
|
`;
|
|
533
674
|
};
|