apteva 0.4.44 → 0.4.51
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 → ActivityPage.sw9p594m.js} +1 -1
- package/dist/{ApiDocsPage.yzcxx5ax.js → ApiDocsPage.90e03bz7.js} +1 -1
- package/dist/App.3vnrera5.js +4 -0
- package/dist/App.94x6mh7f.js +20 -0
- package/dist/{App.qzbx5wtj.js → App.9sryp183.js} +1 -1
- package/dist/App.9t1zc5r7.js +53 -0
- package/dist/{App.r5serxkt.js → App.jhb45d7r.js} +1 -1
- package/dist/App.k4nmqgek.js +221 -0
- package/dist/App.p7jjw1zf.js +4 -0
- package/dist/App.pfbdzrhh.js +4 -0
- package/dist/App.stgng5bx.js +13 -0
- package/dist/{App.152mbs1r.js → App.tm3k7h4b.js} +1 -1
- package/dist/App.vkg121c6.js +4 -0
- package/dist/App.vza4fxg0.js +4 -0
- package/dist/App.wghtdzsk.js +1 -0
- package/dist/App.xva0tfzh.js +4 -0
- package/dist/App.ysxy7akk.js +61 -0
- package/dist/App.yzkh4gq2.js +4 -0
- package/dist/ConnectionsPage.q5f9fd37.js +3 -0
- package/dist/McpPage.f3ccrezb.js +3 -0
- package/dist/SettingsPage.q1pqcc93.js +3 -0
- package/dist/SkillsPage.whxnez67.js +3 -0
- package/dist/TasksPage.zp4jfevw.js +3 -0
- package/dist/TelemetryPage.an0ky78c.js +3 -0
- package/dist/TestsPage.18krj0d1.js +3 -0
- package/dist/ThreadsPage.nnphgy98.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/db.ts +60 -22
- package/src/providers.ts +14 -9
- package/src/routes/api/agent-utils.ts +25 -3
- package/src/routes/api/telemetry.ts +21 -2
- package/src/server.ts +53 -1
- package/src/web/App.tsx +2 -2
- package/src/web/components/agents/AgentCard.tsx +9 -7
- package/src/web/components/agents/AgentPanel.tsx +205 -44
- package/src/web/components/agents/CreateAgentModal.tsx +5 -5
- package/src/web/components/auth/LoginPage.tsx +2 -2
- package/src/web/components/common/LoadingSpinner.tsx +1 -1
- package/src/web/components/common/Modal.tsx +6 -6
- package/src/web/components/common/Select.tsx +2 -2
- package/src/web/components/connections/ConnectionsPage.tsx +1 -1
- package/src/web/components/connections/IntegrationsTab.tsx +3 -3
- package/src/web/components/connections/OverviewTab.tsx +3 -3
- package/src/web/components/connections/TriggersTab.tsx +8 -8
- package/src/web/components/dashboard/Dashboard.tsx +4 -4
- package/src/web/components/layout/Header.tsx +3 -3
- package/src/web/components/layout/Sidebar.tsx +6 -5
- package/src/web/components/mcp/McpPage.tsx +13 -13
- package/src/web/components/onboarding/OnboardingWizard.tsx +2 -2
- package/src/web/components/settings/SettingsPage.tsx +59 -26
- package/src/web/components/skills/SkillsPage.tsx +7 -7
- package/src/web/components/tasks/TasksPage.tsx +212 -36
- package/src/web/components/telemetry/TelemetryPage.tsx +414 -94
- package/src/web/components/tests/TestsPage.tsx +2 -2
- package/src/web/components/threads/ThreadsPage.tsx +2 -2
- package/src/web/context/TelemetryContext.tsx +1 -0
- package/src/web/context/ThemeContext.tsx +31 -10
- package/src/web/index.html +1 -6
- package/src/web/styles.css +47 -0
- package/src/web/themes.ts +68 -5
- package/src/web/types.ts +1 -1
- package/dist/App.09yb8t0b.js +0 -1
- package/dist/App.3a67nx9w.js +0 -4
- package/dist/App.9epx6785.js +0 -4
- package/dist/App.d8955awp.js +0 -4
- package/dist/App.drwb57jq.js +0 -4
- package/dist/App.gssbmajb.js +0 -4
- package/dist/App.qw70pc29.js +0 -53
- package/dist/App.tpmp9020.js +0 -20
- package/dist/App.v2wb4d7d.js +0 -61
- package/dist/App.vxmaaj0m.js +0 -13
- package/dist/App.w4p2tda9.js +0 -4
- package/dist/App.wv2ng55q.js +0 -221
- package/dist/App.yncnrn0f.js +0 -4
- package/dist/ConnectionsPage.k6cspyqq.js +0 -3
- package/dist/McpPage.cdxm48xj.js +0 -3
- package/dist/SettingsPage.evpv7c2y.js +0 -3
- package/dist/SkillsPage.pvzp6c1a.js +0 -3
- package/dist/TasksPage.6jnvbpsy.js +0 -3
- package/dist/TelemetryPage.t7vk24zc.js +0 -3
- package/dist/TestsPage.5x6658aa.js +0 -3
- package/dist/ThreadsPage.3fvhtevh.js +0 -3
|
@@ -3,7 +3,7 @@ import { CheckIcon, CloseIcon, PlusIcon } from "../common/Icons";
|
|
|
3
3
|
import { Modal, useConfirm } from "../common/Modal";
|
|
4
4
|
import { Select } from "../common/Select";
|
|
5
5
|
import { useProjects, useAuth, useTheme, type Project } from "../../context";
|
|
6
|
-
import type { ThemeMode } from "../../themes";
|
|
6
|
+
import type { ThemeMode, ThemeStyle } from "../../themes";
|
|
7
7
|
import type { Provider } from "../../types";
|
|
8
8
|
|
|
9
9
|
type SettingsTab = "general" | "providers" | "projects" | "channels" | "api-keys" | "account" | "updates" | "data" | "assistant";
|
|
@@ -101,7 +101,7 @@ function SettingsNavItem({
|
|
|
101
101
|
|
|
102
102
|
function GeneralSettings() {
|
|
103
103
|
const { authFetch } = useAuth();
|
|
104
|
-
const { mode, setMode } = useTheme();
|
|
104
|
+
const { mode, style, setMode, setStyle } = useTheme();
|
|
105
105
|
const [instanceUrl, setInstanceUrl] = useState("");
|
|
106
106
|
const [loading, setLoading] = useState(true);
|
|
107
107
|
const [saving, setSaving] = useState(false);
|
|
@@ -149,6 +149,11 @@ function GeneralSettings() {
|
|
|
149
149
|
{ value: "light", label: "Light", description: "Light background" },
|
|
150
150
|
];
|
|
151
151
|
|
|
152
|
+
const styleOptions: { value: ThemeStyle; label: string; description: string }[] = [
|
|
153
|
+
{ value: "classic", label: "Classic", description: "Terminal-inspired, sharp" },
|
|
154
|
+
{ value: "professional", label: "Professional", description: "Polished, enterprise" },
|
|
155
|
+
];
|
|
156
|
+
|
|
152
157
|
return (
|
|
153
158
|
<div className="max-w-4xl w-full">
|
|
154
159
|
<div className="mb-6">
|
|
@@ -156,22 +161,23 @@ function GeneralSettings() {
|
|
|
156
161
|
<p className="text-[var(--color-text-muted)]">Instance configuration and appearance.</p>
|
|
157
162
|
</div>
|
|
158
163
|
|
|
159
|
-
{/*
|
|
160
|
-
<div className="bg-[var(--color-surface)]
|
|
161
|
-
<h3 className="font-medium mb-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
</p>
|
|
165
|
-
<div className="flex gap-3">
|
|
164
|
+
{/* Appearance */}
|
|
165
|
+
<div className="bg-[var(--color-surface)] card p-4 mb-4">
|
|
166
|
+
<h3 className="font-medium mb-4">Appearance</h3>
|
|
167
|
+
|
|
168
|
+
{/* Color scheme */}
|
|
169
|
+
<p className="text-sm text-[var(--color-text-secondary)] mb-2">Color scheme</p>
|
|
170
|
+
<div className="flex gap-3 mb-5">
|
|
166
171
|
{themeOptions.map(opt => (
|
|
167
172
|
<button
|
|
168
173
|
key={opt.value}
|
|
169
174
|
onClick={() => setMode(opt.value)}
|
|
170
|
-
className={`flex-1 max-w-[160px] px-4 py-3
|
|
175
|
+
className={`flex-1 max-w-[160px] px-4 py-3 border text-left transition ${
|
|
171
176
|
mode === opt.value
|
|
172
177
|
? "border-[var(--color-accent)] bg-[var(--color-accent-10)]"
|
|
173
178
|
: "border-[var(--color-border-light)] bg-[var(--color-bg)] hover:border-[var(--color-scrollbar)]"
|
|
174
179
|
}`}
|
|
180
|
+
style={{ borderRadius: "var(--radius-card)" }}
|
|
175
181
|
>
|
|
176
182
|
<div className="flex items-center gap-2 mb-1">
|
|
177
183
|
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
|
|
@@ -185,9 +191,36 @@ function GeneralSettings() {
|
|
|
185
191
|
</button>
|
|
186
192
|
))}
|
|
187
193
|
</div>
|
|
194
|
+
|
|
195
|
+
{/* Style */}
|
|
196
|
+
<p className="text-sm text-[var(--color-text-secondary)] mb-2">Style</p>
|
|
197
|
+
<div className="flex gap-3">
|
|
198
|
+
{styleOptions.map(opt => (
|
|
199
|
+
<button
|
|
200
|
+
key={opt.value}
|
|
201
|
+
onClick={() => setStyle(opt.value)}
|
|
202
|
+
className={`flex-1 max-w-[200px] px-4 py-3 border text-left transition ${
|
|
203
|
+
style === opt.value
|
|
204
|
+
? "border-[var(--color-accent)] bg-[var(--color-accent-10)]"
|
|
205
|
+
: "border-[var(--color-border-light)] bg-[var(--color-bg)] hover:border-[var(--color-scrollbar)]"
|
|
206
|
+
}`}
|
|
207
|
+
style={{ borderRadius: "var(--radius-card)" }}
|
|
208
|
+
>
|
|
209
|
+
<div className="flex items-center gap-2 mb-1">
|
|
210
|
+
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
|
|
211
|
+
style === opt.value ? "border-[var(--color-accent)]" : "border-[var(--color-scrollbar)]"
|
|
212
|
+
}`}>
|
|
213
|
+
{style === opt.value && <div className="w-2 h-2 rounded-full bg-[var(--color-accent)]" />}
|
|
214
|
+
</div>
|
|
215
|
+
<span className="text-sm font-medium">{opt.label}</span>
|
|
216
|
+
</div>
|
|
217
|
+
<p className="text-xs text-[var(--color-text-muted)] ml-6">{opt.description}</p>
|
|
218
|
+
</button>
|
|
219
|
+
))}
|
|
220
|
+
</div>
|
|
188
221
|
</div>
|
|
189
222
|
|
|
190
|
-
<div className="bg-[var(--color-surface)]
|
|
223
|
+
<div className="bg-[var(--color-surface)] card p-4">
|
|
191
224
|
<h3 className="font-medium mb-2">Instance URL</h3>
|
|
192
225
|
<p className="text-sm text-[var(--color-text-muted)] mb-4">
|
|
193
226
|
The public HTTPS URL for this instance. Used for webhook callbacks from external services like Composio.
|
|
@@ -548,7 +581,7 @@ function ProjectsSettings() {
|
|
|
548
581
|
{projects.map(project => (
|
|
549
582
|
<div
|
|
550
583
|
key={project.id}
|
|
551
|
-
className="bg-[var(--color-surface)]
|
|
584
|
+
className="bg-[var(--color-surface)] card p-4 flex items-center gap-4"
|
|
552
585
|
>
|
|
553
586
|
<div
|
|
554
587
|
className="w-4 h-4 rounded-full flex-shrink-0"
|
|
@@ -818,7 +851,7 @@ function UpdatesSettings() {
|
|
|
818
851
|
</div>
|
|
819
852
|
|
|
820
853
|
{/* Current Version */}
|
|
821
|
-
<div className="bg-[var(--color-surface)]
|
|
854
|
+
<div className="bg-[var(--color-surface)] card p-5">
|
|
822
855
|
<div className="flex items-center justify-between mb-4">
|
|
823
856
|
<div>
|
|
824
857
|
<h3 className="font-medium text-lg">Current Version</h3>
|
|
@@ -881,7 +914,7 @@ function UpdatesSettings() {
|
|
|
881
914
|
)}
|
|
882
915
|
|
|
883
916
|
{/* Apteva App Version */}
|
|
884
|
-
<div className="bg-[var(--color-surface)]
|
|
917
|
+
<div className="bg-[var(--color-surface)] card p-5">
|
|
885
918
|
<div className="flex items-center justify-between mb-4">
|
|
886
919
|
<div>
|
|
887
920
|
<h3 className="font-medium text-lg">apteva</h3>
|
|
@@ -921,7 +954,7 @@ function UpdatesSettings() {
|
|
|
921
954
|
</div>
|
|
922
955
|
|
|
923
956
|
{/* Agent Binary Version */}
|
|
924
|
-
<div className="bg-[var(--color-surface)]
|
|
957
|
+
<div className="bg-[var(--color-surface)] card p-5">
|
|
925
958
|
<div className="flex items-center justify-between mb-4">
|
|
926
959
|
<div>
|
|
927
960
|
<h3 className="font-medium text-lg">Agent Binary</h3>
|
|
@@ -1783,7 +1816,7 @@ function ApiKeysSettings() {
|
|
|
1783
1816
|
|
|
1784
1817
|
{/* Create Form */}
|
|
1785
1818
|
{showCreate && !newKey && (
|
|
1786
|
-
<div className="bg-[var(--color-surface)]
|
|
1819
|
+
<div className="bg-[var(--color-surface)] card p-4 mb-6">
|
|
1787
1820
|
<h3 className="font-medium mb-4">Create new API key</h3>
|
|
1788
1821
|
<div className="space-y-4 max-w-md">
|
|
1789
1822
|
<div>
|
|
@@ -1880,7 +1913,7 @@ function ApiKeysSettings() {
|
|
|
1880
1913
|
|
|
1881
1914
|
{/* Usage Info */}
|
|
1882
1915
|
{keys.length > 0 && (
|
|
1883
|
-
<div className="mt-6 bg-[var(--color-surface)]
|
|
1916
|
+
<div className="mt-6 bg-[var(--color-surface)] card p-4">
|
|
1884
1917
|
<h3 className="font-medium mb-2 text-sm">Usage</h3>
|
|
1885
1918
|
<code className="block bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-xs text-[var(--color-text-secondary)]">
|
|
1886
1919
|
curl -H "X-API-Key: apt_..." http://localhost:4280/api/agents
|
|
@@ -1953,7 +1986,7 @@ function AccountSettings() {
|
|
|
1953
1986
|
|
|
1954
1987
|
{/* User Info */}
|
|
1955
1988
|
{user && (
|
|
1956
|
-
<div className="bg-[var(--color-surface)]
|
|
1989
|
+
<div className="bg-[var(--color-surface)] card p-4 mb-6">
|
|
1957
1990
|
<h3 className="font-medium mb-3">Profile</h3>
|
|
1958
1991
|
<div className="space-y-2 text-sm">
|
|
1959
1992
|
<div className="flex justify-between">
|
|
@@ -1975,7 +2008,7 @@ function AccountSettings() {
|
|
|
1975
2008
|
)}
|
|
1976
2009
|
|
|
1977
2010
|
{/* Change Password */}
|
|
1978
|
-
<div className="bg-[var(--color-surface)]
|
|
2011
|
+
<div className="bg-[var(--color-surface)] card p-4">
|
|
1979
2012
|
<h3 className="font-medium mb-4">Change Password</h3>
|
|
1980
2013
|
|
|
1981
2014
|
<div className="space-y-4 max-w-md">
|
|
@@ -2054,7 +2087,7 @@ function DataSettings() {
|
|
|
2054
2087
|
}, []);
|
|
2055
2088
|
|
|
2056
2089
|
const clearTelemetry = async () => {
|
|
2057
|
-
const confirmed = await confirm("Are you sure you want to delete all
|
|
2090
|
+
const confirmed = await confirm("Are you sure you want to delete all analytics data? This cannot be undone.", { confirmText: "Clear All", title: "Clear Analytics Data" });
|
|
2058
2091
|
if (!confirmed) return;
|
|
2059
2092
|
|
|
2060
2093
|
setClearing(true);
|
|
@@ -2083,11 +2116,11 @@ function DataSettings() {
|
|
|
2083
2116
|
<div className="max-w-4xl w-full">
|
|
2084
2117
|
<div className="mb-6">
|
|
2085
2118
|
<h1 className="text-2xl font-semibold mb-1">Data Management</h1>
|
|
2086
|
-
<p className="text-[var(--color-text-muted)]">Manage stored data and
|
|
2119
|
+
<p className="text-[var(--color-text-muted)]">Manage stored data and analytics.</p>
|
|
2087
2120
|
</div>
|
|
2088
2121
|
|
|
2089
|
-
<div className="bg-[var(--color-surface)]
|
|
2090
|
-
<h3 className="font-medium mb-2">
|
|
2122
|
+
<div className="bg-[var(--color-surface)] card p-4">
|
|
2123
|
+
<h3 className="font-medium mb-2">Analytics Data</h3>
|
|
2091
2124
|
<p className="text-sm text-[var(--color-text-muted)] mb-4">
|
|
2092
2125
|
{eventCount !== null
|
|
2093
2126
|
? `${eventCount.toLocaleString()} events stored`
|
|
@@ -2109,7 +2142,7 @@ function DataSettings() {
|
|
|
2109
2142
|
disabled={clearing || eventCount === 0}
|
|
2110
2143
|
className="px-4 py-2 bg-red-500/20 text-red-400 hover:bg-red-500/30 disabled:opacity-50 disabled:cursor-not-allowed rounded text-sm font-medium transition"
|
|
2111
2144
|
>
|
|
2112
|
-
{clearing ? "Clearing..." : "Clear All
|
|
2145
|
+
{clearing ? "Clearing..." : "Clear All Analytics"}
|
|
2113
2146
|
</button>
|
|
2114
2147
|
</div>
|
|
2115
2148
|
</div>
|
|
@@ -2272,7 +2305,7 @@ function ChannelsSettings() {
|
|
|
2272
2305
|
|
|
2273
2306
|
{/* Create form */}
|
|
2274
2307
|
{showForm && (
|
|
2275
|
-
<div className="mb-6 bg-[var(--color-surface)]
|
|
2308
|
+
<div className="mb-6 bg-[var(--color-surface)] card p-4 space-y-3">
|
|
2276
2309
|
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-2">New Telegram Channel</h3>
|
|
2277
2310
|
|
|
2278
2311
|
<div>
|
|
@@ -2339,7 +2372,7 @@ function ChannelsSettings() {
|
|
|
2339
2372
|
) : (
|
|
2340
2373
|
<div className="space-y-3">
|
|
2341
2374
|
{channels.map(channel => (
|
|
2342
|
-
<div key={channel.id} className="bg-[var(--color-surface)]
|
|
2375
|
+
<div key={channel.id} className="bg-[var(--color-surface)] card p-4">
|
|
2343
2376
|
<div className="flex items-start justify-between">
|
|
2344
2377
|
<div className="flex-1 min-w-0">
|
|
2345
2378
|
<div className="flex items-center gap-2 mb-1">
|
|
@@ -316,7 +316,7 @@ export function SkillsPage() {
|
|
|
316
316
|
</div>
|
|
317
317
|
|
|
318
318
|
{/* Tabs */}
|
|
319
|
-
<div className="flex gap-1 mb-6 bg-[var(--color-surface)]
|
|
319
|
+
<div className="flex gap-1 mb-6 bg-[var(--color-surface)] card p-1 w-fit">
|
|
320
320
|
<button
|
|
321
321
|
onClick={() => setActiveTab("installed")}
|
|
322
322
|
className={`px-4 py-2 rounded text-sm font-medium transition ${
|
|
@@ -366,7 +366,7 @@ export function SkillsPage() {
|
|
|
366
366
|
</button>
|
|
367
367
|
</div>
|
|
368
368
|
) : filteredSkills.length === 0 ? (
|
|
369
|
-
<div className="bg-[var(--color-surface)]
|
|
369
|
+
<div className="bg-[var(--color-surface)] card p-6 text-center">
|
|
370
370
|
<p className="text-[var(--color-text-muted)]">No skills match this filter.</p>
|
|
371
371
|
</div>
|
|
372
372
|
) : (
|
|
@@ -478,7 +478,7 @@ export function SkillsPage() {
|
|
|
478
478
|
|
|
479
479
|
{/* Empty State */}
|
|
480
480
|
{!githubLoading && !githubRepoInfo && !githubError && (
|
|
481
|
-
<div className="bg-[var(--color-surface)]
|
|
481
|
+
<div className="bg-[var(--color-surface)] card p-8 text-center">
|
|
482
482
|
<div className="text-4xl mb-4">📦</div>
|
|
483
483
|
<h3 className="text-lg font-medium mb-2">Browse Skills from GitHub</h3>
|
|
484
484
|
<p className="text-[var(--color-text-muted)] mb-6 max-w-md mx-auto">
|
|
@@ -563,7 +563,7 @@ export function SkillsPage() {
|
|
|
563
563
|
)}
|
|
564
564
|
|
|
565
565
|
{/* Info */}
|
|
566
|
-
<div className="p-4 bg-[var(--color-surface)]
|
|
566
|
+
<div className="p-4 bg-[var(--color-surface)] card text-sm text-[var(--color-text-muted)]">
|
|
567
567
|
<p>
|
|
568
568
|
Skills are sourced from GitHub repositories. Each skill should be in its own directory with a{" "}
|
|
569
569
|
<code className="text-[var(--color-text-secondary)] bg-[var(--color-bg)] px-1 rounded">SKILL.md</code> file containing instructions.
|
|
@@ -895,7 +895,7 @@ function CreateSkillModal({
|
|
|
895
895
|
return (
|
|
896
896
|
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 p-4" onClick={onClose}>
|
|
897
897
|
<div
|
|
898
|
-
className="bg-[var(--color-surface)]
|
|
898
|
+
className="bg-[var(--color-surface)] card w-full max-w-2xl max-h-[90vh] overflow-auto"
|
|
899
899
|
onClick={(e) => e.stopPropagation()}
|
|
900
900
|
>
|
|
901
901
|
<div className="p-6 border-b border-[var(--color-border)]">
|
|
@@ -1029,7 +1029,7 @@ function ImportSkillModal({
|
|
|
1029
1029
|
return (
|
|
1030
1030
|
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 p-4" onClick={onClose}>
|
|
1031
1031
|
<div
|
|
1032
|
-
className="bg-[var(--color-surface)]
|
|
1032
|
+
className="bg-[var(--color-surface)] card w-full max-w-2xl max-h-[90vh] overflow-auto"
|
|
1033
1033
|
onClick={(e) => e.stopPropagation()}
|
|
1034
1034
|
>
|
|
1035
1035
|
<div className="p-6 border-b border-[var(--color-border)]">
|
|
@@ -1129,7 +1129,7 @@ function ViewSkillModal({
|
|
|
1129
1129
|
return (
|
|
1130
1130
|
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 p-4" onClick={onClose}>
|
|
1131
1131
|
<div
|
|
1132
|
-
className="bg-[var(--color-surface)]
|
|
1132
|
+
className="bg-[var(--color-surface)] card w-full max-w-3xl max-h-[90vh] overflow-auto"
|
|
1133
1133
|
onClick={(e) => e.stopPropagation()}
|
|
1134
1134
|
>
|
|
1135
1135
|
<div className="p-6 border-b border-[var(--color-border)] flex items-center justify-between">
|
|
@@ -304,8 +304,63 @@ export interface TaskDetailPanelProps {
|
|
|
304
304
|
export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading, authFetch, onRefresh }: TaskDetailPanelProps) {
|
|
305
305
|
const [executing, setExecuting] = useState(false);
|
|
306
306
|
const [deleting, setDeleting] = useState(false);
|
|
307
|
+
const [editing, setEditing] = useState(false);
|
|
308
|
+
const [saving, setSaving] = useState(false);
|
|
309
|
+
const [editForm, setEditForm] = useState({
|
|
310
|
+
title: task.title,
|
|
311
|
+
description: task.description || "",
|
|
312
|
+
type: task.type as "once" | "recurring",
|
|
313
|
+
priority: task.priority,
|
|
314
|
+
execute_at: task.execute_at ? new Date(task.execute_at).toISOString().slice(0, 16) : "",
|
|
315
|
+
recurrence: task.recurrence || "",
|
|
316
|
+
});
|
|
307
317
|
const { confirm, ConfirmDialog } = useConfirm();
|
|
308
318
|
|
|
319
|
+
// Reset edit form when task changes
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
setEditForm({
|
|
322
|
+
title: task.title,
|
|
323
|
+
description: task.description || "",
|
|
324
|
+
type: task.type as "once" | "recurring",
|
|
325
|
+
priority: task.priority,
|
|
326
|
+
execute_at: task.execute_at ? new Date(task.execute_at).toISOString().slice(0, 16) : "",
|
|
327
|
+
recurrence: task.recurrence || "",
|
|
328
|
+
});
|
|
329
|
+
setEditing(false);
|
|
330
|
+
}, [task.id, task.agentId]);
|
|
331
|
+
|
|
332
|
+
const handleSave = async () => {
|
|
333
|
+
if (!authFetch || saving) return;
|
|
334
|
+
setSaving(true);
|
|
335
|
+
try {
|
|
336
|
+
const body: Record<string, unknown> = {
|
|
337
|
+
title: editForm.title.trim(),
|
|
338
|
+
description: editForm.description.trim() || undefined,
|
|
339
|
+
type: editForm.type,
|
|
340
|
+
priority: editForm.priority,
|
|
341
|
+
};
|
|
342
|
+
if (editForm.type === "once" && editForm.execute_at) {
|
|
343
|
+
body.execute_at = new Date(editForm.execute_at).toISOString();
|
|
344
|
+
}
|
|
345
|
+
if (editForm.type === "recurring" && editForm.recurrence.trim()) {
|
|
346
|
+
body.recurrence = editForm.recurrence.trim();
|
|
347
|
+
}
|
|
348
|
+
const res = await authFetch(`/api/tasks/${task.agentId}/${task.id}`, {
|
|
349
|
+
method: "PUT",
|
|
350
|
+
headers: { "Content-Type": "application/json" },
|
|
351
|
+
body: JSON.stringify(body),
|
|
352
|
+
});
|
|
353
|
+
if (res.ok) {
|
|
354
|
+
setEditing(false);
|
|
355
|
+
onRefresh?.();
|
|
356
|
+
}
|
|
357
|
+
} catch (e) {
|
|
358
|
+
console.error("Failed to update task:", e);
|
|
359
|
+
} finally {
|
|
360
|
+
setSaving(false);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
309
364
|
const handleExecute = async () => {
|
|
310
365
|
if (!authFetch || executing) return;
|
|
311
366
|
setExecuting(true);
|
|
@@ -338,14 +393,25 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
338
393
|
}
|
|
339
394
|
};
|
|
340
395
|
|
|
396
|
+
const inputClass = "w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-2 py-1.5 text-sm focus:outline-none focus:border-[var(--color-accent)] text-[var(--color-text)]";
|
|
397
|
+
|
|
341
398
|
return (
|
|
342
399
|
<div className="w-full md:w-1/2 lg:w-1/3 border-l border-[var(--color-border)] bg-[var(--color-bg)] flex flex-col overflow-hidden">
|
|
343
400
|
{ConfirmDialog}
|
|
344
401
|
{/* Header */}
|
|
345
402
|
<div className="flex items-center justify-between p-4 border-b border-[var(--color-border)]">
|
|
346
|
-
<h2 className="font-medium truncate pr-2">Task Details</h2>
|
|
403
|
+
<h2 className="font-medium truncate pr-2">{editing ? "Edit Task" : "Task Details"}</h2>
|
|
347
404
|
<div className="flex items-center gap-2">
|
|
348
|
-
{authFetch && (task.status === "pending" || task.status === "completed") && (
|
|
405
|
+
{authFetch && !editing && (task.status === "pending" || task.status === "completed" || task.status === "failed") && (
|
|
406
|
+
<button
|
|
407
|
+
onClick={() => setEditing(true)}
|
|
408
|
+
title="Edit task"
|
|
409
|
+
className="text-[var(--color-text-muted)] hover:text-[var(--color-accent)] transition"
|
|
410
|
+
>
|
|
411
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
|
412
|
+
</button>
|
|
413
|
+
)}
|
|
414
|
+
{authFetch && !editing && (task.status === "pending" || task.status === "completed") && (
|
|
349
415
|
<button
|
|
350
416
|
onClick={handleExecute}
|
|
351
417
|
disabled={executing}
|
|
@@ -355,7 +421,7 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
355
421
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
356
422
|
</button>
|
|
357
423
|
)}
|
|
358
|
-
{authFetch && (
|
|
424
|
+
{authFetch && !editing && (
|
|
359
425
|
<button
|
|
360
426
|
onClick={handleDelete}
|
|
361
427
|
disabled={deleting}
|
|
@@ -365,9 +431,38 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
365
431
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
|
366
432
|
</button>
|
|
367
433
|
)}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
434
|
+
{editing && (
|
|
435
|
+
<>
|
|
436
|
+
<button
|
|
437
|
+
onClick={() => {
|
|
438
|
+
setEditing(false);
|
|
439
|
+
setEditForm({
|
|
440
|
+
title: task.title,
|
|
441
|
+
description: task.description || "",
|
|
442
|
+
type: task.type as "once" | "recurring",
|
|
443
|
+
priority: task.priority,
|
|
444
|
+
execute_at: task.execute_at ? new Date(task.execute_at).toISOString().slice(0, 16) : "",
|
|
445
|
+
recurrence: task.recurrence || "",
|
|
446
|
+
});
|
|
447
|
+
}}
|
|
448
|
+
className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] text-sm transition"
|
|
449
|
+
>
|
|
450
|
+
Cancel
|
|
451
|
+
</button>
|
|
452
|
+
<button
|
|
453
|
+
onClick={handleSave}
|
|
454
|
+
disabled={saving || !editForm.title.trim()}
|
|
455
|
+
className="px-3 py-1 rounded text-sm bg-[var(--color-accent)] text-black hover:opacity-90 transition disabled:opacity-50"
|
|
456
|
+
>
|
|
457
|
+
{saving ? "Saving..." : "Save"}
|
|
458
|
+
</button>
|
|
459
|
+
</>
|
|
460
|
+
)}
|
|
461
|
+
{!editing && (
|
|
462
|
+
<button onClick={onClose} className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition">
|
|
463
|
+
<CloseIcon />
|
|
464
|
+
</button>
|
|
465
|
+
)}
|
|
371
466
|
</div>
|
|
372
467
|
</div>
|
|
373
468
|
|
|
@@ -376,51 +471,131 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
376
471
|
{/* Title & Status */}
|
|
377
472
|
<div>
|
|
378
473
|
<div className="flex items-start justify-between gap-2 mb-2">
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
474
|
+
{editing ? (
|
|
475
|
+
<input
|
|
476
|
+
type="text"
|
|
477
|
+
value={editForm.title}
|
|
478
|
+
onChange={e => setEditForm({ ...editForm, title: e.target.value })}
|
|
479
|
+
className={`${inputClass} text-lg font-medium`}
|
|
480
|
+
placeholder="Task title"
|
|
481
|
+
/>
|
|
482
|
+
) : (
|
|
483
|
+
<h3 className="text-lg font-medium">{task.title}</h3>
|
|
484
|
+
)}
|
|
485
|
+
{!editing && (
|
|
486
|
+
<span className={`px-2 py-1 rounded text-xs font-medium flex-shrink-0 ${statusColors[task.status]}`}>
|
|
487
|
+
{task.status}
|
|
488
|
+
</span>
|
|
489
|
+
)}
|
|
383
490
|
</div>
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
491
|
+
{!editing && (
|
|
492
|
+
<button
|
|
493
|
+
onClick={() => onSelectAgent?.(task.agentId)}
|
|
494
|
+
className="text-sm text-[var(--color-accent)] hover:underline"
|
|
495
|
+
>
|
|
496
|
+
{task.agentName}
|
|
497
|
+
</button>
|
|
498
|
+
)}
|
|
390
499
|
</div>
|
|
391
500
|
|
|
392
501
|
{/* Description */}
|
|
393
|
-
{
|
|
502
|
+
{editing ? (
|
|
503
|
+
<div>
|
|
504
|
+
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1">Description</h4>
|
|
505
|
+
<textarea
|
|
506
|
+
value={editForm.description}
|
|
507
|
+
onChange={e => setEditForm({ ...editForm, description: e.target.value })}
|
|
508
|
+
className={`${inputClass} resize-none`}
|
|
509
|
+
rows={3}
|
|
510
|
+
placeholder="Task description..."
|
|
511
|
+
/>
|
|
512
|
+
</div>
|
|
513
|
+
) : task.description ? (
|
|
394
514
|
<div>
|
|
395
515
|
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1">Description</h4>
|
|
396
516
|
<p className="text-sm text-[var(--color-text-secondary)] whitespace-pre-wrap">{task.description}</p>
|
|
397
517
|
</div>
|
|
398
|
-
)}
|
|
518
|
+
) : null}
|
|
399
519
|
|
|
400
520
|
{/* Metadata */}
|
|
401
|
-
|
|
402
|
-
<div>
|
|
403
|
-
<
|
|
404
|
-
|
|
521
|
+
{editing ? (
|
|
522
|
+
<div className="grid grid-cols-2 gap-3">
|
|
523
|
+
<div>
|
|
524
|
+
<label className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1 block">Type</label>
|
|
525
|
+
<select
|
|
526
|
+
value={editForm.type}
|
|
527
|
+
onChange={e => setEditForm({ ...editForm, type: e.target.value as "once" | "recurring" })}
|
|
528
|
+
className={inputClass}
|
|
529
|
+
>
|
|
530
|
+
<option value="once">One-time</option>
|
|
531
|
+
<option value="recurring">Recurring</option>
|
|
532
|
+
</select>
|
|
533
|
+
</div>
|
|
534
|
+
<div>
|
|
535
|
+
<label className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1 block">Priority</label>
|
|
536
|
+
<input
|
|
537
|
+
type="number"
|
|
538
|
+
min={1}
|
|
539
|
+
max={10}
|
|
540
|
+
value={editForm.priority}
|
|
541
|
+
onChange={e => setEditForm({ ...editForm, priority: Number(e.target.value) })}
|
|
542
|
+
className={inputClass}
|
|
543
|
+
/>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
) : (
|
|
547
|
+
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
548
|
+
<div>
|
|
549
|
+
<span className="text-[var(--color-text-muted)]">Type</span>
|
|
550
|
+
<p className="capitalize">{task.type}</p>
|
|
551
|
+
</div>
|
|
552
|
+
<div>
|
|
553
|
+
<span className="text-[var(--color-text-muted)]">Priority</span>
|
|
554
|
+
<p>{task.priority}</p>
|
|
555
|
+
</div>
|
|
556
|
+
<div>
|
|
557
|
+
<span className="text-[var(--color-text-muted)]">Source</span>
|
|
558
|
+
<p className="capitalize">{task.source}</p>
|
|
559
|
+
</div>
|
|
560
|
+
{task.recurrence && (
|
|
561
|
+
<div>
|
|
562
|
+
<span className="text-[var(--color-text-muted)]">Recurrence</span>
|
|
563
|
+
<p>{formatCron(task.recurrence)}</p>
|
|
564
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-0.5 font-mono">{task.recurrence}</p>
|
|
565
|
+
</div>
|
|
566
|
+
)}
|
|
405
567
|
</div>
|
|
568
|
+
)}
|
|
569
|
+
|
|
570
|
+
{/* Schedule (edit mode) */}
|
|
571
|
+
{editing && editForm.type === "once" && (
|
|
406
572
|
<div>
|
|
407
|
-
<
|
|
408
|
-
<
|
|
573
|
+
<label className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1 block">Schedule</label>
|
|
574
|
+
<input
|
|
575
|
+
type="datetime-local"
|
|
576
|
+
value={editForm.execute_at}
|
|
577
|
+
onChange={e => setEditForm({ ...editForm, execute_at: e.target.value })}
|
|
578
|
+
className={inputClass}
|
|
579
|
+
/>
|
|
580
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">Leave empty for manual execution</p>
|
|
409
581
|
</div>
|
|
582
|
+
)}
|
|
583
|
+
{editing && editForm.type === "recurring" && (
|
|
410
584
|
<div>
|
|
411
|
-
<
|
|
412
|
-
<
|
|
585
|
+
<label className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1 block">Cron Schedule</label>
|
|
586
|
+
<input
|
|
587
|
+
type="text"
|
|
588
|
+
value={editForm.recurrence}
|
|
589
|
+
onChange={e => setEditForm({ ...editForm, recurrence: e.target.value })}
|
|
590
|
+
className={`${inputClass} font-mono`}
|
|
591
|
+
placeholder="*/30 * * * *"
|
|
592
|
+
/>
|
|
593
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">e.g. */30 * * * * = every 30 min</p>
|
|
413
594
|
</div>
|
|
414
|
-
|
|
415
|
-
<div>
|
|
416
|
-
<span className="text-[var(--color-text-muted)]">Recurrence</span>
|
|
417
|
-
<p>{formatCron(task.recurrence)}</p>
|
|
418
|
-
<p className="text-xs text-[var(--color-text-faint)] mt-0.5 font-mono">{task.recurrence}</p>
|
|
419
|
-
</div>
|
|
420
|
-
)}
|
|
421
|
-
</div>
|
|
595
|
+
)}
|
|
422
596
|
|
|
423
|
-
{/* Timestamps */}
|
|
597
|
+
{/* Timestamps (view mode only) */}
|
|
598
|
+
{!editing && (
|
|
424
599
|
<div className="space-y-2 text-sm">
|
|
425
600
|
<div className="flex justify-between">
|
|
426
601
|
<span className="text-[var(--color-text-muted)]">Created</span>
|
|
@@ -451,6 +626,7 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
451
626
|
</div>
|
|
452
627
|
)}
|
|
453
628
|
</div>
|
|
629
|
+
)}
|
|
454
630
|
|
|
455
631
|
{/* Error */}
|
|
456
632
|
{task.status === "failed" && task.error && (
|
|
@@ -707,7 +883,7 @@ function CreateTaskModal({ authFetch, currentProjectId, onClose, onCreated }: Cr
|
|
|
707
883
|
|
|
708
884
|
return (
|
|
709
885
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" onClick={onClose}>
|
|
710
|
-
<div className="bg-[var(--color-surface)]
|
|
886
|
+
<div className="bg-[var(--color-surface)] card w-full max-w-md" onClick={e => e.stopPropagation()}>
|
|
711
887
|
<div className="flex items-center justify-between p-4 border-b border-[var(--color-border)]">
|
|
712
888
|
<h2 className="font-medium">Create Task</h2>
|
|
713
889
|
<button onClick={onClose} className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition">
|