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,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
- }
@@ -1,4 +0,0 @@
1
- export { AgentCard } from "./AgentCard";
2
- export { CreateAgentModal } from "./CreateAgentModal";
3
- export { AgentPanel } from "./AgentPanel";
4
- export { AgentsView } from "./AgentsView";