apteva 0.4.32 → 0.4.44
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/dist/ActivityPage.c48n83h2.js +3 -0
- package/dist/ApiDocsPage.yzcxx5ax.js +4 -0
- package/dist/App.09yb8t0b.js +1 -0
- package/dist/App.152mbs1r.js +4 -0
- package/dist/App.3a67nx9w.js +4 -0
- package/dist/App.9epx6785.js +4 -0
- package/dist/App.d8955awp.js +4 -0
- package/dist/App.drwb57jq.js +4 -0
- package/dist/App.gssbmajb.js +4 -0
- package/dist/App.qw70pc29.js +53 -0
- package/dist/App.qzbx5wtj.js +4 -0
- package/dist/App.r5serxkt.js +8 -0
- package/dist/App.tpmp9020.js +20 -0
- package/dist/App.v2wb4d7d.js +61 -0
- package/dist/App.vxmaaj0m.js +13 -0
- package/dist/App.w4p2tda9.js +4 -0
- package/dist/App.wv2ng55q.js +221 -0
- package/dist/App.yncnrn0f.js +4 -0
- package/dist/ConnectionsPage.k6cspyqq.js +3 -0
- package/dist/McpPage.cdxm48xj.js +3 -0
- package/dist/SettingsPage.evpv7c2y.js +3 -0
- package/dist/SkillsPage.pvzp6c1a.js +3 -0
- package/dist/TasksPage.6jnvbpsy.js +3 -0
- package/dist/TelemetryPage.t7vk24zc.js +3 -0
- package/dist/TestsPage.5x6658aa.js +3 -0
- package/dist/ThreadsPage.3fvhtevh.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +10 -9
- package/src/crypto.ts +4 -3
- package/src/db.ts +171 -36
- package/src/integrations/agentdojo.ts +95 -12
- package/src/integrations/index.ts +7 -0
- package/src/mcp-platform.ts +870 -142
- package/src/openapi.ts +96 -0
- package/src/providers.ts +60 -34
- package/src/routes/api/agent-utils.ts +59 -47
- package/src/routes/api/agents.ts +71 -2
- package/src/routes/api/integrations.ts +11 -5
- package/src/routes/api/mcp.ts +5 -4
- package/src/routes/api/meta-agent.ts +37 -1
- package/src/routes/api/projects.ts +3 -3
- package/src/routes/api/providers.ts +121 -30
- package/src/routes/api/skills.ts +2 -3
- package/src/routes/api/system.ts +98 -14
- package/src/routes/api/telemetry.ts +19 -1
- package/src/routes/share.ts +85 -0
- package/src/server.ts +43 -32
- package/src/triggers/agentdojo.ts +2 -2
- package/src/web/App.tsx +107 -21
- package/src/web/components/activity/ActivityPage.tsx +242 -389
- package/src/web/components/agents/AgentCard.tsx +19 -27
- package/src/web/components/agents/AgentPanel.tsx +358 -198
- package/src/web/components/agents/AgentsView.tsx +4 -4
- package/src/web/components/agents/CreateAgentModal.tsx +21 -79
- package/src/web/components/api/ApiDocsPage.tsx +66 -66
- package/src/web/components/auth/CreateAccountStep.tsx +16 -16
- package/src/web/components/auth/LoginPage.tsx +10 -10
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/LoadingSpinner.tsx +2 -2
- package/src/web/components/common/Modal.tsx +8 -8
- package/src/web/components/common/Select.tsx +11 -10
- package/src/web/components/connections/ConnectionsPage.tsx +4 -4
- package/src/web/components/connections/IntegrationsTab.tsx +18 -18
- package/src/web/components/connections/OverviewTab.tsx +13 -13
- package/src/web/components/connections/TriggersTab.tsx +99 -99
- package/src/web/components/dashboard/Dashboard.tsx +177 -52
- package/src/web/components/index.ts +1 -1
- package/src/web/components/layout/Header.tsx +50 -34
- package/src/web/components/layout/Sidebar.tsx +41 -16
- package/src/web/components/mcp/IntegrationsPanel.tsx +160 -69
- package/src/web/components/mcp/McpPage.tsx +218 -209
- package/src/web/components/meta-agent/MetaAgent.tsx +15 -11
- package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
- package/src/web/components/settings/SettingsPage.tsx +389 -221
- package/src/web/components/skills/SkillsPage.tsx +88 -88
- package/src/web/components/tasks/TasksPage.tsx +385 -68
- package/src/web/components/telemetry/TelemetryPage.tsx +294 -39
- package/src/web/components/tests/TestsPage.tsx +50 -50
- package/src/web/components/threads/ThreadsPage.tsx +315 -0
- package/src/web/context/AuthContext.tsx +3 -3
- package/src/web/context/ProjectContext.tsx +8 -3
- package/src/web/context/TelemetryContext.tsx +24 -6
- package/src/web/context/ThemeContext.tsx +69 -0
- package/src/web/context/index.ts +3 -1
- package/src/web/styles.css +25 -7
- package/src/web/themes.ts +99 -0
- package/src/web/types.ts +4 -7
- package/dist/ActivityPage.41nbye4r.js +0 -3
- package/dist/ApiDocsPage.4smnt8m3.js +0 -4
- package/dist/App.0sbax9et.js +0 -4
- package/dist/App.0ws427h8.js +0 -4
- package/dist/App.6q6bar8b.js +0 -4
- package/dist/App.80301vdb.js +0 -4
- package/dist/App.af2wg84v.js +0 -267
- package/dist/App.ca1rz1ph.js +0 -4
- package/dist/App.ensa6z0r.js +0 -4
- package/dist/App.f8g7tych.js +0 -13
- package/dist/App.mvtqv6qc.js +0 -20
- package/dist/App.ncgc9cxy.js +0 -4
- package/dist/App.p02f4ret.js +0 -1
- package/dist/App.p0fb1pds.js +0 -4
- package/dist/App.pmaq48sj.js +0 -4
- package/dist/App.yv87t9m5.js +0 -4
- package/dist/App.zjmfm8p6.js +0 -4
- package/dist/ConnectionsPage.anb3rv9a.js +0 -3
- package/dist/McpPage.y396h6fy.js +0 -3
- package/dist/SettingsPage.p1hc60gk.js +0 -3
- package/dist/SkillsPage.yj3xdsay.js +0 -3
- package/dist/TasksPage.sjv0khtv.js +0 -3
- package/dist/TelemetryPage.2qm4w16r.js +0 -3
- package/dist/TestsPage.zzs4qfj8.js +0 -3
|
@@ -2,7 +2,8 @@ import React, { useState, useEffect } from "react";
|
|
|
2
2
|
import { CheckIcon, CloseIcon, PlusIcon } from "../common/Icons";
|
|
3
3
|
import { Modal, useConfirm } from "../common/Modal";
|
|
4
4
|
import { Select } from "../common/Select";
|
|
5
|
-
import { useProjects, useAuth, type Project } from "../../context";
|
|
5
|
+
import { useProjects, useAuth, useTheme, type Project } from "../../context";
|
|
6
|
+
import type { ThemeMode } from "../../themes";
|
|
6
7
|
import type { Provider } from "../../types";
|
|
7
8
|
|
|
8
9
|
type SettingsTab = "general" | "providers" | "projects" | "channels" | "api-keys" | "account" | "updates" | "data" | "assistant";
|
|
@@ -26,7 +27,7 @@ export function SettingsPage() {
|
|
|
26
27
|
return (
|
|
27
28
|
<div className="flex-1 flex flex-col md:flex-row overflow-hidden">
|
|
28
29
|
{/* Mobile: Horizontal scrolling tabs */}
|
|
29
|
-
<div className="md:hidden border-b border-[
|
|
30
|
+
<div className="md:hidden border-b border-[var(--color-border)] bg-[var(--color-bg)]">
|
|
30
31
|
<div className="flex overflow-x-auto" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
|
|
31
32
|
{tabs.map(tab => (
|
|
32
33
|
<button
|
|
@@ -34,8 +35,8 @@ export function SettingsPage() {
|
|
|
34
35
|
onClick={() => setActiveTab(tab.key)}
|
|
35
36
|
className={`flex-shrink-0 px-4 py-3 text-sm font-medium border-b-2 transition ${
|
|
36
37
|
activeTab === tab.key
|
|
37
|
-
? "border-[
|
|
38
|
-
: "border-transparent text-[
|
|
38
|
+
? "border-[var(--color-accent)] text-[var(--color-accent)]"
|
|
39
|
+
: "border-transparent text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
|
|
39
40
|
}`}
|
|
40
41
|
>
|
|
41
42
|
{tab.label}
|
|
@@ -45,8 +46,8 @@ export function SettingsPage() {
|
|
|
45
46
|
</div>
|
|
46
47
|
|
|
47
48
|
{/* Desktop: Settings Sidebar */}
|
|
48
|
-
<div className="hidden md:block w-48 border-r border-[
|
|
49
|
-
<h2 className="text-sm font-medium text-[
|
|
49
|
+
<div className="hidden md:block w-48 border-r border-[var(--color-border)] p-4 flex-shrink-0">
|
|
50
|
+
<h2 className="text-sm font-medium text-[var(--color-text-muted)] uppercase tracking-wider mb-3">Settings</h2>
|
|
50
51
|
<nav className="space-y-1">
|
|
51
52
|
{tabs.map(tab => (
|
|
52
53
|
<SettingsNavItem
|
|
@@ -89,8 +90,8 @@ function SettingsNavItem({
|
|
|
89
90
|
onClick={onClick}
|
|
90
91
|
className={`w-full text-left px-3 py-2 rounded text-sm transition ${
|
|
91
92
|
active
|
|
92
|
-
? "bg-[
|
|
93
|
-
: "text-[
|
|
93
|
+
? "bg-[var(--color-surface-raised)] text-[var(--color-text)]"
|
|
94
|
+
: "text-[var(--color-text-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-text-secondary)]"
|
|
94
95
|
}`}
|
|
95
96
|
>
|
|
96
97
|
{label}
|
|
@@ -100,6 +101,7 @@ function SettingsNavItem({
|
|
|
100
101
|
|
|
101
102
|
function GeneralSettings() {
|
|
102
103
|
const { authFetch } = useAuth();
|
|
104
|
+
const { mode, setMode } = useTheme();
|
|
103
105
|
const [instanceUrl, setInstanceUrl] = useState("");
|
|
104
106
|
const [loading, setLoading] = useState(true);
|
|
105
107
|
const [saving, setSaving] = useState(false);
|
|
@@ -141,21 +143,58 @@ function GeneralSettings() {
|
|
|
141
143
|
setSaving(false);
|
|
142
144
|
};
|
|
143
145
|
|
|
146
|
+
const themeOptions: { value: ThemeMode; label: string; description: string }[] = [
|
|
147
|
+
{ value: "auto", label: "Auto", description: "Follow system preference" },
|
|
148
|
+
{ value: "dark", label: "Dark", description: "Dark background" },
|
|
149
|
+
{ value: "light", label: "Light", description: "Light background" },
|
|
150
|
+
];
|
|
151
|
+
|
|
144
152
|
return (
|
|
145
153
|
<div className="max-w-4xl w-full">
|
|
146
154
|
<div className="mb-6">
|
|
147
155
|
<h1 className="text-2xl font-semibold mb-1">General</h1>
|
|
148
|
-
<p className="text-[
|
|
156
|
+
<p className="text-[var(--color-text-muted)]">Instance configuration and appearance.</p>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Theme */}
|
|
160
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 mb-4">
|
|
161
|
+
<h3 className="font-medium mb-2">Theme</h3>
|
|
162
|
+
<p className="text-sm text-[var(--color-text-muted)] mb-4">
|
|
163
|
+
Choose your preferred color scheme. Auto follows your operating system setting.
|
|
164
|
+
</p>
|
|
165
|
+
<div className="flex gap-3">
|
|
166
|
+
{themeOptions.map(opt => (
|
|
167
|
+
<button
|
|
168
|
+
key={opt.value}
|
|
169
|
+
onClick={() => setMode(opt.value)}
|
|
170
|
+
className={`flex-1 max-w-[160px] px-4 py-3 rounded-lg border text-left transition ${
|
|
171
|
+
mode === opt.value
|
|
172
|
+
? "border-[var(--color-accent)] bg-[var(--color-accent-10)]"
|
|
173
|
+
: "border-[var(--color-border-light)] bg-[var(--color-bg)] hover:border-[var(--color-scrollbar)]"
|
|
174
|
+
}`}
|
|
175
|
+
>
|
|
176
|
+
<div className="flex items-center gap-2 mb-1">
|
|
177
|
+
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
|
|
178
|
+
mode === opt.value ? "border-[var(--color-accent)]" : "border-[var(--color-scrollbar)]"
|
|
179
|
+
}`}>
|
|
180
|
+
{mode === opt.value && <div className="w-2 h-2 rounded-full bg-[var(--color-accent)]" />}
|
|
181
|
+
</div>
|
|
182
|
+
<span className="text-sm font-medium">{opt.label}</span>
|
|
183
|
+
</div>
|
|
184
|
+
<p className="text-xs text-[var(--color-text-muted)] ml-6">{opt.description}</p>
|
|
185
|
+
</button>
|
|
186
|
+
))}
|
|
187
|
+
</div>
|
|
149
188
|
</div>
|
|
150
189
|
|
|
151
|
-
<div className="bg-[
|
|
190
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
152
191
|
<h3 className="font-medium mb-2">Instance URL</h3>
|
|
153
|
-
<p className="text-sm text-[
|
|
192
|
+
<p className="text-sm text-[var(--color-text-muted)] mb-4">
|
|
154
193
|
The public HTTPS URL for this instance. Used for webhook callbacks from external services like Composio.
|
|
155
194
|
</p>
|
|
156
195
|
|
|
157
196
|
{loading ? (
|
|
158
|
-
<div className="text-[
|
|
197
|
+
<div className="text-[var(--color-text-muted)] text-sm">Loading...</div>
|
|
159
198
|
) : (
|
|
160
199
|
<div className="space-y-3 max-w-lg">
|
|
161
200
|
<input
|
|
@@ -163,7 +202,7 @@ function GeneralSettings() {
|
|
|
163
202
|
value={instanceUrl}
|
|
164
203
|
onChange={e => setInstanceUrl(e.target.value)}
|
|
165
204
|
placeholder="https://your-domain.com"
|
|
166
|
-
className="w-full bg-[
|
|
205
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)] font-mono text-sm"
|
|
167
206
|
/>
|
|
168
207
|
|
|
169
208
|
{message && (
|
|
@@ -179,7 +218,7 @@ function GeneralSettings() {
|
|
|
179
218
|
<button
|
|
180
219
|
onClick={handleSave}
|
|
181
220
|
disabled={saving}
|
|
182
|
-
className="px-4 py-2 bg-[
|
|
221
|
+
className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black rounded text-sm font-medium transition"
|
|
183
222
|
>
|
|
184
223
|
{saving ? "Saving..." : "Save"}
|
|
185
224
|
</button>
|
|
@@ -320,7 +359,7 @@ function ProvidersSettings() {
|
|
|
320
359
|
<div>
|
|
321
360
|
<div className="mb-6">
|
|
322
361
|
<h1 className="text-2xl font-semibold mb-1">AI Providers</h1>
|
|
323
|
-
<p className="text-[
|
|
362
|
+
<p className="text-[var(--color-text-muted)]">
|
|
324
363
|
Manage your API keys for AI providers. {llmConfiguredCount} of {llmProviders.length} configured.
|
|
325
364
|
</p>
|
|
326
365
|
</div>
|
|
@@ -358,7 +397,7 @@ function ProvidersSettings() {
|
|
|
358
397
|
<div>
|
|
359
398
|
<div className="mb-6">
|
|
360
399
|
<h2 className="text-xl font-semibold mb-1">MCP Integrations</h2>
|
|
361
|
-
<p className="text-[
|
|
400
|
+
<p className="text-[var(--color-text-muted)]">
|
|
362
401
|
Connect to MCP gateways for tool integrations. {intConfiguredCount} of {integrations.length} configured.
|
|
363
402
|
</p>
|
|
364
403
|
</div>
|
|
@@ -399,14 +438,14 @@ function ProvidersSettings() {
|
|
|
399
438
|
<div>
|
|
400
439
|
<div className="mb-6">
|
|
401
440
|
<h2 className="text-xl font-semibold mb-1">Browser Providers</h2>
|
|
402
|
-
<p className="text-[
|
|
441
|
+
<p className="text-[var(--color-text-muted)]">
|
|
403
442
|
Configure browser environments for operator mode (computer use). {browserConfiguredCount} of {browserProviders.length} configured.
|
|
404
443
|
</p>
|
|
405
444
|
</div>
|
|
406
445
|
|
|
407
446
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
408
447
|
{browserProviders.map(provider => (
|
|
409
|
-
<
|
|
448
|
+
<IntegrationKeyCard
|
|
410
449
|
key={provider.id}
|
|
411
450
|
provider={provider}
|
|
412
451
|
isEditing={selectedProvider === provider.id}
|
|
@@ -423,14 +462,14 @@ function ProvidersSettings() {
|
|
|
423
462
|
onCancelEdit={() => {
|
|
424
463
|
setSelectedProvider(null);
|
|
425
464
|
setApiKey("");
|
|
426
|
-
setExtraField("");
|
|
427
465
|
setError(null);
|
|
428
466
|
}}
|
|
429
467
|
onApiKeyChange={setApiKey}
|
|
430
468
|
onSave={saveKey}
|
|
431
469
|
onDelete={() => deleteKey(provider.id)}
|
|
432
|
-
|
|
433
|
-
|
|
470
|
+
projectsEnabled={projectsEnabled}
|
|
471
|
+
projects={projects}
|
|
472
|
+
onRefresh={fetchProviders}
|
|
434
473
|
/>
|
|
435
474
|
))}
|
|
436
475
|
</div>
|
|
@@ -485,13 +524,13 @@ function ProjectsSettings() {
|
|
|
485
524
|
<div className="mb-6 flex items-center justify-between gap-4">
|
|
486
525
|
<div>
|
|
487
526
|
<h1 className="text-2xl font-semibold mb-1">Projects</h1>
|
|
488
|
-
<p className="text-[
|
|
527
|
+
<p className="text-[var(--color-text-muted)]">
|
|
489
528
|
Organize agents into projects for better management.
|
|
490
529
|
</p>
|
|
491
530
|
</div>
|
|
492
531
|
<button
|
|
493
532
|
onClick={openCreate}
|
|
494
|
-
className="flex items-center gap-2 bg-[
|
|
533
|
+
className="flex items-center gap-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-black px-4 py-2 rounded font-medium transition flex-shrink-0"
|
|
495
534
|
>
|
|
496
535
|
<PlusIcon className="w-4 h-4" />
|
|
497
536
|
New Project
|
|
@@ -500,7 +539,7 @@ function ProjectsSettings() {
|
|
|
500
539
|
|
|
501
540
|
{/* Project List */}
|
|
502
541
|
{projects.length === 0 ? (
|
|
503
|
-
<div className="text-center py-12 text-[
|
|
542
|
+
<div className="text-center py-12 text-[var(--color-text-muted)]">
|
|
504
543
|
<p className="text-lg mb-2">No projects yet</p>
|
|
505
544
|
<p className="text-sm">Create a project to organize your agents.</p>
|
|
506
545
|
</div>
|
|
@@ -509,7 +548,7 @@ function ProjectsSettings() {
|
|
|
509
548
|
{projects.map(project => (
|
|
510
549
|
<div
|
|
511
550
|
key={project.id}
|
|
512
|
-
className="bg-[
|
|
551
|
+
className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 flex items-center gap-4"
|
|
513
552
|
>
|
|
514
553
|
<div
|
|
515
554
|
className="w-4 h-4 rounded-full flex-shrink-0"
|
|
@@ -518,16 +557,16 @@ function ProjectsSettings() {
|
|
|
518
557
|
<div className="flex-1 min-w-0">
|
|
519
558
|
<h3 className="font-medium">{project.name}</h3>
|
|
520
559
|
{project.description && (
|
|
521
|
-
<p className="text-sm text-[
|
|
560
|
+
<p className="text-sm text-[var(--color-text-muted)] truncate">{project.description}</p>
|
|
522
561
|
)}
|
|
523
|
-
<p className="text-xs text-[
|
|
562
|
+
<p className="text-xs text-[var(--color-text-muted)] mt-1">
|
|
524
563
|
{project.agentCount} agent{project.agentCount !== 1 ? "s" : ""}
|
|
525
564
|
</p>
|
|
526
565
|
</div>
|
|
527
566
|
<div className="flex items-center gap-2">
|
|
528
567
|
<button
|
|
529
568
|
onClick={() => openEdit(project)}
|
|
530
|
-
className="text-sm text-[
|
|
569
|
+
className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)] px-2 py-1"
|
|
531
570
|
>
|
|
532
571
|
Edit
|
|
533
572
|
</button>
|
|
@@ -601,30 +640,30 @@ function ProjectModal({ project, onSave, onClose }: ProjectModalProps) {
|
|
|
601
640
|
|
|
602
641
|
<div className="space-y-4">
|
|
603
642
|
<div>
|
|
604
|
-
<label className="block text-sm text-[
|
|
643
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Name</label>
|
|
605
644
|
<input
|
|
606
645
|
type="text"
|
|
607
646
|
value={name}
|
|
608
647
|
onChange={e => setName(e.target.value)}
|
|
609
|
-
className="w-full bg-[
|
|
648
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
610
649
|
placeholder="My Project"
|
|
611
650
|
autoFocus
|
|
612
651
|
/>
|
|
613
652
|
</div>
|
|
614
653
|
|
|
615
654
|
<div>
|
|
616
|
-
<label className="block text-sm text-[
|
|
655
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Description (optional)</label>
|
|
617
656
|
<input
|
|
618
657
|
type="text"
|
|
619
658
|
value={description}
|
|
620
659
|
onChange={e => setDescription(e.target.value)}
|
|
621
|
-
className="w-full bg-[
|
|
660
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
622
661
|
placeholder="A short description"
|
|
623
662
|
/>
|
|
624
663
|
</div>
|
|
625
664
|
|
|
626
665
|
<div>
|
|
627
|
-
<label className="block text-sm text-[
|
|
666
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Color</label>
|
|
628
667
|
<div className="flex gap-3 flex-wrap">
|
|
629
668
|
{DEFAULT_PROJECT_COLORS.map(c => (
|
|
630
669
|
<button
|
|
@@ -646,14 +685,14 @@ function ProjectModal({ project, onSave, onClose }: ProjectModalProps) {
|
|
|
646
685
|
<div className="flex gap-3 mt-6">
|
|
647
686
|
<button
|
|
648
687
|
onClick={onClose}
|
|
649
|
-
className="flex-1 border border-[
|
|
688
|
+
className="flex-1 border border-[var(--color-border-light)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent)] px-4 py-2 rounded font-medium transition"
|
|
650
689
|
>
|
|
651
690
|
Cancel
|
|
652
691
|
</button>
|
|
653
692
|
<button
|
|
654
693
|
onClick={handleSubmit}
|
|
655
694
|
disabled={saving || !name.trim()}
|
|
656
|
-
className="flex-1 bg-[
|
|
695
|
+
className="flex-1 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black px-4 py-2 rounded font-medium transition"
|
|
657
696
|
>
|
|
658
697
|
{saving ? "Saving..." : project ? "Update" : "Create"}
|
|
659
698
|
</button>
|
|
@@ -754,13 +793,13 @@ function UpdatesSettings() {
|
|
|
754
793
|
<div className="max-w-4xl w-full">
|
|
755
794
|
<div className="mb-6">
|
|
756
795
|
<h1 className="text-2xl font-semibold mb-1">Updates</h1>
|
|
757
|
-
<p className="text-[
|
|
796
|
+
<p className="text-[var(--color-text-muted)]">
|
|
758
797
|
Check for new versions of apteva and the agent binary.
|
|
759
798
|
</p>
|
|
760
799
|
</div>
|
|
761
800
|
|
|
762
801
|
{checking && !versions ? (
|
|
763
|
-
<div className="text-[
|
|
802
|
+
<div className="text-[var(--color-text-muted)]">Checking version info...</div>
|
|
764
803
|
) : error && !versions ? (
|
|
765
804
|
<div className="text-red-400">{error}</div>
|
|
766
805
|
) : versions?.isDocker ? (
|
|
@@ -773,17 +812,17 @@ function UpdatesSettings() {
|
|
|
773
812
|
</svg>
|
|
774
813
|
<span className="font-medium">Docker Environment</span>
|
|
775
814
|
</div>
|
|
776
|
-
<p className="text-sm text-[
|
|
815
|
+
<p className="text-sm text-[var(--color-text-secondary)]">
|
|
777
816
|
Updates are automatic when you pull a new image version.
|
|
778
817
|
</p>
|
|
779
818
|
</div>
|
|
780
819
|
|
|
781
820
|
{/* Current Version */}
|
|
782
|
-
<div className="bg-[
|
|
821
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-5">
|
|
783
822
|
<div className="flex items-center justify-between mb-4">
|
|
784
823
|
<div>
|
|
785
824
|
<h3 className="font-medium text-lg">Current Version</h3>
|
|
786
|
-
<p className="text-sm text-[
|
|
825
|
+
<p className="text-sm text-[var(--color-text-muted)]">apteva + agent binary</p>
|
|
787
826
|
</div>
|
|
788
827
|
<div className="text-right">
|
|
789
828
|
<div className="text-xl font-mono">v{versions.apteva.installed || "?"}</div>
|
|
@@ -791,15 +830,15 @@ function UpdatesSettings() {
|
|
|
791
830
|
</div>
|
|
792
831
|
|
|
793
832
|
{hasAnyUpdate ? (
|
|
794
|
-
<div className="bg-[
|
|
795
|
-
<p className="text-sm text-[
|
|
833
|
+
<div className="bg-[var(--color-accent-10)] border border-[var(--color-accent-30)] rounded-lg p-4">
|
|
834
|
+
<p className="text-sm text-[var(--color-text-secondary)] mb-3">
|
|
796
835
|
A newer version (v{versions.apteva.latest}) is available. To update:
|
|
797
836
|
</p>
|
|
798
837
|
<div className="space-y-2">
|
|
799
|
-
<code className="block bg-[
|
|
838
|
+
<code className="block bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-sm text-[var(--color-text-secondary)]">
|
|
800
839
|
docker pull apteva/apteva:latest
|
|
801
840
|
</code>
|
|
802
|
-
<code className="block bg-[
|
|
841
|
+
<code className="block bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-sm text-[var(--color-text-secondary)]">
|
|
803
842
|
docker compose up -d
|
|
804
843
|
</code>
|
|
805
844
|
</div>
|
|
@@ -809,7 +848,7 @@ function UpdatesSettings() {
|
|
|
809
848
|
setCopied("docker");
|
|
810
849
|
setTimeout(() => setCopied(null), 2000);
|
|
811
850
|
}}
|
|
812
|
-
className="mt-3 px-3 py-1.5 bg-[
|
|
851
|
+
className="mt-3 px-3 py-1.5 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] rounded text-sm"
|
|
813
852
|
>
|
|
814
853
|
{copied === "docker" ? "Copied!" : "Copy commands"}
|
|
815
854
|
</button>
|
|
@@ -822,7 +861,7 @@ function UpdatesSettings() {
|
|
|
822
861
|
)}
|
|
823
862
|
</div>
|
|
824
863
|
|
|
825
|
-
<p className="text-xs text-[
|
|
864
|
+
<p className="text-xs text-[var(--color-text-faint)]">
|
|
826
865
|
Your data is stored in a Docker volume and persists across updates.
|
|
827
866
|
</p>
|
|
828
867
|
</div>
|
|
@@ -842,32 +881,32 @@ function UpdatesSettings() {
|
|
|
842
881
|
)}
|
|
843
882
|
|
|
844
883
|
{/* Apteva App Version */}
|
|
845
|
-
<div className="bg-[
|
|
884
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-5">
|
|
846
885
|
<div className="flex items-center justify-between mb-4">
|
|
847
886
|
<div>
|
|
848
887
|
<h3 className="font-medium text-lg">apteva</h3>
|
|
849
|
-
<p className="text-sm text-[
|
|
888
|
+
<p className="text-sm text-[var(--color-text-muted)]">The app you're running</p>
|
|
850
889
|
</div>
|
|
851
890
|
<div className="text-right">
|
|
852
891
|
<div className="text-xl font-mono">v{versions.apteva.installed || "?"}</div>
|
|
853
892
|
{versions.apteva.updateAvailable && (
|
|
854
|
-
<div className="text-sm text-[
|
|
893
|
+
<div className="text-sm text-[var(--color-accent)]">→ v{versions.apteva.latest}</div>
|
|
855
894
|
)}
|
|
856
895
|
</div>
|
|
857
896
|
</div>
|
|
858
897
|
|
|
859
898
|
{versions.apteva.updateAvailable ? (
|
|
860
|
-
<div className="bg-[
|
|
861
|
-
<p className="text-sm text-[
|
|
899
|
+
<div className="bg-[var(--color-accent-10)] border border-[var(--color-accent-30)] rounded-lg p-4">
|
|
900
|
+
<p className="text-sm text-[var(--color-text-secondary)] mb-3">
|
|
862
901
|
Update by running:
|
|
863
902
|
</p>
|
|
864
903
|
<div className="flex items-center gap-2">
|
|
865
|
-
<code className="flex-1 bg-[
|
|
904
|
+
<code className="flex-1 bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-sm text-[var(--color-text-secondary)]">
|
|
866
905
|
npx apteva@latest
|
|
867
906
|
</code>
|
|
868
907
|
<button
|
|
869
908
|
onClick={() => copyCommand("npx apteva@latest", "apteva")}
|
|
870
|
-
className="px-3 py-2 bg-[
|
|
909
|
+
className="px-3 py-2 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] rounded text-sm"
|
|
871
910
|
>
|
|
872
911
|
{copied === "apteva" ? "Copied!" : "Copy"}
|
|
873
912
|
</button>
|
|
@@ -882,30 +921,30 @@ function UpdatesSettings() {
|
|
|
882
921
|
</div>
|
|
883
922
|
|
|
884
923
|
{/* Agent Binary Version */}
|
|
885
|
-
<div className="bg-[
|
|
924
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-5">
|
|
886
925
|
<div className="flex items-center justify-between mb-4">
|
|
887
926
|
<div>
|
|
888
927
|
<h3 className="font-medium text-lg">Agent Binary</h3>
|
|
889
|
-
<p className="text-sm text-[
|
|
928
|
+
<p className="text-sm text-[var(--color-text-muted)]">The Go binary that runs agents</p>
|
|
890
929
|
</div>
|
|
891
930
|
<div className="text-right">
|
|
892
931
|
<div className="text-xl font-mono">v{versions.agent.installed || "?"}</div>
|
|
893
932
|
{versions.agent.updateAvailable && (
|
|
894
|
-
<div className="text-sm text-[
|
|
933
|
+
<div className="text-sm text-[var(--color-accent)]">→ v{versions.agent.latest}</div>
|
|
895
934
|
)}
|
|
896
935
|
</div>
|
|
897
936
|
</div>
|
|
898
937
|
|
|
899
938
|
{versions.agent.updateAvailable ? (
|
|
900
|
-
<div className="bg-[
|
|
901
|
-
<p className="text-sm text-[
|
|
939
|
+
<div className="bg-[var(--color-accent-10)] border border-[var(--color-accent-30)] rounded-lg p-4">
|
|
940
|
+
<p className="text-sm text-[var(--color-text-secondary)] mb-3">
|
|
902
941
|
A new version is available. Stop all agents before updating.
|
|
903
942
|
</p>
|
|
904
943
|
<div className="flex items-center gap-2">
|
|
905
944
|
<button
|
|
906
945
|
onClick={updateAgent}
|
|
907
946
|
disabled={updatingAgent}
|
|
908
|
-
className="px-4 py-2 bg-[
|
|
947
|
+
className="px-4 py-2 bg-[var(--color-accent)] text-black rounded font-medium text-sm disabled:opacity-50"
|
|
909
948
|
>
|
|
910
949
|
{updatingAgent ? "Updating..." : "Update Agent"}
|
|
911
950
|
</button>
|
|
@@ -929,7 +968,7 @@ function UpdatesSettings() {
|
|
|
929
968
|
<button
|
|
930
969
|
onClick={checkForUpdates}
|
|
931
970
|
disabled={checking}
|
|
932
|
-
className="text-sm text-[
|
|
971
|
+
className="text-sm text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] disabled:opacity-50"
|
|
933
972
|
>
|
|
934
973
|
{checking ? "Checking..." : "Check for updates"}
|
|
935
974
|
</button>
|
|
@@ -956,29 +995,56 @@ function ProviderKeyCard({
|
|
|
956
995
|
onExtraFieldChange,
|
|
957
996
|
}: ProviderKeyCardProps) {
|
|
958
997
|
const isOllama = provider.id === "ollama";
|
|
959
|
-
const
|
|
998
|
+
const isCDP = provider.id === "cdp";
|
|
999
|
+
const isUrlBased = isOllama || isCDP;
|
|
960
1000
|
const isBrowser = provider.type === "browser";
|
|
961
1001
|
const isMultiField = provider.id === "browserbase";
|
|
962
|
-
const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number } | null>(null);
|
|
1002
|
+
const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number; isDocker?: boolean } | null>(null);
|
|
1003
|
+
const [installing, setInstalling] = React.useState(false);
|
|
1004
|
+
const [installResult, setInstallResult] = React.useState<{ success: boolean; message: string } | null>(null);
|
|
1005
|
+
|
|
1006
|
+
// Check Ollama status when configured or after install
|
|
1007
|
+
const checkOllamaStatus = React.useCallback(() => {
|
|
1008
|
+
fetch("/api/providers/ollama/status")
|
|
1009
|
+
.then(res => res.json())
|
|
1010
|
+
.then(data => setOllamaStatus({ connected: data.connected, modelCount: data.modelCount, isDocker: data.isDocker }))
|
|
1011
|
+
.catch(() => setOllamaStatus({ connected: false }));
|
|
1012
|
+
}, []);
|
|
963
1013
|
|
|
964
|
-
// Check Ollama status when configured
|
|
965
1014
|
React.useEffect(() => {
|
|
966
|
-
if (isOllama
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1015
|
+
if (isOllama) {
|
|
1016
|
+
checkOllamaStatus();
|
|
1017
|
+
}
|
|
1018
|
+
}, [isOllama, provider.hasKey, checkOllamaStatus]);
|
|
1019
|
+
|
|
1020
|
+
const handleInstallOllama = async () => {
|
|
1021
|
+
setInstalling(true);
|
|
1022
|
+
setInstallResult(null);
|
|
1023
|
+
try {
|
|
1024
|
+
const res = await fetch("/api/providers/ollama/install", { method: "POST" });
|
|
1025
|
+
const data = await res.json();
|
|
1026
|
+
if (data.success) {
|
|
1027
|
+
setInstallResult({ success: true, message: data.message });
|
|
1028
|
+
// Auto-save the default URL and refresh status
|
|
1029
|
+
checkOllamaStatus();
|
|
1030
|
+
} else {
|
|
1031
|
+
setInstallResult({ success: false, message: data.error || "Installation failed" });
|
|
1032
|
+
}
|
|
1033
|
+
} catch {
|
|
1034
|
+
setInstallResult({ success: false, message: "Failed to connect to server" });
|
|
1035
|
+
} finally {
|
|
1036
|
+
setInstalling(false);
|
|
971
1037
|
}
|
|
972
|
-
}
|
|
1038
|
+
};
|
|
973
1039
|
|
|
974
1040
|
return (
|
|
975
|
-
<div className={`bg-[
|
|
976
|
-
provider.hasKey ? 'border-green-500/20' : 'border-[
|
|
1041
|
+
<div className={`bg-[var(--color-surface)] border rounded-lg p-4 ${
|
|
1042
|
+
provider.hasKey ? 'border-green-500/20' : 'border-[var(--color-border)]'
|
|
977
1043
|
}`}>
|
|
978
1044
|
<div className="flex items-start justify-between gap-2 mb-2">
|
|
979
1045
|
<div className="min-w-0">
|
|
980
1046
|
<h3 className="font-medium">{provider.name}</h3>
|
|
981
|
-
<p className="text-sm text-[
|
|
1047
|
+
<p className="text-sm text-[var(--color-text-muted)] truncate">
|
|
982
1048
|
{isBrowser
|
|
983
1049
|
? (provider.description || "Browser automation")
|
|
984
1050
|
: provider.type === "integration"
|
|
@@ -1009,36 +1075,36 @@ function ProviderKeyCard({
|
|
|
1009
1075
|
)}
|
|
1010
1076
|
</span>
|
|
1011
1077
|
) : (
|
|
1012
|
-
<span className="text-[
|
|
1078
|
+
<span className="text-[var(--color-text-muted)] text-xs bg-[var(--color-surface-raised)] px-2 py-1 rounded whitespace-nowrap flex-shrink-0">
|
|
1013
1079
|
Not configured
|
|
1014
1080
|
</span>
|
|
1015
1081
|
)}
|
|
1016
1082
|
</div>
|
|
1017
1083
|
|
|
1018
|
-
<div className="mt-3 pt-3 border-t border-[
|
|
1084
|
+
<div className="mt-3 pt-3 border-t border-[var(--color-border)]">
|
|
1019
1085
|
{isEditing ? (
|
|
1020
1086
|
<div className="space-y-3">
|
|
1021
1087
|
{isMultiField ? (
|
|
1022
1088
|
<>
|
|
1023
1089
|
<div>
|
|
1024
|
-
<label className="block text-xs text-[
|
|
1090
|
+
<label className="block text-xs text-[var(--color-text-secondary)] mb-1">API Key</label>
|
|
1025
1091
|
<input
|
|
1026
1092
|
type="password"
|
|
1027
1093
|
value={apiKey}
|
|
1028
1094
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
1029
1095
|
placeholder={provider.hasKey ? "Enter new API key..." : "Enter API key..."}
|
|
1030
1096
|
autoFocus
|
|
1031
|
-
className="w-full bg-[
|
|
1097
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1032
1098
|
/>
|
|
1033
1099
|
</div>
|
|
1034
1100
|
<div>
|
|
1035
|
-
<label className="block text-xs text-[
|
|
1101
|
+
<label className="block text-xs text-[var(--color-text-secondary)] mb-1">Project ID</label>
|
|
1036
1102
|
<input
|
|
1037
1103
|
type="text"
|
|
1038
1104
|
value={extraField || ""}
|
|
1039
1105
|
onChange={e => onExtraFieldChange?.(e.target.value)}
|
|
1040
1106
|
placeholder="Enter your Browserbase project ID..."
|
|
1041
|
-
className="w-full bg-[
|
|
1107
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1042
1108
|
/>
|
|
1043
1109
|
</div>
|
|
1044
1110
|
</>
|
|
@@ -1049,22 +1115,17 @@ function ProviderKeyCard({
|
|
|
1049
1115
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
1050
1116
|
placeholder={isOllama
|
|
1051
1117
|
? "http://localhost:11434"
|
|
1052
|
-
:
|
|
1053
|
-
|
|
1054
|
-
: provider.id === "chrome"
|
|
1055
|
-
? "http://localhost:9222"
|
|
1056
|
-
: provider.hasKey ? "Enter new API key..." : "Enter API key..."}
|
|
1118
|
+
: isCDP ? "ws://localhost:9222"
|
|
1119
|
+
: provider.hasKey ? "Enter new API key..." : "Enter API key..."}
|
|
1057
1120
|
autoFocus
|
|
1058
|
-
className="w-full bg-[
|
|
1121
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1059
1122
|
/>
|
|
1060
1123
|
)}
|
|
1061
1124
|
{isUrlBased && (
|
|
1062
|
-
<p className="text-xs text-[
|
|
1063
|
-
{
|
|
1064
|
-
? "Enter
|
|
1065
|
-
:
|
|
1066
|
-
? "Enter your BrowserEngine service URL (e.g., http://localhost:8098)"
|
|
1067
|
-
: "Enter your Chrome DevTools URL (e.g., http://localhost:9222)"}
|
|
1125
|
+
<p className="text-xs text-[var(--color-text-muted)]">
|
|
1126
|
+
{isCDP
|
|
1127
|
+
? "Enter the CDP URL of your browser (e.g., ws://localhost:9222)"
|
|
1128
|
+
: "Enter your Ollama server URL. Default is http://localhost:11434"}
|
|
1068
1129
|
</p>
|
|
1069
1130
|
)}
|
|
1070
1131
|
{error && <p className="text-red-400 text-sm">{error}</p>}
|
|
@@ -1072,21 +1133,38 @@ function ProviderKeyCard({
|
|
|
1072
1133
|
<div className="flex gap-2">
|
|
1073
1134
|
<button
|
|
1074
1135
|
onClick={onCancelEdit}
|
|
1075
|
-
className="flex-1 px-3 py-1.5 border border-[
|
|
1136
|
+
className="flex-1 px-3 py-1.5 border border-[var(--color-border-light)] rounded text-sm hover:border-[var(--color-text-muted)]"
|
|
1076
1137
|
>
|
|
1077
1138
|
Cancel
|
|
1078
1139
|
</button>
|
|
1079
1140
|
<button
|
|
1080
1141
|
onClick={onSave}
|
|
1081
1142
|
disabled={!apiKey || saving}
|
|
1082
|
-
className="flex-1 px-3 py-1.5 bg-[
|
|
1143
|
+
className="flex-1 px-3 py-1.5 bg-[var(--color-accent)] text-black rounded text-sm font-medium disabled:opacity-50"
|
|
1083
1144
|
>
|
|
1084
1145
|
{testing ? "Validating..." : saving ? "Saving..." : isUrlBased ? "Connect" : "Save"}
|
|
1085
1146
|
</button>
|
|
1086
1147
|
</div>
|
|
1087
1148
|
</div>
|
|
1088
1149
|
) : provider.hasKey ? (
|
|
1089
|
-
<div
|
|
1150
|
+
<div>
|
|
1151
|
+
{isOllama && ollamaStatus && !ollamaStatus.connected && !ollamaStatus.isDocker && (
|
|
1152
|
+
<div className="mb-3">
|
|
1153
|
+
<button
|
|
1154
|
+
onClick={handleInstallOllama}
|
|
1155
|
+
disabled={installing}
|
|
1156
|
+
className="w-full px-3 py-1.5 bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30 rounded text-sm font-medium transition disabled:opacity-50 disabled:cursor-wait"
|
|
1157
|
+
>
|
|
1158
|
+
{installing ? "Starting Ollama..." : "Start Ollama"}
|
|
1159
|
+
</button>
|
|
1160
|
+
{installResult && (
|
|
1161
|
+
<p className={`text-xs mt-1.5 ${installResult.success ? "text-green-400" : "text-red-400"}`}>
|
|
1162
|
+
{installResult.message}
|
|
1163
|
+
</p>
|
|
1164
|
+
)}
|
|
1165
|
+
</div>
|
|
1166
|
+
)}
|
|
1167
|
+
<div className="flex items-center justify-between">
|
|
1090
1168
|
{provider.docsUrl ? (
|
|
1091
1169
|
<a
|
|
1092
1170
|
href={provider.docsUrl}
|
|
@@ -1094,7 +1172,7 @@ function ProviderKeyCard({
|
|
|
1094
1172
|
rel="noopener noreferrer"
|
|
1095
1173
|
className="text-sm text-[#3b82f6] hover:underline"
|
|
1096
1174
|
>
|
|
1097
|
-
{isOllama ? "
|
|
1175
|
+
{isOllama ? "Ollama docs" : "View docs"}
|
|
1098
1176
|
</a>
|
|
1099
1177
|
) : (
|
|
1100
1178
|
<span />
|
|
@@ -1102,7 +1180,7 @@ function ProviderKeyCard({
|
|
|
1102
1180
|
<div className="flex items-center gap-3">
|
|
1103
1181
|
<button
|
|
1104
1182
|
onClick={onStartEdit}
|
|
1105
|
-
className="text-sm text-[
|
|
1183
|
+
className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)]"
|
|
1106
1184
|
>
|
|
1107
1185
|
{isUrlBased ? "Change URL" : "Update key"}
|
|
1108
1186
|
</button>
|
|
@@ -1113,27 +1191,46 @@ function ProviderKeyCard({
|
|
|
1113
1191
|
Remove
|
|
1114
1192
|
</button>
|
|
1115
1193
|
</div>
|
|
1194
|
+
</div>
|
|
1116
1195
|
</div>
|
|
1117
1196
|
) : (
|
|
1118
|
-
<div
|
|
1119
|
-
{
|
|
1120
|
-
<
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1197
|
+
<div>
|
|
1198
|
+
{isOllama && !ollamaStatus?.isDocker && (
|
|
1199
|
+
<div className="mb-3">
|
|
1200
|
+
<button
|
|
1201
|
+
onClick={handleInstallOllama}
|
|
1202
|
+
disabled={installing}
|
|
1203
|
+
className="w-full px-3 py-2 bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30 rounded text-sm font-medium transition disabled:opacity-50 disabled:cursor-wait"
|
|
1204
|
+
>
|
|
1205
|
+
{installing ? "Installing Ollama..." : ollamaStatus?.connected ? "Ollama Running" : "Install Ollama"}
|
|
1206
|
+
</button>
|
|
1207
|
+
{installResult && (
|
|
1208
|
+
<p className={`text-xs mt-1.5 ${installResult.success ? "text-green-400" : "text-red-400"}`}>
|
|
1209
|
+
{installResult.message}
|
|
1210
|
+
</p>
|
|
1211
|
+
)}
|
|
1212
|
+
</div>
|
|
1130
1213
|
)}
|
|
1131
|
-
<
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1214
|
+
<div className="flex items-center justify-between">
|
|
1215
|
+
{provider.docsUrl ? (
|
|
1216
|
+
<a
|
|
1217
|
+
href={provider.docsUrl}
|
|
1218
|
+
target="_blank"
|
|
1219
|
+
rel="noopener noreferrer"
|
|
1220
|
+
className="text-sm text-[#3b82f6] hover:underline"
|
|
1221
|
+
>
|
|
1222
|
+
{isOllama ? "Manual install" : isBrowser ? "View docs" : "Get API key"}
|
|
1223
|
+
</a>
|
|
1224
|
+
) : (
|
|
1225
|
+
<span />
|
|
1226
|
+
)}
|
|
1227
|
+
<button
|
|
1228
|
+
onClick={onStartEdit}
|
|
1229
|
+
className="text-sm text-[var(--color-accent)] hover:text-[var(--color-accent-hover)]"
|
|
1230
|
+
>
|
|
1231
|
+
{isUrlBased ? "Configure" : "+ Add key"}
|
|
1232
|
+
</button>
|
|
1233
|
+
</div>
|
|
1137
1234
|
</div>
|
|
1138
1235
|
)}
|
|
1139
1236
|
</div>
|
|
@@ -1180,8 +1277,11 @@ function IntegrationKeyCard({
|
|
|
1180
1277
|
const [expanded, setExpanded] = useState(false);
|
|
1181
1278
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
1182
1279
|
const [localSaving, setLocalSaving] = useState(false);
|
|
1280
|
+
const [bbProjectId, setBbProjectId] = useState(""); // Browserbase project ID (their internal ID)
|
|
1183
1281
|
const { confirm, ConfirmDialog } = useConfirm();
|
|
1184
1282
|
|
|
1283
|
+
const isBrowserbase = provider.id === "browserbase";
|
|
1284
|
+
|
|
1185
1285
|
// Fetch all keys for this provider
|
|
1186
1286
|
const fetchKeys = async () => {
|
|
1187
1287
|
try {
|
|
@@ -1212,12 +1312,18 @@ function IntegrationKeyCard({
|
|
|
1212
1312
|
setLocalSaving(true);
|
|
1213
1313
|
setLocalError(null);
|
|
1214
1314
|
|
|
1315
|
+
// For Browserbase, combine API key + BB project ID into JSON
|
|
1316
|
+
let keyToSave = apiKey;
|
|
1317
|
+
if (isBrowserbase && bbProjectId) {
|
|
1318
|
+
keyToSave = JSON.stringify({ api_key: apiKey, project_id: bbProjectId });
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1215
1321
|
try {
|
|
1216
1322
|
const res = await authFetch(`/api/keys/${provider.id}`, {
|
|
1217
1323
|
method: "POST",
|
|
1218
1324
|
headers: { "Content-Type": "application/json" },
|
|
1219
1325
|
body: JSON.stringify({
|
|
1220
|
-
key:
|
|
1326
|
+
key: keyToSave,
|
|
1221
1327
|
project_id: selectedProjectId || null,
|
|
1222
1328
|
}),
|
|
1223
1329
|
});
|
|
@@ -1226,6 +1332,7 @@ function IntegrationKeyCard({
|
|
|
1226
1332
|
|
|
1227
1333
|
if (res.ok) {
|
|
1228
1334
|
onApiKeyChange("");
|
|
1335
|
+
setBbProjectId("");
|
|
1229
1336
|
setSelectedProjectId("");
|
|
1230
1337
|
onCancelEdit();
|
|
1231
1338
|
fetchKeys();
|
|
@@ -1264,50 +1371,50 @@ function IntegrationKeyCard({
|
|
|
1264
1371
|
// Simple view when projects not enabled
|
|
1265
1372
|
if (!projectsEnabled) {
|
|
1266
1373
|
return (
|
|
1267
|
-
<div className={`bg-[
|
|
1268
|
-
provider.hasKey ? 'border-[
|
|
1374
|
+
<div className={`bg-[var(--color-surface)] border rounded-lg p-4 ${
|
|
1375
|
+
provider.hasKey ? 'border-[var(--color-accent-20)]' : 'border-[var(--color-border)]'
|
|
1269
1376
|
}`}>
|
|
1270
1377
|
<div className="flex items-center justify-between mb-2">
|
|
1271
1378
|
<div>
|
|
1272
1379
|
<h3 className="font-medium">{provider.name}</h3>
|
|
1273
|
-
<p className="text-sm text-[
|
|
1380
|
+
<p className="text-sm text-[var(--color-text-muted)]">{provider.description || "MCP integration"}</p>
|
|
1274
1381
|
</div>
|
|
1275
1382
|
{provider.hasKey ? (
|
|
1276
|
-
<span className="text-[
|
|
1383
|
+
<span className="text-[var(--color-accent)] text-xs flex items-center gap-1 bg-[var(--color-accent-10)] px-2 py-1 rounded">
|
|
1277
1384
|
<CheckIcon className="w-3 h-3" />
|
|
1278
1385
|
{provider.keyHint}
|
|
1279
1386
|
</span>
|
|
1280
1387
|
) : (
|
|
1281
|
-
<span className="text-[
|
|
1388
|
+
<span className="text-[var(--color-text-muted)] text-xs bg-[var(--color-surface-raised)] px-2 py-1 rounded">
|
|
1282
1389
|
Not configured
|
|
1283
1390
|
</span>
|
|
1284
1391
|
)}
|
|
1285
1392
|
</div>
|
|
1286
1393
|
|
|
1287
|
-
<div className="mt-3 pt-3 border-t border-[
|
|
1394
|
+
<div className="mt-3 pt-3 border-t border-[var(--color-border)]">
|
|
1288
1395
|
{isEditing ? (
|
|
1289
1396
|
<div className="space-y-3">
|
|
1290
1397
|
<input
|
|
1291
|
-
type=
|
|
1398
|
+
type={inputType}
|
|
1292
1399
|
value={apiKey}
|
|
1293
1400
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
1294
|
-
placeholder={provider.hasKey ?
|
|
1401
|
+
placeholder={provider.hasKey ? `Enter new ${isUrlBased ? "URL" : "API key"}...` : inputPlaceholder}
|
|
1295
1402
|
autoFocus
|
|
1296
|
-
className="w-full bg-[
|
|
1403
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1297
1404
|
/>
|
|
1298
1405
|
{error && <p className="text-red-400 text-sm">{error}</p>}
|
|
1299
1406
|
{success && <p className="text-green-400 text-sm">{success}</p>}
|
|
1300
1407
|
<div className="flex gap-2">
|
|
1301
1408
|
<button
|
|
1302
1409
|
onClick={onCancelEdit}
|
|
1303
|
-
className="flex-1 px-3 py-1.5 border border-[
|
|
1410
|
+
className="flex-1 px-3 py-1.5 border border-[var(--color-border-light)] rounded text-sm hover:border-[var(--color-text-muted)]"
|
|
1304
1411
|
>
|
|
1305
1412
|
Cancel
|
|
1306
1413
|
</button>
|
|
1307
1414
|
<button
|
|
1308
1415
|
onClick={onSave}
|
|
1309
1416
|
disabled={!apiKey || saving}
|
|
1310
|
-
className="flex-1 px-3 py-1.5 bg-[
|
|
1417
|
+
className="flex-1 px-3 py-1.5 bg-[var(--color-accent)] text-black rounded text-sm font-medium disabled:opacity-50"
|
|
1311
1418
|
>
|
|
1312
1419
|
{testing ? "Validating..." : saving ? "Saving..." : "Save"}
|
|
1313
1420
|
</button>
|
|
@@ -1326,7 +1433,7 @@ function IntegrationKeyCard({
|
|
|
1326
1433
|
<div className="flex items-center gap-3">
|
|
1327
1434
|
<button
|
|
1328
1435
|
onClick={onStartEdit}
|
|
1329
|
-
className="text-sm text-[
|
|
1436
|
+
className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)]"
|
|
1330
1437
|
>
|
|
1331
1438
|
Update key
|
|
1332
1439
|
</button>
|
|
@@ -1350,7 +1457,7 @@ function IntegrationKeyCard({
|
|
|
1350
1457
|
</a>
|
|
1351
1458
|
<button
|
|
1352
1459
|
onClick={onStartEdit}
|
|
1353
|
-
className="text-sm text-[
|
|
1460
|
+
className="text-sm text-[var(--color-accent)] hover:text-[var(--color-accent-hover)]"
|
|
1354
1461
|
>
|
|
1355
1462
|
+ Add key
|
|
1356
1463
|
</button>
|
|
@@ -1361,25 +1468,32 @@ function IntegrationKeyCard({
|
|
|
1361
1468
|
);
|
|
1362
1469
|
}
|
|
1363
1470
|
|
|
1471
|
+
// Determine input type and placeholder based on provider
|
|
1472
|
+
const isUrlBased = provider.isLocal;
|
|
1473
|
+
const inputType = isUrlBased ? "text" : "password";
|
|
1474
|
+
const inputPlaceholder = isUrlBased
|
|
1475
|
+
? (provider.id === "cdp" ? "ws://localhost:9222" : "http://localhost:11434")
|
|
1476
|
+
: "Enter API key...";
|
|
1477
|
+
|
|
1364
1478
|
// Enhanced view with project support
|
|
1365
1479
|
return (
|
|
1366
1480
|
<>
|
|
1367
1481
|
{ConfirmDialog}
|
|
1368
|
-
<div className={`bg-[
|
|
1369
|
-
keys.length > 0 ? 'border-[
|
|
1482
|
+
<div className={`bg-[var(--color-surface)] border rounded-lg p-4 ${
|
|
1483
|
+
keys.length > 0 ? 'border-[var(--color-accent-20)]' : 'border-[var(--color-border)]'
|
|
1370
1484
|
}`}>
|
|
1371
1485
|
<div className="flex items-center justify-between mb-2">
|
|
1372
1486
|
<div>
|
|
1373
1487
|
<h3 className="font-medium">{provider.name}</h3>
|
|
1374
|
-
<p className="text-sm text-[
|
|
1488
|
+
<p className="text-sm text-[var(--color-text-muted)]">{provider.description || "MCP integration"}</p>
|
|
1375
1489
|
</div>
|
|
1376
1490
|
{keys.length > 0 ? (
|
|
1377
|
-
<span className="text-[
|
|
1491
|
+
<span className="text-[var(--color-accent)] text-xs flex items-center gap-1 bg-[var(--color-accent-10)] px-2 py-1 rounded">
|
|
1378
1492
|
<CheckIcon className="w-3 h-3" />
|
|
1379
1493
|
{keys.length} key{keys.length !== 1 ? "s" : ""}
|
|
1380
1494
|
</span>
|
|
1381
1495
|
) : (
|
|
1382
|
-
<span className="text-[
|
|
1496
|
+
<span className="text-[var(--color-text-muted)] text-xs bg-[var(--color-surface-raised)] px-2 py-1 rounded">
|
|
1383
1497
|
Not configured
|
|
1384
1498
|
</span>
|
|
1385
1499
|
)}
|
|
@@ -1390,11 +1504,11 @@ function IntegrationKeyCard({
|
|
|
1390
1504
|
<div className="mt-3 space-y-2">
|
|
1391
1505
|
{/* Global Key */}
|
|
1392
1506
|
{globalKey && (
|
|
1393
|
-
<div className="flex items-center justify-between text-sm bg-[
|
|
1507
|
+
<div className="flex items-center justify-between text-sm bg-[var(--color-bg)] rounded px-3 py-2">
|
|
1394
1508
|
<div className="flex items-center gap-2">
|
|
1395
|
-
<span className="text-[
|
|
1396
|
-
<span className="text-[
|
|
1397
|
-
<span className="text-[
|
|
1509
|
+
<span className="text-[var(--color-text-secondary)]">Global</span>
|
|
1510
|
+
<span className="text-[var(--color-text-faint)]">·</span>
|
|
1511
|
+
<span className="text-[var(--color-text-muted)] font-mono text-xs">{globalKey.key_hint}</span>
|
|
1398
1512
|
</div>
|
|
1399
1513
|
<button
|
|
1400
1514
|
onClick={() => handleDeleteKey(globalKey.id, "Global")}
|
|
@@ -1407,15 +1521,15 @@ function IntegrationKeyCard({
|
|
|
1407
1521
|
|
|
1408
1522
|
{/* Project Keys - show first 2, expand for more */}
|
|
1409
1523
|
{projectKeys.slice(0, expanded ? undefined : 2).map(key => (
|
|
1410
|
-
<div key={key.id} className="flex items-center justify-between text-sm bg-[
|
|
1524
|
+
<div key={key.id} className="flex items-center justify-between text-sm bg-[var(--color-bg)] rounded px-3 py-2">
|
|
1411
1525
|
<div className="flex items-center gap-2 min-w-0">
|
|
1412
1526
|
<span
|
|
1413
1527
|
className="w-2 h-2 rounded-full flex-shrink-0"
|
|
1414
1528
|
style={{ backgroundColor: getProjectColor(key.project_id!) }}
|
|
1415
1529
|
/>
|
|
1416
|
-
<span className="text-[
|
|
1417
|
-
<span className="text-[
|
|
1418
|
-
<span className="text-[
|
|
1530
|
+
<span className="text-[var(--color-text-secondary)] truncate">{key.name || getProjectName(key.project_id!)}</span>
|
|
1531
|
+
<span className="text-[var(--color-text-faint)]">·</span>
|
|
1532
|
+
<span className="text-[var(--color-text-muted)] font-mono text-xs">{key.key_hint}</span>
|
|
1419
1533
|
</div>
|
|
1420
1534
|
<button
|
|
1421
1535
|
onClick={() => handleDeleteKey(key.id, key.name || getProjectName(key.project_id!))}
|
|
@@ -1429,7 +1543,7 @@ function IntegrationKeyCard({
|
|
|
1429
1543
|
{projectKeys.length > 2 && !expanded && (
|
|
1430
1544
|
<button
|
|
1431
1545
|
onClick={() => setExpanded(true)}
|
|
1432
|
-
className="text-xs text-[
|
|
1546
|
+
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] w-full text-center py-1"
|
|
1433
1547
|
>
|
|
1434
1548
|
Show {projectKeys.length - 2} more...
|
|
1435
1549
|
</button>
|
|
@@ -1437,18 +1551,28 @@ function IntegrationKeyCard({
|
|
|
1437
1551
|
</div>
|
|
1438
1552
|
)}
|
|
1439
1553
|
|
|
1440
|
-
<div className="mt-3 pt-3 border-t border-[
|
|
1554
|
+
<div className="mt-3 pt-3 border-t border-[var(--color-border)]">
|
|
1441
1555
|
{isEditing ? (
|
|
1442
1556
|
<div className="space-y-3">
|
|
1443
1557
|
<input
|
|
1444
|
-
type=
|
|
1558
|
+
type={inputType}
|
|
1445
1559
|
value={apiKey}
|
|
1446
1560
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
1447
|
-
placeholder=
|
|
1561
|
+
placeholder={inputPlaceholder}
|
|
1448
1562
|
autoFocus
|
|
1449
|
-
className="w-full bg-[
|
|
1563
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1450
1564
|
/>
|
|
1451
1565
|
|
|
1566
|
+
{isBrowserbase && (
|
|
1567
|
+
<input
|
|
1568
|
+
type="text"
|
|
1569
|
+
value={bbProjectId}
|
|
1570
|
+
onChange={e => setBbProjectId(e.target.value)}
|
|
1571
|
+
placeholder="Browserbase Project ID (optional)"
|
|
1572
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)] text-sm"
|
|
1573
|
+
/>
|
|
1574
|
+
)}
|
|
1575
|
+
|
|
1452
1576
|
<Select
|
|
1453
1577
|
value={selectedProjectId}
|
|
1454
1578
|
onChange={setSelectedProjectId}
|
|
@@ -1468,14 +1592,14 @@ function IntegrationKeyCard({
|
|
|
1468
1592
|
setSelectedProjectId("");
|
|
1469
1593
|
setLocalError(null);
|
|
1470
1594
|
}}
|
|
1471
|
-
className="flex-1 px-3 py-1.5 border border-[
|
|
1595
|
+
className="flex-1 px-3 py-1.5 border border-[var(--color-border-light)] rounded text-sm hover:border-[var(--color-text-muted)]"
|
|
1472
1596
|
>
|
|
1473
1597
|
Cancel
|
|
1474
1598
|
</button>
|
|
1475
1599
|
<button
|
|
1476
1600
|
onClick={handleSaveWithProject}
|
|
1477
1601
|
disabled={!apiKey || localSaving}
|
|
1478
|
-
className="flex-1 px-3 py-1.5 bg-[
|
|
1602
|
+
className="flex-1 px-3 py-1.5 bg-[var(--color-accent)] text-black rounded text-sm font-medium disabled:opacity-50"
|
|
1479
1603
|
>
|
|
1480
1604
|
{localSaving ? "Saving..." : "Save"}
|
|
1481
1605
|
</button>
|
|
@@ -1493,7 +1617,7 @@ function IntegrationKeyCard({
|
|
|
1493
1617
|
</a>
|
|
1494
1618
|
<button
|
|
1495
1619
|
onClick={onStartEdit}
|
|
1496
|
-
className="text-sm text-[
|
|
1620
|
+
className="text-sm text-[var(--color-accent)] hover:text-[var(--color-accent-hover)]"
|
|
1497
1621
|
>
|
|
1498
1622
|
+ Add key
|
|
1499
1623
|
</button>
|
|
@@ -1612,14 +1736,14 @@ function ApiKeysSettings() {
|
|
|
1612
1736
|
<div className="mb-6 flex items-center justify-between gap-4">
|
|
1613
1737
|
<div>
|
|
1614
1738
|
<h1 className="text-2xl font-semibold mb-1">API Keys</h1>
|
|
1615
|
-
<p className="text-[
|
|
1616
|
-
Create personal API keys for programmatic access. Use them with the <code className="text-[
|
|
1739
|
+
<p className="text-[var(--color-text-muted)]">
|
|
1740
|
+
Create personal API keys for programmatic access. Use them with the <code className="text-[var(--color-text-secondary)] bg-[var(--color-surface-raised)] px-1 rounded text-xs">X-API-Key</code> header.
|
|
1617
1741
|
</p>
|
|
1618
1742
|
</div>
|
|
1619
1743
|
{!showCreate && !newKey && (
|
|
1620
1744
|
<button
|
|
1621
1745
|
onClick={() => { setShowCreate(true); setError(null); }}
|
|
1622
|
-
className="flex items-center gap-2 bg-[
|
|
1746
|
+
className="flex items-center gap-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-black px-4 py-2 rounded font-medium transition flex-shrink-0"
|
|
1623
1747
|
>
|
|
1624
1748
|
<PlusIcon className="w-4 h-4" />
|
|
1625
1749
|
New Key
|
|
@@ -1634,23 +1758,23 @@ function ApiKeysSettings() {
|
|
|
1634
1758
|
<CheckIcon className="w-5 h-5" />
|
|
1635
1759
|
<span className="font-medium">API key created</span>
|
|
1636
1760
|
</div>
|
|
1637
|
-
<p className="text-sm text-[
|
|
1761
|
+
<p className="text-sm text-[var(--color-text-secondary)] mb-3">
|
|
1638
1762
|
Copy this key now. You won't be able to see it again.
|
|
1639
1763
|
</p>
|
|
1640
1764
|
<div className="flex items-center gap-2">
|
|
1641
|
-
<code className="flex-1 bg-[
|
|
1765
|
+
<code className="flex-1 bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-sm text-[var(--color-text)] break-all select-all">
|
|
1642
1766
|
{newKey}
|
|
1643
1767
|
</code>
|
|
1644
1768
|
<button
|
|
1645
1769
|
onClick={copyKey}
|
|
1646
|
-
className="px-3 py-2 bg-[
|
|
1770
|
+
className="px-3 py-2 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] rounded text-sm flex-shrink-0"
|
|
1647
1771
|
>
|
|
1648
1772
|
{copied ? "Copied!" : "Copy"}
|
|
1649
1773
|
</button>
|
|
1650
1774
|
</div>
|
|
1651
1775
|
<button
|
|
1652
1776
|
onClick={() => { setNewKey(null); setShowCreate(false); }}
|
|
1653
|
-
className="mt-3 text-sm text-[
|
|
1777
|
+
className="mt-3 text-sm text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
|
|
1654
1778
|
>
|
|
1655
1779
|
Done
|
|
1656
1780
|
</button>
|
|
@@ -1659,26 +1783,26 @@ function ApiKeysSettings() {
|
|
|
1659
1783
|
|
|
1660
1784
|
{/* Create Form */}
|
|
1661
1785
|
{showCreate && !newKey && (
|
|
1662
|
-
<div className="bg-[
|
|
1786
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 mb-6">
|
|
1663
1787
|
<h3 className="font-medium mb-4">Create new API key</h3>
|
|
1664
1788
|
<div className="space-y-4 max-w-md">
|
|
1665
1789
|
<div>
|
|
1666
|
-
<label className="block text-sm text-[
|
|
1790
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Name</label>
|
|
1667
1791
|
<input
|
|
1668
1792
|
type="text"
|
|
1669
1793
|
value={name}
|
|
1670
1794
|
onChange={e => setName(e.target.value)}
|
|
1671
1795
|
placeholder="e.g. CI Pipeline, My Script"
|
|
1672
1796
|
autoFocus
|
|
1673
|
-
className="w-full bg-[
|
|
1797
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1674
1798
|
/>
|
|
1675
1799
|
</div>
|
|
1676
1800
|
<div>
|
|
1677
|
-
<label className="block text-sm text-[
|
|
1801
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Expiration</label>
|
|
1678
1802
|
<select
|
|
1679
1803
|
value={expiresInDays}
|
|
1680
1804
|
onChange={e => setExpiresInDays(e.target.value)}
|
|
1681
|
-
className="w-full bg-[
|
|
1805
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1682
1806
|
>
|
|
1683
1807
|
<option value="30">30 days</option>
|
|
1684
1808
|
<option value="90">90 days</option>
|
|
@@ -1693,14 +1817,14 @@ function ApiKeysSettings() {
|
|
|
1693
1817
|
<div className="flex gap-2">
|
|
1694
1818
|
<button
|
|
1695
1819
|
onClick={() => { setShowCreate(false); setError(null); setName(""); }}
|
|
1696
|
-
className="flex-1 px-3 py-2 border border-[
|
|
1820
|
+
className="flex-1 px-3 py-2 border border-[var(--color-border-light)] rounded text-sm hover:border-[var(--color-text-muted)]"
|
|
1697
1821
|
>
|
|
1698
1822
|
Cancel
|
|
1699
1823
|
</button>
|
|
1700
1824
|
<button
|
|
1701
1825
|
onClick={handleCreate}
|
|
1702
1826
|
disabled={creating || !name.trim()}
|
|
1703
|
-
className="flex-1 px-3 py-2 bg-[
|
|
1827
|
+
className="flex-1 px-3 py-2 bg-[var(--color-accent)] text-black rounded text-sm font-medium disabled:opacity-50"
|
|
1704
1828
|
>
|
|
1705
1829
|
{creating ? "Creating..." : "Create Key"}
|
|
1706
1830
|
</button>
|
|
@@ -1711,7 +1835,7 @@ function ApiKeysSettings() {
|
|
|
1711
1835
|
|
|
1712
1836
|
{/* Keys List */}
|
|
1713
1837
|
{keys.length === 0 ? (
|
|
1714
|
-
<div className="text-center py-12 text-[
|
|
1838
|
+
<div className="text-center py-12 text-[var(--color-text-muted)]">
|
|
1715
1839
|
<p className="text-lg mb-2">No API keys yet</p>
|
|
1716
1840
|
<p className="text-sm">Create an API key to access apteva programmatically.</p>
|
|
1717
1841
|
</div>
|
|
@@ -1720,8 +1844,8 @@ function ApiKeysSettings() {
|
|
|
1720
1844
|
{keys.map(key => (
|
|
1721
1845
|
<div
|
|
1722
1846
|
key={key.id}
|
|
1723
|
-
className={`bg-[
|
|
1724
|
-
!key.is_active || isExpired(key.expires_at) ? "border-[
|
|
1847
|
+
className={`bg-[var(--color-surface)] border rounded-lg p-4 flex items-center gap-4 ${
|
|
1848
|
+
!key.is_active || isExpired(key.expires_at) ? "border-[var(--color-border)] opacity-60" : "border-[var(--color-border)]"
|
|
1725
1849
|
}`}
|
|
1726
1850
|
>
|
|
1727
1851
|
<div className="flex-1 min-w-0">
|
|
@@ -1734,8 +1858,8 @@ function ApiKeysSettings() {
|
|
|
1734
1858
|
<span className="text-xs text-yellow-400 bg-yellow-500/10 px-2 py-0.5 rounded">Expired</span>
|
|
1735
1859
|
)}
|
|
1736
1860
|
</div>
|
|
1737
|
-
<div className="flex items-center gap-3 text-sm text-[
|
|
1738
|
-
<code className="font-mono text-xs bg-[
|
|
1861
|
+
<div className="flex items-center gap-3 text-sm text-[var(--color-text-muted)]">
|
|
1862
|
+
<code className="font-mono text-xs bg-[var(--color-bg)] px-2 py-0.5 rounded">{key.prefix}...</code>
|
|
1739
1863
|
<span>Created {formatDate(key.created_at)}</span>
|
|
1740
1864
|
{key.expires_at && <span>Expires {formatDate(key.expires_at)}</span>}
|
|
1741
1865
|
{key.last_used_at && <span>Last used {formatDate(key.last_used_at)}</span>}
|
|
@@ -1756,9 +1880,9 @@ function ApiKeysSettings() {
|
|
|
1756
1880
|
|
|
1757
1881
|
{/* Usage Info */}
|
|
1758
1882
|
{keys.length > 0 && (
|
|
1759
|
-
<div className="mt-6 bg-[
|
|
1883
|
+
<div className="mt-6 bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
1760
1884
|
<h3 className="font-medium mb-2 text-sm">Usage</h3>
|
|
1761
|
-
<code className="block bg-[
|
|
1885
|
+
<code className="block bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-xs text-[var(--color-text-secondary)]">
|
|
1762
1886
|
curl -H "X-API-Key: apt_..." http://localhost:4280/api/agents
|
|
1763
1887
|
</code>
|
|
1764
1888
|
</div>
|
|
@@ -1824,26 +1948,26 @@ function AccountSettings() {
|
|
|
1824
1948
|
<div className="max-w-4xl w-full">
|
|
1825
1949
|
<div className="mb-6">
|
|
1826
1950
|
<h1 className="text-2xl font-semibold mb-1">Account Settings</h1>
|
|
1827
|
-
<p className="text-[
|
|
1951
|
+
<p className="text-[var(--color-text-muted)]">Manage your account and security.</p>
|
|
1828
1952
|
</div>
|
|
1829
1953
|
|
|
1830
1954
|
{/* User Info */}
|
|
1831
1955
|
{user && (
|
|
1832
|
-
<div className="bg-[
|
|
1956
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 mb-6">
|
|
1833
1957
|
<h3 className="font-medium mb-3">Profile</h3>
|
|
1834
1958
|
<div className="space-y-2 text-sm">
|
|
1835
1959
|
<div className="flex justify-between">
|
|
1836
|
-
<span className="text-[
|
|
1960
|
+
<span className="text-[var(--color-text-muted)]">Username</span>
|
|
1837
1961
|
<span>{user.username}</span>
|
|
1838
1962
|
</div>
|
|
1839
1963
|
{user.email && (
|
|
1840
1964
|
<div className="flex justify-between">
|
|
1841
|
-
<span className="text-[
|
|
1965
|
+
<span className="text-[var(--color-text-muted)]">Email</span>
|
|
1842
1966
|
<span>{user.email}</span>
|
|
1843
1967
|
</div>
|
|
1844
1968
|
)}
|
|
1845
1969
|
<div className="flex justify-between">
|
|
1846
|
-
<span className="text-[
|
|
1970
|
+
<span className="text-[var(--color-text-muted)]">Role</span>
|
|
1847
1971
|
<span className="capitalize">{user.role}</span>
|
|
1848
1972
|
</div>
|
|
1849
1973
|
</div>
|
|
@@ -1851,37 +1975,37 @@ function AccountSettings() {
|
|
|
1851
1975
|
)}
|
|
1852
1976
|
|
|
1853
1977
|
{/* Change Password */}
|
|
1854
|
-
<div className="bg-[
|
|
1978
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
1855
1979
|
<h3 className="font-medium mb-4">Change Password</h3>
|
|
1856
1980
|
|
|
1857
1981
|
<div className="space-y-4 max-w-md">
|
|
1858
1982
|
<div>
|
|
1859
|
-
<label className="block text-sm text-[
|
|
1983
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Current Password</label>
|
|
1860
1984
|
<input
|
|
1861
1985
|
type="password"
|
|
1862
1986
|
value={currentPassword}
|
|
1863
1987
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
1864
|
-
className="w-full bg-[
|
|
1988
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1865
1989
|
/>
|
|
1866
1990
|
</div>
|
|
1867
1991
|
|
|
1868
1992
|
<div>
|
|
1869
|
-
<label className="block text-sm text-[
|
|
1993
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">New Password</label>
|
|
1870
1994
|
<input
|
|
1871
1995
|
type="password"
|
|
1872
1996
|
value={newPassword}
|
|
1873
1997
|
onChange={(e) => setNewPassword(e.target.value)}
|
|
1874
|
-
className="w-full bg-[
|
|
1998
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1875
1999
|
/>
|
|
1876
2000
|
</div>
|
|
1877
2001
|
|
|
1878
2002
|
<div>
|
|
1879
|
-
<label className="block text-sm text-[
|
|
2003
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Confirm New Password</label>
|
|
1880
2004
|
<input
|
|
1881
2005
|
type="password"
|
|
1882
2006
|
value={confirmPassword}
|
|
1883
2007
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
1884
|
-
className="w-full bg-[
|
|
2008
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
|
|
1885
2009
|
/>
|
|
1886
2010
|
</div>
|
|
1887
2011
|
|
|
@@ -1898,7 +2022,7 @@ function AccountSettings() {
|
|
|
1898
2022
|
<button
|
|
1899
2023
|
onClick={handleChangePassword}
|
|
1900
2024
|
disabled={saving || !currentPassword || !newPassword || !confirmPassword}
|
|
1901
|
-
className="px-4 py-2 bg-[
|
|
2025
|
+
className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 disabled:cursor-not-allowed text-black rounded text-sm font-medium transition"
|
|
1902
2026
|
>
|
|
1903
2027
|
{saving ? "Updating..." : "Update Password"}
|
|
1904
2028
|
</button>
|
|
@@ -1959,12 +2083,12 @@ function DataSettings() {
|
|
|
1959
2083
|
<div className="max-w-4xl w-full">
|
|
1960
2084
|
<div className="mb-6">
|
|
1961
2085
|
<h1 className="text-2xl font-semibold mb-1">Data Management</h1>
|
|
1962
|
-
<p className="text-[
|
|
2086
|
+
<p className="text-[var(--color-text-muted)]">Manage stored data and telemetry.</p>
|
|
1963
2087
|
</div>
|
|
1964
2088
|
|
|
1965
|
-
<div className="bg-[
|
|
2089
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
1966
2090
|
<h3 className="font-medium mb-2">Telemetry Data</h3>
|
|
1967
|
-
<p className="text-sm text-[
|
|
2091
|
+
<p className="text-sm text-[var(--color-text-muted)] mb-4">
|
|
1968
2092
|
{eventCount !== null
|
|
1969
2093
|
? `${eventCount.toLocaleString()} events stored`
|
|
1970
2094
|
: "Loading..."}
|
|
@@ -2112,7 +2236,7 @@ function ChannelsSettings() {
|
|
|
2112
2236
|
|
|
2113
2237
|
const statusColors: Record<string, string> = {
|
|
2114
2238
|
running: "bg-green-500/20 text-green-400",
|
|
2115
|
-
stopped: "bg-[
|
|
2239
|
+
stopped: "bg-[var(--color-surface-raised)] text-[var(--color-text-muted)]",
|
|
2116
2240
|
error: "bg-red-500/20 text-red-400",
|
|
2117
2241
|
};
|
|
2118
2242
|
|
|
@@ -2127,11 +2251,11 @@ function ChannelsSettings() {
|
|
|
2127
2251
|
<div className="flex items-center justify-between mb-6">
|
|
2128
2252
|
<div>
|
|
2129
2253
|
<h2 className="text-xl font-semibold mb-1">Channels</h2>
|
|
2130
|
-
<p className="text-sm text-[
|
|
2254
|
+
<p className="text-sm text-[var(--color-text-muted)]">Connect agents to external messaging platforms</p>
|
|
2131
2255
|
</div>
|
|
2132
2256
|
<button
|
|
2133
2257
|
onClick={() => setShowForm(!showForm)}
|
|
2134
|
-
className="flex items-center gap-2 bg-[
|
|
2258
|
+
className="flex items-center gap-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-black px-3 py-1.5 rounded text-sm font-medium transition"
|
|
2135
2259
|
>
|
|
2136
2260
|
<PlusIcon /> Add Channel
|
|
2137
2261
|
</button>
|
|
@@ -2148,22 +2272,22 @@ function ChannelsSettings() {
|
|
|
2148
2272
|
|
|
2149
2273
|
{/* Create form */}
|
|
2150
2274
|
{showForm && (
|
|
2151
|
-
<div className="mb-6 bg-[
|
|
2152
|
-
<h3 className="text-sm font-medium text-[
|
|
2275
|
+
<div className="mb-6 bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 space-y-3">
|
|
2276
|
+
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-2">New Telegram Channel</h3>
|
|
2153
2277
|
|
|
2154
2278
|
<div>
|
|
2155
|
-
<label className="block text-xs text-[
|
|
2279
|
+
<label className="block text-xs text-[var(--color-text-muted)] mb-1">Channel Name</label>
|
|
2156
2280
|
<input
|
|
2157
2281
|
type="text"
|
|
2158
2282
|
value={formData.name}
|
|
2159
2283
|
onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
|
2160
2284
|
placeholder="e.g. My Telegram Bot"
|
|
2161
|
-
className="w-full bg-[
|
|
2285
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)]"
|
|
2162
2286
|
/>
|
|
2163
2287
|
</div>
|
|
2164
2288
|
|
|
2165
2289
|
<div>
|
|
2166
|
-
<label className="block text-xs text-[
|
|
2290
|
+
<label className="block text-xs text-[var(--color-text-muted)] mb-1">Agent</label>
|
|
2167
2291
|
<Select
|
|
2168
2292
|
value={formData.agent_id}
|
|
2169
2293
|
options={agents.map(a => ({ value: a.id, label: a.name }))}
|
|
@@ -2173,16 +2297,16 @@ function ChannelsSettings() {
|
|
|
2173
2297
|
</div>
|
|
2174
2298
|
|
|
2175
2299
|
<div>
|
|
2176
|
-
<label className="block text-xs text-[
|
|
2300
|
+
<label className="block text-xs text-[var(--color-text-muted)] mb-1">Bot Token</label>
|
|
2177
2301
|
<input
|
|
2178
2302
|
type="password"
|
|
2179
2303
|
value={formData.botToken}
|
|
2180
2304
|
onChange={e => setFormData(prev => ({ ...prev, botToken: e.target.value }))}
|
|
2181
2305
|
placeholder="From @BotFather on Telegram"
|
|
2182
|
-
className="w-full bg-[
|
|
2306
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)]"
|
|
2183
2307
|
/>
|
|
2184
|
-
<p className="text-xs text-[
|
|
2185
|
-
Create a bot via <a href="https://t.me/BotFather" target="_blank" className="text-[
|
|
2308
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">
|
|
2309
|
+
Create a bot via <a href="https://t.me/BotFather" target="_blank" className="text-[var(--color-accent)] hover:underline">@BotFather</a> on Telegram to get a token.
|
|
2186
2310
|
</p>
|
|
2187
2311
|
</div>
|
|
2188
2312
|
|
|
@@ -2190,13 +2314,13 @@ function ChannelsSettings() {
|
|
|
2190
2314
|
<button
|
|
2191
2315
|
onClick={createChannel}
|
|
2192
2316
|
disabled={creating || !formData.name || !formData.agent_id || !formData.botToken}
|
|
2193
|
-
className="bg-[
|
|
2317
|
+
className="bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black px-4 py-1.5 rounded text-sm font-medium transition"
|
|
2194
2318
|
>
|
|
2195
2319
|
{creating ? "Creating..." : "Create"}
|
|
2196
2320
|
</button>
|
|
2197
2321
|
<button
|
|
2198
2322
|
onClick={() => { setShowForm(false); setFormData({ name: "", agent_id: "", botToken: "" }); }}
|
|
2199
|
-
className="border border-[
|
|
2323
|
+
className="border border-[var(--color-border-light)] hover:border-[var(--color-scrollbar)] px-4 py-1.5 rounded text-sm transition"
|
|
2200
2324
|
>
|
|
2201
2325
|
Cancel
|
|
2202
2326
|
</button>
|
|
@@ -2206,16 +2330,16 @@ function ChannelsSettings() {
|
|
|
2206
2330
|
|
|
2207
2331
|
{/* Channel list */}
|
|
2208
2332
|
{loading ? (
|
|
2209
|
-
<p className="text-[
|
|
2333
|
+
<p className="text-[var(--color-text-muted)] text-sm">Loading channels...</p>
|
|
2210
2334
|
) : channels.length === 0 ? (
|
|
2211
|
-
<div className="text-center py-12 text-[
|
|
2335
|
+
<div className="text-center py-12 text-[var(--color-text-muted)]">
|
|
2212
2336
|
<p className="text-lg mb-2">No channels configured</p>
|
|
2213
2337
|
<p className="text-sm">Add a Telegram channel to let users message your agents directly.</p>
|
|
2214
2338
|
</div>
|
|
2215
2339
|
) : (
|
|
2216
2340
|
<div className="space-y-3">
|
|
2217
2341
|
{channels.map(channel => (
|
|
2218
|
-
<div key={channel.id} className="bg-[
|
|
2342
|
+
<div key={channel.id} className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
2219
2343
|
<div className="flex items-start justify-between">
|
|
2220
2344
|
<div className="flex-1 min-w-0">
|
|
2221
2345
|
<div className="flex items-center gap-2 mb-1">
|
|
@@ -2224,7 +2348,7 @@ function ChannelsSettings() {
|
|
|
2224
2348
|
{channel.status}
|
|
2225
2349
|
</span>
|
|
2226
2350
|
</div>
|
|
2227
|
-
<p className="text-sm text-[
|
|
2351
|
+
<p className="text-sm text-[var(--color-text-muted)]">
|
|
2228
2352
|
{channel.type === "telegram" ? "Telegram" : channel.type} → {getAgentName(channel.agent_id)}
|
|
2229
2353
|
</p>
|
|
2230
2354
|
{channel.status === "error" && channel.error && (
|
|
@@ -2236,7 +2360,7 @@ function ChannelsSettings() {
|
|
|
2236
2360
|
onClick={() => toggleChannel(channel)}
|
|
2237
2361
|
className={`px-3 py-1 rounded text-xs font-medium transition ${
|
|
2238
2362
|
channel.status === "running"
|
|
2239
|
-
? "bg-[
|
|
2363
|
+
? "bg-[var(--color-accent-20)] text-[var(--color-accent)] hover:bg-[var(--color-accent-30)]"
|
|
2240
2364
|
: "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
|
|
2241
2365
|
}`}
|
|
2242
2366
|
>
|
|
@@ -2244,7 +2368,7 @@ function ChannelsSettings() {
|
|
|
2244
2368
|
</button>
|
|
2245
2369
|
<button
|
|
2246
2370
|
onClick={() => deleteChannel(channel)}
|
|
2247
|
-
className="text-[
|
|
2371
|
+
className="text-[var(--color-text-muted)] hover:text-red-400 transition text-sm"
|
|
2248
2372
|
>
|
|
2249
2373
|
×
|
|
2250
2374
|
</button>
|
|
@@ -2270,9 +2394,11 @@ function AssistantSettings() {
|
|
|
2270
2394
|
const [saving, setSaving] = useState(false);
|
|
2271
2395
|
const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
|
|
2272
2396
|
const [starting, setStarting] = useState(false);
|
|
2397
|
+
const [webSearch, setWebSearch] = useState(false);
|
|
2398
|
+
const [webFetch, setWebFetch] = useState(false);
|
|
2273
2399
|
|
|
2274
2400
|
// Original values for change detection
|
|
2275
|
-
const [original, setOriginal] = useState({ provider: "", model: "", systemPrompt: "" });
|
|
2401
|
+
const [original, setOriginal] = useState({ provider: "", model: "", systemPrompt: "", webSearch: false, webFetch: false });
|
|
2276
2402
|
|
|
2277
2403
|
useEffect(() => {
|
|
2278
2404
|
const fetchData = async () => {
|
|
@@ -2291,7 +2417,11 @@ function AssistantSettings() {
|
|
|
2291
2417
|
setModel(a.model || "");
|
|
2292
2418
|
setSystemPrompt(a.systemPrompt || "");
|
|
2293
2419
|
setStatus(a.status || "stopped");
|
|
2294
|
-
|
|
2420
|
+
const ws = a.features?.builtinTools?.webSearch || false;
|
|
2421
|
+
const wf = a.features?.builtinTools?.webFetch || false;
|
|
2422
|
+
setWebSearch(ws);
|
|
2423
|
+
setWebFetch(wf);
|
|
2424
|
+
setOriginal({ provider: a.provider || "", model: a.model || "", systemPrompt: a.systemPrompt || "", webSearch: ws, webFetch: wf });
|
|
2295
2425
|
}
|
|
2296
2426
|
} catch {
|
|
2297
2427
|
setMessage({ type: "error", text: "Failed to load assistant config" });
|
|
@@ -2312,7 +2442,7 @@ function AssistantSettings() {
|
|
|
2312
2442
|
setModel(defaultModel);
|
|
2313
2443
|
};
|
|
2314
2444
|
|
|
2315
|
-
const hasChanges = provider !== original.provider || model !== original.model || systemPrompt !== original.systemPrompt;
|
|
2445
|
+
const hasChanges = provider !== original.provider || model !== original.model || systemPrompt !== original.systemPrompt || webSearch !== original.webSearch || webFetch !== original.webFetch;
|
|
2316
2446
|
|
|
2317
2447
|
const handleSave = async () => {
|
|
2318
2448
|
setSaving(true);
|
|
@@ -2321,10 +2451,10 @@ function AssistantSettings() {
|
|
|
2321
2451
|
const res = await authFetch("/api/agents/apteva-assistant", {
|
|
2322
2452
|
method: "PUT",
|
|
2323
2453
|
headers: { "Content-Type": "application/json" },
|
|
2324
|
-
body: JSON.stringify({ provider, model, systemPrompt }),
|
|
2454
|
+
body: JSON.stringify({ provider, model, systemPrompt, features: { builtinTools: { webSearch, webFetch } } }),
|
|
2325
2455
|
});
|
|
2326
2456
|
if (res.ok) {
|
|
2327
|
-
setOriginal({ provider, model, systemPrompt });
|
|
2457
|
+
setOriginal({ provider, model, systemPrompt, webSearch, webFetch });
|
|
2328
2458
|
setMessage({ type: "success", text: "Assistant settings saved" });
|
|
2329
2459
|
setTimeout(() => setMessage(null), 3000);
|
|
2330
2460
|
} else {
|
|
@@ -2358,13 +2488,13 @@ function AssistantSettings() {
|
|
|
2358
2488
|
};
|
|
2359
2489
|
|
|
2360
2490
|
if (loading) {
|
|
2361
|
-
return <div className="text-[
|
|
2491
|
+
return <div className="text-[var(--color-text-muted)]">Loading assistant settings...</div>;
|
|
2362
2492
|
}
|
|
2363
2493
|
|
|
2364
2494
|
return (
|
|
2365
2495
|
<div className="max-w-2xl">
|
|
2366
2496
|
<h2 className="text-lg font-medium mb-1">Apteva Assistant</h2>
|
|
2367
|
-
<p className="text-sm text-[
|
|
2497
|
+
<p className="text-sm text-[var(--color-text-muted)] mb-6">Configure the built-in AI assistant that manages your agents and platform.</p>
|
|
2368
2498
|
|
|
2369
2499
|
{message && (
|
|
2370
2500
|
<div className={`mb-4 px-3 py-2 rounded text-sm ${
|
|
@@ -2376,9 +2506,9 @@ function AssistantSettings() {
|
|
|
2376
2506
|
|
|
2377
2507
|
{/* Status */}
|
|
2378
2508
|
<div className="mb-6 flex items-center gap-3">
|
|
2379
|
-
<span className="text-sm text-[
|
|
2509
|
+
<span className="text-sm text-[var(--color-text-muted)]">Status:</span>
|
|
2380
2510
|
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
|
2381
|
-
status === "running" ? "bg-[#3b82f6]/20 text-[#3b82f6]" : "bg-[
|
|
2511
|
+
status === "running" ? "bg-[#3b82f6]/20 text-[#3b82f6]" : "bg-[var(--color-surface-raised)] text-[var(--color-text-muted)]"
|
|
2382
2512
|
}`}>
|
|
2383
2513
|
{status}
|
|
2384
2514
|
</span>
|
|
@@ -2387,7 +2517,7 @@ function AssistantSettings() {
|
|
|
2387
2517
|
disabled={starting}
|
|
2388
2518
|
className={`px-3 py-1.5 rounded text-sm font-medium transition ${
|
|
2389
2519
|
status === "running"
|
|
2390
|
-
? "bg-[
|
|
2520
|
+
? "bg-[var(--color-accent-20)] text-[var(--color-accent)] hover:bg-[var(--color-accent-30)]"
|
|
2391
2521
|
: "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
|
|
2392
2522
|
} disabled:opacity-50`}
|
|
2393
2523
|
>
|
|
@@ -2397,7 +2527,7 @@ function AssistantSettings() {
|
|
|
2397
2527
|
|
|
2398
2528
|
{/* Provider */}
|
|
2399
2529
|
<div className="mb-4">
|
|
2400
|
-
<label className="block text-sm text-[
|
|
2530
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Provider</label>
|
|
2401
2531
|
<Select
|
|
2402
2532
|
value={provider}
|
|
2403
2533
|
onChange={handleProviderChange}
|
|
@@ -2408,7 +2538,7 @@ function AssistantSettings() {
|
|
|
2408
2538
|
|
|
2409
2539
|
{/* Model */}
|
|
2410
2540
|
<div className="mb-4">
|
|
2411
|
-
<label className="block text-sm text-[
|
|
2541
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Model</label>
|
|
2412
2542
|
<Select
|
|
2413
2543
|
value={model}
|
|
2414
2544
|
onChange={setModel}
|
|
@@ -2417,14 +2547,52 @@ function AssistantSettings() {
|
|
|
2417
2547
|
/>
|
|
2418
2548
|
</div>
|
|
2419
2549
|
|
|
2550
|
+
{/* Built-in Tools - Anthropic only */}
|
|
2551
|
+
{provider === "anthropic" && (
|
|
2552
|
+
<div className="mb-4">
|
|
2553
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Built-in Tools</label>
|
|
2554
|
+
<div className="flex flex-wrap gap-2">
|
|
2555
|
+
<button
|
|
2556
|
+
type="button"
|
|
2557
|
+
onClick={() => setWebSearch(!webSearch)}
|
|
2558
|
+
className={`flex items-center gap-2 px-3 py-2 rounded border transition ${
|
|
2559
|
+
webSearch
|
|
2560
|
+
? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]"
|
|
2561
|
+
: "border-[var(--color-border-light)] hover:border-[var(--color-border-light)] text-[var(--color-text-secondary)]"
|
|
2562
|
+
}`}
|
|
2563
|
+
>
|
|
2564
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2565
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
2566
|
+
</svg>
|
|
2567
|
+
<span className="text-sm">Web Search</span>
|
|
2568
|
+
</button>
|
|
2569
|
+
<button
|
|
2570
|
+
type="button"
|
|
2571
|
+
onClick={() => setWebFetch(!webFetch)}
|
|
2572
|
+
className={`flex items-center gap-2 px-3 py-2 rounded border transition ${
|
|
2573
|
+
webFetch
|
|
2574
|
+
? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]"
|
|
2575
|
+
: "border-[var(--color-border-light)] hover:border-[var(--color-border-light)] text-[var(--color-text-secondary)]"
|
|
2576
|
+
}`}
|
|
2577
|
+
>
|
|
2578
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2579
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
|
2580
|
+
</svg>
|
|
2581
|
+
<span className="text-sm">Web Fetch</span>
|
|
2582
|
+
</button>
|
|
2583
|
+
</div>
|
|
2584
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-2">Provider-native tools for real-time web access</p>
|
|
2585
|
+
</div>
|
|
2586
|
+
)}
|
|
2587
|
+
|
|
2420
2588
|
{/* System Prompt */}
|
|
2421
2589
|
<div className="mb-6">
|
|
2422
|
-
<label className="block text-sm text-[
|
|
2590
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">System Prompt</label>
|
|
2423
2591
|
<textarea
|
|
2424
2592
|
value={systemPrompt}
|
|
2425
2593
|
onChange={e => setSystemPrompt(e.target.value)}
|
|
2426
2594
|
rows={12}
|
|
2427
|
-
className="w-full bg-[
|
|
2595
|
+
className="w-full bg-[var(--color-surface)] border border-[var(--color-border)] rounded px-3 py-2 text-sm font-mono focus:outline-none focus:border-[var(--color-accent)] resize-y"
|
|
2428
2596
|
/>
|
|
2429
2597
|
</div>
|
|
2430
2598
|
|
|
@@ -2432,13 +2600,13 @@ function AssistantSettings() {
|
|
|
2432
2600
|
<button
|
|
2433
2601
|
onClick={handleSave}
|
|
2434
2602
|
disabled={!hasChanges || saving}
|
|
2435
|
-
className="bg-[
|
|
2603
|
+
className="bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 disabled:cursor-not-allowed text-black px-4 py-2 rounded font-medium transition"
|
|
2436
2604
|
>
|
|
2437
2605
|
{saving ? "Saving..." : "Save Changes"}
|
|
2438
2606
|
</button>
|
|
2439
2607
|
|
|
2440
2608
|
{status === "running" && hasChanges && (
|
|
2441
|
-
<p className="text-xs text-[
|
|
2609
|
+
<p className="text-xs text-[var(--color-text-muted)] mt-2">Changes will be applied to the running assistant</p>
|
|
2442
2610
|
)}
|
|
2443
2611
|
</div>
|
|
2444
2612
|
);
|