@agentforge-ai/cli 0.4.3 → 0.5.1

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.
Files changed (67) hide show
  1. package/dist/default/convex/agents.ts +204 -0
  2. package/dist/default/convex/apiKeys.ts +133 -0
  3. package/dist/default/convex/cronJobs.ts +224 -0
  4. package/dist/default/convex/files.ts +103 -0
  5. package/dist/default/convex/folders.ts +110 -0
  6. package/dist/default/convex/heartbeat.ts +371 -0
  7. package/dist/default/convex/logs.ts +66 -0
  8. package/dist/default/convex/mastraIntegration.ts +185 -0
  9. package/dist/default/convex/mcpConnections.ts +127 -0
  10. package/dist/default/convex/messages.ts +90 -0
  11. package/dist/default/convex/projects.ts +114 -0
  12. package/dist/default/convex/schema.ts +150 -83
  13. package/dist/default/convex/sessions.ts +174 -0
  14. package/dist/default/convex/settings.ts +79 -0
  15. package/dist/default/convex/skills.ts +178 -0
  16. package/dist/default/convex/threads.ts +100 -0
  17. package/dist/default/convex/usage.ts +195 -0
  18. package/dist/default/convex/vault.ts +397 -0
  19. package/dist/default/dashboard/app/main.tsx +7 -3
  20. package/dist/default/dashboard/app/routes/agents.tsx +103 -161
  21. package/dist/default/dashboard/app/routes/chat.tsx +163 -317
  22. package/dist/default/dashboard/app/routes/connections.tsx +247 -386
  23. package/dist/default/dashboard/app/routes/cron.tsx +127 -286
  24. package/dist/default/dashboard/app/routes/files.tsx +184 -167
  25. package/dist/default/dashboard/app/routes/index.tsx +63 -96
  26. package/dist/default/dashboard/app/routes/projects.tsx +106 -225
  27. package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
  28. package/dist/default/dashboard/app/routes/settings.tsx +316 -532
  29. package/dist/default/dashboard/app/routes/skills.tsx +329 -216
  30. package/dist/default/dashboard/app/routes/usage.tsx +107 -150
  31. package/dist/default/dashboard/tsconfig.json +3 -2
  32. package/dist/default/dashboard/vite.config.ts +6 -0
  33. package/dist/index.js +256 -49
  34. package/dist/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/templates/default/convex/agents.ts +204 -0
  37. package/templates/default/convex/apiKeys.ts +133 -0
  38. package/templates/default/convex/cronJobs.ts +224 -0
  39. package/templates/default/convex/files.ts +103 -0
  40. package/templates/default/convex/folders.ts +110 -0
  41. package/templates/default/convex/heartbeat.ts +371 -0
  42. package/templates/default/convex/logs.ts +66 -0
  43. package/templates/default/convex/mastraIntegration.ts +185 -0
  44. package/templates/default/convex/mcpConnections.ts +127 -0
  45. package/templates/default/convex/messages.ts +90 -0
  46. package/templates/default/convex/projects.ts +114 -0
  47. package/templates/default/convex/schema.ts +150 -83
  48. package/templates/default/convex/sessions.ts +174 -0
  49. package/templates/default/convex/settings.ts +79 -0
  50. package/templates/default/convex/skills.ts +178 -0
  51. package/templates/default/convex/threads.ts +100 -0
  52. package/templates/default/convex/usage.ts +195 -0
  53. package/templates/default/convex/vault.ts +397 -0
  54. package/templates/default/dashboard/app/main.tsx +7 -3
  55. package/templates/default/dashboard/app/routes/agents.tsx +103 -161
  56. package/templates/default/dashboard/app/routes/chat.tsx +163 -317
  57. package/templates/default/dashboard/app/routes/connections.tsx +247 -386
  58. package/templates/default/dashboard/app/routes/cron.tsx +127 -286
  59. package/templates/default/dashboard/app/routes/files.tsx +184 -167
  60. package/templates/default/dashboard/app/routes/index.tsx +63 -96
  61. package/templates/default/dashboard/app/routes/projects.tsx +106 -225
  62. package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
  63. package/templates/default/dashboard/app/routes/settings.tsx +316 -532
  64. package/templates/default/dashboard/app/routes/skills.tsx +329 -216
  65. package/templates/default/dashboard/app/routes/usage.tsx +107 -150
  66. package/templates/default/dashboard/tsconfig.json +3 -2
  67. package/templates/default/dashboard/vite.config.ts +6 -0
@@ -1,137 +1,104 @@
1
- import { createFileRoute } from '@tanstack/react-router';
1
+ import { createFileRoute, Link } from '@tanstack/react-router';
2
2
  import { DashboardLayout } from '../components/DashboardLayout';
3
- import { useState } from 'react';
4
- // import { useQuery } from 'convex/react';
5
- // import { api } from '~/../convex/_generated/api';
6
- import { Bot, Activity, MessageSquare, FileText, Plus, Heart, Zap, ArrowRight, AlertTriangle, CheckCircle } from 'lucide-react';
3
+ import { useQuery } from 'convex/react';
4
+ import { api } from '@convex/_generated/api';
5
+ import { Bot, MessageSquare, FileText, Activity, Zap, Plus, CheckCircle, AlertTriangle } from 'lucide-react';
7
6
 
8
7
  export const Route = createFileRoute('/')({ component: OverviewPage });
9
8
 
10
- function StatCard({ icon: Icon, title, value, change }) {
9
+ function StatCard({ icon: Icon, title, value, subtitle }: { icon: any; title: string; value: string | number; subtitle?: string }) {
11
10
  return (
12
- <div className="bg-card p-6 rounded-lg shadow-md flex items-center justify-between">
13
- <div>
14
- <p className="text-sm text-muted-foreground">{title}</p>
15
- <p className="text-3xl font-bold text-foreground">{value}</p>
16
- <p className={`text-xs ${change.startsWith('+') ? 'text-green-500' : 'text-red-500'}`}>{change}</p>
11
+ <div className="bg-card border border-border rounded-lg p-6 shadow-sm">
12
+ <div className="flex items-center justify-between mb-2">
13
+ <span className="text-sm font-medium text-muted-foreground">{title}</span>
14
+ <Icon className="w-5 h-5 text-primary" />
17
15
  </div>
18
- <Icon className="w-10 h-10 text-primary" />
16
+ <p className="text-3xl font-bold text-foreground">{value}</p>
17
+ {subtitle && <p className="text-xs text-muted-foreground mt-1">{subtitle}</p>}
19
18
  </div>
20
19
  );
21
20
  }
22
21
 
23
- function QuickActionButton({ icon: Icon, label, to }) {
24
- return (
25
- <a href={to} className="bg-card hover:bg-primary/10 border border-border p-4 rounded-lg flex flex-col items-center justify-center text-center transition-colors">
26
- <Icon className="w-8 h-8 text-primary mb-2" />
27
- <span className="text-sm font-medium text-foreground">{label}</span>
28
- </a>
29
- );
30
- }
31
-
32
- function ActivityItem({ icon: Icon, text, time }) {
22
+ function QuickActionButton({ icon: Icon, label, to }: { icon: any; label: string; to: string }) {
33
23
  return (
34
- <li className="flex items-center space-x-4 py-3 border-b border-border last:border-b-0">
35
- <div className="bg-primary/10 p-2 rounded-full">
36
- <Icon className="w-5 h-5 text-primary" />
37
- </div>
38
- <div className="flex-grow">
39
- <p className="text-sm text-foreground">{text}</p>
40
- </div>
41
- <p className="text-xs text-muted-foreground whitespace-nowrap">{time}</p>
42
- <ArrowRight className="w-4 h-4 text-muted-foreground" />
43
- </li>
24
+ <Link to={to} className="flex flex-col items-center justify-center p-4 rounded-lg border border-border hover:bg-muted transition-colors gap-2">
25
+ <Icon className="w-6 h-6 text-primary" />
26
+ <span className="text-xs font-medium text-muted-foreground">{label}</span>
27
+ </Link>
44
28
  );
45
29
  }
46
30
 
47
- function SystemHealthIndicator({ isHealthy }) {
48
- const Icon = isHealthy ? CheckCircle : AlertTriangle;
49
- const text = isHealthy ? 'All systems operational' : 'Service degradation';
50
- const color = isHealthy ? 'text-green-500' : 'text-yellow-500';
51
-
52
- return (
53
- <div className="flex items-center space-x-2">
54
- <Icon className={`w-5 h-5 ${color}`} />
55
- <span className={`text-sm font-medium ${color}`}>{text}</span>
56
- </div>
57
- );
58
- }
59
-
60
31
  function OverviewPage() {
61
- // Local state with realistic initial values, simulating data from Convex
62
- const [stats, setStats] = useState({
63
- totalAgents: 12,
64
- activeSessions: 3,
65
- messagesToday: 1402,
66
- totalFiles: 256,
67
- });
68
-
69
- const [activity, setActivity] = useState([
70
- { id: 1, icon: Bot, text: 'New agent "SupportBot" created.', time: '5m ago' },
71
- { id: 2, icon: MessageSquare, text: 'Session #1245 ended with 52 messages.', time: '1h ago' },
72
- { id: 3, icon: FileText, text: 'Uploaded "project_spec.pdf".', time: '3h ago' },
73
- { id: 4, icon: Zap, text: 'Agent "DataAnalyzer" completed a task.', time: '5h ago' },
74
- { id: 5, icon: Bot, text: 'Agent "CodeGenerator" was updated.', time: '1d ago' },
75
- ]);
76
-
77
- const [systemHealth, setSystemHealth] = useState({ isHealthy: true });
78
-
79
- // Commented-out Convex hooks for future integration
80
- /*
81
- const overviewStats = useQuery(api.overview.getStats);
82
- const recentActivity = useQuery(api.overview.getRecentActivity, { count: 5 });
83
- const systemStatus = useQuery(api.heartbeat.getStatus);
32
+ const agents = useQuery(api.agents.list, {});
33
+ const sessions = useQuery(api.sessions.list, {});
34
+ const files = useQuery(api.files.list, {});
35
+ const usage = useQuery(api.usage.list, {});
36
+ const logs = useQuery(api.logs.list, { limit: 5 });
84
37
 
85
- // In a real implementation, you would use the data from hooks:
86
- // const stats = overviewStats || { totalAgents: 0, activeSessions: 0, messagesToday: 0, totalFiles: 0 };
87
- // const activity = recentActivity || [];
88
- // const systemHealth = { isHealthy: systemStatus === 'operational' };
89
- */
38
+ const totalAgents = agents?.length ?? 0;
39
+ const activeSessions = sessions?.filter((s: any) => s.status === 'active').length ?? 0;
40
+ const totalFiles = files?.length ?? 0;
41
+ const totalTokens = usage?.reduce((sum: number, u: any) => sum + (u.totalTokens || 0), 0) ?? 0;
42
+ const isLoading = agents === undefined;
90
43
 
91
44
  return (
92
45
  <DashboardLayout>
93
- <div className="p-6 md:p-8 space-y-8 bg-background text-foreground">
46
+ <div className="space-y-8">
94
47
  <div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
95
- <div>
96
- <h1 className="text-3xl font-bold">Overview</h1>
97
- <p className="text-muted-foreground">Welcome back, here's a snapshot of your workspace.</p>
98
- </div>
99
- <SystemHealthIndicator isHealthy={systemHealth.isHealthy} />
48
+ <div>
49
+ <h1 className="text-3xl font-bold">Overview</h1>
50
+ <p className="text-muted-foreground">Welcome back. Here is a snapshot of your workspace.</p>
51
+ </div>
52
+ <div className="flex items-center space-x-2">
53
+ {isLoading ? (
54
+ <span className="text-sm text-muted-foreground animate-pulse">Connecting to Convex...</span>
55
+ ) : (
56
+ <><CheckCircle className="w-5 h-5 text-green-500" /><span className="text-sm font-medium text-green-500">All systems operational</span></>
57
+ )}
58
+ </div>
100
59
  </div>
101
60
 
102
- {/* Stat Cards */}
103
61
  <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
104
- <StatCard icon={Bot} title="Total Agents" value={stats.totalAgents} change="+2 this week" />
105
- <StatCard icon={Activity} title="Active Sessions" value={stats.activeSessions} change="-1 since yesterday" />
106
- <StatCard icon={MessageSquare} title="Messages Today" value={stats.messagesToday.toLocaleString()} change="+15%" />
107
- <StatCard icon={FileText} title="Total Files" value={stats.totalFiles} change="+12 files" />
62
+ <StatCard icon={Bot} title="Total Agents" value={totalAgents} subtitle={`${agents?.filter((a: any) => a.isActive).length ?? 0} active`} />
63
+ <StatCard icon={Activity} title="Active Sessions" value={activeSessions} />
64
+ <StatCard icon={MessageSquare} title="Total Tokens Used" value={totalTokens.toLocaleString()} />
65
+ <StatCard icon={FileText} title="Total Files" value={totalFiles} />
108
66
  </div>
109
67
 
110
68
  <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
111
- {/* Recent Activity */}
112
- <div className="lg:col-span-2 bg-card p-6 rounded-lg shadow-md">
113
- <h2 className="text-xl font-semibold mb-4 flex items-center"><Activity className="w-6 h-6 mr-2 text-primary"/>Recent Activity</h2>
114
- {activity.length > 0 ? (
69
+ <div className="lg:col-span-2 bg-card p-6 rounded-lg shadow-sm border border-border">
70
+ <h2 className="text-xl font-semibold mb-4 flex items-center"><Activity className="w-6 h-6 mr-2 text-primary" />Recent Activity</h2>
71
+ {logs && logs.length > 0 ? (
115
72
  <ul>
116
- {activity.map(item => (
117
- <ActivityItem key={item.id} icon={item.icon} text={item.text} time={item.time} />
73
+ {logs.map((log: any) => (
74
+ <li key={log._id} className="flex items-center space-x-4 py-3 border-b border-border last:border-b-0">
75
+ <div className="bg-primary/10 p-2 rounded-full">
76
+ {log.level === 'error' ? <AlertTriangle className="w-5 h-5 text-destructive" /> : <Activity className="w-5 h-5 text-primary" />}
77
+ </div>
78
+ <div className="flex-grow min-w-0">
79
+ <p className="text-sm text-foreground truncate">{log.message}</p>
80
+ <p className="text-xs text-muted-foreground">{log.source}</p>
81
+ </div>
82
+ <p className="text-xs text-muted-foreground whitespace-nowrap">{new Date(log.timestamp).toLocaleString()}</p>
83
+ </li>
118
84
  ))}
119
85
  </ul>
120
86
  ) : (
121
87
  <div className="text-center py-10">
122
- <p className="text-muted-foreground">No recent activity to display.</p>
88
+ <Activity className="w-12 h-12 text-muted-foreground/30 mx-auto mb-3" />
89
+ <p className="text-muted-foreground">No recent activity yet.</p>
90
+ <p className="text-sm text-muted-foreground mt-1">Activity will appear here as you use your agents.</p>
123
91
  </div>
124
92
  )}
125
93
  </div>
126
94
 
127
- {/* Quick Actions */}
128
- <div className="bg-card p-6 rounded-lg shadow-md">
129
- <h2 className="text-xl font-semibold mb-4 flex items-center"><Zap className="w-6 h-6 mr-2 text-primary"/>Quick Actions</h2>
95
+ <div className="bg-card p-6 rounded-lg shadow-sm border border-border">
96
+ <h2 className="text-xl font-semibold mb-4 flex items-center"><Zap className="w-6 h-6 mr-2 text-primary" />Quick Actions</h2>
130
97
  <div className="grid grid-cols-2 gap-4">
131
- <QuickActionButton icon={Bot} label="Create Agent" to="/agents/new" />
98
+ <QuickActionButton icon={Bot} label="Create Agent" to="/agents" />
132
99
  <QuickActionButton icon={MessageSquare} label="Start Chat" to="/chat" />
133
100
  <QuickActionButton icon={FileText} label="Upload File" to="/files" />
134
- <QuickActionButton icon={Plus} label="New Project" to="/projects/new" />
101
+ <QuickActionButton icon={Plus} label="New Project" to="/projects" />
135
102
  </div>
136
103
  </div>
137
104
  </div>
@@ -1,254 +1,135 @@
1
- import { createFileRoute, Link } from '@tanstack/react-router';
1
+ import { createFileRoute } from '@tanstack/react-router';
2
2
  import { DashboardLayout } from '../components/DashboardLayout';
3
- import React, { useState, useMemo } from 'react';
4
- // import { useQuery, useMutation } from 'convex/react';
5
- // import { api } from '../../convex/_generated/api';
6
- import { FolderKanban, Plus, Settings, Users, FileText, Trash2, Edit, X, Search, MoreVertical } from 'lucide-react';
7
- import * as Dialog from '@radix-ui/react-dialog';
8
- import * as Tabs from '@radix-ui/react-tabs';
9
-
10
- // --- MOCK DATA & TYPES ---
11
- type Project = {
12
- id: string;
13
- name: string;
14
- description: string;
15
- agentCount: number;
16
- fileCount: number;
17
- createdAt: string;
18
- };
19
-
20
- const initialProjects: Project[] = [
21
- { id: 'proj_1', name: 'AgentForge UI Revamp', description: 'Complete overhaul of the dashboard UI using TanStack and Tailwind.', agentCount: 5, fileCount: 42, createdAt: '2024-07-21' },
22
- { id: 'proj_2', name: 'Customer Support Bot', description: 'An AI-powered chatbot to handle customer queries in real-time.', agentCount: 2, fileCount: 15, createdAt: '2024-07-20' },
23
- { id: 'proj_3', name: 'Data Analysis Pipeline', description: 'Automated workflow for processing and analyzing sales data.', agentCount: 8, fileCount: 128, createdAt: '2024-06-15' },
24
- { id: 'proj_4', name: 'Internal Documentation Hub', description: 'A centralized place for all internal project documentation.', agentCount: 1, fileCount: 256, createdAt: '2024-05-01' },
25
- ];
26
-
27
- // --- HELPER COMPONENTS ---
28
-
29
- const ProjectCard = ({ project, onEdit, onDelete, onSelect }: { project: Project, onEdit: (project: Project) => void, onDelete: (id: string) => void, onSelect: (project: Project) => void }) => (
30
- <div className="bg-card border border-border rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 flex flex-col">
31
- <div className="p-4 flex-grow cursor-pointer" onClick={() => onSelect(project)}>
32
- <div className="flex items-start justify-between">
33
- <h3 className="font-bold text-lg text-foreground mb-2">{project.name}</h3>
34
- <FolderKanban className="w-6 h-6 text-muted-foreground" />
35
- </div>
36
- <p className="text-sm text-muted-foreground mb-4 h-10 overflow-hidden">{project.description}</p>
37
- </div>
38
- <div className="border-t border-border p-4 flex justify-between items-center text-xs text-muted-foreground">
39
- <div className="flex items-center gap-4">
40
- <span className="flex items-center gap-1"><Users className="w-4 h-4" /> {project.agentCount}</span>
41
- <span className="flex items-center gap-1"><FileText className="w-4 h-4" /> {project.fileCount}</span>
42
- </div>
43
- <div className="flex items-center gap-2">
44
- <button onClick={() => onEdit(project)} className="hover:text-foreground"><Edit className="w-4 h-4" /></button>
45
- <button onClick={() => onDelete(project.id)} className="hover:text-destructive"><Trash2 className="w-4 h-4" /></button>
46
- </div>
47
- </div>
48
- </div>
49
- );
50
-
51
- const ProjectForm = ({ project, onSave, onCancel }: { project?: Project | null, onSave: (p: Omit<Project, 'id' | 'agentCount' | 'fileCount' | 'createdAt'>) => void, onCancel: () => void }) => {
52
- const [name, setName] = useState(project?.name || '');
53
- const [description, setDescription] = useState(project?.description || '');
54
-
55
- const handleSubmit = (e: React.FormEvent) => {
56
- e.preventDefault();
57
- if (!name) return;
58
- onSave({ name, description });
59
- };
60
-
61
- return (
62
- <form onSubmit={handleSubmit} className="flex flex-col gap-4">
63
- <div>
64
- <label htmlFor="name" className="block text-sm font-medium text-muted-foreground mb-1">Project Name</label>
65
- <input id="name" value={name} onChange={e => setName(e.target.value)} className="w-full bg-background border border-border rounded-md px-3 py-2 text-foreground focus:ring-2 focus:ring-primary" required />
66
- </div>
67
- <div>
68
- <label htmlFor="description" className="block text-sm font-medium text-muted-foreground mb-1">Description</label>
69
- <textarea id="description" value={description} onChange={e => setDescription(e.target.value)} rows={4} className="w-full bg-background border border-border rounded-md px-3 py-2 text-foreground focus:ring-2 focus:ring-primary" />
70
- </div>
71
- <div className="flex justify-end gap-2 mt-4">
72
- <button type="button" onClick={onCancel} className="px-4 py-2 rounded-md border border-border text-foreground hover:bg-card">Cancel</button>
73
- <button type="submit" className="px-4 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90">Save Project</button>
74
- </div>
75
- </form>
76
- );
77
- };
78
-
79
- const ProjectDetailView = ({ project, onBack }: { project: Project, onBack: () => void }) => (
80
- <div className="bg-background p-6 rounded-lg">
81
- <div className="flex justify-between items-start mb-6">
82
- <div>
83
- <button onClick={onBack} className="text-sm text-primary hover:underline mb-2">&larr; Back to Projects</button>
84
- <h2 className="text-2xl font-bold text-foreground">{project.name}</h2>
85
- <p className="text-muted-foreground">{project.description}</p>
86
- </div>
87
- <div className="text-sm text-muted-foreground">Created: {new Date(project.createdAt).toLocaleDateString()}</div>
88
- </div>
89
-
90
- <Tabs.Root defaultValue="overview" className="w-full">
91
- <Tabs.List className="flex border-b border-border mb-4">
92
- <Tabs.Trigger value="overview" className="px-4 py-2 text-sm font-medium text-muted-foreground data-[state=active]:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-primary">Overview</Tabs.Trigger>
93
- <Tabs.Trigger value="agents" className="px-4 py-2 text-sm font-medium text-muted-foreground data-[state=active]:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-primary">Agents</Tabs.Trigger>
94
- <Tabs.Trigger value="files" className="px-4 py-2 text-sm font-medium text-muted-foreground data-[state=active]:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-primary">Files</Tabs.Trigger>
95
- <Tabs.Trigger value="settings" className="px-4 py-2 text-sm font-medium text-muted-foreground data-[state=active]:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-primary">Settings</Tabs.Trigger>
96
- </Tabs.List>
97
- <Tabs.Content value="overview" className="p-4 bg-card rounded-b-lg border border-t-0 border-border">
98
- <h4 className="font-bold text-lg mb-2">Project Overview</h4>
99
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-center">
100
- <div className="bg-background p-4 rounded-lg border border-border"><p className="text-2xl font-bold">{project.agentCount}</p><p className="text-sm text-muted-foreground">Agents</p></div>
101
- <div className="bg-background p-4 rounded-lg border border-border"><p className="text-2xl font-bold">{project.fileCount}</p><p className="text-sm text-muted-foreground">Files</p></div>
102
- </div>
103
- </Tabs.Content>
104
- <Tabs.Content value="agents" className="p-4 bg-card rounded-b-lg border border-t-0 border-border"><p className="text-muted-foreground">Agent management UI will be here.</p></Tabs.Content>
105
- <Tabs.Content value="files" className="p-4 bg-card rounded-b-lg border border-t-0 border-border"><p className="text-muted-foreground">File management UI will be here.</p></Tabs.Content>
106
- <Tabs.Content value="settings" className="p-4 bg-card rounded-b-lg border border-t-0 border-border"><p className="text-muted-foreground">Project settings UI will be here.</p></Tabs.Content>
107
- </Tabs.Root>
108
- </div>
109
- );
110
-
111
- // --- MAIN PAGE COMPONENT ---
3
+ import { useState, useMemo } from 'react';
4
+ import { useQuery, useMutation } from 'convex/react';
5
+ import { api } from '@convex/_generated/api';
6
+ import { FolderKanban, Plus, Trash2, Edit, Search, X } from 'lucide-react';
112
7
 
113
8
  export const Route = createFileRoute('/projects')({ component: ProjectsPage });
114
9
 
115
10
  function ProjectsPage() {
116
- const [projects, setProjects] = useState<Project[]>(initialProjects);
117
- const [isLoading, setIsLoading] = useState(false);
118
- const [error, setError] = useState<string | null>(null);
119
- const [searchQuery, setSearchQuery] = useState('');
120
- const [isModalOpen, setIsModalOpen] = useState(false);
121
- const [editingProject, setEditingProject] = useState<Project | null>(null);
122
- const [selectedProject, setSelectedProject] = useState<Project | null>(null);
123
-
124
- // --- Commented-out Convex Hooks ---
125
- /*
126
- const projectsData = useQuery(api.projects.list, { search: searchQuery });
11
+ const projects = useQuery(api.projects.list, {}) ?? [];
127
12
  const createProject = useMutation(api.projects.create);
128
13
  const updateProject = useMutation(api.projects.update);
129
- const deleteProject = useMutation(api.projects.delete);
14
+ const removeProject = useMutation(api.projects.remove);
130
15
 
131
- React.useEffect(() => {
132
- if (projectsData) {
133
- setProjects(projectsData);
16
+ const [searchQuery, setSearchQuery] = useState('');
17
+ const [isModalOpen, setIsModalOpen] = useState(false);
18
+ const [editingProject, setEditingProject] = useState<any>(null);
19
+
20
+ const filtered = useMemo(() => {
21
+ if (!searchQuery) return projects;
22
+ const q = searchQuery.toLowerCase();
23
+ return projects.filter((p: any) => p.name.toLowerCase().includes(q) || (p.description || '').toLowerCase().includes(q));
24
+ }, [projects, searchQuery]);
25
+
26
+ const handleSave = async (data: { name: string; description: string }) => {
27
+ if (editingProject) {
28
+ await updateProject({ id: editingProject._id, ...data });
29
+ } else {
30
+ await createProject(data);
134
31
  }
135
- }, [projectsData]);
136
- */
137
-
138
- const handleCreate = () => {
32
+ setIsModalOpen(false);
139
33
  setEditingProject(null);
140
- setIsModalOpen(true);
141
- };
142
-
143
- const handleEdit = (project: Project) => {
144
- setEditingProject(project);
145
- setIsModalOpen(true);
146
34
  };
147
35
 
148
- const handleDelete = (id: string) => {
149
- if (window.confirm('Are you sure you want to delete this project?')) {
150
- // await deleteProject({ id });
151
- setProjects(projects.filter(p => p.id !== id));
36
+ const handleDelete = async (id: any) => {
37
+ if (confirm('Delete this project?')) {
38
+ await removeProject({ id });
152
39
  }
153
40
  };
154
41
 
155
- const handleSave = (data: Omit<Project, 'id' | 'agentCount' | 'fileCount' | 'createdAt'>) => {
156
- setIsLoading(true);
157
- setTimeout(() => { // Simulate async operation
158
- if (editingProject) {
159
- // await updateProject({ id: editingProject.id, ...data });
160
- setProjects(projects.map(p => p.id === editingProject.id ? { ...p, ...data } : p));
161
- } else {
162
- const newProject: Project = {
163
- id: `proj_${Date.now()}`,
164
- ...data,
165
- agentCount: 0,
166
- fileCount: 0,
167
- createdAt: new Date().toISOString().split('T')[0],
168
- };
169
- // await createProject(newProject);
170
- setProjects([newProject, ...projects]);
171
- }
172
- setIsLoading(false);
173
- setIsModalOpen(false);
174
- setEditingProject(null);
175
- }, 500);
176
- };
177
-
178
- const filteredProjects = useMemo(() =>
179
- projects.filter(p =>
180
- p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
181
- p.description.toLowerCase().includes(searchQuery.toLowerCase())
182
- ), [projects, searchQuery]);
183
-
184
- if (selectedProject) {
185
- return (
186
- <DashboardLayout>
187
- <ProjectDetailView project={selectedProject} onBack={() => setSelectedProject(null)} />
188
- </DashboardLayout>
189
- );
190
- }
191
-
192
42
  return (
193
43
  <DashboardLayout>
194
- <div className="p-6 bg-background min-h-full">
195
- <div className="flex justify-between items-center mb-6">
196
- <h1 className="text-2xl font-bold text-foreground">Projects</h1>
197
- <button onClick={handleCreate} className="flex items-center gap-2 bg-primary text-primary-foreground px-4 py-2 rounded-md hover:bg-primary/90">
198
- <Plus className="w-5 h-5" />
199
- New Project
44
+ <div className="space-y-6">
45
+ <div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
46
+ <div>
47
+ <h1 className="text-3xl font-bold">Projects</h1>
48
+ <p className="text-muted-foreground">Organize your agents, files, and threads into workspaces.</p>
49
+ </div>
50
+ <button onClick={() => { setEditingProject(null); setIsModalOpen(true); }} className="bg-primary text-primary-foreground px-4 py-2 rounded-lg hover:bg-primary/90 flex items-center gap-2">
51
+ <Plus className="w-4 h-4" /> New Project
200
52
  </button>
201
53
  </div>
202
54
 
203
- <div className="mb-6 relative">
204
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
205
- <input
206
- type="text"
207
- placeholder="Search projects..."
208
- value={searchQuery}
209
- onChange={e => setSearchQuery(e.target.value)}
210
- className="w-full bg-card border border-border rounded-md pl-10 pr-4 py-2 text-foreground"
211
- />
55
+ <div className="relative max-w-sm">
56
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
57
+ <input type="text" placeholder="Search projects..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} className="pl-9 pr-3 py-2 bg-card border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary w-full" />
212
58
  </div>
213
59
 
214
- {isLoading && <p className="text-muted-foreground">Loading projects...</p>}
215
- {error && <p className="text-destructive">{error}</p>}
216
-
217
- {!isLoading && !error && (
218
- filteredProjects.length > 0 ? (
219
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
220
- {filteredProjects.map(project => (
221
- <ProjectCard key={project.id} project={project} onEdit={handleEdit} onDelete={handleDelete} onSelect={setSelectedProject} />
222
- ))}
223
- </div>
224
- ) : (
225
- <div className="text-center py-16 border-2 border-dashed border-border rounded-lg">
226
- <FolderKanban className="mx-auto w-12 h-12 text-muted-foreground" />
227
- <h3 className="mt-4 text-lg font-medium text-foreground">No projects found</h3>
228
- <p className="mt-1 text-sm text-muted-foreground">Get started by creating a new project.</p>
229
- <button onClick={handleCreate} className="mt-6 flex mx-auto items-center gap-2 bg-primary text-primary-foreground px-4 py-2 rounded-md hover:bg-primary/90">
230
- <Plus className="w-5 h-5" />
231
- New Project
60
+ {filtered.length === 0 ? (
61
+ <div className="text-center py-16 bg-card border border-border rounded-lg">
62
+ <FolderKanban className="w-16 h-16 text-muted-foreground/30 mx-auto mb-4" />
63
+ <h3 className="text-lg font-semibold mb-2">{projects.length === 0 ? 'No projects yet' : 'No matching projects'}</h3>
64
+ <p className="text-muted-foreground mb-4">Create a project to organize your work.</p>
65
+ {projects.length === 0 && (
66
+ <button onClick={() => setIsModalOpen(true)} className="bg-primary text-primary-foreground px-4 py-2 rounded-lg hover:bg-primary/90">
67
+ <Plus className="w-4 h-4 inline mr-2" />Create Project
232
68
  </button>
233
- </div>
234
- )
69
+ )}
70
+ </div>
71
+ ) : (
72
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
73
+ {filtered.map((project: any) => (
74
+ <div key={project._id} className="bg-card border border-border rounded-lg p-5 shadow-sm hover:shadow-md transition-shadow">
75
+ <div className="flex items-start justify-between mb-3">
76
+ <div className="flex items-center gap-2">
77
+ <FolderKanban className="w-5 h-5 text-primary" />
78
+ <h3 className="font-semibold text-foreground">{project.name}</h3>
79
+ </div>
80
+ </div>
81
+ <p className="text-sm text-muted-foreground mb-4 line-clamp-2">{project.description || 'No description'}</p>
82
+ <div className="text-xs text-muted-foreground mb-4">Created {new Date(project.createdAt).toLocaleDateString()}</div>
83
+ <div className="flex items-center justify-between pt-3 border-t border-border">
84
+ <button onClick={() => { setEditingProject(project); setIsModalOpen(true); }} className="p-1.5 rounded hover:bg-muted flex items-center gap-1 text-xs text-muted-foreground"><Edit className="w-4 h-4" /> Edit</button>
85
+ <button onClick={() => handleDelete(project._id)} className="p-1.5 rounded hover:bg-destructive/10"><Trash2 className="w-4 h-4 text-destructive" /></button>
86
+ </div>
87
+ </div>
88
+ ))}
89
+ </div>
235
90
  )}
236
91
 
237
- <Dialog.Root open={isModalOpen} onOpenChange={setIsModalOpen}>
238
- <Dialog.Portal>
239
- <Dialog.Overlay className="fixed inset-0 bg-black/50" />
240
- <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] max-w-md bg-card p-6 rounded-lg shadow-lg border border-border">
241
- <Dialog.Title className="text-lg font-bold text-foreground mb-4">{editingProject ? 'Edit Project' : 'Create New Project'}</Dialog.Title>
242
- <ProjectForm project={editingProject} onSave={handleSave} onCancel={() => setIsModalOpen(false)} />
243
- <Dialog.Close asChild>
244
- <button className="absolute top-4 right-4 text-muted-foreground hover:text-foreground">
245
- <X className="w-5 h-5" />
246
- </button>
247
- </Dialog.Close>
248
- </Dialog.Content>
249
- </Dialog.Portal>
250
- </Dialog.Root>
92
+ {isModalOpen && (
93
+ <div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4">
94
+ <div className="bg-card border border-border rounded-lg shadow-xl w-full max-w-md">
95
+ <div className="flex justify-between items-center p-4 border-b border-border">
96
+ <h2 className="text-lg font-bold">{editingProject ? 'Edit Project' : 'New Project'}</h2>
97
+ <button onClick={() => { setIsModalOpen(false); setEditingProject(null); }} className="text-muted-foreground hover:text-foreground"><X className="h-5 w-5" /></button>
98
+ </div>
99
+ <ProjectForm initial={editingProject} onSave={handleSave} onClose={() => { setIsModalOpen(false); setEditingProject(null); }} />
100
+ </div>
101
+ </div>
102
+ )}
251
103
  </div>
252
104
  </DashboardLayout>
253
105
  );
254
106
  }
107
+
108
+ function ProjectForm({ initial, onSave, onClose }: { initial: any; onSave: (data: any) => void; onClose: () => void }) {
109
+ const [name, setName] = useState(initial?.name || '');
110
+ const [description, setDescription] = useState(initial?.description || '');
111
+
112
+ const handleSubmit = (e: any) => {
113
+ e.preventDefault();
114
+ onSave({ name, description });
115
+ };
116
+
117
+ return (
118
+ <form onSubmit={handleSubmit}>
119
+ <div className="p-6 space-y-4">
120
+ <div>
121
+ <label className="block text-sm font-medium mb-1">Name</label>
122
+ <input type="text" value={name} onChange={(e) => setName(e.target.value)} className="w-full bg-background border border-border rounded-md px-3 py-2 text-sm" placeholder="Project name" required />
123
+ </div>
124
+ <div>
125
+ <label className="block text-sm font-medium mb-1">Description</label>
126
+ <textarea value={description} onChange={(e) => setDescription(e.target.value)} rows={3} className="w-full bg-background border border-border rounded-md px-3 py-2 text-sm" placeholder="What is this project about?" />
127
+ </div>
128
+ </div>
129
+ <div className="p-4 border-t border-border flex justify-end gap-2">
130
+ <button type="button" onClick={onClose} className="px-4 py-2 rounded-lg bg-muted text-muted-foreground text-sm">Cancel</button>
131
+ <button type="submit" disabled={!name.trim()} className="px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm hover:bg-primary/90 disabled:opacity-50">Save</button>
132
+ </div>
133
+ </form>
134
+ );
135
+ }