apteva 0.4.54 → 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.ccs4g85x.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.g1qhcmpc.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 +198 -35
- 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/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 +41 -36
- package/src/web/components/tests/TestsPage.tsx +91 -76
- 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.k33hj12v.js +0 -3
- package/dist/ApiDocsPage.q37747gr.js +0 -4
- package/dist/App.3hp80jc2.js +0 -53
- package/dist/App.5ebcd85d.js +0 -4
- package/dist/App.6fefs2d5.js +0 -4
- package/dist/App.794kjn6a.js +0 -4
- package/dist/App.c5ebgvec.js +0 -61
- package/dist/App.cb1np6f0.js +0 -20
- package/dist/App.jemr4v3a.js +0 -221
- package/dist/App.kpyf0grm.js +0 -4
- package/dist/App.p7zc1bv2.js +0 -13
- package/dist/App.qx4wdtjg.js +0 -4
- package/dist/App.wjxmwjrp.js +0 -4
- package/dist/App.wq1f2jke.js +0 -4
- package/dist/App.zx6x0gk2.js +0 -4
- package/dist/ConnectionsPage.8b2v07qp.js +0 -3
- package/dist/McpPage.3800a82c.js +0 -3
- package/dist/SettingsPage.88nj3hbv.js +0 -3
- package/dist/SkillsPage.b8pxj5mb.js +0 -3
- package/dist/TasksPage.6b3y4b1n.js +0 -3
- package/dist/TelemetryPage.7q4d69wj.js +0 -3
- package/dist/TestsPage.dpevv5xb.js +0 -3
- package/dist/ThreadsPage.1h15363y.js +0 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo, useCallback, createContext, useContext, type ReactNode } from "react";
|
|
2
2
|
import { Chat } from "@apteva/apteva-kit";
|
|
3
|
-
import { useAuth, useProjects, useTriggerRefresh, useTheme } from "../../context";
|
|
3
|
+
import { useAuth, useProjects, useTriggerRefresh, useTheme, useUIMode } from "../../context";
|
|
4
4
|
|
|
5
5
|
interface MetaAgentStatus {
|
|
6
6
|
enabled: boolean;
|
|
@@ -129,6 +129,7 @@ export function MetaAgentButton() {
|
|
|
129
129
|
// Chat panel component - renders as a right-side drawer
|
|
130
130
|
export function MetaAgentPanel() {
|
|
131
131
|
const { theme } = useTheme();
|
|
132
|
+
const { t } = useUIMode();
|
|
132
133
|
const ctx = useMetaAgent();
|
|
133
134
|
const { currentProjectId, currentProject } = useProjects();
|
|
134
135
|
const triggerRefresh = useTriggerRefresh();
|
|
@@ -191,7 +192,10 @@ export function MetaAgentPanel() {
|
|
|
191
192
|
<AssistantIcon className="w-12 h-12 text-[var(--color-border-light)] mb-4" />
|
|
192
193
|
<h3 className="font-medium mb-2">Apteva Assistant</h3>
|
|
193
194
|
<p className="text-sm text-[var(--color-text-muted)] mb-6">
|
|
194
|
-
|
|
195
|
+
{t(
|
|
196
|
+
"I can help you navigate Apteva, create agents, set up MCP servers, and more.",
|
|
197
|
+
"I can help you hire employees, assign them work, and manage your team."
|
|
198
|
+
)}
|
|
195
199
|
</p>
|
|
196
200
|
{error && (
|
|
197
201
|
<p className="text-sm text-red-400 mb-4">{error}</p>
|
|
@@ -2,16 +2,20 @@ import React, { useState, useEffect } from "react";
|
|
|
2
2
|
import { CheckIcon, CloseIcon, PlusIcon } from "../common/Icons";
|
|
3
3
|
import { Modal, useConfirm } from "../common/Modal";
|
|
4
4
|
import { Select } from "../common/Select";
|
|
5
|
-
import { useProjects, useAuth, useTheme, type Project } from "../../context";
|
|
5
|
+
import { useProjects, useAuth, useTheme, useUIMode, type Project } from "../../context";
|
|
6
6
|
import type { ThemeMode, ThemeStyle } from "../../themes";
|
|
7
7
|
import type { Provider } from "../../types";
|
|
8
|
+
import type { UIMode } from "../../context";
|
|
8
9
|
|
|
9
10
|
type SettingsTab = "general" | "providers" | "projects" | "channels" | "api-keys" | "account" | "updates" | "data" | "assistant";
|
|
10
11
|
|
|
11
12
|
export function SettingsPage() {
|
|
12
13
|
const { projectsEnabled, metaAgentEnabled } = useProjects();
|
|
14
|
+
const { isBusiness } = useUIMode();
|
|
13
15
|
const [activeTab, setActiveTab] = useState<SettingsTab>("general");
|
|
14
16
|
|
|
17
|
+
const hiddenInBusiness: SettingsTab[] = ["providers", "api-keys", "data"];
|
|
18
|
+
|
|
15
19
|
const tabs: { key: SettingsTab; label: string }[] = [
|
|
16
20
|
{ key: "general", label: "General" },
|
|
17
21
|
{ key: "providers", label: "Providers" },
|
|
@@ -22,7 +26,7 @@ export function SettingsPage() {
|
|
|
22
26
|
{ key: "account", label: "Account" },
|
|
23
27
|
{ key: "updates", label: "Updates" },
|
|
24
28
|
{ key: "data", label: "Data" },
|
|
25
|
-
];
|
|
29
|
+
].filter(tab => !isBusiness || !hiddenInBusiness.includes(tab.key));
|
|
26
30
|
|
|
27
31
|
return (
|
|
28
32
|
<div className="flex-1 flex flex-col md:flex-row overflow-hidden">
|
|
@@ -102,6 +106,7 @@ function SettingsNavItem({
|
|
|
102
106
|
function GeneralSettings() {
|
|
103
107
|
const { authFetch } = useAuth();
|
|
104
108
|
const { mode, style, setMode, setStyle } = useTheme();
|
|
109
|
+
const { mode: uiMode, setMode: setUIMode } = useUIMode();
|
|
105
110
|
const [instanceUrl, setInstanceUrl] = useState("");
|
|
106
111
|
const [loading, setLoading] = useState(true);
|
|
107
112
|
const [saving, setSaving] = useState(false);
|
|
@@ -220,6 +225,39 @@ function GeneralSettings() {
|
|
|
220
225
|
</div>
|
|
221
226
|
</div>
|
|
222
227
|
|
|
228
|
+
{/* UI Mode */}
|
|
229
|
+
<div className="bg-[var(--color-surface)] card p-4 mb-4">
|
|
230
|
+
<h3 className="font-medium mb-2">UI Mode</h3>
|
|
231
|
+
<p className="text-sm text-[var(--color-text-muted)] mb-4">Switch between developer and business views.</p>
|
|
232
|
+
<div className="flex gap-3">
|
|
233
|
+
{([
|
|
234
|
+
{ value: "developer" as UIMode, label: "Developer", description: "Full control over agents, providers, MCP, and configuration" },
|
|
235
|
+
{ value: "business" as UIMode, label: "Business", description: "Simplified view focused on employees and conversations" },
|
|
236
|
+
]).map(opt => (
|
|
237
|
+
<button
|
|
238
|
+
key={opt.value}
|
|
239
|
+
onClick={() => setUIMode(opt.value)}
|
|
240
|
+
className={`flex-1 max-w-[240px] px-4 py-3 border text-left transition ${
|
|
241
|
+
uiMode === opt.value
|
|
242
|
+
? "border-[var(--color-accent)] bg-[var(--color-accent-10)]"
|
|
243
|
+
: "border-[var(--color-border-light)] bg-[var(--color-bg)] hover:border-[var(--color-scrollbar)]"
|
|
244
|
+
}`}
|
|
245
|
+
style={{ borderRadius: "var(--radius-card)" }}
|
|
246
|
+
>
|
|
247
|
+
<div className="flex items-center gap-2 mb-1">
|
|
248
|
+
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
|
|
249
|
+
uiMode === opt.value ? "border-[var(--color-accent)]" : "border-[var(--color-scrollbar)]"
|
|
250
|
+
}`}>
|
|
251
|
+
{uiMode === opt.value && <div className="w-2 h-2 rounded-full bg-[var(--color-accent)]" />}
|
|
252
|
+
</div>
|
|
253
|
+
<span className="text-sm font-medium">{opt.label}</span>
|
|
254
|
+
</div>
|
|
255
|
+
<p className="text-xs text-[var(--color-text-muted)] ml-6">{opt.description}</p>
|
|
256
|
+
</button>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
223
261
|
<div className="bg-[var(--color-surface)] card p-4">
|
|
224
262
|
<h3 className="font-medium mb-2">Instance URL</h3>
|
|
225
263
|
<p className="text-sm text-[var(--color-text-muted)] mb-4">
|
|
@@ -354,9 +392,13 @@ function ProvidersSettings() {
|
|
|
354
392
|
};
|
|
355
393
|
|
|
356
394
|
const llmProviders = providers.filter(p => p.type === "llm");
|
|
395
|
+
const cloudVoiceProviders = providers.filter(p => p.type === "voice" && !p.isLocal);
|
|
396
|
+
const localVoiceProviders = providers.filter(p => p.type === "voice" && p.isLocal);
|
|
397
|
+
const voiceProviders = providers.filter(p => p.type === "voice");
|
|
357
398
|
const integrations = providers.filter(p => p.type === "integration");
|
|
358
399
|
const browserProviders = providers.filter(p => p.type === "browser");
|
|
359
400
|
const llmConfiguredCount = llmProviders.filter(p => p.hasKey).length;
|
|
401
|
+
const voiceConfiguredCount = voiceProviders.filter(p => p.hasKey).length;
|
|
360
402
|
const intConfiguredCount = integrations.filter(p => p.hasKey).length;
|
|
361
403
|
const browserConfiguredCount = browserProviders.filter(p => p.hasKey).length;
|
|
362
404
|
|
|
@@ -426,6 +468,79 @@ function ProvidersSettings() {
|
|
|
426
468
|
</div>
|
|
427
469
|
</div>
|
|
428
470
|
|
|
471
|
+
{/* Voice Providers Section */}
|
|
472
|
+
<div>
|
|
473
|
+
<div className="mb-6">
|
|
474
|
+
<h2 className="text-xl font-semibold mb-1">Voice Providers</h2>
|
|
475
|
+
<p className="text-[var(--color-text-muted)]">
|
|
476
|
+
Configure voice providers for real-time voice conversations. {voiceConfiguredCount} of {voiceProviders.length} configured.
|
|
477
|
+
</p>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
{/* Cloud Voice Providers */}
|
|
481
|
+
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-3 uppercase tracking-wider">Cloud</h3>
|
|
482
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 mb-6">
|
|
483
|
+
{cloudVoiceProviders.map(provider => (
|
|
484
|
+
<IntegrationKeyCard
|
|
485
|
+
key={provider.id}
|
|
486
|
+
provider={provider}
|
|
487
|
+
isEditing={selectedProvider === provider.id}
|
|
488
|
+
apiKey={apiKey}
|
|
489
|
+
saving={saving}
|
|
490
|
+
testing={testing}
|
|
491
|
+
error={selectedProvider === provider.id ? error : null}
|
|
492
|
+
success={selectedProvider === provider.id ? success : null}
|
|
493
|
+
onStartEdit={() => {
|
|
494
|
+
setSelectedProvider(provider.id);
|
|
495
|
+
setError(null);
|
|
496
|
+
setSuccess(null);
|
|
497
|
+
}}
|
|
498
|
+
onCancelEdit={() => {
|
|
499
|
+
setSelectedProvider(null);
|
|
500
|
+
setApiKey("");
|
|
501
|
+
setError(null);
|
|
502
|
+
}}
|
|
503
|
+
onApiKeyChange={setApiKey}
|
|
504
|
+
onSave={saveKey}
|
|
505
|
+
onDelete={() => deleteKey(provider.id)}
|
|
506
|
+
projectsEnabled={projectsEnabled}
|
|
507
|
+
projects={projects}
|
|
508
|
+
onRefresh={fetchProviders}
|
|
509
|
+
/>
|
|
510
|
+
))}
|
|
511
|
+
</div>
|
|
512
|
+
|
|
513
|
+
{/* Local Voice Providers */}
|
|
514
|
+
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-3 uppercase tracking-wider">Local</h3>
|
|
515
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
516
|
+
{localVoiceProviders.map(provider => (
|
|
517
|
+
<ProviderKeyCard
|
|
518
|
+
key={provider.id}
|
|
519
|
+
provider={provider}
|
|
520
|
+
isEditing={selectedProvider === provider.id}
|
|
521
|
+
apiKey={apiKey}
|
|
522
|
+
saving={saving}
|
|
523
|
+
testing={testing}
|
|
524
|
+
error={selectedProvider === provider.id ? error : null}
|
|
525
|
+
success={selectedProvider === provider.id ? success : null}
|
|
526
|
+
onStartEdit={() => {
|
|
527
|
+
setSelectedProvider(provider.id);
|
|
528
|
+
setError(null);
|
|
529
|
+
setSuccess(null);
|
|
530
|
+
}}
|
|
531
|
+
onCancelEdit={() => {
|
|
532
|
+
setSelectedProvider(null);
|
|
533
|
+
setApiKey("");
|
|
534
|
+
setError(null);
|
|
535
|
+
}}
|
|
536
|
+
onApiKeyChange={setApiKey}
|
|
537
|
+
onSave={saveKey}
|
|
538
|
+
onDelete={() => deleteKey(provider.id)}
|
|
539
|
+
/>
|
|
540
|
+
))}
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
|
|
429
544
|
{/* MCP Integrations Section */}
|
|
430
545
|
<div>
|
|
431
546
|
<div className="mb-6">
|
|
@@ -1030,26 +1145,28 @@ function ProviderKeyCard({
|
|
|
1030
1145
|
const { authFetch: providerAuthFetch } = useAuth();
|
|
1031
1146
|
const isOllama = provider.id === "ollama";
|
|
1032
1147
|
const isCDP = provider.id === "cdp";
|
|
1033
|
-
const
|
|
1148
|
+
const isLocal = provider.isLocal || false;
|
|
1149
|
+
const isUrlBased = isLocal || isCDP;
|
|
1034
1150
|
const isBrowser = provider.type === "browser";
|
|
1035
1151
|
const isMultiField = provider.id === "browserbase";
|
|
1036
|
-
const
|
|
1152
|
+
const voiceSubtype = (provider as any).voiceSubtype as string | undefined;
|
|
1153
|
+
const [localStatus, setLocalStatus] = React.useState<{ connected: boolean; modelCount?: number; isDocker?: boolean } | null>(null);
|
|
1037
1154
|
const [installing, setInstalling] = React.useState(false);
|
|
1038
1155
|
const [installResult, setInstallResult] = React.useState<{ success: boolean; message: string } | null>(null);
|
|
1039
1156
|
|
|
1040
|
-
// Check
|
|
1041
|
-
const
|
|
1042
|
-
providerAuthFetch(
|
|
1157
|
+
// Check status for local providers (Ollama + local voice providers)
|
|
1158
|
+
const checkLocalStatus = React.useCallback(() => {
|
|
1159
|
+
providerAuthFetch(`/api/providers/${provider.id}/status`)
|
|
1043
1160
|
.then(res => res.json())
|
|
1044
|
-
.then(data =>
|
|
1045
|
-
.catch(() =>
|
|
1046
|
-
}, [providerAuthFetch]);
|
|
1161
|
+
.then(data => setLocalStatus({ connected: data.connected, modelCount: data.modelCount, isDocker: data.isDocker }))
|
|
1162
|
+
.catch(() => setLocalStatus({ connected: false }));
|
|
1163
|
+
}, [providerAuthFetch, provider.id]);
|
|
1047
1164
|
|
|
1048
1165
|
React.useEffect(() => {
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1166
|
+
if (isLocal) {
|
|
1167
|
+
checkLocalStatus();
|
|
1051
1168
|
}
|
|
1052
|
-
}, [
|
|
1169
|
+
}, [isLocal, provider.hasKey, checkLocalStatus]);
|
|
1053
1170
|
|
|
1054
1171
|
const handleInstallOllama = async () => {
|
|
1055
1172
|
setInstalling(true);
|
|
@@ -1059,8 +1176,7 @@ function ProviderKeyCard({
|
|
|
1059
1176
|
const data = await res.json();
|
|
1060
1177
|
if (data.success) {
|
|
1061
1178
|
setInstallResult({ success: true, message: data.message });
|
|
1062
|
-
|
|
1063
|
-
checkOllamaStatus();
|
|
1179
|
+
checkLocalStatus();
|
|
1064
1180
|
} else {
|
|
1065
1181
|
setInstallResult({ success: false, message: data.error || "Installation failed" });
|
|
1066
1182
|
}
|
|
@@ -1079,26 +1195,33 @@ function ProviderKeyCard({
|
|
|
1079
1195
|
<div className="min-w-0">
|
|
1080
1196
|
<h3 className="font-medium">{provider.name}</h3>
|
|
1081
1197
|
<p className="text-sm text-[var(--color-text-muted)] truncate">
|
|
1082
|
-
{
|
|
1083
|
-
?
|
|
1084
|
-
:
|
|
1085
|
-
?
|
|
1086
|
-
:
|
|
1087
|
-
? "
|
|
1088
|
-
:
|
|
1198
|
+
{provider.description
|
|
1199
|
+
? provider.description
|
|
1200
|
+
: isBrowser
|
|
1201
|
+
? "Browser automation"
|
|
1202
|
+
: provider.type === "integration"
|
|
1203
|
+
? "MCP integration"
|
|
1204
|
+
: isLocal
|
|
1205
|
+
? "Run locally"
|
|
1206
|
+
: `${provider.models.length} models`}
|
|
1089
1207
|
</p>
|
|
1208
|
+
{voiceSubtype && (
|
|
1209
|
+
<span className="text-[10px] uppercase tracking-wider text-[var(--color-text-muted)] bg-[var(--color-surface-raised)] px-1.5 py-0.5 rounded mt-1 inline-block">
|
|
1210
|
+
{voiceSubtype === "both" ? "STT + TTS" : voiceSubtype === "stt" ? "STT" : "TTS"}
|
|
1211
|
+
</span>
|
|
1212
|
+
)}
|
|
1090
1213
|
</div>
|
|
1091
1214
|
{provider.hasKey ? (
|
|
1092
1215
|
<span className={`text-xs flex items-center gap-1 px-2 py-1 rounded whitespace-nowrap flex-shrink-0 ${
|
|
1093
|
-
|
|
1094
|
-
?
|
|
1216
|
+
isLocal && localStatus
|
|
1217
|
+
? localStatus.connected
|
|
1095
1218
|
? "text-green-400 bg-green-500/10"
|
|
1096
1219
|
: "text-yellow-400 bg-yellow-500/10"
|
|
1097
1220
|
: "text-green-400 bg-green-500/10"
|
|
1098
1221
|
}`}>
|
|
1099
|
-
{
|
|
1100
|
-
|
|
1101
|
-
<><CheckIcon className="w-3 h-3" />{
|
|
1222
|
+
{isLocal && localStatus ? (
|
|
1223
|
+
localStatus.connected ? (
|
|
1224
|
+
<><CheckIcon className="w-3 h-3" />{localStatus.modelCount ? `${localStatus.modelCount} models` : "Connected"}</>
|
|
1102
1225
|
) : (
|
|
1103
1226
|
<>Not running</>
|
|
1104
1227
|
)
|
|
@@ -1147,8 +1270,8 @@ function ProviderKeyCard({
|
|
|
1147
1270
|
type={isUrlBased ? "text" : "password"}
|
|
1148
1271
|
value={apiKey}
|
|
1149
1272
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
1150
|
-
placeholder={
|
|
1151
|
-
? "http://localhost:
|
|
1273
|
+
placeholder={isLocal
|
|
1274
|
+
? (provider.defaultBaseUrl || "http://localhost:8080")
|
|
1152
1275
|
: isCDP ? "ws://localhost:9222"
|
|
1153
1276
|
: provider.hasKey ? "Enter new API key..." : "Enter API key..."}
|
|
1154
1277
|
autoFocus
|
|
@@ -1159,7 +1282,7 @@ function ProviderKeyCard({
|
|
|
1159
1282
|
<p className="text-xs text-[var(--color-text-muted)]">
|
|
1160
1283
|
{isCDP
|
|
1161
1284
|
? "Enter the CDP URL of your browser (e.g., ws://localhost:9222)"
|
|
1162
|
-
:
|
|
1285
|
+
: `Enter the server URL. Default is ${provider.defaultBaseUrl || "http://localhost:8080"}`}
|
|
1163
1286
|
</p>
|
|
1164
1287
|
)}
|
|
1165
1288
|
{error && <p className="text-red-400 text-sm">{error}</p>}
|
|
@@ -1182,15 +1305,21 @@ function ProviderKeyCard({
|
|
|
1182
1305
|
</div>
|
|
1183
1306
|
) : provider.hasKey ? (
|
|
1184
1307
|
<div>
|
|
1185
|
-
{
|
|
1308
|
+
{isLocal && localStatus && !localStatus.connected && (
|
|
1186
1309
|
<div className="mb-3">
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1310
|
+
{isOllama && !localStatus.isDocker ? (
|
|
1311
|
+
<button
|
|
1312
|
+
onClick={handleInstallOllama}
|
|
1313
|
+
disabled={installing}
|
|
1314
|
+
className="w-full px-3 py-1.5 bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30 rounded text-sm font-medium transition disabled:opacity-50 disabled:cursor-wait"
|
|
1315
|
+
>
|
|
1316
|
+
{installing ? "Starting Ollama..." : "Start Ollama"}
|
|
1317
|
+
</button>
|
|
1318
|
+
) : (
|
|
1319
|
+
<p className="text-xs text-yellow-400/80">
|
|
1320
|
+
Service not reachable. Make sure it's running at the configured URL.
|
|
1321
|
+
</p>
|
|
1322
|
+
)}
|
|
1194
1323
|
{installResult && (
|
|
1195
1324
|
<p className={`text-xs mt-1.5 ${installResult.success ? "text-green-400" : "text-red-400"}`}>
|
|
1196
1325
|
{installResult.message}
|
|
@@ -1206,7 +1335,7 @@ function ProviderKeyCard({
|
|
|
1206
1335
|
rel="noopener noreferrer"
|
|
1207
1336
|
className="text-sm text-[#3b82f6] hover:underline"
|
|
1208
1337
|
>
|
|
1209
|
-
{
|
|
1338
|
+
{isLocal ? "Setup guide" : "View docs"}
|
|
1210
1339
|
</a>
|
|
1211
1340
|
) : (
|
|
1212
1341
|
<span />
|
|
@@ -1229,14 +1358,14 @@ function ProviderKeyCard({
|
|
|
1229
1358
|
</div>
|
|
1230
1359
|
) : (
|
|
1231
1360
|
<div>
|
|
1232
|
-
{isOllama && !
|
|
1361
|
+
{isOllama && !localStatus?.isDocker && (
|
|
1233
1362
|
<div className="mb-3">
|
|
1234
1363
|
<button
|
|
1235
1364
|
onClick={handleInstallOllama}
|
|
1236
1365
|
disabled={installing}
|
|
1237
1366
|
className="w-full px-3 py-2 bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30 rounded text-sm font-medium transition disabled:opacity-50 disabled:cursor-wait"
|
|
1238
1367
|
>
|
|
1239
|
-
{installing ? "Installing Ollama..." :
|
|
1368
|
+
{installing ? "Installing Ollama..." : localStatus?.connected ? "Ollama Running" : "Install Ollama"}
|
|
1240
1369
|
</button>
|
|
1241
1370
|
{installResult && (
|
|
1242
1371
|
<p className={`text-xs mt-1.5 ${installResult.success ? "text-green-400" : "text-red-400"}`}>
|
|
@@ -1253,7 +1382,7 @@ function ProviderKeyCard({
|
|
|
1253
1382
|
rel="noopener noreferrer"
|
|
1254
1383
|
className="text-sm text-[#3b82f6] hover:underline"
|
|
1255
1384
|
>
|
|
1256
|
-
{
|
|
1385
|
+
{isLocal ? "Setup guide" : isBrowser ? "View docs" : "Get API key"}
|
|
1257
1386
|
</a>
|
|
1258
1387
|
) : (
|
|
1259
1388
|
<span />
|
|
@@ -1506,7 +1635,7 @@ function IntegrationKeyCard({
|
|
|
1506
1635
|
const isUrlBased = provider.isLocal;
|
|
1507
1636
|
const inputType = isUrlBased ? "text" : "password";
|
|
1508
1637
|
const inputPlaceholder = isUrlBased
|
|
1509
|
-
? (provider.
|
|
1638
|
+
? (provider.defaultBaseUrl || "http://localhost:8080")
|
|
1510
1639
|
: "Enter API key...";
|
|
1511
1640
|
|
|
1512
1641
|
// Enhanced view with project support
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo, useRef, useCallback } from "react";
|
|
2
2
|
import { Select } from "../common/Select";
|
|
3
|
-
import { useTelemetryContext, useProjects, useAuth, type TelemetryEvent } from "../../context";
|
|
3
|
+
import { useTelemetryContext, useProjects, useAuth, useUIMode, type TelemetryEvent } from "../../context";
|
|
4
4
|
import {
|
|
5
5
|
AreaChart, Area, BarChart, Bar,
|
|
6
6
|
XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
|
|
@@ -97,6 +97,7 @@ export function TelemetryPage() {
|
|
|
97
97
|
const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
|
|
98
98
|
const { currentProjectId, currentProject, costTrackingEnabled, projectsEnabled, projects } = useProjects();
|
|
99
99
|
const { authFetch } = useAuth();
|
|
100
|
+
const { isDev, isBusiness, t } = useUIMode();
|
|
100
101
|
const [fetchedStats, setFetchedStats] = useState<TelemetryStats | null>(null);
|
|
101
102
|
const [historicalEvents, setHistoricalEvents] = useState<TelemetryEvent[]>([]);
|
|
102
103
|
const [fetchedUsage, setFetchedUsage] = useState<UsageByAgent[]>([]);
|
|
@@ -618,19 +619,23 @@ export function TelemetryPage() {
|
|
|
618
619
|
<div className="flex flex-wrap gap-4 mb-6">
|
|
619
620
|
<StatCard label="Events" value={formatNumber(stats.total_events)} />
|
|
620
621
|
<StatCard label="LLM Calls" value={formatNumber(stats.total_llm_calls)} />
|
|
621
|
-
<StatCard label="Tool Calls" value={formatNumber(stats.total_tool_calls)} />
|
|
622
|
+
{isDev && <StatCard label="Tool Calls" value={formatNumber(stats.total_tool_calls)} />}
|
|
622
623
|
<StatCard label="Errors" value={formatNumber(stats.total_errors)} color="red" />
|
|
623
|
-
|
|
624
|
-
<StatCard label="Output Tokens" value={formatNumber(stats.total_output_tokens)} />
|
|
625
|
-
{(stats.total_cache_creation_tokens > 0 || stats.total_cache_read_tokens > 0) && (
|
|
624
|
+
{isDev && (
|
|
626
625
|
<>
|
|
627
|
-
<StatCard label="
|
|
628
|
-
<StatCard label="
|
|
626
|
+
<StatCard label="Input Tokens" value={formatNumber(stats.total_input_tokens)} />
|
|
627
|
+
<StatCard label="Output Tokens" value={formatNumber(stats.total_output_tokens)} />
|
|
628
|
+
{(stats.total_cache_creation_tokens > 0 || stats.total_cache_read_tokens > 0) && (
|
|
629
|
+
<>
|
|
630
|
+
<StatCard label="Cache Write" value={formatNumber(stats.total_cache_creation_tokens)} />
|
|
631
|
+
<StatCard label="Cache Read" value={formatNumber(stats.total_cache_read_tokens)} />
|
|
632
|
+
</>
|
|
633
|
+
)}
|
|
634
|
+
{stats.total_reasoning_tokens > 0 && (
|
|
635
|
+
<StatCard label="Reasoning" value={formatNumber(stats.total_reasoning_tokens)} />
|
|
636
|
+
)}
|
|
629
637
|
</>
|
|
630
638
|
)}
|
|
631
|
-
{stats.total_reasoning_tokens > 0 && (
|
|
632
|
-
<StatCard label="Reasoning" value={formatNumber(stats.total_reasoning_tokens)} />
|
|
633
|
-
)}
|
|
634
639
|
{costTrackingEnabled && (
|
|
635
640
|
<StatCard label="Total Cost" value={`$${stats.total_cost.toFixed(4)}`} color="orange" />
|
|
636
641
|
)}
|
|
@@ -830,19 +835,19 @@ export function TelemetryPage() {
|
|
|
830
835
|
|
|
831
836
|
return (
|
|
832
837
|
<div className="mb-6">
|
|
833
|
-
<h2 className="text-lg font-medium mb-3">Usage by Agent</h2>
|
|
838
|
+
<h2 className="text-lg font-medium mb-3">{t("Usage by Agent", "Usage by Employee")}</h2>
|
|
834
839
|
<div className="bg-[var(--color-surface)] card overflow-hidden">
|
|
835
840
|
<table className="w-full text-sm">
|
|
836
841
|
<thead>
|
|
837
842
|
<tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
|
|
838
|
-
<SortHeader label="Agent" field="agent" align="left" />
|
|
843
|
+
<SortHeader label={t("Agent", "Employee")} field="agent" align="left" />
|
|
839
844
|
<SortHeader label="LLM Calls" field="llm_calls" />
|
|
840
|
-
<SortHeader label="Tool Calls" field="tool_calls" />
|
|
841
|
-
<SortHeader label="Input Tokens" field="input_tokens" />
|
|
842
|
-
<SortHeader label="Output Tokens" field="output_tokens" />
|
|
843
|
-
{hasCacheTokens && <SortHeader label="Cache Write" field="cache_creation_tokens" />}
|
|
844
|
-
{hasCacheTokens && <SortHeader label="Cache Read" field="cache_read_tokens" />}
|
|
845
|
-
{hasReasoningTokens && <SortHeader label="Reasoning" field="reasoning_tokens" />}
|
|
845
|
+
{isDev && <SortHeader label="Tool Calls" field="tool_calls" />}
|
|
846
|
+
{isDev && <SortHeader label="Input Tokens" field="input_tokens" />}
|
|
847
|
+
{isDev && <SortHeader label="Output Tokens" field="output_tokens" />}
|
|
848
|
+
{isDev && hasCacheTokens && <SortHeader label="Cache Write" field="cache_creation_tokens" />}
|
|
849
|
+
{isDev && hasCacheTokens && <SortHeader label="Cache Read" field="cache_read_tokens" />}
|
|
850
|
+
{isDev && hasReasoningTokens && <SortHeader label="Reasoning" field="reasoning_tokens" />}
|
|
846
851
|
<SortHeader label="Errors" field="errors" />
|
|
847
852
|
{costTrackingEnabled && <SortHeader label="Est. Cost" field="cost" />}
|
|
848
853
|
</tr>
|
|
@@ -852,16 +857,16 @@ export function TelemetryPage() {
|
|
|
852
857
|
<tr key={u.agent_id} className="border-b border-[var(--color-border)] last:border-0 hover:bg-[var(--color-bg)]">
|
|
853
858
|
<td className="p-3 font-medium">{getAgentName(u.agent_id)}</td>
|
|
854
859
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.llm_calls)}</td>
|
|
855
|
-
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
|
|
856
|
-
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
|
|
857
|
-
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>
|
|
858
|
-
{hasCacheTokens && (
|
|
860
|
+
{isDev && <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>}
|
|
861
|
+
{isDev && <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>}
|
|
862
|
+
{isDev && <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>}
|
|
863
|
+
{isDev && hasCacheTokens && (
|
|
859
864
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_creation_tokens || 0)}</td>
|
|
860
865
|
)}
|
|
861
|
-
{hasCacheTokens && (
|
|
866
|
+
{isDev && hasCacheTokens && (
|
|
862
867
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_read_tokens || 0)}</td>
|
|
863
868
|
)}
|
|
864
|
-
{hasReasoningTokens && (
|
|
869
|
+
{isDev && hasReasoningTokens && (
|
|
865
870
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.reasoning_tokens || 0)}</td>
|
|
866
871
|
)}
|
|
867
872
|
<td className="p-3 text-right">
|
|
@@ -924,12 +929,12 @@ export function TelemetryPage() {
|
|
|
924
929
|
<tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
|
|
925
930
|
<PSortHeader label="Project" field="project" align="left" />
|
|
926
931
|
<PSortHeader label="LLM Calls" field="llm_calls" />
|
|
927
|
-
<PSortHeader label="Tool Calls" field="tool_calls" />
|
|
928
|
-
<PSortHeader label="Input Tokens" field="input_tokens" />
|
|
929
|
-
<PSortHeader label="Output Tokens" field="output_tokens" />
|
|
930
|
-
{hasProjCacheTokens && <PSortHeader label="Cache Write" field="cache_creation_tokens" />}
|
|
931
|
-
{hasProjCacheTokens && <PSortHeader label="Cache Read" field="cache_read_tokens" />}
|
|
932
|
-
{hasProjReasoningTokens && <PSortHeader label="Reasoning" field="reasoning_tokens" />}
|
|
932
|
+
{isDev && <PSortHeader label="Tool Calls" field="tool_calls" />}
|
|
933
|
+
{isDev && <PSortHeader label="Input Tokens" field="input_tokens" />}
|
|
934
|
+
{isDev && <PSortHeader label="Output Tokens" field="output_tokens" />}
|
|
935
|
+
{isDev && hasProjCacheTokens && <PSortHeader label="Cache Write" field="cache_creation_tokens" />}
|
|
936
|
+
{isDev && hasProjCacheTokens && <PSortHeader label="Cache Read" field="cache_read_tokens" />}
|
|
937
|
+
{isDev && hasProjReasoningTokens && <PSortHeader label="Reasoning" field="reasoning_tokens" />}
|
|
933
938
|
<PSortHeader label="Errors" field="errors" />
|
|
934
939
|
{costTrackingEnabled && <PSortHeader label="Est. Cost" field="cost" />}
|
|
935
940
|
</tr>
|
|
@@ -946,16 +951,16 @@ export function TelemetryPage() {
|
|
|
946
951
|
</span>
|
|
947
952
|
</td>
|
|
948
953
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.llm_calls)}</td>
|
|
949
|
-
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
|
|
950
|
-
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
|
|
951
|
-
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>
|
|
952
|
-
{hasProjCacheTokens && (
|
|
954
|
+
{isDev && <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>}
|
|
955
|
+
{isDev && <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>}
|
|
956
|
+
{isDev && <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>}
|
|
957
|
+
{isDev && hasProjCacheTokens && (
|
|
953
958
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_creation_tokens || 0)}</td>
|
|
954
959
|
)}
|
|
955
|
-
{hasProjCacheTokens && (
|
|
960
|
+
{isDev && hasProjCacheTokens && (
|
|
956
961
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_read_tokens || 0)}</td>
|
|
957
962
|
)}
|
|
958
|
-
{hasProjReasoningTokens && (
|
|
963
|
+
{isDev && hasProjReasoningTokens && (
|
|
959
964
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.reasoning_tokens || 0)}</td>
|
|
960
965
|
)}
|
|
961
966
|
<td className="p-3 text-right">
|