apteva 0.4.57 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +216 -54
  2. package/cli.js +35 -0
  3. package/install.js +92 -0
  4. package/package.json +12 -79
  5. package/LICENSE +0 -63
  6. package/bin/apteva.js +0 -196
  7. package/dist/ActivityPage.kxzzb4yc.js +0 -3
  8. package/dist/ApiDocsPage.zq998hbm.js +0 -4
  9. package/dist/App.55rea8mn.js +0 -61
  10. package/dist/App.5ywb23z4.js +0 -53
  11. package/dist/App.6thds120.js +0 -4
  12. package/dist/App.9tctxzqm.js +0 -8
  13. package/dist/App.a8r8ttaz.js +0 -4
  14. package/dist/App.agsv5bje.js +0 -4
  15. package/dist/App.cepapqmx.js +0 -4
  16. package/dist/App.dp041gb3.js +0 -221
  17. package/dist/App.fds72zb5.js +0 -4
  18. package/dist/App.fg9qj2dq.js +0 -4
  19. package/dist/App.ndfejbm9.js +0 -4
  20. package/dist/App.nxmfmq1h.js +0 -13
  21. package/dist/App.qdfyt8ba.js +0 -4
  22. package/dist/App.x2d0ygt6.js +0 -4
  23. package/dist/App.yt9p4nr3.js +0 -20
  24. package/dist/App.zn4mw16t.js +0 -1
  25. package/dist/ConnectionsPage.8r96ryw7.js +0 -3
  26. package/dist/McpPage.3cwh0gnd.js +0 -3
  27. package/dist/SettingsPage.ykgdh5ev.js +0 -3
  28. package/dist/SkillsPage.4np1s65b.js +0 -3
  29. package/dist/TasksPage.4g08t7p6.js +0 -3
  30. package/dist/TelemetryPage.72w9pwcp.js +0 -3
  31. package/dist/TestsPage.z4fk3r7r.js +0 -3
  32. package/dist/ThreadsPage.63tcajeh.js +0 -3
  33. package/dist/apteva-kit.css +0 -1
  34. package/dist/icon.png +0 -0
  35. package/dist/index.html +0 -16
  36. package/dist/styles.css +0 -1
  37. package/scripts/postinstall.mjs +0 -102
  38. package/src/auth/index.ts +0 -394
  39. package/src/auth/middleware.ts +0 -213
  40. package/src/binary.ts +0 -536
  41. package/src/channels/index.ts +0 -40
  42. package/src/channels/telegram.ts +0 -311
  43. package/src/crypto.ts +0 -301
  44. package/src/db-tests.ts +0 -174
  45. package/src/db.ts +0 -3133
  46. package/src/integrations/agentdojo.ts +0 -559
  47. package/src/integrations/composio.ts +0 -437
  48. package/src/integrations/index.ts +0 -87
  49. package/src/integrations/skillsmp.ts +0 -318
  50. package/src/mcp-client.ts +0 -605
  51. package/src/mcp-handler.ts +0 -394
  52. package/src/mcp-platform.ts +0 -2403
  53. package/src/openapi.ts +0 -2410
  54. package/src/providers.ts +0 -597
  55. package/src/routes/api/agent-utils.ts +0 -890
  56. package/src/routes/api/agents.ts +0 -916
  57. package/src/routes/api/api-keys.ts +0 -95
  58. package/src/routes/api/channels.ts +0 -182
  59. package/src/routes/api/helpers.ts +0 -12
  60. package/src/routes/api/integrations.ts +0 -639
  61. package/src/routes/api/mcp.ts +0 -574
  62. package/src/routes/api/meta-agent.ts +0 -195
  63. package/src/routes/api/projects.ts +0 -112
  64. package/src/routes/api/providers.ts +0 -424
  65. package/src/routes/api/skills.ts +0 -537
  66. package/src/routes/api/system.ts +0 -333
  67. package/src/routes/api/telemetry.ts +0 -203
  68. package/src/routes/api/tests.ts +0 -148
  69. package/src/routes/api/triggers.ts +0 -518
  70. package/src/routes/api/users.ts +0 -148
  71. package/src/routes/api/webhooks.ts +0 -171
  72. package/src/routes/api.ts +0 -53
  73. package/src/routes/auth.ts +0 -251
  74. package/src/routes/share.ts +0 -86
  75. package/src/routes/static.ts +0 -131
  76. package/src/server.ts +0 -642
  77. package/src/test-runner.ts +0 -598
  78. package/src/triggers/agentdojo.ts +0 -253
  79. package/src/triggers/composio.ts +0 -264
  80. package/src/triggers/index.ts +0 -71
  81. package/src/tui/AgentList.tsx +0 -145
  82. package/src/tui/App.tsx +0 -102
  83. package/src/tui/Login.tsx +0 -104
  84. package/src/tui/api.ts +0 -72
  85. package/src/tui/index.tsx +0 -7
  86. package/src/web/App.tsx +0 -455
  87. package/src/web/components/activity/ActivityPage.tsx +0 -314
  88. package/src/web/components/activity/index.ts +0 -1
  89. package/src/web/components/agents/AgentCard.tsx +0 -189
  90. package/src/web/components/agents/AgentPanel.tsx +0 -2244
  91. package/src/web/components/agents/AgentsView.tsx +0 -180
  92. package/src/web/components/agents/CreateAgentModal.tsx +0 -475
  93. package/src/web/components/agents/index.ts +0 -4
  94. package/src/web/components/api/ApiDocsPage.tsx +0 -842
  95. package/src/web/components/auth/CreateAccountStep.tsx +0 -176
  96. package/src/web/components/auth/LoginPage.tsx +0 -91
  97. package/src/web/components/auth/index.ts +0 -2
  98. package/src/web/components/common/Icons.tsx +0 -250
  99. package/src/web/components/common/LoadingSpinner.tsx +0 -44
  100. package/src/web/components/common/Modal.tsx +0 -199
  101. package/src/web/components/common/Select.tsx +0 -97
  102. package/src/web/components/common/index.ts +0 -20
  103. package/src/web/components/connections/ConnectionsPage.tsx +0 -54
  104. package/src/web/components/connections/IntegrationsTab.tsx +0 -170
  105. package/src/web/components/connections/OverviewTab.tsx +0 -137
  106. package/src/web/components/connections/TriggersTab.tsx +0 -1346
  107. package/src/web/components/dashboard/Dashboard.tsx +0 -572
  108. package/src/web/components/dashboard/index.ts +0 -1
  109. package/src/web/components/index.ts +0 -21
  110. package/src/web/components/layout/ErrorBanner.tsx +0 -18
  111. package/src/web/components/layout/Header.tsx +0 -332
  112. package/src/web/components/layout/Sidebar.tsx +0 -231
  113. package/src/web/components/layout/index.ts +0 -3
  114. package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
  115. package/src/web/components/mcp/McpPage.tsx +0 -2515
  116. package/src/web/components/mcp/index.ts +0 -1
  117. package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
  118. package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
  119. package/src/web/components/onboarding/index.ts +0 -1
  120. package/src/web/components/settings/SettingsPage.tsx +0 -2776
  121. package/src/web/components/settings/index.ts +0 -1
  122. package/src/web/components/skills/SkillsPage.tsx +0 -1200
  123. package/src/web/components/tasks/TasksPage.tsx +0 -1116
  124. package/src/web/components/tasks/index.ts +0 -1
  125. package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
  126. package/src/web/components/tests/TestsPage.tsx +0 -594
  127. package/src/web/components/threads/ThreadsPage.tsx +0 -315
  128. package/src/web/context/AuthContext.tsx +0 -242
  129. package/src/web/context/ProjectContext.tsx +0 -214
  130. package/src/web/context/TelemetryContext.tsx +0 -299
  131. package/src/web/context/ThemeContext.tsx +0 -90
  132. package/src/web/context/UIModeContext.tsx +0 -49
  133. package/src/web/context/index.ts +0 -12
  134. package/src/web/hooks/index.ts +0 -3
  135. package/src/web/hooks/useAgents.ts +0 -115
  136. package/src/web/hooks/useOnboarding.ts +0 -20
  137. package/src/web/hooks/useProviders.ts +0 -75
  138. package/src/web/icon.png +0 -0
  139. package/src/web/index.html +0 -16
  140. package/src/web/styles.css +0 -118
  141. package/src/web/themes.ts +0 -162
  142. package/src/web/types.ts +0 -298
@@ -1,332 +0,0 @@
1
- import React, { useState, useEffect, useCallback, useRef } from "react";
2
- import { useTelemetryContext, useAuth, useAuthHeaders, useProjects, useNotificationChange } from "../../context";
3
- import { MenuIcon, ChevronDownIcon, BellIcon } from "../common/Icons";
4
- import { MetaAgentButton } from "../meta-agent/MetaAgent";
5
-
6
- interface Notification {
7
- id: string;
8
- agent_id: string;
9
- timestamp: string;
10
- category: string;
11
- type: string;
12
- level: string;
13
- error: string | null;
14
- data: Record<string, unknown> | null;
15
- seen?: boolean;
16
- }
17
-
18
- interface HeaderProps {
19
- onMenuClick?: () => void;
20
- agents?: Array<{ id: string; name: string; projectId: string | null }>;
21
- }
22
-
23
- export function Header({ onMenuClick, agents = [] }: HeaderProps) {
24
- const { connected } = useTelemetryContext();
25
- const authHeaders = useAuthHeaders();
26
- const { projects, currentProjectId, currentProject, setCurrentProjectId, unassignedCount, projectsEnabled } = useProjects();
27
- const [showProjectMenu, setShowProjectMenu] = useState(false);
28
- const [showNotifications, setShowNotifications] = useState(false);
29
- const [unseenCount, setUnseenCount] = useState(0);
30
- const [notifications, setNotifications] = useState<Notification[]>([]);
31
- const notificationChange = useNotificationChange();
32
- const { events } = useTelemetryContext();
33
- const { accessToken } = useAuth();
34
- const fetchedOnce = useRef(false);
35
-
36
- const agentNames = React.useMemo(() => {
37
- const map: Record<string, string> = {};
38
- for (const a of agents) map[a.id] = a.name;
39
- return map;
40
- }, [agents]);
41
-
42
- // Set of agent IDs matching the current project filter
43
- const projectAgentIds = React.useMemo(() => {
44
- if (!projectsEnabled || currentProjectId === null) return null; // null = show all
45
- if (currentProjectId === "unassigned") return new Set(agents.filter(a => !a.projectId).map(a => a.id));
46
- return new Set(agents.filter(a => a.projectId === currentProjectId).map(a => a.id));
47
- }, [agents, currentProjectId, projectsEnabled]);
48
-
49
- // Fetch initial unseen count once
50
- useEffect(() => {
51
- if (fetchedOnce.current || !accessToken) return;
52
- fetchedOnce.current = true;
53
- fetch("/api/notifications/count", { headers: { Authorization: `Bearer ${accessToken}` } })
54
- .then(r => r.json())
55
- .then(d => setUnseenCount(d.count || 0))
56
- .catch(() => {});
57
- }, [accessToken]);
58
-
59
- // Bump count live from SSE (only if event matches current project)
60
- useEffect(() => {
61
- if (notificationChange === 0) return;
62
- const latest = events.find(e =>
63
- e.level === "error" || e.category === "ERROR" || (e.category === "system" && e.type === "agent_stopped")
64
- );
65
- if (latest && (!projectAgentIds || projectAgentIds.has(latest.agent_id))) {
66
- setUnseenCount(c => c + 1);
67
- }
68
- }, [notificationChange]); // eslint-disable-line react-hooks/exhaustive-deps
69
-
70
- // Re-count when project filter changes (from cached notifications or reset)
71
- const prevProjectRef = useRef(currentProjectId);
72
- useEffect(() => {
73
- if (prevProjectRef.current === currentProjectId) return;
74
- prevProjectRef.current = currentProjectId;
75
- // Refetch count with project filter
76
- if (!accessToken) return;
77
- fetch("/api/notifications/count", { headers: { Authorization: `Bearer ${accessToken}` } })
78
- .then(r => r.json())
79
- .then(d => {
80
- // API returns total unseen — client filters by project
81
- if (!projectAgentIds) {
82
- setUnseenCount(d.count || 0);
83
- } else {
84
- // We need to fetch actual notifications to filter by project
85
- fetch("/api/notifications?limit=200", { headers: { Authorization: `Bearer ${accessToken}` } })
86
- .then(r => r.json())
87
- .then(nd => {
88
- const unseen = (nd.notifications || []).filter(
89
- (n: Notification) => !n.seen && projectAgentIds.has(n.agent_id)
90
- );
91
- setUnseenCount(unseen.length);
92
- })
93
- .catch(() => {});
94
- }
95
- })
96
- .catch(() => {});
97
- }, [currentProjectId, accessToken, projectAgentIds]);
98
-
99
- const openNotifications = useCallback(async () => {
100
- setShowNotifications(prev => !prev);
101
- if (!showNotifications) {
102
- try {
103
- const headers: Record<string, string> = { "Content-Type": "application/json" };
104
- if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
105
- const res = await fetch("/api/notifications?limit=50", { headers });
106
- const data = await res.json();
107
- let items: Notification[] = data.notifications || [];
108
- if (projectAgentIds) items = items.filter(n => projectAgentIds.has(n.agent_id));
109
- setNotifications(items);
110
- // Mark all as seen
111
- if (unseenCount > 0) {
112
- await fetch("/api/notifications/mark-seen", {
113
- method: "POST",
114
- headers,
115
- body: JSON.stringify({ all: true }),
116
- });
117
- setUnseenCount(0);
118
- }
119
- } catch {
120
- // Ignore
121
- }
122
- }
123
- }, [showNotifications, unseenCount, accessToken, projectAgentIds]);
124
-
125
- const handleProjectSelect = (projectId: string | null) => {
126
- setCurrentProjectId(projectId);
127
- setShowProjectMenu(false);
128
- };
129
-
130
- const getProjectLabel = () => {
131
- if (currentProjectId === null) return "All Projects";
132
- if (currentProjectId === "unassigned") return "Unassigned";
133
- return currentProject?.name || "Select Project";
134
- };
135
-
136
- const getProjectColor = () => {
137
- if (currentProjectId === null) return "var(--color-text-muted)";
138
- if (currentProjectId === "unassigned") return "var(--color-text-secondary)";
139
- return currentProject?.color || "#6366f1";
140
- };
141
-
142
- return (
143
- <header className="px-4 md:px-6 py-4 flex-shrink-0" style={{ borderBottom: "1px solid var(--color-border)" }}>
144
- <div className="flex items-center justify-between">
145
- <div className="flex items-center gap-3">
146
- {/* Hamburger menu button - mobile only */}
147
- <button
148
- onClick={onMenuClick}
149
- className="p-2 -ml-2 transition md:hidden"
150
- style={{ color: "var(--color-text-muted)" }}
151
- >
152
- <MenuIcon />
153
- </button>
154
- <div className="flex items-center gap-2">
155
- <span style={{ color: "var(--color-accent)" }}>&gt;_</span>
156
- <span className="text-xl tracking-wider">apteva</span>
157
- </div>
158
-
159
- {/* Project Selector */}
160
- {projectsEnabled && projects.length > 0 && (
161
- <div className="relative ml-2 md:ml-4">
162
- <button
163
- onClick={() => setShowProjectMenu(!showProjectMenu)}
164
- className="flex items-center gap-2 px-3 py-1.5 btn transition text-sm"
165
- style={{ border: "1px solid var(--color-border-light)", backgroundColor: "var(--color-surface)" }}
166
- >
167
- <span
168
- className="w-2.5 h-2.5 rounded-full"
169
- style={{ backgroundColor: getProjectColor() }}
170
- />
171
- <span className="hidden sm:inline max-w-[120px] md:max-w-[180px] truncate">
172
- {getProjectLabel()}
173
- </span>
174
- <ChevronDownIcon />
175
- </button>
176
- {showProjectMenu && (
177
- <div className="absolute left-0 top-full mt-1 w-56 card shadow-xl z-50" style={{ backgroundColor: "var(--color-surface)" }}>
178
- <div className="py-1 max-h-64 overflow-y-auto">
179
- <button
180
- onClick={() => handleProjectSelect(null)}
181
- className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
182
- style={{
183
- backgroundColor: currentProjectId === null ? "var(--color-surface-raised)" : "transparent",
184
- color: currentProjectId === null ? "var(--color-accent)" : "var(--color-text)",
185
- }}
186
- >
187
- <span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: "var(--color-text-muted)" }} />
188
- All Projects
189
- </button>
190
- {projects.map(project => (
191
- <button
192
- key={project.id}
193
- onClick={() => handleProjectSelect(project.id)}
194
- className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
195
- style={{
196
- backgroundColor: currentProjectId === project.id ? "var(--color-surface-raised)" : "transparent",
197
- color: currentProjectId === project.id ? "var(--color-accent)" : "var(--color-text)",
198
- }}
199
- >
200
- <span
201
- className="w-2.5 h-2.5 rounded-full flex-shrink-0"
202
- style={{ backgroundColor: project.color }}
203
- />
204
- <span className="truncate">{project.name}</span>
205
- <span className="ml-auto text-xs" style={{ color: "var(--color-text-muted)" }}>{project.agentCount}</span>
206
- </button>
207
- ))}
208
- {unassignedCount > 0 && (
209
- <button
210
- onClick={() => handleProjectSelect("unassigned")}
211
- className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
212
- style={{
213
- backgroundColor: currentProjectId === "unassigned" ? "var(--color-surface-raised)" : "transparent",
214
- color: currentProjectId === "unassigned" ? "var(--color-accent)" : "var(--color-text)",
215
- }}
216
- >
217
- <span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: "var(--color-text-secondary)" }} />
218
- <span className="truncate">Unassigned</span>
219
- <span className="ml-auto text-xs" style={{ color: "var(--color-text-muted)" }}>{unassignedCount}</span>
220
- </button>
221
- )}
222
- </div>
223
- </div>
224
- )}
225
- </div>
226
- )}
227
- </div>
228
- <div className="flex items-center gap-3 md:gap-4">
229
- <div className="flex items-center gap-2">
230
- <span
231
- className={`w-2 h-2 rounded-full ${connected ? "bg-green-400" : "bg-red-400"}`}
232
- />
233
- <span className="text-xs hidden sm:inline" style={{ color: "var(--color-text-muted)" }}>
234
- {connected ? "Live" : "Offline"}
235
- </span>
236
- </div>
237
- {/* Notification Bell */}
238
- <div className="relative">
239
- <button
240
- onClick={openNotifications}
241
- className="relative p-2 transition rounded"
242
- style={{ color: "var(--color-text-muted)" }}
243
- >
244
- <BellIcon className="w-5 h-5" />
245
- {unseenCount > 0 && (
246
- <span
247
- className="absolute flex items-center justify-center bg-red-500 text-white font-bold rounded-full pointer-events-none"
248
- style={{
249
- top: 2,
250
- right: 2,
251
- fontSize: 9,
252
- lineHeight: 1,
253
- minWidth: 16,
254
- height: 16,
255
- padding: "0 4px",
256
- }}
257
- >
258
- {unseenCount > 99 ? "99+" : unseenCount}
259
- </span>
260
- )}
261
- </button>
262
- {showNotifications && (
263
- <>
264
- <div className="fixed inset-0 z-40" onClick={() => setShowNotifications(false)} />
265
- <div className="absolute right-0 top-full mt-1 w-80 card shadow-xl z-50 max-h-96 overflow-y-auto" style={{ backgroundColor: "var(--color-surface)" }}>
266
- <div className="px-4 py-3 flex items-center justify-between" style={{ borderBottom: "1px solid var(--color-border-light)" }}>
267
- <span className="text-sm font-medium">Notifications</span>
268
- {notifications.length > 0 && (
269
- <span className="text-xs" style={{ color: "var(--color-text-muted)" }}>{notifications.length} recent</span>
270
- )}
271
- </div>
272
- {notifications.length === 0 ? (
273
- <div className="px-4 py-8 text-center text-sm" style={{ color: "var(--color-text-muted)" }}>
274
- No notifications
275
- </div>
276
- ) : (
277
- <div className="py-1">
278
- {notifications.map(n => (
279
- <div key={n.id} className="px-4 py-3 transition" style={{
280
- borderBottom: "1px solid var(--color-border)",
281
- backgroundColor: !n.seen ? "var(--color-bg-secondary)" : "transparent",
282
- }}>
283
- <div className="flex items-center gap-2 mb-1">
284
- <span className={`w-2 h-2 rounded-full flex-shrink-0 ${
285
- !n.seen
286
- ? (n.level === "error" || n.category === "ERROR" ? "bg-red-400" : "")
287
- : ""
288
- }`} style={{
289
- backgroundColor: !n.seen
290
- ? (n.level === "error" || n.category === "ERROR" ? undefined : "var(--color-accent)")
291
- : "var(--color-surface-raised)",
292
- }} />
293
- <span className="text-xs font-medium truncate" style={{ color: !n.seen ? "var(--color-text)" : "var(--color-text-muted)" }}>
294
- {n.category === "system" && n.type === "agent_stopped" ? "Agent Stopped" :
295
- n.category === "ERROR" ? "Error" :
296
- `${n.category} / ${n.type}`}
297
- </span>
298
- <span className="text-[10px] ml-auto flex-shrink-0" style={{ color: "var(--color-text-faint)" }}>
299
- {formatNotifTime(n.timestamp)}
300
- </span>
301
- </div>
302
- <div className="text-xs truncate" style={{ color: !n.seen ? "var(--color-text-secondary)" : "var(--color-text-muted)" }}>
303
- {n.error || (n.data as any)?.message || (n.data as any)?.error || `${n.type} event`}
304
- </div>
305
- <div className="text-[10px] mt-1" style={{ color: "var(--color-text-faint)" }}>
306
- {agentNames[n.agent_id] || n.agent_id.slice(0, 8)}
307
- </div>
308
- </div>
309
- ))}
310
- </div>
311
- )}
312
- </div>
313
- </>
314
- )}
315
- </div>
316
- <MetaAgentButton />
317
- </div>
318
- </div>
319
- </header>
320
- );
321
- }
322
-
323
- function formatNotifTime(timestamp: string): string {
324
- const diff = Date.now() - new Date(timestamp).getTime();
325
- const mins = Math.floor(diff / 60000);
326
- if (mins < 1) return "just now";
327
- if (mins < 60) return `${mins}m ago`;
328
- const hours = Math.floor(mins / 60);
329
- if (hours < 24) return `${hours}h ago`;
330
- const days = Math.floor(hours / 24);
331
- return `${days}d ago`;
332
- }
@@ -1,231 +0,0 @@
1
- import React, { useState } from "react";
2
- import { DashboardIcon, ThreadsIcon, AgentsIcon, ActivityIcon, TasksIcon, ConnectionsIcon, McpIcon, SkillsIcon, TestsIcon, TelemetryIcon, ApiIcon, SettingsIcon, CloseIcon } from "../common/Icons";
3
- import { useAuth, useUIMode } from "../../context";
4
- import type { Route } from "../../types";
5
-
6
- interface SidebarProps {
7
- route: Route;
8
- agentCount: number;
9
- taskCount?: number;
10
- onNavigate: (route: Route) => void;
11
- isOpen?: boolean;
12
- onClose?: () => void;
13
- }
14
-
15
- export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onClose }: SidebarProps) {
16
- const { user, logout } = useAuth();
17
- const { isDev, t } = useUIMode();
18
- const [showUserMenu, setShowUserMenu] = useState(false);
19
-
20
- const handleNavigate = (newRoute: Route) => {
21
- onNavigate(newRoute);
22
- onClose?.();
23
- };
24
-
25
- const handleLogout = async () => {
26
- await logout();
27
- setShowUserMenu(false);
28
- };
29
-
30
- return (
31
- <>
32
- {/* Mobile overlay backdrop */}
33
- {isOpen && (
34
- <div
35
- className="fixed inset-0 bg-black/60 z-40 md:hidden"
36
- onClick={onClose}
37
- />
38
- )}
39
-
40
- {/* Sidebar - hidden on mobile unless open, always visible on md+ */}
41
- <aside
42
- className={`
43
- fixed inset-y-0 left-0 z-50 w-64 p-4 flex flex-col transform transition-transform duration-200 ease-in-out
44
- md:relative md:w-56 md:translate-x-0 md:z-auto
45
- ${isOpen ? "translate-x-0" : "-translate-x-full"}
46
- `}
47
- style={{ backgroundColor: "var(--color-bg)", borderRight: "1px solid var(--color-border)" }}
48
- >
49
- {/* Mobile header with close button */}
50
- <div className="flex items-center justify-between mb-4 md:hidden">
51
- <div className="flex items-center gap-2">
52
- <span style={{ color: "var(--color-accent)" }}>&gt;_</span>
53
- <span className="text-lg tracking-wider">apteva</span>
54
- </div>
55
- <button
56
- onClick={onClose}
57
- className="p-2 transition"
58
- style={{ color: "var(--color-text-muted)" }}
59
- >
60
- <CloseIcon />
61
- </button>
62
- </div>
63
-
64
- <nav className="space-y-1 flex-1">
65
- <NavButton
66
- icon={<DashboardIcon />}
67
- label="Dashboard"
68
- active={route === "dashboard"}
69
- onClick={() => handleNavigate("dashboard")}
70
- />
71
- <NavButton
72
- icon={<AgentsIcon />}
73
- label={t("Agents", "Employees")}
74
- active={route === "agents"}
75
- onClick={() => handleNavigate("agents")}
76
- badge={agentCount > 0 ? String(agentCount) : undefined}
77
- />
78
- <NavButton
79
- icon={<ThreadsIcon />}
80
- label="Threads"
81
- active={route === "threads"}
82
- onClick={() => handleNavigate("threads")}
83
- />
84
- <NavButton
85
- icon={<ActivityIcon />}
86
- label="Activity"
87
- active={route === "activity"}
88
- onClick={() => handleNavigate("activity")}
89
- />
90
- <NavButton
91
- icon={<TasksIcon />}
92
- label="Tasks"
93
- active={route === "tasks"}
94
- onClick={() => handleNavigate("tasks")}
95
- badge={taskCount && taskCount > 0 ? String(taskCount) : undefined}
96
- />
97
- {isDev && (
98
- <NavButton
99
- icon={<McpIcon />}
100
- label="MCP"
101
- active={route === "mcp"}
102
- onClick={() => handleNavigate("mcp")}
103
- />
104
- )}
105
- {isDev && (
106
- <NavButton
107
- icon={<SkillsIcon />}
108
- label="Skills"
109
- active={route === "skills"}
110
- onClick={() => handleNavigate("skills")}
111
- />
112
- )}
113
- {isDev && (
114
- <NavButton
115
- icon={<ConnectionsIcon />}
116
- label="Connections"
117
- active={route === "connections"}
118
- onClick={() => handleNavigate("connections")}
119
- />
120
- )}
121
- {isDev && (
122
- <NavButton
123
- icon={<TestsIcon />}
124
- label="Tests"
125
- active={route === "tests"}
126
- onClick={() => handleNavigate("tests")}
127
- />
128
- )}
129
- <NavButton
130
- icon={<TelemetryIcon />}
131
- label="Analytics"
132
- active={route === "analytics"}
133
- onClick={() => handleNavigate("analytics")}
134
- />
135
- {isDev && (
136
- <NavButton
137
- icon={<ApiIcon />}
138
- label="API"
139
- active={route === "api"}
140
- onClick={() => handleNavigate("api")}
141
- />
142
- )}
143
- <NavButton
144
- icon={<SettingsIcon />}
145
- label="Settings"
146
- active={route === "settings"}
147
- onClick={() => handleNavigate("settings")}
148
- />
149
- </nav>
150
-
151
- {/* User profile - pinned to bottom */}
152
- {user && (
153
- <div className="relative pt-3 mt-3" style={{ borderTop: "1px solid var(--color-border)" }}>
154
- <button
155
- onClick={() => setShowUserMenu(!showUserMenu)}
156
- className="w-full flex items-center gap-3 px-3 py-2 rounded transition"
157
- style={{ color: "var(--color-text)" }}
158
- onMouseEnter={e => e.currentTarget.style.backgroundColor = "var(--color-surface)"}
159
- onMouseLeave={e => e.currentTarget.style.backgroundColor = "transparent"}
160
- >
161
- <div className="w-8 h-8 rounded-full flex items-center justify-center text-black font-medium text-sm flex-shrink-0" style={{ backgroundColor: "var(--color-accent)" }}>
162
- {user.username.charAt(0).toUpperCase()}
163
- </div>
164
- <div className="flex-1 min-w-0 text-left">
165
- <p className="text-sm font-medium truncate">{user.username}</p>
166
- <p className="text-xs" style={{ color: "var(--color-text-faint)" }}>{user.role}</p>
167
- </div>
168
- </button>
169
- {showUserMenu && (
170
- <>
171
- <div className="fixed inset-0 z-40" onClick={() => setShowUserMenu(false)} />
172
- <div className="absolute left-3 bottom-full mb-1 w-48 card shadow-xl z-50" style={{ backgroundColor: "var(--color-surface)" }}>
173
- <button
174
- onClick={handleLogout}
175
- className="w-full px-4 py-2.5 text-left text-sm text-red-400 transition rounded-lg"
176
- onMouseEnter={e => e.currentTarget.style.backgroundColor = "var(--color-surface-raised)"}
177
- onMouseLeave={e => e.currentTarget.style.backgroundColor = "transparent"}
178
- >
179
- Sign out
180
- </button>
181
- </div>
182
- </>
183
- )}
184
- </div>
185
- )}
186
- </aside>
187
- </>
188
- );
189
- }
190
-
191
- interface NavButtonProps {
192
- icon: React.ReactNode;
193
- label: string;
194
- active: boolean;
195
- onClick: () => void;
196
- badge?: string;
197
- }
198
-
199
- function NavButton({ icon, label, active, onClick, badge }: NavButtonProps) {
200
- return (
201
- <button
202
- onClick={onClick}
203
- className="w-full flex items-center gap-3 px-3 py-2 font-medium transition"
204
- style={{
205
- borderRadius: "var(--radius-button)",
206
- backgroundColor: active ? "var(--color-surface)" : "transparent",
207
- color: active ? "var(--color-text)" : "var(--color-text-muted)",
208
- }}
209
- onMouseEnter={e => {
210
- if (!active) {
211
- e.currentTarget.style.backgroundColor = "var(--color-surface)";
212
- e.currentTarget.style.color = "var(--color-text-secondary)";
213
- }
214
- }}
215
- onMouseLeave={e => {
216
- if (!active) {
217
- e.currentTarget.style.backgroundColor = "transparent";
218
- e.currentTarget.style.color = "var(--color-text-muted)";
219
- }
220
- }}
221
- >
222
- {icon}
223
- {label}
224
- {badge && (
225
- <span className="ml-auto text-xs px-2 py-0.5 rounded-full" style={{ backgroundColor: "var(--color-surface-raised)", color: "var(--color-text-secondary)" }}>
226
- {badge}
227
- </span>
228
- )}
229
- </button>
230
- );
231
- }
@@ -1,3 +0,0 @@
1
- export { Header } from "./Header";
2
- export { Sidebar } from "./Sidebar";
3
- export { ErrorBanner } from "./ErrorBanner";