apteva 0.1.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 (47) hide show
  1. package/LICENSE +63 -0
  2. package/README.md +84 -0
  3. package/bin/agent-linux-amd64 +0 -0
  4. package/bin/apteva.js +144 -0
  5. package/dist/App.g02zmbqf.js +213 -0
  6. package/dist/App.g02zmbqf.js.map +37 -0
  7. package/dist/App.mq6jqare.js +1 -0
  8. package/dist/apteva-kit.css +1 -0
  9. package/dist/index.html +14 -0
  10. package/dist/styles.css +1 -0
  11. package/package.json +65 -0
  12. package/src/binary.ts +116 -0
  13. package/src/crypto.ts +152 -0
  14. package/src/db.ts +446 -0
  15. package/src/providers.ts +255 -0
  16. package/src/routes/api.ts +380 -0
  17. package/src/routes/static.ts +47 -0
  18. package/src/server.ts +134 -0
  19. package/src/web/App.tsx +218 -0
  20. package/src/web/components/agents/AgentCard.tsx +71 -0
  21. package/src/web/components/agents/AgentsView.tsx +69 -0
  22. package/src/web/components/agents/ChatPanel.tsx +63 -0
  23. package/src/web/components/agents/CreateAgentModal.tsx +128 -0
  24. package/src/web/components/agents/index.ts +4 -0
  25. package/src/web/components/common/Icons.tsx +61 -0
  26. package/src/web/components/common/LoadingSpinner.tsx +44 -0
  27. package/src/web/components/common/Modal.tsx +16 -0
  28. package/src/web/components/common/Select.tsx +96 -0
  29. package/src/web/components/common/index.ts +4 -0
  30. package/src/web/components/dashboard/Dashboard.tsx +136 -0
  31. package/src/web/components/dashboard/index.ts +1 -0
  32. package/src/web/components/index.ts +11 -0
  33. package/src/web/components/layout/ErrorBanner.tsx +18 -0
  34. package/src/web/components/layout/Header.tsx +26 -0
  35. package/src/web/components/layout/Sidebar.tsx +66 -0
  36. package/src/web/components/layout/index.ts +3 -0
  37. package/src/web/components/onboarding/OnboardingWizard.tsx +344 -0
  38. package/src/web/components/onboarding/index.ts +1 -0
  39. package/src/web/components/settings/SettingsPage.tsx +285 -0
  40. package/src/web/components/settings/index.ts +1 -0
  41. package/src/web/hooks/index.ts +3 -0
  42. package/src/web/hooks/useAgents.ts +62 -0
  43. package/src/web/hooks/useOnboarding.ts +25 -0
  44. package/src/web/hooks/useProviders.ts +65 -0
  45. package/src/web/index.html +21 -0
  46. package/src/web/styles.css +23 -0
  47. package/src/web/types.ts +43 -0
@@ -0,0 +1,218 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "@apteva/apteva-kit/styles.css";
4
+
5
+ // Types
6
+ import type { Agent, Provider, Route, NewAgentForm } from "./types";
7
+
8
+ // Hooks
9
+ import { useAgents, useProviders, useOnboarding } from "./hooks";
10
+
11
+ // Components
12
+ import {
13
+ LoadingSpinner,
14
+ Header,
15
+ Sidebar,
16
+ ErrorBanner,
17
+ OnboardingWizard,
18
+ SettingsPage,
19
+ CreateAgentModal,
20
+ AgentsView,
21
+ Dashboard,
22
+ } from "./components";
23
+
24
+ function App() {
25
+ // Onboarding state
26
+ const { isComplete: onboardingComplete, setIsComplete: setOnboardingComplete } = useOnboarding();
27
+
28
+ // Data hooks
29
+ const {
30
+ agents,
31
+ loading,
32
+ runningCount,
33
+ fetchAgents,
34
+ createAgent,
35
+ deleteAgent,
36
+ toggleAgent,
37
+ } = useAgents(onboardingComplete === true);
38
+
39
+ const {
40
+ providers,
41
+ configuredProviders,
42
+ fetchProviders,
43
+ } = useProviders(onboardingComplete === true);
44
+
45
+ // UI state
46
+ const [showCreate, setShowCreate] = useState(false);
47
+ const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
48
+ const [route, setRoute] = useState<Route>("dashboard");
49
+ const [startError, setStartError] = useState<string | null>(null);
50
+
51
+ // Form state
52
+ const [newAgent, setNewAgent] = useState<NewAgentForm>({
53
+ name: "",
54
+ model: "",
55
+ provider: "",
56
+ systemPrompt: "You are a helpful assistant.",
57
+ });
58
+
59
+ // Set default provider when providers are loaded
60
+ useEffect(() => {
61
+ if (configuredProviders.length > 0 && !newAgent.provider) {
62
+ const defaultProvider = configuredProviders[0];
63
+ const defaultModel = defaultProvider.models.find(m => m.recommended)?.value || defaultProvider.models[0]?.value || "";
64
+ setNewAgent(prev => ({
65
+ ...prev,
66
+ provider: defaultProvider.id,
67
+ model: defaultModel,
68
+ }));
69
+ }
70
+ }, [configuredProviders, newAgent.provider]);
71
+
72
+ // Update selected agent when agents list changes
73
+ useEffect(() => {
74
+ if (selectedAgent) {
75
+ const updated = agents.find(a => a.id === selectedAgent.id);
76
+ if (updated) {
77
+ setSelectedAgent(updated);
78
+ } else {
79
+ setSelectedAgent(null);
80
+ }
81
+ }
82
+ }, [agents, selectedAgent]);
83
+
84
+ const handleProviderChange = (providerId: string) => {
85
+ const provider = providers.find(p => p.id === providerId);
86
+ const defaultModel = provider?.models.find(m => m.recommended)?.value || provider?.models[0]?.value || "";
87
+ setNewAgent(prev => ({
88
+ ...prev,
89
+ provider: providerId,
90
+ model: defaultModel,
91
+ }));
92
+ };
93
+
94
+ const handleCreateAgent = async () => {
95
+ if (!newAgent.name) return;
96
+ await createAgent(newAgent);
97
+ const defaultProvider = configuredProviders[0];
98
+ const defaultModel = defaultProvider?.models.find(m => m.recommended)?.value || defaultProvider?.models[0]?.value || "";
99
+ setNewAgent({
100
+ name: "",
101
+ model: defaultModel,
102
+ provider: defaultProvider?.id || "",
103
+ systemPrompt: "You are a helpful assistant.",
104
+ });
105
+ setShowCreate(false);
106
+ };
107
+
108
+ const handleToggleAgent = async (agent: Agent, e?: React.MouseEvent) => {
109
+ e?.stopPropagation();
110
+ setStartError(null);
111
+ const result = await toggleAgent(agent);
112
+ if (result.error) {
113
+ setStartError(result.error);
114
+ }
115
+ };
116
+
117
+ const handleDeleteAgent = async (id: string, e?: React.MouseEvent) => {
118
+ e?.stopPropagation();
119
+ if (selectedAgent?.id === id) {
120
+ setSelectedAgent(null);
121
+ }
122
+ await deleteAgent(id);
123
+ };
124
+
125
+ const handleSelectAgent = (agent: Agent) => {
126
+ setSelectedAgent(agent);
127
+ setStartError(null);
128
+ setRoute("agents");
129
+ };
130
+
131
+ const handleNavigate = (newRoute: Route) => {
132
+ setRoute(newRoute);
133
+ setSelectedAgent(null);
134
+ };
135
+
136
+ const handleOnboardingComplete = () => {
137
+ setOnboardingComplete(true);
138
+ fetchProviders();
139
+ };
140
+
141
+ // Show loading while checking onboarding
142
+ if (onboardingComplete === null) {
143
+ return <LoadingSpinner fullScreen />;
144
+ }
145
+
146
+ // Show onboarding if not complete
147
+ if (!onboardingComplete) {
148
+ return <OnboardingWizard onComplete={handleOnboardingComplete} />;
149
+ }
150
+
151
+ return (
152
+ <div className="min-h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono flex flex-col">
153
+ <Header
154
+ onNewAgent={() => setShowCreate(true)}
155
+ canCreateAgent={configuredProviders.length > 0}
156
+ />
157
+
158
+ {startError && (
159
+ <ErrorBanner message={startError} onDismiss={() => setStartError(null)} />
160
+ )}
161
+
162
+ <div className="flex flex-1 overflow-hidden">
163
+ <Sidebar
164
+ route={route}
165
+ agentCount={agents.length}
166
+ onNavigate={handleNavigate}
167
+ />
168
+
169
+ <main className="flex-1 overflow-hidden flex">
170
+ {route === "settings" && <SettingsPage />}
171
+
172
+ {route === "agents" && (
173
+ <AgentsView
174
+ agents={agents}
175
+ loading={loading}
176
+ selectedAgent={selectedAgent}
177
+ onSelectAgent={handleSelectAgent}
178
+ onCloseAgent={() => setSelectedAgent(null)}
179
+ onToggleAgent={handleToggleAgent}
180
+ onDeleteAgent={handleDeleteAgent}
181
+ />
182
+ )}
183
+
184
+ {route === "dashboard" && (
185
+ <Dashboard
186
+ agents={agents}
187
+ loading={loading}
188
+ runningCount={runningCount}
189
+ configuredProviders={configuredProviders}
190
+ onNavigate={handleNavigate}
191
+ onSelectAgent={handleSelectAgent}
192
+ />
193
+ )}
194
+ </main>
195
+ </div>
196
+
197
+ {showCreate && (
198
+ <CreateAgentModal
199
+ form={newAgent}
200
+ providers={providers}
201
+ configuredProviders={configuredProviders}
202
+ onFormChange={setNewAgent}
203
+ onProviderChange={handleProviderChange}
204
+ onCreate={handleCreateAgent}
205
+ onClose={() => setShowCreate(false)}
206
+ onGoToSettings={() => {
207
+ setShowCreate(false);
208
+ setRoute("settings");
209
+ }}
210
+ />
211
+ )}
212
+ </div>
213
+ );
214
+ }
215
+
216
+ // Mount the app
217
+ const root = createRoot(document.getElementById("root")!);
218
+ root.render(<App />);
@@ -0,0 +1,71 @@
1
+ import React from "react";
2
+ import type { Agent } from "../../types";
3
+
4
+ interface AgentCardProps {
5
+ agent: Agent;
6
+ selected: boolean;
7
+ onSelect: () => void;
8
+ onToggle: (e?: React.MouseEvent) => void;
9
+ onDelete: (e?: React.MouseEvent) => void;
10
+ }
11
+
12
+ export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: AgentCardProps) {
13
+ return (
14
+ <div
15
+ onClick={onSelect}
16
+ className={`bg-[#111] rounded p-5 border transition cursor-pointer ${
17
+ selected
18
+ ? 'border-[#f97316]'
19
+ : 'border-[#1a1a1a] hover:border-[#333]'
20
+ }`}
21
+ >
22
+ <div className="flex items-start justify-between mb-3">
23
+ <div>
24
+ <h3 className="font-semibold text-lg">{agent.name}</h3>
25
+ <p className="text-sm text-[#666]">
26
+ {agent.provider} / {agent.model}
27
+ {agent.port && <span className="text-[#444]"> · :{agent.port}</span>}
28
+ </p>
29
+ </div>
30
+ <StatusBadge status={agent.status} />
31
+ </div>
32
+
33
+ <p className="text-sm text-[#666] line-clamp-2 mb-4">
34
+ {agent.systemPrompt}
35
+ </p>
36
+
37
+ <div className="flex gap-2">
38
+ <button
39
+ onClick={onToggle}
40
+ className={`flex-1 px-3 py-1.5 rounded text-sm font-medium transition ${
41
+ agent.status === "running"
42
+ ? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
43
+ : "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
44
+ }`}
45
+ >
46
+ {agent.status === "running" ? "Stop" : "Start"}
47
+ </button>
48
+ <button
49
+ onClick={onDelete}
50
+ className="px-3 py-1.5 rounded text-sm font-medium bg-red-500/20 text-red-400 hover:bg-red-500/30 transition"
51
+ >
52
+ Delete
53
+ </button>
54
+ </div>
55
+ </div>
56
+ );
57
+ }
58
+
59
+ function StatusBadge({ status }: { status: Agent["status"] }) {
60
+ return (
61
+ <span
62
+ className={`px-2 py-1 rounded text-xs font-medium ${
63
+ status === "running"
64
+ ? "bg-[#3b82f6]/20 text-[#3b82f6]"
65
+ : "bg-[#333] text-[#666]"
66
+ }`}
67
+ >
68
+ {status}
69
+ </span>
70
+ );
71
+ }
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { AgentCard } from "./AgentCard";
3
+ import { ChatPanel } from "./ChatPanel";
4
+ import { LoadingSpinner } from "../common/LoadingSpinner";
5
+ import type { Agent } from "../../types";
6
+
7
+ interface AgentsViewProps {
8
+ agents: Agent[];
9
+ loading: boolean;
10
+ selectedAgent: Agent | null;
11
+ onSelectAgent: (agent: Agent) => void;
12
+ onCloseAgent: () => void;
13
+ onToggleAgent: (agent: Agent, e?: React.MouseEvent) => void;
14
+ onDeleteAgent: (id: string, e?: React.MouseEvent) => void;
15
+ }
16
+
17
+ export function AgentsView({
18
+ agents,
19
+ loading,
20
+ selectedAgent,
21
+ onSelectAgent,
22
+ onCloseAgent,
23
+ onToggleAgent,
24
+ onDeleteAgent,
25
+ }: AgentsViewProps) {
26
+ return (
27
+ <div className="flex-1 flex overflow-hidden">
28
+ {/* Agents list */}
29
+ <div className={`${selectedAgent ? 'w-1/2 border-r border-[#1a1a1a]' : 'flex-1'} overflow-auto p-6 transition-all`}>
30
+ {loading ? (
31
+ <LoadingSpinner message="Loading agents..." />
32
+ ) : agents.length === 0 ? (
33
+ <EmptyState />
34
+ ) : (
35
+ <div className={`grid gap-4 ${selectedAgent ? 'grid-cols-1 xl:grid-cols-2' : 'md:grid-cols-2 xl:grid-cols-3'}`}>
36
+ {agents.map((agent) => (
37
+ <AgentCard
38
+ key={agent.id}
39
+ agent={agent}
40
+ selected={selectedAgent?.id === agent.id}
41
+ onSelect={() => onSelectAgent(agent)}
42
+ onToggle={(e) => onToggleAgent(agent, e)}
43
+ onDelete={(e) => onDeleteAgent(agent.id, e)}
44
+ />
45
+ ))}
46
+ </div>
47
+ )}
48
+ </div>
49
+
50
+ {/* Chat Panel */}
51
+ {selectedAgent && (
52
+ <ChatPanel
53
+ agent={selectedAgent}
54
+ onClose={onCloseAgent}
55
+ onStartAgent={(e) => onToggleAgent(selectedAgent, e)}
56
+ />
57
+ )}
58
+ </div>
59
+ );
60
+ }
61
+
62
+ function EmptyState() {
63
+ return (
64
+ <div className="text-center py-20 text-[#666]">
65
+ <p className="text-lg">No agents yet</p>
66
+ <p className="text-sm mt-1">Create your first agent to get started</p>
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,63 @@
1
+ import React from "react";
2
+ import { Chat } from "@apteva/apteva-kit";
3
+ import { CloseIcon } from "../common/Icons";
4
+ import type { Agent } from "../../types";
5
+
6
+ interface ChatPanelProps {
7
+ agent: Agent;
8
+ onClose: () => void;
9
+ onStartAgent: (e?: React.MouseEvent) => void;
10
+ }
11
+
12
+ export function ChatPanel({ agent, onClose, onStartAgent }: ChatPanelProps) {
13
+ if (agent.status === "running" && agent.port) {
14
+ return (
15
+ <div className="w-1/2 flex flex-col overflow-hidden">
16
+ <div className="flex-1 flex flex-col overflow-hidden relative">
17
+ <button
18
+ onClick={onClose}
19
+ className="absolute top-3 right-3 z-10 text-[#666] hover:text-[#e0e0e0] transition"
20
+ >
21
+ <CloseIcon />
22
+ </button>
23
+ <Chat
24
+ agentId="default"
25
+ apiUrl={`/api/agents/${agent.id}`}
26
+ placeholder="Message this agent..."
27
+ context={agent.systemPrompt}
28
+ variant="terminal"
29
+ headerTitle={agent.name}
30
+ />
31
+ </div>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ return (
37
+ <div className="w-1/2 flex flex-col overflow-hidden">
38
+ <div className="border-b border-[#1a1a1a] p-4 flex items-center justify-between">
39
+ <div>
40
+ <h2 className="font-semibold">{agent.name}</h2>
41
+ <p className="text-sm text-[#666]">{agent.provider} / {agent.model}</p>
42
+ </div>
43
+ <button
44
+ onClick={onClose}
45
+ className="text-[#666] hover:text-[#e0e0e0] transition"
46
+ >
47
+ <CloseIcon />
48
+ </button>
49
+ </div>
50
+ <div className="flex-1 flex items-center justify-center text-[#666]">
51
+ <div className="text-center">
52
+ <p className="text-lg mb-2">Agent is not running</p>
53
+ <button
54
+ onClick={onStartAgent}
55
+ className="bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30 px-4 py-2 rounded font-medium transition"
56
+ >
57
+ Start Agent
58
+ </button>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,128 @@
1
+ import React from "react";
2
+ import { Modal } from "../common/Modal";
3
+ import { Select } from "../common/Select";
4
+ import type { Provider, NewAgentForm } from "../../types";
5
+
6
+ interface CreateAgentModalProps {
7
+ form: NewAgentForm;
8
+ providers: Provider[];
9
+ configuredProviders: Provider[];
10
+ onFormChange: (form: NewAgentForm) => void;
11
+ onProviderChange: (providerId: string) => void;
12
+ onCreate: () => void;
13
+ onClose: () => void;
14
+ onGoToSettings: () => void;
15
+ }
16
+
17
+ export function CreateAgentModal({
18
+ form,
19
+ providers,
20
+ configuredProviders,
21
+ onFormChange,
22
+ onProviderChange,
23
+ onCreate,
24
+ onClose,
25
+ onGoToSettings,
26
+ }: CreateAgentModalProps) {
27
+ const selectedProvider = providers.find(p => p.id === form.provider);
28
+
29
+ const providerOptions = configuredProviders.map(p => ({
30
+ value: p.id,
31
+ label: p.name,
32
+ }));
33
+
34
+ const modelOptions = selectedProvider?.models.map(m => ({
35
+ value: m.value,
36
+ label: m.label,
37
+ recommended: m.recommended,
38
+ })) || [];
39
+
40
+ return (
41
+ <Modal>
42
+ <h2 className="text-xl font-semibold mb-4">Create New Agent</h2>
43
+
44
+ {configuredProviders.length === 0 ? (
45
+ <NoProvidersMessage onGoToSettings={onGoToSettings} />
46
+ ) : (
47
+ <>
48
+ <div className="space-y-4">
49
+ <FormField label="Name">
50
+ <input
51
+ type="text"
52
+ value={form.name}
53
+ onChange={(e) => onFormChange({ ...form, name: e.target.value })}
54
+ className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 focus:outline-none focus:border-[#f97316] text-[#e0e0e0]"
55
+ placeholder="My Agent"
56
+ />
57
+ </FormField>
58
+
59
+ <FormField label="Provider">
60
+ <Select
61
+ value={form.provider}
62
+ options={providerOptions}
63
+ onChange={onProviderChange}
64
+ placeholder="Select provider..."
65
+ />
66
+ </FormField>
67
+
68
+ <FormField label="Model">
69
+ <Select
70
+ value={form.model}
71
+ options={modelOptions}
72
+ onChange={(value) => onFormChange({ ...form, model: value })}
73
+ placeholder="Select model..."
74
+ />
75
+ </FormField>
76
+
77
+ <FormField label="System Prompt">
78
+ <textarea
79
+ value={form.systemPrompt}
80
+ onChange={(e) => onFormChange({ ...form, systemPrompt: e.target.value })}
81
+ className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 h-24 resize-none focus:outline-none focus:border-[#f97316] text-[#e0e0e0]"
82
+ />
83
+ </FormField>
84
+ </div>
85
+
86
+ <div className="flex gap-3 mt-6">
87
+ <button
88
+ onClick={onClose}
89
+ className="flex-1 border border-[#333] hover:border-[#f97316] hover:text-[#f97316] px-4 py-2 rounded font-medium transition"
90
+ >
91
+ Cancel
92
+ </button>
93
+ <button
94
+ onClick={onCreate}
95
+ disabled={!form.name}
96
+ className="flex-1 bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 text-black px-4 py-2 rounded font-medium transition"
97
+ >
98
+ Create
99
+ </button>
100
+ </div>
101
+ </>
102
+ )}
103
+ </Modal>
104
+ );
105
+ }
106
+
107
+ function FormField({ label, children }: { label: string; children: React.ReactNode }) {
108
+ return (
109
+ <div>
110
+ <label className="block text-sm text-[#666] mb-1">{label}</label>
111
+ {children}
112
+ </div>
113
+ );
114
+ }
115
+
116
+ function NoProvidersMessage({ onGoToSettings }: { onGoToSettings: () => void }) {
117
+ return (
118
+ <div className="text-center py-6">
119
+ <p className="text-[#666] mb-4">No API keys configured. Add a provider key first.</p>
120
+ <button
121
+ onClick={onGoToSettings}
122
+ className="bg-[#f97316] hover:bg-[#fb923c] text-black px-4 py-2 rounded font-medium transition"
123
+ >
124
+ Go to Settings
125
+ </button>
126
+ </div>
127
+ );
128
+ }
@@ -0,0 +1,4 @@
1
+ export { AgentCard } from "./AgentCard";
2
+ export { CreateAgentModal } from "./CreateAgentModal";
3
+ export { ChatPanel } from "./ChatPanel";
4
+ export { AgentsView } from "./AgentsView";
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+
3
+ interface IconProps {
4
+ className?: string;
5
+ }
6
+
7
+ export function CheckIcon({ className = "w-4 h-4" }: IconProps) {
8
+ return (
9
+ <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
10
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
11
+ </svg>
12
+ );
13
+ }
14
+
15
+ export function CloseIcon({ className = "w-5 h-5" }: IconProps) {
16
+ return (
17
+ <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
18
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
19
+ </svg>
20
+ );
21
+ }
22
+
23
+ export function DashboardIcon({ className = "w-5 h-5" }: IconProps) {
24
+ return (
25
+ <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
26
+ <path
27
+ strokeLinecap="round"
28
+ strokeLinejoin="round"
29
+ strokeWidth={2}
30
+ d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
31
+ />
32
+ </svg>
33
+ );
34
+ }
35
+
36
+ export function AgentsIcon({ className = "w-5 h-5" }: IconProps) {
37
+ return (
38
+ <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
39
+ <path
40
+ strokeLinecap="round"
41
+ strokeLinejoin="round"
42
+ strokeWidth={2}
43
+ d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
44
+ />
45
+ </svg>
46
+ );
47
+ }
48
+
49
+ export function SettingsIcon({ className = "w-5 h-5" }: IconProps) {
50
+ return (
51
+ <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path
53
+ strokeLinecap="round"
54
+ strokeLinejoin="round"
55
+ strokeWidth={2}
56
+ d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
57
+ />
58
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
59
+ </svg>
60
+ );
61
+ }
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+
3
+ interface LoadingSpinnerProps {
4
+ message?: string;
5
+ fullScreen?: boolean;
6
+ }
7
+
8
+ export function LoadingSpinner({ message = "Loading...", fullScreen = false }: LoadingSpinnerProps) {
9
+ const content = (
10
+ <div className="flex items-center gap-3 text-[#666]">
11
+ <svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
12
+ <circle
13
+ className="opacity-25"
14
+ cx="12"
15
+ cy="12"
16
+ r="10"
17
+ stroke="currentColor"
18
+ strokeWidth="4"
19
+ fill="none"
20
+ />
21
+ <path
22
+ className="opacity-75"
23
+ fill="currentColor"
24
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
25
+ />
26
+ </svg>
27
+ <span>{message}</span>
28
+ </div>
29
+ );
30
+
31
+ if (fullScreen) {
32
+ return (
33
+ <div className="min-h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono flex items-center justify-center">
34
+ {content}
35
+ </div>
36
+ );
37
+ }
38
+
39
+ return (
40
+ <div className="flex items-center justify-center py-20">
41
+ {content}
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+
3
+ interface ModalProps {
4
+ children: React.ReactNode;
5
+ onClose?: () => void;
6
+ }
7
+
8
+ export function Modal({ children, onClose }: ModalProps) {
9
+ return (
10
+ <div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50">
11
+ <div className="bg-[#111] rounded p-6 w-full max-w-md border border-[#1a1a1a]">
12
+ {children}
13
+ </div>
14
+ </div>
15
+ );
16
+ }