@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,141 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router';
|
|
3
|
+
import { ChevronDown, ChevronRight, Folder, MessageSquare, Settings, Plus, Trash2, GitBranch } from 'lucide-react';
|
|
4
|
+
import type { ProjectWithThreadCount, ThreadSummary } from '@chaaskit/shared';
|
|
5
|
+
import { useAppPath } from '../hooks/useAppPath';
|
|
6
|
+
|
|
7
|
+
interface ProjectFolderProps {
|
|
8
|
+
project: ProjectWithThreadCount;
|
|
9
|
+
threads: ThreadSummary[];
|
|
10
|
+
selectedThreadId?: string;
|
|
11
|
+
onEditProject: () => void;
|
|
12
|
+
onNewThread: () => void;
|
|
13
|
+
onSelectThread: (threadId: string) => void;
|
|
14
|
+
onDeleteThread: (threadId: string, e: React.MouseEvent) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function ProjectFolder({
|
|
18
|
+
project,
|
|
19
|
+
threads,
|
|
20
|
+
selectedThreadId,
|
|
21
|
+
onEditProject,
|
|
22
|
+
onNewThread,
|
|
23
|
+
onSelectThread,
|
|
24
|
+
onDeleteThread,
|
|
25
|
+
}: ProjectFolderProps) {
|
|
26
|
+
const [isExpanded, setIsExpanded] = useState(true);
|
|
27
|
+
const navigate = useNavigate();
|
|
28
|
+
const appPath = useAppPath();
|
|
29
|
+
|
|
30
|
+
const projectThreads = threads.filter((t) => t.projectId === project.id);
|
|
31
|
+
|
|
32
|
+
function handleToggle() {
|
|
33
|
+
setIsExpanded(!isExpanded);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function handleEditClick(e: React.MouseEvent) {
|
|
37
|
+
e.stopPropagation();
|
|
38
|
+
onEditProject();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleNewThreadClick(e: React.MouseEvent) {
|
|
42
|
+
e.stopPropagation();
|
|
43
|
+
onNewThread();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function handleThreadClick(threadId: string) {
|
|
47
|
+
onSelectThread(threadId);
|
|
48
|
+
navigate(appPath(`/thread/${threadId}`));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className="mb-1">
|
|
53
|
+
{/* Project Header */}
|
|
54
|
+
<div
|
|
55
|
+
onClick={handleToggle}
|
|
56
|
+
className="group flex items-center gap-2 rounded-lg px-2 py-1.5 cursor-pointer transition-colors text-text-secondary hover:bg-background-secondary hover:text-text-primary"
|
|
57
|
+
>
|
|
58
|
+
{/* Expand/collapse chevron */}
|
|
59
|
+
<span className="p-0.5">
|
|
60
|
+
{isExpanded ? (
|
|
61
|
+
<ChevronDown size={14} />
|
|
62
|
+
) : (
|
|
63
|
+
<ChevronRight size={14} />
|
|
64
|
+
)}
|
|
65
|
+
</span>
|
|
66
|
+
|
|
67
|
+
{/* Folder icon with project color */}
|
|
68
|
+
<Folder
|
|
69
|
+
size={16}
|
|
70
|
+
className="flex-shrink-0"
|
|
71
|
+
style={{ color: project.color }}
|
|
72
|
+
fill={project.color}
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
{/* Project name */}
|
|
76
|
+
<span className="flex-1 text-sm truncate">{project.name}</span>
|
|
77
|
+
|
|
78
|
+
{/* Thread count badge */}
|
|
79
|
+
<span className="text-xs text-text-muted px-1.5 py-0.5 bg-background-secondary rounded">
|
|
80
|
+
{project.threadCount}
|
|
81
|
+
</span>
|
|
82
|
+
|
|
83
|
+
{/* Action buttons (visible on hover) */}
|
|
84
|
+
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
85
|
+
<button
|
|
86
|
+
onClick={handleNewThreadClick}
|
|
87
|
+
className="p-1 rounded hover:bg-black/10 dark:hover:bg-white/10"
|
|
88
|
+
title="New thread in project"
|
|
89
|
+
>
|
|
90
|
+
<Plus size={14} />
|
|
91
|
+
</button>
|
|
92
|
+
<button
|
|
93
|
+
onClick={handleEditClick}
|
|
94
|
+
className="p-1 rounded hover:bg-black/10 dark:hover:bg-white/10"
|
|
95
|
+
title="Edit project"
|
|
96
|
+
>
|
|
97
|
+
<Settings size={14} />
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Project Threads */}
|
|
103
|
+
{isExpanded && (
|
|
104
|
+
<div className="ml-6 pl-2 border-l border-border">
|
|
105
|
+
{projectThreads.length === 0 ? (
|
|
106
|
+
<div className="py-2 px-2 text-xs text-text-muted italic">
|
|
107
|
+
No threads yet
|
|
108
|
+
</div>
|
|
109
|
+
) : (
|
|
110
|
+
projectThreads.map((thread) => (
|
|
111
|
+
<div
|
|
112
|
+
key={thread.id}
|
|
113
|
+
onClick={() => handleThreadClick(thread.id)}
|
|
114
|
+
className={`group flex items-center gap-2 rounded-lg px-2 py-1.5 cursor-pointer transition-colors ${
|
|
115
|
+
selectedThreadId === thread.id
|
|
116
|
+
? 'bg-background-secondary text-text-primary'
|
|
117
|
+
: 'text-text-secondary hover:bg-background-secondary hover:text-text-primary'
|
|
118
|
+
}`}
|
|
119
|
+
>
|
|
120
|
+
{thread.parentThreadId ? (
|
|
121
|
+
<span title="Branched conversation">
|
|
122
|
+
<GitBranch size={14} className="flex-shrink-0" />
|
|
123
|
+
</span>
|
|
124
|
+
) : (
|
|
125
|
+
<MessageSquare size={14} className="flex-shrink-0" />
|
|
126
|
+
)}
|
|
127
|
+
<span className="flex-1 text-xs truncate">{thread.title}</span>
|
|
128
|
+
<button
|
|
129
|
+
onClick={(e) => onDeleteThread(thread.id, e)}
|
|
130
|
+
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"
|
|
131
|
+
>
|
|
132
|
+
<Trash2 size={12} />
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
))
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { X, FolderPlus, Loader2, Trash2 } from 'lucide-react';
|
|
4
|
+
import type { Project, ProjectSharing } from '@chaaskit/shared';
|
|
5
|
+
import { useProject } from '../contexts/ProjectContext';
|
|
6
|
+
import { useTeam } from '../contexts/TeamContext';
|
|
7
|
+
|
|
8
|
+
interface ProjectModalProps {
|
|
9
|
+
isOpen: boolean;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
project?: Project | null; // If provided, we're editing; otherwise creating
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function ProjectModal({ isOpen, onClose, project }: ProjectModalProps) {
|
|
15
|
+
const { createProject, updateProject, archiveProject, projectColors } = useProject();
|
|
16
|
+
const { currentTeamId, getCurrentTeamRole } = useTeam();
|
|
17
|
+
const [name, setName] = useState('');
|
|
18
|
+
const [color, setColor] = useState(projectColors[0] || '#3b82f6');
|
|
19
|
+
const [context, setContext] = useState('');
|
|
20
|
+
const [sharing, setSharing] = useState<ProjectSharing>('private');
|
|
21
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
22
|
+
const [showArchiveConfirm, setShowArchiveConfirm] = useState(false);
|
|
23
|
+
|
|
24
|
+
const isEditing = !!project;
|
|
25
|
+
const teamRole = getCurrentTeamRole();
|
|
26
|
+
const canEdit = !project || project.userId === undefined ||
|
|
27
|
+
(currentTeamId && (teamRole === 'owner' || teamRole === 'admin'));
|
|
28
|
+
|
|
29
|
+
// Reset form when modal opens/closes or project changes
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (isOpen) {
|
|
32
|
+
if (project) {
|
|
33
|
+
setName(project.name);
|
|
34
|
+
setColor(project.color);
|
|
35
|
+
setContext(project.context || '');
|
|
36
|
+
setSharing(project.sharing);
|
|
37
|
+
} else {
|
|
38
|
+
setName('');
|
|
39
|
+
setColor(projectColors[0] || '#3b82f6');
|
|
40
|
+
setContext('');
|
|
41
|
+
setSharing('private');
|
|
42
|
+
}
|
|
43
|
+
setShowArchiveConfirm(false);
|
|
44
|
+
}
|
|
45
|
+
}, [isOpen, project, projectColors]);
|
|
46
|
+
|
|
47
|
+
if (!isOpen) return null;
|
|
48
|
+
|
|
49
|
+
async function handleSubmit(e: React.FormEvent) {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
if (!name.trim()) return;
|
|
52
|
+
|
|
53
|
+
setIsLoading(true);
|
|
54
|
+
try {
|
|
55
|
+
if (isEditing && project) {
|
|
56
|
+
await updateProject(project.id, {
|
|
57
|
+
name: name.trim(),
|
|
58
|
+
color,
|
|
59
|
+
context: context.trim() || null,
|
|
60
|
+
sharing: currentTeamId ? sharing : 'private',
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
await createProject({
|
|
64
|
+
name: name.trim(),
|
|
65
|
+
color,
|
|
66
|
+
context: context.trim() || undefined,
|
|
67
|
+
sharing: currentTeamId ? sharing : 'private',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
onClose();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Failed to save project:', error);
|
|
73
|
+
} finally {
|
|
74
|
+
setIsLoading(false);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function handleArchive() {
|
|
79
|
+
if (!project) return;
|
|
80
|
+
|
|
81
|
+
setIsLoading(true);
|
|
82
|
+
try {
|
|
83
|
+
await archiveProject(project.id);
|
|
84
|
+
onClose();
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Failed to archive project:', error);
|
|
87
|
+
} finally {
|
|
88
|
+
setIsLoading(false);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return createPortal(
|
|
93
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
|
|
94
|
+
<div className="w-full max-w-lg rounded-lg bg-background border border-border shadow-xl">
|
|
95
|
+
{/* Header */}
|
|
96
|
+
<div className="flex items-center justify-between border-b border-border p-4">
|
|
97
|
+
<div className="flex items-center gap-2">
|
|
98
|
+
<FolderPlus size={20} className="text-primary" />
|
|
99
|
+
<h2 className="text-lg font-semibold text-text-primary">
|
|
100
|
+
{isEditing ? 'Edit Project' : 'Create Project'}
|
|
101
|
+
</h2>
|
|
102
|
+
</div>
|
|
103
|
+
<button
|
|
104
|
+
onClick={onClose}
|
|
105
|
+
className="rounded p-1 text-text-muted hover:bg-background-secondary hover:text-text-primary"
|
|
106
|
+
>
|
|
107
|
+
<X size={20} />
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Content */}
|
|
112
|
+
<form onSubmit={handleSubmit} className="p-4 space-y-4">
|
|
113
|
+
{/* Name */}
|
|
114
|
+
<div>
|
|
115
|
+
<label className="block text-sm font-medium text-text-secondary mb-2">
|
|
116
|
+
Project Name
|
|
117
|
+
</label>
|
|
118
|
+
<input
|
|
119
|
+
type="text"
|
|
120
|
+
value={name}
|
|
121
|
+
onChange={(e) => setName(e.target.value)}
|
|
122
|
+
placeholder="My Project"
|
|
123
|
+
className="w-full rounded-lg border border-border bg-background p-3 text-sm text-text-primary placeholder-text-muted focus:border-primary focus:outline-none"
|
|
124
|
+
autoFocus
|
|
125
|
+
required
|
|
126
|
+
maxLength={100}
|
|
127
|
+
disabled={!canEdit}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* Color Picker */}
|
|
132
|
+
<div>
|
|
133
|
+
<label className="block text-sm font-medium text-text-secondary mb-2">
|
|
134
|
+
Color
|
|
135
|
+
</label>
|
|
136
|
+
<div className="flex gap-2 flex-wrap">
|
|
137
|
+
{projectColors.map((c) => (
|
|
138
|
+
<button
|
|
139
|
+
key={c}
|
|
140
|
+
type="button"
|
|
141
|
+
onClick={() => canEdit && setColor(c)}
|
|
142
|
+
className={`w-8 h-8 rounded-full border-2 transition-all ${
|
|
143
|
+
color === c
|
|
144
|
+
? 'border-text-primary scale-110'
|
|
145
|
+
: 'border-transparent hover:scale-105'
|
|
146
|
+
} ${!canEdit ? 'cursor-not-allowed opacity-50' : ''}`}
|
|
147
|
+
style={{ backgroundColor: c }}
|
|
148
|
+
disabled={!canEdit}
|
|
149
|
+
/>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Context */}
|
|
155
|
+
<div>
|
|
156
|
+
<label className="block text-sm font-medium text-text-secondary mb-2">
|
|
157
|
+
Project Context (Optional)
|
|
158
|
+
</label>
|
|
159
|
+
<textarea
|
|
160
|
+
value={context}
|
|
161
|
+
onChange={(e) => setContext(e.target.value)}
|
|
162
|
+
placeholder="Add context that the AI will use for all conversations in this project..."
|
|
163
|
+
className="w-full rounded-lg border border-border bg-background p-3 text-sm text-text-primary placeholder-text-muted focus:border-primary focus:outline-none resize-none"
|
|
164
|
+
rows={4}
|
|
165
|
+
maxLength={10000}
|
|
166
|
+
disabled={!canEdit}
|
|
167
|
+
/>
|
|
168
|
+
<p className="mt-1 text-xs text-text-muted">
|
|
169
|
+
This context is included in all AI conversations within this project.
|
|
170
|
+
</p>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
{/* Sharing (only show for team projects) */}
|
|
174
|
+
{currentTeamId && (
|
|
175
|
+
<div>
|
|
176
|
+
<label className="block text-sm font-medium text-text-secondary mb-2">
|
|
177
|
+
Sharing
|
|
178
|
+
</label>
|
|
179
|
+
<div className="space-y-2">
|
|
180
|
+
<label className="flex items-start gap-3 cursor-pointer">
|
|
181
|
+
<input
|
|
182
|
+
type="radio"
|
|
183
|
+
name="sharing"
|
|
184
|
+
value="private"
|
|
185
|
+
checked={sharing === 'private'}
|
|
186
|
+
onChange={() => setSharing('private')}
|
|
187
|
+
className="mt-1"
|
|
188
|
+
disabled={!canEdit}
|
|
189
|
+
/>
|
|
190
|
+
<div>
|
|
191
|
+
<div className="text-sm font-medium text-text-primary">Private</div>
|
|
192
|
+
<div className="text-xs text-text-muted">
|
|
193
|
+
Only you can see this project and its threads
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</label>
|
|
197
|
+
<label className="flex items-start gap-3 cursor-pointer">
|
|
198
|
+
<input
|
|
199
|
+
type="radio"
|
|
200
|
+
name="sharing"
|
|
201
|
+
value="team"
|
|
202
|
+
checked={sharing === 'team'}
|
|
203
|
+
onChange={() => setSharing('team')}
|
|
204
|
+
className="mt-1"
|
|
205
|
+
disabled={!canEdit}
|
|
206
|
+
/>
|
|
207
|
+
<div>
|
|
208
|
+
<div className="text-sm font-medium text-text-primary">Team</div>
|
|
209
|
+
<div className="text-xs text-text-muted">
|
|
210
|
+
All team members can see and contribute to this project
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</label>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
|
|
218
|
+
{/* Archive section (only for editing) */}
|
|
219
|
+
{isEditing && canEdit && (
|
|
220
|
+
<div className="pt-4 border-t border-border">
|
|
221
|
+
{!showArchiveConfirm ? (
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
onClick={() => setShowArchiveConfirm(true)}
|
|
225
|
+
className="flex items-center gap-2 text-sm text-error hover:text-error/80"
|
|
226
|
+
>
|
|
227
|
+
<Trash2 size={16} />
|
|
228
|
+
Archive Project
|
|
229
|
+
</button>
|
|
230
|
+
) : (
|
|
231
|
+
<div className="p-3 bg-error/10 rounded-lg border border-error/20">
|
|
232
|
+
<p className="text-sm text-text-primary mb-3">
|
|
233
|
+
Are you sure you want to archive this project? All threads in this project will also be archived.
|
|
234
|
+
</p>
|
|
235
|
+
<div className="flex gap-2">
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
onClick={handleArchive}
|
|
239
|
+
disabled={isLoading}
|
|
240
|
+
className="flex items-center gap-2 rounded-lg bg-error px-3 py-1.5 text-sm font-medium text-white hover:bg-error/80 disabled:opacity-50"
|
|
241
|
+
>
|
|
242
|
+
{isLoading ? (
|
|
243
|
+
<Loader2 size={14} className="animate-spin" />
|
|
244
|
+
) : (
|
|
245
|
+
<Trash2 size={14} />
|
|
246
|
+
)}
|
|
247
|
+
Archive
|
|
248
|
+
</button>
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
onClick={() => setShowArchiveConfirm(false)}
|
|
252
|
+
className="rounded-lg px-3 py-1.5 text-sm font-medium text-text-secondary hover:bg-background-secondary"
|
|
253
|
+
>
|
|
254
|
+
Cancel
|
|
255
|
+
</button>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{/* Footer */}
|
|
263
|
+
<div className="flex justify-end gap-2 pt-4">
|
|
264
|
+
<button
|
|
265
|
+
type="button"
|
|
266
|
+
onClick={onClose}
|
|
267
|
+
className="rounded-lg px-4 py-2 text-sm font-medium text-text-secondary hover:bg-background-secondary"
|
|
268
|
+
>
|
|
269
|
+
Cancel
|
|
270
|
+
</button>
|
|
271
|
+
{canEdit && (
|
|
272
|
+
<button
|
|
273
|
+
type="submit"
|
|
274
|
+
disabled={isLoading || !name.trim()}
|
|
275
|
+
className="flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover disabled:opacity-50"
|
|
276
|
+
>
|
|
277
|
+
{isLoading ? (
|
|
278
|
+
<>
|
|
279
|
+
<Loader2 size={16} className="animate-spin" />
|
|
280
|
+
{isEditing ? 'Saving...' : 'Creating...'}
|
|
281
|
+
</>
|
|
282
|
+
) : (
|
|
283
|
+
<>
|
|
284
|
+
<FolderPlus size={16} />
|
|
285
|
+
{isEditing ? 'Save Changes' : 'Create Project'}
|
|
286
|
+
</>
|
|
287
|
+
)}
|
|
288
|
+
</button>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
</form>
|
|
292
|
+
</div>
|
|
293
|
+
</div>,
|
|
294
|
+
document.body
|
|
295
|
+
);
|
|
296
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-safe message list component for SSR.
|
|
3
|
+
* This is a simplified, read-only version of MessageList that doesn't use:
|
|
4
|
+
* - Browser APIs (localStorage, window, navigator)
|
|
5
|
+
* - React hooks that require client-side state (useState, useEffect)
|
|
6
|
+
* - Context providers that require browser APIs (ThemeContext, etc.)
|
|
7
|
+
*
|
|
8
|
+
* For interactive features, hydrate with the full MessageList component.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Message } from '@chaaskit/shared';
|
|
12
|
+
import { SSRMarkdownRenderer } from './content/SSRMarkdownRenderer';
|
|
13
|
+
|
|
14
|
+
interface SSRMessageListProps {
|
|
15
|
+
messages: Message[];
|
|
16
|
+
appName?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function UserIcon() {
|
|
20
|
+
return (
|
|
21
|
+
<svg
|
|
22
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
23
|
+
width="12"
|
|
24
|
+
height="12"
|
|
25
|
+
viewBox="0 0 24 24"
|
|
26
|
+
fill="none"
|
|
27
|
+
stroke="currentColor"
|
|
28
|
+
strokeWidth="2"
|
|
29
|
+
strokeLinecap="round"
|
|
30
|
+
strokeLinejoin="round"
|
|
31
|
+
>
|
|
32
|
+
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
|
|
33
|
+
<circle cx="12" cy="7" r="4" />
|
|
34
|
+
</svg>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function BotIcon() {
|
|
39
|
+
return (
|
|
40
|
+
<svg
|
|
41
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
42
|
+
width="12"
|
|
43
|
+
height="12"
|
|
44
|
+
viewBox="0 0 24 24"
|
|
45
|
+
fill="none"
|
|
46
|
+
stroke="currentColor"
|
|
47
|
+
strokeWidth="2"
|
|
48
|
+
strokeLinecap="round"
|
|
49
|
+
strokeLinejoin="round"
|
|
50
|
+
>
|
|
51
|
+
<path d="M12 8V4H8" />
|
|
52
|
+
<rect width="16" height="12" x="4" y="8" rx="2" />
|
|
53
|
+
<path d="M2 14h2" />
|
|
54
|
+
<path d="M20 14h2" />
|
|
55
|
+
<path d="M15 13v2" />
|
|
56
|
+
<path d="M9 13v2" />
|
|
57
|
+
</svg>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function SSRMessageItem({ message }: { message: Message }) {
|
|
62
|
+
const isUser = message.role === 'user';
|
|
63
|
+
|
|
64
|
+
if (isUser) {
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex gap-3 flex-row-reverse">
|
|
67
|
+
{/* Avatar */}
|
|
68
|
+
<div
|
|
69
|
+
className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-primary"
|
|
70
|
+
style={{ backgroundColor: 'rgb(var(--color-primary))' }}
|
|
71
|
+
>
|
|
72
|
+
<span className="text-white">
|
|
73
|
+
<UserIcon />
|
|
74
|
+
</span>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Message Content */}
|
|
78
|
+
<div
|
|
79
|
+
className="flex max-w-[85%] flex-col items-end"
|
|
80
|
+
style={{ maxWidth: '85%' }}
|
|
81
|
+
>
|
|
82
|
+
<div
|
|
83
|
+
className="rounded-lg px-3 py-2 bg-user-message-bg text-user-message-text"
|
|
84
|
+
style={{
|
|
85
|
+
backgroundColor: 'rgb(var(--color-user-message-bg))',
|
|
86
|
+
color: 'rgb(var(--color-user-message-text))',
|
|
87
|
+
borderRadius: '0.5rem',
|
|
88
|
+
padding: '0.5rem 0.75rem',
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<p style={{ whiteSpace: 'pre-wrap', fontSize: '0.875rem', margin: 0 }}>
|
|
92
|
+
{message.content}
|
|
93
|
+
</p>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Assistant message
|
|
101
|
+
return (
|
|
102
|
+
<div className="flex gap-3">
|
|
103
|
+
{/* Avatar */}
|
|
104
|
+
<div
|
|
105
|
+
className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-secondary"
|
|
106
|
+
style={{ backgroundColor: 'rgb(var(--color-secondary))' }}
|
|
107
|
+
>
|
|
108
|
+
<span className="text-white">
|
|
109
|
+
<BotIcon />
|
|
110
|
+
</span>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{/* Message Content */}
|
|
114
|
+
<div className="flex max-w-[85%] flex-col items-start" style={{ maxWidth: '85%' }}>
|
|
115
|
+
<div
|
|
116
|
+
className="rounded-lg px-3 py-2 bg-assistant-message-bg text-assistant-message-text"
|
|
117
|
+
style={{
|
|
118
|
+
backgroundColor: 'rgb(var(--color-assistant-message-bg))',
|
|
119
|
+
color: 'rgb(var(--color-assistant-message-text))',
|
|
120
|
+
borderRadius: '0.5rem',
|
|
121
|
+
padding: '0.5rem 0.75rem',
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
<div className="markdown-content" style={{ fontSize: '0.875rem' }}>
|
|
125
|
+
<SSRMarkdownRenderer content={message.content} />
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function SSRMessageList({ messages }: SSRMessageListProps) {
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
className="mx-auto max-w-3xl px-4 py-6"
|
|
137
|
+
style={{
|
|
138
|
+
maxWidth: '48rem',
|
|
139
|
+
marginLeft: 'auto',
|
|
140
|
+
marginRight: 'auto',
|
|
141
|
+
padding: '1.5rem 1rem',
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
<div className="space-y-4" style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
145
|
+
{messages.map((message) => (
|
|
146
|
+
<SSRMessageItem key={message.id} message={message} />
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default SSRMessageList;
|