@agentforge-ai/cli 0.3.2 → 0.4.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.
- package/dist/default/.env.example +46 -6
- package/dist/default/README.md +89 -9
- package/dist/default/convex/schema.ts +248 -4
- package/dist/default/dashboard/app/components/DashboardLayout.tsx +245 -0
- package/dist/default/dashboard/app/components/ui/badge.tsx +26 -0
- package/dist/default/dashboard/app/components/ui/button.tsx +41 -0
- package/dist/default/dashboard/app/components/ui/card.tsx +44 -0
- package/dist/default/dashboard/app/components/ui/dialog.tsx +66 -0
- package/dist/default/dashboard/app/components/ui/input.tsx +21 -0
- package/dist/default/dashboard/app/components/ui/label.tsx +18 -0
- package/dist/default/dashboard/app/components/ui/select.tsx +75 -0
- package/dist/default/dashboard/app/components/ui/sheet.tsx +73 -0
- package/dist/default/dashboard/app/components/ui/switch.tsx +34 -0
- package/dist/default/dashboard/app/components/ui/table.tsx +60 -0
- package/dist/default/dashboard/app/components/ui/tabs.tsx +50 -0
- package/dist/default/dashboard/app/components/ui/tooltip.tsx +23 -0
- package/dist/default/dashboard/app/lib/utils.ts +6 -0
- package/dist/default/dashboard/app/main.tsx +35 -0
- package/dist/default/dashboard/app/routeTree.gen.ts +352 -0
- package/dist/default/dashboard/app/routes/__root.tsx +10 -0
- package/dist/default/dashboard/app/routes/agents.tsx +255 -0
- package/dist/default/dashboard/app/routes/chat.tsx +427 -0
- package/dist/default/dashboard/app/routes/connections.tsx +413 -0
- package/dist/default/dashboard/app/routes/cron.tsx +322 -0
- package/dist/default/dashboard/app/routes/files.tsx +203 -0
- package/dist/default/dashboard/app/routes/index.tsx +141 -0
- package/dist/default/dashboard/app/routes/projects.tsx +254 -0
- package/dist/default/dashboard/app/routes/sessions.tsx +272 -0
- package/dist/default/dashboard/app/routes/settings.tsx +583 -0
- package/dist/default/dashboard/app/routes/skills.tsx +252 -0
- package/dist/default/dashboard/app/routes/usage.tsx +181 -0
- package/dist/default/dashboard/app/styles/globals.css +93 -0
- package/dist/default/dashboard/index.html +13 -0
- package/dist/default/dashboard/package.json +36 -0
- package/dist/default/dashboard/postcss.config.js +6 -0
- package/dist/default/dashboard/tailwind.config.js +50 -0
- package/dist/default/dashboard/tsconfig.json +24 -0
- package/dist/default/dashboard/vite.config.ts +16 -0
- package/dist/default/package.json +8 -3
- package/dist/default/skills/skill-creator/SKILL.md +270 -0
- package/dist/default/skills/skill-creator/config.json +11 -0
- package/dist/default/skills/skill-creator/index.ts +392 -0
- package/dist/default/src/agent.ts +85 -5
- package/dist/index.js +1574 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/templates/default/.env.example +46 -6
- package/templates/default/README.md +89 -9
- package/templates/default/convex/schema.ts +248 -4
- package/templates/default/dashboard/app/components/DashboardLayout.tsx +245 -0
- package/templates/default/dashboard/app/components/ui/badge.tsx +26 -0
- package/templates/default/dashboard/app/components/ui/button.tsx +41 -0
- package/templates/default/dashboard/app/components/ui/card.tsx +44 -0
- package/templates/default/dashboard/app/components/ui/dialog.tsx +66 -0
- package/templates/default/dashboard/app/components/ui/input.tsx +21 -0
- package/templates/default/dashboard/app/components/ui/label.tsx +18 -0
- package/templates/default/dashboard/app/components/ui/select.tsx +75 -0
- package/templates/default/dashboard/app/components/ui/sheet.tsx +73 -0
- package/templates/default/dashboard/app/components/ui/switch.tsx +34 -0
- package/templates/default/dashboard/app/components/ui/table.tsx +60 -0
- package/templates/default/dashboard/app/components/ui/tabs.tsx +50 -0
- package/templates/default/dashboard/app/components/ui/tooltip.tsx +23 -0
- package/templates/default/dashboard/app/lib/utils.ts +6 -0
- package/templates/default/dashboard/app/main.tsx +35 -0
- package/templates/default/dashboard/app/routeTree.gen.ts +352 -0
- package/templates/default/dashboard/app/routes/__root.tsx +10 -0
- package/templates/default/dashboard/app/routes/agents.tsx +255 -0
- package/templates/default/dashboard/app/routes/chat.tsx +427 -0
- package/templates/default/dashboard/app/routes/connections.tsx +413 -0
- package/templates/default/dashboard/app/routes/cron.tsx +322 -0
- package/templates/default/dashboard/app/routes/files.tsx +203 -0
- package/templates/default/dashboard/app/routes/index.tsx +141 -0
- package/templates/default/dashboard/app/routes/projects.tsx +254 -0
- package/templates/default/dashboard/app/routes/sessions.tsx +272 -0
- package/templates/default/dashboard/app/routes/settings.tsx +583 -0
- package/templates/default/dashboard/app/routes/skills.tsx +252 -0
- package/templates/default/dashboard/app/routes/usage.tsx +181 -0
- package/templates/default/dashboard/app/styles/globals.css +93 -0
- package/templates/default/dashboard/index.html +13 -0
- package/templates/default/dashboard/package.json +36 -0
- package/templates/default/dashboard/postcss.config.js +6 -0
- package/templates/default/dashboard/tailwind.config.js +50 -0
- package/templates/default/dashboard/tsconfig.json +24 -0
- package/templates/default/dashboard/vite.config.ts +16 -0
- package/templates/default/package.json +8 -3
- package/templates/default/skills/skill-creator/SKILL.md +270 -0
- package/templates/default/skills/skill-creator/config.json +11 -0
- package/templates/default/skills/skill-creator/index.ts +392 -0
- package/templates/default/src/agent.ts +85 -5
|
@@ -1,19 +1,53 @@
|
|
|
1
1
|
import { defineSchema, defineTable } from "convex/server";
|
|
2
2
|
import { v } from "convex/values";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* AgentForge Database Schema
|
|
6
|
+
*
|
|
7
|
+
* This schema defines all the tables needed for your AgentForge project.
|
|
8
|
+
* Customize it to fit your needs — add new tables, fields, or indexes.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: Index names cannot be "by_id" or "by_creation_time" (reserved by Convex).
|
|
11
|
+
* Use camelCase names like "byAgentId", "byUserId", etc.
|
|
12
|
+
*/
|
|
4
13
|
export default defineSchema({
|
|
14
|
+
// ─── Agent Definitions ───────────────────────────────────────────────
|
|
5
15
|
agents: defineTable({
|
|
6
16
|
id: v.string(),
|
|
7
17
|
name: v.string(),
|
|
18
|
+
description: v.optional(v.string()),
|
|
8
19
|
instructions: v.string(),
|
|
9
20
|
model: v.string(),
|
|
21
|
+
provider: v.string(),
|
|
10
22
|
tools: v.optional(v.any()),
|
|
11
|
-
|
|
23
|
+
temperature: v.optional(v.number()),
|
|
24
|
+
maxTokens: v.optional(v.number()),
|
|
25
|
+
topP: v.optional(v.number()),
|
|
26
|
+
isActive: v.boolean(),
|
|
27
|
+
createdAt: v.number(),
|
|
28
|
+
updatedAt: v.number(),
|
|
29
|
+
userId: v.optional(v.string()),
|
|
30
|
+
})
|
|
31
|
+
.index("byAgentId", ["id"])
|
|
32
|
+
.index("byUserId", ["userId"])
|
|
33
|
+
.index("byIsActive", ["isActive"]),
|
|
12
34
|
|
|
35
|
+
// ─── Conversation Threads ────────────────────────────────────────────
|
|
13
36
|
threads: defineTable({
|
|
14
37
|
name: v.optional(v.string()),
|
|
15
|
-
|
|
38
|
+
agentId: v.string(),
|
|
39
|
+
userId: v.optional(v.string()),
|
|
40
|
+
projectId: v.optional(v.string()),
|
|
41
|
+
status: v.string(),
|
|
42
|
+
metadata: v.optional(v.any()),
|
|
43
|
+
createdAt: v.number(),
|
|
44
|
+
updatedAt: v.number(),
|
|
45
|
+
})
|
|
46
|
+
.index("byAgentId", ["agentId"])
|
|
47
|
+
.index("byUserId", ["userId"])
|
|
48
|
+
.index("byStatus", ["status"]),
|
|
16
49
|
|
|
50
|
+
// ─── Messages ────────────────────────────────────────────────────────
|
|
17
51
|
messages: defineTable({
|
|
18
52
|
threadId: v.id("threads"),
|
|
19
53
|
role: v.union(
|
|
@@ -23,6 +57,216 @@ export default defineSchema({
|
|
|
23
57
|
v.literal("tool")
|
|
24
58
|
),
|
|
25
59
|
content: v.string(),
|
|
26
|
-
|
|
27
|
-
|
|
60
|
+
toolCalls: v.optional(v.any()),
|
|
61
|
+
toolResults: v.optional(v.any()),
|
|
62
|
+
tokenUsage: v.optional(v.any()),
|
|
63
|
+
model: v.optional(v.string()),
|
|
64
|
+
provider: v.optional(v.string()),
|
|
65
|
+
timestamp: v.number(),
|
|
66
|
+
})
|
|
67
|
+
.index("byThreadId", ["threadId"])
|
|
68
|
+
.index("byTimestamp", ["timestamp"]),
|
|
69
|
+
|
|
70
|
+
// ─── Sessions ────────────────────────────────────────────────────────
|
|
71
|
+
sessions: defineTable({
|
|
72
|
+
name: v.string(),
|
|
73
|
+
agentId: v.string(),
|
|
74
|
+
threadId: v.optional(v.id("threads")),
|
|
75
|
+
status: v.string(),
|
|
76
|
+
userId: v.optional(v.string()),
|
|
77
|
+
startedAt: v.number(),
|
|
78
|
+
lastActivityAt: v.number(),
|
|
79
|
+
metadata: v.optional(v.any()),
|
|
80
|
+
})
|
|
81
|
+
.index("byAgentId", ["agentId"])
|
|
82
|
+
.index("byUserId", ["userId"])
|
|
83
|
+
.index("byStatus", ["status"]),
|
|
84
|
+
|
|
85
|
+
// ─── Files ───────────────────────────────────────────────────────────
|
|
86
|
+
files: defineTable({
|
|
87
|
+
name: v.string(),
|
|
88
|
+
folderId: v.optional(v.string()),
|
|
89
|
+
mimeType: v.string(),
|
|
90
|
+
size: v.number(),
|
|
91
|
+
storageId: v.optional(v.string()),
|
|
92
|
+
url: v.optional(v.string()),
|
|
93
|
+
userId: v.optional(v.string()),
|
|
94
|
+
projectId: v.optional(v.string()),
|
|
95
|
+
createdAt: v.number(),
|
|
96
|
+
})
|
|
97
|
+
.index("byFolderId", ["folderId"])
|
|
98
|
+
.index("byUserId", ["userId"])
|
|
99
|
+
.index("byProjectId", ["projectId"]),
|
|
100
|
+
|
|
101
|
+
// ─── Folders ─────────────────────────────────────────────────────────
|
|
102
|
+
folders: defineTable({
|
|
103
|
+
name: v.string(),
|
|
104
|
+
parentId: v.optional(v.string()),
|
|
105
|
+
userId: v.optional(v.string()),
|
|
106
|
+
projectId: v.optional(v.string()),
|
|
107
|
+
createdAt: v.number(),
|
|
108
|
+
})
|
|
109
|
+
.index("byParentId", ["parentId"])
|
|
110
|
+
.index("byUserId", ["userId"]),
|
|
111
|
+
|
|
112
|
+
// ─── Projects / Workspaces ───────────────────────────────────────────
|
|
113
|
+
projects: defineTable({
|
|
114
|
+
name: v.string(),
|
|
115
|
+
description: v.optional(v.string()),
|
|
116
|
+
status: v.string(),
|
|
117
|
+
userId: v.optional(v.string()),
|
|
118
|
+
settings: v.optional(v.any()),
|
|
119
|
+
createdAt: v.number(),
|
|
120
|
+
updatedAt: v.number(),
|
|
121
|
+
})
|
|
122
|
+
.index("byUserId", ["userId"])
|
|
123
|
+
.index("byStatus", ["status"]),
|
|
124
|
+
|
|
125
|
+
// ─── Skills ──────────────────────────────────────────────────────────
|
|
126
|
+
skills: defineTable({
|
|
127
|
+
name: v.string(),
|
|
128
|
+
description: v.optional(v.string()),
|
|
129
|
+
category: v.string(),
|
|
130
|
+
version: v.string(),
|
|
131
|
+
isInstalled: v.boolean(),
|
|
132
|
+
configuration: v.optional(v.any()),
|
|
133
|
+
agentId: v.optional(v.string()),
|
|
134
|
+
userId: v.optional(v.string()),
|
|
135
|
+
createdAt: v.number(),
|
|
136
|
+
updatedAt: v.number(),
|
|
137
|
+
})
|
|
138
|
+
.index("byAgentId", ["agentId"])
|
|
139
|
+
.index("byCategory", ["category"])
|
|
140
|
+
.index("byIsInstalled", ["isInstalled"]),
|
|
141
|
+
|
|
142
|
+
// ─── Cron Jobs ───────────────────────────────────────────────────────
|
|
143
|
+
cronJobs: defineTable({
|
|
144
|
+
name: v.string(),
|
|
145
|
+
schedule: v.string(),
|
|
146
|
+
agentId: v.string(),
|
|
147
|
+
action: v.string(),
|
|
148
|
+
isEnabled: v.boolean(),
|
|
149
|
+
lastRunAt: v.optional(v.number()),
|
|
150
|
+
nextRunAt: v.optional(v.number()),
|
|
151
|
+
userId: v.optional(v.string()),
|
|
152
|
+
createdAt: v.number(),
|
|
153
|
+
updatedAt: v.number(),
|
|
154
|
+
})
|
|
155
|
+
.index("byAgentId", ["agentId"])
|
|
156
|
+
.index("byIsEnabled", ["isEnabled"])
|
|
157
|
+
.index("byUserId", ["userId"]),
|
|
158
|
+
|
|
159
|
+
// ─── MCP Connections ─────────────────────────────────────────────────
|
|
160
|
+
mcpConnections: defineTable({
|
|
161
|
+
name: v.string(),
|
|
162
|
+
type: v.string(),
|
|
163
|
+
endpoint: v.string(),
|
|
164
|
+
isConnected: v.boolean(),
|
|
165
|
+
isEnabled: v.boolean(),
|
|
166
|
+
credentials: v.optional(v.any()),
|
|
167
|
+
capabilities: v.optional(v.any()),
|
|
168
|
+
userId: v.optional(v.string()),
|
|
169
|
+
lastConnectedAt: v.optional(v.number()),
|
|
170
|
+
createdAt: v.number(),
|
|
171
|
+
updatedAt: v.number(),
|
|
172
|
+
})
|
|
173
|
+
.index("byUserId", ["userId"])
|
|
174
|
+
.index("byIsEnabled", ["isEnabled"]),
|
|
175
|
+
|
|
176
|
+
// ─── API Keys ────────────────────────────────────────────────────────
|
|
177
|
+
apiKeys: defineTable({
|
|
178
|
+
provider: v.string(),
|
|
179
|
+
keyName: v.string(),
|
|
180
|
+
encryptedKey: v.string(),
|
|
181
|
+
isActive: v.boolean(),
|
|
182
|
+
userId: v.optional(v.string()),
|
|
183
|
+
createdAt: v.number(),
|
|
184
|
+
lastUsedAt: v.optional(v.number()),
|
|
185
|
+
})
|
|
186
|
+
.index("byProvider", ["provider"])
|
|
187
|
+
.index("byUserId", ["userId"])
|
|
188
|
+
.index("byIsActive", ["isActive"]),
|
|
189
|
+
|
|
190
|
+
// ─── Usage Tracking ──────────────────────────────────────────────────
|
|
191
|
+
usage: defineTable({
|
|
192
|
+
agentId: v.string(),
|
|
193
|
+
sessionId: v.optional(v.string()),
|
|
194
|
+
provider: v.string(),
|
|
195
|
+
model: v.string(),
|
|
196
|
+
promptTokens: v.number(),
|
|
197
|
+
completionTokens: v.number(),
|
|
198
|
+
totalTokens: v.number(),
|
|
199
|
+
cost: v.optional(v.number()),
|
|
200
|
+
userId: v.optional(v.string()),
|
|
201
|
+
timestamp: v.number(),
|
|
202
|
+
})
|
|
203
|
+
.index("byAgentId", ["agentId"])
|
|
204
|
+
.index("byUserId", ["userId"])
|
|
205
|
+
.index("byTimestamp", ["timestamp"])
|
|
206
|
+
.index("byProvider", ["provider"]),
|
|
207
|
+
|
|
208
|
+
// ─── Settings ────────────────────────────────────────────────────────
|
|
209
|
+
settings: defineTable({
|
|
210
|
+
userId: v.string(),
|
|
211
|
+
key: v.string(),
|
|
212
|
+
value: v.any(),
|
|
213
|
+
updatedAt: v.number(),
|
|
214
|
+
})
|
|
215
|
+
.index("byUserId", ["userId"])
|
|
216
|
+
.index("byUserIdAndKey", ["userId", "key"]),
|
|
217
|
+
|
|
218
|
+
// ─── System Logs ─────────────────────────────────────────────────────
|
|
219
|
+
logs: defineTable({
|
|
220
|
+
level: v.union(
|
|
221
|
+
v.literal("debug"),
|
|
222
|
+
v.literal("info"),
|
|
223
|
+
v.literal("warn"),
|
|
224
|
+
v.literal("error")
|
|
225
|
+
),
|
|
226
|
+
source: v.string(),
|
|
227
|
+
message: v.string(),
|
|
228
|
+
metadata: v.optional(v.any()),
|
|
229
|
+
userId: v.optional(v.string()),
|
|
230
|
+
timestamp: v.number(),
|
|
231
|
+
})
|
|
232
|
+
.index("byLevel", ["level"])
|
|
233
|
+
.index("bySource", ["source"])
|
|
234
|
+
.index("byTimestamp", ["timestamp"]),
|
|
235
|
+
|
|
236
|
+
// ─── Heartbeat (Task Continuation) ───────────────────────────────────
|
|
237
|
+
heartbeats: defineTable({
|
|
238
|
+
agentId: v.string(),
|
|
239
|
+
threadId: v.optional(v.id("threads")),
|
|
240
|
+
status: v.string(),
|
|
241
|
+
currentTask: v.optional(v.string()),
|
|
242
|
+
pendingTasks: v.array(v.string()),
|
|
243
|
+
context: v.string(),
|
|
244
|
+
lastCheck: v.number(),
|
|
245
|
+
nextCheck: v.number(),
|
|
246
|
+
metadata: v.optional(v.any()),
|
|
247
|
+
})
|
|
248
|
+
.index("byAgentId", ["agentId"])
|
|
249
|
+
.index("byStatus", ["status"])
|
|
250
|
+
.index("byNextCheck", ["nextCheck"]),
|
|
251
|
+
|
|
252
|
+
// ─── Secure Vault ────────────────────────────────────────────────────
|
|
253
|
+
vault: defineTable({
|
|
254
|
+
name: v.string(),
|
|
255
|
+
category: v.string(),
|
|
256
|
+
provider: v.optional(v.string()),
|
|
257
|
+
encryptedValue: v.string(),
|
|
258
|
+
iv: v.string(),
|
|
259
|
+
maskedValue: v.string(),
|
|
260
|
+
isActive: v.boolean(),
|
|
261
|
+
expiresAt: v.optional(v.number()),
|
|
262
|
+
lastAccessedAt: v.optional(v.number()),
|
|
263
|
+
accessCount: v.number(),
|
|
264
|
+
userId: v.optional(v.string()),
|
|
265
|
+
createdAt: v.number(),
|
|
266
|
+
updatedAt: v.number(),
|
|
267
|
+
})
|
|
268
|
+
.index("byUserId", ["userId"])
|
|
269
|
+
.index("byCategory", ["category"])
|
|
270
|
+
.index("byProvider", ["provider"])
|
|
271
|
+
.index("byIsActive", ["isActive"]),
|
|
28
272
|
});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Link, useRouterState } from "@tanstack/react-router";
|
|
3
|
+
import {
|
|
4
|
+
MessageSquare, LayoutDashboard, Radio, Server, Activity, Clock,
|
|
5
|
+
Bot, Sparkles, Network, Settings, Bug, FileText, Menu, X,
|
|
6
|
+
ChevronLeft, ChevronRight, User, Heart, FolderKanban, Folder,
|
|
7
|
+
} from "lucide-react";
|
|
8
|
+
|
|
9
|
+
const navItems = [
|
|
10
|
+
{
|
|
11
|
+
section: "Chat",
|
|
12
|
+
items: [
|
|
13
|
+
{ href: "/chat", label: "Chat", icon: MessageSquare },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
section: "Control",
|
|
18
|
+
items: [
|
|
19
|
+
{ href: "/", label: "Overview", icon: LayoutDashboard },
|
|
20
|
+
{ href: "/sessions", label: "Sessions", icon: Activity },
|
|
21
|
+
{ href: "/usage", label: "Usage", icon: Clock },
|
|
22
|
+
{ href: "/cron", label: "Cron Jobs", icon: Clock },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
section: "Agent",
|
|
27
|
+
items: [
|
|
28
|
+
{ href: "/agents", label: "Agents", icon: Bot },
|
|
29
|
+
{ href: "/skills", label: "Skills", icon: Sparkles },
|
|
30
|
+
{ href: "/connections", label: "Connections", icon: Network },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
section: "Workspace",
|
|
35
|
+
items: [
|
|
36
|
+
{ href: "/projects", label: "Projects", icon: FolderKanban },
|
|
37
|
+
{ href: "/files", label: "Files", icon: Folder },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
section: "Settings",
|
|
42
|
+
items: [
|
|
43
|
+
{ href: "/settings", label: "Config", icon: Settings },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const HealthStatus = () => {
|
|
49
|
+
const [isOnline, setIsOnline] = useState(true);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const interval = setInterval(() => {
|
|
53
|
+
// In production, poll the Convex backend heartbeat
|
|
54
|
+
}, 30000);
|
|
55
|
+
return () => clearInterval(interval);
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
60
|
+
<span className="relative flex h-2 w-2">
|
|
61
|
+
<span
|
|
62
|
+
className={`animate-ping absolute inline-flex h-full w-full rounded-full ${
|
|
63
|
+
isOnline ? "bg-green-400" : "bg-red-400"
|
|
64
|
+
} opacity-75`}
|
|
65
|
+
></span>
|
|
66
|
+
<span
|
|
67
|
+
className={`relative inline-flex rounded-full h-2 w-2 ${
|
|
68
|
+
isOnline ? "bg-green-500" : "bg-red-500"
|
|
69
|
+
}`}
|
|
70
|
+
></span>
|
|
71
|
+
</span>
|
|
72
|
+
{isOnline ? "Online" : "Offline"}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const Breadcrumb = () => {
|
|
78
|
+
const { location } = useRouterState();
|
|
79
|
+
const pathnames = location.pathname.split("/").filter((x) => x);
|
|
80
|
+
|
|
81
|
+
if (pathnames.length === 0) {
|
|
82
|
+
return <span className="text-sm font-medium">Overview</span>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<nav className="flex" aria-label="Breadcrumb">
|
|
87
|
+
<ol className="inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse">
|
|
88
|
+
<li className="inline-flex items-center">
|
|
89
|
+
<Link
|
|
90
|
+
to="/"
|
|
91
|
+
className="inline-flex items-center text-sm font-medium text-muted-foreground hover:text-foreground"
|
|
92
|
+
>
|
|
93
|
+
<LayoutDashboard className="w-4 h-4 me-2.5" />
|
|
94
|
+
Home
|
|
95
|
+
</Link>
|
|
96
|
+
</li>
|
|
97
|
+
{pathnames.map((value, index) => {
|
|
98
|
+
const to = `/${pathnames.slice(0, index + 1).join("/")}`;
|
|
99
|
+
const isLast = index === pathnames.length - 1;
|
|
100
|
+
return (
|
|
101
|
+
<li key={to}>
|
|
102
|
+
<div className="flex items-center">
|
|
103
|
+
<ChevronRight className="w-4 h-4 text-muted-foreground" />
|
|
104
|
+
<Link
|
|
105
|
+
to={to}
|
|
106
|
+
className={`ms-1 text-sm font-medium ${
|
|
107
|
+
isLast
|
|
108
|
+
? "text-foreground"
|
|
109
|
+
: "text-muted-foreground hover:text-foreground"
|
|
110
|
+
} md:ms-2`}
|
|
111
|
+
>
|
|
112
|
+
{value.charAt(0).toUpperCase() + value.slice(1)}
|
|
113
|
+
</Link>
|
|
114
|
+
</div>
|
|
115
|
+
</li>
|
|
116
|
+
);
|
|
117
|
+
})}
|
|
118
|
+
</ol>
|
|
119
|
+
</nav>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
124
|
+
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
|
125
|
+
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
126
|
+
const { location } = useRouterState();
|
|
127
|
+
|
|
128
|
+
const NavLink = ({ item }: { item: any }) => {
|
|
129
|
+
const isActive = location.pathname === item.href;
|
|
130
|
+
return (
|
|
131
|
+
<Link
|
|
132
|
+
to={item.href}
|
|
133
|
+
className={`flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors ${
|
|
134
|
+
isActive
|
|
135
|
+
? "bg-primary text-primary-foreground"
|
|
136
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
137
|
+
}`}
|
|
138
|
+
onClick={() => setIsMobileMenuOpen(false)}
|
|
139
|
+
>
|
|
140
|
+
<item.icon className="w-5 h-5 mr-3" />
|
|
141
|
+
<span>{item.label}</span>
|
|
142
|
+
</Link>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const sidebarContent = (
|
|
147
|
+
<div className="flex flex-col h-full">
|
|
148
|
+
<div className="flex items-center justify-between h-16 px-4 border-b border-border">
|
|
149
|
+
<Link to="/" className="flex items-center gap-2 text-lg font-bold">
|
|
150
|
+
<Bot className="w-7 h-7 text-primary" />
|
|
151
|
+
<span>AgentForge</span>
|
|
152
|
+
</Link>
|
|
153
|
+
</div>
|
|
154
|
+
<nav className="flex-1 px-2 py-4 space-y-4 overflow-y-auto">
|
|
155
|
+
{navItems.map((section) => (
|
|
156
|
+
<div key={section.section}>
|
|
157
|
+
<h3 className="px-3 mb-2 text-xs font-semibold tracking-wider text-muted-foreground/80 uppercase">
|
|
158
|
+
{section.section}
|
|
159
|
+
</h3>
|
|
160
|
+
<div className="space-y-1">
|
|
161
|
+
{section.items.map((item) => (
|
|
162
|
+
<NavLink key={item.href} item={item} />
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
))}
|
|
167
|
+
</nav>
|
|
168
|
+
<div className="p-4 border-t border-border">
|
|
169
|
+
<HealthStatus />
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div className="flex h-screen bg-background text-foreground">
|
|
176
|
+
{/* Desktop Sidebar */}
|
|
177
|
+
<aside
|
|
178
|
+
className={`hidden md:block bg-card border-r border-border transition-all duration-300 ease-in-out ${
|
|
179
|
+
isSidebarOpen ? "w-64" : "w-0 overflow-hidden"
|
|
180
|
+
}`}
|
|
181
|
+
>
|
|
182
|
+
{sidebarContent}
|
|
183
|
+
</aside>
|
|
184
|
+
|
|
185
|
+
{/* Mobile Sidebar Overlay */}
|
|
186
|
+
{isMobileMenuOpen && (
|
|
187
|
+
<div
|
|
188
|
+
className="fixed inset-0 z-40 flex md:hidden"
|
|
189
|
+
role="dialog"
|
|
190
|
+
aria-modal="true"
|
|
191
|
+
>
|
|
192
|
+
<div
|
|
193
|
+
className="fixed inset-0 bg-black/60"
|
|
194
|
+
aria-hidden="true"
|
|
195
|
+
onClick={() => setIsMobileMenuOpen(false)}
|
|
196
|
+
></div>
|
|
197
|
+
<div className="relative flex flex-col flex-1 w-full max-w-xs bg-card">
|
|
198
|
+
<div className="absolute top-0 right-0 pt-2 -mr-12">
|
|
199
|
+
<button
|
|
200
|
+
type="button"
|
|
201
|
+
className="flex items-center justify-center w-10 h-10 ml-1 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
|
202
|
+
onClick={() => setIsMobileMenuOpen(false)}
|
|
203
|
+
>
|
|
204
|
+
<span className="sr-only">Close sidebar</span>
|
|
205
|
+
<X className="w-6 h-6 text-white" />
|
|
206
|
+
</button>
|
|
207
|
+
</div>
|
|
208
|
+
{sidebarContent}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
<div className="flex flex-col flex-1 min-w-0">
|
|
214
|
+
<header className="flex items-center justify-between h-16 px-4 bg-card border-b border-border md:px-6">
|
|
215
|
+
<div className="flex items-center gap-4">
|
|
216
|
+
<button
|
|
217
|
+
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
|
218
|
+
className="hidden p-2 rounded-md md:block hover:bg-muted"
|
|
219
|
+
>
|
|
220
|
+
{isSidebarOpen ? (
|
|
221
|
+
<ChevronLeft className="w-5 h-5" />
|
|
222
|
+
) : (
|
|
223
|
+
<ChevronRight className="w-5 h-5" />
|
|
224
|
+
)}
|
|
225
|
+
</button>
|
|
226
|
+
<button
|
|
227
|
+
onClick={() => setIsMobileMenuOpen(true)}
|
|
228
|
+
className="p-2 rounded-md md:hidden hover:bg-muted"
|
|
229
|
+
>
|
|
230
|
+
<Menu className="w-5 h-5" />
|
|
231
|
+
</button>
|
|
232
|
+
<Breadcrumb />
|
|
233
|
+
</div>
|
|
234
|
+
<div className="flex items-center gap-4">
|
|
235
|
+
<HealthStatus />
|
|
236
|
+
<div className="p-2 rounded-full bg-muted">
|
|
237
|
+
<User className="w-5 h-5 text-muted-foreground" />
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</header>
|
|
241
|
+
<main className="flex-1 p-4 overflow-y-auto md:p-6">{children}</main>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
|
|
4
|
+
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
variant?: "default" | "secondary" | "destructive" | "outline";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const variants: Record<string, string> = {
|
|
9
|
+
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
|
10
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
11
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
12
|
+
outline: "text-foreground",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function Badge({ className, variant = "default", ...props }: BadgeProps) {
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
className={cn(
|
|
19
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
20
|
+
variants[variant],
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
|
|
4
|
+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
|
6
|
+
size?: "default" | "sm" | "lg" | "icon";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const variants: Record<string, string> = {
|
|
10
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
11
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
12
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
13
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
14
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
15
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const sizes: Record<string, string> = {
|
|
19
|
+
default: "h-10 px-4 py-2",
|
|
20
|
+
sm: "h-9 rounded-md px-3",
|
|
21
|
+
lg: "h-11 rounded-md px-8",
|
|
22
|
+
icon: "h-10 w-10",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
26
|
+
({ className, variant = "default", size = "default", ...props }, ref) => {
|
|
27
|
+
return (
|
|
28
|
+
<button
|
|
29
|
+
className={cn(
|
|
30
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
31
|
+
variants[variant],
|
|
32
|
+
sizes[size],
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
ref={ref}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
Button.displayName = "Button";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
|
|
4
|
+
export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
5
|
+
({ className, ...props }, ref) => (
|
|
6
|
+
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
|
|
7
|
+
)
|
|
8
|
+
);
|
|
9
|
+
Card.displayName = "Card";
|
|
10
|
+
|
|
11
|
+
export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
12
|
+
({ className, ...props }, ref) => (
|
|
13
|
+
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
CardHeader.displayName = "CardHeader";
|
|
17
|
+
|
|
18
|
+
export const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
19
|
+
({ className, ...props }, ref) => (
|
|
20
|
+
<h3 ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} />
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
CardTitle.displayName = "CardTitle";
|
|
24
|
+
|
|
25
|
+
export const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
26
|
+
({ className, ...props }, ref) => (
|
|
27
|
+
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
CardDescription.displayName = "CardDescription";
|
|
31
|
+
|
|
32
|
+
export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
33
|
+
({ className, ...props }, ref) => (
|
|
34
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
CardContent.displayName = "CardContent";
|
|
38
|
+
|
|
39
|
+
export const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
40
|
+
({ className, ...props }, ref) => (
|
|
41
|
+
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
CardFooter.displayName = "CardFooter";
|