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.
- package/LICENSE +63 -0
- package/README.md +84 -0
- package/bin/agent-linux-amd64 +0 -0
- package/bin/apteva.js +144 -0
- package/dist/App.g02zmbqf.js +213 -0
- package/dist/App.g02zmbqf.js.map +37 -0
- package/dist/App.mq6jqare.js +1 -0
- package/dist/apteva-kit.css +1 -0
- package/dist/index.html +14 -0
- package/dist/styles.css +1 -0
- package/package.json +65 -0
- package/src/binary.ts +116 -0
- package/src/crypto.ts +152 -0
- package/src/db.ts +446 -0
- package/src/providers.ts +255 -0
- package/src/routes/api.ts +380 -0
- package/src/routes/static.ts +47 -0
- package/src/server.ts +134 -0
- package/src/web/App.tsx +218 -0
- package/src/web/components/agents/AgentCard.tsx +71 -0
- package/src/web/components/agents/AgentsView.tsx +69 -0
- package/src/web/components/agents/ChatPanel.tsx +63 -0
- package/src/web/components/agents/CreateAgentModal.tsx +128 -0
- package/src/web/components/agents/index.ts +4 -0
- package/src/web/components/common/Icons.tsx +61 -0
- package/src/web/components/common/LoadingSpinner.tsx +44 -0
- package/src/web/components/common/Modal.tsx +16 -0
- package/src/web/components/common/Select.tsx +96 -0
- package/src/web/components/common/index.ts +4 -0
- package/src/web/components/dashboard/Dashboard.tsx +136 -0
- package/src/web/components/dashboard/index.ts +1 -0
- package/src/web/components/index.ts +11 -0
- package/src/web/components/layout/ErrorBanner.tsx +18 -0
- package/src/web/components/layout/Header.tsx +26 -0
- package/src/web/components/layout/Sidebar.tsx +66 -0
- package/src/web/components/layout/index.ts +3 -0
- package/src/web/components/onboarding/OnboardingWizard.tsx +344 -0
- package/src/web/components/onboarding/index.ts +1 -0
- package/src/web/components/settings/SettingsPage.tsx +285 -0
- package/src/web/components/settings/index.ts +1 -0
- package/src/web/hooks/index.ts +3 -0
- package/src/web/hooks/useAgents.ts +62 -0
- package/src/web/hooks/useOnboarding.ts +25 -0
- package/src/web/hooks/useProviders.ts +65 -0
- package/src/web/index.html +21 -0
- package/src/web/styles.css +23 -0
- package/src/web/types.ts +43 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
interface SelectOption {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
recommended?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface SelectProps {
|
|
10
|
+
value: string;
|
|
11
|
+
options: SelectOption[];
|
|
12
|
+
onChange: (value: string) => void;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Select({ value, options, onChange, placeholder = "Select..." }: SelectProps) {
|
|
17
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
18
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
19
|
+
|
|
20
|
+
const selectedOption = options.find(o => o.value === value);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
function handleClickOutside(event: MouseEvent) {
|
|
24
|
+
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
25
|
+
setIsOpen(false);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
29
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div ref={ref} className="relative">
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
37
|
+
className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 text-left flex items-center justify-between focus:outline-none focus:border-[#f97316] text-[#e0e0e0] hover:border-[#333] transition"
|
|
38
|
+
>
|
|
39
|
+
<span className={selectedOption ? "text-[#e0e0e0]" : "text-[#666]"}>
|
|
40
|
+
{selectedOption ? (
|
|
41
|
+
<>
|
|
42
|
+
{selectedOption.label}
|
|
43
|
+
{selectedOption.recommended && (
|
|
44
|
+
<span className="text-[#f97316] text-xs ml-2">(Recommended)</span>
|
|
45
|
+
)}
|
|
46
|
+
</>
|
|
47
|
+
) : placeholder}
|
|
48
|
+
</span>
|
|
49
|
+
<ChevronIcon isOpen={isOpen} />
|
|
50
|
+
</button>
|
|
51
|
+
|
|
52
|
+
{isOpen && (
|
|
53
|
+
<div className="absolute z-50 w-full mt-1 bg-[#111] border border-[#222] rounded shadow-lg max-h-60 overflow-auto">
|
|
54
|
+
{options.map((option) => (
|
|
55
|
+
<button
|
|
56
|
+
key={option.value}
|
|
57
|
+
type="button"
|
|
58
|
+
onClick={() => {
|
|
59
|
+
onChange(option.value);
|
|
60
|
+
setIsOpen(false);
|
|
61
|
+
}}
|
|
62
|
+
className={`w-full px-3 py-2 text-left flex items-center justify-between hover:bg-[#1a1a1a] transition ${
|
|
63
|
+
option.value === value ? "bg-[#1a1a1a] text-[#f97316]" : "text-[#e0e0e0]"
|
|
64
|
+
}`}
|
|
65
|
+
>
|
|
66
|
+
<span>
|
|
67
|
+
{option.label}
|
|
68
|
+
{option.recommended && (
|
|
69
|
+
<span className="text-[#f97316] text-xs ml-2">(Recommended)</span>
|
|
70
|
+
)}
|
|
71
|
+
</span>
|
|
72
|
+
{option.value === value && (
|
|
73
|
+
<svg className="w-4 h-4 text-[#f97316]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
74
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
75
|
+
</svg>
|
|
76
|
+
)}
|
|
77
|
+
</button>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ChevronIcon({ isOpen }: { isOpen: boolean }) {
|
|
86
|
+
return (
|
|
87
|
+
<svg
|
|
88
|
+
className={`w-4 h-4 text-[#666] transition-transform ${isOpen ? "rotate-180" : ""}`}
|
|
89
|
+
fill="none"
|
|
90
|
+
stroke="currentColor"
|
|
91
|
+
viewBox="0 0 24 24"
|
|
92
|
+
>
|
|
93
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
94
|
+
</svg>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Agent, Provider, Route } from "../../types";
|
|
3
|
+
|
|
4
|
+
interface DashboardProps {
|
|
5
|
+
agents: Agent[];
|
|
6
|
+
loading: boolean;
|
|
7
|
+
runningCount: number;
|
|
8
|
+
configuredProviders: Provider[];
|
|
9
|
+
onNavigate: (route: Route) => void;
|
|
10
|
+
onSelectAgent: (agent: Agent) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Dashboard({
|
|
14
|
+
agents,
|
|
15
|
+
loading,
|
|
16
|
+
runningCount,
|
|
17
|
+
configuredProviders,
|
|
18
|
+
onNavigate,
|
|
19
|
+
onSelectAgent,
|
|
20
|
+
}: DashboardProps) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex-1 overflow-auto p-6">
|
|
23
|
+
{/* Stats Cards */}
|
|
24
|
+
<div className="grid grid-cols-3 gap-4 mb-6">
|
|
25
|
+
<StatCard label="Total Agents" value={agents.length} />
|
|
26
|
+
<StatCard label="Running" value={runningCount} color="text-[#3b82f6]" />
|
|
27
|
+
<StatCard label="Providers" value={configuredProviders.length} color="text-[#f97316]" />
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div className="grid grid-cols-2 gap-6">
|
|
31
|
+
{/* Agents List */}
|
|
32
|
+
<DashboardCard
|
|
33
|
+
title="Agents"
|
|
34
|
+
actionLabel="View All"
|
|
35
|
+
onAction={() => onNavigate("agents")}
|
|
36
|
+
>
|
|
37
|
+
{loading ? (
|
|
38
|
+
<div className="p-4 text-center text-[#666]">Loading...</div>
|
|
39
|
+
) : agents.length === 0 ? (
|
|
40
|
+
<div className="p-4 text-center text-[#666]">No agents yet</div>
|
|
41
|
+
) : (
|
|
42
|
+
<div className="divide-y divide-[#1a1a1a]">
|
|
43
|
+
{agents.slice(0, 5).map((agent) => (
|
|
44
|
+
<div
|
|
45
|
+
key={agent.id}
|
|
46
|
+
onClick={() => onSelectAgent(agent)}
|
|
47
|
+
className="px-4 py-3 hover:bg-[#1a1a1a] cursor-pointer flex items-center justify-between"
|
|
48
|
+
>
|
|
49
|
+
<div>
|
|
50
|
+
<p className="font-medium">{agent.name}</p>
|
|
51
|
+
<p className="text-sm text-[#666]">{agent.provider}</p>
|
|
52
|
+
</div>
|
|
53
|
+
<span
|
|
54
|
+
className={`w-2 h-2 rounded-full ${
|
|
55
|
+
agent.status === "running" ? "bg-[#3b82f6]" : "bg-[#444]"
|
|
56
|
+
}`}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
</DashboardCard>
|
|
63
|
+
|
|
64
|
+
{/* Configured Providers */}
|
|
65
|
+
<DashboardCard
|
|
66
|
+
title="Providers"
|
|
67
|
+
actionLabel="Manage"
|
|
68
|
+
onAction={() => onNavigate("settings")}
|
|
69
|
+
>
|
|
70
|
+
{configuredProviders.length === 0 ? (
|
|
71
|
+
<div className="p-4 text-center text-[#666]">
|
|
72
|
+
<p>No providers configured</p>
|
|
73
|
+
<button
|
|
74
|
+
onClick={() => onNavigate("settings")}
|
|
75
|
+
className="text-[#f97316] hover:underline mt-1"
|
|
76
|
+
>
|
|
77
|
+
Add API Key
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
) : (
|
|
81
|
+
<div className="divide-y divide-[#1a1a1a]">
|
|
82
|
+
{configuredProviders.map((provider) => (
|
|
83
|
+
<div key={provider.id} className="px-4 py-3 flex items-center justify-between">
|
|
84
|
+
<div>
|
|
85
|
+
<p className="font-medium">{provider.name}</p>
|
|
86
|
+
<p className="text-sm text-[#666]">{provider.models.length} models</p>
|
|
87
|
+
</div>
|
|
88
|
+
<span className="text-green-400 text-sm">{provider.keyHint}</span>
|
|
89
|
+
</div>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</DashboardCard>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface StatCardProps {
|
|
100
|
+
label: string;
|
|
101
|
+
value: number;
|
|
102
|
+
color?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function StatCard({ label, value, color }: StatCardProps) {
|
|
106
|
+
return (
|
|
107
|
+
<div className="bg-[#111] rounded p-4 border border-[#1a1a1a]">
|
|
108
|
+
<p className="text-sm text-[#666] mb-1">{label}</p>
|
|
109
|
+
<p className={`text-2xl font-semibold ${color || ''}`}>{value}</p>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface DashboardCardProps {
|
|
115
|
+
title: string;
|
|
116
|
+
actionLabel: string;
|
|
117
|
+
onAction: () => void;
|
|
118
|
+
children: React.ReactNode;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function DashboardCard({ title, actionLabel, onAction, children }: DashboardCardProps) {
|
|
122
|
+
return (
|
|
123
|
+
<div className="bg-[#111] rounded border border-[#1a1a1a] overflow-hidden">
|
|
124
|
+
<div className="px-4 py-3 border-b border-[#1a1a1a] flex items-center justify-between">
|
|
125
|
+
<h3 className="font-semibold">{title}</h3>
|
|
126
|
+
<button
|
|
127
|
+
onClick={onAction}
|
|
128
|
+
className="text-sm text-[#3b82f6] hover:text-[#60a5fa]"
|
|
129
|
+
>
|
|
130
|
+
{actionLabel}
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
{children}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Dashboard } from "./Dashboard";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Common components
|
|
2
|
+
export { LoadingSpinner, Modal, Select, CheckIcon, CloseIcon, DashboardIcon, AgentsIcon, SettingsIcon } from "./common";
|
|
3
|
+
|
|
4
|
+
// Layout components
|
|
5
|
+
export { Header, Sidebar, ErrorBanner } from "./layout";
|
|
6
|
+
|
|
7
|
+
// Feature components
|
|
8
|
+
export { OnboardingWizard } from "./onboarding";
|
|
9
|
+
export { SettingsPage } from "./settings";
|
|
10
|
+
export { AgentCard, CreateAgentModal, ChatPanel, AgentsView } from "./agents";
|
|
11
|
+
export { Dashboard } from "./dashboard";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { CloseIcon } from "../common/Icons";
|
|
3
|
+
|
|
4
|
+
interface ErrorBannerProps {
|
|
5
|
+
message: string;
|
|
6
|
+
onDismiss: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ErrorBanner({ message, onDismiss }: ErrorBannerProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="bg-red-500/10 border-b border-red-500/30 px-6 py-3 text-red-400 text-sm flex items-center justify-between">
|
|
12
|
+
<span>{message}</span>
|
|
13
|
+
<button onClick={onDismiss} className="hover:text-red-300">
|
|
14
|
+
<CloseIcon className="w-4 h-4" />
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface HeaderProps {
|
|
4
|
+
onNewAgent: () => void;
|
|
5
|
+
canCreateAgent: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Header({ onNewAgent, canCreateAgent }: HeaderProps) {
|
|
9
|
+
return (
|
|
10
|
+
<header className="border-b border-[#1a1a1a] px-6 py-4 flex-shrink-0">
|
|
11
|
+
<div className="flex items-center justify-between">
|
|
12
|
+
<div className="flex items-center gap-2">
|
|
13
|
+
<span className="text-[#f97316]">>_</span>
|
|
14
|
+
<span className="text-xl tracking-wider">apteva</span>
|
|
15
|
+
</div>
|
|
16
|
+
<button
|
|
17
|
+
onClick={onNewAgent}
|
|
18
|
+
disabled={!canCreateAgent}
|
|
19
|
+
className="bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 disabled:cursor-not-allowed text-black px-4 py-2 rounded font-medium transition"
|
|
20
|
+
>
|
|
21
|
+
+ New Agent
|
|
22
|
+
</button>
|
|
23
|
+
</div>
|
|
24
|
+
</header>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DashboardIcon, AgentsIcon, SettingsIcon } from "../common/Icons";
|
|
3
|
+
import type { Route } from "../../types";
|
|
4
|
+
|
|
5
|
+
interface SidebarProps {
|
|
6
|
+
route: Route;
|
|
7
|
+
agentCount: number;
|
|
8
|
+
onNavigate: (route: Route) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Sidebar({ route, agentCount, onNavigate }: SidebarProps) {
|
|
12
|
+
return (
|
|
13
|
+
<aside className="w-56 border-r border-[#1a1a1a] flex-shrink-0 p-4">
|
|
14
|
+
<nav className="space-y-1">
|
|
15
|
+
<NavButton
|
|
16
|
+
icon={<DashboardIcon />}
|
|
17
|
+
label="Dashboard"
|
|
18
|
+
active={route === "dashboard"}
|
|
19
|
+
onClick={() => onNavigate("dashboard")}
|
|
20
|
+
/>
|
|
21
|
+
<NavButton
|
|
22
|
+
icon={<AgentsIcon />}
|
|
23
|
+
label="Agents"
|
|
24
|
+
active={route === "agents"}
|
|
25
|
+
onClick={() => onNavigate("agents")}
|
|
26
|
+
badge={agentCount > 0 ? String(agentCount) : undefined}
|
|
27
|
+
/>
|
|
28
|
+
<NavButton
|
|
29
|
+
icon={<SettingsIcon />}
|
|
30
|
+
label="Settings"
|
|
31
|
+
active={route === "settings"}
|
|
32
|
+
onClick={() => onNavigate("settings")}
|
|
33
|
+
/>
|
|
34
|
+
</nav>
|
|
35
|
+
</aside>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface NavButtonProps {
|
|
40
|
+
icon: React.ReactNode;
|
|
41
|
+
label: string;
|
|
42
|
+
active: boolean;
|
|
43
|
+
onClick: () => void;
|
|
44
|
+
badge?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function NavButton({ icon, label, active, onClick, badge }: NavButtonProps) {
|
|
48
|
+
return (
|
|
49
|
+
<button
|
|
50
|
+
onClick={onClick}
|
|
51
|
+
className={`w-full flex items-center gap-3 px-3 py-2 rounded font-medium transition ${
|
|
52
|
+
active
|
|
53
|
+
? "bg-[#111] text-[#e0e0e0]"
|
|
54
|
+
: "text-[#666] hover:bg-[#111] hover:text-[#888]"
|
|
55
|
+
}`}
|
|
56
|
+
>
|
|
57
|
+
{icon}
|
|
58
|
+
{label}
|
|
59
|
+
{badge && (
|
|
60
|
+
<span className="ml-auto bg-[#333] text-[#888] text-xs px-2 py-0.5 rounded-full">
|
|
61
|
+
{badge}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
</button>
|
|
65
|
+
);
|
|
66
|
+
}
|