@auxiora/dashboard 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +195 -49
- package/dist/router.js.map +1 -1
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -4
- package/dist-ui/assets/index-BfY0i5jw.css +0 -1
- package/dist-ui/assets/index-CXpk9mvw.js +0 -60
- package/dist-ui/icon.svg +0 -59
- package/dist-ui/index.html +0 -20
- package/src/auth.ts +0 -83
- package/src/cloud-types.ts +0 -63
- package/src/index.ts +0 -5
- package/src/router.ts +0 -2494
- package/src/types.ts +0 -269
- package/tests/auth.test.ts +0 -51
- package/tests/cloud-router.test.ts +0 -249
- package/tests/desktop-router.test.ts +0 -151
- package/tests/router.test.ts +0 -388
- package/tests/trust-router.test.ts +0 -170
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
- package/ui/index.html +0 -19
- package/ui/node_modules/.bin/browserslist +0 -17
- package/ui/node_modules/.bin/tsc +0 -17
- package/ui/node_modules/.bin/tsserver +0 -17
- package/ui/node_modules/.bin/vite +0 -17
- package/ui/package.json +0 -23
- package/ui/public/icon.svg +0 -59
- package/ui/src/App.tsx +0 -63
- package/ui/src/api.ts +0 -238
- package/ui/src/components/ActivityFeed.tsx +0 -123
- package/ui/src/components/BehaviorHealth.tsx +0 -105
- package/ui/src/components/DataTable.tsx +0 -39
- package/ui/src/components/Layout.tsx +0 -160
- package/ui/src/components/PasswordStrength.tsx +0 -31
- package/ui/src/components/SetupProgress.tsx +0 -26
- package/ui/src/components/StatusBadge.tsx +0 -12
- package/ui/src/components/ThemeSelector.tsx +0 -39
- package/ui/src/contexts/ThemeContext.tsx +0 -58
- package/ui/src/hooks/useApi.ts +0 -19
- package/ui/src/hooks/usePolling.ts +0 -8
- package/ui/src/main.tsx +0 -16
- package/ui/src/pages/AuditLog.tsx +0 -36
- package/ui/src/pages/Behaviors.tsx +0 -426
- package/ui/src/pages/Chat.tsx +0 -688
- package/ui/src/pages/Login.tsx +0 -64
- package/ui/src/pages/Overview.tsx +0 -56
- package/ui/src/pages/Sessions.tsx +0 -26
- package/ui/src/pages/SettingsAmbient.tsx +0 -185
- package/ui/src/pages/SettingsConnections.tsx +0 -201
- package/ui/src/pages/SettingsNotifications.tsx +0 -241
- package/ui/src/pages/SetupAppearance.tsx +0 -45
- package/ui/src/pages/SetupChannels.tsx +0 -143
- package/ui/src/pages/SetupComplete.tsx +0 -31
- package/ui/src/pages/SetupConnections.tsx +0 -80
- package/ui/src/pages/SetupDashboardPassword.tsx +0 -50
- package/ui/src/pages/SetupIdentity.tsx +0 -68
- package/ui/src/pages/SetupPersonality.tsx +0 -78
- package/ui/src/pages/SetupProvider.tsx +0 -65
- package/ui/src/pages/SetupVault.tsx +0 -50
- package/ui/src/pages/SetupWelcome.tsx +0 -19
- package/ui/src/pages/UnlockVault.tsx +0 -56
- package/ui/src/pages/Webhooks.tsx +0 -158
- package/ui/src/pages/settings/Appearance.tsx +0 -63
- package/ui/src/pages/settings/Channels.tsx +0 -138
- package/ui/src/pages/settings/Identity.tsx +0 -61
- package/ui/src/pages/settings/Personality.tsx +0 -54
- package/ui/src/pages/settings/PersonalityEditor.tsx +0 -577
- package/ui/src/pages/settings/Provider.tsx +0 -537
- package/ui/src/pages/settings/Security.tsx +0 -111
- package/ui/src/styles/global.css +0 -2308
- package/ui/src/styles/themes/index.css +0 -7
- package/ui/src/styles/themes/monolith.css +0 -125
- package/ui/src/styles/themes/nebula.css +0 -90
- package/ui/src/styles/themes/neon.css +0 -149
- package/ui/src/styles/themes/polar.css +0 -151
- package/ui/src/styles/themes/signal.css +0 -163
- package/ui/src/styles/themes/terra.css +0 -146
- package/ui/tsconfig.json +0 -14
- package/ui/vite.config.ts +0 -20
package/ui/src/api.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
const BASE = '/api/v1/dashboard';
|
|
2
|
-
|
|
3
|
-
async function fetchApi<T>(path: string, options?: RequestInit): Promise<T> {
|
|
4
|
-
const res = await fetch(`${BASE}${path}`, {
|
|
5
|
-
credentials: 'include',
|
|
6
|
-
headers: { 'Content-Type': 'application/json' },
|
|
7
|
-
...options,
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
if (res.status === 401) {
|
|
11
|
-
window.location.href = '/dashboard/login';
|
|
12
|
-
throw new Error('Unauthorized');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (!res.ok) {
|
|
16
|
-
const body = await res.json().catch(() => ({}));
|
|
17
|
-
throw new Error(body.error || `HTTP ${res.status}`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return res.json();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const api = {
|
|
24
|
-
checkAuth: () => fetchApi<{ authenticated: boolean }>('/auth/check'),
|
|
25
|
-
login: (pw: string) =>
|
|
26
|
-
fetchApi<{ success: boolean }>('/auth/login', {
|
|
27
|
-
method: 'POST',
|
|
28
|
-
body: JSON.stringify({ password: pw }),
|
|
29
|
-
}),
|
|
30
|
-
logout: () => fetchApi<{ success: boolean }>('/auth/logout', { method: 'POST' }),
|
|
31
|
-
getBehaviors: () => fetchApi<{ data: any[] }>('/behaviors'),
|
|
32
|
-
patchBehavior: (id: string, updates: Record<string, unknown>) =>
|
|
33
|
-
fetchApi<{ data: any }>(`/behaviors/${id}`, {
|
|
34
|
-
method: 'PATCH',
|
|
35
|
-
body: JSON.stringify(updates),
|
|
36
|
-
}),
|
|
37
|
-
deleteBehavior: (id: string) =>
|
|
38
|
-
fetchApi<{ data: any }>(`/behaviors/${id}`, { method: 'DELETE' }),
|
|
39
|
-
createBehavior: (input: Record<string, unknown>) =>
|
|
40
|
-
fetchApi<{ data: any }>('/behaviors', {
|
|
41
|
-
method: 'POST',
|
|
42
|
-
body: JSON.stringify(input),
|
|
43
|
-
}),
|
|
44
|
-
getWebhooks: () => fetchApi<{ data: any[] }>('/webhooks'),
|
|
45
|
-
patchWebhook: (id: string, updates: Record<string, unknown>) =>
|
|
46
|
-
fetchApi<{ data: any }>(`/webhooks/${id}`, {
|
|
47
|
-
method: 'PATCH',
|
|
48
|
-
body: JSON.stringify(updates),
|
|
49
|
-
}),
|
|
50
|
-
deleteWebhook: (id: string) =>
|
|
51
|
-
fetchApi<{ data: any }>(`/webhooks/${id}`, { method: 'DELETE' }),
|
|
52
|
-
createWebhook: (input: Record<string, unknown>) =>
|
|
53
|
-
fetchApi<{ data: any }>('/webhooks', {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
body: JSON.stringify(input),
|
|
56
|
-
}),
|
|
57
|
-
getSessions: () => fetchApi<{ data: any[] }>('/sessions'),
|
|
58
|
-
getAudit: (params?: { type?: string; limit?: number }) => {
|
|
59
|
-
const query = new URLSearchParams();
|
|
60
|
-
if (params?.type) query.set('type', params.type);
|
|
61
|
-
if (params?.limit) query.set('limit', String(params.limit));
|
|
62
|
-
const qs = query.toString();
|
|
63
|
-
return fetchApi<{ data: any[] }>(`/audit${qs ? `?${qs}` : ''}`);
|
|
64
|
-
},
|
|
65
|
-
getStatus: () => fetchApi<{ data: any }>('/status'),
|
|
66
|
-
getSetupStatus: () =>
|
|
67
|
-
fetchApi<{ needsSetup: boolean; completedSteps: string[]; vaultUnlocked: boolean; dashboardPasswordSet: boolean; agentName: string }>('/setup/status'),
|
|
68
|
-
setupVault: (password: string) =>
|
|
69
|
-
fetchApi<{ success: boolean }>('/setup/vault', { method: 'POST', body: JSON.stringify({ password }) }),
|
|
70
|
-
setupDashboardPassword: (password: string) =>
|
|
71
|
-
fetchApi<{ success: boolean }>('/setup/dashboard-password', { method: 'POST', body: JSON.stringify({ password }) }),
|
|
72
|
-
setupIdentity: (name: string, pronouns: string, vibe?: string) =>
|
|
73
|
-
fetchApi<{ success: boolean }>('/setup/identity', { method: 'POST', body: JSON.stringify({ name, pronouns, vibe }) }),
|
|
74
|
-
getSetupTemplates: () =>
|
|
75
|
-
fetchApi<{ data: Array<{ id: string; name: string; description: string; preview: string }> }>('/setup/templates'),
|
|
76
|
-
setupPersonality: (template: string) =>
|
|
77
|
-
fetchApi<{ success: boolean }>('/setup/personality', { method: 'POST', body: JSON.stringify({ template }) }),
|
|
78
|
-
setupProvider: (provider: string, apiKey?: string, endpoint?: string) =>
|
|
79
|
-
fetchApi<{ success: boolean }>('/setup/provider', { method: 'POST', body: JSON.stringify({ provider, apiKey, endpoint }) }),
|
|
80
|
-
setupChannels: (channels: Array<{ type: string; enabled: boolean; credentials?: Record<string, string> }>) =>
|
|
81
|
-
fetchApi<{ success: boolean }>('/setup/channels', { method: 'POST', body: JSON.stringify({ channels }) }),
|
|
82
|
-
completeSetup: () =>
|
|
83
|
-
fetchApi<{ success: boolean }>('/setup/complete', { method: 'POST' }),
|
|
84
|
-
|
|
85
|
-
// Settings API
|
|
86
|
-
getModels: () => fetchApi<{ providers: any[]; routing: any; cost: any }>('/models'),
|
|
87
|
-
getIdentity: () => fetchApi<{ data: { name: string; pronouns: string } }>('/identity'),
|
|
88
|
-
updateIdentity: (name: string, pronouns: string) =>
|
|
89
|
-
fetchApi<{ success: boolean }>('/identity', {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
body: JSON.stringify({ name, pronouns }),
|
|
92
|
-
}),
|
|
93
|
-
getPersonality: () =>
|
|
94
|
-
fetchApi<{ data: { template: { id: string; name: string } | null } }>('/personality'),
|
|
95
|
-
getTemplates: () =>
|
|
96
|
-
fetchApi<{ data: Array<{ id: string; name: string; description: string; preview: string }> }>('/personality/templates'),
|
|
97
|
-
updatePersonality: (template: string) =>
|
|
98
|
-
fetchApi<{ success: boolean }>('/personality', {
|
|
99
|
-
method: 'POST',
|
|
100
|
-
body: JSON.stringify({ template }),
|
|
101
|
-
}),
|
|
102
|
-
getPersonalityFull: () =>
|
|
103
|
-
fetchApi<{ data: {
|
|
104
|
-
name: string; pronouns: string; avatar: string | null; vibe: string;
|
|
105
|
-
tone: { warmth: number; directness: number; humor: number; formality: number };
|
|
106
|
-
errorStyle: string; expertise: string[]; catchphrases: Record<string, string>;
|
|
107
|
-
boundaries: { neverJokeAbout: string[]; neverAdviseOn: string[] };
|
|
108
|
-
customInstructions: string; soulContent: string | null; activeTemplate: string | null;
|
|
109
|
-
} }>('/personality/full'),
|
|
110
|
-
updatePersonalityFull: (data: Record<string, unknown>) =>
|
|
111
|
-
fetchApi<{ success: boolean }>('/personality/full', {
|
|
112
|
-
method: 'PUT',
|
|
113
|
-
body: JSON.stringify(data),
|
|
114
|
-
}),
|
|
115
|
-
updateProvider: (provider: string, apiKey?: string, endpoint?: string) =>
|
|
116
|
-
fetchApi<{ success: boolean }>('/provider', {
|
|
117
|
-
method: 'POST',
|
|
118
|
-
body: JSON.stringify({ provider, apiKey, endpoint }),
|
|
119
|
-
}),
|
|
120
|
-
configureProvider: (provider: string, apiKey?: string, endpoint?: string) =>
|
|
121
|
-
fetchApi<{ success: boolean }>('/provider/configure', {
|
|
122
|
-
method: 'POST',
|
|
123
|
-
body: JSON.stringify({ provider, apiKey, endpoint }),
|
|
124
|
-
}),
|
|
125
|
-
// Claude OAuth
|
|
126
|
-
startClaudeOAuth: () =>
|
|
127
|
-
fetchApi<{ authUrl: string }>('/provider/claude-oauth/start', { method: 'POST' }),
|
|
128
|
-
completeClaudeOAuth: (code: string) =>
|
|
129
|
-
fetchApi<{ success: boolean }>('/provider/claude-oauth/callback', {
|
|
130
|
-
method: 'POST',
|
|
131
|
-
body: JSON.stringify({ code }),
|
|
132
|
-
}),
|
|
133
|
-
disconnectClaudeOAuth: () =>
|
|
134
|
-
fetchApi<{ success: boolean }>('/provider/claude-oauth/disconnect', { method: 'POST' }),
|
|
135
|
-
getClaudeOAuthStatus: () =>
|
|
136
|
-
fetchApi<{ connected: boolean }>('/provider/claude-oauth/status'),
|
|
137
|
-
updateRouting: (primary: string, fallback?: string) =>
|
|
138
|
-
fetchApi<{ success: boolean }>('/provider/routing', {
|
|
139
|
-
method: 'POST',
|
|
140
|
-
body: JSON.stringify({ primary, fallback }),
|
|
141
|
-
}),
|
|
142
|
-
setProviderModel: (provider: string, model: string) =>
|
|
143
|
-
fetchApi<{ success: boolean }>('/provider/model', {
|
|
144
|
-
method: 'POST',
|
|
145
|
-
body: JSON.stringify({ provider, model }),
|
|
146
|
-
}),
|
|
147
|
-
getSessionMessages: () =>
|
|
148
|
-
fetchApi<{ data: Array<{ id: string; role: string; content: string; timestamp: number }> }>('/session/messages'),
|
|
149
|
-
getChannels: () => fetchApi<{ data: { connected: string[]; configured: Array<{ type: string; enabled: boolean }> } }>('/channels'),
|
|
150
|
-
updateChannels: (channels: Array<{ type: string; enabled: boolean; credentials?: Record<string, string> }>) =>
|
|
151
|
-
fetchApi<{ success: boolean }>('/channels', {
|
|
152
|
-
method: 'POST',
|
|
153
|
-
body: JSON.stringify({ channels }),
|
|
154
|
-
}),
|
|
155
|
-
changeDashboardPassword: (oldPassword: string, newPassword: string) =>
|
|
156
|
-
fetchApi<{ success: boolean }>('/security/dashboard-password', {
|
|
157
|
-
method: 'POST',
|
|
158
|
-
body: JSON.stringify({ oldPassword, newPassword }),
|
|
159
|
-
}),
|
|
160
|
-
changeVaultPassword: (newPassword: string) =>
|
|
161
|
-
fetchApi<{ success: boolean }>('/security/vault-password', {
|
|
162
|
-
method: 'POST',
|
|
163
|
-
body: JSON.stringify({ newPassword }),
|
|
164
|
-
}),
|
|
165
|
-
|
|
166
|
-
// Connector OAuth API
|
|
167
|
-
getConnectorStatus: (connectorId: string) =>
|
|
168
|
-
fetchApi<{ data: { connectorId: string; hasCredentials: boolean; connected: boolean; expiresAt?: number } }>(
|
|
169
|
-
`/connectors/${connectorId}/status`,
|
|
170
|
-
),
|
|
171
|
-
saveConnectorCredentials: (connectorId: string, clientId: string, clientSecret: string) =>
|
|
172
|
-
fetchApi<{ success: boolean; oauthUrl?: string }>(`/connectors/${connectorId}/credentials`, {
|
|
173
|
-
method: 'POST',
|
|
174
|
-
body: JSON.stringify({ clientId, clientSecret }),
|
|
175
|
-
}),
|
|
176
|
-
disconnectConnector: (connectorId: string) =>
|
|
177
|
-
fetchApi<{ success: boolean }>(`/connectors/${connectorId}/disconnect`, {
|
|
178
|
-
method: 'POST',
|
|
179
|
-
}),
|
|
180
|
-
|
|
181
|
-
// Ambient config
|
|
182
|
-
getAmbientConfig: () =>
|
|
183
|
-
fetchApi<{ data: any }>('/ambient/config'),
|
|
184
|
-
updateAmbientConfig: (config: Record<string, unknown>) =>
|
|
185
|
-
fetchApi<{ success: boolean }>('/ambient/config', {
|
|
186
|
-
method: 'POST',
|
|
187
|
-
body: JSON.stringify(config),
|
|
188
|
-
}),
|
|
189
|
-
|
|
190
|
-
// Appearance
|
|
191
|
-
getAppearance: () =>
|
|
192
|
-
fetchApi<{ data: { theme: string } }>('/appearance'),
|
|
193
|
-
updateAppearance: (theme: string) =>
|
|
194
|
-
fetchApi<{ success: boolean }>('/appearance', {
|
|
195
|
-
method: 'POST',
|
|
196
|
-
body: JSON.stringify({ theme }),
|
|
197
|
-
}),
|
|
198
|
-
|
|
199
|
-
// Chat management
|
|
200
|
-
getChats: (archived?: boolean) => {
|
|
201
|
-
const qs = archived ? '?archived=true' : '';
|
|
202
|
-
return fetchApi<{ data: any[]; total: number }>(`/chats${qs}`);
|
|
203
|
-
},
|
|
204
|
-
createNewChat: (title?: string) =>
|
|
205
|
-
fetchApi<{ data: any }>('/chats', {
|
|
206
|
-
method: 'POST',
|
|
207
|
-
body: JSON.stringify({ title }),
|
|
208
|
-
}),
|
|
209
|
-
getChatMessages: (chatId: string) =>
|
|
210
|
-
fetchApi<{ data: Array<{ id: string; role: string; content: string; timestamp: number }> }>(`/chats/${chatId}/messages`),
|
|
211
|
-
renameChat: (chatId: string, title: string) =>
|
|
212
|
-
fetchApi<{ data: any }>(`/chats/${chatId}`, {
|
|
213
|
-
method: 'PATCH',
|
|
214
|
-
body: JSON.stringify({ title }),
|
|
215
|
-
}),
|
|
216
|
-
archiveChat: (chatId: string) =>
|
|
217
|
-
fetchApi<{ data: any }>(`/chats/${chatId}`, {
|
|
218
|
-
method: 'PATCH',
|
|
219
|
-
body: JSON.stringify({ archived: true }),
|
|
220
|
-
}),
|
|
221
|
-
deleteChatThread: (chatId: string) =>
|
|
222
|
-
fetchApi<{ data: any }>(`/chats/${chatId}`, { method: 'DELETE' }),
|
|
223
|
-
|
|
224
|
-
// Notifications
|
|
225
|
-
getNotifications: () =>
|
|
226
|
-
fetchApi<{ data: any[] }>('/notifications'),
|
|
227
|
-
dismissNotification: (id: string) =>
|
|
228
|
-
fetchApi<{ data: { dismissed: boolean } }>(`/notifications/${id}/dismiss`, {
|
|
229
|
-
method: 'POST',
|
|
230
|
-
}),
|
|
231
|
-
getNotificationPreferences: () =>
|
|
232
|
-
fetchApi<{ data: any }>('/notifications/preferences'),
|
|
233
|
-
updateNotificationPreferences: (prefs: Record<string, unknown>) =>
|
|
234
|
-
fetchApi<{ success: boolean }>('/notifications/preferences', {
|
|
235
|
-
method: 'POST',
|
|
236
|
-
body: JSON.stringify(prefs),
|
|
237
|
-
}),
|
|
238
|
-
};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { api } from '../api';
|
|
3
|
-
|
|
4
|
-
interface ActivityEvent {
|
|
5
|
-
timestamp: string;
|
|
6
|
-
sequence: number;
|
|
7
|
-
event: string;
|
|
8
|
-
details: Record<string, unknown>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const EVENT_LABELS: Record<string, (d: Record<string, unknown>) => string> = {
|
|
12
|
-
'behavior.executed': (d) => d.success ? `Behavior ran successfully` : `Behavior failed: ${d.error ?? 'unknown'}`,
|
|
13
|
-
'behavior.created': () => 'Behavior created',
|
|
14
|
-
'behavior.updated': () => 'Behavior updated',
|
|
15
|
-
'behavior.deleted': () => 'Behavior deleted',
|
|
16
|
-
'behavior.paused': () => 'Behavior paused',
|
|
17
|
-
'behavior.failed': (d) => `Behavior failed: ${d.error ?? 'unknown'}`,
|
|
18
|
-
'message.received': (d) => `Message received on ${d.channelType ?? 'unknown'}`,
|
|
19
|
-
'message.sent': (d) => `Message sent to ${d.channelType ?? 'unknown'}`,
|
|
20
|
-
'message.filtered': () => 'Message filtered',
|
|
21
|
-
'channel.connected': (d) => `${d.channelType ?? 'Channel'} connected`,
|
|
22
|
-
'channel.disconnected': (d) => `${d.channelType ?? 'Channel'} disconnected`,
|
|
23
|
-
'channel.error': (d) => `${d.channelType ?? 'Channel'} error`,
|
|
24
|
-
'webhook.triggered': (d) => `Webhook triggered: ${d.path ?? ''}`,
|
|
25
|
-
'webhook.received': () => 'Webhook received',
|
|
26
|
-
'webhook.created': () => 'Webhook created',
|
|
27
|
-
'webhook.deleted': () => 'Webhook deleted',
|
|
28
|
-
'webhook.error': (d) => `Webhook error: ${d.error ?? 'unknown'}`,
|
|
29
|
-
'system.startup': () => 'System started',
|
|
30
|
-
'system.shutdown': () => 'System shut down',
|
|
31
|
-
'system.error': (d) => `System error: ${d.error ?? 'unknown'}`,
|
|
32
|
-
'auth.login': () => 'Login',
|
|
33
|
-
'auth.logout': () => 'Logout',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const CATEGORY_COLORS: Record<string, string> = {
|
|
37
|
-
behavior: 'var(--accent)',
|
|
38
|
-
message: '#3b82f6',
|
|
39
|
-
channel: 'var(--success)',
|
|
40
|
-
webhook: '#f97316',
|
|
41
|
-
system: 'var(--text-secondary)',
|
|
42
|
-
auth: 'var(--danger)',
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
function getCategory(event: string): string {
|
|
46
|
-
return event.split('.')[0];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function getLabel(event: string, details: Record<string, unknown>): string {
|
|
50
|
-
const fn = EVENT_LABELS[event];
|
|
51
|
-
if (fn) return fn(details);
|
|
52
|
-
return event.replace(/\./g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function timeAgo(iso: string): string {
|
|
56
|
-
const diff = (Date.now() - new Date(iso).getTime()) / 1000;
|
|
57
|
-
if (diff < 60) return 'just now';
|
|
58
|
-
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
59
|
-
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
60
|
-
return `${Math.floor(diff / 86400)}d ago`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const MAX_EVENTS = 100;
|
|
64
|
-
|
|
65
|
-
export function ActivityFeed() {
|
|
66
|
-
const [events, setEvents] = useState<ActivityEvent[]>([]);
|
|
67
|
-
const wsRef = useRef<WebSocket | null>(null);
|
|
68
|
-
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
api.getAudit({ limit: 50 }).then((res) => {
|
|
71
|
-
if (res.data) {
|
|
72
|
-
const PREFIXES = ['behavior.', 'message.', 'channel.', 'webhook.', 'system.', 'auth.login', 'auth.logout'];
|
|
73
|
-
const filtered = res.data
|
|
74
|
-
.filter((e: any) => PREFIXES.some((p) => e.event.startsWith(p)))
|
|
75
|
-
.reverse();
|
|
76
|
-
setEvents(filtered);
|
|
77
|
-
}
|
|
78
|
-
}).catch(() => {});
|
|
79
|
-
}, []);
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
83
|
-
const host = import.meta.env.DEV ? 'localhost:18800' : window.location.host;
|
|
84
|
-
const ws = new WebSocket(`${protocol}//${host}/`);
|
|
85
|
-
wsRef.current = ws;
|
|
86
|
-
|
|
87
|
-
ws.onmessage = (evt) => {
|
|
88
|
-
try {
|
|
89
|
-
const msg = JSON.parse(evt.data);
|
|
90
|
-
if (msg.type === 'activity' && msg.payload) {
|
|
91
|
-
setEvents((prev) => {
|
|
92
|
-
const next = [msg.payload, ...prev];
|
|
93
|
-
return next.length > MAX_EVENTS ? next.slice(0, MAX_EVENTS) : next;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
} catch { /* ignore non-JSON */ }
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
return () => { ws.close(); };
|
|
100
|
-
}, []);
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<div className="activity-feed">
|
|
104
|
-
<h3 className="mc-section-title">Live Activity</h3>
|
|
105
|
-
<div className="activity-feed-list">
|
|
106
|
-
{events.length === 0 && (
|
|
107
|
-
<div className="activity-empty">No recent activity</div>
|
|
108
|
-
)}
|
|
109
|
-
{events.map((e, i) => {
|
|
110
|
-
const cat = getCategory(e.event);
|
|
111
|
-
const color = CATEGORY_COLORS[cat] ?? 'var(--text-secondary)';
|
|
112
|
-
return (
|
|
113
|
-
<div key={`${e.sequence}-${i}`} className="activity-item">
|
|
114
|
-
<span className="activity-dot" style={{ background: color }} />
|
|
115
|
-
<span className="activity-label">{getLabel(e.event, e.details)}</span>
|
|
116
|
-
<span className="activity-time">{timeAgo(e.timestamp)}</span>
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
})}
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { api } from '../api';
|
|
3
|
-
|
|
4
|
-
interface Behavior {
|
|
5
|
-
id: string;
|
|
6
|
-
type: 'scheduled' | 'monitor' | 'one-shot';
|
|
7
|
-
status: 'active' | 'paused' | 'deleted' | 'missed';
|
|
8
|
-
action: string;
|
|
9
|
-
runCount: number;
|
|
10
|
-
failCount: number;
|
|
11
|
-
maxFailures: number;
|
|
12
|
-
lastRun?: string;
|
|
13
|
-
lastResult?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function timeAgo(iso: string): string {
|
|
17
|
-
const diff = (Date.now() - new Date(iso).getTime()) / 1000;
|
|
18
|
-
if (diff < 60) return 'just now';
|
|
19
|
-
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
20
|
-
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
21
|
-
return `${Math.floor(diff / 86400)}d ago`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function getHealthColor(b: Behavior): string {
|
|
25
|
-
if (b.status === 'paused' && b.failCount >= b.maxFailures) return 'var(--danger)';
|
|
26
|
-
if (b.status === 'paused') return 'var(--text-secondary)';
|
|
27
|
-
if (b.failCount > 0) return 'var(--warning)';
|
|
28
|
-
return 'var(--success)';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function getHealthLabel(b: Behavior): string {
|
|
32
|
-
if (b.status === 'paused' && b.failCount >= b.maxFailures) return 'Auto-paused';
|
|
33
|
-
if (b.status === 'paused') return 'Paused';
|
|
34
|
-
if (b.failCount > 0) return `${b.failCount} failures`;
|
|
35
|
-
return 'Healthy';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const TYPE_LABELS: Record<string, string> = {
|
|
39
|
-
scheduled: 'Sched',
|
|
40
|
-
monitor: 'Monitor',
|
|
41
|
-
'one-shot': 'Once',
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export function BehaviorHealth() {
|
|
45
|
-
const [behaviors, setBehaviors] = useState<Behavior[]>([]);
|
|
46
|
-
const wsRef = useRef<WebSocket | null>(null);
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
api.getBehaviors().then((res) => {
|
|
50
|
-
if (res.data) {
|
|
51
|
-
setBehaviors(res.data.filter((b: any) => b.status !== 'deleted'));
|
|
52
|
-
}
|
|
53
|
-
}).catch(() => {});
|
|
54
|
-
}, []);
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
58
|
-
const host = import.meta.env.DEV ? 'localhost:18800' : window.location.host;
|
|
59
|
-
const ws = new WebSocket(`${protocol}//${host}/`);
|
|
60
|
-
wsRef.current = ws;
|
|
61
|
-
|
|
62
|
-
ws.onmessage = (evt) => {
|
|
63
|
-
try {
|
|
64
|
-
const msg = JSON.parse(evt.data);
|
|
65
|
-
if (msg.type === 'activity' && msg.payload?.event?.startsWith('behavior.')) {
|
|
66
|
-
api.getBehaviors().then((res) => {
|
|
67
|
-
if (res.data) {
|
|
68
|
-
setBehaviors(res.data.filter((b: any) => b.status !== 'deleted'));
|
|
69
|
-
}
|
|
70
|
-
}).catch(() => {});
|
|
71
|
-
}
|
|
72
|
-
} catch { /* ignore */ }
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
return () => { ws.close(); };
|
|
76
|
-
}, []);
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<div className="behavior-health">
|
|
80
|
-
<h3 className="mc-section-title">Behavior Health</h3>
|
|
81
|
-
{behaviors.length === 0 && (
|
|
82
|
-
<div className="bh-empty">No behaviors configured</div>
|
|
83
|
-
)}
|
|
84
|
-
<div className="bh-list">
|
|
85
|
-
{behaviors.map((b) => (
|
|
86
|
-
<div key={b.id} className="bh-card">
|
|
87
|
-
<div className="bh-card-header">
|
|
88
|
-
<span className="bh-dot" style={{ background: getHealthColor(b) }} />
|
|
89
|
-
<span className="bh-action">{b.action.length > 60 ? b.action.slice(0, 57) + '...' : b.action}</span>
|
|
90
|
-
<span className="bh-type-badge">{TYPE_LABELS[b.type] ?? b.type}</span>
|
|
91
|
-
</div>
|
|
92
|
-
<div className="bh-card-meta">
|
|
93
|
-
<span className="bh-health-label" style={{ color: getHealthColor(b) }}>{getHealthLabel(b)}</span>
|
|
94
|
-
<span className="bh-stat">{b.runCount} runs</span>
|
|
95
|
-
{b.lastRun && <span className="bh-stat">{timeAgo(b.lastRun)}</span>}
|
|
96
|
-
</div>
|
|
97
|
-
{b.lastResult && (
|
|
98
|
-
<div className="bh-result">{b.lastResult.length > 120 ? b.lastResult.slice(0, 117) + '...' : b.lastResult}</div>
|
|
99
|
-
)}
|
|
100
|
-
</div>
|
|
101
|
-
))}
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
interface Column<T> {
|
|
2
|
-
key: string;
|
|
3
|
-
label: string;
|
|
4
|
-
render?: (row: T) => React.ReactNode;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
interface DataTableProps<T> {
|
|
8
|
-
columns: Column<T>[];
|
|
9
|
-
rows: T[];
|
|
10
|
-
keyField: string;
|
|
11
|
-
actions?: (row: T) => React.ReactNode;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function DataTable<T extends Record<string, any>>({ columns, rows, keyField, actions }: DataTableProps<T>) {
|
|
15
|
-
return (
|
|
16
|
-
<table className="data-table">
|
|
17
|
-
<thead>
|
|
18
|
-
<tr>
|
|
19
|
-
{columns.map((col) => <th key={col.key}>{col.label}</th>)}
|
|
20
|
-
{actions && <th>Actions</th>}
|
|
21
|
-
</tr>
|
|
22
|
-
</thead>
|
|
23
|
-
<tbody>
|
|
24
|
-
{rows.length === 0 ? (
|
|
25
|
-
<tr><td colSpan={columns.length + (actions ? 1 : 0)} className="empty-row">No data</td></tr>
|
|
26
|
-
) : (
|
|
27
|
-
rows.map((row) => (
|
|
28
|
-
<tr key={row[keyField]}>
|
|
29
|
-
{columns.map((col) => (
|
|
30
|
-
<td key={col.key}>{col.render ? col.render(row) : String(row[col.key] ?? '')}</td>
|
|
31
|
-
))}
|
|
32
|
-
{actions && <td className="actions-cell">{actions(row)}</td>}
|
|
33
|
-
</tr>
|
|
34
|
-
))
|
|
35
|
-
)}
|
|
36
|
-
</tbody>
|
|
37
|
-
</table>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
|
3
|
-
import { useApi } from '../hooks/useApi';
|
|
4
|
-
import { usePolling } from '../hooks/usePolling';
|
|
5
|
-
import { api } from '../api';
|
|
6
|
-
|
|
7
|
-
const CHANNEL_TYPES = ['webchat', 'discord', 'telegram', 'slack', 'twilio', 'matrix', 'signal', 'teams', 'whatsapp', 'email'] as const;
|
|
8
|
-
|
|
9
|
-
export function Layout() {
|
|
10
|
-
const [checking, setChecking] = useState(true);
|
|
11
|
-
const [ready, setReady] = useState(false);
|
|
12
|
-
const [agentName, setAgentName] = useState('Auxiora');
|
|
13
|
-
const navigate = useNavigate();
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
api.getSetupStatus()
|
|
17
|
-
.then(status => {
|
|
18
|
-
if (status.agentName) setAgentName(status.agentName);
|
|
19
|
-
if (status.needsSetup) {
|
|
20
|
-
navigate('/setup', { replace: true });
|
|
21
|
-
} else if (!status.vaultUnlocked) {
|
|
22
|
-
navigate('/unlock', { replace: true });
|
|
23
|
-
} else {
|
|
24
|
-
setReady(true);
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
.catch(() => {})
|
|
28
|
-
.finally(() => setChecking(false));
|
|
29
|
-
}, []);
|
|
30
|
-
|
|
31
|
-
// Only make authenticated calls once vault/setup check passes
|
|
32
|
-
const { data: status, refresh } = useApi(() => ready ? api.getStatus() : Promise.resolve(null), [ready]);
|
|
33
|
-
const { data: sessions, refresh: refreshSessions } = useApi(() => ready ? api.getSessions() : Promise.resolve(null), [ready]);
|
|
34
|
-
usePolling(() => { if (ready) { refresh(); refreshSessions(); } });
|
|
35
|
-
|
|
36
|
-
if (checking) return null;
|
|
37
|
-
|
|
38
|
-
// Derive connected channel types from active sessions
|
|
39
|
-
const connectedChannels = new Set<string>();
|
|
40
|
-
if (sessions?.data) {
|
|
41
|
-
for (const s of sessions.data) {
|
|
42
|
-
if (s.channelType) connectedChannels.add(s.channelType);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Only show channels that have connections or are commonly configured
|
|
47
|
-
const visibleChannels = CHANNEL_TYPES.filter(ch =>
|
|
48
|
-
connectedChannels.has(ch) || ch === 'webchat'
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<div className="layout">
|
|
53
|
-
<nav className="sidebar">
|
|
54
|
-
<div className="sidebar-header">
|
|
55
|
-
<h1>{agentName}</h1>
|
|
56
|
-
</div>
|
|
57
|
-
<ul className="nav-list">
|
|
58
|
-
{/* MAIN */}
|
|
59
|
-
<div className="nav-group">
|
|
60
|
-
<div className="nav-group-label">Main</div>
|
|
61
|
-
<li>
|
|
62
|
-
<NavLink to="/chat" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
63
|
-
Chat
|
|
64
|
-
</NavLink>
|
|
65
|
-
</li>
|
|
66
|
-
<li>
|
|
67
|
-
<NavLink to="/" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'} end>
|
|
68
|
-
Mission Control
|
|
69
|
-
</NavLink>
|
|
70
|
-
</li>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
{/* CHANNELS */}
|
|
74
|
-
<div className="nav-group">
|
|
75
|
-
<div className="nav-group-label">Channels</div>
|
|
76
|
-
{visibleChannels.map(ch => (
|
|
77
|
-
<li key={ch}>
|
|
78
|
-
<NavLink to="/settings/channels" className="nav-link">
|
|
79
|
-
{ch.charAt(0).toUpperCase() + ch.slice(1)}
|
|
80
|
-
<span className={`channel-dot ${connectedChannels.has(ch) ? 'connected' : 'disconnected'}`} />
|
|
81
|
-
</NavLink>
|
|
82
|
-
</li>
|
|
83
|
-
))}
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
{/* MANAGEMENT */}
|
|
87
|
-
<div className="nav-group">
|
|
88
|
-
<div className="nav-group-label">Management</div>
|
|
89
|
-
<li>
|
|
90
|
-
<NavLink to="/behaviors" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
91
|
-
Behaviors
|
|
92
|
-
</NavLink>
|
|
93
|
-
</li>
|
|
94
|
-
<li>
|
|
95
|
-
<NavLink to="/webhooks" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
96
|
-
Webhooks
|
|
97
|
-
</NavLink>
|
|
98
|
-
</li>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
{/* SETTINGS */}
|
|
102
|
-
<div className="nav-group">
|
|
103
|
-
<div className="nav-group-label">Settings</div>
|
|
104
|
-
<li>
|
|
105
|
-
<NavLink to="/settings/personality" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
106
|
-
Personality
|
|
107
|
-
</NavLink>
|
|
108
|
-
</li>
|
|
109
|
-
<li>
|
|
110
|
-
<NavLink to="/settings/provider" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
111
|
-
Provider
|
|
112
|
-
</NavLink>
|
|
113
|
-
</li>
|
|
114
|
-
<li>
|
|
115
|
-
<NavLink to="/settings/channels" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
116
|
-
Channels
|
|
117
|
-
</NavLink>
|
|
118
|
-
</li>
|
|
119
|
-
<li>
|
|
120
|
-
<NavLink to="/settings/connections" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
121
|
-
Connections
|
|
122
|
-
</NavLink>
|
|
123
|
-
</li>
|
|
124
|
-
<li>
|
|
125
|
-
<NavLink to="/settings/ambient" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
126
|
-
Ambient
|
|
127
|
-
</NavLink>
|
|
128
|
-
</li>
|
|
129
|
-
<li>
|
|
130
|
-
<NavLink to="/settings/appearance" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
131
|
-
Appearance
|
|
132
|
-
</NavLink>
|
|
133
|
-
</li>
|
|
134
|
-
<li>
|
|
135
|
-
<NavLink to="/settings/notifications" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
136
|
-
Notifications
|
|
137
|
-
</NavLink>
|
|
138
|
-
</li>
|
|
139
|
-
<li>
|
|
140
|
-
<NavLink to="/settings/security" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
141
|
-
Security
|
|
142
|
-
</NavLink>
|
|
143
|
-
</li>
|
|
144
|
-
<li>
|
|
145
|
-
<NavLink to="/settings/audit" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
|
|
146
|
-
Audit Log
|
|
147
|
-
</NavLink>
|
|
148
|
-
</li>
|
|
149
|
-
</div>
|
|
150
|
-
</ul>
|
|
151
|
-
<button className="logout-btn" onClick={() => api.logout().then(() => { window.location.href = '/dashboard/login'; })}>
|
|
152
|
-
Logout
|
|
153
|
-
</button>
|
|
154
|
-
</nav>
|
|
155
|
-
<main className="content">
|
|
156
|
-
<Outlet />
|
|
157
|
-
</main>
|
|
158
|
-
</div>
|
|
159
|
-
);
|
|
160
|
-
}
|