create-feltdb 0.4.4 → 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/README.md +2 -1
- package/dist/application-identity.js +2 -2
- package/dist/cli.js +133 -9
- package/dist/create.js +1309 -260
- package/dist/index.js +1 -0
- package/dist/managed-account.js +61 -0
- package/dist/package-versions.js +1 -1
- package/package.json +2 -1
package/dist/create.js
CHANGED
|
@@ -53,15 +53,12 @@ export async function createProject(options) {
|
|
|
53
53
|
'@feltdb/core': feltdbPackageRange,
|
|
54
54
|
},
|
|
55
55
|
devDependencies: {
|
|
56
|
-
'@feltdb/cli': feltdbPackageRange,
|
|
57
|
-
'@feltdb/studio': feltdbPackageRange,
|
|
58
56
|
typescript: '^5.0.0',
|
|
59
57
|
'@types/node': '^20.0.0',
|
|
60
58
|
vite: '^8.2.1',
|
|
61
59
|
},
|
|
62
60
|
};
|
|
63
61
|
if (framework === 'react') {
|
|
64
|
-
packageJson.dependencies['@feltdb/react'] = feltdbPackageRange;
|
|
65
62
|
packageJson.dependencies['react'] = '^18.0.0';
|
|
66
63
|
packageJson.dependencies['react-dom'] = '^18.0.0';
|
|
67
64
|
packageJson.devDependencies['@types/react'] = '^18.0.0';
|
|
@@ -75,7 +72,7 @@ export async function createProject(options) {
|
|
|
75
72
|
const feltdbConfig = {
|
|
76
73
|
namespace: applicationName,
|
|
77
74
|
runtime,
|
|
78
|
-
storage: runtime === 'browser' ? '
|
|
75
|
+
storage: runtime === 'browser' ? 'indexeddb' : runtime === 'managed' ? 'managed' : 'durable',
|
|
79
76
|
distributed,
|
|
80
77
|
agents: {
|
|
81
78
|
enabled: hasAgents,
|
|
@@ -159,16 +156,165 @@ export async function createProject(options) {
|
|
|
159
156
|
// Create main application files
|
|
160
157
|
const runtimeOptions = runtime === 'browser'
|
|
161
158
|
? "{ namespace: '" + applicationName + "', browser: true }"
|
|
162
|
-
: runtime === '
|
|
163
|
-
? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || '
|
|
164
|
-
:
|
|
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 }";
|
|
165
164
|
const feltdbTs = `import { createFeltDB } from '@feltdb/core';
|
|
166
165
|
|
|
167
166
|
export const db = createFeltDB(${runtimeOptions});
|
|
168
167
|
|
|
169
168
|
// Collections
|
|
170
|
-
export const
|
|
171
|
-
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
|
+
}
|
|
172
318
|
`;
|
|
173
319
|
fs.writeFileSync(path.join(srcDir, 'feltdb.ts'), feltdbTs);
|
|
174
320
|
// Create a real local-inference agent for browser projects.
|
|
@@ -183,12 +329,16 @@ import { db, reports } from '../../src/feltdb';
|
|
|
183
329
|
export const researcher = db.defineAgent({
|
|
184
330
|
name: 'researcher',
|
|
185
331
|
version: 1,
|
|
186
|
-
description: '
|
|
332
|
+
description: 'Private, on-device AI assistant for document enhancement and analysis',
|
|
187
333
|
capabilities: [
|
|
188
334
|
'document-read',
|
|
189
335
|
'report-write'
|
|
190
336
|
],
|
|
191
|
-
goals: [
|
|
337
|
+
goals: [
|
|
338
|
+
'Analyze documents privately without sending data off-device',
|
|
339
|
+
'Generate insights and enhancements locally',
|
|
340
|
+
'Support document summarization, expansion, and improvement'
|
|
341
|
+
],
|
|
192
342
|
constraints: { maxIterations: 1 },
|
|
193
343
|
});
|
|
194
344
|
|
|
@@ -198,20 +348,138 @@ export async function runResearcher(
|
|
|
198
348
|
prompt: string,
|
|
199
349
|
onProgress?: (message: string) => void,
|
|
200
350
|
) {
|
|
201
|
-
provider
|
|
202
|
-
|
|
203
|
-
onProgress
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
351
|
+
if (!provider) {
|
|
352
|
+
provider = new WebLLMProvider({
|
|
353
|
+
onProgress: ({ progress, text }) => {
|
|
354
|
+
const pct = Math.round(progress * 100);
|
|
355
|
+
onProgress?.(\`\${pct}% - \${text}\`);
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const systemPrompt = \`You are a helpful AI assistant that operates entirely in the browser.
|
|
362
|
+
You help users:
|
|
363
|
+
- Summarize documents concisely
|
|
364
|
+
- Expand and enhance text with more details
|
|
365
|
+
- Generate ideas and suggestions
|
|
366
|
+
- Improve writing clarity and structure
|
|
367
|
+
- Answer questions about document content
|
|
368
|
+
|
|
369
|
+
Always be concise and practical. Respect the user's intent.\`;
|
|
370
|
+
|
|
371
|
+
const content = await provider.generate([
|
|
372
|
+
{ role: 'system', content: systemPrompt },
|
|
373
|
+
{ role: 'user', content: prompt },
|
|
374
|
+
]);
|
|
375
|
+
|
|
376
|
+
await reports.insert({
|
|
377
|
+
id: 'report_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
|
378
|
+
title: prompt.slice(0, 80) || 'AI Research Result',
|
|
379
|
+
content,
|
|
380
|
+
createdAt: new Date().toISOString(),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return content;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
throw new Error(error instanceof Error ? error.message : 'Failed to generate response');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export async function summarizeDocument(
|
|
390
|
+
title: string,
|
|
391
|
+
content: string,
|
|
392
|
+
onProgress?: (message: string) => void,
|
|
393
|
+
): Promise<string> {
|
|
394
|
+
const prompt = \`Summarize this document in 2-3 concise sentences:
|
|
395
|
+
|
|
396
|
+
Title: \${title}
|
|
397
|
+
|
|
398
|
+
Content: \${content.substring(0, 2000)}\`;
|
|
399
|
+
|
|
400
|
+
return runResearcher(prompt, onProgress);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export async function enhanceDocument(
|
|
404
|
+
title: string,
|
|
405
|
+
content: string,
|
|
406
|
+
onProgress?: (message: string) => void,
|
|
407
|
+
): Promise<string> {
|
|
408
|
+
const prompt = \`Enhance and improve this document by making it more detailed and clearer:
|
|
409
|
+
|
|
410
|
+
Title: \${title}
|
|
411
|
+
|
|
412
|
+
Content: \${content}
|
|
413
|
+
|
|
414
|
+
Provide the enhanced version:\`;
|
|
415
|
+
|
|
416
|
+
return runResearcher(prompt, onProgress);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export async function generateIdeas(
|
|
420
|
+
topic: string,
|
|
421
|
+
onProgress?: (message: string) => void,
|
|
422
|
+
): Promise<string> {
|
|
423
|
+
const prompt = \`Generate 5 creative ideas related to: \${topic}
|
|
424
|
+
|
|
425
|
+
Format as a numbered list with brief explanations.\`;
|
|
426
|
+
|
|
427
|
+
return runResearcher(prompt, onProgress);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export async function generateFeltDBFeature(
|
|
431
|
+
featureDescription: string,
|
|
432
|
+
onProgress?: (message: string) => void,
|
|
433
|
+
): Promise<string> {
|
|
434
|
+
const prompt = \`Generate FeltDB flow syntax for: \${featureDescription}
|
|
435
|
+
|
|
436
|
+
Include:
|
|
437
|
+
1. Collection definitions with proper types
|
|
438
|
+
2. Capability definitions
|
|
439
|
+
3. Workflow definitions if needed
|
|
440
|
+
4. Policy rules for access control
|
|
441
|
+
|
|
442
|
+
Format the output as valid FeltDB flow language.\`;
|
|
443
|
+
|
|
444
|
+
return runResearcher(prompt, onProgress);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export async function generateReactComponent(
|
|
448
|
+
componentDescription: string,
|
|
449
|
+
onProgress?: (message: string) => void,
|
|
450
|
+
): Promise<string> {
|
|
451
|
+
const prompt = \`Generate a React component for: \${componentDescription}
|
|
452
|
+
|
|
453
|
+
Include:
|
|
454
|
+
1. TypeScript types/interfaces
|
|
455
|
+
2. useState and useEffect hooks
|
|
456
|
+
3. Error handling
|
|
457
|
+
4. Loading states
|
|
458
|
+
5. Proper styling with CSS classes
|
|
459
|
+
|
|
460
|
+
Export a functional component that can be imported and used.\`;
|
|
461
|
+
|
|
462
|
+
return runResearcher(prompt, onProgress);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export async function generateCapability(
|
|
466
|
+
capabilityName: string,
|
|
467
|
+
description: string,
|
|
468
|
+
onProgress?: (message: string) => void,
|
|
469
|
+
): Promise<string> {
|
|
470
|
+
const prompt = \`Generate a FeltDB capability definition for: \${capabilityName}
|
|
471
|
+
|
|
472
|
+
Description: \${description}
|
|
473
|
+
|
|
474
|
+
Include:
|
|
475
|
+
1. TypeScript interface definitions
|
|
476
|
+
2. Implementation with proper types
|
|
477
|
+
3. Error handling
|
|
478
|
+
4. Documentation comments
|
|
479
|
+
|
|
480
|
+
Make it production-ready.\`;
|
|
481
|
+
|
|
482
|
+
return runResearcher(prompt, onProgress);
|
|
215
483
|
}
|
|
216
484
|
`;
|
|
217
485
|
fs.writeFileSync(path.join(feltdbDir, 'agents', 'researcher.ts'), agentTs);
|
|
@@ -243,24 +511,44 @@ export const capabilities = {
|
|
|
243
511
|
// Create main application file based on framework
|
|
244
512
|
if (framework === 'react') {
|
|
245
513
|
const agentImport = hasAgents
|
|
246
|
-
? "import { runResearcher } from '../feltdb/agents/researcher';"
|
|
514
|
+
? "import { runResearcher, summarizeDocument, enhanceDocument, generateIdeas, generateFeltDBFeature, generateReactComponent, generateCapability } from '../feltdb/agents/researcher';"
|
|
247
515
|
: '';
|
|
248
516
|
const agentState = hasAgents
|
|
249
|
-
? ` const [prompt, setPrompt] = useState('
|
|
517
|
+
? ` const [prompt, setPrompt] = useState('');
|
|
250
518
|
const [answer, setAnswer] = useState('');
|
|
251
519
|
const [modelStatus, setModelStatus] = useState('Model not loaded');
|
|
252
|
-
const [generating, setGenerating] = useState(false)
|
|
520
|
+
const [generating, setGenerating] = useState(false);
|
|
521
|
+
const [selectedDoc, setSelectedDoc] = useState<any | null>(null);
|
|
522
|
+
const [aiMode, setAiMode] = useState<'custom' | 'summarize' | 'enhance' | 'ideas' | 'feltdb' | 'react' | 'capability'>('custom');`
|
|
253
523
|
: '';
|
|
254
524
|
const agentHandler = hasAgents
|
|
255
525
|
? `
|
|
256
|
-
const
|
|
526
|
+
const handleRunAI = async (mode: 'custom' | 'summarize' | 'enhance' | 'ideas' | 'feltdb' | 'react' | 'capability') => {
|
|
257
527
|
setGenerating(true);
|
|
258
528
|
setAnswer('');
|
|
529
|
+
setModelStatus('Running inference...');
|
|
530
|
+
|
|
259
531
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
532
|
+
let result = '';
|
|
533
|
+
if (mode === 'custom') {
|
|
534
|
+
result = await runResearcher(prompt, setModelStatus);
|
|
535
|
+
} else if (mode === 'summarize' && selectedDoc) {
|
|
536
|
+
result = await summarizeDocument(selectedDoc.title, selectedDoc.content, setModelStatus);
|
|
537
|
+
} else if (mode === 'enhance' && selectedDoc) {
|
|
538
|
+
result = await enhanceDocument(selectedDoc.title, selectedDoc.content, setModelStatus);
|
|
539
|
+
} else if (mode === 'ideas') {
|
|
540
|
+
result = await generateIdeas(prompt, setModelStatus);
|
|
541
|
+
} else if (mode === 'feltdb') {
|
|
542
|
+
result = await generateFeltDBFeature(prompt, setModelStatus);
|
|
543
|
+
} else if (mode === 'react') {
|
|
544
|
+
result = await generateReactComponent(prompt, setModelStatus);
|
|
545
|
+
} else if (mode === 'capability') {
|
|
546
|
+
result = await generateCapability('Feature', prompt, setModelStatus);
|
|
547
|
+
}
|
|
548
|
+
setAnswer(result);
|
|
549
|
+
setModelStatus('✓ Ready — all processing done locally');
|
|
262
550
|
} catch (error) {
|
|
263
|
-
setModelStatus(error instanceof Error ? error.message : String(error));
|
|
551
|
+
setModelStatus('✗ Error: ' + (error instanceof Error ? error.message : String(error)));
|
|
264
552
|
} finally {
|
|
265
553
|
setGenerating(false);
|
|
266
554
|
}
|
|
@@ -270,228 +558,551 @@ export const capabilities = {
|
|
|
270
558
|
const agentMarkup = hasAgents
|
|
271
559
|
? `
|
|
272
560
|
<section className="researcher">
|
|
273
|
-
<h2
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
<
|
|
277
|
-
|
|
561
|
+
<h2>🤖 AI Assistant (Private, Local)</h2>
|
|
562
|
+
<div className="status-badge">{modelStatus}</div>
|
|
563
|
+
|
|
564
|
+
<div className="ai-modes">
|
|
565
|
+
<div className="mode-group">
|
|
566
|
+
<span className="mode-group-label">📝 Content</span>
|
|
567
|
+
<button
|
|
568
|
+
className={\`mode-btn \${aiMode === 'custom' ? 'active' : ''}\`}
|
|
569
|
+
onClick={() => setAiMode('custom')}
|
|
570
|
+
>
|
|
571
|
+
✍️ Custom
|
|
572
|
+
</button>
|
|
573
|
+
<button
|
|
574
|
+
className={\`mode-btn \${aiMode === 'summarize' ? 'active' : ''}\`}
|
|
575
|
+
onClick={() => setAiMode('summarize')}
|
|
576
|
+
disabled={!selectedDoc}
|
|
577
|
+
>
|
|
578
|
+
📄 Summarize
|
|
579
|
+
</button>
|
|
580
|
+
<button
|
|
581
|
+
className={\`mode-btn \${aiMode === 'enhance' ? 'active' : ''}\`}
|
|
582
|
+
onClick={() => setAiMode('enhance')}
|
|
583
|
+
disabled={!selectedDoc}
|
|
584
|
+
>
|
|
585
|
+
✨ Enhance
|
|
586
|
+
</button>
|
|
587
|
+
<button
|
|
588
|
+
className={\`mode-btn \${aiMode === 'ideas' ? 'active' : ''}\`}
|
|
589
|
+
onClick={() => setAiMode('ideas')}
|
|
590
|
+
>
|
|
591
|
+
💡 Ideas
|
|
592
|
+
</button>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<div className="mode-group">
|
|
596
|
+
<span className="mode-group-label">💻 Code Gen</span>
|
|
597
|
+
<button
|
|
598
|
+
className={\`mode-btn code-mode \${aiMode === 'feltdb' ? 'active' : ''}\`}
|
|
599
|
+
onClick={() => setAiMode('feltdb')}
|
|
600
|
+
>
|
|
601
|
+
⚙️ FeltDB Flow
|
|
602
|
+
</button>
|
|
603
|
+
<button
|
|
604
|
+
className={\`mode-btn code-mode \${aiMode === 'react' ? 'active' : ''}\`}
|
|
605
|
+
onClick={() => setAiMode('react')}
|
|
606
|
+
>
|
|
607
|
+
⚛️ React
|
|
608
|
+
</button>
|
|
609
|
+
<button
|
|
610
|
+
className={\`mode-btn code-mode \${aiMode === 'capability' ? 'active' : ''}\`}
|
|
611
|
+
onClick={() => setAiMode('capability')}
|
|
612
|
+
>
|
|
613
|
+
🔧 Capability
|
|
614
|
+
</button>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
{aiMode === 'summarize' || aiMode === 'enhance' ? (
|
|
619
|
+
<div className="doc-selector">
|
|
620
|
+
<label>Select a document:</label>
|
|
621
|
+
<select
|
|
622
|
+
value={selectedDoc?.id || ''}
|
|
623
|
+
onChange={(e) => {
|
|
624
|
+
const doc = docs.find((d: any) => d.id === e.target.value);
|
|
625
|
+
setSelectedDoc(doc || null);
|
|
626
|
+
}}
|
|
627
|
+
>
|
|
628
|
+
<option value="">Choose a document...</option>
|
|
629
|
+
{docs.map((doc: any) => (
|
|
630
|
+
<option key={doc.id} value={doc.id}>
|
|
631
|
+
{doc.title}
|
|
632
|
+
</option>
|
|
633
|
+
))}
|
|
634
|
+
</select>
|
|
635
|
+
</div>
|
|
636
|
+
) : (
|
|
637
|
+
<textarea
|
|
638
|
+
value={prompt}
|
|
639
|
+
onChange={(event) => setPrompt(event.target.value)}
|
|
640
|
+
placeholder={
|
|
641
|
+
aiMode === 'ideas' ? 'What topic do you want ideas for?' :
|
|
642
|
+
aiMode === 'feltdb' ? 'Describe the FeltDB feature you want to create...' :
|
|
643
|
+
aiMode === 'react' ? 'Describe the React component you need...' :
|
|
644
|
+
aiMode === 'capability' ? 'Describe the capability implementation...' :
|
|
645
|
+
'Enter your prompt...'
|
|
646
|
+
}
|
|
647
|
+
rows={5}
|
|
648
|
+
/>
|
|
649
|
+
)}
|
|
650
|
+
|
|
651
|
+
<button
|
|
652
|
+
onClick={() => handleRunAI(aiMode)}
|
|
653
|
+
disabled={
|
|
654
|
+
generating ||
|
|
655
|
+
(aiMode === 'custom' && !prompt.trim()) ||
|
|
656
|
+
((aiMode === 'summarize' || aiMode === 'enhance') && !selectedDoc) ||
|
|
657
|
+
((aiMode === 'ideas' || aiMode === 'feltdb' || aiMode === 'react' || aiMode === 'capability') && !prompt.trim())
|
|
658
|
+
}
|
|
659
|
+
className="research-btn"
|
|
660
|
+
>
|
|
661
|
+
{generating ? '⏳ Running locally…' : '🚀 Generate'}
|
|
278
662
|
</button>
|
|
279
|
-
|
|
663
|
+
|
|
664
|
+
{answer && (
|
|
665
|
+
<article className="research-result">
|
|
666
|
+
<h3>Generated Output</h3>
|
|
667
|
+
<div className="result-content">{answer}</div>
|
|
668
|
+
<div className="result-actions">
|
|
669
|
+
{aiMode === 'custom' || aiMode === 'enhance' || aiMode === 'ideas' ? (
|
|
670
|
+
<button
|
|
671
|
+
onClick={() => {
|
|
672
|
+
const title = aiMode === 'ideas' ? prompt : (selectedDoc?.title || 'AI Generated');
|
|
673
|
+
documents.insert({
|
|
674
|
+
id: 'doc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
|
675
|
+
title: title + ' (AI Enhanced)',
|
|
676
|
+
content: answer,
|
|
677
|
+
createdAt: new Date().toISOString(),
|
|
678
|
+
});
|
|
679
|
+
loadDocs();
|
|
680
|
+
setAnswer('');
|
|
681
|
+
}}
|
|
682
|
+
className="save-result-btn"
|
|
683
|
+
>
|
|
684
|
+
💾 Save to Documents
|
|
685
|
+
</button>
|
|
686
|
+
) : (
|
|
687
|
+
<>
|
|
688
|
+
<button
|
|
689
|
+
onClick={() => {
|
|
690
|
+
const filename = aiMode === 'feltdb' ? 'feature.flow' :
|
|
691
|
+
aiMode === 'react' ? 'Component.tsx' :
|
|
692
|
+
'capability.ts';
|
|
693
|
+
const blob = new Blob([answer], { type: 'text/plain' });
|
|
694
|
+
const url = URL.createObjectURL(blob);
|
|
695
|
+
const a = document.createElement('a');
|
|
696
|
+
a.href = url;
|
|
697
|
+
a.download = filename;
|
|
698
|
+
a.click();
|
|
699
|
+
URL.revokeObjectURL(url);
|
|
700
|
+
}}
|
|
701
|
+
className="save-result-btn"
|
|
702
|
+
>
|
|
703
|
+
📥 Download Code
|
|
704
|
+
</button>
|
|
705
|
+
<button
|
|
706
|
+
onClick={() => {
|
|
707
|
+
navigator.clipboard.writeText(answer);
|
|
708
|
+
alert('Code copied to clipboard!');
|
|
709
|
+
}}
|
|
710
|
+
className="copy-result-btn"
|
|
711
|
+
>
|
|
712
|
+
📋 Copy Code
|
|
713
|
+
</button>
|
|
714
|
+
</>
|
|
715
|
+
)}
|
|
716
|
+
</div>
|
|
717
|
+
<p className="code-note">
|
|
718
|
+
💡 Tip: Generated code can be downloaded, copied, or saved to documents for later reference.
|
|
719
|
+
</p>
|
|
720
|
+
</article>
|
|
721
|
+
)}
|
|
280
722
|
</section>`
|
|
281
723
|
: '';
|
|
282
724
|
const appTsx = `import React, { useState, useEffect } from 'react';
|
|
283
|
-
import {
|
|
284
|
-
|
|
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';
|
|
285
737
|
|
|
286
738
|
export function App() {
|
|
287
|
-
const [
|
|
288
|
-
const [
|
|
289
|
-
|
|
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);
|
|
290
749
|
|
|
291
750
|
useEffect(() => {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const allDocs = await documents.find({});
|
|
295
|
-
setDocs(allDocs);
|
|
296
|
-
} catch (err) {
|
|
297
|
-
console.error('Error loading documents:', err);
|
|
298
|
-
} finally {
|
|
299
|
-
setLoading(false);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
751
|
+
getDashboardStats().then(setDashboardStats);
|
|
752
|
+
}, [projectsList, tasksList]);
|
|
302
753
|
|
|
303
|
-
|
|
304
|
-
|
|
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
|
+
};
|
|
305
765
|
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
+
};
|
|
778
|
+
|
|
779
|
+
const containerStyle: React.CSSProperties = {
|
|
780
|
+
display: 'flex',
|
|
781
|
+
minHeight: '100vh',
|
|
782
|
+
fontFamily: 'system-ui, sans-serif',
|
|
783
|
+
backgroundColor: '#f5f7fa',
|
|
784
|
+
};
|
|
785
|
+
|
|
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',
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
const mainStyle: React.CSSProperties = {
|
|
798
|
+
flex: 1,
|
|
799
|
+
display: 'flex',
|
|
800
|
+
flexDirection: 'column',
|
|
801
|
+
};
|
|
802
|
+
|
|
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
|
+
};
|
|
811
|
+
|
|
812
|
+
const contentStyle: React.CSSProperties = {
|
|
813
|
+
flex: 1,
|
|
814
|
+
padding: '20px',
|
|
815
|
+
overflowY: 'auto',
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const titleStyle: React.CSSProperties = {
|
|
819
|
+
fontSize: '24px',
|
|
820
|
+
fontWeight: 'bold',
|
|
821
|
+
color: '#1e293b',
|
|
822
|
+
};
|
|
823
|
+
|
|
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',
|
|
319
880
|
};
|
|
320
|
-
${agentHandler}
|
|
321
881
|
|
|
322
882
|
return (
|
|
323
|
-
<div
|
|
324
|
-
<
|
|
325
|
-
<
|
|
326
|
-
<
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
</
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
|
|
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'}
|
|
342
931
|
</div>
|
|
343
|
-
</
|
|
344
|
-
|
|
345
|
-
<
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
<
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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>
|
|
962
|
+
</div>
|
|
360
963
|
)}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
964
|
+
|
|
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}
|
|
984
|
+
</div>
|
|
985
|
+
)}
|
|
986
|
+
<div style={{ fontSize: '12px', color: '#0284c7', marginTop: '4px' }}>
|
|
987
|
+
Status: {p.status}
|
|
988
|
+
</div>
|
|
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}
|
|
1027
|
+
</div>
|
|
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()}
|
|
1051
|
+
</div>
|
|
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/>
|
|
1077
|
+
</div>
|
|
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>
|
|
1097
|
+
</div>
|
|
1098
|
+
)}
|
|
1099
|
+
</div>
|
|
1100
|
+
</div>
|
|
491
1101
|
</div>
|
|
492
1102
|
);
|
|
493
1103
|
}
|
|
494
1104
|
`;
|
|
1105
|
+
;
|
|
495
1106
|
fs.writeFileSync(path.join(srcDir, 'App.tsx'), appTsx);
|
|
496
1107
|
const indexTsx = `import React from 'react';
|
|
497
1108
|
import ReactDOM from 'react-dom/client';
|
|
@@ -537,13 +1148,282 @@ main().catch(console.error);
|
|
|
537
1148
|
fs.writeFileSync(path.join(projectDir, 'index.html'), indexHtml);
|
|
538
1149
|
// Create .env.example
|
|
539
1150
|
const envExample = `# FeltDB Configuration
|
|
1151
|
+
# Copy this file to .env.local and update the values
|
|
1152
|
+
|
|
1153
|
+
# API Key for authenticating with FeltDB servers
|
|
1154
|
+
# Leave empty for browser runtime; required for authenticated self-hosted and managed runtimes
|
|
540
1155
|
VITE_FELTDB_API_KEY=
|
|
1156
|
+
|
|
1157
|
+
# FeltDB Server URL (self-hosted or managed runtime)
|
|
541
1158
|
VITE_FELTDB_URL=http://localhost:7700
|
|
542
|
-
|
|
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
|
+
|
|
1168
|
+
# Override the default self-hosted container image
|
|
1169
|
+
# Example: ghcr.io/rkendel1/feltdb:latest
|
|
543
1170
|
FELTDB_IMAGE=
|
|
1171
|
+
|
|
1172
|
+
# Node environment
|
|
544
1173
|
NODE_ENV=development
|
|
545
1174
|
`;
|
|
546
1175
|
fs.writeFileSync(path.join(projectDir, '.env.example'), envExample);
|
|
1176
|
+
// Create RUNTIME_GUIDE.md
|
|
1177
|
+
const runtimeGuide = `# Runtime Configuration Guide
|
|
1178
|
+
|
|
1179
|
+
This guide explains the differences between FeltDB runtime options and how to choose the right one for your use case.
|
|
1180
|
+
|
|
1181
|
+
## Browser Runtime
|
|
1182
|
+
|
|
1183
|
+
\`\`\`json
|
|
1184
|
+
{
|
|
1185
|
+
"runtime": "browser",
|
|
1186
|
+
"storage": "opfs"
|
|
1187
|
+
}
|
|
1188
|
+
\`\`\`
|
|
1189
|
+
|
|
1190
|
+
### What it does
|
|
1191
|
+
- Runs FeltDB entirely in the browser using JavaScript
|
|
1192
|
+
- Stores data in the browser's Origin Private File System (OPFS)
|
|
1193
|
+
- No backend server required
|
|
1194
|
+
|
|
1195
|
+
### When to use
|
|
1196
|
+
- Building offline-first applications
|
|
1197
|
+
- Client-side only projects
|
|
1198
|
+
- Prototyping and development
|
|
1199
|
+
- Privacy-focused applications where data never leaves the user's device
|
|
1200
|
+
|
|
1201
|
+
### Limitations
|
|
1202
|
+
- Data is isolated per browser/device
|
|
1203
|
+
- Single-user only
|
|
1204
|
+
- Limited by browser disk space (usually 50GB+)
|
|
1205
|
+
- Cannot be accessed from other devices/browsers
|
|
1206
|
+
|
|
1207
|
+
### Setup
|
|
1208
|
+
No special setup required. Just run \`npm run dev\`.
|
|
1209
|
+
|
|
1210
|
+
## Node.js Runtime
|
|
1211
|
+
|
|
1212
|
+
\`\`\`json
|
|
1213
|
+
{
|
|
1214
|
+
"runtime": "node",
|
|
1215
|
+
"storage": "durable"
|
|
1216
|
+
}
|
|
1217
|
+
\`\`\`
|
|
1218
|
+
|
|
1219
|
+
### What it does
|
|
1220
|
+
- Runs FeltDB as a Node.js server
|
|
1221
|
+
- Uses file-based or database storage
|
|
1222
|
+
- Accessible via HTTP/API
|
|
1223
|
+
|
|
1224
|
+
### When to use
|
|
1225
|
+
- Building backend APIs
|
|
1226
|
+
- Server-side applications
|
|
1227
|
+
- REST API backends
|
|
1228
|
+
- Integration with other services
|
|
1229
|
+
|
|
1230
|
+
### Limitations
|
|
1231
|
+
- Requires Node.js environment
|
|
1232
|
+
- Single server by default (no automatic distribution)
|
|
1233
|
+
- Data persistence depends on storage backend
|
|
1234
|
+
|
|
1235
|
+
### Setup
|
|
1236
|
+
\`\`\`bash
|
|
1237
|
+
npm run dev
|
|
1238
|
+
# or
|
|
1239
|
+
npm run feltdb:server
|
|
1240
|
+
\`\`\`
|
|
1241
|
+
|
|
1242
|
+
## Self-Hosted Runtime
|
|
1243
|
+
|
|
1244
|
+
\`\`\`json
|
|
1245
|
+
{
|
|
1246
|
+
"runtime": "self-hosted",
|
|
1247
|
+
"storage": "durable"
|
|
1248
|
+
}
|
|
1249
|
+
\`\`\`
|
|
1250
|
+
|
|
1251
|
+
### What it does
|
|
1252
|
+
- Runs a dedicated FeltDB server in Docker
|
|
1253
|
+
- Provides distributed capabilities
|
|
1254
|
+
- Enables multi-user, multi-device access
|
|
1255
|
+
- Includes API management and authentication
|
|
1256
|
+
|
|
1257
|
+
### When to use
|
|
1258
|
+
- Production deployments
|
|
1259
|
+
- Multi-user applications
|
|
1260
|
+
- Synchronization across devices
|
|
1261
|
+
- Team collaboration features
|
|
1262
|
+
- Advanced distributed scenarios
|
|
1263
|
+
|
|
1264
|
+
### Requirements
|
|
1265
|
+
- Docker installed and running
|
|
1266
|
+
- Internet access (first run downloads image)
|
|
1267
|
+
|
|
1268
|
+
### Setup
|
|
1269
|
+
\`\`\`bash
|
|
1270
|
+
npm run dev
|
|
1271
|
+
# Docker will be started automatically
|
|
1272
|
+
\`\`\`
|
|
1273
|
+
|
|
1274
|
+
The self-hosted instance runs on \`http://localhost:7700\` by default.
|
|
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
|
+
|
|
1287
|
+
## Vector Search Status
|
|
1288
|
+
|
|
1289
|
+
### Current Status
|
|
1290
|
+
Vector search support in FeltDB is available through integrations with vector databases:
|
|
1291
|
+
- Milvus
|
|
1292
|
+
- Pinecone
|
|
1293
|
+
- Weaviate
|
|
1294
|
+
- Local vector storage (development)
|
|
1295
|
+
|
|
1296
|
+
### Enabling Vector Search
|
|
1297
|
+
To enable vector search capabilities:
|
|
1298
|
+
|
|
1299
|
+
1. Update \`feltdb.config.json\`:
|
|
1300
|
+
\`\`\`json
|
|
1301
|
+
{
|
|
1302
|
+
"capabilities": {
|
|
1303
|
+
"search": true,
|
|
1304
|
+
"vector-search": true
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
\`\`\`
|
|
1308
|
+
|
|
1309
|
+
2. Configure your vector database in your environment variables or code
|
|
1310
|
+
|
|
1311
|
+
3. Implement vector embedding logic in your capabilities:
|
|
1312
|
+
\`\`\`typescript
|
|
1313
|
+
// feeldb/capabilities/index.ts
|
|
1314
|
+
export const capabilities = {
|
|
1315
|
+
'vector-search': {
|
|
1316
|
+
enabled: true,
|
|
1317
|
+
scope: ['documents:read', 'capabilities:execute'],
|
|
1318
|
+
},
|
|
1319
|
+
};
|
|
1320
|
+
\`\`\`
|
|
1321
|
+
|
|
1322
|
+
### Vector Storage Options
|
|
1323
|
+
|
|
1324
|
+
#### Local Development
|
|
1325
|
+
For development, FeltDB includes a local vector store:
|
|
1326
|
+
- In-memory embeddings
|
|
1327
|
+
- No external dependencies
|
|
1328
|
+
- Perfect for prototyping
|
|
1329
|
+
|
|
1330
|
+
#### Production
|
|
1331
|
+
For production deployments:
|
|
1332
|
+
|
|
1333
|
+
**Milvus** (Open source)
|
|
1334
|
+
- Self-hosted vector database
|
|
1335
|
+
- Scalable to millions of vectors
|
|
1336
|
+
- Full-text + vector search combined
|
|
1337
|
+
|
|
1338
|
+
**Pinecone** (Managed service)
|
|
1339
|
+
- Serverless vector database
|
|
1340
|
+
- Easy to set up
|
|
1341
|
+
- No infrastructure to manage
|
|
1342
|
+
|
|
1343
|
+
**Weaviate** (Open source + Cloud)
|
|
1344
|
+
- GraphQL interface
|
|
1345
|
+
- Hybrid search (text + vectors)
|
|
1346
|
+
- Multiple deployment options
|
|
1347
|
+
|
|
1348
|
+
## Migration Between Runtimes
|
|
1349
|
+
|
|
1350
|
+
You can migrate between runtimes by:
|
|
1351
|
+
|
|
1352
|
+
1. Export data from current runtime
|
|
1353
|
+
2. Update \`feltdb.config.json\` with new runtime
|
|
1354
|
+
3. Import data to new runtime
|
|
1355
|
+
|
|
1356
|
+
Example:
|
|
1357
|
+
\`\`\`bash
|
|
1358
|
+
# Export from browser
|
|
1359
|
+
npm run feltdb:export
|
|
1360
|
+
|
|
1361
|
+
# Update config
|
|
1362
|
+
# nano feltdb.config.json
|
|
1363
|
+
|
|
1364
|
+
# Start new runtime
|
|
1365
|
+
npm run dev
|
|
1366
|
+
|
|
1367
|
+
# Import data
|
|
1368
|
+
npm run feltdb:import
|
|
1369
|
+
\`\`\`
|
|
1370
|
+
|
|
1371
|
+
## Environment Variables by Runtime
|
|
1372
|
+
|
|
1373
|
+
### Browser
|
|
1374
|
+
- No special variables needed
|
|
1375
|
+
- Optional: \`VITE_DEBUG=1\` for debugging
|
|
1376
|
+
|
|
1377
|
+
### Node.js
|
|
1378
|
+
- \`DATABASE_URL\`: Connection string for persistent database
|
|
1379
|
+
- \`PORT\`: Server port (default: 3000)
|
|
1380
|
+
- \`NODE_ENV\`: development|production
|
|
1381
|
+
|
|
1382
|
+
### Self-Hosted
|
|
1383
|
+
- \`VITE_FELTDB_URL\`: Docker container URL (default: http://localhost:7700)
|
|
1384
|
+
- \`VITE_FELTDB_API_KEY\`: API token for authentication
|
|
1385
|
+
- \`FELTDB_IMAGE\`: Docker image to use (optional override)
|
|
1386
|
+
- \`DOCKER_NETWORK\`: Custom Docker network (optional)
|
|
1387
|
+
|
|
1388
|
+
## Troubleshooting
|
|
1389
|
+
|
|
1390
|
+
### "Runtime not configured" Error
|
|
1391
|
+
Update \`feltdb.config.json\` with a valid runtime selection.
|
|
1392
|
+
|
|
1393
|
+
### "Cannot connect to self-hosted server"
|
|
1394
|
+
Check that Docker is running:
|
|
1395
|
+
\`\`\`bash
|
|
1396
|
+
docker ps | grep feltdb
|
|
1397
|
+
\`\`\`
|
|
1398
|
+
|
|
1399
|
+
If not running, restart:
|
|
1400
|
+
\`\`\`bash
|
|
1401
|
+
npm run dev
|
|
1402
|
+
\`\`\`
|
|
1403
|
+
|
|
1404
|
+
### Browser Storage Full
|
|
1405
|
+
OPFS limit reached. Clear unused data or migrate to self-hosted.
|
|
1406
|
+
|
|
1407
|
+
### Vector Search Not Working
|
|
1408
|
+
Verify vector database connection and that embeddings are enabled in capabilities.
|
|
1409
|
+
|
|
1410
|
+
## Performance Considerations
|
|
1411
|
+
|
|
1412
|
+
| Metric | Browser | Node.js | Self-Hosted |
|
|
1413
|
+
|--------|---------|---------|-------------|
|
|
1414
|
+
| Latency | Immediate | Low | Low |
|
|
1415
|
+
| Throughput | ~1000 ops/sec | ~10K ops/sec | ~100K ops/sec |
|
|
1416
|
+
| Data Limit | 50GB+ | Disk dependent | Unlimited |
|
|
1417
|
+
| Users | 1 | Few | Many |
|
|
1418
|
+
| Cost | Free | Hosting cost | Hosting cost |
|
|
1419
|
+
|
|
1420
|
+
## Next Steps
|
|
1421
|
+
|
|
1422
|
+
- Read the [FeltDB Documentation](https://github.com/rkendel1/feltdb)
|
|
1423
|
+
- Check out example projects in the templates
|
|
1424
|
+
- Join the community Discord for support
|
|
1425
|
+
`;
|
|
1426
|
+
fs.writeFileSync(path.join(projectDir, 'RUNTIME_GUIDE.md'), runtimeGuide);
|
|
547
1427
|
// Create .gitignore
|
|
548
1428
|
const gitignore = `node_modules/
|
|
549
1429
|
dist/
|
|
@@ -560,7 +1440,14 @@ build/
|
|
|
560
1440
|
// Create README
|
|
561
1441
|
const readme = `# ${applicationName}
|
|
562
1442
|
|
|
563
|
-
A FeltDB
|
|
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'}
|
|
1447
|
+
- **Framework:** ${framework}
|
|
1448
|
+
- **Distributed:** ${distributed ? 'Yes' : 'No'}
|
|
1449
|
+
- **Agents:** ${hasAgents ? 'Yes' : 'No'}
|
|
1450
|
+
- **Capabilities:** ${capabilities}
|
|
564
1451
|
|
|
565
1452
|
## Quick Start
|
|
566
1453
|
|
|
@@ -569,37 +1456,123 @@ npm install
|
|
|
569
1456
|
npm run dev
|
|
570
1457
|
\`\`\`
|
|
571
1458
|
|
|
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
|
|
1481
|
+
|
|
572
1482
|
## Project Structure
|
|
573
1483
|
|
|
574
1484
|
\`\`\`
|
|
575
1485
|
${applicationName}/
|
|
576
|
-
├── feltdb/
|
|
1486
|
+
├── feltdb/ # FeltDB configuration
|
|
577
1487
|
│ ├── agents/ # Agent definitions
|
|
578
1488
|
│ ├── capabilities/ # Capability implementations
|
|
579
1489
|
│ ├── workflows/ # Workflow definitions
|
|
580
1490
|
│ └── schema/ # Data schemas
|
|
581
|
-
├── src/
|
|
582
|
-
│ ├── App.${framework === 'react' ? 'tsx' : 'js'}
|
|
583
|
-
│ ├── feltdb.ts
|
|
584
|
-
│ └── index.${framework === 'react' ? 'tsx' : 'js'}
|
|
585
|
-
├── public/
|
|
1491
|
+
├── src/ # Application source code
|
|
1492
|
+
│ ├── App.${framework === 'react' ? 'tsx' : 'js'} # Main workspace UI
|
|
1493
|
+
│ ├── feltdb.ts # FeltDB collections and operations
|
|
1494
|
+
│ └── index.${framework === 'react' ? 'tsx' : 'js'} # Entry point
|
|
1495
|
+
├── public/ # Static assets
|
|
586
1496
|
├── index.html
|
|
587
1497
|
├── .feltdb/ # Local FeltDB configuration
|
|
588
|
-
├── feltdb.config.json
|
|
589
|
-
├── .env.example
|
|
1498
|
+
├── feltdb.config.json # FeltDB configuration
|
|
1499
|
+
├── .env.example # Environment variables template
|
|
590
1500
|
├── package.json
|
|
591
1501
|
├── tsconfig.json
|
|
592
1502
|
└── README.md
|
|
593
1503
|
\`\`\`
|
|
594
1504
|
|
|
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\`.
|
|
1545
|
+
|
|
595
1546
|
## Configuration
|
|
596
1547
|
|
|
597
1548
|
Configuration is in \`feltdb.config.json\`:
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
+
\`\`\`
|
|
1562
|
+
|
|
1563
|
+
### Environment Variables
|
|
1564
|
+
|
|
1565
|
+
Create a \`.env.local\` file (copy from \`.env.example\`):
|
|
1566
|
+
|
|
1567
|
+
\`\`\`
|
|
1568
|
+
VITE_FELTDB_API_KEY=your_api_key_here
|
|
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
|
|
1573
|
+
\`\`\`
|
|
1574
|
+
|
|
1575
|
+
These are required when connecting to an authenticated self-hosted or managed FeltDB instance.
|
|
603
1576
|
|
|
604
1577
|
## Development
|
|
605
1578
|
|
|
@@ -613,29 +1586,105 @@ npm run dev
|
|
|
613
1586
|
npm run build
|
|
614
1587
|
\`\`\`
|
|
615
1588
|
|
|
616
|
-
###
|
|
1589
|
+
### Validate FeltDB Configuration
|
|
617
1590
|
\`\`\`bash
|
|
618
|
-
|
|
1591
|
+
npm run feltdb:validate
|
|
619
1592
|
\`\`\`
|
|
620
1593
|
|
|
621
|
-
###
|
|
1594
|
+
### View FeltDB Status
|
|
622
1595
|
\`\`\`bash
|
|
623
|
-
|
|
1596
|
+
npm run feltdb:status
|
|
624
1597
|
\`\`\`
|
|
625
1598
|
|
|
1599
|
+
### Open FeltDB Studio
|
|
1600
|
+
\`\`\`bash
|
|
1601
|
+
npm run feltdb:studio
|
|
1602
|
+
\`\`\`
|
|
1603
|
+
|
|
1604
|
+
## Database Schema
|
|
1605
|
+
|
|
1606
|
+
### Collections
|
|
1607
|
+
|
|
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)
|
|
1643
|
+
|
|
626
1644
|
## Agents
|
|
627
1645
|
|
|
628
|
-
${hasAgents ?
|
|
629
|
-
|
|
630
|
-
|
|
1646
|
+
${hasAgents ? `### Available Agents
|
|
1647
|
+
The application includes autonomous agents powered by \`@feltdb/webllm\`:
|
|
1648
|
+
- Runs entirely in the browser (private, no external APIs)
|
|
1649
|
+
- Model downloads on first use
|
|
1650
|
+
- Inference runs in a Web Worker
|
|
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.'}
|
|
1652
|
+
|
|
1653
|
+
## Troubleshooting
|
|
631
1654
|
|
|
632
|
-
|
|
1655
|
+
### Data not persisting
|
|
1656
|
+
1. Check browser console for errors
|
|
1657
|
+
2. Verify IndexedDB is enabled (not in private mode)
|
|
1658
|
+
3. Check browser storage quota
|
|
1659
|
+
4. Clear browser cache and try again
|
|
633
1660
|
|
|
634
|
-
|
|
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
|
+
|
|
1666
|
+
### Self-Hosted Connection Issues
|
|
1667
|
+
If using self-hosted mode and connection fails:
|
|
1668
|
+
1. Ensure Docker is installed and running
|
|
1669
|
+
2. Check VITE_FELTDB_URL and API key in .env.local
|
|
1670
|
+
3. Run \`npm run feltdb:status\` to check server health
|
|
1671
|
+
4. View Docker logs: \`docker logs feltdb\`
|
|
1672
|
+
|
|
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\`
|
|
635
1677
|
|
|
636
1678
|
## Learn More
|
|
637
1679
|
|
|
638
|
-
|
|
1680
|
+
- [FeltDB Documentation](https://github.com/rkendel1/feltdb)
|
|
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
|
|
639
1688
|
`;
|
|
640
1689
|
fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
|
|
641
1690
|
}
|