@chaaskit/client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/favicon.svg +11 -0
  2. package/dist/index.html +17 -0
  3. package/dist/lib/LoadingSkeletons-IcIC2JPq.js +132 -0
  4. package/dist/lib/LoadingSkeletons-IcIC2JPq.js.map +1 -0
  5. package/dist/lib/ServerThemeProvider-DNF0LAyk.js +42 -0
  6. package/dist/lib/ServerThemeProvider-DNF0LAyk.js.map +1 -0
  7. package/dist/lib/extensions.js +10 -0
  8. package/dist/lib/extensions.js.map +1 -0
  9. package/dist/lib/favicon.svg +11 -0
  10. package/dist/lib/index.js +74126 -0
  11. package/dist/lib/index.js.map +1 -0
  12. package/dist/lib/logo.svg +12 -0
  13. package/dist/lib/routes/AcceptInviteRoute.js +19 -0
  14. package/dist/lib/routes/AcceptInviteRoute.js.map +1 -0
  15. package/dist/lib/routes/AdminDashboardRoute.js +19 -0
  16. package/dist/lib/routes/AdminDashboardRoute.js.map +1 -0
  17. package/dist/lib/routes/AdminTeamRoute.js +19 -0
  18. package/dist/lib/routes/AdminTeamRoute.js.map +1 -0
  19. package/dist/lib/routes/AdminTeamsRoute.js +19 -0
  20. package/dist/lib/routes/AdminTeamsRoute.js.map +1 -0
  21. package/dist/lib/routes/AdminUsersRoute.js +19 -0
  22. package/dist/lib/routes/AdminUsersRoute.js.map +1 -0
  23. package/dist/lib/routes/ApiKeysRoute.js +19 -0
  24. package/dist/lib/routes/ApiKeysRoute.js.map +1 -0
  25. package/dist/lib/routes/AutomationsRoute.js +19 -0
  26. package/dist/lib/routes/AutomationsRoute.js.map +1 -0
  27. package/dist/lib/routes/ChatRoute.js +19 -0
  28. package/dist/lib/routes/ChatRoute.js.map +1 -0
  29. package/dist/lib/routes/DocumentsRoute.js +19 -0
  30. package/dist/lib/routes/DocumentsRoute.js.map +1 -0
  31. package/dist/lib/routes/OAuthConsentRoute.js +19 -0
  32. package/dist/lib/routes/OAuthConsentRoute.js.map +1 -0
  33. package/dist/lib/routes/PricingRoute.js +19 -0
  34. package/dist/lib/routes/PricingRoute.js.map +1 -0
  35. package/dist/lib/routes/PrivacyRoute.js +19 -0
  36. package/dist/lib/routes/PrivacyRoute.js.map +1 -0
  37. package/dist/lib/routes/TeamSettingsRoute.js +19 -0
  38. package/dist/lib/routes/TeamSettingsRoute.js.map +1 -0
  39. package/dist/lib/routes/TermsRoute.js +19 -0
  40. package/dist/lib/routes/TermsRoute.js.map +1 -0
  41. package/dist/lib/routes/VerifyEmailRoute.js +19 -0
  42. package/dist/lib/routes/VerifyEmailRoute.js.map +1 -0
  43. package/dist/lib/routes.js +79 -0
  44. package/dist/lib/routes.js.map +1 -0
  45. package/dist/lib/ssr-utils.js +29 -0
  46. package/dist/lib/ssr-utils.js.map +1 -0
  47. package/dist/lib/ssr.js +60 -0
  48. package/dist/lib/ssr.js.map +1 -0
  49. package/dist/lib/styles.css +2410 -0
  50. package/dist/lib/useExtensions-B5nX_8XD.js +155 -0
  51. package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -0
  52. package/dist/logo.svg +12 -0
  53. package/package.json +84 -0
  54. package/src/components/AgentSelector.tsx +90 -0
  55. package/src/components/BranchModal.tsx +129 -0
  56. package/src/components/ClientOnly.tsx +27 -0
  57. package/src/components/ExportMenu.tsx +122 -0
  58. package/src/components/LoadingSkeletons.tsx +110 -0
  59. package/src/components/MCPCredentialsSection.tsx +309 -0
  60. package/src/components/MentionChip.tsx +149 -0
  61. package/src/components/MentionDropdown.tsx +175 -0
  62. package/src/components/MentionInput.tsx +293 -0
  63. package/src/components/MessageItem.tsx +300 -0
  64. package/src/components/MessageList.tsx +159 -0
  65. package/src/components/OAuthAppsSection.tsx +124 -0
  66. package/src/components/ProjectFolder.tsx +141 -0
  67. package/src/components/ProjectModal.tsx +296 -0
  68. package/src/components/SSRMessageList.tsx +153 -0
  69. package/src/components/SearchModal.tsx +173 -0
  70. package/src/components/SettingsModal.tsx +412 -0
  71. package/src/components/ShareModal.tsx +280 -0
  72. package/src/components/Sidebar.tsx +491 -0
  73. package/src/components/TeamSwitcher.tsx +273 -0
  74. package/src/components/ToolCallDisplay.tsx +473 -0
  75. package/src/components/ToolConfirmationModal.tsx +130 -0
  76. package/src/components/UsageChart.tsx +177 -0
  77. package/src/components/content/CodeBlock.tsx +69 -0
  78. package/src/components/content/MarkdownRenderer.tsx +64 -0
  79. package/src/components/content/SSRMarkdownRenderer.tsx +158 -0
  80. package/src/contexts/AuthContext.tsx +119 -0
  81. package/src/contexts/ConfigContext.tsx +214 -0
  82. package/src/contexts/ProjectContext.tsx +167 -0
  83. package/src/contexts/ServerConfigProvider.tsx +41 -0
  84. package/src/contexts/ServerThemeProvider.tsx +47 -0
  85. package/src/contexts/TeamContext.tsx +255 -0
  86. package/src/contexts/ThemeContext.tsx +113 -0
  87. package/src/extensions/index.ts +15 -0
  88. package/src/extensions/registry.ts +187 -0
  89. package/src/extensions/useExtensions.ts +52 -0
  90. package/src/hooks/useAppPath.ts +34 -0
  91. package/src/hooks/useBasePath.ts +13 -0
  92. package/src/hooks/useKeyboardShortcuts.ts +50 -0
  93. package/src/hooks/useMentionSearch.ts +106 -0
  94. package/src/index.tsx +116 -0
  95. package/src/layouts/MainLayout.tsx +98 -0
  96. package/src/pages/AcceptInvitePage.tsx +175 -0
  97. package/src/pages/AdminDashboardPage.tsx +362 -0
  98. package/src/pages/AdminTeamPage.tsx +304 -0
  99. package/src/pages/AdminTeamsPage.tsx +242 -0
  100. package/src/pages/AdminUsersPage.tsx +385 -0
  101. package/src/pages/ApiKeysPage.tsx +449 -0
  102. package/src/pages/ChatPage.tsx +310 -0
  103. package/src/pages/DocumentsPage.tsx +577 -0
  104. package/src/pages/LoginPage.tsx +232 -0
  105. package/src/pages/OAuthConsentPage.tsx +234 -0
  106. package/src/pages/PricingPage.tsx +314 -0
  107. package/src/pages/PrivacyPage.tsx +65 -0
  108. package/src/pages/RegisterPage.tsx +153 -0
  109. package/src/pages/ScheduledPromptsPage.tsx +702 -0
  110. package/src/pages/SharedThreadPage.tsx +116 -0
  111. package/src/pages/TeamSettingsPage.tsx +1085 -0
  112. package/src/pages/TermsPage.tsx +82 -0
  113. package/src/pages/VerifyEmailPage.tsx +202 -0
  114. package/src/routes/AcceptInviteRoute.tsx +24 -0
  115. package/src/routes/AdminDashboardRoute.tsx +24 -0
  116. package/src/routes/AdminTeamRoute.tsx +24 -0
  117. package/src/routes/AdminTeamsRoute.tsx +24 -0
  118. package/src/routes/AdminUsersRoute.tsx +24 -0
  119. package/src/routes/ApiKeysRoute.tsx +24 -0
  120. package/src/routes/AutomationsRoute.tsx +24 -0
  121. package/src/routes/ChatRoute.tsx +28 -0
  122. package/src/routes/DocumentsRoute.tsx +24 -0
  123. package/src/routes/OAuthConsentRoute.tsx +24 -0
  124. package/src/routes/PricingRoute.tsx +24 -0
  125. package/src/routes/PrivacyRoute.tsx +24 -0
  126. package/src/routes/TeamSettingsRoute.tsx +24 -0
  127. package/src/routes/TermsRoute.tsx +24 -0
  128. package/src/routes/VerifyEmailRoute.tsx +24 -0
  129. package/src/routes/index.ts +57 -0
  130. package/src/ssr-utils.tsx +84 -0
  131. package/src/ssr.ts +123 -0
  132. package/src/stores/chatStore.ts +670 -0
  133. package/src/styles/index.css +254 -0
  134. package/src/utils/api.ts +78 -0
  135. package/src/vite-env.d.ts +13 -0
@@ -0,0 +1,491 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useNavigate, useMatch, useSearchParams, Link } from 'react-router';
3
+ import {
4
+ Plus,
5
+ MessageSquare,
6
+ Settings,
7
+ Trash2,
8
+ X,
9
+ Sun,
10
+ Moon,
11
+ LogOut,
12
+ Search,
13
+ Download,
14
+ Share2,
15
+ GitBranch,
16
+ Shield,
17
+ Users,
18
+ FolderPlus,
19
+ LayoutDashboard,
20
+ Puzzle,
21
+ FileText,
22
+ Clock,
23
+ } from 'lucide-react';
24
+ import type { LucideIcon } from 'lucide-react';
25
+ import * as LucideIcons from 'lucide-react';
26
+ import type { Project } from '@chaaskit/shared';
27
+ import { useAuth } from '../contexts/AuthContext';
28
+ import { useTheme } from '../contexts/ThemeContext';
29
+ import { useConfig } from '../contexts/ConfigContext';
30
+ import { useTeam } from '../contexts/TeamContext';
31
+ import { useProject } from '../contexts/ProjectContext';
32
+ import { useChatStore } from '../stores/chatStore';
33
+ import { useSidebarPages } from '../extensions/useExtensions';
34
+ import { useAppPath } from '../hooks/useAppPath';
35
+ import { useBasePath } from '../hooks/useBasePath';
36
+ import SettingsModal from './SettingsModal';
37
+ import ExportMenu from './ExportMenu';
38
+ import ShareModal from './ShareModal';
39
+ import ProjectModal from './ProjectModal';
40
+ import ProjectFolder from './ProjectFolder';
41
+ import { TeamSwitcher } from './TeamSwitcher';
42
+ import { formatShortcut } from '../hooks/useKeyboardShortcuts';
43
+
44
+ // Helper to get icon component from string name
45
+ function getIconComponent(iconName?: string): LucideIcon {
46
+ if (!iconName) return Puzzle;
47
+ const icons = LucideIcons as unknown as Record<string, LucideIcon>;
48
+ const Icon = icons[iconName];
49
+ return Icon || Puzzle;
50
+ }
51
+
52
+ interface SidebarProps {
53
+ onClose: () => void;
54
+ onOpenSearch?: () => void;
55
+ }
56
+
57
+ export default function Sidebar({ onClose, onOpenSearch }: SidebarProps) {
58
+ const navigate = useNavigate();
59
+ const appPath = useAppPath();
60
+ const basePath = useBasePath();
61
+ // Match thread routes with and without basePath for flexibility
62
+ const threadMatch = useMatch('/thread/:threadId') || useMatch(`${appPath('/thread/:threadId')}`);
63
+ const threadId = threadMatch?.params.threadId;
64
+ const [searchParams, setSearchParams] = useSearchParams();
65
+ const { user, logout } = useAuth();
66
+ const { theme, setTheme, availableThemes } = useTheme();
67
+ const config = useConfig();
68
+ const { currentTeamId } = useTeam();
69
+ const {
70
+ projects,
71
+ currentProjectId,
72
+ setCurrentProjectId,
73
+ projectsEnabled,
74
+ } = useProject();
75
+ const { threads, isLoadingThreads, loadThreads, deleteThread, currentThread, clearCurrentThread, setCurrentTeamId, setCurrentProjectId: setChatStoreProjectId } =
76
+ useChatStore();
77
+ const extensionPages = useSidebarPages();
78
+ const [settingsOpen, setSettingsOpen] = useState(false);
79
+ const [exportOpen, setExportOpen] = useState(false);
80
+ const [shareOpen, setShareOpen] = useState(false);
81
+ const [projectModalOpen, setProjectModalOpen] = useState(false);
82
+ const [editingProject, setEditingProject] = useState<Project | null>(null);
83
+
84
+ // Auto-open settings modal if redirected from OAuth callback
85
+ useEffect(() => {
86
+ if (searchParams.get('openSettings') === 'true') {
87
+ setSettingsOpen(true);
88
+ // Clean up URL params after opening
89
+ searchParams.delete('openSettings');
90
+ setSearchParams(searchParams, { replace: true });
91
+ }
92
+ }, [searchParams, setSearchParams]);
93
+
94
+ // Sync chat store with team context
95
+ useEffect(() => {
96
+ setCurrentTeamId(currentTeamId);
97
+ }, [currentTeamId, setCurrentTeamId]);
98
+
99
+ // Sync chat store with project context
100
+ useEffect(() => {
101
+ setChatStoreProjectId(currentProjectId);
102
+ }, [currentProjectId, setChatStoreProjectId]);
103
+
104
+ // Auto-select project when viewing a thread that belongs to a project
105
+ // Clear project selection when viewing a thread outside any project
106
+ useEffect(() => {
107
+ if (currentThread) {
108
+ const threadProjectId = currentThread.projectId || null;
109
+ // Only update if different to avoid unnecessary re-renders
110
+ if (threadProjectId !== currentProjectId) {
111
+ setCurrentProjectId(threadProjectId);
112
+ }
113
+ }
114
+ }, [currentThread?.id, currentThread?.projectId, currentProjectId, setCurrentProjectId]);
115
+
116
+ useEffect(() => {
117
+ if (user) {
118
+ loadThreads(currentTeamId);
119
+ }
120
+ }, [user, currentTeamId]);
121
+
122
+ function handleNewChat(projectId?: string | null) {
123
+ // Clear current thread and navigate to welcome screen where user can select agent
124
+ // Thread will be created when user sends first message with the selected agent
125
+ clearCurrentThread();
126
+ // Set project context: explicit projectId if provided, otherwise clear selection
127
+ // This ensures main "New Chat" button clears project context
128
+ setCurrentProjectId(projectId ?? null);
129
+ navigate(appPath('/'));
130
+ onClose();
131
+ }
132
+
133
+ function handleNewProject() {
134
+ setEditingProject(null);
135
+ setProjectModalOpen(true);
136
+ }
137
+
138
+ function handleEditProject(project: Project) {
139
+ setEditingProject(project);
140
+ setProjectModalOpen(true);
141
+ }
142
+
143
+ function handleCloseProjectModal() {
144
+ setProjectModalOpen(false);
145
+ setEditingProject(null);
146
+ }
147
+
148
+ async function handleDeleteThread(id: string, e: React.MouseEvent) {
149
+ e.stopPropagation();
150
+ if (confirm('Are you sure you want to delete this conversation?')) {
151
+ await deleteThread(id);
152
+ if (threadId === id) {
153
+ navigate(appPath('/'));
154
+ }
155
+ }
156
+ }
157
+
158
+ function handleThreadClick(id: string) {
159
+ navigate(appPath(`/thread/${id}`));
160
+ onClose();
161
+ }
162
+
163
+ function toggleTheme() {
164
+ const currentIndex = availableThemes.indexOf(theme);
165
+ const nextIndex = (currentIndex + 1) % availableThemes.length;
166
+ setTheme(availableThemes[nextIndex]!);
167
+ }
168
+
169
+ return (
170
+ <div className="flex h-full flex-col">
171
+ {/* Header */}
172
+ <div className="flex items-center justify-between border-b border-border px-3 py-2.5">
173
+ {basePath !== '/' ? (
174
+ <a href="/" className="flex items-center gap-2 hover:opacity-80">
175
+ {config.ui.logo && (
176
+ <img
177
+ src={typeof config.ui.logo === 'string' ? config.ui.logo : (theme === 'dark' ? config.ui.logo.dark : config.ui.logo.light)}
178
+ alt={config.app.name}
179
+ className="h-6 w-6 rounded object-contain"
180
+ />
181
+ )}
182
+ <h1 className="text-sm font-semibold text-text-primary">
183
+ {config.app.name}
184
+ </h1>
185
+ </a>
186
+ ) : (
187
+ <div className="flex items-center gap-2">
188
+ {config.ui.logo && (
189
+ <img
190
+ src={typeof config.ui.logo === 'string' ? config.ui.logo : (theme === 'dark' ? config.ui.logo.dark : config.ui.logo.light)}
191
+ alt={config.app.name}
192
+ className="h-6 w-6 rounded object-contain"
193
+ />
194
+ )}
195
+ <h1 className="text-sm font-semibold text-text-primary">
196
+ {config.app.name}
197
+ </h1>
198
+ </div>
199
+ )}
200
+ <button
201
+ onClick={onClose}
202
+ className="p-1 text-text-secondary hover:text-text-primary lg:hidden"
203
+ >
204
+ <X size={18} />
205
+ </button>
206
+ </div>
207
+
208
+ {/* Team Switcher */}
209
+ {user && config.teams?.enabled && (
210
+ <div className="px-3 pt-3 pb-1">
211
+ <TeamSwitcher />
212
+ </div>
213
+ )}
214
+
215
+ {/* New Chat & New Project Buttons */}
216
+ <div className="space-y-1 p-3">
217
+ <button
218
+ onClick={() => handleNewChat()}
219
+ className="flex w-full items-center justify-center gap-2 rounded-md border border-border bg-background px-3 py-1.5 text-xs text-text-primary hover:bg-background-secondary active:bg-background-secondary"
220
+ >
221
+ <Plus size={14} />
222
+ New Chat
223
+ <span className="ml-auto text-[10px] text-text-muted">{formatShortcut('N')}</span>
224
+ </button>
225
+
226
+ {/* New Project Button */}
227
+ {projectsEnabled && (
228
+ <button
229
+ onClick={handleNewProject}
230
+ className="flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
231
+ >
232
+ <FolderPlus size={14} />
233
+ New Project
234
+ </button>
235
+ )}
236
+
237
+ {/* Search Button */}
238
+ {onOpenSearch && (
239
+ <button
240
+ onClick={onOpenSearch}
241
+ className="flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
242
+ >
243
+ <Search size={14} />
244
+ Search
245
+ <span className="ml-auto text-[10px] text-text-muted">{formatShortcut('K')}</span>
246
+ </button>
247
+ )}
248
+ </div>
249
+
250
+ {/* Thread List with Projects */}
251
+ <div className="flex-1 overflow-y-auto px-2">
252
+ {isLoadingThreads ? (
253
+ <div className="flex items-center justify-center py-4">
254
+ <div className="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent" />
255
+ </div>
256
+ ) : (
257
+ <div className="space-y-1">
258
+ {/* Project Folders */}
259
+ {projectsEnabled && projects.length > 0 && (
260
+ <div className="mb-2">
261
+ {projects.map((project) => (
262
+ <ProjectFolder
263
+ key={project.id}
264
+ project={project}
265
+ threads={threads}
266
+ selectedThreadId={threadId}
267
+ onEditProject={() => handleEditProject(project)}
268
+ onNewThread={() => handleNewChat(project.id)}
269
+ onSelectThread={handleThreadClick}
270
+ onDeleteThread={handleDeleteThread}
271
+ />
272
+ ))}
273
+ </div>
274
+ )}
275
+
276
+ {/* Unorganized Threads (no project) */}
277
+ {(() => {
278
+ const unorganizedThreads = threads.filter((t) => !t.projectId);
279
+ if (unorganizedThreads.length === 0 && (!projectsEnabled || projects.length === 0)) {
280
+ return (
281
+ <p className="px-2 py-4 text-sm text-text-muted">
282
+ No conversations yet
283
+ </p>
284
+ );
285
+ }
286
+ if (unorganizedThreads.length === 0) {
287
+ return null;
288
+ }
289
+ return (
290
+ <div className="space-y-0.5">
291
+ {projectsEnabled && projects.length > 0 && (
292
+ <div className="px-2 py-1 text-xs font-medium text-text-muted uppercase tracking-wider">
293
+ Other Threads
294
+ </div>
295
+ )}
296
+ {unorganizedThreads.map((thread) => (
297
+ <div
298
+ key={thread.id}
299
+ onClick={() => handleThreadClick(thread.id)}
300
+ className={`group flex cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 ${
301
+ threadId === thread.id
302
+ ? 'bg-background-secondary text-text-primary'
303
+ : 'text-text-secondary hover:bg-background-secondary hover:text-text-primary'
304
+ }`}
305
+ >
306
+ {thread.parentThreadId ? (
307
+ <span title="Branched conversation">
308
+ <GitBranch size={14} className="flex-shrink-0" />
309
+ </span>
310
+ ) : (
311
+ <MessageSquare size={14} className="flex-shrink-0" />
312
+ )}
313
+ <span className="flex-1 truncate text-xs">{thread.title}</span>
314
+ <button
315
+ onClick={(e) => handleDeleteThread(thread.id, e)}
316
+ className="rounded p-1 text-text-muted opacity-0 hover:bg-error/10 hover:text-error group-hover:opacity-100 touch-device:opacity-100 transition-opacity"
317
+ >
318
+ <Trash2 size={12} />
319
+ </button>
320
+ </div>
321
+ ))}
322
+ </div>
323
+ );
324
+ })()}
325
+ </div>
326
+ )}
327
+ </div>
328
+
329
+ {/* Footer */}
330
+ <div className="border-t border-border p-3">
331
+ {/* Theme Toggle */}
332
+ {config.theming.allowUserThemeSwitch && (
333
+ <button
334
+ onClick={toggleTheme}
335
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
336
+ >
337
+ {theme === 'dark' ? <Sun size={14} /> : <Moon size={14} />}
338
+ {theme === 'dark' ? 'Light Mode' : 'Dark Mode'}
339
+ </button>
340
+ )}
341
+
342
+ {/* Share & Export (only show if there's a current thread) */}
343
+ {currentThread && currentThread.id !== 'pending' && (
344
+ <>
345
+ {config.sharing?.enabled && (
346
+ <button
347
+ onClick={() => setShareOpen(true)}
348
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
349
+ >
350
+ <Share2 size={14} />
351
+ Share
352
+ </button>
353
+ )}
354
+ <button
355
+ onClick={() => setExportOpen(true)}
356
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
357
+ >
358
+ <Download size={14} />
359
+ Export Chat
360
+ </button>
361
+ </>
362
+ )}
363
+
364
+ {/* Team Settings - only show when viewing a team */}
365
+ {currentTeamId && (
366
+ <Link
367
+ to={appPath(`/team/${currentTeamId}/settings`)}
368
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
369
+ >
370
+ <Users size={14} />
371
+ Team Settings
372
+ </Link>
373
+ )}
374
+
375
+ {/* Admin Dashboard - show for site admins (config or database flag) */}
376
+ {(user?.isAdmin || config.admin?.emails?.some(
377
+ (email: string) => email.toLowerCase() === user?.email?.toLowerCase()
378
+ )) && (
379
+ <Link
380
+ to={appPath('/admin')}
381
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
382
+ >
383
+ <Shield size={14} />
384
+ Admin
385
+ </Link>
386
+ )}
387
+
388
+ {/* Extension Pages */}
389
+ {extensionPages.map(page => {
390
+ const IconComponent = getIconComponent(page.icon);
391
+ return (
392
+ <Link
393
+ key={page.id}
394
+ to={page.path}
395
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
396
+ >
397
+ <IconComponent size={14} />
398
+ {page.label}
399
+ </Link>
400
+ );
401
+ })}
402
+
403
+ {/* Documents - only show if enabled */}
404
+ {config.documents?.enabled && (
405
+ <Link
406
+ to={appPath('/documents')}
407
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
408
+ >
409
+ <FileText size={14} />
410
+ Documents
411
+ </Link>
412
+ )}
413
+
414
+ {/* Scheduled Prompts / Automations - only show if enabled */}
415
+ {config.scheduledPrompts?.enabled && (
416
+ <Link
417
+ to={appPath('/automations')}
418
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
419
+ >
420
+ <Clock size={14} />
421
+ {config.scheduledPrompts.featureName || 'Automations'}
422
+ </Link>
423
+ )}
424
+
425
+ {/* Settings */}
426
+ <button
427
+ onClick={() => setSettingsOpen(true)}
428
+ className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
429
+ >
430
+ <Settings size={14} />
431
+ Settings
432
+ </button>
433
+
434
+ {/* User info & logout */}
435
+ {user && (
436
+ <div className="mt-2 flex items-center justify-between rounded-md bg-background-secondary px-2.5 py-2">
437
+ <div className="min-w-0 flex-1">
438
+ <p className="truncate text-xs font-medium text-text-primary">
439
+ {user.name || user.email}
440
+ </p>
441
+ <p className="text-[10px] text-text-muted">{user.plan} plan</p>
442
+ </div>
443
+ <button
444
+ onClick={logout}
445
+ className="ml-2 flex-shrink-0 rounded p-1 text-text-muted hover:bg-error/10 hover:text-error active:bg-error/10"
446
+ title="Sign out"
447
+ aria-label="Sign out"
448
+ >
449
+ <LogOut size={14} />
450
+ </button>
451
+ </div>
452
+ )}
453
+ </div>
454
+
455
+ {/* Settings Modal */}
456
+ <SettingsModal
457
+ isOpen={settingsOpen}
458
+ onClose={() => setSettingsOpen(false)}
459
+ />
460
+
461
+ {/* Export Menu */}
462
+ {currentThread && (
463
+ <ExportMenu
464
+ threadId={currentThread.id}
465
+ threadTitle={currentThread.title}
466
+ isOpen={exportOpen}
467
+ onClose={() => setExportOpen(false)}
468
+ />
469
+ )}
470
+
471
+ {/* Share Modal */}
472
+ {currentThread && (
473
+ <ShareModal
474
+ threadId={currentThread.id}
475
+ threadTitle={currentThread.title}
476
+ isOpen={shareOpen}
477
+ onClose={() => setShareOpen(false)}
478
+ />
479
+ )}
480
+
481
+ {/* Project Modal */}
482
+ {projectsEnabled && (
483
+ <ProjectModal
484
+ isOpen={projectModalOpen}
485
+ onClose={handleCloseProjectModal}
486
+ project={editingProject}
487
+ />
488
+ )}
489
+ </div>
490
+ );
491
+ }