@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.
- package/dist/favicon.svg +11 -0
- package/dist/index.html +17 -0
- package/dist/lib/LoadingSkeletons-IcIC2JPq.js +132 -0
- package/dist/lib/LoadingSkeletons-IcIC2JPq.js.map +1 -0
- package/dist/lib/ServerThemeProvider-DNF0LAyk.js +42 -0
- package/dist/lib/ServerThemeProvider-DNF0LAyk.js.map +1 -0
- package/dist/lib/extensions.js +10 -0
- package/dist/lib/extensions.js.map +1 -0
- package/dist/lib/favicon.svg +11 -0
- package/dist/lib/index.js +74126 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logo.svg +12 -0
- package/dist/lib/routes/AcceptInviteRoute.js +19 -0
- package/dist/lib/routes/AcceptInviteRoute.js.map +1 -0
- package/dist/lib/routes/AdminDashboardRoute.js +19 -0
- package/dist/lib/routes/AdminDashboardRoute.js.map +1 -0
- package/dist/lib/routes/AdminTeamRoute.js +19 -0
- package/dist/lib/routes/AdminTeamRoute.js.map +1 -0
- package/dist/lib/routes/AdminTeamsRoute.js +19 -0
- package/dist/lib/routes/AdminTeamsRoute.js.map +1 -0
- package/dist/lib/routes/AdminUsersRoute.js +19 -0
- package/dist/lib/routes/AdminUsersRoute.js.map +1 -0
- package/dist/lib/routes/ApiKeysRoute.js +19 -0
- package/dist/lib/routes/ApiKeysRoute.js.map +1 -0
- package/dist/lib/routes/AutomationsRoute.js +19 -0
- package/dist/lib/routes/AutomationsRoute.js.map +1 -0
- package/dist/lib/routes/ChatRoute.js +19 -0
- package/dist/lib/routes/ChatRoute.js.map +1 -0
- package/dist/lib/routes/DocumentsRoute.js +19 -0
- package/dist/lib/routes/DocumentsRoute.js.map +1 -0
- package/dist/lib/routes/OAuthConsentRoute.js +19 -0
- package/dist/lib/routes/OAuthConsentRoute.js.map +1 -0
- package/dist/lib/routes/PricingRoute.js +19 -0
- package/dist/lib/routes/PricingRoute.js.map +1 -0
- package/dist/lib/routes/PrivacyRoute.js +19 -0
- package/dist/lib/routes/PrivacyRoute.js.map +1 -0
- package/dist/lib/routes/TeamSettingsRoute.js +19 -0
- package/dist/lib/routes/TeamSettingsRoute.js.map +1 -0
- package/dist/lib/routes/TermsRoute.js +19 -0
- package/dist/lib/routes/TermsRoute.js.map +1 -0
- package/dist/lib/routes/VerifyEmailRoute.js +19 -0
- package/dist/lib/routes/VerifyEmailRoute.js.map +1 -0
- package/dist/lib/routes.js +79 -0
- package/dist/lib/routes.js.map +1 -0
- package/dist/lib/ssr-utils.js +29 -0
- package/dist/lib/ssr-utils.js.map +1 -0
- package/dist/lib/ssr.js +60 -0
- package/dist/lib/ssr.js.map +1 -0
- package/dist/lib/styles.css +2410 -0
- package/dist/lib/useExtensions-B5nX_8XD.js +155 -0
- package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -0
- package/dist/logo.svg +12 -0
- package/package.json +84 -0
- package/src/components/AgentSelector.tsx +90 -0
- package/src/components/BranchModal.tsx +129 -0
- package/src/components/ClientOnly.tsx +27 -0
- package/src/components/ExportMenu.tsx +122 -0
- package/src/components/LoadingSkeletons.tsx +110 -0
- package/src/components/MCPCredentialsSection.tsx +309 -0
- package/src/components/MentionChip.tsx +149 -0
- package/src/components/MentionDropdown.tsx +175 -0
- package/src/components/MentionInput.tsx +293 -0
- package/src/components/MessageItem.tsx +300 -0
- package/src/components/MessageList.tsx +159 -0
- package/src/components/OAuthAppsSection.tsx +124 -0
- package/src/components/ProjectFolder.tsx +141 -0
- package/src/components/ProjectModal.tsx +296 -0
- package/src/components/SSRMessageList.tsx +153 -0
- package/src/components/SearchModal.tsx +173 -0
- package/src/components/SettingsModal.tsx +412 -0
- package/src/components/ShareModal.tsx +280 -0
- package/src/components/Sidebar.tsx +491 -0
- package/src/components/TeamSwitcher.tsx +273 -0
- package/src/components/ToolCallDisplay.tsx +473 -0
- package/src/components/ToolConfirmationModal.tsx +130 -0
- package/src/components/UsageChart.tsx +177 -0
- package/src/components/content/CodeBlock.tsx +69 -0
- package/src/components/content/MarkdownRenderer.tsx +64 -0
- package/src/components/content/SSRMarkdownRenderer.tsx +158 -0
- package/src/contexts/AuthContext.tsx +119 -0
- package/src/contexts/ConfigContext.tsx +214 -0
- package/src/contexts/ProjectContext.tsx +167 -0
- package/src/contexts/ServerConfigProvider.tsx +41 -0
- package/src/contexts/ServerThemeProvider.tsx +47 -0
- package/src/contexts/TeamContext.tsx +255 -0
- package/src/contexts/ThemeContext.tsx +113 -0
- package/src/extensions/index.ts +15 -0
- package/src/extensions/registry.ts +187 -0
- package/src/extensions/useExtensions.ts +52 -0
- package/src/hooks/useAppPath.ts +34 -0
- package/src/hooks/useBasePath.ts +13 -0
- package/src/hooks/useKeyboardShortcuts.ts +50 -0
- package/src/hooks/useMentionSearch.ts +106 -0
- package/src/index.tsx +116 -0
- package/src/layouts/MainLayout.tsx +98 -0
- package/src/pages/AcceptInvitePage.tsx +175 -0
- package/src/pages/AdminDashboardPage.tsx +362 -0
- package/src/pages/AdminTeamPage.tsx +304 -0
- package/src/pages/AdminTeamsPage.tsx +242 -0
- package/src/pages/AdminUsersPage.tsx +385 -0
- package/src/pages/ApiKeysPage.tsx +449 -0
- package/src/pages/ChatPage.tsx +310 -0
- package/src/pages/DocumentsPage.tsx +577 -0
- package/src/pages/LoginPage.tsx +232 -0
- package/src/pages/OAuthConsentPage.tsx +234 -0
- package/src/pages/PricingPage.tsx +314 -0
- package/src/pages/PrivacyPage.tsx +65 -0
- package/src/pages/RegisterPage.tsx +153 -0
- package/src/pages/ScheduledPromptsPage.tsx +702 -0
- package/src/pages/SharedThreadPage.tsx +116 -0
- package/src/pages/TeamSettingsPage.tsx +1085 -0
- package/src/pages/TermsPage.tsx +82 -0
- package/src/pages/VerifyEmailPage.tsx +202 -0
- package/src/routes/AcceptInviteRoute.tsx +24 -0
- package/src/routes/AdminDashboardRoute.tsx +24 -0
- package/src/routes/AdminTeamRoute.tsx +24 -0
- package/src/routes/AdminTeamsRoute.tsx +24 -0
- package/src/routes/AdminUsersRoute.tsx +24 -0
- package/src/routes/ApiKeysRoute.tsx +24 -0
- package/src/routes/AutomationsRoute.tsx +24 -0
- package/src/routes/ChatRoute.tsx +28 -0
- package/src/routes/DocumentsRoute.tsx +24 -0
- package/src/routes/OAuthConsentRoute.tsx +24 -0
- package/src/routes/PricingRoute.tsx +24 -0
- package/src/routes/PrivacyRoute.tsx +24 -0
- package/src/routes/TeamSettingsRoute.tsx +24 -0
- package/src/routes/TermsRoute.tsx +24 -0
- package/src/routes/VerifyEmailRoute.tsx +24 -0
- package/src/routes/index.ts +57 -0
- package/src/ssr-utils.tsx +84 -0
- package/src/ssr.ts +123 -0
- package/src/stores/chatStore.ts +670 -0
- package/src/styles/index.css +254 -0
- package/src/utils/api.ts +78 -0
- 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
|
+
}
|