@feltdb/core 0.4.8 → 0.4.9
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/commands.js +41 -8
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +99 -25
- package/dist/create/create.js +678 -850
- package/dist/create/index.js +1 -0
- package/dist/create/managed-account.js +61 -0
- package/dist/create/package-versions.js +1 -1
- package/dist/studio/{KeyManagementPanel-B0s0xAXz.js → KeyManagementPanel-DZuSeWBK.js} +80 -100
- package/dist/studio/app.d.ts +4 -1
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/KeyManagementPanel.d.ts +1 -0
- package/dist/studio/components/KeyManagementPanel.d.ts.map +1 -1
- package/dist/studio/components/KeyManagementPanel.js +1 -1
- package/dist/studio/components/SettingsPanel.d.ts +6 -2
- package/dist/studio/components/SettingsPanel.d.ts.map +1 -1
- package/dist/studio/components/index.js +2 -2
- package/dist/studio/{components-BAycgZhP.js → components-gmwCs-Pb.js} +44 -45
- package/dist/studio/index.js +57 -48
- package/dist/studio/studio.css +1 -1
- package/dist/studio-app/assets/{index-DKVLtS37.js → index-BCrZl0OT.js} +2 -2
- package/dist/studio-app/assets/{index-CB-Eed9V.css → index-BkHEyTzN.css} +1 -1
- package/dist/studio-app/index.html +2 -2
- package/package.json +1 -1
package/dist/create/create.js
CHANGED
|
@@ -72,7 +72,7 @@ export async function createProject(options) {
|
|
|
72
72
|
const feltdbConfig = {
|
|
73
73
|
namespace: applicationName,
|
|
74
74
|
runtime,
|
|
75
|
-
storage: runtime === 'browser' ? '
|
|
75
|
+
storage: runtime === 'browser' ? 'indexeddb' : runtime === 'managed' ? 'managed' : 'durable',
|
|
76
76
|
distributed,
|
|
77
77
|
agents: {
|
|
78
78
|
enabled: hasAgents,
|
|
@@ -156,16 +156,165 @@ export async function createProject(options) {
|
|
|
156
156
|
// Create main application files
|
|
157
157
|
const runtimeOptions = runtime === 'browser'
|
|
158
158
|
? "{ namespace: '" + applicationName + "', browser: true }"
|
|
159
|
-
: runtime === '
|
|
160
|
-
? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || '
|
|
161
|
-
:
|
|
159
|
+
: runtime === 'managed'
|
|
160
|
+
? "{ namespace: import.meta.env.VITE_FELTDB_MANAGED_NAMESPACE || '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_MANAGED_URL || import.meta.env.VITE_FELTDB_URL || '', token: import.meta.env.VITE_FELTDB_MANAGED_API_KEY || import.meta.env.VITE_FELTDB_API_KEY || '' } }"
|
|
161
|
+
: runtime === 'self-hosted'
|
|
162
|
+
? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || 'http://localhost:7700', token: import.meta.env.VITE_FELTDB_API_KEY || '' } }"
|
|
163
|
+
: "{ namespace: '" + applicationName + "', memory: true }";
|
|
162
164
|
const feltdbTs = `import { createFeltDB } from '@feltdb/core';
|
|
163
165
|
|
|
164
166
|
export const db = createFeltDB(${runtimeOptions});
|
|
165
167
|
|
|
166
168
|
// Collections
|
|
167
|
-
export const
|
|
168
|
-
export const
|
|
169
|
+
export const projects = db.collection('projects');
|
|
170
|
+
export const tasks = db.collection('tasks');
|
|
171
|
+
export const activity = db.collection('activity');
|
|
172
|
+
|
|
173
|
+
// Types
|
|
174
|
+
export interface Project {
|
|
175
|
+
id: string;
|
|
176
|
+
name: string;
|
|
177
|
+
description: string;
|
|
178
|
+
status: 'active' | 'archived' | 'completed';
|
|
179
|
+
createdAt: string;
|
|
180
|
+
updatedAt: string;
|
|
181
|
+
metadata?: Record<string, any>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface Task {
|
|
185
|
+
id: string;
|
|
186
|
+
projectId: string;
|
|
187
|
+
title: string;
|
|
188
|
+
description: string;
|
|
189
|
+
status: 'todo' | 'in-progress' | 'completed';
|
|
190
|
+
priority: 'low' | 'medium' | 'high';
|
|
191
|
+
assignee?: string;
|
|
192
|
+
createdAt: string;
|
|
193
|
+
updatedAt: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface ActivityEvent {
|
|
197
|
+
id: string;
|
|
198
|
+
timestamp: string;
|
|
199
|
+
type: string;
|
|
200
|
+
entityType: 'project' | 'task';
|
|
201
|
+
entityId: string;
|
|
202
|
+
entityName: string;
|
|
203
|
+
changes?: Record<string, any>;
|
|
204
|
+
userId?: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Indexes
|
|
208
|
+
projects.createIndex('status');
|
|
209
|
+
tasks.createIndex('projectId');
|
|
210
|
+
tasks.createIndex('status');
|
|
211
|
+
tasks.createIndex('priority');
|
|
212
|
+
activity.createIndex('timestamp');
|
|
213
|
+
|
|
214
|
+
// Operations
|
|
215
|
+
export async function createProject(data: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project> {
|
|
216
|
+
const project: Project = {
|
|
217
|
+
id: 'proj_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
|
218
|
+
...data,
|
|
219
|
+
createdAt: new Date().toISOString(),
|
|
220
|
+
updatedAt: new Date().toISOString(),
|
|
221
|
+
};
|
|
222
|
+
await projects.insert(project);
|
|
223
|
+
await logActivity({
|
|
224
|
+
type: 'project_created',
|
|
225
|
+
entityType: 'project',
|
|
226
|
+
entityId: project.id,
|
|
227
|
+
entityName: project.name,
|
|
228
|
+
});
|
|
229
|
+
return project;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function createTask(data: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<Task> {
|
|
233
|
+
const task: Task = {
|
|
234
|
+
id: 'task_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
|
235
|
+
...data,
|
|
236
|
+
createdAt: new Date().toISOString(),
|
|
237
|
+
updatedAt: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
await tasks.insert(task);
|
|
240
|
+
await logActivity({
|
|
241
|
+
type: 'task_created',
|
|
242
|
+
entityType: 'task',
|
|
243
|
+
entityId: task.id,
|
|
244
|
+
entityName: task.title,
|
|
245
|
+
});
|
|
246
|
+
return task;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function updateTask(id: string, updates: Partial<Task>): Promise<void> {
|
|
250
|
+
const task = await tasks.findOne({ id });
|
|
251
|
+
if (!task) throw new Error('Task not found');
|
|
252
|
+
|
|
253
|
+
const updated = {
|
|
254
|
+
...task,
|
|
255
|
+
...updates,
|
|
256
|
+
updatedAt: new Date().toISOString(),
|
|
257
|
+
};
|
|
258
|
+
await tasks.update({ id }, updated);
|
|
259
|
+
await logActivity({
|
|
260
|
+
type: 'task_updated',
|
|
261
|
+
entityType: 'task',
|
|
262
|
+
entityId: id,
|
|
263
|
+
entityName: updated.title,
|
|
264
|
+
changes: updates,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export async function updateProject(id: string, updates: Partial<Project>): Promise<void> {
|
|
269
|
+
const project = await projects.findOne({ id });
|
|
270
|
+
if (!project) throw new Error('Project not found');
|
|
271
|
+
|
|
272
|
+
const updated = {
|
|
273
|
+
...project,
|
|
274
|
+
...updates,
|
|
275
|
+
updatedAt: new Date().toISOString(),
|
|
276
|
+
};
|
|
277
|
+
await projects.update({ id }, updated);
|
|
278
|
+
await logActivity({
|
|
279
|
+
type: 'project_updated',
|
|
280
|
+
entityType: 'project',
|
|
281
|
+
entityId: id,
|
|
282
|
+
entityName: updated.name,
|
|
283
|
+
changes: updates,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function getTasksByProject(projectId: string): Promise<Task[]> {
|
|
288
|
+
return tasks.find({ projectId }) as Promise<Task[]>;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function getRecentActivity(limit = 50): Promise<ActivityEvent[]> {
|
|
292
|
+
const events = await activity.find({});
|
|
293
|
+
return (events as ActivityEvent[]).sort((a, b) =>
|
|
294
|
+
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
295
|
+
).slice(0, limit);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function getDashboardStats(): Promise<any> {
|
|
299
|
+
const allTasks = await tasks.find({});
|
|
300
|
+
const allProjects = await projects.find({});
|
|
301
|
+
return {
|
|
302
|
+
totalProjects: allProjects.length,
|
|
303
|
+
totalTasks: allTasks.length,
|
|
304
|
+
completedTasks: (allTasks as Task[]).filter(t => t.status === 'completed').length,
|
|
305
|
+
inProgressTasks: (allTasks as Task[]).filter(t => t.status === 'in-progress').length,
|
|
306
|
+
activeTasks: (allTasks as Task[]).filter(t => t.status === 'todo').length,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function logActivity(event: Omit<ActivityEvent, 'id' | 'timestamp'>): Promise<void> {
|
|
311
|
+
const activityEvent: ActivityEvent = {
|
|
312
|
+
id: 'evt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
|
313
|
+
timestamp: new Date().toISOString(),
|
|
314
|
+
...event,
|
|
315
|
+
};
|
|
316
|
+
await activity.insert(activityEvent);
|
|
317
|
+
}
|
|
169
318
|
`;
|
|
170
319
|
fs.writeFileSync(path.join(srcDir, 'feltdb.ts'), feltdbTs);
|
|
171
320
|
// Create a real local-inference agent for browser projects.
|
|
@@ -573,815 +722,387 @@ export const capabilities = {
|
|
|
573
722
|
</section>`
|
|
574
723
|
: '';
|
|
575
724
|
const appTsx = `import React, { useState, useEffect } from 'react';
|
|
576
|
-
import {
|
|
577
|
-
|
|
725
|
+
import { useCollection } from '@feltdb/core/react';
|
|
726
|
+
import {
|
|
727
|
+
projects,
|
|
728
|
+
tasks,
|
|
729
|
+
activity,
|
|
730
|
+
createProject,
|
|
731
|
+
createTask,
|
|
732
|
+
updateTask,
|
|
733
|
+
getDashboardStats,
|
|
734
|
+
} from './feltdb';
|
|
735
|
+
|
|
736
|
+
type View = 'dashboard' | 'projects' | 'tasks' | 'activity' | 'inspector';
|
|
578
737
|
|
|
579
738
|
export function App() {
|
|
580
|
-
const [
|
|
581
|
-
const [
|
|
582
|
-
const [
|
|
583
|
-
const [
|
|
584
|
-
const [
|
|
585
|
-
const [
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const loadDocs = async () => {
|
|
592
|
-
try {
|
|
593
|
-
setError(null);
|
|
594
|
-
const allDocs = await documents.find({});
|
|
595
|
-
setDocs(allDocs.sort((a: any, b: any) =>
|
|
596
|
-
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
597
|
-
));
|
|
598
|
-
} catch (err) {
|
|
599
|
-
const msg = err instanceof Error ? err.message : 'Failed to load documents';
|
|
600
|
-
console.error('Error loading documents:', err);
|
|
601
|
-
setError(msg);
|
|
602
|
-
} finally {
|
|
603
|
-
setLoading(false);
|
|
604
|
-
}
|
|
605
|
-
};
|
|
739
|
+
const [currentView, setCurrentView] = useState<View>('dashboard');
|
|
740
|
+
const [selectedProjectId, setSelectedProjectId] = useState<string>('');
|
|
741
|
+
const [dashboardStats, setDashboardStats] = useState<any>(null);
|
|
742
|
+
const [newProjectName, setNewProjectName] = useState('');
|
|
743
|
+
const [newTaskTitle, setNewTaskTitle] = useState('');
|
|
744
|
+
const [newTaskProject, setNewTaskProject] = useState('');
|
|
745
|
+
|
|
746
|
+
const { data: projectsList } = useCollection(projects);
|
|
747
|
+
const { data: tasksList } = useCollection(tasks);
|
|
748
|
+
const { data: activityList } = useCollection(activity);
|
|
606
749
|
|
|
607
750
|
useEffect(() => {
|
|
608
|
-
|
|
609
|
-
}, []);
|
|
751
|
+
getDashboardStats().then(setDashboardStats);
|
|
752
|
+
}, [projectsList, tasksList]);
|
|
753
|
+
|
|
754
|
+
const handleCreateProject = async (e: React.FormEvent) => {
|
|
755
|
+
e.preventDefault();
|
|
756
|
+
if (!newProjectName.trim()) return;
|
|
757
|
+
await createProject({
|
|
758
|
+
name: newProjectName,
|
|
759
|
+
description: 'Created via Workspace',
|
|
760
|
+
status: 'active',
|
|
761
|
+
metadata: {},
|
|
762
|
+
});
|
|
763
|
+
setNewProjectName('');
|
|
764
|
+
};
|
|
610
765
|
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
766
|
+
const handleCreateTask = async (e: React.FormEvent) => {
|
|
767
|
+
e.preventDefault();
|
|
768
|
+
if (!newTaskTitle.trim() || !newTaskProject) return;
|
|
769
|
+
await createTask({
|
|
770
|
+
projectId: newTaskProject,
|
|
771
|
+
title: newTaskTitle,
|
|
772
|
+
description: '',
|
|
773
|
+
status: 'todo',
|
|
774
|
+
priority: 'medium',
|
|
775
|
+
});
|
|
776
|
+
setNewTaskTitle('');
|
|
777
|
+
};
|
|
615
778
|
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
779
|
+
const containerStyle: React.CSSProperties = {
|
|
780
|
+
display: 'flex',
|
|
781
|
+
minHeight: '100vh',
|
|
782
|
+
fontFamily: 'system-ui, sans-serif',
|
|
783
|
+
backgroundColor: '#f5f7fa',
|
|
784
|
+
};
|
|
620
785
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
} catch (err) {
|
|
631
|
-
const msg = err instanceof Error ? err.message : 'Failed to add document';
|
|
632
|
-
console.error('Error adding document:', err);
|
|
633
|
-
setError(msg);
|
|
634
|
-
} finally {
|
|
635
|
-
setAddingDoc(false);
|
|
636
|
-
}
|
|
786
|
+
const sidebarStyle: React.CSSProperties = {
|
|
787
|
+
width: '240px',
|
|
788
|
+
backgroundColor: '#1e293b',
|
|
789
|
+
color: '#e2e8f0',
|
|
790
|
+
padding: '20px',
|
|
791
|
+
borderRight: '1px solid #334155',
|
|
792
|
+
display: 'flex',
|
|
793
|
+
flexDirection: 'column',
|
|
794
|
+
gap: '20px',
|
|
637
795
|
};
|
|
638
796
|
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
797
|
+
const mainStyle: React.CSSProperties = {
|
|
798
|
+
flex: 1,
|
|
799
|
+
display: 'flex',
|
|
800
|
+
flexDirection: 'column',
|
|
643
801
|
};
|
|
644
802
|
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
803
|
+
const headerStyle: React.CSSProperties = {
|
|
804
|
+
backgroundColor: '#fff',
|
|
805
|
+
borderBottom: '1px solid #e2e8f0',
|
|
806
|
+
padding: '20px',
|
|
807
|
+
display: 'flex',
|
|
808
|
+
justifyContent: 'space-between',
|
|
809
|
+
alignItems: 'center',
|
|
810
|
+
};
|
|
648
811
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
...doc,
|
|
654
|
-
title: editTitle,
|
|
655
|
-
content: editContent,
|
|
656
|
-
updatedAt: new Date().toISOString(),
|
|
657
|
-
};
|
|
658
|
-
await documents.insert(updatedDoc);
|
|
659
|
-
setEditingId(null);
|
|
660
|
-
await loadDocs();
|
|
661
|
-
}
|
|
662
|
-
} catch (err) {
|
|
663
|
-
const msg = err instanceof Error ? err.message : 'Failed to save document';
|
|
664
|
-
console.error('Error saving document:', err);
|
|
665
|
-
setError(msg);
|
|
666
|
-
}
|
|
812
|
+
const contentStyle: React.CSSProperties = {
|
|
813
|
+
flex: 1,
|
|
814
|
+
padding: '20px',
|
|
815
|
+
overflowY: 'auto',
|
|
667
816
|
};
|
|
668
817
|
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
818
|
+
const titleStyle: React.CSSProperties = {
|
|
819
|
+
fontSize: '24px',
|
|
820
|
+
fontWeight: 'bold',
|
|
821
|
+
color: '#1e293b',
|
|
822
|
+
};
|
|
673
823
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
824
|
+
const navItemStyle = (active: boolean): React.CSSProperties => ({
|
|
825
|
+
padding: '10px 12px',
|
|
826
|
+
borderRadius: '6px',
|
|
827
|
+
cursor: 'pointer',
|
|
828
|
+
backgroundColor: active ? '#334155' : 'transparent',
|
|
829
|
+
color: active ? '#fff' : '#cbd5e1',
|
|
830
|
+
border: 'none',
|
|
831
|
+
textAlign: 'left',
|
|
832
|
+
width: '100%',
|
|
833
|
+
fontSize: '14px',
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
const statsStyle: React.CSSProperties = {
|
|
837
|
+
display: 'grid',
|
|
838
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
839
|
+
gap: '16px',
|
|
840
|
+
marginBottom: '24px',
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
const statCardStyle: React.CSSProperties = {
|
|
844
|
+
backgroundColor: '#fff',
|
|
845
|
+
padding: '16px',
|
|
846
|
+
borderRadius: '8px',
|
|
847
|
+
border: '1px solid #e2e8f0',
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const formStyle: React.CSSProperties = {
|
|
851
|
+
display: 'flex',
|
|
852
|
+
gap: '8px',
|
|
853
|
+
marginBottom: '20px',
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const inputStyle: React.CSSProperties = {
|
|
857
|
+
flex: 1,
|
|
858
|
+
padding: '8px 12px',
|
|
859
|
+
border: '1px solid #cbd5e1',
|
|
860
|
+
borderRadius: '6px',
|
|
861
|
+
fontSize: '14px',
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
const buttonStyle: React.CSSProperties = {
|
|
865
|
+
padding: '8px 16px',
|
|
866
|
+
backgroundColor: '#0284c7',
|
|
867
|
+
color: '#fff',
|
|
868
|
+
border: 'none',
|
|
869
|
+
borderRadius: '6px',
|
|
870
|
+
cursor: 'pointer',
|
|
871
|
+
fontSize: '14px',
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const itemStyle: React.CSSProperties = {
|
|
875
|
+
backgroundColor: '#fff',
|
|
876
|
+
padding: '12px',
|
|
877
|
+
borderRadius: '6px',
|
|
878
|
+
marginBottom: '8px',
|
|
879
|
+
border: '1px solid #e2e8f0',
|
|
686
880
|
};
|
|
687
|
-
${agentHandler}
|
|
688
881
|
|
|
689
882
|
return (
|
|
690
|
-
<div
|
|
691
|
-
<
|
|
692
|
-
<
|
|
693
|
-
<
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
</
|
|
706
|
-
<
|
|
707
|
-
|
|
708
|
-
|
|
883
|
+
<div style={containerStyle}>
|
|
884
|
+
<div style={sidebarStyle}>
|
|
885
|
+
<div style={{ fontSize: '16px', fontWeight: 'bold' }}>⚡ FeltDB Workspace</div>
|
|
886
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
887
|
+
<button
|
|
888
|
+
style={navItemStyle(currentView === 'dashboard')}
|
|
889
|
+
onClick={() => setCurrentView('dashboard')}
|
|
890
|
+
>
|
|
891
|
+
📊 Dashboard
|
|
892
|
+
</button>
|
|
893
|
+
<button
|
|
894
|
+
style={navItemStyle(currentView === 'projects')}
|
|
895
|
+
onClick={() => setCurrentView('projects')}
|
|
896
|
+
>
|
|
897
|
+
📁 Projects
|
|
898
|
+
</button>
|
|
899
|
+
<button
|
|
900
|
+
style={navItemStyle(currentView === 'tasks')}
|
|
901
|
+
onClick={() => setCurrentView('tasks')}
|
|
902
|
+
>
|
|
903
|
+
✅ Tasks
|
|
904
|
+
</button>
|
|
905
|
+
<button
|
|
906
|
+
style={navItemStyle(currentView === 'activity')}
|
|
907
|
+
onClick={() => setCurrentView('activity')}
|
|
908
|
+
>
|
|
909
|
+
📝 Activity
|
|
910
|
+
</button>
|
|
911
|
+
<button
|
|
912
|
+
style={navItemStyle(currentView === 'inspector')}
|
|
913
|
+
onClick={() => setCurrentView('inspector')}
|
|
914
|
+
>
|
|
915
|
+
🔍 Inspector
|
|
916
|
+
</button>
|
|
917
|
+
</div>
|
|
918
|
+
<div style={{ marginTop: 'auto', fontSize: '12px', color: '#64748b' }}>
|
|
919
|
+
{projectsList.length} projects • {tasksList.length} tasks
|
|
920
|
+
</div>
|
|
921
|
+
</div>
|
|
922
|
+
|
|
923
|
+
<div style={mainStyle}>
|
|
924
|
+
<div style={headerStyle}>
|
|
925
|
+
<div style={titleStyle}>
|
|
926
|
+
{currentView === 'dashboard' && '📊 Dashboard'}
|
|
927
|
+
{currentView === 'projects' && '📁 Projects'}
|
|
928
|
+
{currentView === 'tasks' && '✅ Tasks'}
|
|
929
|
+
{currentView === 'activity' && '📝 Activity'}
|
|
930
|
+
{currentView === 'inspector' && '🔍 Data Inspector'}
|
|
709
931
|
</div>
|
|
710
|
-
</
|
|
711
|
-
|
|
712
|
-
{
|
|
713
|
-
|
|
714
|
-
<
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
932
|
+
</div>
|
|
933
|
+
|
|
934
|
+
<div style={contentStyle}>
|
|
935
|
+
{currentView === 'dashboard' && (
|
|
936
|
+
<div>
|
|
937
|
+
<div style={statsStyle}>
|
|
938
|
+
<div style={statCardStyle}>
|
|
939
|
+
<div style={{ fontSize: '12px', color: '#64748b' }}>Projects</div>
|
|
940
|
+
<div style={{ fontSize: '28px', fontWeight: 'bold' }}>{projectsList.length}</div>
|
|
941
|
+
</div>
|
|
942
|
+
<div style={statCardStyle}>
|
|
943
|
+
<div style={{ fontSize: '12px', color: '#64748b' }}>Tasks</div>
|
|
944
|
+
<div style={{ fontSize: '28px', fontWeight: 'bold' }}>{tasksList.length}</div>
|
|
945
|
+
</div>
|
|
946
|
+
<div style={statCardStyle}>
|
|
947
|
+
<div style={{ fontSize: '12px', color: '#64748b' }}>Activity Events</div>
|
|
948
|
+
<div style={{ fontSize: '28px', fontWeight: 'bold' }}>{activityList.length}</div>
|
|
949
|
+
</div>
|
|
950
|
+
</div>
|
|
951
|
+
<h3>Welcome to FeltDB Workspace!</h3>
|
|
952
|
+
<p style={{ color: '#64748b', lineHeight: '1.6' }}>
|
|
953
|
+
This application demonstrates core FeltDB capabilities:<br/>
|
|
954
|
+
✅ Collections and relationships (Projects → Tasks)<br/>
|
|
955
|
+
✅ Indexed querying for efficient lookups<br/>
|
|
956
|
+
✅ Activity logs for audit trails<br/>
|
|
957
|
+
✅ Local-first persistence with IndexedDB<br/>
|
|
958
|
+
✅ Reactive updates using React hooks<br/>
|
|
959
|
+
<br/>
|
|
960
|
+
Create a project to get started! 👇
|
|
961
|
+
</p>
|
|
739
962
|
</div>
|
|
740
|
-
|
|
963
|
+
)}
|
|
741
964
|
|
|
742
|
-
{
|
|
743
|
-
<div
|
|
744
|
-
<
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
<
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
<div className="edit-mode">
|
|
762
|
-
<input
|
|
763
|
-
type="text"
|
|
764
|
-
value={editTitle}
|
|
765
|
-
onChange={(e) => setEditTitle(e.target.value)}
|
|
766
|
-
className="edit-title"
|
|
767
|
-
/>
|
|
768
|
-
<textarea
|
|
769
|
-
value={editContent}
|
|
770
|
-
onChange={(e) => setEditContent(e.target.value)}
|
|
771
|
-
className="edit-content"
|
|
772
|
-
rows={6}
|
|
773
|
-
/>
|
|
774
|
-
<div className="edit-actions">
|
|
775
|
-
<button onClick={handleEditSave} className="save-btn">
|
|
776
|
-
✓ Save
|
|
777
|
-
</button>
|
|
778
|
-
<button onClick={() => setEditingId(null)} className="cancel-btn">
|
|
779
|
-
✕ Cancel
|
|
780
|
-
</button>
|
|
965
|
+
{currentView === 'projects' && (
|
|
966
|
+
<div>
|
|
967
|
+
<form style={formStyle} onSubmit={handleCreateProject}>
|
|
968
|
+
<input
|
|
969
|
+
style={inputStyle}
|
|
970
|
+
type="text"
|
|
971
|
+
placeholder="New project name..."
|
|
972
|
+
value={newProjectName}
|
|
973
|
+
onChange={(e) => setNewProjectName(e.target.value)}
|
|
974
|
+
/>
|
|
975
|
+
<button style={buttonStyle} type="submit">Create</button>
|
|
976
|
+
</form>
|
|
977
|
+
<div>
|
|
978
|
+
{projectsList.map((p: any) => (
|
|
979
|
+
<div key={p.id} style={itemStyle}>
|
|
980
|
+
<div style={{ fontWeight: 'bold' }}>{p.name}</div>
|
|
981
|
+
{p.description && (
|
|
982
|
+
<div style={{ fontSize: '14px', color: '#64748b', margin: '4px 0' }}>
|
|
983
|
+
{p.description}
|
|
781
984
|
</div>
|
|
985
|
+
)}
|
|
986
|
+
<div style={{ fontSize: '12px', color: '#0284c7', marginTop: '4px' }}>
|
|
987
|
+
Status: {p.status}
|
|
782
988
|
</div>
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
989
|
+
</div>
|
|
990
|
+
))}
|
|
991
|
+
{projectsList.length === 0 && (
|
|
992
|
+
<p style={{ color: '#64748b' }}>No projects yet. Create one above!</p>
|
|
993
|
+
)}
|
|
994
|
+
</div>
|
|
995
|
+
</div>
|
|
996
|
+
)}
|
|
997
|
+
|
|
998
|
+
{currentView === 'tasks' && (
|
|
999
|
+
<div>
|
|
1000
|
+
<form style={formStyle} onSubmit={handleCreateTask}>
|
|
1001
|
+
<select
|
|
1002
|
+
style={inputStyle}
|
|
1003
|
+
value={newTaskProject}
|
|
1004
|
+
onChange={(e) => setNewTaskProject(e.target.value)}
|
|
1005
|
+
>
|
|
1006
|
+
<option value="">Select a project...</option>
|
|
1007
|
+
{projectsList.map((p: any) => (
|
|
1008
|
+
<option key={p.id} value={p.id}>{p.name}</option>
|
|
1009
|
+
))}
|
|
1010
|
+
</select>
|
|
1011
|
+
<input
|
|
1012
|
+
style={inputStyle}
|
|
1013
|
+
type="text"
|
|
1014
|
+
placeholder="Task title..."
|
|
1015
|
+
value={newTaskTitle}
|
|
1016
|
+
onChange={(e) => setNewTaskTitle(e.target.value)}
|
|
1017
|
+
/>
|
|
1018
|
+
<button style={buttonStyle} type="submit">Create</button>
|
|
1019
|
+
</form>
|
|
1020
|
+
<div>
|
|
1021
|
+
{tasksList.map((t: any) => (
|
|
1022
|
+
<div key={t.id} style={itemStyle}>
|
|
1023
|
+
<div style={{ fontWeight: 'bold' }}>{t.title}</div>
|
|
1024
|
+
{t.description && (
|
|
1025
|
+
<div style={{ fontSize: '14px', color: '#64748b', margin: '4px 0' }}>
|
|
1026
|
+
{t.description}
|
|
790
1027
|
</div>
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1028
|
+
)}
|
|
1029
|
+
<div style={{ fontSize: '12px', color: '#64748b', marginTop: '4px' }}>
|
|
1030
|
+
Priority: <strong>{t.priority}</strong> | Status: <strong>{t.status}</strong>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
))}
|
|
1034
|
+
{tasksList.length === 0 && (
|
|
1035
|
+
<p style={{ color: '#64748b' }}>No tasks yet. Create one above!</p>
|
|
1036
|
+
)}
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
)}
|
|
1040
|
+
|
|
1041
|
+
{currentView === 'activity' && (
|
|
1042
|
+
<div>
|
|
1043
|
+
{activityList.length === 0 ? (
|
|
1044
|
+
<p style={{ color: '#64748b' }}>No activity yet</p>
|
|
1045
|
+
) : (
|
|
1046
|
+
<div>
|
|
1047
|
+
{activityList.slice(-30).map((event: any, idx: number) => (
|
|
1048
|
+
<div key={idx} style={itemStyle}>
|
|
1049
|
+
<div style={{ fontSize: '12px', color: '#64748b' }}>
|
|
1050
|
+
{new Date(event.timestamp).toLocaleString()}
|
|
805
1051
|
</div>
|
|
806
|
-
|
|
807
|
-
|
|
1052
|
+
<div style={{ fontWeight: 'bold' }}>{event.type}</div>
|
|
1053
|
+
{event.entityName && (
|
|
1054
|
+
<div style={{ fontSize: '14px', marginTop: '4px' }}>
|
|
1055
|
+
{event.entityName}
|
|
1056
|
+
</div>
|
|
1057
|
+
)}
|
|
1058
|
+
</div>
|
|
1059
|
+
))}
|
|
1060
|
+
</div>
|
|
1061
|
+
)}
|
|
1062
|
+
</div>
|
|
1063
|
+
)}
|
|
1064
|
+
|
|
1065
|
+
{currentView === 'inspector' && (
|
|
1066
|
+
<div>
|
|
1067
|
+
<h3>Data Inspector</h3>
|
|
1068
|
+
<p style={{ color: '#64748b', marginBottom: '16px' }}>
|
|
1069
|
+
Developer panel showing FeltDB internals
|
|
1070
|
+
</p>
|
|
1071
|
+
<div style={statCardStyle}>
|
|
1072
|
+
<div style={{ fontWeight: 'bold', marginBottom: '8px' }}>Collections</div>
|
|
1073
|
+
<div style={{ fontSize: '14px', color: '#64748b', lineHeight: '1.6' }}>
|
|
1074
|
+
📦 projects: {projectsList.length} records<br/>
|
|
1075
|
+
📦 tasks: {tasksList.length} records<br/>
|
|
1076
|
+
📦 activity: {activityList.length} events<br/>
|
|
808
1077
|
</div>
|
|
809
|
-
|
|
1078
|
+
</div>
|
|
1079
|
+
<div style={statCardStyle}>
|
|
1080
|
+
<div style={{ fontWeight: 'bold', marginBottom: '8px' }}>Storage</div>
|
|
1081
|
+
<div style={{ fontSize: '14px', color: '#64748b', lineHeight: '1.6' }}>
|
|
1082
|
+
💾 Type: IndexedDB<br/>
|
|
1083
|
+
🔑 Namespace: FeltDB<br/>
|
|
1084
|
+
🔄 Sync: Automatic
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
<div style={statCardStyle}>
|
|
1088
|
+
<div style={{ fontWeight: 'bold', marginBottom: '8px' }}>Indexes</div>
|
|
1089
|
+
<div style={{ fontSize: '14px', color: '#64748b', lineHeight: '1.6' }}>
|
|
1090
|
+
📌 projects.status<br/>
|
|
1091
|
+
📌 tasks.projectId<br/>
|
|
1092
|
+
📌 tasks.status<br/>
|
|
1093
|
+
📌 tasks.priority<br/>
|
|
1094
|
+
📌 activity.timestamp
|
|
1095
|
+
</div>
|
|
1096
|
+
</div>
|
|
810
1097
|
</div>
|
|
811
1098
|
)}
|
|
812
|
-
</
|
|
813
|
-
|
|
814
|
-
</main>
|
|
815
|
-
|
|
816
|
-
<style>{\`
|
|
817
|
-
* { box-sizing: border-box; }
|
|
818
|
-
|
|
819
|
-
body {
|
|
820
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
|
|
821
|
-
margin: 0;
|
|
822
|
-
padding: 0;
|
|
823
|
-
background: #f5f7fa;
|
|
824
|
-
color: #333;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
.app {
|
|
828
|
-
max-width: 1400px;
|
|
829
|
-
margin: 0 auto;
|
|
830
|
-
padding: 20px;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
header {
|
|
834
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
835
|
-
color: white;
|
|
836
|
-
padding: 40px 30px;
|
|
837
|
-
border-radius: 12px;
|
|
838
|
-
margin-bottom: 30px;
|
|
839
|
-
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
header h1 {
|
|
843
|
-
margin: 0 0 10px 0;
|
|
844
|
-
font-size: 32px;
|
|
845
|
-
font-weight: 700;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
header p {
|
|
849
|
-
margin: 0;
|
|
850
|
-
opacity: 0.95;
|
|
851
|
-
font-size: 16px;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
main {
|
|
855
|
-
background: white;
|
|
856
|
-
padding: 30px;
|
|
857
|
-
border-radius: 12px;
|
|
858
|
-
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
.stats {
|
|
862
|
-
display: grid;
|
|
863
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
864
|
-
gap: 20px;
|
|
865
|
-
margin-bottom: 30px;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
.stat {
|
|
869
|
-
padding: 24px;
|
|
870
|
-
background: linear-gradient(135deg, #f5f7fa 0%, #f9f9f9 100%);
|
|
871
|
-
border-radius: 8px;
|
|
872
|
-
border-left: 4px solid #667eea;
|
|
873
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
.stat .label {
|
|
877
|
-
display: block;
|
|
878
|
-
color: #999;
|
|
879
|
-
font-size: 10px;
|
|
880
|
-
text-transform: uppercase;
|
|
881
|
-
letter-spacing: 1px;
|
|
882
|
-
margin-bottom: 8px;
|
|
883
|
-
font-weight: 700;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
.stat .value {
|
|
887
|
-
display: block;
|
|
888
|
-
font-size: 32px;
|
|
889
|
-
font-weight: 700;
|
|
890
|
-
color: #667eea;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
.error-message {
|
|
894
|
-
padding: 16px 20px;
|
|
895
|
-
background: #fff5f5;
|
|
896
|
-
border: 1px solid #fca5a5;
|
|
897
|
-
border-left: 4px solid #f56565;
|
|
898
|
-
border-radius: 8px;
|
|
899
|
-
margin-bottom: 20px;
|
|
900
|
-
display: flex;
|
|
901
|
-
justify-content: space-between;
|
|
902
|
-
align-items: center;
|
|
903
|
-
color: #c53030;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
.error-message strong { font-weight: 600; }
|
|
907
|
-
|
|
908
|
-
.retry-btn {
|
|
909
|
-
background: #f56565;
|
|
910
|
-
padding: 8px 16px;
|
|
911
|
-
font-size: 12px;
|
|
912
|
-
margin-left: 15px;
|
|
913
|
-
border-radius: 4px;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
.retry-btn:hover { background: #e53e3e; }
|
|
917
|
-
|
|
918
|
-
.docs-header {
|
|
919
|
-
display: flex;
|
|
920
|
-
justify-content: space-between;
|
|
921
|
-
align-items: center;
|
|
922
|
-
margin-bottom: 24px;
|
|
923
|
-
gap: 20px;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
.docs-header h2 {
|
|
927
|
-
margin: 0;
|
|
928
|
-
color: #1a202c;
|
|
929
|
-
font-size: 26px;
|
|
930
|
-
flex: 1;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
.docs-controls {
|
|
934
|
-
display: flex;
|
|
935
|
-
gap: 12px;
|
|
936
|
-
flex: 1;
|
|
937
|
-
max-width: 600px;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
.search-input {
|
|
941
|
-
flex: 1;
|
|
942
|
-
padding: 10px 16px;
|
|
943
|
-
border: 1px solid #e2e8f0;
|
|
944
|
-
border-radius: 6px;
|
|
945
|
-
font-size: 14px;
|
|
946
|
-
transition: all 0.2s ease;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
.search-input:focus {
|
|
950
|
-
outline: none;
|
|
951
|
-
border-color: #667eea;
|
|
952
|
-
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
.loading {
|
|
956
|
-
text-align: center;
|
|
957
|
-
padding: 60px 20px;
|
|
958
|
-
color: #999;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
.spinner {
|
|
962
|
-
display: inline-block;
|
|
963
|
-
width: 48px;
|
|
964
|
-
height: 48px;
|
|
965
|
-
border: 4px solid #e2e8f0;
|
|
966
|
-
border-top-color: #667eea;
|
|
967
|
-
border-radius: 50%;
|
|
968
|
-
animation: spin 0.8s linear infinite;
|
|
969
|
-
margin-bottom: 20px;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
973
|
-
|
|
974
|
-
.empty-state {
|
|
975
|
-
text-align: center;
|
|
976
|
-
padding: 60px 20px;
|
|
977
|
-
color: #999;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
.empty-state p { margin: 8px 0; font-size: 16px; }
|
|
981
|
-
.empty-state .hint { font-size: 14px; opacity: 0.8; }
|
|
982
|
-
|
|
983
|
-
.docs-grid {
|
|
984
|
-
display: grid;
|
|
985
|
-
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
|
986
|
-
gap: 20px;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
.doc-card {
|
|
990
|
-
background: #f9f9f9;
|
|
991
|
-
border: 1px solid #e2e8f0;
|
|
992
|
-
border-radius: 8px;
|
|
993
|
-
padding: 20px;
|
|
994
|
-
transition: all 0.3s ease;
|
|
995
|
-
display: flex;
|
|
996
|
-
flex-direction: column;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
.doc-card:hover {
|
|
1000
|
-
border-color: #667eea;
|
|
1001
|
-
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
|
|
1002
|
-
transform: translateY(-2px);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
.doc-card h3 {
|
|
1006
|
-
margin: 0 0 12px 0;
|
|
1007
|
-
color: #1a202c;
|
|
1008
|
-
font-size: 18px;
|
|
1009
|
-
font-weight: 600;
|
|
1010
|
-
word-break: break-word;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
.doc-preview {
|
|
1014
|
-
flex: 1;
|
|
1015
|
-
margin: 0 0 12px 0;
|
|
1016
|
-
color: #666;
|
|
1017
|
-
font-size: 14px;
|
|
1018
|
-
line-height: 1.6;
|
|
1019
|
-
display: -webkit-box;
|
|
1020
|
-
-webkit-line-clamp: 3;
|
|
1021
|
-
-webkit-box-orient: vertical;
|
|
1022
|
-
overflow: hidden;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
.doc-meta {
|
|
1026
|
-
display: flex;
|
|
1027
|
-
justify-content: space-between;
|
|
1028
|
-
align-items: center;
|
|
1029
|
-
padding: 12px 0;
|
|
1030
|
-
border-top: 1px solid #e2e8f0;
|
|
1031
|
-
border-bottom: 1px solid #e2e8f0;
|
|
1032
|
-
margin-bottom: 12px;
|
|
1033
|
-
font-size: 12px;
|
|
1034
|
-
color: #999;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
.doc-size {
|
|
1038
|
-
background: #f0f4ff;
|
|
1039
|
-
padding: 2px 8px;
|
|
1040
|
-
border-radius: 4px;
|
|
1041
|
-
color: #667eea;
|
|
1042
|
-
font-weight: 600;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
.doc-actions {
|
|
1046
|
-
display: flex;
|
|
1047
|
-
gap: 8px;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
.edit-btn, .delete-btn, .save-btn, .cancel-btn {
|
|
1051
|
-
flex: 1;
|
|
1052
|
-
padding: 8px 12px;
|
|
1053
|
-
font-size: 13px;
|
|
1054
|
-
border: 1px solid;
|
|
1055
|
-
border-radius: 6px;
|
|
1056
|
-
cursor: pointer;
|
|
1057
|
-
font-weight: 600;
|
|
1058
|
-
transition: all 0.2s ease;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
.edit-btn {
|
|
1062
|
-
background: #eef2ff;
|
|
1063
|
-
border-color: #667eea;
|
|
1064
|
-
color: #667eea;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
.edit-btn:hover { background: #e0e7ff; }
|
|
1068
|
-
|
|
1069
|
-
.delete-btn {
|
|
1070
|
-
background: #fee;
|
|
1071
|
-
border-color: #fca5a5;
|
|
1072
|
-
color: #c53030;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
.delete-btn:hover:not(:disabled) { background: #fdd; }
|
|
1076
|
-
.delete-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
1077
|
-
|
|
1078
|
-
.edit-mode {
|
|
1079
|
-
display: flex;
|
|
1080
|
-
flex-direction: column;
|
|
1081
|
-
gap: 12px;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
.edit-title, .edit-content {
|
|
1085
|
-
padding: 10px 12px;
|
|
1086
|
-
border: 1px solid #ddd;
|
|
1087
|
-
border-radius: 6px;
|
|
1088
|
-
font-family: inherit;
|
|
1089
|
-
font-size: 14px;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
.edit-title {
|
|
1093
|
-
font-size: 16px;
|
|
1094
|
-
font-weight: 600;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
.edit-title:focus, .edit-content:focus {
|
|
1098
|
-
outline: none;
|
|
1099
|
-
border-color: #667eea;
|
|
1100
|
-
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
.edit-actions {
|
|
1104
|
-
display: flex;
|
|
1105
|
-
gap: 8px;
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
.save-btn {
|
|
1109
|
-
background: #667eea;
|
|
1110
|
-
color: white;
|
|
1111
|
-
border: none;
|
|
1112
|
-
flex: 1;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
.save-btn:hover { background: #5568d3; }
|
|
1116
|
-
|
|
1117
|
-
.cancel-btn {
|
|
1118
|
-
background: #f0f0f0;
|
|
1119
|
-
color: #666;
|
|
1120
|
-
border: none;
|
|
1121
|
-
flex: 1;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
.cancel-btn:hover { background: #e0e0e0; }
|
|
1125
|
-
|
|
1126
|
-
.primary-btn {
|
|
1127
|
-
background: #667eea;
|
|
1128
|
-
color: white;
|
|
1129
|
-
border: none;
|
|
1130
|
-
padding: 10px 20px;
|
|
1131
|
-
white-space: nowrap;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
.primary-btn:hover:not(:disabled) { background: #5568d3; }
|
|
1135
|
-
.primary-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
1136
|
-
|
|
1137
|
-
textarea, input {
|
|
1138
|
-
display: block;
|
|
1139
|
-
width: 100%;
|
|
1140
|
-
margin: 12px 0;
|
|
1141
|
-
padding: 12px;
|
|
1142
|
-
font-family: inherit;
|
|
1143
|
-
font-size: 14px;
|
|
1144
|
-
border: 1px solid #ddd;
|
|
1145
|
-
border-radius: 6px;
|
|
1146
|
-
resize: vertical;
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
textarea:focus, input:focus {
|
|
1150
|
-
outline: none;
|
|
1151
|
-
border-color: #667eea;
|
|
1152
|
-
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
.status-badge {
|
|
1156
|
-
display: inline-block;
|
|
1157
|
-
padding: 8px 12px;
|
|
1158
|
-
background: #f0f4ff;
|
|
1159
|
-
color: #667eea;
|
|
1160
|
-
border-radius: 6px;
|
|
1161
|
-
font-size: 12px;
|
|
1162
|
-
font-weight: 600;
|
|
1163
|
-
margin-bottom: 16px;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
.researcher {
|
|
1167
|
-
border-top: 2px solid #e2e8f0;
|
|
1168
|
-
margin-top: 40px;
|
|
1169
|
-
padding-top: 30px;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
.researcher h2 {
|
|
1173
|
-
margin-top: 0;
|
|
1174
|
-
color: #1a202c;
|
|
1175
|
-
font-size: 20px;
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
.research-btn {
|
|
1179
|
-
background: #667eea;
|
|
1180
|
-
color: white;
|
|
1181
|
-
border: none;
|
|
1182
|
-
padding: 12px 24px;
|
|
1183
|
-
margin: 16px 0;
|
|
1184
|
-
border-radius: 6px;
|
|
1185
|
-
cursor: pointer;
|
|
1186
|
-
font-weight: 600;
|
|
1187
|
-
transition: all 0.2s ease;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
.research-btn:hover:not(:disabled) { background: #5568d3; }
|
|
1191
|
-
.research-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
1192
|
-
|
|
1193
|
-
.research-result {
|
|
1194
|
-
background: #f5f8ff;
|
|
1195
|
-
border: 1px solid #d4e0ff;
|
|
1196
|
-
border-radius: 8px;
|
|
1197
|
-
padding: 16px;
|
|
1198
|
-
margin-top: 20px;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
.ai-modes {
|
|
1202
|
-
display: flex;
|
|
1203
|
-
gap: 16px;
|
|
1204
|
-
margin-bottom: 16px;
|
|
1205
|
-
flex-wrap: wrap;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
.mode-group {
|
|
1209
|
-
display: flex;
|
|
1210
|
-
flex-direction: column;
|
|
1211
|
-
gap: 6px;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
.mode-group-label {
|
|
1215
|
-
font-size: 11px;
|
|
1216
|
-
font-weight: 700;
|
|
1217
|
-
text-transform: uppercase;
|
|
1218
|
-
color: #999;
|
|
1219
|
-
letter-spacing: 0.5px;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
.mode-group {
|
|
1223
|
-
display: flex;
|
|
1224
|
-
gap: 6px;
|
|
1225
|
-
flex-wrap: wrap;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
.mode-btn {
|
|
1229
|
-
background: #f0f4ff;
|
|
1230
|
-
color: #667eea;
|
|
1231
|
-
border: 1px solid #d4e0ff;
|
|
1232
|
-
padding: 8px 14px;
|
|
1233
|
-
border-radius: 6px;
|
|
1234
|
-
cursor: pointer;
|
|
1235
|
-
font-size: 12px;
|
|
1236
|
-
font-weight: 600;
|
|
1237
|
-
transition: all 0.2s ease;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
.mode-btn:hover:not(:disabled) {
|
|
1241
|
-
background: #e0e7ff;
|
|
1242
|
-
border-color: #667eea;
|
|
1243
|
-
transform: translateY(-1px);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
.mode-btn.active {
|
|
1247
|
-
background: #667eea;
|
|
1248
|
-
color: white;
|
|
1249
|
-
border-color: #667eea;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
.mode-btn.code-mode {
|
|
1253
|
-
background: #fef5e7;
|
|
1254
|
-
color: #c87832;
|
|
1255
|
-
border-color: #f4d29d;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
.mode-btn.code-mode:hover:not(:disabled) {
|
|
1259
|
-
background: #fdebd0;
|
|
1260
|
-
border-color: #c87832;
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
.mode-btn.code-mode.active {
|
|
1264
|
-
background: #c87832;
|
|
1265
|
-
color: white;
|
|
1266
|
-
border-color: #c87832;
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
.mode-btn:disabled {
|
|
1270
|
-
opacity: 0.5;
|
|
1271
|
-
cursor: not-allowed;
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
.doc-selector {
|
|
1275
|
-
margin-bottom: 16px;
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
.doc-selector label {
|
|
1279
|
-
display: block;
|
|
1280
|
-
font-size: 13px;
|
|
1281
|
-
font-weight: 600;
|
|
1282
|
-
color: #666;
|
|
1283
|
-
margin-bottom: 8px;
|
|
1284
|
-
text-transform: uppercase;
|
|
1285
|
-
letter-spacing: 0.5px;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
.doc-selector select {
|
|
1289
|
-
width: 100%;
|
|
1290
|
-
padding: 10px 12px;
|
|
1291
|
-
border: 1px solid #ddd;
|
|
1292
|
-
border-radius: 6px;
|
|
1293
|
-
font-family: inherit;
|
|
1294
|
-
font-size: 14px;
|
|
1295
|
-
background: white;
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
.doc-selector select:focus {
|
|
1299
|
-
outline: none;
|
|
1300
|
-
border-color: #667eea;
|
|
1301
|
-
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
.research-result h3 {
|
|
1305
|
-
margin: 0 0 12px 0;
|
|
1306
|
-
color: #667eea;
|
|
1307
|
-
font-size: 16px;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
.result-content {
|
|
1311
|
-
color: #555;
|
|
1312
|
-
line-height: 1.6;
|
|
1313
|
-
font-size: 14px;
|
|
1314
|
-
margin-bottom: 16px;
|
|
1315
|
-
max-height: 400px;
|
|
1316
|
-
overflow-y: auto;
|
|
1317
|
-
padding: 12px;
|
|
1318
|
-
background: white;
|
|
1319
|
-
border-radius: 4px;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
.result-actions {
|
|
1323
|
-
display: flex;
|
|
1324
|
-
gap: 8px;
|
|
1325
|
-
flex-wrap: wrap;
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
.save-result-btn, .copy-result-btn {
|
|
1329
|
-
flex: 1;
|
|
1330
|
-
min-width: 140px;
|
|
1331
|
-
background: #48bb78;
|
|
1332
|
-
color: white;
|
|
1333
|
-
border: none;
|
|
1334
|
-
padding: 10px 16px;
|
|
1335
|
-
border-radius: 6px;
|
|
1336
|
-
cursor: pointer;
|
|
1337
|
-
font-weight: 600;
|
|
1338
|
-
font-size: 13px;
|
|
1339
|
-
transition: all 0.2s ease;
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
.save-result-btn:hover {
|
|
1343
|
-
background: #38a169;
|
|
1344
|
-
transform: translateY(-1px);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
.copy-result-btn {
|
|
1348
|
-
background: #4299e1;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
.copy-result-btn:hover {
|
|
1352
|
-
background: #3182ce;
|
|
1353
|
-
transform: translateY(-1px);
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
.code-note {
|
|
1357
|
-
font-size: 12px;
|
|
1358
|
-
color: #999;
|
|
1359
|
-
margin-top: 12px;
|
|
1360
|
-
font-style: italic;
|
|
1361
|
-
border-top: 1px solid #e2e8f0;
|
|
1362
|
-
padding-top: 12px;
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
@media (max-width: 768px) {
|
|
1366
|
-
.docs-header {
|
|
1367
|
-
flex-direction: column;
|
|
1368
|
-
align-items: stretch;
|
|
1369
|
-
}
|
|
1370
|
-
.docs-controls {
|
|
1371
|
-
flex-direction: column;
|
|
1372
|
-
max-width: 100%;
|
|
1373
|
-
}
|
|
1374
|
-
.docs-grid {
|
|
1375
|
-
grid-template-columns: 1fr;
|
|
1376
|
-
}
|
|
1377
|
-
header { padding: 24px 20px; }
|
|
1378
|
-
main { padding: 20px; }
|
|
1379
|
-
}
|
|
1380
|
-
\`}</style>
|
|
1099
|
+
</div>
|
|
1100
|
+
</div>
|
|
1381
1101
|
</div>
|
|
1382
1102
|
);
|
|
1383
1103
|
}
|
|
1384
1104
|
`;
|
|
1105
|
+
;
|
|
1385
1106
|
fs.writeFileSync(path.join(srcDir, 'App.tsx'), appTsx);
|
|
1386
1107
|
const indexTsx = `import React from 'react';
|
|
1387
1108
|
import ReactDOM from 'react-dom/client';
|
|
@@ -1430,12 +1151,20 @@ main().catch(console.error);
|
|
|
1430
1151
|
# Copy this file to .env.local and update the values
|
|
1431
1152
|
|
|
1432
1153
|
# API Key for authenticating with FeltDB servers
|
|
1433
|
-
# Leave empty for browser runtime
|
|
1154
|
+
# Leave empty for browser runtime; required for authenticated self-hosted and managed runtimes
|
|
1434
1155
|
VITE_FELTDB_API_KEY=
|
|
1435
1156
|
|
|
1436
|
-
# FeltDB Server URL (
|
|
1157
|
+
# FeltDB Server URL (self-hosted or managed runtime)
|
|
1437
1158
|
VITE_FELTDB_URL=http://localhost:7700
|
|
1438
1159
|
|
|
1160
|
+
# Managed example: https://runtime.your-app.feltdb.com
|
|
1161
|
+
VITE_FELTDB_MANAGED_URL=
|
|
1162
|
+
VITE_FELTDB_MANAGED_API_KEY=
|
|
1163
|
+
VITE_FELTDB_MANAGED_TENANT_ID=
|
|
1164
|
+
VITE_FELTDB_MANAGED_APPLICATION_ID=
|
|
1165
|
+
VITE_FELTDB_MANAGED_NAMESPACE=
|
|
1166
|
+
VITE_FELTDB_MANAGED_ENVIRONMENT=production
|
|
1167
|
+
|
|
1439
1168
|
# Override the default self-hosted container image
|
|
1440
1169
|
# Example: ghcr.io/rkendel1/feltdb:latest
|
|
1441
1170
|
FELTDB_IMAGE=
|
|
@@ -1544,6 +1273,17 @@ npm run dev
|
|
|
1544
1273
|
|
|
1545
1274
|
The self-hosted instance runs on \`http://localhost:7700\` by default.
|
|
1546
1275
|
|
|
1276
|
+
## Managed Runtime
|
|
1277
|
+
|
|
1278
|
+
\`\`\`json
|
|
1279
|
+
{
|
|
1280
|
+
"runtime": "managed",
|
|
1281
|
+
"storage": "managed"
|
|
1282
|
+
}
|
|
1283
|
+
\`\`\`
|
|
1284
|
+
|
|
1285
|
+
Managed mode uses the same application API with a FeltDB-hosted endpoint. The CLI creates \`.env.local\` with \`VITE_FELTDB_MANAGED_URL\` and \`VITE_FELTDB_MANAGED_API_KEY\` after account setup. Studio uses that same connection for state, health, operations, and API-key administration.
|
|
1286
|
+
|
|
1547
1287
|
## Vector Search Status
|
|
1548
1288
|
|
|
1549
1289
|
### Current Status
|
|
@@ -1700,8 +1440,10 @@ build/
|
|
|
1700
1440
|
// Create README
|
|
1701
1441
|
const readme = `# ${applicationName}
|
|
1702
1442
|
|
|
1703
|
-
A FeltDB
|
|
1704
|
-
|
|
1443
|
+
A FeltDB Workspace application demonstrating local-first database capabilities.
|
|
1444
|
+
|
|
1445
|
+
**Deployment Target:** \`${runtime}\`
|
|
1446
|
+
- **Runtime:** ${runtime === 'browser' ? 'Browser (IndexedDB)' : runtime === 'node' ? 'Node.js Server' : runtime === 'managed' ? 'Managed FeltDB' : 'Self-hosted Docker'}
|
|
1705
1447
|
- **Framework:** ${framework}
|
|
1706
1448
|
- **Distributed:** ${distributed ? 'Yes' : 'No'}
|
|
1707
1449
|
- **Agents:** ${hasAgents ? 'Yes' : 'No'}
|
|
@@ -1714,21 +1456,42 @@ npm install
|
|
|
1714
1456
|
npm run dev
|
|
1715
1457
|
\`\`\`
|
|
1716
1458
|
|
|
1717
|
-
The application will be available at http://localhost:5173.
|
|
1459
|
+
The application will be available at http://localhost:5173 (or your configured port).
|
|
1460
|
+
|
|
1461
|
+
## FeltDB Workspace Showcase
|
|
1462
|
+
|
|
1463
|
+
This application demonstrates core FeltDB capabilities:
|
|
1464
|
+
|
|
1465
|
+
✅ **Collections & Relationships** - Projects, Tasks, and Activity collections with foreign key relationships
|
|
1466
|
+
✅ **Indexed Querying** - Efficient queries by projectId, status, priority, and timestamp
|
|
1467
|
+
✅ **Activity/Audit Logs** - Append-only event collection demonstrating immutable history
|
|
1468
|
+
✅ **Persistent Storage** - Data persists through page reloads (IndexedDB)
|
|
1469
|
+
✅ **Reactive State** - Real-time updates using React hooks
|
|
1470
|
+
✅ **Local-first** - Works offline with no server required
|
|
1471
|
+
✅ **Data Inspector** - Built-in developer panel to inspect collections and indexes
|
|
1472
|
+
|
|
1473
|
+
### Application Features
|
|
1474
|
+
|
|
1475
|
+
- **Dashboard** - Overview of projects, tasks, and runtime status
|
|
1476
|
+
- **Projects** - Create and manage projects with descriptions
|
|
1477
|
+
- **Tasks** - Create tasks within projects with priority and status
|
|
1478
|
+
- **Activity Log** - Append-only event history of all changes
|
|
1479
|
+
- **Search** - Indexed searching across task titles and descriptions
|
|
1480
|
+
- **Data Inspector** - Developer panel showing collections, indexes, and storage info
|
|
1718
1481
|
|
|
1719
1482
|
## Project Structure
|
|
1720
1483
|
|
|
1721
1484
|
\`\`\`
|
|
1722
1485
|
${applicationName}/
|
|
1723
|
-
├── feltdb/ # FeltDB configuration
|
|
1486
|
+
├── feltdb/ # FeltDB configuration
|
|
1724
1487
|
│ ├── agents/ # Agent definitions
|
|
1725
1488
|
│ ├── capabilities/ # Capability implementations
|
|
1726
1489
|
│ ├── workflows/ # Workflow definitions
|
|
1727
1490
|
│ └── schema/ # Data schemas
|
|
1728
1491
|
├── src/ # Application source code
|
|
1729
|
-
│ ├── App.${framework === 'react' ? 'tsx' : 'js'}
|
|
1730
|
-
│ ├── feltdb.ts # FeltDB
|
|
1731
|
-
│ └── index.${framework === 'react' ? 'tsx' : 'js'}
|
|
1492
|
+
│ ├── App.${framework === 'react' ? 'tsx' : 'js'} # Main workspace UI
|
|
1493
|
+
│ ├── feltdb.ts # FeltDB collections and operations
|
|
1494
|
+
│ └── index.${framework === 'react' ? 'tsx' : 'js'} # Entry point
|
|
1732
1495
|
├── public/ # Static assets
|
|
1733
1496
|
├── index.html
|
|
1734
1497
|
├── .feltdb/ # Local FeltDB configuration
|
|
@@ -1739,37 +1502,63 @@ ${applicationName}/
|
|
|
1739
1502
|
└── README.md
|
|
1740
1503
|
\`\`\`
|
|
1741
1504
|
|
|
1742
|
-
## Runtime
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
-
|
|
1753
|
-
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
-
|
|
1762
|
-
-
|
|
1763
|
-
-
|
|
1505
|
+
## FeltDB Runtime
|
|
1506
|
+
|
|
1507
|
+
This project was created with the **${runtime === 'browser' ? 'Browser' : runtime === 'node' ? 'Node.js Server' : runtime === 'managed' ? 'Managed' : 'Self-hosted'}** runtime.
|
|
1508
|
+
|
|
1509
|
+
### Browser Runtime
|
|
1510
|
+
|
|
1511
|
+
FeltDB runs locally in the browser and persists through IndexedDB.
|
|
1512
|
+
|
|
1513
|
+
- ✅ Zero server/database infrastructure
|
|
1514
|
+
- ✅ Works completely offline
|
|
1515
|
+
- ✅ Data stays on your device (privacy-first)
|
|
1516
|
+
- ✅ Best for: Client-side apps, offline-first experiences, prototypes
|
|
1517
|
+
|
|
1518
|
+
**Data Persistence:** IndexedDB (OPFS fallback)
|
|
1519
|
+
|
|
1520
|
+
### Node.js Runtime
|
|
1521
|
+
|
|
1522
|
+
FeltDB runs as a Node.js server with server-side authority.
|
|
1523
|
+
|
|
1524
|
+
- ✅ Server-side FeltDB authority
|
|
1525
|
+
- ✅ Persistent file-based storage
|
|
1526
|
+
- ✅ Multi-client capable
|
|
1527
|
+
- ✅ Best for: Server-side applications, APIs, backend services
|
|
1528
|
+
|
|
1529
|
+
**Data Persistence:** File-based storage
|
|
1530
|
+
|
|
1531
|
+
### Self-hosted Runtime
|
|
1532
|
+
|
|
1533
|
+
FeltDB runs through a dedicated server with Docker Compose and persistent data volume.
|
|
1534
|
+
|
|
1535
|
+
- ✅ Production-ready deployment
|
|
1536
|
+
- ✅ Multi-node replication
|
|
1537
|
+
- ✅ Health checks and orchestration
|
|
1538
|
+
- ✅ Best for: Production deployments, multi-user systems
|
|
1539
|
+
|
|
1540
|
+
**Data Persistence:** Docker volume (persistent /data)
|
|
1541
|
+
|
|
1542
|
+
### Managed Runtime
|
|
1543
|
+
|
|
1544
|
+
FeltDB provides the runtime endpoint, durable storage, synchronization, and background workload infrastructure. The CLI configures \`VITE_FELTDB_MANAGED_URL\` and \`VITE_FELTDB_MANAGED_API_KEY\` in \`.env.local\`.
|
|
1764
1545
|
|
|
1765
1546
|
## Configuration
|
|
1766
1547
|
|
|
1767
1548
|
Configuration is in \`feltdb.config.json\`:
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1549
|
+
|
|
1550
|
+
\`\`\`json
|
|
1551
|
+
{
|
|
1552
|
+
"namespace": "${applicationName}",
|
|
1553
|
+
"runtime": "${runtime}",
|
|
1554
|
+
"storage": "${runtime === 'browser' ? 'indexeddb' : runtime === 'managed' ? 'managed' : 'durable'}",
|
|
1555
|
+
"distributed": ${distributed},
|
|
1556
|
+
"agents": {
|
|
1557
|
+
"enabled": ${hasAgents}
|
|
1558
|
+
},
|
|
1559
|
+
"capabilities": ["${capabilities}"]
|
|
1560
|
+
}
|
|
1561
|
+
\`\`\`
|
|
1773
1562
|
|
|
1774
1563
|
### Environment Variables
|
|
1775
1564
|
|
|
@@ -1778,9 +1567,12 @@ Create a \`.env.local\` file (copy from \`.env.example\`):
|
|
|
1778
1567
|
\`\`\`
|
|
1779
1568
|
VITE_FELTDB_API_KEY=your_api_key_here
|
|
1780
1569
|
VITE_FELTDB_URL=http://localhost:7700
|
|
1570
|
+
VITE_FELTDB_MANAGED_API_KEY=your_managed_api_key_here
|
|
1571
|
+
VITE_FELTDB_MANAGED_URL=https://api.feltdb.com
|
|
1572
|
+
VITE_FELTDB_WEBSOCKET_URL=ws://localhost:7700
|
|
1781
1573
|
\`\`\`
|
|
1782
1574
|
|
|
1783
|
-
These are
|
|
1575
|
+
These are required when connecting to an authenticated self-hosted or managed FeltDB instance.
|
|
1784
1576
|
|
|
1785
1577
|
## Development
|
|
1786
1578
|
|
|
@@ -1804,59 +1596,95 @@ npm run feltdb:validate
|
|
|
1804
1596
|
npm run feltdb:status
|
|
1805
1597
|
\`\`\`
|
|
1806
1598
|
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1599
|
+
### Open FeltDB Studio
|
|
1600
|
+
\`\`\`bash
|
|
1601
|
+
npm run feltdb:studio
|
|
1602
|
+
\`\`\`
|
|
1810
1603
|
|
|
1811
|
-
|
|
1812
|
-
- \`documents\`: Stores research documents
|
|
1813
|
-
- \`reports\`: Stores generated reports
|
|
1604
|
+
## Database Schema
|
|
1814
1605
|
|
|
1815
|
-
###
|
|
1606
|
+
### Collections
|
|
1816
1607
|
|
|
1817
|
-
|
|
1608
|
+
#### projects
|
|
1609
|
+
- **id** (string): Unique project identifier
|
|
1610
|
+
- **name** (string): Project name
|
|
1611
|
+
- **description** (string): Project description
|
|
1612
|
+
- **status** (string): 'active' | 'archived' | 'completed'
|
|
1613
|
+
- **createdAt** (string): ISO timestamp
|
|
1614
|
+
- **updatedAt** (string): ISO timestamp
|
|
1615
|
+
- **metadata** (object, optional): Custom metadata
|
|
1616
|
+
|
|
1617
|
+
**Indexes:** status
|
|
1618
|
+
|
|
1619
|
+
#### tasks
|
|
1620
|
+
- **id** (string): Unique task identifier
|
|
1621
|
+
- **projectId** (string): Foreign key to projects
|
|
1622
|
+
- **title** (string): Task title
|
|
1623
|
+
- **description** (string): Task description
|
|
1624
|
+
- **status** (string): 'todo' | 'in-progress' | 'completed'
|
|
1625
|
+
- **priority** (string): 'low' | 'medium' | 'high'
|
|
1626
|
+
- **assignee** (string, optional): Assigned team member
|
|
1627
|
+
- **createdAt** (string): ISO timestamp
|
|
1628
|
+
- **updatedAt** (string): ISO timestamp
|
|
1629
|
+
|
|
1630
|
+
**Indexes:** projectId, status, priority
|
|
1631
|
+
|
|
1632
|
+
#### activity
|
|
1633
|
+
- **id** (string): Unique event identifier
|
|
1634
|
+
- **timestamp** (string): ISO timestamp
|
|
1635
|
+
- **type** (string): Event type
|
|
1636
|
+
- **entityType** (string): 'project' | 'task'
|
|
1637
|
+
- **entityId** (string): Reference to entity
|
|
1638
|
+
- **entityName** (string): Name of entity
|
|
1639
|
+
- **changes** (object, optional): Changed fields
|
|
1640
|
+
- **userId** (string, optional): User who made change
|
|
1641
|
+
|
|
1642
|
+
**Indexes:** timestamp, (entityType, entityId)
|
|
1818
1643
|
|
|
1819
1644
|
## Agents
|
|
1820
1645
|
|
|
1821
|
-
${hasAgents ? `###
|
|
1822
|
-
The
|
|
1823
|
-
- Runs entirely in the browser (no
|
|
1646
|
+
${hasAgents ? `### Available Agents
|
|
1647
|
+
The application includes autonomous agents powered by \`@feltdb/webllm\`:
|
|
1648
|
+
- Runs entirely in the browser (private, no external APIs)
|
|
1824
1649
|
- Model downloads on first use
|
|
1825
1650
|
- Inference runs in a Web Worker
|
|
1826
|
-
-
|
|
1827
|
-
- Learn more: https://github.com/mlc-ai/web-llm` : 'No agents configured. Add agents by re-running create-feltdb.'}
|
|
1828
|
-
|
|
1829
|
-
## Vector Search
|
|
1830
|
-
|
|
1831
|
-
${capabilities.includes('vector') ? `Vector search is enabled. Configure your vector storage backend in \`feltdb.config.json\`.` : `Vector search is not enabled. To add it, update \`feltdb.config.json\` to include \`"vector-search": true\` in capabilities.`}
|
|
1651
|
+
- Learn more: https://github.com/mlc-ai/web-llm` : 'No agents configured. To enable agents, re-run create-feltdb or add them to feltdb.config.json.'}
|
|
1832
1652
|
|
|
1833
1653
|
## Troubleshooting
|
|
1834
1654
|
|
|
1835
|
-
###
|
|
1836
|
-
If the WebLLM Researcher shows "Model not loaded":
|
|
1655
|
+
### Data not persisting
|
|
1837
1656
|
1. Check browser console for errors
|
|
1838
|
-
2.
|
|
1839
|
-
3.
|
|
1657
|
+
2. Verify IndexedDB is enabled (not in private mode)
|
|
1658
|
+
3. Check browser storage quota
|
|
1840
1659
|
4. Clear browser cache and try again
|
|
1841
1660
|
|
|
1661
|
+
### Slow performance
|
|
1662
|
+
1. Check browser DevTools Performance tab
|
|
1663
|
+
2. Verify indexes are being used (check Data Inspector)
|
|
1664
|
+
3. Consider optimizing queries or collection size
|
|
1665
|
+
|
|
1842
1666
|
### Self-Hosted Connection Issues
|
|
1843
1667
|
If using self-hosted mode and connection fails:
|
|
1844
1668
|
1. Ensure Docker is installed and running
|
|
1845
|
-
2. Check
|
|
1669
|
+
2. Check VITE_FELTDB_URL and API key in .env.local
|
|
1846
1670
|
3. Run \`npm run feltdb:status\` to check server health
|
|
1847
|
-
4. View logs: \`docker logs feltdb\`
|
|
1671
|
+
4. View Docker logs: \`docker logs feltdb\`
|
|
1848
1672
|
|
|
1849
|
-
###
|
|
1850
|
-
If
|
|
1851
|
-
1.
|
|
1852
|
-
2.
|
|
1853
|
-
3. Check CORS settings on self-hosted server
|
|
1673
|
+
### Port conflicts
|
|
1674
|
+
If port 5173 is in use:
|
|
1675
|
+
1. Change the port in \`vite.config.ts\`
|
|
1676
|
+
2. Or: \`npm run dev -- --port 3000\`
|
|
1854
1677
|
|
|
1855
1678
|
## Learn More
|
|
1856
1679
|
|
|
1857
1680
|
- [FeltDB Documentation](https://github.com/rkendel1/feltdb)
|
|
1858
|
-
- [
|
|
1859
|
-
- [
|
|
1681
|
+
- [FeltDB Architecture](https://github.com/rkendel1/feltdb/blob/main/ARCHITECTURE.md)
|
|
1682
|
+
- [WebLLM (In-browser LLM)](https://github.com/mlc-ai/web-llm)
|
|
1683
|
+
- [Getting Started Guide](https://github.com/rkendel1/feltdb/blob/main/GETTING_STARTED.md)
|
|
1684
|
+
|
|
1685
|
+
## License
|
|
1686
|
+
|
|
1687
|
+
MIT
|
|
1860
1688
|
`;
|
|
1861
1689
|
fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
|
|
1862
1690
|
}
|