@chrysb/alphaclaw 0.6.2-beta.6 → 0.7.0-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.
@@ -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 = getEstimatedCostForEntry(entry);
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 Outcome Trend</h3>
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 { useState, useEffect, useCallback, useRef } from "https://esm.sh/preact/hooks";
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) => raw.trim().toUpperCase().replace(/[^A-Z0-9_]/g, "_");
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(String(key || "").trim().toUpperCase());
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 <a href="https://console.anthropic.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">console.anthropic.com</a>`,
89
- ANTHROPIC_TOKEN: html`from <code class="text-xs bg-black/30 px-1 rounded">claude setup-token</code>`,
90
- OPENAI_API_KEY: html`from <a href="https://platform.openai.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">platform.openai.com</a>`,
91
- GEMINI_API_KEY: html`from <a href="https://aistudio.google.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">aistudio.google.com</a>`,
92
- ELEVENLABS_API_KEY: html`from <a href="https://elevenlabs.io" target="_blank" class="hover:underline" style="color: var(--accent-link)">elevenlabs.io</a> · <code class="text-xs bg-black/30 px-1 rounded">XI_API_KEY</code> also supported`,
93
- GITHUB_WORKSPACE_REPO: html`use <code class="text-xs bg-black/30 px-1 rounded">owner/repo</code> or <code class="text-xs bg-black/30 px-1 rounded">https://github.com/owner/repo</code>`,
94
- TELEGRAM_BOT_TOKEN: html`from <a href="https://t.me/BotFather" target="_blank" class="hover:underline" style="color: var(--accent-link)">@BotFather</a> · <a href="https://docs.openclaw.ai/channels/telegram" target="_blank" class="hover:underline" style="color: var(--accent-link)">full guide</a>`,
95
- DISCORD_BOT_TOKEN: html`from <a href="https://discord.com/developers/applications" target="_blank" class="hover:underline" style="color: var(--accent-link)">developer portal</a> · <a href="https://docs.openclaw.ai/channels/discord" target="_blank" class="hover:underline" style="color: var(--accent-link)">full guide</a>`,
96
- MISTRAL_API_KEY: html`from <a href="https://console.mistral.ai" target="_blank" class="hover:underline" style="color: var(--accent-link)">console.mistral.ai</a>`,
97
- VOYAGE_API_KEY: html`from <a href="https://dash.voyageai.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">dash.voyageai.com</a>`,
98
- GROQ_API_KEY: html`from <a href="https://console.groq.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">console.groq.com</a>`,
99
- DEEPGRAM_API_KEY: html`from <a href="https://console.deepgram.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">console.deepgram.com</a>`,
100
- BRAVE_API_KEY: html`from <a href="https://brave.com/search/api/" target="_blank" class="hover:underline" style="color: var(--accent-link)">brave.com/search/api</a> — free tier available`,
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) => html`<${FeatureIcon} key=${feature} feature=${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) => prev.filter((pendingKey) => pendingKey !== key));
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(`Added ${added} variable${added !== 1 ? "s" : ""}`, "success");
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) => pending.has(item.key));
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">${renderEnvRows(hidden)}</div>`
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
- <div class="space-y-4">
474
- <${PageHeader}
475
- title="Envars"
476
- actions=${html`
477
- <${ActionButton}
478
- onClick=${handleSave}
479
- disabled=${!dirty || saving}
480
- loading=${saving}
481
- tone="primary"
482
- size="sm"
483
- idleLabel="Save changes"
484
- loadingLabel="Saving..."
485
- className="transition-all"
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 class="bg-surface border border-border rounded-xl overflow-hidden">
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)">Paste KEY=VALUE or multiple lines</span>
635
+ <span class="text-xs" style="color: var(--text-dim)"
636
+ >Paste KEY=VALUE or multiple lines</span
637
+ >
498
638
  </div>
499
- <div class="flex items-start gap-4 px-4 py-3 border-t border-border">
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
  };
@@ -428,3 +428,14 @@ export const ErrorWarningLineIcon = ({ className = "" }) => html`
428
428
  />
429
429
  </svg>
430
430
  `;
431
+
432
+ export const FullscreenLineIcon = ({ className = "" }) => html`
433
+ <svg
434
+ class=${className}
435
+ viewBox="0 0 24 24"
436
+ fill="currentColor"
437
+ aria-hidden="true"
438
+ >
439
+ <path d="M8 3V5H4V9H2V3H8ZM2 21V15H4V19H8V21H2ZM22 21H16V19H20V15H22V21ZM22 9H20V5H16V3H22V9Z" />
440
+ </svg>
441
+ `;