@chrysb/alphaclaw 0.6.1 → 0.6.2-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/css/agents.css +1 -1
- package/lib/public/css/cron.css +541 -0
- package/lib/public/css/theme.css +72 -0
- package/lib/public/js/app.js +45 -10
- package/lib/public/js/components/action-button.js +26 -20
- package/lib/public/js/components/agents-tab/agent-detail-panel.js +98 -17
- package/lib/public/js/components/agents-tab/agent-tools/index.js +105 -0
- package/lib/public/js/components/agents-tab/agent-tools/tool-catalog.js +289 -0
- package/lib/public/js/components/agents-tab/agent-tools/use-agent-tools.js +128 -0
- package/lib/public/js/components/agents-tab/index.js +4 -0
- package/lib/public/js/components/cron-tab/cron-calendar-helpers.js +385 -0
- package/lib/public/js/components/cron-tab/cron-calendar.js +441 -0
- package/lib/public/js/components/cron-tab/cron-helpers.js +326 -0
- package/lib/public/js/components/cron-tab/cron-job-detail.js +425 -0
- package/lib/public/js/components/cron-tab/cron-job-list.js +305 -0
- package/lib/public/js/components/cron-tab/cron-job-usage.js +70 -0
- package/lib/public/js/components/cron-tab/cron-overview.js +599 -0
- package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +277 -0
- package/lib/public/js/components/cron-tab/index.js +100 -0
- package/lib/public/js/components/cron-tab/use-cron-tab.js +366 -0
- package/lib/public/js/components/doctor/summary-cards.js +5 -11
- package/lib/public/js/components/icons.js +13 -0
- package/lib/public/js/components/pill-tabs.js +33 -0
- package/lib/public/js/components/pop-actions.js +58 -0
- package/lib/public/js/components/routes/agents-route.js +4 -0
- package/lib/public/js/components/routes/cron-route.js +9 -0
- package/lib/public/js/components/routes/index.js +1 -0
- package/lib/public/js/components/segmented-control.js +15 -9
- package/lib/public/js/components/summary-stat-card.js +17 -0
- package/lib/public/js/components/tooltip.js +50 -4
- package/lib/public/js/components/watchdog-tab.js +46 -1
- package/lib/public/js/lib/api.js +94 -0
- package/lib/public/js/lib/app-navigation.js +2 -0
- package/lib/public/js/lib/storage-keys.js +1 -0
- package/lib/public/setup.html +1 -0
- package/lib/server/agents/agents.js +15 -0
- package/lib/server/constants.js +1 -0
- package/lib/server/cost-utils.js +312 -0
- package/lib/server/cron-service.js +461 -0
- package/lib/server/db/usage/index.js +131 -1
- package/lib/server/db/usage/pricing.js +1 -83
- package/lib/server/db/usage/sessions.js +4 -1
- package/lib/server/db/usage/shared.js +2 -1
- package/lib/server/db/usage/summary.js +5 -1
- package/lib/server/onboarding/index.js +39 -5
- package/lib/server/onboarding/openclaw.js +25 -19
- package/lib/server/onboarding/validation.js +28 -0
- package/lib/server/routes/cron.js +148 -0
- package/lib/server.js +13 -0
- package/package.json +1 -1
package/lib/public/js/app.js
CHANGED
|
@@ -16,6 +16,7 @@ import { AppSidebar } from "./components/sidebar.js";
|
|
|
16
16
|
import {
|
|
17
17
|
AgentsRoute,
|
|
18
18
|
BrowseRoute,
|
|
19
|
+
CronRoute,
|
|
19
20
|
DoctorRoute,
|
|
20
21
|
EnvarsRoute,
|
|
21
22
|
GeneralRoute,
|
|
@@ -75,10 +76,20 @@ const App = () => {
|
|
|
75
76
|
} = useAgents();
|
|
76
77
|
|
|
77
78
|
const isAgentsRoute = location.startsWith("/agents");
|
|
79
|
+
const isCronRoute = location.startsWith("/cron");
|
|
78
80
|
const selectedAgentId = (() => {
|
|
79
81
|
const match = location.match(/^\/agents\/([^/]+)/);
|
|
80
82
|
return match ? decodeURIComponent(match[1]) : "";
|
|
81
83
|
})();
|
|
84
|
+
const agentDetailTab = (() => {
|
|
85
|
+
const match = location.match(/^\/agents\/[^/]+\/([^/]+)/);
|
|
86
|
+
const tab = match ? match[1] : "";
|
|
87
|
+
return tab === "tools" ? "tools" : "overview";
|
|
88
|
+
})();
|
|
89
|
+
const selectedCronJobId = (() => {
|
|
90
|
+
const match = location.match(/^\/cron\/([^/]+)/);
|
|
91
|
+
return match ? decodeURIComponent(match[1]) : "";
|
|
92
|
+
})();
|
|
82
93
|
|
|
83
94
|
useEffect(() => {
|
|
84
95
|
if (!isAgentsRoute) return;
|
|
@@ -233,15 +244,31 @@ const App = () => {
|
|
|
233
244
|
saving=${agentsState.saving}
|
|
234
245
|
agentsActions=${agentsActions}
|
|
235
246
|
selectedAgentId=${selectedAgentId}
|
|
247
|
+
activeTab=${agentDetailTab}
|
|
236
248
|
onSelectAgent=${(agentId) => setLocation(`/agents/${encodeURIComponent(agentId)}`)}
|
|
249
|
+
onSelectTab=${(tab) => {
|
|
250
|
+
const safePath = tab && tab !== "overview"
|
|
251
|
+
? `/agents/${encodeURIComponent(selectedAgentId)}/${tab}`
|
|
252
|
+
: `/agents/${encodeURIComponent(selectedAgentId)}`;
|
|
253
|
+
setLocation(safePath);
|
|
254
|
+
}}
|
|
237
255
|
onNavigateToBrowseFile=${browseActions.navigateToBrowseFile}
|
|
238
256
|
onSetLocation=${setLocation}
|
|
239
257
|
/>
|
|
240
258
|
</div>
|
|
259
|
+
<div
|
|
260
|
+
class="app-content-pane cron-pane"
|
|
261
|
+
style=${{ display: isCronRoute ? "block" : "none" }}
|
|
262
|
+
>
|
|
263
|
+
<${CronRoute}
|
|
264
|
+
jobId=${selectedCronJobId}
|
|
265
|
+
onSetLocation=${setLocation}
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
241
268
|
<div
|
|
242
269
|
class="app-content-pane"
|
|
243
270
|
onscroll=${shellActions.handlePaneScroll}
|
|
244
|
-
style=${{ display: browseState.isBrowseRoute || isAgentsRoute ? "none" : "block" }}
|
|
271
|
+
style=${{ display: browseState.isBrowseRoute || isAgentsRoute || isCronRoute ? "none" : "block" }}
|
|
245
272
|
>
|
|
246
273
|
<div
|
|
247
274
|
class=${`mobile-topbar ${shellState.mobileTopbarScrolled ? "is-scrolled" : ""}`}
|
|
@@ -269,7 +296,7 @@ const App = () => {
|
|
|
269
296
|
</span>
|
|
270
297
|
</div>
|
|
271
298
|
<div class="max-w-2xl w-full mx-auto">
|
|
272
|
-
${!browseState.isBrowseRoute && !isAgentsRoute
|
|
299
|
+
${!browseState.isBrowseRoute && !isAgentsRoute && !isCronRoute
|
|
273
300
|
? html`
|
|
274
301
|
<${Switch}>
|
|
275
302
|
<${Route} path="/general">
|
|
@@ -408,11 +435,19 @@ const App = () => {
|
|
|
408
435
|
`;
|
|
409
436
|
};
|
|
410
437
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
438
|
+
const rootElement = document.getElementById("app");
|
|
439
|
+
if (rootElement) {
|
|
440
|
+
const appBootCounter = "__alphaclawSetupAppBootCount";
|
|
441
|
+
window[appBootCounter] = Number(window[appBootCounter] || 0) + 1;
|
|
442
|
+
// Defensive: clear root so duplicate bootstraps cannot stack full app shells.
|
|
443
|
+
render(null, rootElement);
|
|
444
|
+
rootElement.replaceChildren();
|
|
445
|
+
render(
|
|
446
|
+
html`
|
|
447
|
+
<${Router} hook=${useHashLocation}>
|
|
448
|
+
<${App} />
|
|
449
|
+
</${Router}>
|
|
450
|
+
`,
|
|
451
|
+
rootElement,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
@@ -72,19 +72,21 @@ export const ActionButton = ({
|
|
|
72
72
|
: "opacity-80"
|
|
73
73
|
}`
|
|
74
74
|
: "";
|
|
75
|
-
const spinnerSizeClass =
|
|
75
|
+
const spinnerSizeClass =
|
|
76
|
+
size === "md" || size === "lg" ? "h-4 w-4" : "h-3 w-3";
|
|
76
77
|
const isInlineLoading = loadingMode === "inline";
|
|
77
78
|
const IdleIcon = idleIcon;
|
|
78
|
-
const idleContent =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
const idleContent =
|
|
80
|
+
iconOnly && IdleIcon
|
|
81
|
+
? html`<${IdleIcon} className=${idleIconClassName} />`
|
|
82
|
+
: IdleIcon
|
|
83
|
+
? html`
|
|
84
|
+
<span class="inline-flex items-center gap-1.5 leading-none">
|
|
85
|
+
<${IdleIcon} className=${idleIconClassName} />
|
|
86
|
+
${idleLabel}
|
|
87
|
+
</span>
|
|
88
|
+
`
|
|
89
|
+
: idleLabel;
|
|
88
90
|
const currentLabel = loading && !isInlineLoading ? loadingLabel : idleContent;
|
|
89
91
|
|
|
90
92
|
return html`
|
|
@@ -98,11 +100,15 @@ export const ActionButton = ({
|
|
|
98
100
|
>
|
|
99
101
|
${isInlineLoading
|
|
100
102
|
? html`
|
|
101
|
-
<span
|
|
103
|
+
<span
|
|
104
|
+
class="relative inline-flex items-center justify-center leading-none"
|
|
105
|
+
>
|
|
102
106
|
<span class=${loading ? "invisible" : ""}>${currentLabel}</span>
|
|
103
107
|
${loading
|
|
104
108
|
? html`
|
|
105
|
-
<span
|
|
109
|
+
<span
|
|
110
|
+
class="absolute inset-0 inline-flex items-center justify-center"
|
|
111
|
+
>
|
|
106
112
|
<${LoadingSpinner} className=${spinnerSizeClass} />
|
|
107
113
|
</span>
|
|
108
114
|
`
|
|
@@ -110,13 +116,13 @@ export const ActionButton = ({
|
|
|
110
116
|
</span>
|
|
111
117
|
`
|
|
112
118
|
: loading
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
? html`
|
|
120
|
+
<span class="inline-flex items-center gap-1.5 leading-none">
|
|
121
|
+
<${LoadingSpinner} className=${spinnerSizeClass} />
|
|
122
|
+
${currentLabel}
|
|
123
|
+
</span>
|
|
124
|
+
`
|
|
125
|
+
: currentLabel}
|
|
120
126
|
</button>
|
|
121
127
|
`;
|
|
122
128
|
};
|
|
@@ -1,22 +1,69 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import { useState, useCallback } from "https://esm.sh/preact/hooks";
|
|
2
3
|
import htm from "https://esm.sh/htm";
|
|
3
4
|
import { ActionButton } from "../action-button.js";
|
|
4
5
|
import { Badge } from "../badge.js";
|
|
6
|
+
import { PillTabs } from "../pill-tabs.js";
|
|
7
|
+
import { PopActions } from "../pop-actions.js";
|
|
5
8
|
import { AgentOverview } from "./agent-overview/index.js";
|
|
9
|
+
import { AgentToolsPanel } from "./agent-tools/index.js";
|
|
10
|
+
import { useAgentTools } from "./agent-tools/use-agent-tools.js";
|
|
6
11
|
|
|
7
12
|
const html = htm.bind(h);
|
|
8
13
|
|
|
14
|
+
const kDetailTabs = [
|
|
15
|
+
{ label: "Overview", value: "overview" },
|
|
16
|
+
{ label: "Tools", value: "tools" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const PencilIcon = ({ className = "w-3.5 h-3.5" }) => html`
|
|
20
|
+
<svg
|
|
21
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
+
viewBox="0 0 24 24"
|
|
23
|
+
fill="currentColor"
|
|
24
|
+
class=${className}
|
|
25
|
+
>
|
|
26
|
+
<path
|
|
27
|
+
d="M15.7279 9.57627L14.3137 8.16206L5 17.4758V18.89H6.41421L15.7279 9.57627ZM17.1421 8.16206L18.5563 6.74785L17.1421 5.33363L15.7279 6.74785L17.1421 8.16206ZM7.24264 20.89H3V16.6473L16.435 3.21231C16.8256 2.82179 17.4587 2.82179 17.8492 3.21231L20.6777 6.04074C21.0682 6.43126 21.0682 7.06443 20.6777 7.45495L7.24264 20.89Z"
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
|
30
|
+
`;
|
|
31
|
+
|
|
9
32
|
export const AgentDetailPanel = ({
|
|
10
33
|
agent = null,
|
|
11
34
|
agents = [],
|
|
35
|
+
activeTab = "overview",
|
|
12
36
|
saving = false,
|
|
13
37
|
onUpdateAgent = async () => {},
|
|
14
38
|
onSetLocation = () => {},
|
|
39
|
+
onSelectTab = () => {},
|
|
15
40
|
onEdit = () => {},
|
|
16
41
|
onDelete = () => {},
|
|
17
42
|
onSetDefault = () => {},
|
|
18
43
|
onOpenWorkspace = () => {},
|
|
19
44
|
}) => {
|
|
45
|
+
const tools = useAgentTools({ agent: agent || {} });
|
|
46
|
+
const [savingTools, setSavingTools] = useState(false);
|
|
47
|
+
|
|
48
|
+
const handleSaveTools = useCallback(async () => {
|
|
49
|
+
if (!agent) return;
|
|
50
|
+
setSavingTools(true);
|
|
51
|
+
try {
|
|
52
|
+
const nextAgent = await onUpdateAgent(
|
|
53
|
+
agent.id,
|
|
54
|
+
{ tools: tools.toolsConfig },
|
|
55
|
+
"Tool access updated",
|
|
56
|
+
);
|
|
57
|
+
tools.markSaved(nextAgent?.tools || tools.toolsConfig);
|
|
58
|
+
} catch {
|
|
59
|
+
// toast handled by parent
|
|
60
|
+
} finally {
|
|
61
|
+
setSavingTools(false);
|
|
62
|
+
}
|
|
63
|
+
}, [agent, tools.toolsConfig, tools.markSaved, onUpdateAgent]);
|
|
64
|
+
|
|
65
|
+
const isSaving = saving || savingTools;
|
|
66
|
+
|
|
20
67
|
if (!agent) {
|
|
21
68
|
return html`
|
|
22
69
|
<div class="agents-detail-panel">
|
|
@@ -32,10 +79,18 @@ export const AgentDetailPanel = ({
|
|
|
32
79
|
<div class="agents-detail-inner">
|
|
33
80
|
<div class="agents-detail-header">
|
|
34
81
|
<div class="min-w-0">
|
|
35
|
-
<div class="flex items-center gap-
|
|
82
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
36
83
|
<span class="agents-detail-header-title">
|
|
37
84
|
${agent.name || agent.id}
|
|
38
85
|
</span>
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class="text-gray-500 hover:text-gray-300 transition-colors p-0.5 -ml-0.5"
|
|
89
|
+
onclick=${() => onEdit(agent)}
|
|
90
|
+
title="Edit agent name"
|
|
91
|
+
>
|
|
92
|
+
<${PencilIcon} />
|
|
93
|
+
</button>
|
|
39
94
|
${agent.default
|
|
40
95
|
? html`<${Badge} tone="cyan">Default</${Badge}>`
|
|
41
96
|
: null}
|
|
@@ -44,29 +99,55 @@ export const AgentDetailPanel = ({
|
|
|
44
99
|
<span class="font-mono">${agent.id}</span>
|
|
45
100
|
</div>
|
|
46
101
|
</div>
|
|
47
|
-
|
|
102
|
+
<${PopActions} visible=${tools.dirty}>
|
|
48
103
|
<${ActionButton}
|
|
49
|
-
onClick=${
|
|
50
|
-
disabled=${
|
|
104
|
+
onClick=${tools.reset}
|
|
105
|
+
disabled=${isSaving}
|
|
51
106
|
tone="secondary"
|
|
52
107
|
size="sm"
|
|
53
|
-
idleLabel="
|
|
108
|
+
idleLabel="Cancel"
|
|
54
109
|
className="text-xs"
|
|
55
110
|
/>
|
|
56
|
-
|
|
111
|
+
<${ActionButton}
|
|
112
|
+
onClick=${handleSaveTools}
|
|
113
|
+
disabled=${isSaving}
|
|
114
|
+
loading=${isSaving}
|
|
115
|
+
loadingMode="inline"
|
|
116
|
+
tone="primary"
|
|
117
|
+
size="sm"
|
|
118
|
+
idleLabel="Save changes"
|
|
119
|
+
loadingLabel="Saving…"
|
|
120
|
+
className="text-xs"
|
|
121
|
+
/>
|
|
122
|
+
</${PopActions}>
|
|
57
123
|
</div>
|
|
124
|
+
<${PillTabs}
|
|
125
|
+
tabs=${kDetailTabs}
|
|
126
|
+
activeTab=${activeTab}
|
|
127
|
+
onSelectTab=${onSelectTab}
|
|
128
|
+
className="flex items-center gap-2 pt-6"
|
|
129
|
+
/>
|
|
58
130
|
<div class="agents-detail-content">
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
131
|
+
${activeTab === "overview"
|
|
132
|
+
? html`
|
|
133
|
+
<${AgentOverview}
|
|
134
|
+
agent=${agent}
|
|
135
|
+
agents=${agents}
|
|
136
|
+
saving=${saving}
|
|
137
|
+
onUpdateAgent=${onUpdateAgent}
|
|
138
|
+
onSetLocation=${onSetLocation}
|
|
139
|
+
onOpenWorkspace=${onOpenWorkspace}
|
|
140
|
+
onSwitchToModels=${() => onSetLocation("/models")}
|
|
141
|
+
onSetDefault=${onSetDefault}
|
|
142
|
+
onDelete=${onDelete}
|
|
143
|
+
/>
|
|
144
|
+
`
|
|
145
|
+
: html`
|
|
146
|
+
<${AgentToolsPanel}
|
|
147
|
+
agent=${agent}
|
|
148
|
+
tools=${tools}
|
|
149
|
+
/>
|
|
150
|
+
`}
|
|
70
151
|
</div>
|
|
71
152
|
</div>
|
|
72
153
|
</div>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { ToggleSwitch } from "../../toggle-switch.js";
|
|
4
|
+
import { InfoTooltip } from "../../info-tooltip.js";
|
|
5
|
+
import { SegmentedControl } from "../../segmented-control.js";
|
|
6
|
+
import { kSections, kToolProfiles, kProfileLabels } from "./tool-catalog.js";
|
|
7
|
+
|
|
8
|
+
const html = htm.bind(h);
|
|
9
|
+
|
|
10
|
+
const kProfileDescriptions = {
|
|
11
|
+
minimal: "Only session status — grant specific tools with alsoAllow",
|
|
12
|
+
messaging: "Session access and messaging — ideal for notification agents",
|
|
13
|
+
coding: "File I/O, shell, memory, sessions, cron, and image generation",
|
|
14
|
+
full: "All tools enabled, no restrictions",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const kProfileOptions = kToolProfiles.map((p) => ({
|
|
18
|
+
label: kProfileLabels[p],
|
|
19
|
+
value: p,
|
|
20
|
+
title: kProfileDescriptions[p],
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const ToolRow = ({ tool, onToggle }) => html`
|
|
24
|
+
<div class="flex items-center justify-between gap-3 py-2.5 px-4">
|
|
25
|
+
<div class="min-w-0">
|
|
26
|
+
<div class="text-sm text-gray-200 flex items-center gap-1.5">
|
|
27
|
+
<span>${tool.label}</span>
|
|
28
|
+
${tool.help
|
|
29
|
+
? html`<${InfoTooltip} text=${tool.help} widthClass="w-72" />`
|
|
30
|
+
: null}
|
|
31
|
+
</div>
|
|
32
|
+
<span class="text-xs font-mono text-gray-500">${tool.id}</span>
|
|
33
|
+
</div>
|
|
34
|
+
<${ToggleSwitch}
|
|
35
|
+
checked=${tool.enabled}
|
|
36
|
+
onChange=${(checked) => onToggle(tool.id, checked)}
|
|
37
|
+
label=${null}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const ToolSection = ({ section, toolStates, onToggle }) => {
|
|
43
|
+
const sectionTools = toolStates.filter((t) => t.section === section.id);
|
|
44
|
+
if (!sectionTools.length) return null;
|
|
45
|
+
|
|
46
|
+
return html`
|
|
47
|
+
<div class="bg-surface border border-border rounded-xl overflow-hidden">
|
|
48
|
+
<h3 class="card-label text-xs px-4 pt-3 pb-2">${section.label}</h3>
|
|
49
|
+
<div class="divide-y divide-border">
|
|
50
|
+
${sectionTools.map(
|
|
51
|
+
(tool) =>
|
|
52
|
+
html`<${ToolRow}
|
|
53
|
+
key=${tool.id}
|
|
54
|
+
tool=${tool}
|
|
55
|
+
onToggle=${onToggle}
|
|
56
|
+
/>`,
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
`;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const AgentToolsPanel = ({ agent = {}, tools = {} }) => {
|
|
64
|
+
const { profile, toolStates, setProfile, toggleTool } = tools;
|
|
65
|
+
|
|
66
|
+
const enabledTotal = (toolStates || []).filter((t) => t.enabled).length;
|
|
67
|
+
const totalTools = (toolStates || []).length;
|
|
68
|
+
|
|
69
|
+
return html`
|
|
70
|
+
<div class="space-y-4">
|
|
71
|
+
<div class="bg-surface border border-border rounded-xl p-4 space-y-4">
|
|
72
|
+
<div>
|
|
73
|
+
<div class="flex items-center justify-between mb-3">
|
|
74
|
+
<h3 class="card-label text-xs">Preset</h3>
|
|
75
|
+
<span class="text-xs text-gray-500"
|
|
76
|
+
>${enabledTotal}/${totalTools} tools enabled</span
|
|
77
|
+
>
|
|
78
|
+
</div>
|
|
79
|
+
<${SegmentedControl}
|
|
80
|
+
options=${kProfileOptions}
|
|
81
|
+
value=${profile}
|
|
82
|
+
onChange=${setProfile}
|
|
83
|
+
fullWidth
|
|
84
|
+
className="ac-segmented-control-dark"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div style="columns: 2; column-gap: 0.75rem;">
|
|
90
|
+
${kSections.map(
|
|
91
|
+
(section) => html`
|
|
92
|
+
<div style="break-inside: avoid; margin-bottom: 0.75rem;">
|
|
93
|
+
<${ToolSection}
|
|
94
|
+
key=${section.id}
|
|
95
|
+
section=${section}
|
|
96
|
+
toolStates=${toolStates || []}
|
|
97
|
+
onToggle=${toggleTool}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
`,
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
};
|