create-feltdb 0.4.5 → 0.4.10
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/cli.js +131 -8
- package/dist/create.js +1309 -258
- 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,7 +53,6 @@ export async function createProject(options) {
|
|
|
53
53
|
'@feltdb/core': feltdbPackageRange,
|
|
54
54
|
},
|
|
55
55
|
devDependencies: {
|
|
56
|
-
'@feltdb/cli': feltdbPackageRange,
|
|
57
56
|
typescript: '^5.0.0',
|
|
58
57
|
'@types/node': '^20.0.0',
|
|
59
58
|
vite: '^8.2.1',
|
|
@@ -73,7 +72,7 @@ export async function createProject(options) {
|
|
|
73
72
|
const feltdbConfig = {
|
|
74
73
|
namespace: applicationName,
|
|
75
74
|
runtime,
|
|
76
|
-
storage: runtime === 'browser' ? '
|
|
75
|
+
storage: runtime === 'browser' ? 'indexeddb' : runtime === 'managed' ? 'managed' : 'durable',
|
|
77
76
|
distributed,
|
|
78
77
|
agents: {
|
|
79
78
|
enabled: hasAgents,
|
|
@@ -157,16 +156,165 @@ export async function createProject(options) {
|
|
|
157
156
|
// Create main application files
|
|
158
157
|
const runtimeOptions = runtime === 'browser'
|
|
159
158
|
? "{ namespace: '" + applicationName + "', browser: true }"
|
|
160
|
-
: runtime === '
|
|
161
|
-
? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || '
|
|
162
|
-
:
|
|
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 }";
|
|
163
164
|
const feltdbTs = `import { createFeltDB } from '@feltdb/core';
|
|
164
165
|
|
|
165
166
|
export const db = createFeltDB(${runtimeOptions});
|
|
166
167
|
|
|
167
168
|
// Collections
|
|
168
|
-
export const
|
|
169
|
-
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({ name: 'projects_status', type: 'hash', field: 'status' });
|
|
209
|
+
tasks.createIndex({ name: 'tasks_project', type: 'hash', field: 'projectId' });
|
|
210
|
+
tasks.createIndex({ name: 'tasks_status', type: 'hash', field: 'status' });
|
|
211
|
+
tasks.createIndex({ name: 'tasks_priority', type: 'hash', field: 'priority' });
|
|
212
|
+
activity.createIndex({ name: 'activity_timestamp', type: 'sorted', field: '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
|
+
}
|
|
170
318
|
`;
|
|
171
319
|
fs.writeFileSync(path.join(srcDir, 'feltdb.ts'), feltdbTs);
|
|
172
320
|
// Create a real local-inference agent for browser projects.
|
|
@@ -181,12 +329,16 @@ import { db, reports } from '../../src/feltdb';
|
|
|
181
329
|
export const researcher = db.defineAgent({
|
|
182
330
|
name: 'researcher',
|
|
183
331
|
version: 1,
|
|
184
|
-
description: '
|
|
332
|
+
description: 'Private, on-device AI assistant for document enhancement and analysis',
|
|
185
333
|
capabilities: [
|
|
186
334
|
'document-read',
|
|
187
335
|
'report-write'
|
|
188
336
|
],
|
|
189
|
-
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
|
+
],
|
|
190
342
|
constraints: { maxIterations: 1 },
|
|
191
343
|
});
|
|
192
344
|
|
|
@@ -196,20 +348,138 @@ export async function runResearcher(
|
|
|
196
348
|
prompt: string,
|
|
197
349
|
onProgress?: (message: string) => void,
|
|
198
350
|
) {
|
|
199
|
-
provider
|
|
200
|
-
|
|
201
|
-
onProgress
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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);
|
|
213
483
|
}
|
|
214
484
|
`;
|
|
215
485
|
fs.writeFileSync(path.join(feltdbDir, 'agents', 'researcher.ts'), agentTs);
|
|
@@ -241,24 +511,44 @@ export const capabilities = {
|
|
|
241
511
|
// Create main application file based on framework
|
|
242
512
|
if (framework === 'react') {
|
|
243
513
|
const agentImport = hasAgents
|
|
244
|
-
? "import { runResearcher } from '../feltdb/agents/researcher';"
|
|
514
|
+
? "import { runResearcher, summarizeDocument, enhanceDocument, generateIdeas, generateFeltDBFeature, generateReactComponent, generateCapability } from '../feltdb/agents/researcher';"
|
|
245
515
|
: '';
|
|
246
516
|
const agentState = hasAgents
|
|
247
|
-
? ` const [prompt, setPrompt] = useState('
|
|
517
|
+
? ` const [prompt, setPrompt] = useState('');
|
|
248
518
|
const [answer, setAnswer] = useState('');
|
|
249
519
|
const [modelStatus, setModelStatus] = useState('Model not loaded');
|
|
250
|
-
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');`
|
|
251
523
|
: '';
|
|
252
524
|
const agentHandler = hasAgents
|
|
253
525
|
? `
|
|
254
|
-
const
|
|
526
|
+
const handleRunAI = async (mode: 'custom' | 'summarize' | 'enhance' | 'ideas' | 'feltdb' | 'react' | 'capability') => {
|
|
255
527
|
setGenerating(true);
|
|
256
528
|
setAnswer('');
|
|
529
|
+
setModelStatus('Running inference...');
|
|
530
|
+
|
|
257
531
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
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');
|
|
260
550
|
} catch (error) {
|
|
261
|
-
setModelStatus(error instanceof Error ? error.message : String(error));
|
|
551
|
+
setModelStatus('✗ Error: ' + (error instanceof Error ? error.message : String(error)));
|
|
262
552
|
} finally {
|
|
263
553
|
setGenerating(false);
|
|
264
554
|
}
|
|
@@ -268,228 +558,551 @@ export const capabilities = {
|
|
|
268
558
|
const agentMarkup = hasAgents
|
|
269
559
|
? `
|
|
270
560
|
<section className="researcher">
|
|
271
|
-
<h2
|
|
272
|
-
<
|
|
273
|
-
|
|
274
|
-
<
|
|
275
|
-
|
|
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'}
|
|
276
662
|
</button>
|
|
277
|
-
|
|
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
|
+
)}
|
|
278
722
|
</section>`
|
|
279
723
|
: '';
|
|
280
724
|
const appTsx = `import React, { useState, useEffect } from 'react';
|
|
281
|
-
import {
|
|
282
|
-
|
|
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';
|
|
283
737
|
|
|
284
738
|
export function App() {
|
|
285
|
-
const [
|
|
286
|
-
const [
|
|
287
|
-
|
|
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);
|
|
288
749
|
|
|
289
750
|
useEffect(() => {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const allDocs = await documents.find({});
|
|
293
|
-
setDocs(allDocs);
|
|
294
|
-
} catch (err) {
|
|
295
|
-
console.error('Error loading documents:', err);
|
|
296
|
-
} finally {
|
|
297
|
-
setLoading(false);
|
|
298
|
-
}
|
|
299
|
-
};
|
|
751
|
+
getDashboardStats().then(setDashboardStats);
|
|
752
|
+
}, [projectsList, tasksList]);
|
|
300
753
|
|
|
301
|
-
|
|
302
|
-
|
|
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
|
+
};
|
|
303
765
|
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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',
|
|
317
880
|
};
|
|
318
|
-
${agentHandler}
|
|
319
881
|
|
|
320
882
|
return (
|
|
321
|
-
<div
|
|
322
|
-
<
|
|
323
|
-
<
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
</
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
|
|
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'}
|
|
340
931
|
</div>
|
|
341
|
-
</
|
|
342
|
-
|
|
343
|
-
<
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
<
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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>
|
|
358
963
|
)}
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
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>
|
|
489
1101
|
</div>
|
|
490
1102
|
);
|
|
491
1103
|
}
|
|
492
1104
|
`;
|
|
1105
|
+
;
|
|
493
1106
|
fs.writeFileSync(path.join(srcDir, 'App.tsx'), appTsx);
|
|
494
1107
|
const indexTsx = `import React from 'react';
|
|
495
1108
|
import ReactDOM from 'react-dom/client';
|
|
@@ -535,13 +1148,282 @@ main().catch(console.error);
|
|
|
535
1148
|
fs.writeFileSync(path.join(projectDir, 'index.html'), indexHtml);
|
|
536
1149
|
// Create .env.example
|
|
537
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
|
|
538
1155
|
VITE_FELTDB_API_KEY=
|
|
1156
|
+
|
|
1157
|
+
# FeltDB Server URL (self-hosted or managed runtime)
|
|
539
1158
|
VITE_FELTDB_URL=http://localhost:7700
|
|
540
|
-
|
|
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
|
|
541
1170
|
FELTDB_IMAGE=
|
|
1171
|
+
|
|
1172
|
+
# Node environment
|
|
542
1173
|
NODE_ENV=development
|
|
543
1174
|
`;
|
|
544
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);
|
|
545
1427
|
// Create .gitignore
|
|
546
1428
|
const gitignore = `node_modules/
|
|
547
1429
|
dist/
|
|
@@ -558,7 +1440,14 @@ build/
|
|
|
558
1440
|
// Create README
|
|
559
1441
|
const readme = `# ${applicationName}
|
|
560
1442
|
|
|
561
|
-
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}
|
|
562
1451
|
|
|
563
1452
|
## Quick Start
|
|
564
1453
|
|
|
@@ -567,37 +1456,123 @@ npm install
|
|
|
567
1456
|
npm run dev
|
|
568
1457
|
\`\`\`
|
|
569
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
|
+
|
|
570
1482
|
## Project Structure
|
|
571
1483
|
|
|
572
1484
|
\`\`\`
|
|
573
1485
|
${applicationName}/
|
|
574
|
-
├── feltdb/
|
|
1486
|
+
├── feltdb/ # FeltDB configuration
|
|
575
1487
|
│ ├── agents/ # Agent definitions
|
|
576
1488
|
│ ├── capabilities/ # Capability implementations
|
|
577
1489
|
│ ├── workflows/ # Workflow definitions
|
|
578
1490
|
│ └── schema/ # Data schemas
|
|
579
|
-
├── src/
|
|
580
|
-
│ ├── App.${framework === 'react' ? 'tsx' : 'js'}
|
|
581
|
-
│ ├── feltdb.ts
|
|
582
|
-
│ └── index.${framework === 'react' ? 'tsx' : 'js'}
|
|
583
|
-
├── 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
|
|
584
1496
|
├── index.html
|
|
585
1497
|
├── .feltdb/ # Local FeltDB configuration
|
|
586
|
-
├── feltdb.config.json
|
|
587
|
-
├── .env.example
|
|
1498
|
+
├── feltdb.config.json # FeltDB configuration
|
|
1499
|
+
├── .env.example # Environment variables template
|
|
588
1500
|
├── package.json
|
|
589
1501
|
├── tsconfig.json
|
|
590
1502
|
└── README.md
|
|
591
1503
|
\`\`\`
|
|
592
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
|
+
|
|
593
1546
|
## Configuration
|
|
594
1547
|
|
|
595
1548
|
Configuration is in \`feltdb.config.json\`:
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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.
|
|
601
1576
|
|
|
602
1577
|
## Development
|
|
603
1578
|
|
|
@@ -611,29 +1586,105 @@ npm run dev
|
|
|
611
1586
|
npm run build
|
|
612
1587
|
\`\`\`
|
|
613
1588
|
|
|
614
|
-
###
|
|
1589
|
+
### Validate FeltDB Configuration
|
|
615
1590
|
\`\`\`bash
|
|
616
|
-
|
|
1591
|
+
npm run feltdb:validate
|
|
617
1592
|
\`\`\`
|
|
618
1593
|
|
|
619
|
-
###
|
|
1594
|
+
### View FeltDB Status
|
|
620
1595
|
\`\`\`bash
|
|
621
|
-
|
|
1596
|
+
npm run feltdb:status
|
|
622
1597
|
\`\`\`
|
|
623
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
|
+
|
|
624
1644
|
## Agents
|
|
625
1645
|
|
|
626
|
-
${hasAgents ?
|
|
627
|
-
|
|
628
|
-
|
|
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
|
|
629
1654
|
|
|
630
|
-
|
|
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
|
|
631
1660
|
|
|
632
|
-
|
|
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\`
|
|
633
1677
|
|
|
634
1678
|
## Learn More
|
|
635
1679
|
|
|
636
|
-
|
|
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
|
|
637
1688
|
`;
|
|
638
1689
|
fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
|
|
639
1690
|
}
|