apteva 0.4.53 → 0.4.56
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/dist/ActivityPage.kxzzb4yc.js +3 -0
- package/dist/ApiDocsPage.zq998hbm.js +4 -0
- package/dist/App.55rea8mn.js +61 -0
- package/dist/App.5ywb23z4.js +53 -0
- package/dist/App.6thds120.js +4 -0
- package/dist/{App.jhb45d7r.js → App.9tctxzqm.js} +3 -3
- package/dist/App.a8r8ttaz.js +4 -0
- package/dist/App.agsv5bje.js +4 -0
- package/dist/App.cepapqmx.js +4 -0
- package/dist/App.dp041gb3.js +221 -0
- package/dist/App.fds72zb5.js +4 -0
- package/dist/App.fg9qj2dq.js +4 -0
- package/dist/App.ndfejbm9.js +4 -0
- package/dist/App.nxmfmq1h.js +13 -0
- package/dist/App.qdfyt8ba.js +4 -0
- package/dist/{App.9sryp183.js → App.x2d0ygt6.js} +2 -2
- package/dist/App.yt9p4nr3.js +20 -0
- package/dist/{App.wghtdzsk.js → App.zn4mw16t.js} +1 -1
- package/dist/ConnectionsPage.8r96ryw7.js +3 -0
- package/dist/McpPage.3cwh0gnd.js +3 -0
- package/dist/SettingsPage.ykgdh5ev.js +3 -0
- package/dist/SkillsPage.4np1s65b.js +3 -0
- package/dist/TasksPage.4g08t7p6.js +3 -0
- package/dist/TelemetryPage.72w9pwcp.js +3 -0
- package/dist/TestsPage.z4fk3r7r.js +3 -0
- package/dist/ThreadsPage.63tcajeh.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/src/crypto.ts +25 -4
- package/src/db.ts +24 -1
- package/src/mcp-platform.ts +273 -44
- package/src/providers.ts +125 -5
- package/src/routes/api/agent-utils.ts +105 -8
- package/src/routes/api/providers.ts +64 -0
- package/src/routes/api/telemetry.ts +0 -7
- package/src/routes/share.ts +3 -2
- package/src/server.ts +53 -7
- package/src/test-runner.ts +1 -1
- package/src/web/App.tsx +37 -22
- package/src/web/components/agents/AgentCard.tsx +12 -9
- package/src/web/components/agents/AgentPanel.tsx +126 -7
- package/src/web/components/agents/AgentsView.tsx +30 -8
- package/src/web/components/agents/CreateAgentModal.tsx +155 -5
- package/src/web/components/dashboard/Dashboard.tsx +9 -7
- package/src/web/components/layout/Sidebar.tsx +43 -32
- package/src/web/components/meta-agent/MetaAgent.tsx +6 -2
- package/src/web/components/settings/SettingsPage.tsx +172 -43
- package/src/web/components/telemetry/TelemetryPage.tsx +54 -46
- package/src/web/components/tests/TestsPage.tsx +91 -76
- package/src/web/context/TelemetryContext.tsx +4 -1
- package/src/web/context/UIModeContext.tsx +49 -0
- package/src/web/context/index.ts +3 -0
- package/src/web/types.ts +67 -3
- package/dist/ActivityPage.sw9p594m.js +0 -3
- package/dist/ApiDocsPage.90e03bz7.js +0 -4
- package/dist/App.3vnrera5.js +0 -4
- package/dist/App.94x6mh7f.js +0 -20
- package/dist/App.9t1zc5r7.js +0 -53
- package/dist/App.p7jjw1zf.js +0 -4
- package/dist/App.pfbdzrhh.js +0 -4
- package/dist/App.pse0pzar.js +0 -4
- package/dist/App.r43t58w6.js +0 -221
- package/dist/App.stgng5bx.js +0 -13
- package/dist/App.tm3k7h4b.js +0 -4
- package/dist/App.vkg121c6.js +0 -4
- package/dist/App.xva0tfzh.js +0 -4
- package/dist/App.ysxy7akk.js +0 -61
- package/dist/App.yzkh4gq2.js +0 -4
- package/dist/ConnectionsPage.q5f9fd37.js +0 -3
- package/dist/McpPage.f3ccrezb.js +0 -3
- package/dist/SettingsPage.zmzm1pp6.js +0 -3
- package/dist/SkillsPage.whxnez67.js +0 -3
- package/dist/TasksPage.zp4jfevw.js +0 -3
- package/dist/TelemetryPage.an0ky78c.js +0 -3
- package/dist/TestsPage.18krj0d1.js +0 -3
- package/dist/ThreadsPage.nnphgy98.js +0 -3
|
@@ -4,10 +4,10 @@ import { CloseIcon, MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, Re
|
|
|
4
4
|
import { formatCron, formatRelativeTime, TrajectoryView } from "../tasks/TasksPage";
|
|
5
5
|
import { Select } from "../common/Select";
|
|
6
6
|
import { useConfirm } from "../common/Modal";
|
|
7
|
-
import { useTelemetry, useTheme } from "../../context";
|
|
7
|
+
import { useTelemetry, useTheme, useUIMode } from "../../context";
|
|
8
8
|
import { useAuth } from "../../context";
|
|
9
|
-
import type { Agent, Provider, AgentFeatures, McpServer, SkillSummary, MultiAgentConfig, OperatorConfig, Task } from "../../types";
|
|
10
|
-
import { getMultiAgentConfig, getOperatorConfig } from "../../types";
|
|
9
|
+
import type { Agent, Provider, AgentFeatures, McpServer, SkillSummary, MultiAgentConfig, OperatorConfig, RealtimeConfig, Task } from "../../types";
|
|
10
|
+
import { getMultiAgentConfig, getOperatorConfig, getRealtimeConfig, isRealtimeEnabled, REALTIME_PROVIDERS } from "../../types";
|
|
11
11
|
|
|
12
12
|
type Tab = "chat" | "threads" | "tasks" | "memory" | "files" | "settings";
|
|
13
13
|
|
|
@@ -33,6 +33,7 @@ const FEATURE_CONFIG = [
|
|
|
33
33
|
|
|
34
34
|
export function AgentPanel({ agent, providers, onClose, onStartAgent, onUpdateAgent, onDeleteAgent }: AgentPanelProps) {
|
|
35
35
|
const [activeTab, setActiveTab] = useState<Tab>("chat");
|
|
36
|
+
const { isDev } = useUIMode();
|
|
36
37
|
|
|
37
38
|
return (
|
|
38
39
|
<div className="w-full h-full flex flex-col overflow-hidden bg-[var(--color-bg)] border-l border-[var(--color-border)]">
|
|
@@ -56,9 +57,11 @@ export function AgentPanel({ agent, providers, onClose, onStartAgent, onUpdateAg
|
|
|
56
57
|
<TabButton active={activeTab === "files"} onClick={() => setActiveTab("files")}>
|
|
57
58
|
Files
|
|
58
59
|
</TabButton>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
{isDev && (
|
|
61
|
+
<TabButton active={activeTab === "settings"} onClick={() => setActiveTab("settings")}>
|
|
62
|
+
Settings
|
|
63
|
+
</TabButton>
|
|
64
|
+
)}
|
|
62
65
|
</div>
|
|
63
66
|
</div>
|
|
64
67
|
|
|
@@ -113,17 +116,20 @@ function TabButton({ active, onClick, children }: { active: boolean; onClick: ()
|
|
|
113
116
|
|
|
114
117
|
function ChatTab({ agent, onStartAgent }: { agent: Agent; onStartAgent: (e?: React.MouseEvent) => void }) {
|
|
115
118
|
const { theme } = useTheme();
|
|
119
|
+
const { accessToken } = useAuth();
|
|
116
120
|
if (agent.status === "running" && agent.port) {
|
|
117
121
|
return (
|
|
118
122
|
<Chat
|
|
119
123
|
agentId="default"
|
|
120
124
|
apiUrl={`/api/agents/${agent.id}`}
|
|
125
|
+
apiKey={accessToken || undefined}
|
|
121
126
|
placeholder="Message this agent..."
|
|
122
127
|
context={agent.systemPrompt}
|
|
123
128
|
variant="terminal"
|
|
124
129
|
theme={theme.id as "light" | "dark"}
|
|
125
130
|
headerTitle={agent.name}
|
|
126
|
-
enableVoice={
|
|
131
|
+
enableVoice={isRealtimeEnabled(agent.features)}
|
|
132
|
+
voiceProvider={getRealtimeConfig(agent.features).ttsProvider}
|
|
127
133
|
/>
|
|
128
134
|
);
|
|
129
135
|
}
|
|
@@ -1626,6 +1632,21 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1626
1632
|
};
|
|
1627
1633
|
}
|
|
1628
1634
|
});
|
|
1635
|
+
} else if (key === "realtime") {
|
|
1636
|
+
// Special handling for realtime feature - convert to RealtimeConfig
|
|
1637
|
+
setForm(prev => {
|
|
1638
|
+
if (isRealtimeEnabled(prev.features)) {
|
|
1639
|
+
return { ...prev, features: { ...prev.features, realtime: false } };
|
|
1640
|
+
} else {
|
|
1641
|
+
return {
|
|
1642
|
+
...prev,
|
|
1643
|
+
features: {
|
|
1644
|
+
...prev.features,
|
|
1645
|
+
realtime: { enabled: true },
|
|
1646
|
+
},
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1629
1650
|
} else {
|
|
1630
1651
|
setForm(prev => ({
|
|
1631
1652
|
...prev,
|
|
@@ -1758,6 +1779,7 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1758
1779
|
// For agents/operator features, check the enabled property of the config
|
|
1759
1780
|
const isEnabled = key === "agents" ? isAgentsEnabled()
|
|
1760
1781
|
: key === "operator" ? isOperatorEnabled()
|
|
1782
|
+
: key === "realtime" ? isRealtimeEnabled(form.features)
|
|
1761
1783
|
: !!form.features[key];
|
|
1762
1784
|
return (
|
|
1763
1785
|
<button
|
|
@@ -1806,6 +1828,103 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1806
1828
|
</FormField>
|
|
1807
1829
|
)}
|
|
1808
1830
|
|
|
1831
|
+
{/* Voice Configuration - shown when Realtime is enabled */}
|
|
1832
|
+
{isRealtimeEnabled(form.features) && (() => {
|
|
1833
|
+
const rtConfig = getRealtimeConfig(form.features);
|
|
1834
|
+
const hasOpenAI = providers.some(p => p.id === "openai" && p.hasKey);
|
|
1835
|
+
const hasGemini = providers.some(p => p.id === "gemini" && p.hasKey);
|
|
1836
|
+
const voiceProviders = providers.filter(p => p.type === "voice" && p.hasKey);
|
|
1837
|
+
const hasStandard = voiceProviders.length > 0;
|
|
1838
|
+
const currentMode = rtConfig.provider || "standard";
|
|
1839
|
+
|
|
1840
|
+
const updateRt = (updates: Partial<RealtimeConfig>) => {
|
|
1841
|
+
setForm(prev => ({
|
|
1842
|
+
...prev,
|
|
1843
|
+
features: {
|
|
1844
|
+
...prev.features,
|
|
1845
|
+
realtime: { ...getRealtimeConfig(prev.features), ...updates },
|
|
1846
|
+
},
|
|
1847
|
+
}));
|
|
1848
|
+
};
|
|
1849
|
+
|
|
1850
|
+
const sttOptions = voiceProviders
|
|
1851
|
+
.filter(p => !(p as any).voiceSubtype || (p as any).voiceSubtype === "stt" || (p as any).voiceSubtype === "both")
|
|
1852
|
+
.map(p => ({ value: p.id, label: p.name }));
|
|
1853
|
+
const ttsOptions = voiceProviders
|
|
1854
|
+
.filter(p => !(p as any).voiceSubtype || (p as any).voiceSubtype === "tts" || (p as any).voiceSubtype === "both")
|
|
1855
|
+
.map(p => ({ value: p.id, label: p.name }));
|
|
1856
|
+
|
|
1857
|
+
return (
|
|
1858
|
+
<div className="p-3 bg-[var(--color-surface)] rounded border border-[var(--color-border-light)] space-y-3">
|
|
1859
|
+
<p className="text-xs text-[var(--color-text-muted)] font-medium uppercase tracking-wider">Voice Configuration</p>
|
|
1860
|
+
<FormField label="Voice Mode">
|
|
1861
|
+
<div className="flex gap-2 flex-wrap">
|
|
1862
|
+
{hasOpenAI && (
|
|
1863
|
+
<button type="button" onClick={() => updateRt({ provider: "openai", model: "gpt-realtime", voice: "alloy" })}
|
|
1864
|
+
className={`px-3 py-1.5 text-sm rounded border transition ${currentMode === "openai" ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]" : "border-[var(--color-border-light)]"}`}>
|
|
1865
|
+
OpenAI Realtime
|
|
1866
|
+
</button>
|
|
1867
|
+
)}
|
|
1868
|
+
{hasGemini && (
|
|
1869
|
+
<button type="button" onClick={() => updateRt({ provider: "gemini", geminiVoice: "Kore" })}
|
|
1870
|
+
className={`px-3 py-1.5 text-sm rounded border transition ${currentMode === "gemini" ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]" : "border-[var(--color-border-light)]"}`}>
|
|
1871
|
+
Gemini Live
|
|
1872
|
+
</button>
|
|
1873
|
+
)}
|
|
1874
|
+
{hasStandard && (
|
|
1875
|
+
<button type="button" onClick={() => updateRt({ provider: "standard" })}
|
|
1876
|
+
className={`px-3 py-1.5 text-sm rounded border transition ${currentMode === "standard" ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]" : "border-[var(--color-border-light)]"}`}>
|
|
1877
|
+
Standard (STT+LLM+TTS)
|
|
1878
|
+
</button>
|
|
1879
|
+
)}
|
|
1880
|
+
</div>
|
|
1881
|
+
</FormField>
|
|
1882
|
+
{currentMode === "openai" && (
|
|
1883
|
+
<>
|
|
1884
|
+
<FormField label="Realtime Model">
|
|
1885
|
+
<Select value={rtConfig.model || "gpt-realtime"} options={REALTIME_PROVIDERS.openai.models.map(m => ({ value: m.value, label: m.label, recommended: m.recommended }))} onChange={(value) => updateRt({ model: value })} placeholder="Select model..." />
|
|
1886
|
+
</FormField>
|
|
1887
|
+
<FormField label="Voice">
|
|
1888
|
+
<Select value={rtConfig.voice || "alloy"} options={REALTIME_PROVIDERS.openai.voices.map(v => ({ value: v.value, label: v.label, recommended: v.recommended }))} onChange={(value) => updateRt({ voice: value })} placeholder="Select voice..." />
|
|
1889
|
+
</FormField>
|
|
1890
|
+
</>
|
|
1891
|
+
)}
|
|
1892
|
+
{currentMode === "gemini" && (
|
|
1893
|
+
<>
|
|
1894
|
+
<FormField label="Realtime Model">
|
|
1895
|
+
<Select value={rtConfig.geminiModel || REALTIME_PROVIDERS.gemini.models[0].value} options={REALTIME_PROVIDERS.gemini.models.map(m => ({ value: m.value, label: m.label, recommended: m.recommended }))} onChange={(value) => updateRt({ geminiModel: value })} placeholder="Select model..." />
|
|
1896
|
+
</FormField>
|
|
1897
|
+
<FormField label="Voice">
|
|
1898
|
+
<Select value={rtConfig.geminiVoice || "Kore"} options={REALTIME_PROVIDERS.gemini.voices.map(v => ({ value: v.value, label: v.label, recommended: v.recommended }))} onChange={(value) => updateRt({ geminiVoice: value })} placeholder="Select voice..." />
|
|
1899
|
+
</FormField>
|
|
1900
|
+
<div className="flex items-center gap-2">
|
|
1901
|
+
<button type="button" onClick={() => updateRt({ googleSearch: !rtConfig.googleSearch })}
|
|
1902
|
+
className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded border transition ${rtConfig.googleSearch ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]" : "border-[var(--color-border-light)]"}`}>
|
|
1903
|
+
<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>
|
|
1904
|
+
Google Search
|
|
1905
|
+
</button>
|
|
1906
|
+
<span className="text-xs text-[var(--color-text-faint)]">Enable search grounding</span>
|
|
1907
|
+
</div>
|
|
1908
|
+
</>
|
|
1909
|
+
)}
|
|
1910
|
+
{currentMode === "standard" && (
|
|
1911
|
+
<>
|
|
1912
|
+
{sttOptions.length > 0 && (
|
|
1913
|
+
<FormField label="STT Provider">
|
|
1914
|
+
<Select value={rtConfig.sttProvider || (sttOptions[0]?.value ?? "")} options={sttOptions} onChange={(value) => updateRt({ sttProvider: value })} placeholder="Select STT provider..." />
|
|
1915
|
+
</FormField>
|
|
1916
|
+
)}
|
|
1917
|
+
{ttsOptions.length > 0 && (
|
|
1918
|
+
<FormField label="TTS Provider">
|
|
1919
|
+
<Select value={rtConfig.ttsProvider || (ttsOptions[0]?.value ?? "")} options={ttsOptions} onChange={(value) => updateRt({ ttsProvider: value })} placeholder="Select TTS provider..." />
|
|
1920
|
+
</FormField>
|
|
1921
|
+
)}
|
|
1922
|
+
</>
|
|
1923
|
+
)}
|
|
1924
|
+
</div>
|
|
1925
|
+
);
|
|
1926
|
+
})()}
|
|
1927
|
+
|
|
1809
1928
|
{/* Agent Built-in Tools - Anthropic only */}
|
|
1810
1929
|
{form.provider === "anthropic" && (
|
|
1811
1930
|
<FormField label="Agent Built-in Tools">
|
|
@@ -2,7 +2,8 @@ import React, { useMemo } from "react";
|
|
|
2
2
|
import { AgentCard } from "./AgentCard";
|
|
3
3
|
import { AgentPanel } from "./AgentPanel";
|
|
4
4
|
import { LoadingSpinner } from "../common/LoadingSpinner";
|
|
5
|
-
import { useProjects } from "../../context";
|
|
5
|
+
import { useProjects, useUIMode } from "../../context";
|
|
6
|
+
import { useMetaAgent } from "../meta-agent/MetaAgent";
|
|
6
7
|
import type { Agent, Provider } from "../../types";
|
|
7
8
|
|
|
8
9
|
interface AgentsViewProps {
|
|
@@ -33,6 +34,7 @@ export function AgentsView({
|
|
|
33
34
|
canCreateAgent = true,
|
|
34
35
|
}: AgentsViewProps) {
|
|
35
36
|
const { currentProjectId, currentProject } = useProjects();
|
|
37
|
+
const { isBusiness, t } = useUIMode();
|
|
36
38
|
|
|
37
39
|
// Filter agents by current project
|
|
38
40
|
const filteredAgents = useMemo(() => {
|
|
@@ -49,10 +51,10 @@ export function AgentsView({
|
|
|
49
51
|
}, [agents, currentProjectId]);
|
|
50
52
|
|
|
51
53
|
const headerTitle = currentProjectId === null
|
|
52
|
-
? "Agents"
|
|
54
|
+
? t("Agents", "Employees")
|
|
53
55
|
: currentProjectId === "unassigned"
|
|
54
|
-
? "Unassigned Agents"
|
|
55
|
-
: currentProject?.name || "Agents";
|
|
56
|
+
? t("Unassigned Agents", "Unassigned Employees")
|
|
57
|
+
: currentProject?.name || t("Agents", "Employees");
|
|
56
58
|
|
|
57
59
|
return (
|
|
58
60
|
<div className="flex-1 flex overflow-hidden relative">
|
|
@@ -70,11 +72,11 @@ export function AgentsView({
|
|
|
70
72
|
<h1 className="text-xl font-semibold">{headerTitle}</h1>
|
|
71
73
|
{currentProjectId !== null && (
|
|
72
74
|
<span className="text-sm text-[var(--color-text-muted)]">
|
|
73
|
-
({filteredAgents.length} agent{filteredAgents.length !== 1 ? "s" : ""})
|
|
75
|
+
({filteredAgents.length} {t("agent", "employee")}{filteredAgents.length !== 1 ? "s" : ""})
|
|
74
76
|
</span>
|
|
75
77
|
)}
|
|
76
78
|
</div>
|
|
77
|
-
{onNewAgent && (
|
|
79
|
+
{!isBusiness && onNewAgent && (
|
|
78
80
|
<button
|
|
79
81
|
onClick={onNewAgent}
|
|
80
82
|
disabled={!canCreateAgent}
|
|
@@ -86,9 +88,13 @@ export function AgentsView({
|
|
|
86
88
|
</div>
|
|
87
89
|
|
|
88
90
|
{loading ? (
|
|
89
|
-
<LoadingSpinner message="Loading agents..." />
|
|
91
|
+
<LoadingSpinner message={t("Loading agents...", "Loading employees...")} />
|
|
90
92
|
) : filteredAgents.length === 0 ? (
|
|
91
|
-
|
|
93
|
+
isBusiness ? (
|
|
94
|
+
<BusinessEmptyState />
|
|
95
|
+
) : (
|
|
96
|
+
<EmptyState onNewAgent={onNewAgent} canCreateAgent={canCreateAgent} hasProjectFilter={currentProjectId !== null} />
|
|
97
|
+
)
|
|
92
98
|
) : (
|
|
93
99
|
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3 auto-rows-fr">
|
|
94
100
|
{filteredAgents.map((agent) => (
|
|
@@ -156,3 +162,19 @@ function EmptyState({ onNewAgent, canCreateAgent, hasProjectFilter }: { onNewAge
|
|
|
156
162
|
</div>
|
|
157
163
|
);
|
|
158
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
|
+
}
|
|
@@ -3,8 +3,8 @@ import { Modal } from "../common/Modal";
|
|
|
3
3
|
import { Select } from "../common/Select";
|
|
4
4
|
import { MemoryIcon, TasksIcon, FilesIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, MultiAgentIcon } from "../common/Icons";
|
|
5
5
|
import { useProjects, useAuth } from "../../context";
|
|
6
|
-
import type { Provider, NewAgentForm, AgentFeatures, MultiAgentConfig } from "../../types";
|
|
7
|
-
import { getMultiAgentConfig } from "../../types";
|
|
6
|
+
import type { Provider, NewAgentForm, AgentFeatures, MultiAgentConfig, RealtimeConfig } from "../../types";
|
|
7
|
+
import { getMultiAgentConfig, getRealtimeConfig, isRealtimeEnabled, REALTIME_PROVIDERS } from "../../types";
|
|
8
8
|
|
|
9
9
|
interface CreateAgentModalProps {
|
|
10
10
|
form: NewAgentForm;
|
|
@@ -99,10 +99,8 @@ export function CreateAgentModal({
|
|
|
99
99
|
? form.features.agents
|
|
100
100
|
: (form.features.agents as MultiAgentConfig)?.enabled ?? false;
|
|
101
101
|
if (isEnabled) {
|
|
102
|
-
// Turning off
|
|
103
102
|
onFormChange({ ...form, features: { ...form.features, agents: false } });
|
|
104
103
|
} else {
|
|
105
|
-
// Turning on with defaults - use project as group
|
|
106
104
|
onFormChange({
|
|
107
105
|
...form,
|
|
108
106
|
features: {
|
|
@@ -111,6 +109,19 @@ export function CreateAgentModal({
|
|
|
111
109
|
},
|
|
112
110
|
});
|
|
113
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
|
+
}
|
|
114
125
|
} else {
|
|
115
126
|
onFormChange({
|
|
116
127
|
...form,
|
|
@@ -197,7 +208,9 @@ export function CreateAgentModal({
|
|
|
197
208
|
<FormField label="Features">
|
|
198
209
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
199
210
|
{FEATURE_CONFIG.map(({ key, label, description, icon: Icon }) => {
|
|
200
|
-
const isEnabled = key === "agents" ? isAgentsEnabled()
|
|
211
|
+
const isEnabled = key === "agents" ? isAgentsEnabled()
|
|
212
|
+
: key === "realtime" ? isRealtimeEnabled(form.features)
|
|
213
|
+
: !!form.features[key];
|
|
201
214
|
return (
|
|
202
215
|
<button
|
|
203
216
|
key={key}
|
|
@@ -222,6 +235,143 @@ export function CreateAgentModal({
|
|
|
222
235
|
</div>
|
|
223
236
|
</FormField>
|
|
224
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
|
+
|
|
225
375
|
{/* Agent Built-in Tools - Anthropic only */}
|
|
226
376
|
{form.provider === "anthropic" && (
|
|
227
377
|
<FormField label="Agent Built-in Tools">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
2
|
-
import { useAgentActivity, useAuth, useProjects, useTelemetryContext } from "../../context";
|
|
2
|
+
import { useAgentActivity, useAuth, useProjects, useTelemetryContext, useUIMode } from "../../context";
|
|
3
3
|
import { useTelemetry } from "../../context/TelemetryContext";
|
|
4
4
|
import type { TelemetryEvent } from "../../context";
|
|
5
5
|
import type { Agent, Provider, Route, DashboardStats, Task } from "../../types";
|
|
@@ -24,6 +24,7 @@ export function Dashboard({
|
|
|
24
24
|
}: DashboardProps) {
|
|
25
25
|
const { authFetch } = useAuth();
|
|
26
26
|
const { currentProjectId } = useProjects();
|
|
27
|
+
const { isDev, t } = useUIMode();
|
|
27
28
|
const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
|
|
28
29
|
const { events: taskTelemetryEvents } = useTelemetry({ category: "TASK" });
|
|
29
30
|
const lastProcessedTaskEventRef = useRef<string | null>(null);
|
|
@@ -145,24 +146,24 @@ export function Dashboard({
|
|
|
145
146
|
return (
|
|
146
147
|
<div className="flex-1 overflow-auto p-6">
|
|
147
148
|
{/* Stats Cards */}
|
|
148
|
-
<div className=
|
|
149
|
-
<StatCard label="Agents" value={filteredAgents.length} subValue={`${filteredRunningCount} running`} />
|
|
149
|
+
<div className={`grid grid-cols-2 ${isDev ? 'sm:grid-cols-4' : 'sm:grid-cols-3'} gap-4 mb-6`}>
|
|
150
|
+
<StatCard label={t("Agents", "Employees")} value={filteredAgents.length} subValue={`${filteredRunningCount} running`} />
|
|
150
151
|
<StatCard label="Tasks" value={taskStats.total} subValue={`${taskStats.pending} pending`} />
|
|
151
152
|
<StatCard label="Completed" value={taskStats.completed} color="text-green-400" />
|
|
152
|
-
<StatCard label="Providers" value={configuredProviders.length} color="text-[var(--color-accent)]" />
|
|
153
|
+
{isDev && <StatCard label="Providers" value={configuredProviders.length} color="text-[var(--color-accent)]" />}
|
|
153
154
|
</div>
|
|
154
155
|
|
|
155
156
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
156
157
|
{/* Agents List */}
|
|
157
158
|
<DashboardCard
|
|
158
|
-
title="Agents"
|
|
159
|
+
title={t("Agents", "Employees")}
|
|
159
160
|
actionLabel="View All"
|
|
160
161
|
onAction={() => onNavigate("agents")}
|
|
161
162
|
>
|
|
162
163
|
{loading ? (
|
|
163
164
|
<div className="p-4 text-center text-[var(--color-text-muted)]">Loading...</div>
|
|
164
165
|
) : filteredAgents.length === 0 ? (
|
|
165
|
-
<div className="p-4 text-center text-[var(--color-text-muted)]">No agents yet</div>
|
|
166
|
+
<div className="p-4 text-center text-[var(--color-text-muted)]">{t("No agents yet", "No employees yet")}</div>
|
|
166
167
|
) : (
|
|
167
168
|
<div className="divide-y divide-[var(--color-border)]">
|
|
168
169
|
{filteredAgents.slice(0, 5).map((agent) => (
|
|
@@ -299,6 +300,7 @@ function DashboardCard({ title, actionLabel, onAction, children }: DashboardCard
|
|
|
299
300
|
function AgentListItem({ agent, onSelect, onMessage, showProject }: { agent: Agent; onSelect: () => void; onMessage?: () => void; showProject?: boolean }) {
|
|
300
301
|
const { isActive, label } = useAgentActivity(agent.id);
|
|
301
302
|
const { projects } = useProjects();
|
|
303
|
+
const { isDev } = useUIMode();
|
|
302
304
|
const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
|
|
303
305
|
|
|
304
306
|
return (
|
|
@@ -322,7 +324,7 @@ function AgentListItem({ agent, onSelect, onMessage, showProject }: { agent: Age
|
|
|
322
324
|
{isActive && label ? (
|
|
323
325
|
<span className="text-green-400 truncate">{label}</span>
|
|
324
326
|
) : (
|
|
325
|
-
<span>{agent.provider} · {agent.status === "running" ? "idle" : "stopped"}</span>
|
|
327
|
+
<span>{isDev ? `${agent.provider} · ` : ""}{agent.status === "running" ? "idle" : "stopped"}</span>
|
|
326
328
|
)}
|
|
327
329
|
{showProject && project && (
|
|
328
330
|
<>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
import { DashboardIcon, ThreadsIcon, AgentsIcon, ActivityIcon, TasksIcon, ConnectionsIcon, McpIcon, SkillsIcon, TestsIcon, TelemetryIcon, ApiIcon, SettingsIcon, CloseIcon } from "../common/Icons";
|
|
3
|
-
import { useAuth } from "../../context";
|
|
3
|
+
import { useAuth, useUIMode } from "../../context";
|
|
4
4
|
import type { Route } from "../../types";
|
|
5
5
|
|
|
6
6
|
interface SidebarProps {
|
|
@@ -14,6 +14,7 @@ interface SidebarProps {
|
|
|
14
14
|
|
|
15
15
|
export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onClose }: SidebarProps) {
|
|
16
16
|
const { user, logout } = useAuth();
|
|
17
|
+
const { isDev, t } = useUIMode();
|
|
17
18
|
const [showUserMenu, setShowUserMenu] = useState(false);
|
|
18
19
|
|
|
19
20
|
const handleNavigate = (newRoute: Route) => {
|
|
@@ -69,7 +70,7 @@ export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onCl
|
|
|
69
70
|
/>
|
|
70
71
|
<NavButton
|
|
71
72
|
icon={<AgentsIcon />}
|
|
72
|
-
label="Agents"
|
|
73
|
+
label={t("Agents", "Employees")}
|
|
73
74
|
active={route === "agents"}
|
|
74
75
|
onClick={() => handleNavigate("agents")}
|
|
75
76
|
badge={agentCount > 0 ? String(agentCount) : undefined}
|
|
@@ -93,42 +94,52 @@ export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onCl
|
|
|
93
94
|
onClick={() => handleNavigate("tasks")}
|
|
94
95
|
badge={taskCount && taskCount > 0 ? String(taskCount) : undefined}
|
|
95
96
|
/>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
97
|
+
{isDev && (
|
|
98
|
+
<NavButton
|
|
99
|
+
icon={<McpIcon />}
|
|
100
|
+
label="MCP"
|
|
101
|
+
active={route === "mcp"}
|
|
102
|
+
onClick={() => handleNavigate("mcp")}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
{isDev && (
|
|
106
|
+
<NavButton
|
|
107
|
+
icon={<SkillsIcon />}
|
|
108
|
+
label="Skills"
|
|
109
|
+
active={route === "skills"}
|
|
110
|
+
onClick={() => handleNavigate("skills")}
|
|
111
|
+
/>
|
|
112
|
+
)}
|
|
113
|
+
{isDev && (
|
|
114
|
+
<NavButton
|
|
115
|
+
icon={<ConnectionsIcon />}
|
|
116
|
+
label="Connections"
|
|
117
|
+
active={route === "connections"}
|
|
118
|
+
onClick={() => handleNavigate("connections")}
|
|
119
|
+
/>
|
|
120
|
+
)}
|
|
121
|
+
{isDev && (
|
|
122
|
+
<NavButton
|
|
123
|
+
icon={<TestsIcon />}
|
|
124
|
+
label="Tests"
|
|
125
|
+
active={route === "tests"}
|
|
126
|
+
onClick={() => handleNavigate("tests")}
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
120
129
|
<NavButton
|
|
121
130
|
icon={<TelemetryIcon />}
|
|
122
131
|
label="Analytics"
|
|
123
132
|
active={route === "analytics"}
|
|
124
133
|
onClick={() => handleNavigate("analytics")}
|
|
125
134
|
/>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
{isDev && (
|
|
136
|
+
<NavButton
|
|
137
|
+
icon={<ApiIcon />}
|
|
138
|
+
label="API"
|
|
139
|
+
active={route === "api"}
|
|
140
|
+
onClick={() => handleNavigate("api")}
|
|
141
|
+
/>
|
|
142
|
+
)}
|
|
132
143
|
<NavButton
|
|
133
144
|
icon={<SettingsIcon />}
|
|
134
145
|
label="Settings"
|