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.
- package/README.md +216 -54
- package/cli.js +35 -0
- package/install.js +92 -0
- package/package.json +12 -79
- package/LICENSE +0 -63
- package/bin/apteva.js +0 -196
- package/dist/ActivityPage.kxzzb4yc.js +0 -3
- package/dist/ApiDocsPage.zq998hbm.js +0 -4
- package/dist/App.55rea8mn.js +0 -61
- package/dist/App.5ywb23z4.js +0 -53
- package/dist/App.6thds120.js +0 -4
- package/dist/App.9tctxzqm.js +0 -8
- package/dist/App.a8r8ttaz.js +0 -4
- package/dist/App.agsv5bje.js +0 -4
- package/dist/App.cepapqmx.js +0 -4
- package/dist/App.dp041gb3.js +0 -221
- package/dist/App.fds72zb5.js +0 -4
- package/dist/App.fg9qj2dq.js +0 -4
- package/dist/App.ndfejbm9.js +0 -4
- package/dist/App.nxmfmq1h.js +0 -13
- package/dist/App.qdfyt8ba.js +0 -4
- package/dist/App.x2d0ygt6.js +0 -4
- package/dist/App.yt9p4nr3.js +0 -20
- package/dist/App.zn4mw16t.js +0 -1
- package/dist/ConnectionsPage.8r96ryw7.js +0 -3
- package/dist/McpPage.3cwh0gnd.js +0 -3
- package/dist/SettingsPage.ykgdh5ev.js +0 -3
- package/dist/SkillsPage.4np1s65b.js +0 -3
- package/dist/TasksPage.4g08t7p6.js +0 -3
- package/dist/TelemetryPage.72w9pwcp.js +0 -3
- package/dist/TestsPage.z4fk3r7r.js +0 -3
- package/dist/ThreadsPage.63tcajeh.js +0 -3
- package/dist/apteva-kit.css +0 -1
- package/dist/icon.png +0 -0
- package/dist/index.html +0 -16
- package/dist/styles.css +0 -1
- package/scripts/postinstall.mjs +0 -102
- package/src/auth/index.ts +0 -394
- package/src/auth/middleware.ts +0 -213
- package/src/binary.ts +0 -536
- package/src/channels/index.ts +0 -40
- package/src/channels/telegram.ts +0 -311
- package/src/crypto.ts +0 -301
- package/src/db-tests.ts +0 -174
- package/src/db.ts +0 -3133
- package/src/integrations/agentdojo.ts +0 -559
- package/src/integrations/composio.ts +0 -437
- package/src/integrations/index.ts +0 -87
- package/src/integrations/skillsmp.ts +0 -318
- package/src/mcp-client.ts +0 -605
- package/src/mcp-handler.ts +0 -394
- package/src/mcp-platform.ts +0 -2403
- package/src/openapi.ts +0 -2410
- package/src/providers.ts +0 -597
- package/src/routes/api/agent-utils.ts +0 -890
- package/src/routes/api/agents.ts +0 -916
- package/src/routes/api/api-keys.ts +0 -95
- package/src/routes/api/channels.ts +0 -182
- package/src/routes/api/helpers.ts +0 -12
- package/src/routes/api/integrations.ts +0 -639
- package/src/routes/api/mcp.ts +0 -574
- package/src/routes/api/meta-agent.ts +0 -195
- package/src/routes/api/projects.ts +0 -112
- package/src/routes/api/providers.ts +0 -424
- package/src/routes/api/skills.ts +0 -537
- package/src/routes/api/system.ts +0 -333
- package/src/routes/api/telemetry.ts +0 -203
- package/src/routes/api/tests.ts +0 -148
- package/src/routes/api/triggers.ts +0 -518
- package/src/routes/api/users.ts +0 -148
- package/src/routes/api/webhooks.ts +0 -171
- package/src/routes/api.ts +0 -53
- package/src/routes/auth.ts +0 -251
- package/src/routes/share.ts +0 -86
- package/src/routes/static.ts +0 -131
- package/src/server.ts +0 -642
- package/src/test-runner.ts +0 -598
- package/src/triggers/agentdojo.ts +0 -253
- package/src/triggers/composio.ts +0 -264
- package/src/triggers/index.ts +0 -71
- package/src/tui/AgentList.tsx +0 -145
- package/src/tui/App.tsx +0 -102
- package/src/tui/Login.tsx +0 -104
- package/src/tui/api.ts +0 -72
- package/src/tui/index.tsx +0 -7
- package/src/web/App.tsx +0 -455
- package/src/web/components/activity/ActivityPage.tsx +0 -314
- package/src/web/components/activity/index.ts +0 -1
- package/src/web/components/agents/AgentCard.tsx +0 -189
- package/src/web/components/agents/AgentPanel.tsx +0 -2244
- package/src/web/components/agents/AgentsView.tsx +0 -180
- package/src/web/components/agents/CreateAgentModal.tsx +0 -475
- package/src/web/components/agents/index.ts +0 -4
- package/src/web/components/api/ApiDocsPage.tsx +0 -842
- package/src/web/components/auth/CreateAccountStep.tsx +0 -176
- package/src/web/components/auth/LoginPage.tsx +0 -91
- package/src/web/components/auth/index.ts +0 -2
- package/src/web/components/common/Icons.tsx +0 -250
- package/src/web/components/common/LoadingSpinner.tsx +0 -44
- package/src/web/components/common/Modal.tsx +0 -199
- package/src/web/components/common/Select.tsx +0 -97
- package/src/web/components/common/index.ts +0 -20
- package/src/web/components/connections/ConnectionsPage.tsx +0 -54
- package/src/web/components/connections/IntegrationsTab.tsx +0 -170
- package/src/web/components/connections/OverviewTab.tsx +0 -137
- package/src/web/components/connections/TriggersTab.tsx +0 -1346
- package/src/web/components/dashboard/Dashboard.tsx +0 -572
- package/src/web/components/dashboard/index.ts +0 -1
- package/src/web/components/index.ts +0 -21
- package/src/web/components/layout/ErrorBanner.tsx +0 -18
- package/src/web/components/layout/Header.tsx +0 -332
- package/src/web/components/layout/Sidebar.tsx +0 -231
- package/src/web/components/layout/index.ts +0 -3
- package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
- package/src/web/components/mcp/McpPage.tsx +0 -2515
- package/src/web/components/mcp/index.ts +0 -1
- package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
- package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
- package/src/web/components/onboarding/index.ts +0 -1
- package/src/web/components/settings/SettingsPage.tsx +0 -2776
- package/src/web/components/settings/index.ts +0 -1
- package/src/web/components/skills/SkillsPage.tsx +0 -1200
- package/src/web/components/tasks/TasksPage.tsx +0 -1116
- package/src/web/components/tasks/index.ts +0 -1
- package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
- package/src/web/components/tests/TestsPage.tsx +0 -594
- package/src/web/components/threads/ThreadsPage.tsx +0 -315
- package/src/web/context/AuthContext.tsx +0 -242
- package/src/web/context/ProjectContext.tsx +0 -214
- package/src/web/context/TelemetryContext.tsx +0 -299
- package/src/web/context/ThemeContext.tsx +0 -90
- package/src/web/context/UIModeContext.tsx +0 -49
- package/src/web/context/index.ts +0 -12
- package/src/web/hooks/index.ts +0 -3
- package/src/web/hooks/useAgents.ts +0 -115
- package/src/web/hooks/useOnboarding.ts +0 -20
- package/src/web/hooks/useProviders.ts +0 -75
- package/src/web/icon.png +0 -0
- package/src/web/index.html +0 -16
- package/src/web/styles.css +0 -118
- package/src/web/themes.ts +0 -162
- package/src/web/types.ts +0 -298
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from "react";
|
|
2
|
-
import { AgentCard } from "./AgentCard";
|
|
3
|
-
import { AgentPanel } from "./AgentPanel";
|
|
4
|
-
import { LoadingSpinner } from "../common/LoadingSpinner";
|
|
5
|
-
import { useProjects, useUIMode } from "../../context";
|
|
6
|
-
import { useMetaAgent } from "../meta-agent/MetaAgent";
|
|
7
|
-
import type { Agent, Provider } from "../../types";
|
|
8
|
-
|
|
9
|
-
interface AgentsViewProps {
|
|
10
|
-
agents: Agent[];
|
|
11
|
-
loading: boolean;
|
|
12
|
-
selectedAgent: Agent | null;
|
|
13
|
-
providers: Provider[];
|
|
14
|
-
onSelectAgent: (agent: Agent) => void;
|
|
15
|
-
onCloseAgent: () => void;
|
|
16
|
-
onToggleAgent: (agent: Agent, e?: React.MouseEvent) => void;
|
|
17
|
-
onDeleteAgent: (id: string, e?: React.MouseEvent) => void;
|
|
18
|
-
onUpdateAgent: (id: string, updates: Partial<Agent>) => Promise<{ error?: string }>;
|
|
19
|
-
onNewAgent?: () => void;
|
|
20
|
-
canCreateAgent?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function AgentsView({
|
|
24
|
-
agents,
|
|
25
|
-
loading,
|
|
26
|
-
selectedAgent,
|
|
27
|
-
providers,
|
|
28
|
-
onSelectAgent,
|
|
29
|
-
onCloseAgent,
|
|
30
|
-
onToggleAgent,
|
|
31
|
-
onDeleteAgent,
|
|
32
|
-
onUpdateAgent,
|
|
33
|
-
onNewAgent,
|
|
34
|
-
canCreateAgent = true,
|
|
35
|
-
}: AgentsViewProps) {
|
|
36
|
-
const { currentProjectId, currentProject } = useProjects();
|
|
37
|
-
const { isBusiness, t } = useUIMode();
|
|
38
|
-
|
|
39
|
-
// Filter agents by current project
|
|
40
|
-
const filteredAgents = useMemo(() => {
|
|
41
|
-
if (currentProjectId === null) {
|
|
42
|
-
// "All Projects" - show all agents
|
|
43
|
-
return agents;
|
|
44
|
-
}
|
|
45
|
-
if (currentProjectId === "unassigned") {
|
|
46
|
-
// Show only agents without a project
|
|
47
|
-
return agents.filter(a => !a.projectId);
|
|
48
|
-
}
|
|
49
|
-
// Show only agents in the selected project
|
|
50
|
-
return agents.filter(a => a.projectId === currentProjectId);
|
|
51
|
-
}, [agents, currentProjectId]);
|
|
52
|
-
|
|
53
|
-
const headerTitle = currentProjectId === null
|
|
54
|
-
? t("Agents", "Employees")
|
|
55
|
-
: currentProjectId === "unassigned"
|
|
56
|
-
? t("Unassigned Agents", "Unassigned Employees")
|
|
57
|
-
: currentProject?.name || t("Agents", "Employees");
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="flex-1 flex overflow-hidden relative">
|
|
61
|
-
{/* Agents list */}
|
|
62
|
-
<div className="flex-1 overflow-auto p-6">
|
|
63
|
-
{/* Header with create button */}
|
|
64
|
-
<div className="flex items-center justify-between mb-6">
|
|
65
|
-
<div className="flex items-center gap-3">
|
|
66
|
-
{currentProject && (
|
|
67
|
-
<span
|
|
68
|
-
className="w-3 h-3 rounded-full"
|
|
69
|
-
style={{ backgroundColor: currentProject.color }}
|
|
70
|
-
/>
|
|
71
|
-
)}
|
|
72
|
-
<h1 className="text-xl font-semibold">{headerTitle}</h1>
|
|
73
|
-
{currentProjectId !== null && (
|
|
74
|
-
<span className="text-sm text-[var(--color-text-muted)]">
|
|
75
|
-
({filteredAgents.length} {t("agent", "employee")}{filteredAgents.length !== 1 ? "s" : ""})
|
|
76
|
-
</span>
|
|
77
|
-
)}
|
|
78
|
-
</div>
|
|
79
|
-
{!isBusiness && onNewAgent && (
|
|
80
|
-
<button
|
|
81
|
-
onClick={onNewAgent}
|
|
82
|
-
disabled={!canCreateAgent}
|
|
83
|
-
className="bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 disabled:cursor-not-allowed text-black px-4 py-2 rounded font-medium transition"
|
|
84
|
-
>
|
|
85
|
-
+ New Agent
|
|
86
|
-
</button>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
{loading ? (
|
|
91
|
-
<LoadingSpinner message={t("Loading agents...", "Loading employees...")} />
|
|
92
|
-
) : filteredAgents.length === 0 ? (
|
|
93
|
-
isBusiness ? (
|
|
94
|
-
<BusinessEmptyState />
|
|
95
|
-
) : (
|
|
96
|
-
<EmptyState onNewAgent={onNewAgent} canCreateAgent={canCreateAgent} hasProjectFilter={currentProjectId !== null} />
|
|
97
|
-
)
|
|
98
|
-
) : (
|
|
99
|
-
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3 auto-rows-fr">
|
|
100
|
-
{filteredAgents.map((agent) => (
|
|
101
|
-
<AgentCard
|
|
102
|
-
key={agent.id}
|
|
103
|
-
agent={agent}
|
|
104
|
-
selected={selectedAgent?.id === agent.id}
|
|
105
|
-
onSelect={() => onSelectAgent(agent)}
|
|
106
|
-
onToggle={(e) => onToggleAgent(agent, e)}
|
|
107
|
-
showProject={currentProjectId === null}
|
|
108
|
-
/>
|
|
109
|
-
))}
|
|
110
|
-
</div>
|
|
111
|
-
)}
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
{/* Overlay backdrop */}
|
|
115
|
-
{selectedAgent && (
|
|
116
|
-
<div
|
|
117
|
-
className="absolute inset-0 bg-black/40 backdrop-blur-[2px] z-10"
|
|
118
|
-
onClick={onCloseAgent}
|
|
119
|
-
/>
|
|
120
|
-
)}
|
|
121
|
-
|
|
122
|
-
{/* Agent Panel - slides in from right */}
|
|
123
|
-
{selectedAgent && (
|
|
124
|
-
<div className="absolute right-0 top-0 bottom-0 w-full sm:w-[500px] lg:w-[600px] xl:w-[700px] z-20">
|
|
125
|
-
<AgentPanel
|
|
126
|
-
agent={selectedAgent}
|
|
127
|
-
providers={providers}
|
|
128
|
-
onClose={onCloseAgent}
|
|
129
|
-
onStartAgent={(e) => onToggleAgent(selectedAgent, e)}
|
|
130
|
-
onUpdateAgent={(updates) => onUpdateAgent(selectedAgent.id, updates)}
|
|
131
|
-
onDeleteAgent={() => onDeleteAgent(selectedAgent.id)}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
)}
|
|
135
|
-
</div>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function EmptyState({ onNewAgent, canCreateAgent, hasProjectFilter }: { onNewAgent?: () => void; canCreateAgent?: boolean; hasProjectFilter?: boolean }) {
|
|
140
|
-
return (
|
|
141
|
-
<div className="text-center py-20 text-[var(--color-text-muted)]">
|
|
142
|
-
{hasProjectFilter ? (
|
|
143
|
-
<>
|
|
144
|
-
<p className="text-lg">No agents in this project</p>
|
|
145
|
-
<p className="text-sm mt-1">Create an agent or assign existing agents to this project</p>
|
|
146
|
-
</>
|
|
147
|
-
) : (
|
|
148
|
-
<>
|
|
149
|
-
<p className="text-lg">No agents yet</p>
|
|
150
|
-
<p className="text-sm mt-1">Create your first agent to get started</p>
|
|
151
|
-
</>
|
|
152
|
-
)}
|
|
153
|
-
{onNewAgent && (
|
|
154
|
-
<button
|
|
155
|
-
onClick={onNewAgent}
|
|
156
|
-
disabled={!canCreateAgent}
|
|
157
|
-
className="mt-4 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 disabled:cursor-not-allowed text-black px-4 py-2 rounded font-medium transition"
|
|
158
|
-
>
|
|
159
|
-
+ New Agent
|
|
160
|
-
</button>
|
|
161
|
-
)}
|
|
162
|
-
</div>
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function BusinessEmptyState() {
|
|
167
|
-
const { toggle } = useMetaAgent();
|
|
168
|
-
return (
|
|
169
|
-
<div className="text-center py-20 text-[var(--color-text-muted)]">
|
|
170
|
-
<p className="text-lg">No employees yet</p>
|
|
171
|
-
<p className="text-sm mt-1">Ask the Assistant to create one for you</p>
|
|
172
|
-
<button
|
|
173
|
-
onClick={toggle}
|
|
174
|
-
className="mt-4 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-black px-4 py-2 rounded font-medium transition"
|
|
175
|
-
>
|
|
176
|
-
Open Assistant
|
|
177
|
-
</button>
|
|
178
|
-
</div>
|
|
179
|
-
);
|
|
180
|
-
}
|
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Modal } from "../common/Modal";
|
|
3
|
-
import { Select } from "../common/Select";
|
|
4
|
-
import { MemoryIcon, TasksIcon, FilesIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, MultiAgentIcon } from "../common/Icons";
|
|
5
|
-
import { useProjects, useAuth } from "../../context";
|
|
6
|
-
import type { Provider, NewAgentForm, AgentFeatures, MultiAgentConfig, RealtimeConfig } from "../../types";
|
|
7
|
-
import { getMultiAgentConfig, getRealtimeConfig, isRealtimeEnabled, REALTIME_PROVIDERS } from "../../types";
|
|
8
|
-
|
|
9
|
-
interface CreateAgentModalProps {
|
|
10
|
-
form: NewAgentForm;
|
|
11
|
-
providers: Provider[];
|
|
12
|
-
configuredProviders: Provider[];
|
|
13
|
-
onFormChange: (form: NewAgentForm) => void;
|
|
14
|
-
onProviderChange: (providerId: string) => void;
|
|
15
|
-
onCreate: () => void;
|
|
16
|
-
onClose: () => void;
|
|
17
|
-
onGoToSettings: () => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const FEATURE_CONFIG = [
|
|
21
|
-
{ key: "memory" as keyof AgentFeatures, label: "Memory", description: "Persistent recall", icon: MemoryIcon },
|
|
22
|
-
{ key: "tasks" as keyof AgentFeatures, label: "Tasks", description: "Schedule and execute tasks", icon: TasksIcon },
|
|
23
|
-
{ key: "files" as keyof AgentFeatures, label: "Files", description: "File storage and management", icon: FilesIcon },
|
|
24
|
-
{ key: "vision" as keyof AgentFeatures, label: "Vision", description: "Process images and PDFs", icon: VisionIcon },
|
|
25
|
-
{ key: "operator" as keyof AgentFeatures, label: "Operator", description: "Browser automation", icon: OperatorIcon },
|
|
26
|
-
{ key: "mcp" as keyof AgentFeatures, label: "MCP", description: "External tools/services", icon: McpIcon },
|
|
27
|
-
{ key: "realtime" as keyof AgentFeatures, label: "Realtime", description: "Voice conversations", icon: RealtimeIcon },
|
|
28
|
-
{ key: "agents" as keyof AgentFeatures, label: "Multi-Agent", description: "Communicate with peer agents", icon: MultiAgentIcon },
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
export function CreateAgentModal({
|
|
32
|
-
form,
|
|
33
|
-
providers,
|
|
34
|
-
configuredProviders,
|
|
35
|
-
onFormChange,
|
|
36
|
-
onProviderChange,
|
|
37
|
-
onCreate,
|
|
38
|
-
onClose,
|
|
39
|
-
onGoToSettings,
|
|
40
|
-
}: CreateAgentModalProps) {
|
|
41
|
-
const { projects, currentProjectId } = useProjects();
|
|
42
|
-
const { authFetch } = useAuth();
|
|
43
|
-
const selectedProvider = providers.find(p => p.id === form.provider);
|
|
44
|
-
const [ollamaModels, setOllamaModels] = React.useState<Array<{ value: string; label: string }>>([]);
|
|
45
|
-
const [loadingOllamaModels, setLoadingOllamaModels] = React.useState(false);
|
|
46
|
-
|
|
47
|
-
// Fetch Ollama models when Ollama is selected
|
|
48
|
-
React.useEffect(() => {
|
|
49
|
-
if (form.provider === "ollama") {
|
|
50
|
-
setLoadingOllamaModels(true);
|
|
51
|
-
authFetch("/api/providers/ollama/models")
|
|
52
|
-
.then(res => res.json())
|
|
53
|
-
.then(data => {
|
|
54
|
-
if (data.models && data.models.length > 0) {
|
|
55
|
-
setOllamaModels(data.models.map((m: { value: string; label?: string }) => ({
|
|
56
|
-
value: m.value,
|
|
57
|
-
label: m.label || m.value,
|
|
58
|
-
})));
|
|
59
|
-
// Auto-select first model if none selected
|
|
60
|
-
if (!form.model && data.models.length > 0) {
|
|
61
|
-
onFormChange({ ...form, model: data.models[0].value });
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
.catch(() => setOllamaModels([]))
|
|
66
|
-
.finally(() => setLoadingOllamaModels(false));
|
|
67
|
-
}
|
|
68
|
-
}, [form.provider]);
|
|
69
|
-
|
|
70
|
-
const providerOptions = configuredProviders
|
|
71
|
-
.filter(p => p.type === "llm")
|
|
72
|
-
.map(p => ({
|
|
73
|
-
value: p.id,
|
|
74
|
-
label: p.name,
|
|
75
|
-
}));
|
|
76
|
-
|
|
77
|
-
// Use dynamic Ollama models if available, otherwise use provider's default models
|
|
78
|
-
const modelOptions = form.provider === "ollama" && ollamaModels.length > 0
|
|
79
|
-
? ollamaModels
|
|
80
|
-
: selectedProvider?.models.map(m => ({
|
|
81
|
-
value: m.value,
|
|
82
|
-
label: m.label,
|
|
83
|
-
recommended: m.recommended,
|
|
84
|
-
})) || [];
|
|
85
|
-
|
|
86
|
-
const projectOptions = projects.map(p => ({ value: p.id, label: p.name }));
|
|
87
|
-
|
|
88
|
-
// Set default project from current selection (but not "unassigned" or "all")
|
|
89
|
-
React.useEffect(() => {
|
|
90
|
-
if (form.projectId === undefined && currentProjectId && currentProjectId !== "unassigned") {
|
|
91
|
-
onFormChange({ ...form, projectId: currentProjectId });
|
|
92
|
-
}
|
|
93
|
-
}, [currentProjectId]);
|
|
94
|
-
|
|
95
|
-
const toggleFeature = (key: keyof AgentFeatures) => {
|
|
96
|
-
if (key === "agents") {
|
|
97
|
-
// Special handling for agents feature
|
|
98
|
-
const isEnabled = typeof form.features.agents === "boolean"
|
|
99
|
-
? form.features.agents
|
|
100
|
-
: (form.features.agents as MultiAgentConfig)?.enabled ?? false;
|
|
101
|
-
if (isEnabled) {
|
|
102
|
-
onFormChange({ ...form, features: { ...form.features, agents: false } });
|
|
103
|
-
} else {
|
|
104
|
-
onFormChange({
|
|
105
|
-
...form,
|
|
106
|
-
features: {
|
|
107
|
-
...form.features,
|
|
108
|
-
agents: { enabled: true, group: form.projectId || undefined },
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
} else if (key === "realtime") {
|
|
113
|
-
// Special handling for realtime feature
|
|
114
|
-
if (isRealtimeEnabled(form.features)) {
|
|
115
|
-
onFormChange({ ...form, features: { ...form.features, realtime: false } });
|
|
116
|
-
} else {
|
|
117
|
-
onFormChange({
|
|
118
|
-
...form,
|
|
119
|
-
features: {
|
|
120
|
-
...form.features,
|
|
121
|
-
realtime: { enabled: true },
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
onFormChange({
|
|
127
|
-
...form,
|
|
128
|
-
features: {
|
|
129
|
-
...form.features,
|
|
130
|
-
[key]: !form.features[key],
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Helper to check if agents feature is enabled
|
|
137
|
-
const isAgentsEnabled = () => {
|
|
138
|
-
const agentsVal = form.features.agents;
|
|
139
|
-
if (typeof agentsVal === "boolean") return agentsVal;
|
|
140
|
-
return (agentsVal as MultiAgentConfig)?.enabled ?? false;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<Modal>
|
|
146
|
-
<h2 className="text-xl font-semibold mb-4">Create New Agent</h2>
|
|
147
|
-
|
|
148
|
-
{providerOptions.length === 0 ? (
|
|
149
|
-
<NoProvidersMessage onGoToSettings={onGoToSettings} />
|
|
150
|
-
) : (
|
|
151
|
-
<>
|
|
152
|
-
<div className="space-y-4">
|
|
153
|
-
<FormField label="Name">
|
|
154
|
-
<input
|
|
155
|
-
type="text"
|
|
156
|
-
value={form.name}
|
|
157
|
-
onChange={(e) => onFormChange({ ...form, name: e.target.value })}
|
|
158
|
-
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)] text-[var(--color-text)]"
|
|
159
|
-
placeholder="My Agent"
|
|
160
|
-
/>
|
|
161
|
-
</FormField>
|
|
162
|
-
|
|
163
|
-
{projects.length > 0 && (
|
|
164
|
-
<FormField label="Project">
|
|
165
|
-
<Select
|
|
166
|
-
value={form.projectId || ""}
|
|
167
|
-
options={projectOptions}
|
|
168
|
-
onChange={(value) => onFormChange({ ...form, projectId: value || null })}
|
|
169
|
-
placeholder="Select project..."
|
|
170
|
-
/>
|
|
171
|
-
</FormField>
|
|
172
|
-
)}
|
|
173
|
-
|
|
174
|
-
<FormField label="Provider">
|
|
175
|
-
<Select
|
|
176
|
-
value={form.provider}
|
|
177
|
-
options={providerOptions}
|
|
178
|
-
onChange={onProviderChange}
|
|
179
|
-
placeholder="Select provider..."
|
|
180
|
-
/>
|
|
181
|
-
</FormField>
|
|
182
|
-
|
|
183
|
-
<FormField label="Model">
|
|
184
|
-
{loadingOllamaModels ? (
|
|
185
|
-
<div className="text-sm text-[var(--color-text-muted)] py-2">Loading Ollama models...</div>
|
|
186
|
-
) : form.provider === "ollama" && modelOptions.length === 0 ? (
|
|
187
|
-
<div className="text-sm text-yellow-400/80 py-2">
|
|
188
|
-
No models found. Run <code className="bg-[var(--color-surface-raised)] px-1 rounded">ollama pull llama3.3</code> to download a model.
|
|
189
|
-
</div>
|
|
190
|
-
) : (
|
|
191
|
-
<Select
|
|
192
|
-
value={form.model}
|
|
193
|
-
options={modelOptions}
|
|
194
|
-
onChange={(value) => onFormChange({ ...form, model: value })}
|
|
195
|
-
placeholder="Select model..."
|
|
196
|
-
/>
|
|
197
|
-
)}
|
|
198
|
-
</FormField>
|
|
199
|
-
|
|
200
|
-
<FormField label="System Prompt">
|
|
201
|
-
<textarea
|
|
202
|
-
value={form.systemPrompt}
|
|
203
|
-
onChange={(e) => onFormChange({ ...form, systemPrompt: e.target.value })}
|
|
204
|
-
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 h-24 resize-none focus:outline-none focus:border-[var(--color-accent)] text-[var(--color-text)]"
|
|
205
|
-
/>
|
|
206
|
-
</FormField>
|
|
207
|
-
|
|
208
|
-
<FormField label="Features">
|
|
209
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
210
|
-
{FEATURE_CONFIG.map(({ key, label, description, icon: Icon }) => {
|
|
211
|
-
const isEnabled = key === "agents" ? isAgentsEnabled()
|
|
212
|
-
: key === "realtime" ? isRealtimeEnabled(form.features)
|
|
213
|
-
: !!form.features[key];
|
|
214
|
-
return (
|
|
215
|
-
<button
|
|
216
|
-
key={key}
|
|
217
|
-
type="button"
|
|
218
|
-
onClick={() => toggleFeature(key)}
|
|
219
|
-
className={`flex items-center gap-3 p-3 btn border text-left transition ${
|
|
220
|
-
isEnabled
|
|
221
|
-
? "border-[var(--color-accent)] bg-[var(--color-accent-10)]"
|
|
222
|
-
: "border-[var(--color-border-light)] hover:border-[var(--color-border-light)]"
|
|
223
|
-
}`}
|
|
224
|
-
>
|
|
225
|
-
<Icon className={`w-5 h-5 flex-shrink-0 ${isEnabled ? "text-[var(--color-accent)]" : "text-[var(--color-text-muted)]"}`} />
|
|
226
|
-
<div className="flex-1 min-w-0">
|
|
227
|
-
<div className={`text-sm font-medium ${isEnabled ? "text-[var(--color-accent)]" : ""}`}>
|
|
228
|
-
{label}
|
|
229
|
-
</div>
|
|
230
|
-
<div className="text-xs text-[var(--color-text-muted)]">{description}</div>
|
|
231
|
-
</div>
|
|
232
|
-
</button>
|
|
233
|
-
);
|
|
234
|
-
})}
|
|
235
|
-
</div>
|
|
236
|
-
</FormField>
|
|
237
|
-
|
|
238
|
-
{/* Voice Configuration - shown when Realtime is enabled */}
|
|
239
|
-
{isRealtimeEnabled(form.features) && (() => {
|
|
240
|
-
const rtConfig = getRealtimeConfig(form.features);
|
|
241
|
-
const hasOpenAI = providers.some(p => p.id === "openai" && p.hasKey);
|
|
242
|
-
const hasGemini = providers.some(p => p.id === "gemini" && p.hasKey);
|
|
243
|
-
const voiceProviders = providers.filter(p => p.type === "voice" && p.hasKey);
|
|
244
|
-
const hasStandard = voiceProviders.length > 0;
|
|
245
|
-
const currentMode = rtConfig.provider || "standard";
|
|
246
|
-
|
|
247
|
-
const updateRt = (updates: Partial<RealtimeConfig>) => {
|
|
248
|
-
onFormChange({
|
|
249
|
-
...form,
|
|
250
|
-
features: {
|
|
251
|
-
...form.features,
|
|
252
|
-
realtime: { ...rtConfig, ...updates },
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
// STT/TTS options for standard mode
|
|
258
|
-
const sttOptions = voiceProviders
|
|
259
|
-
.filter(p => !(p as any).voiceSubtype || (p as any).voiceSubtype === "stt" || (p as any).voiceSubtype === "both")
|
|
260
|
-
.map(p => ({ value: p.id, label: p.name }));
|
|
261
|
-
const ttsOptions = voiceProviders
|
|
262
|
-
.filter(p => !(p as any).voiceSubtype || (p as any).voiceSubtype === "tts" || (p as any).voiceSubtype === "both")
|
|
263
|
-
.map(p => ({ value: p.id, label: p.name }));
|
|
264
|
-
|
|
265
|
-
return (
|
|
266
|
-
<div className="p-3 bg-[var(--color-surface)] rounded border border-[var(--color-border-light)] space-y-3">
|
|
267
|
-
<p className="text-xs text-[var(--color-text-muted)] font-medium uppercase tracking-wider">Voice Configuration</p>
|
|
268
|
-
|
|
269
|
-
{/* Voice Mode Selection */}
|
|
270
|
-
<FormField label="Voice Mode">
|
|
271
|
-
<div className="flex gap-2 flex-wrap">
|
|
272
|
-
{hasOpenAI && (
|
|
273
|
-
<button type="button" onClick={() => updateRt({ provider: "openai", model: "gpt-realtime", voice: "alloy" })}
|
|
274
|
-
className={`px-3 py-1.5 text-sm btn border transition ${currentMode === "openai" ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]" : "border-[var(--color-border-light)]"}`}>
|
|
275
|
-
OpenAI Realtime
|
|
276
|
-
</button>
|
|
277
|
-
)}
|
|
278
|
-
{hasGemini && (
|
|
279
|
-
<button type="button" onClick={() => updateRt({ provider: "gemini", geminiVoice: "Kore" })}
|
|
280
|
-
className={`px-3 py-1.5 text-sm btn border transition ${currentMode === "gemini" ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]" : "border-[var(--color-border-light)]"}`}>
|
|
281
|
-
Gemini Live
|
|
282
|
-
</button>
|
|
283
|
-
)}
|
|
284
|
-
{hasStandard && (
|
|
285
|
-
<button type="button" onClick={() => updateRt({ provider: "standard" })}
|
|
286
|
-
className={`px-3 py-1.5 text-sm btn border transition ${currentMode === "standard" ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]" : "border-[var(--color-border-light)]"}`}>
|
|
287
|
-
Standard (STT+LLM+TTS)
|
|
288
|
-
</button>
|
|
289
|
-
)}
|
|
290
|
-
</div>
|
|
291
|
-
</FormField>
|
|
292
|
-
|
|
293
|
-
{/* OpenAI Realtime options */}
|
|
294
|
-
{currentMode === "openai" && (
|
|
295
|
-
<>
|
|
296
|
-
<FormField label="Realtime Model">
|
|
297
|
-
<Select
|
|
298
|
-
value={rtConfig.model || "gpt-realtime"}
|
|
299
|
-
options={REALTIME_PROVIDERS.openai.models.map(m => ({ value: m.value, label: m.label, recommended: m.recommended }))}
|
|
300
|
-
onChange={(value) => updateRt({ model: value })}
|
|
301
|
-
placeholder="Select model..."
|
|
302
|
-
/>
|
|
303
|
-
</FormField>
|
|
304
|
-
<FormField label="Voice">
|
|
305
|
-
<Select
|
|
306
|
-
value={rtConfig.voice || "alloy"}
|
|
307
|
-
options={REALTIME_PROVIDERS.openai.voices.map(v => ({ value: v.value, label: v.label, recommended: v.recommended }))}
|
|
308
|
-
onChange={(value) => updateRt({ voice: value })}
|
|
309
|
-
placeholder="Select voice..."
|
|
310
|
-
/>
|
|
311
|
-
</FormField>
|
|
312
|
-
</>
|
|
313
|
-
)}
|
|
314
|
-
|
|
315
|
-
{/* Gemini Live options */}
|
|
316
|
-
{currentMode === "gemini" && (
|
|
317
|
-
<>
|
|
318
|
-
<FormField label="Realtime Model">
|
|
319
|
-
<Select
|
|
320
|
-
value={rtConfig.geminiModel || REALTIME_PROVIDERS.gemini.models[0].value}
|
|
321
|
-
options={REALTIME_PROVIDERS.gemini.models.map(m => ({ value: m.value, label: m.label, recommended: m.recommended }))}
|
|
322
|
-
onChange={(value) => updateRt({ geminiModel: value })}
|
|
323
|
-
placeholder="Select model..."
|
|
324
|
-
/>
|
|
325
|
-
</FormField>
|
|
326
|
-
<FormField label="Voice">
|
|
327
|
-
<Select
|
|
328
|
-
value={rtConfig.geminiVoice || "Kore"}
|
|
329
|
-
options={REALTIME_PROVIDERS.gemini.voices.map(v => ({ value: v.value, label: v.label, recommended: v.recommended }))}
|
|
330
|
-
onChange={(value) => updateRt({ geminiVoice: value })}
|
|
331
|
-
placeholder="Select voice..."
|
|
332
|
-
/>
|
|
333
|
-
</FormField>
|
|
334
|
-
<div className="flex items-center gap-2">
|
|
335
|
-
<button type="button"
|
|
336
|
-
onClick={() => updateRt({ googleSearch: !rtConfig.googleSearch })}
|
|
337
|
-
className={`flex items-center gap-2 px-3 py-1.5 text-sm btn border transition ${rtConfig.googleSearch ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]" : "border-[var(--color-border-light)]"}`}>
|
|
338
|
-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
|
|
339
|
-
Google Search
|
|
340
|
-
</button>
|
|
341
|
-
<span className="text-xs text-[var(--color-text-faint)]">Enable search grounding</span>
|
|
342
|
-
</div>
|
|
343
|
-
</>
|
|
344
|
-
)}
|
|
345
|
-
|
|
346
|
-
{/* Standard mode: STT + TTS provider selection */}
|
|
347
|
-
{currentMode === "standard" && (
|
|
348
|
-
<>
|
|
349
|
-
{sttOptions.length > 0 && (
|
|
350
|
-
<FormField label="STT Provider">
|
|
351
|
-
<Select
|
|
352
|
-
value={rtConfig.sttProvider || (sttOptions[0]?.value ?? "")}
|
|
353
|
-
options={sttOptions}
|
|
354
|
-
onChange={(value) => updateRt({ sttProvider: value })}
|
|
355
|
-
placeholder="Select STT provider..."
|
|
356
|
-
/>
|
|
357
|
-
</FormField>
|
|
358
|
-
)}
|
|
359
|
-
{ttsOptions.length > 0 && (
|
|
360
|
-
<FormField label="TTS Provider">
|
|
361
|
-
<Select
|
|
362
|
-
value={rtConfig.ttsProvider || (ttsOptions[0]?.value ?? "")}
|
|
363
|
-
options={ttsOptions}
|
|
364
|
-
onChange={(value) => updateRt({ ttsProvider: value })}
|
|
365
|
-
placeholder="Select TTS provider..."
|
|
366
|
-
/>
|
|
367
|
-
</FormField>
|
|
368
|
-
)}
|
|
369
|
-
</>
|
|
370
|
-
)}
|
|
371
|
-
</div>
|
|
372
|
-
);
|
|
373
|
-
})()}
|
|
374
|
-
|
|
375
|
-
{/* Agent Built-in Tools - Anthropic only */}
|
|
376
|
-
{form.provider === "anthropic" && (
|
|
377
|
-
<FormField label="Agent Built-in Tools">
|
|
378
|
-
<div className="flex flex-wrap gap-2">
|
|
379
|
-
<button
|
|
380
|
-
type="button"
|
|
381
|
-
onClick={() => onFormChange({
|
|
382
|
-
...form,
|
|
383
|
-
features: {
|
|
384
|
-
...form.features,
|
|
385
|
-
builtinTools: {
|
|
386
|
-
...form.features.builtinTools,
|
|
387
|
-
webSearch: !form.features.builtinTools?.webSearch,
|
|
388
|
-
},
|
|
389
|
-
},
|
|
390
|
-
})}
|
|
391
|
-
className={`flex items-center gap-2 px-3 py-2 btn border transition ${
|
|
392
|
-
form.features.builtinTools?.webSearch
|
|
393
|
-
? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]"
|
|
394
|
-
: "border-[var(--color-border-light)] hover:border-[var(--color-border-light)] text-[var(--color-text-secondary)]"
|
|
395
|
-
}`}
|
|
396
|
-
>
|
|
397
|
-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
398
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
399
|
-
</svg>
|
|
400
|
-
<span className="text-sm">Web Search</span>
|
|
401
|
-
</button>
|
|
402
|
-
<button
|
|
403
|
-
type="button"
|
|
404
|
-
onClick={() => onFormChange({
|
|
405
|
-
...form,
|
|
406
|
-
features: {
|
|
407
|
-
...form.features,
|
|
408
|
-
builtinTools: {
|
|
409
|
-
...form.features.builtinTools,
|
|
410
|
-
webFetch: !form.features.builtinTools?.webFetch,
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
})}
|
|
414
|
-
className={`flex items-center gap-2 px-3 py-2 btn border transition ${
|
|
415
|
-
form.features.builtinTools?.webFetch
|
|
416
|
-
? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]"
|
|
417
|
-
: "border-[var(--color-border-light)] hover:border-[var(--color-border-light)] text-[var(--color-text-secondary)]"
|
|
418
|
-
}`}
|
|
419
|
-
>
|
|
420
|
-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
421
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
|
422
|
-
</svg>
|
|
423
|
-
<span className="text-sm">Web Fetch</span>
|
|
424
|
-
</button>
|
|
425
|
-
</div>
|
|
426
|
-
<p className="text-xs text-[var(--color-text-faint)] mt-2">
|
|
427
|
-
Provider-native tools for real-time web access
|
|
428
|
-
</p>
|
|
429
|
-
</FormField>
|
|
430
|
-
)}
|
|
431
|
-
</div>
|
|
432
|
-
|
|
433
|
-
<div className="flex gap-3 mt-6">
|
|
434
|
-
<button
|
|
435
|
-
onClick={onClose}
|
|
436
|
-
className="flex-1 border border-[var(--color-border-light)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent)] px-4 py-2 btn font-medium transition"
|
|
437
|
-
>
|
|
438
|
-
Cancel
|
|
439
|
-
</button>
|
|
440
|
-
<button
|
|
441
|
-
onClick={onCreate}
|
|
442
|
-
disabled={!form.name}
|
|
443
|
-
className="flex-1 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black px-4 py-2 btn font-medium transition"
|
|
444
|
-
>
|
|
445
|
-
Create
|
|
446
|
-
</button>
|
|
447
|
-
</div>
|
|
448
|
-
</>
|
|
449
|
-
)}
|
|
450
|
-
</Modal>
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function FormField({ label, children }: { label: string; children: React.ReactNode }) {
|
|
455
|
-
return (
|
|
456
|
-
<div>
|
|
457
|
-
<label className="block text-sm text-[var(--color-text-muted)] mb-1">{label}</label>
|
|
458
|
-
{children}
|
|
459
|
-
</div>
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function NoProvidersMessage({ onGoToSettings }: { onGoToSettings: () => void }) {
|
|
464
|
-
return (
|
|
465
|
-
<div className="text-center py-6">
|
|
466
|
-
<p className="text-[var(--color-text-muted)] mb-4">No API keys configured. Add a provider key first.</p>
|
|
467
|
-
<button
|
|
468
|
-
onClick={onGoToSettings}
|
|
469
|
-
className="bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-black px-4 py-2 rounded font-medium transition"
|
|
470
|
-
>
|
|
471
|
-
Go to Settings
|
|
472
|
-
</button>
|
|
473
|
-
</div>
|
|
474
|
-
);
|
|
475
|
-
}
|