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.
Files changed (76) hide show
  1. package/dist/ActivityPage.kxzzb4yc.js +3 -0
  2. package/dist/ApiDocsPage.zq998hbm.js +4 -0
  3. package/dist/App.55rea8mn.js +61 -0
  4. package/dist/App.5ywb23z4.js +53 -0
  5. package/dist/App.6thds120.js +4 -0
  6. package/dist/{App.ccs4g85x.js → App.9tctxzqm.js} +3 -3
  7. package/dist/App.a8r8ttaz.js +4 -0
  8. package/dist/App.agsv5bje.js +4 -0
  9. package/dist/App.cepapqmx.js +4 -0
  10. package/dist/App.dp041gb3.js +221 -0
  11. package/dist/App.fds72zb5.js +4 -0
  12. package/dist/App.fg9qj2dq.js +4 -0
  13. package/dist/App.ndfejbm9.js +4 -0
  14. package/dist/App.nxmfmq1h.js +13 -0
  15. package/dist/App.qdfyt8ba.js +4 -0
  16. package/dist/{App.g1qhcmpc.js → App.x2d0ygt6.js} +2 -2
  17. package/dist/App.yt9p4nr3.js +20 -0
  18. package/dist/{App.wghtdzsk.js → App.zn4mw16t.js} +1 -1
  19. package/dist/ConnectionsPage.8r96ryw7.js +3 -0
  20. package/dist/McpPage.3cwh0gnd.js +3 -0
  21. package/dist/SettingsPage.ykgdh5ev.js +3 -0
  22. package/dist/SkillsPage.4np1s65b.js +3 -0
  23. package/dist/TasksPage.4g08t7p6.js +3 -0
  24. package/dist/TelemetryPage.72w9pwcp.js +3 -0
  25. package/dist/TestsPage.z4fk3r7r.js +3 -0
  26. package/dist/ThreadsPage.63tcajeh.js +3 -0
  27. package/dist/apteva-kit.css +1 -1
  28. package/dist/index.html +1 -1
  29. package/dist/styles.css +1 -1
  30. package/package.json +2 -2
  31. package/src/crypto.ts +25 -4
  32. package/src/db.ts +24 -1
  33. package/src/mcp-platform.ts +198 -35
  34. package/src/providers.ts +125 -5
  35. package/src/routes/api/agent-utils.ts +105 -8
  36. package/src/routes/api/providers.ts +64 -0
  37. package/src/routes/share.ts +3 -2
  38. package/src/server.ts +53 -7
  39. package/src/test-runner.ts +1 -1
  40. package/src/web/App.tsx +37 -22
  41. package/src/web/components/agents/AgentCard.tsx +12 -9
  42. package/src/web/components/agents/AgentPanel.tsx +126 -7
  43. package/src/web/components/agents/AgentsView.tsx +30 -8
  44. package/src/web/components/agents/CreateAgentModal.tsx +155 -5
  45. package/src/web/components/dashboard/Dashboard.tsx +9 -7
  46. package/src/web/components/layout/Sidebar.tsx +43 -32
  47. package/src/web/components/meta-agent/MetaAgent.tsx +6 -2
  48. package/src/web/components/settings/SettingsPage.tsx +172 -43
  49. package/src/web/components/telemetry/TelemetryPage.tsx +41 -36
  50. package/src/web/components/tests/TestsPage.tsx +91 -76
  51. package/src/web/context/UIModeContext.tsx +49 -0
  52. package/src/web/context/index.ts +3 -0
  53. package/src/web/types.ts +67 -3
  54. package/dist/ActivityPage.k33hj12v.js +0 -3
  55. package/dist/ApiDocsPage.q37747gr.js +0 -4
  56. package/dist/App.3hp80jc2.js +0 -53
  57. package/dist/App.5ebcd85d.js +0 -4
  58. package/dist/App.6fefs2d5.js +0 -4
  59. package/dist/App.794kjn6a.js +0 -4
  60. package/dist/App.c5ebgvec.js +0 -61
  61. package/dist/App.cb1np6f0.js +0 -20
  62. package/dist/App.jemr4v3a.js +0 -221
  63. package/dist/App.kpyf0grm.js +0 -4
  64. package/dist/App.p7zc1bv2.js +0 -13
  65. package/dist/App.qx4wdtjg.js +0 -4
  66. package/dist/App.wjxmwjrp.js +0 -4
  67. package/dist/App.wq1f2jke.js +0 -4
  68. package/dist/App.zx6x0gk2.js +0 -4
  69. package/dist/ConnectionsPage.8b2v07qp.js +0 -3
  70. package/dist/McpPage.3800a82c.js +0 -3
  71. package/dist/SettingsPage.88nj3hbv.js +0 -3
  72. package/dist/SkillsPage.b8pxj5mb.js +0 -3
  73. package/dist/TasksPage.6b3y4b1n.js +0 -3
  74. package/dist/TelemetryPage.7q4d69wj.js +0 -3
  75. package/dist/TestsPage.dpevv5xb.js +0 -3
  76. 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
- I can help you navigate Apteva, create agents, set up MCP servers, and more.
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 isUrlBased = isOllama || isCDP;
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 [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number; isDocker?: boolean } | null>(null);
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 Ollama status when configured or after install
1041
- const checkOllamaStatus = React.useCallback(() => {
1042
- providerAuthFetch("/api/providers/ollama/status")
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 => setOllamaStatus({ connected: data.connected, modelCount: data.modelCount, isDocker: data.isDocker }))
1045
- .catch(() => setOllamaStatus({ connected: false }));
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 (isOllama) {
1050
- checkOllamaStatus();
1166
+ if (isLocal) {
1167
+ checkLocalStatus();
1051
1168
  }
1052
- }, [isOllama, provider.hasKey, checkOllamaStatus]);
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
- // Auto-save the default URL and refresh status
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
- {isBrowser
1083
- ? (provider.description || "Browser automation")
1084
- : provider.type === "integration"
1085
- ? (provider.description || "MCP integration")
1086
- : isOllama
1087
- ? "Run models locally"
1088
- : `${provider.models.length} models`}
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
- isOllama && ollamaStatus
1094
- ? ollamaStatus.connected
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
- {isOllama && ollamaStatus ? (
1100
- ollamaStatus.connected ? (
1101
- <><CheckIcon className="w-3 h-3" />{ollamaStatus.modelCount} models</>
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={isOllama
1151
- ? "http://localhost:11434"
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
- : "Enter your Ollama server URL. Default is http://localhost:11434"}
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
- {isOllama && ollamaStatus && !ollamaStatus.connected && !ollamaStatus.isDocker && (
1308
+ {isLocal && localStatus && !localStatus.connected && (
1186
1309
  <div className="mb-3">
1187
- <button
1188
- onClick={handleInstallOllama}
1189
- disabled={installing}
1190
- 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"
1191
- >
1192
- {installing ? "Starting Ollama..." : "Start Ollama"}
1193
- </button>
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&apos;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
- {isOllama ? "Ollama docs" : "View docs"}
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 && !ollamaStatus?.isDocker && (
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..." : ollamaStatus?.connected ? "Ollama Running" : "Install 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
- {isOllama ? "Manual install" : isBrowser ? "View docs" : "Get API key"}
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.id === "cdp" ? "ws://localhost:9222" : "http://localhost:11434")
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
- <StatCard label="Input Tokens" value={formatNumber(stats.total_input_tokens)} />
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="Cache Write" value={formatNumber(stats.total_cache_creation_tokens)} />
628
- <StatCard label="Cache Read" value={formatNumber(stats.total_cache_read_tokens)} />
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">