deepspace 0.3.10 → 0.4.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/cli.js +331 -115
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +199 -16
- package/dist/index.js +354 -166
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +242 -73
- package/dist/server.js +985 -374
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +322 -73
- package/dist/worker.js +961 -343
- package/dist/worker.js.map +1 -1
- package/features/admin-page/feature.json +29 -0
- package/features/admin-page/src/AdminPage.tsx +292 -0
- package/features/admin-page/src/admin-schema.ts +20 -0
- package/features/ai-chat/feature.json +41 -0
- package/features/ai-chat/src/AiChatPage.tsx +692 -0
- package/features/ai-chat/src/ChatPanel.tsx +1098 -0
- package/features/ai-chat/src/ai-chat-schema.ts +11 -0
- package/features/canvas/feature.json +45 -0
- package/features/canvas/src/CanvasEditorPage.tsx +87 -0
- package/features/canvas/src/CanvasListPage.tsx +202 -0
- package/features/canvas/src/canvas-schema.ts +33 -0
- package/features/canvas/src/components/CanvasToolbar.tsx +170 -0
- package/features/canvas/src/components/CanvasView.tsx +369 -0
- package/features/canvas/src/components/ShapeRenderer.tsx +114 -0
- package/features/cron/feature.json +33 -0
- package/features/cron/src/CronLogPage.tsx +100 -0
- package/features/cron/src/cron.ts +25 -0
- package/features/docs/feature.json +96 -0
- package/features/docs/src/DocsEditorPage.tsx +1012 -0
- package/features/docs/src/DocsListPage.tsx +951 -0
- package/features/docs/src/DocsOutlinePanel.tsx +68 -0
- package/features/docs/src/DocsPresence.tsx +138 -0
- package/features/docs/src/InviteDialog.tsx +336 -0
- package/features/docs/src/LibrarySidebar.tsx +464 -0
- package/features/docs/src/docs-favorites.ts +18 -0
- package/features/docs/src/docs-library-types.ts +15 -0
- package/features/docs/src/docs-schema.ts +80 -0
- package/features/docs/src/docs-ui.css +309 -0
- package/features/docs/src/editor/DocEditorSurface.tsx +257 -0
- package/features/docs/src/editor/DocsTiptapToolbar.tsx +560 -0
- package/features/docs/src/editor/useDocEditor.ts +206 -0
- package/features/docs/src/use-yjs-room-with-awareness.ts +285 -0
- package/features/file-attachments/feature.json +42 -0
- package/features/file-attachments/src/AttachmentsPage.tsx +864 -0
- package/features/file-attachments/src/attachments-constants.ts +9 -0
- package/features/file-attachments/src/attachments-schema.ts +45 -0
- package/features/file-attachments/src/useFilePreview.ts +471 -0
- package/features/file-attachments/src/useMimeTypeDetection.ts +138 -0
- package/features/file-manager/feature.json +24 -0
- package/features/file-manager/src/FileManagerPage.tsx +330 -0
- package/features/integration-test/feature.json +18 -0
- package/features/integration-test/src/IntegrationTestPage.tsx +423 -0
- package/features/integration-test/src/integration.spec.ts +42 -0
- package/features/items/feature.json +33 -0
- package/features/items/src/ItemsPage.tsx +325 -0
- package/features/items/src/items-constants.ts +10 -0
- package/features/items/src/items-schema.ts +27 -0
- package/features/kanban/feature.json +18 -0
- package/features/kanban/src/Kanban.tsx +283 -0
- package/features/landing/feature.json +62 -0
- package/features/landing/src/LandingPage.tsx +627 -0
- package/features/landing/src/primitives.tsx +389 -0
- package/features/landing/src/sections/FAQSection.tsx +110 -0
- package/features/landing/src/sections/FeaturesGridSection.tsx +311 -0
- package/features/landing/src/sections/HowItWorksSection.tsx +62 -0
- package/features/landing/src/sections/LogoCloudSection.tsx +58 -0
- package/features/landing/src/sections/ShowcaseSection.tsx +63 -0
- package/features/landing/src/sections/StatsSection.tsx +31 -0
- package/features/landing/src/sections/TeamSection.tsx +86 -0
- package/features/landing/src/sections/TestimonialsSection.tsx +68 -0
- package/features/landing/src/sections/VideoSection.tsx +71 -0
- package/features/leaderboard/feature.json +33 -0
- package/features/leaderboard/src/LeaderboardPage.tsx +372 -0
- package/features/leaderboard/src/leaderboard-constants.ts +21 -0
- package/features/leaderboard/src/leaderboard-schema.ts +36 -0
- package/features/messaging/feature.json +111 -0
- package/features/messaging/src/ChatMultiPage.tsx +300 -0
- package/features/messaging/src/ChatPage.tsx +75 -0
- package/features/messaging/src/components/chat/ChatHeader.tsx +76 -0
- package/features/messaging/src/components/chat/MessageActionSheet.tsx +168 -0
- package/features/messaging/src/components/chat/MessageInput.tsx +65 -0
- package/features/messaging/src/components/chat/MessageItem.tsx +352 -0
- package/features/messaging/src/components/chat/MessageList.tsx +248 -0
- package/features/messaging/src/components/chat/ReactionPicker.tsx +2 -0
- package/features/messaging/src/components/chat/ThreadPanel.tsx +107 -0
- package/features/messaging/src/components/chat/UserProfilePopover.tsx +158 -0
- package/features/messaging/src/components/chat-multi/AddMemberModal.tsx +188 -0
- package/features/messaging/src/components/chat-multi/BrowseGroupsModal.tsx +163 -0
- package/features/messaging/src/components/chat-multi/ChannelListItem.tsx +147 -0
- package/features/messaging/src/components/chat-multi/ChannelSettingsPanel.tsx +344 -0
- package/features/messaging/src/components/chat-multi/ChannelSidebar.tsx +460 -0
- package/features/messaging/src/components/chat-multi/CreateGroupModal.tsx +190 -0
- package/features/messaging/src/components/chat-multi/InvitationList.tsx +149 -0
- package/features/messaging/src/components/chat-multi/NewMessageModal.tsx +159 -0
- package/features/messaging/src/hooks/useChatChannel.ts +64 -0
- package/features/messaging/src/hooks/useLongPress.ts +74 -0
- package/features/messaging/src/hooks/useMultiChannel.ts +142 -0
- package/features/messaging/src/messaging-schema.ts +25 -0
- package/features/presence-test/feature.json +21 -0
- package/features/presence-test/src/PresenceTestPage.tsx +120 -0
- package/features/sidebar/feature.json +19 -0
- package/features/sidebar/src/AppSidebar.tsx +558 -0
- package/features/tasks/feature.json +33 -0
- package/features/tasks/src/TasksPage.tsx +598 -0
- package/features/tasks/src/tasks-constants.ts +43 -0
- package/features/tasks/src/tasks-schema.ts +56 -0
- package/features/testing/feature.json +49 -0
- package/features/testing/src/TestPage.tsx +145 -0
- package/features/testing/src/cpu-burn-action.ts +51 -0
- package/features/testing/src/subrequest-burn-action.ts +53 -0
- package/features/testing/src/test-schema.ts +23 -0
- package/features/topbar/feature.json +18 -0
- package/features/topbar/src/Topbar.tsx +353 -0
- package/features/tree/feature.json +26 -0
- package/features/tree/src/TreeSidebar.tsx +607 -0
- package/features/tree/src/tree-constants.ts +68 -0
- package/features/tree/src/tree-hooks.ts +258 -0
- package/package.json +4 -2
- package/scripts/add-feature.cjs +654 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "admin-page",
|
|
3
|
+
"name": "Admin Page",
|
|
4
|
+
"category": "data",
|
|
5
|
+
"description": "User management and app settings admin panel",
|
|
6
|
+
"details": "Admin-only page with user management (list users, change roles) and key-value settings CRUD. Uses useUsers for listing, setRole for role changes, and useQuery/useMutations for a settings collection.",
|
|
7
|
+
"files": [
|
|
8
|
+
{
|
|
9
|
+
"src": "src/AdminPage.tsx",
|
|
10
|
+
"dest": "src/pages/admin.tsx"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"src": "src/admin-schema.ts",
|
|
14
|
+
"dest": "src/schemas/admin-schema.ts"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"schema": {
|
|
18
|
+
"exportName": "settingsSchema",
|
|
19
|
+
"importPath": "./schemas/admin-schema",
|
|
20
|
+
"spreadOperator": false
|
|
21
|
+
},
|
|
22
|
+
"route": {
|
|
23
|
+
"path": "/admin",
|
|
24
|
+
"protected": true
|
|
25
|
+
},
|
|
26
|
+
"requires": [],
|
|
27
|
+
"instructions": [],
|
|
28
|
+
"patterns": []
|
|
29
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Page
|
|
3
|
+
*
|
|
4
|
+
* User management + app settings page.
|
|
5
|
+
* - useUsers for listing all users
|
|
6
|
+
* - setRole for changing user roles
|
|
7
|
+
* - Admin-only settings collection
|
|
8
|
+
* - useQuery with admin permissions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useState, useMemo } from 'react'
|
|
12
|
+
import { useUser } from 'deepspace'
|
|
13
|
+
import { useUsers } from 'deepspace'
|
|
14
|
+
import { useQuery } from 'deepspace'
|
|
15
|
+
import { useMutations } from 'deepspace'
|
|
16
|
+
import { Button, Badge, Avatar, AvatarFallback, useToast } from 'deepspace'
|
|
17
|
+
import { ROLES, ROLE_CONFIG, type Role } from 'deepspace'
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
interface Setting {
|
|
24
|
+
key: string
|
|
25
|
+
value: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Main Page
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
export default function AdminPage() {
|
|
33
|
+
const { user } = useUser()
|
|
34
|
+
const isAdmin = user?.role === 'admin'
|
|
35
|
+
const { users, setRole } = useUsers()
|
|
36
|
+
const [activeTab, setActiveTab] = useState<'users' | 'settings'>('users')
|
|
37
|
+
const toast = useToast()
|
|
38
|
+
|
|
39
|
+
// Security: Don't render admin content if not an admin
|
|
40
|
+
if (!isAdmin) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex items-center justify-center min-h-[60vh] bg-background">
|
|
43
|
+
<div className="text-center">
|
|
44
|
+
<div className="w-16 h-16 bg-destructive/20 rounded-2xl flex items-center justify-center mx-auto mb-4 border border-destructive/40">
|
|
45
|
+
<svg className="w-8 h-8 text-destructive" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
46
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
47
|
+
</svg>
|
|
48
|
+
</div>
|
|
49
|
+
<h2 className="text-lg font-semibold text-destructive mb-1">Access Denied</h2>
|
|
50
|
+
<p className="text-muted-foreground text-sm">You don't have permission to view this page.</p>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="h-full bg-background overflow-y-auto">
|
|
58
|
+
{/* Header */}
|
|
59
|
+
<div className="bg-card/60 backdrop-blur-md border-b border-border sticky top-0 z-10">
|
|
60
|
+
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
|
61
|
+
<div className="flex items-center justify-between">
|
|
62
|
+
<div>
|
|
63
|
+
<h1 className="text-2xl font-bold text-foreground">Admin Panel</h1>
|
|
64
|
+
<p className="text-muted-foreground mt-1">Manage users and settings</p>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{/* Tabs */}
|
|
69
|
+
<div className="flex gap-1 mt-4">
|
|
70
|
+
<button
|
|
71
|
+
onClick={() => setActiveTab('users')}
|
|
72
|
+
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
73
|
+
activeTab === 'users'
|
|
74
|
+
? 'bg-primary/20 text-primary border border-primary/30'
|
|
75
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-muted/60 border border-transparent'
|
|
76
|
+
}`}
|
|
77
|
+
>
|
|
78
|
+
Users ({users.length})
|
|
79
|
+
</button>
|
|
80
|
+
<button
|
|
81
|
+
onClick={() => setActiveTab('settings')}
|
|
82
|
+
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
83
|
+
activeTab === 'settings'
|
|
84
|
+
? 'bg-primary/20 text-primary border border-primary/30'
|
|
85
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-muted/60 border border-transparent'
|
|
86
|
+
}`}
|
|
87
|
+
>
|
|
88
|
+
Settings
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Content */}
|
|
95
|
+
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
96
|
+
{activeTab === 'users' ? (
|
|
97
|
+
<UsersPanel
|
|
98
|
+
users={users}
|
|
99
|
+
currentUserId={user?.id}
|
|
100
|
+
onSetRole={(userId, role) => {
|
|
101
|
+
setRole(userId, role)
|
|
102
|
+
toast.success('Role updated successfully')
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
) : (
|
|
106
|
+
<SettingsPanel toast={toast} />
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Users Panel
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
interface UsersPanelProps {
|
|
119
|
+
users: Array<{
|
|
120
|
+
id: string
|
|
121
|
+
email: string
|
|
122
|
+
name: string
|
|
123
|
+
imageUrl?: string
|
|
124
|
+
role: string
|
|
125
|
+
lastSeenAt: string
|
|
126
|
+
}>
|
|
127
|
+
currentUserId?: string
|
|
128
|
+
onSetRole: (userId: string, role: string) => void
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function UsersPanel({ users, currentUserId, onSetRole }: UsersPanelProps) {
|
|
132
|
+
const [selectedUserId, setSelectedUserId] = useState<string | null>(null)
|
|
133
|
+
|
|
134
|
+
// Sort users by last seen
|
|
135
|
+
const sortedUsers = useMemo(() => {
|
|
136
|
+
return [...users].sort((a, b) =>
|
|
137
|
+
new Date(b.lastSeenAt).getTime() - new Date(a.lastSeenAt).getTime()
|
|
138
|
+
)
|
|
139
|
+
}, [users])
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div className="space-y-4">
|
|
143
|
+
<div className="bg-muted/40 rounded-xl border border-border overflow-hidden">
|
|
144
|
+
<div className="divide-y divide-border/30">
|
|
145
|
+
{sortedUsers.map(user => {
|
|
146
|
+
const roleConfig = ROLE_CONFIG[user.role as Role] ?? ROLE_CONFIG[ROLES.VIEWER]
|
|
147
|
+
const isCurrentUser = user.id === currentUserId
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div key={user.id} className="p-4 flex items-center justify-between">
|
|
151
|
+
<div className="flex items-center gap-3">
|
|
152
|
+
<Avatar>
|
|
153
|
+
<AvatarFallback>{user.name?.[0]?.toUpperCase() ?? '?'}</AvatarFallback>
|
|
154
|
+
</Avatar>
|
|
155
|
+
<div>
|
|
156
|
+
<div className="flex items-center gap-2">
|
|
157
|
+
<span className="font-medium text-foreground">{user.name}</span>
|
|
158
|
+
{isCurrentUser && (
|
|
159
|
+
<span className="text-xs text-muted-foreground">(you)</span>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
<p className="text-sm text-muted-foreground">{user.email}</p>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div className="flex items-center gap-3">
|
|
167
|
+
<p className="text-xs text-muted-foreground">
|
|
168
|
+
Last seen: {new Date(user.lastSeenAt).toLocaleDateString()}
|
|
169
|
+
</p>
|
|
170
|
+
|
|
171
|
+
<div className="flex items-center gap-2">
|
|
172
|
+
<Badge variant={roleConfig.badgeVariant as any}>{roleConfig.title}</Badge>
|
|
173
|
+
<select
|
|
174
|
+
value={user.role}
|
|
175
|
+
onChange={(e) => onSetRole(user.id, e.target.value)}
|
|
176
|
+
disabled={isCurrentUser}
|
|
177
|
+
className="px-3 py-1.5 bg-transparent border border-border rounded-lg text-sm text-foreground focus:outline-none focus:ring-ring disabled:opacity-50"
|
|
178
|
+
>
|
|
179
|
+
{Object.entries(ROLE_CONFIG).map(([role, config]) => (
|
|
180
|
+
<option key={role} value={role}>
|
|
181
|
+
{config.title}
|
|
182
|
+
</option>
|
|
183
|
+
))}
|
|
184
|
+
</select>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
)
|
|
189
|
+
})}
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
{users.length === 0 && (
|
|
194
|
+
<p className="text-center text-muted-foreground py-8">No users found</p>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Settings Panel
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
interface SettingsPanelProps {
|
|
205
|
+
toast: ReturnType<typeof useToast>
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function SettingsPanel({ toast }: SettingsPanelProps) {
|
|
209
|
+
const [newKey, setNewKey] = useState('')
|
|
210
|
+
const [newValue, setNewValue] = useState('')
|
|
211
|
+
|
|
212
|
+
// Query settings (admin-only collection)
|
|
213
|
+
const { records: settings, status } = useQuery<Setting>('settings')
|
|
214
|
+
const { create, put, remove } = useMutations<Setting>('settings')
|
|
215
|
+
|
|
216
|
+
const handleCreate = async () => {
|
|
217
|
+
if (!newKey.trim() || !newValue.trim()) return
|
|
218
|
+
|
|
219
|
+
await create({ key: newKey.trim(), value: newValue.trim() })
|
|
220
|
+
setNewKey('')
|
|
221
|
+
setNewValue('')
|
|
222
|
+
toast.success('Setting created')
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const handleDelete = async (id: string) => {
|
|
226
|
+
if (confirm('Are you sure you want to delete this setting?')) {
|
|
227
|
+
await remove(id)
|
|
228
|
+
toast.success('Setting deleted')
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<div className="space-y-4">
|
|
234
|
+
{/* Add new setting */}
|
|
235
|
+
<div className="bg-muted/40 rounded-xl border border-border p-4">
|
|
236
|
+
<h3 className="text-sm font-medium text-muted-foreground mb-3">Add New Setting</h3>
|
|
237
|
+
<div className="flex gap-2">
|
|
238
|
+
<input
|
|
239
|
+
type="text"
|
|
240
|
+
value={newKey}
|
|
241
|
+
onChange={(e) => setNewKey(e.target.value)}
|
|
242
|
+
placeholder="Key"
|
|
243
|
+
className="flex-1 px-3 py-2 bg-transparent border border-border rounded-lg text-sm text-foreground placeholder-muted-foreground focus:outline-none focus:ring-ring"
|
|
244
|
+
/>
|
|
245
|
+
<input
|
|
246
|
+
type="text"
|
|
247
|
+
value={newValue}
|
|
248
|
+
onChange={(e) => setNewValue(e.target.value)}
|
|
249
|
+
placeholder="Value"
|
|
250
|
+
className="flex-1 px-3 py-2 bg-transparent border border-border rounded-lg text-sm text-foreground placeholder-muted-foreground focus:outline-none focus:ring-ring"
|
|
251
|
+
/>
|
|
252
|
+
<Button onClick={handleCreate} disabled={!newKey.trim() || !newValue.trim()}>
|
|
253
|
+
Add
|
|
254
|
+
</Button>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
{/* Settings list */}
|
|
259
|
+
<div className="bg-muted/40 rounded-xl border border-border overflow-hidden">
|
|
260
|
+
{status === 'loading' ? (
|
|
261
|
+
<div className="flex items-center justify-center py-8">
|
|
262
|
+
<div className="w-6 h-6 border-2 border-primary/20 border-t-primary rounded-full animate-spin" />
|
|
263
|
+
</div>
|
|
264
|
+
) : settings.length === 0 ? (
|
|
265
|
+
<p className="text-center text-muted-foreground py-8">No settings configured</p>
|
|
266
|
+
) : (
|
|
267
|
+
<div className="divide-y divide-border/30">
|
|
268
|
+
{settings.map(setting => (
|
|
269
|
+
<div key={setting.recordId} className="p-4 flex items-center justify-between">
|
|
270
|
+
<div>
|
|
271
|
+
<code className="text-sm bg-muted px-2 py-1 rounded text-primary">
|
|
272
|
+
{setting.data.key}
|
|
273
|
+
</code>
|
|
274
|
+
<span className="text-muted-foreground mx-2">=</span>
|
|
275
|
+
<span className="text-foreground">{setting.data.value}</span>
|
|
276
|
+
</div>
|
|
277
|
+
<button
|
|
278
|
+
onClick={() => handleDelete(setting.recordId)}
|
|
279
|
+
className="p-2 text-muted-foreground hover:text-destructive hover:bg-destructive/20 rounded-lg transition-colors"
|
|
280
|
+
>
|
|
281
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
282
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
283
|
+
</svg>
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
286
|
+
))}
|
|
287
|
+
</div>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Feature - Schema
|
|
3
|
+
*
|
|
4
|
+
* Admin-only settings collection for key-value app configuration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { CollectionSchema } from 'deepspace/worker'
|
|
8
|
+
|
|
9
|
+
export const settingsSchema: CollectionSchema = {
|
|
10
|
+
name: 'settings',
|
|
11
|
+
columns: [
|
|
12
|
+
{ name: 'key', storage: 'text', interpretation: 'plain', required: true },
|
|
13
|
+
{ name: 'value', storage: 'text', interpretation: 'plain', required: true },
|
|
14
|
+
],
|
|
15
|
+
permissions: {
|
|
16
|
+
viewer: { read: false, create: false, update: false, delete: false },
|
|
17
|
+
member: { read: false, create: false, update: false, delete: false },
|
|
18
|
+
admin: { read: true, create: true, update: true, delete: true },
|
|
19
|
+
},
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "ai-chat",
|
|
3
|
+
"name": "AI Chat",
|
|
4
|
+
"category": "assistant",
|
|
5
|
+
"description": "Embeddable AI chat panel with DO-backed conversation history",
|
|
6
|
+
"details": "ChatPanel (reusable component) and AiChatPage (full assistant page with conversation rail). Persists to ai-chats / ai-messages collections in the app's RecordRoom — no localStorage. Routes through the Vercel AI SDK proxy with Anthropic, OpenAI, and Cerebras. User-scoped via RBAC (read: 'own').",
|
|
7
|
+
"files": [
|
|
8
|
+
{ "src": "src/ChatPanel.tsx", "dest": "src/components/ChatPanel.tsx" },
|
|
9
|
+
{ "src": "src/AiChatPage.tsx", "dest": "src/pages/assistant.tsx" },
|
|
10
|
+
{ "src": "src/ai-chat-schema.ts", "dest": "src/schemas/ai-chat-schema.ts" }
|
|
11
|
+
],
|
|
12
|
+
"schema": {
|
|
13
|
+
"exportName": "aiChatSchemas",
|
|
14
|
+
"importPath": "./schemas/ai-chat-schema",
|
|
15
|
+
"spreadOperator": true
|
|
16
|
+
},
|
|
17
|
+
"route": {
|
|
18
|
+
"path": "/assistant",
|
|
19
|
+
"protected": true
|
|
20
|
+
},
|
|
21
|
+
"requires": [],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"react-markdown": "^9.0.0",
|
|
24
|
+
"remark-gfm": "^4.0.0",
|
|
25
|
+
"remark-breaks": "^4.0.0",
|
|
26
|
+
"rehype-highlight": "^7.0.0",
|
|
27
|
+
"highlight.js": "^11.10.0"
|
|
28
|
+
},
|
|
29
|
+
"instructions": [
|
|
30
|
+
"Embed ChatPanel standalone (without the conversation rail) for sidebar / modal / popover use. Pass `chatId={null}` to auto-create on first send; use `onChatCreated` to track the new id.",
|
|
31
|
+
"All chat data lives in the app's RecordRoom DO under the ai-chats and ai-messages collections. RBAC is enforced — users only see their own chats and messages.",
|
|
32
|
+
"The backend route POST /api/ai/chat takes { chatId, userMessageId, content, modelId? } and runs the full server-owned pipeline: load history, compact if needed, stream assistant, persist on finish.",
|
|
33
|
+
"Compaction (summarizing older history when the conversation grows large) is server-side and cached on the chat row. No client-side compaction logic."
|
|
34
|
+
],
|
|
35
|
+
"patterns": [
|
|
36
|
+
"ChatPanel takes `chatId` (or null). Parent owns chat lifecycle (create / select / delete); pass `disabled` while a parent-driven create is in flight so the panel doesn't kick off its own auto-create.",
|
|
37
|
+
"ChatPanel's DEFAULT_MODELS and worker.ts's ALLOWED_MODELS must stay in sync — add entries to both when introducing a new model.",
|
|
38
|
+
"Compaction summary is stored on the ai-chats row (compactedSummary, compactedThroughId). The summarizer is user-billed by default; pass `makeDefaultSummarizer(c.env)` with no authToken in worker.ts to absorb the cost as the developer instead.",
|
|
39
|
+
"Tool execution is server-side and per-user RBAC-enforced (records.* tools see only what the caller would see). `stopWhen: stepCountIs(5)` caps tool-use rounds per turn."
|
|
40
|
+
]
|
|
41
|
+
}
|