stagent 0.1.0

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 (333) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +399 -0
  3. package/components.json +21 -0
  4. package/dist/cli.js +171 -0
  5. package/drizzle.config.ts +12 -0
  6. package/next.config.mjs +15 -0
  7. package/package.json +114 -0
  8. package/postcss.config.mjs +8 -0
  9. package/public/icon-512.png +0 -0
  10. package/public/icon.svg +13 -0
  11. package/public/readme/home-workspace.png +0 -0
  12. package/public/readme/inbox-approvals.png +0 -0
  13. package/public/readme/workflow-blueprints.png +0 -0
  14. package/public/stagent-s-128.png +0 -0
  15. package/public/stagent-s-64.png +0 -0
  16. package/src/app/api/blueprints/[id]/instantiate/route.ts +27 -0
  17. package/src/app/api/blueprints/[id]/route.ts +39 -0
  18. package/src/app/api/blueprints/import/route.ts +68 -0
  19. package/src/app/api/blueprints/route.ts +29 -0
  20. package/src/app/api/command-palette/recent/route.ts +31 -0
  21. package/src/app/api/data/clear/route.ts +22 -0
  22. package/src/app/api/data/seed/route.ts +22 -0
  23. package/src/app/api/documents/[id]/file/route.ts +44 -0
  24. package/src/app/api/documents/[id]/route.ts +123 -0
  25. package/src/app/api/documents/route.ts +59 -0
  26. package/src/app/api/logs/stream/route.ts +101 -0
  27. package/src/app/api/notifications/[id]/route.ts +36 -0
  28. package/src/app/api/notifications/mark-all-read/route.ts +13 -0
  29. package/src/app/api/notifications/pending-approvals/route.ts +10 -0
  30. package/src/app/api/notifications/pending-approvals/stream/route.ts +101 -0
  31. package/src/app/api/notifications/route.ts +34 -0
  32. package/src/app/api/permissions/route.ts +46 -0
  33. package/src/app/api/profiles/[id]/route.ts +79 -0
  34. package/src/app/api/profiles/[id]/test/route.ts +42 -0
  35. package/src/app/api/profiles/import/route.ts +108 -0
  36. package/src/app/api/profiles/route.ts +50 -0
  37. package/src/app/api/projects/[id]/route.ts +72 -0
  38. package/src/app/api/projects/route.ts +53 -0
  39. package/src/app/api/schedules/[id]/route.ts +185 -0
  40. package/src/app/api/schedules/route.ts +117 -0
  41. package/src/app/api/settings/budgets/route.ts +24 -0
  42. package/src/app/api/settings/openai/route.ts +24 -0
  43. package/src/app/api/settings/route.ts +21 -0
  44. package/src/app/api/settings/test/route.ts +26 -0
  45. package/src/app/api/tasks/[id]/cancel/route.ts +21 -0
  46. package/src/app/api/tasks/[id]/execute/route.ts +90 -0
  47. package/src/app/api/tasks/[id]/logs/route.ts +95 -0
  48. package/src/app/api/tasks/[id]/output/route.ts +47 -0
  49. package/src/app/api/tasks/[id]/respond/route.ts +64 -0
  50. package/src/app/api/tasks/[id]/resume/route.ts +76 -0
  51. package/src/app/api/tasks/[id]/route.ts +77 -0
  52. package/src/app/api/tasks/assist/route.ts +35 -0
  53. package/src/app/api/tasks/route.ts +82 -0
  54. package/src/app/api/uploads/[id]/route.ts +81 -0
  55. package/src/app/api/uploads/cleanup/route.ts +7 -0
  56. package/src/app/api/uploads/route.ts +66 -0
  57. package/src/app/api/workflows/[id]/execute/route.ts +82 -0
  58. package/src/app/api/workflows/[id]/route.ts +133 -0
  59. package/src/app/api/workflows/[id]/status/route.ts +54 -0
  60. package/src/app/api/workflows/[id]/steps/[stepId]/retry/route.ts +22 -0
  61. package/src/app/api/workflows/route.ts +61 -0
  62. package/src/app/apple-icon.tsx +31 -0
  63. package/src/app/costs/page.tsx +256 -0
  64. package/src/app/dashboard/page.tsx +44 -0
  65. package/src/app/documents/[id]/page.tsx +46 -0
  66. package/src/app/documents/page.tsx +45 -0
  67. package/src/app/error.tsx +26 -0
  68. package/src/app/global-error.tsx +23 -0
  69. package/src/app/globals.css +733 -0
  70. package/src/app/icon.tsx +30 -0
  71. package/src/app/inbox/loading.tsx +15 -0
  72. package/src/app/inbox/page.tsx +35 -0
  73. package/src/app/layout.tsx +78 -0
  74. package/src/app/manifest.ts +32 -0
  75. package/src/app/monitor/page.tsx +37 -0
  76. package/src/app/page.tsx +162 -0
  77. package/src/app/profiles/[id]/edit/page.tsx +39 -0
  78. package/src/app/profiles/[id]/page.tsx +33 -0
  79. package/src/app/profiles/new/page.tsx +22 -0
  80. package/src/app/profiles/page.tsx +19 -0
  81. package/src/app/projects/[id]/page.tsx +134 -0
  82. package/src/app/projects/loading.tsx +17 -0
  83. package/src/app/projects/page.tsx +32 -0
  84. package/src/app/schedules/[id]/page.tsx +47 -0
  85. package/src/app/schedules/page.tsx +18 -0
  86. package/src/app/settings/loading.tsx +24 -0
  87. package/src/app/settings/page.tsx +27 -0
  88. package/src/app/tasks/[id]/page.tsx +45 -0
  89. package/src/app/tasks/new/page.tsx +27 -0
  90. package/src/app/workflows/[id]/edit/page.tsx +66 -0
  91. package/src/app/workflows/[id]/page.tsx +37 -0
  92. package/src/app/workflows/blueprints/[id]/page.tsx +40 -0
  93. package/src/app/workflows/blueprints/new/page.tsx +20 -0
  94. package/src/app/workflows/blueprints/page.tsx +11 -0
  95. package/src/app/workflows/new/page.tsx +36 -0
  96. package/src/app/workflows/page.tsx +18 -0
  97. package/src/components/charts/donut-ring.tsx +64 -0
  98. package/src/components/charts/mini-bar.tsx +75 -0
  99. package/src/components/charts/sparkline.tsx +107 -0
  100. package/src/components/costs/cost-dashboard.tsx +877 -0
  101. package/src/components/costs/cost-filters.tsx +179 -0
  102. package/src/components/dashboard/activity-feed.tsx +95 -0
  103. package/src/components/dashboard/greeting.tsx +30 -0
  104. package/src/components/dashboard/priority-queue.tsx +79 -0
  105. package/src/components/dashboard/quick-actions.tsx +62 -0
  106. package/src/components/dashboard/recent-projects.tsx +79 -0
  107. package/src/components/dashboard/stats-cards.tsx +114 -0
  108. package/src/components/documents/document-browser.tsx +235 -0
  109. package/src/components/documents/document-detail-view.tsx +367 -0
  110. package/src/components/documents/document-grid.tsx +78 -0
  111. package/src/components/documents/document-preview.tsx +68 -0
  112. package/src/components/documents/document-table.tsx +119 -0
  113. package/src/components/documents/document-upload-dialog.tsx +153 -0
  114. package/src/components/documents/types.ts +6 -0
  115. package/src/components/documents/utils.ts +57 -0
  116. package/src/components/monitoring/connection-indicator.tsx +14 -0
  117. package/src/components/monitoring/log-entry.tsx +79 -0
  118. package/src/components/monitoring/log-filters.tsx +57 -0
  119. package/src/components/monitoring/log-stream.tsx +144 -0
  120. package/src/components/monitoring/monitor-overview-wrapper.tsx +64 -0
  121. package/src/components/monitoring/monitor-overview.tsx +119 -0
  122. package/src/components/notifications/failure-action.tsx +38 -0
  123. package/src/components/notifications/inbox-list.tsx +165 -0
  124. package/src/components/notifications/message-response.tsx +196 -0
  125. package/src/components/notifications/notification-item.tsx +250 -0
  126. package/src/components/notifications/pending-approval-host.tsx +478 -0
  127. package/src/components/notifications/permission-action.tsx +37 -0
  128. package/src/components/notifications/permission-response-actions.tsx +126 -0
  129. package/src/components/notifications/unread-badge.tsx +35 -0
  130. package/src/components/profiles/profile-browser.tsx +117 -0
  131. package/src/components/profiles/profile-card.tsx +78 -0
  132. package/src/components/profiles/profile-detail-view.tsx +564 -0
  133. package/src/components/profiles/profile-form-view.tsx +480 -0
  134. package/src/components/profiles/profile-import-dialog.tsx +113 -0
  135. package/src/components/projects/project-card.tsx +58 -0
  136. package/src/components/projects/project-create-dialog.tsx +140 -0
  137. package/src/components/projects/project-detail.tsx +68 -0
  138. package/src/components/projects/project-edit-dialog.tsx +219 -0
  139. package/src/components/projects/project-list.tsx +108 -0
  140. package/src/components/schedules/schedule-create-dialog.tsx +403 -0
  141. package/src/components/schedules/schedule-detail-view.tsx +274 -0
  142. package/src/components/schedules/schedule-list.tsx +242 -0
  143. package/src/components/schedules/schedule-status-badge.tsx +16 -0
  144. package/src/components/settings/api-key-form.tsx +141 -0
  145. package/src/components/settings/auth-config-section.tsx +141 -0
  146. package/src/components/settings/auth-method-selector.tsx +67 -0
  147. package/src/components/settings/auth-status-badge.tsx +40 -0
  148. package/src/components/settings/auth-status-dot.tsx +59 -0
  149. package/src/components/settings/budget-guardrails-section.tsx +842 -0
  150. package/src/components/settings/data-management-section.tsx +141 -0
  151. package/src/components/settings/openai-runtime-section.tsx +104 -0
  152. package/src/components/settings/permissions-section.tsx +91 -0
  153. package/src/components/shared/app-sidebar.tsx +123 -0
  154. package/src/components/shared/card-skeleton.tsx +42 -0
  155. package/src/components/shared/command-palette.tsx +250 -0
  156. package/src/components/shared/confirm-dialog.tsx +52 -0
  157. package/src/components/shared/empty-state.tsx +24 -0
  158. package/src/components/shared/error-state.tsx +32 -0
  159. package/src/components/shared/form-section-card.tsx +33 -0
  160. package/src/components/shared/section-heading.tsx +14 -0
  161. package/src/components/shared/stagent-logo.tsx +21 -0
  162. package/src/components/shared/theme-toggle.tsx +46 -0
  163. package/src/components/tasks/ai-assist-panel.tsx +210 -0
  164. package/src/components/tasks/content-preview.tsx +89 -0
  165. package/src/components/tasks/empty-board.tsx +12 -0
  166. package/src/components/tasks/file-upload.tsx +120 -0
  167. package/src/components/tasks/kanban-board.tsx +275 -0
  168. package/src/components/tasks/kanban-column.tsx +75 -0
  169. package/src/components/tasks/skeleton-board.tsx +21 -0
  170. package/src/components/tasks/task-attachments.tsx +114 -0
  171. package/src/components/tasks/task-card.tsx +101 -0
  172. package/src/components/tasks/task-create-panel.tsx +360 -0
  173. package/src/components/tasks/task-detail-view.tsx +356 -0
  174. package/src/components/ui/alert-dialog.tsx +196 -0
  175. package/src/components/ui/badge.tsx +50 -0
  176. package/src/components/ui/button.tsx +71 -0
  177. package/src/components/ui/card.tsx +92 -0
  178. package/src/components/ui/checkbox.tsx +32 -0
  179. package/src/components/ui/command.tsx +184 -0
  180. package/src/components/ui/dialog.tsx +158 -0
  181. package/src/components/ui/dropdown-menu.tsx +257 -0
  182. package/src/components/ui/form.tsx +167 -0
  183. package/src/components/ui/input.tsx +21 -0
  184. package/src/components/ui/label.tsx +24 -0
  185. package/src/components/ui/popover.tsx +89 -0
  186. package/src/components/ui/progress.tsx +31 -0
  187. package/src/components/ui/radio-group.tsx +45 -0
  188. package/src/components/ui/scroll-area.tsx +58 -0
  189. package/src/components/ui/select.tsx +190 -0
  190. package/src/components/ui/separator.tsx +28 -0
  191. package/src/components/ui/sheet.tsx +143 -0
  192. package/src/components/ui/sidebar.tsx +726 -0
  193. package/src/components/ui/skeleton.tsx +13 -0
  194. package/src/components/ui/slider.tsx +63 -0
  195. package/src/components/ui/sonner.tsx +36 -0
  196. package/src/components/ui/switch.tsx +35 -0
  197. package/src/components/ui/table.tsx +116 -0
  198. package/src/components/ui/tabs.tsx +91 -0
  199. package/src/components/ui/textarea.tsx +18 -0
  200. package/src/components/ui/tooltip.tsx +57 -0
  201. package/src/components/workflows/blueprint-editor.tsx +109 -0
  202. package/src/components/workflows/blueprint-gallery.tsx +155 -0
  203. package/src/components/workflows/blueprint-preview.tsx +240 -0
  204. package/src/components/workflows/loop-status-view.tsx +272 -0
  205. package/src/components/workflows/swarm-dashboard.tsx +185 -0
  206. package/src/components/workflows/workflow-form-view.tsx +1376 -0
  207. package/src/components/workflows/workflow-list.tsx +230 -0
  208. package/src/components/workflows/workflow-status-view.tsx +477 -0
  209. package/src/hooks/use-mobile.ts +19 -0
  210. package/src/instrumentation.ts +7 -0
  211. package/src/lib/agents/claude-agent.ts +737 -0
  212. package/src/lib/agents/execution-manager.ts +27 -0
  213. package/src/lib/agents/profiles/assignment-validation.ts +75 -0
  214. package/src/lib/agents/profiles/builtins/code-reviewer/SKILL.md +21 -0
  215. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +28 -0
  216. package/src/lib/agents/profiles/builtins/data-analyst/SKILL.md +25 -0
  217. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +27 -0
  218. package/src/lib/agents/profiles/builtins/devops-engineer/SKILL.md +34 -0
  219. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +27 -0
  220. package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +16 -0
  221. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +27 -0
  222. package/src/lib/agents/profiles/builtins/general/SKILL.md +13 -0
  223. package/src/lib/agents/profiles/builtins/general/profile.yaml +18 -0
  224. package/src/lib/agents/profiles/builtins/health-fitness-coach/SKILL.md +34 -0
  225. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +26 -0
  226. package/src/lib/agents/profiles/builtins/learning-coach/SKILL.md +35 -0
  227. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +26 -0
  228. package/src/lib/agents/profiles/builtins/project-manager/SKILL.md +26 -0
  229. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +26 -0
  230. package/src/lib/agents/profiles/builtins/researcher/SKILL.md +15 -0
  231. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +27 -0
  232. package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +34 -0
  233. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +26 -0
  234. package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +31 -0
  235. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +29 -0
  236. package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +23 -0
  237. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +26 -0
  238. package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +24 -0
  239. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +26 -0
  240. package/src/lib/agents/profiles/compatibility.ts +109 -0
  241. package/src/lib/agents/profiles/registry.ts +293 -0
  242. package/src/lib/agents/profiles/test-runner.ts +18 -0
  243. package/src/lib/agents/profiles/test-types.ts +20 -0
  244. package/src/lib/agents/profiles/types.ts +43 -0
  245. package/src/lib/agents/router.ts +56 -0
  246. package/src/lib/agents/runtime/catalog.ts +85 -0
  247. package/src/lib/agents/runtime/claude-sdk.ts +12 -0
  248. package/src/lib/agents/runtime/claude.ts +370 -0
  249. package/src/lib/agents/runtime/codex-app-server-client.ts +289 -0
  250. package/src/lib/agents/runtime/index.ts +167 -0
  251. package/src/lib/agents/runtime/openai-codex.ts +1089 -0
  252. package/src/lib/agents/runtime/task-assist-types.ts +8 -0
  253. package/src/lib/agents/runtime/types.ts +30 -0
  254. package/src/lib/constants/settings.ts +13 -0
  255. package/src/lib/constants/status-colors.ts +44 -0
  256. package/src/lib/constants/task-status.ts +49 -0
  257. package/src/lib/data/clear.ts +63 -0
  258. package/src/lib/data/seed-data/documents.ts +715 -0
  259. package/src/lib/data/seed-data/logs.ts +195 -0
  260. package/src/lib/data/seed-data/notifications.ts +141 -0
  261. package/src/lib/data/seed-data/profiles.ts +175 -0
  262. package/src/lib/data/seed-data/projects.ts +61 -0
  263. package/src/lib/data/seed-data/schedules.ts +108 -0
  264. package/src/lib/data/seed-data/tasks.ts +341 -0
  265. package/src/lib/data/seed-data/usage-ledger.ts +130 -0
  266. package/src/lib/data/seed-data/workflows.ts +213 -0
  267. package/src/lib/data/seed.ts +129 -0
  268. package/src/lib/db/index.ts +221 -0
  269. package/src/lib/db/migrations/0000_aromatic_gargoyle.sql +59 -0
  270. package/src/lib/db/migrations/0001_first_iron_patriot.sql +6 -0
  271. package/src/lib/db/migrations/0002_add_resume_count.sql +1 -0
  272. package/src/lib/db/migrations/0003_add_settings.sql +5 -0
  273. package/src/lib/db/migrations/0004_add_documents.sql +20 -0
  274. package/src/lib/db/migrations/0005_add_document_preprocessing.sql +4 -0
  275. package/src/lib/db/migrations/0006_add_agent_profile.sql +2 -0
  276. package/src/lib/db/migrations/0007_add_usage_metering_ledger.sql +30 -0
  277. package/src/lib/db/migrations/0008_add_document_version.sql +1 -0
  278. package/src/lib/db/migrations/meta/0000_snapshot.json +416 -0
  279. package/src/lib/db/migrations/meta/0001_snapshot.json +461 -0
  280. package/src/lib/db/migrations/meta/0002_snapshot.json +469 -0
  281. package/src/lib/db/migrations/meta/_journal.json +27 -0
  282. package/src/lib/db/schema.ts +227 -0
  283. package/src/lib/documents/cleanup.ts +50 -0
  284. package/src/lib/documents/context-builder.ts +75 -0
  285. package/src/lib/documents/output-scanner.ts +166 -0
  286. package/src/lib/documents/processor.ts +120 -0
  287. package/src/lib/documents/processors/image.ts +21 -0
  288. package/src/lib/documents/processors/office.ts +36 -0
  289. package/src/lib/documents/processors/pdf.ts +12 -0
  290. package/src/lib/documents/processors/spreadsheet.ts +18 -0
  291. package/src/lib/documents/processors/text.ts +8 -0
  292. package/src/lib/documents/registry.ts +25 -0
  293. package/src/lib/notifications/actionable.ts +108 -0
  294. package/src/lib/notifications/permissions.ts +169 -0
  295. package/src/lib/queries/chart-data.ts +184 -0
  296. package/src/lib/schedules/interval-parser.ts +110 -0
  297. package/src/lib/schedules/scheduler.ts +220 -0
  298. package/src/lib/settings/auth.ts +98 -0
  299. package/src/lib/settings/budget-guardrails.ts +590 -0
  300. package/src/lib/settings/helpers.ts +23 -0
  301. package/src/lib/settings/openai-auth.ts +80 -0
  302. package/src/lib/settings/permissions.ts +102 -0
  303. package/src/lib/usage/ledger.ts +489 -0
  304. package/src/lib/usage/pricing.ts +68 -0
  305. package/src/lib/utils/crypto.ts +90 -0
  306. package/src/lib/utils/format-timestamp.ts +46 -0
  307. package/src/lib/utils/session-cleanup.ts +26 -0
  308. package/src/lib/utils/stagent-paths.ts +18 -0
  309. package/src/lib/utils.ts +6 -0
  310. package/src/lib/validators/blueprint.ts +43 -0
  311. package/src/lib/validators/profile.ts +64 -0
  312. package/src/lib/validators/project.ts +17 -0
  313. package/src/lib/validators/settings.ts +57 -0
  314. package/src/lib/validators/task.ts +30 -0
  315. package/src/lib/workflows/blueprints/builtins/code-review-pipeline.yaml +72 -0
  316. package/src/lib/workflows/blueprints/builtins/documentation-generation.yaml +62 -0
  317. package/src/lib/workflows/blueprints/builtins/investment-research.yaml +81 -0
  318. package/src/lib/workflows/blueprints/builtins/meal-planning.yaml +73 -0
  319. package/src/lib/workflows/blueprints/builtins/product-research.yaml +72 -0
  320. package/src/lib/workflows/blueprints/builtins/research-report.yaml +77 -0
  321. package/src/lib/workflows/blueprints/builtins/sprint-planning.yaml +77 -0
  322. package/src/lib/workflows/blueprints/builtins/travel-planning.yaml +80 -0
  323. package/src/lib/workflows/blueprints/instantiator.ts +131 -0
  324. package/src/lib/workflows/blueprints/registry.ts +128 -0
  325. package/src/lib/workflows/blueprints/template.ts +58 -0
  326. package/src/lib/workflows/blueprints/types.ts +38 -0
  327. package/src/lib/workflows/definition-validation.ts +121 -0
  328. package/src/lib/workflows/engine.ts +1113 -0
  329. package/src/lib/workflows/loop-executor.ts +270 -0
  330. package/src/lib/workflows/parallel.ts +55 -0
  331. package/src/lib/workflows/swarm.ts +97 -0
  332. package/src/lib/workflows/types.ts +112 -0
  333. package/tsconfig.json +41 -0
@@ -0,0 +1,155 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useMemo } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Input } from "@/components/ui/input";
8
+ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
9
+ import { Skeleton } from "@/components/ui/skeleton";
10
+ import { EmptyState } from "@/components/shared/empty-state";
11
+ import { Button } from "@/components/ui/button";
12
+ import { Search, Layers, Plus } from "lucide-react";
13
+ import { patternLabels } from "@/lib/constants/status-colors";
14
+ import type { WorkflowBlueprint } from "@/lib/workflows/blueprints/types";
15
+
16
+ const difficultyColors: Record<string, string> = {
17
+ beginner: "border-green-500/30 bg-green-500/10 text-green-700 dark:text-green-400",
18
+ intermediate: "border-yellow-500/30 bg-yellow-500/10 text-yellow-700 dark:text-yellow-400",
19
+ advanced: "border-red-500/30 bg-red-500/10 text-red-700 dark:text-red-400",
20
+ };
21
+
22
+ export function BlueprintGallery() {
23
+ const router = useRouter();
24
+ const [blueprints, setBlueprints] = useState<WorkflowBlueprint[]>([]);
25
+ const [loaded, setLoaded] = useState(false);
26
+ const [search, setSearch] = useState("");
27
+ const [domainFilter, setDomainFilter] = useState<"all" | "work" | "personal">("all");
28
+
29
+ useEffect(() => {
30
+ fetch("/api/blueprints")
31
+ .then((r) => (r.ok ? r.json() : []))
32
+ .then((data) => setBlueprints(data))
33
+ .finally(() => setLoaded(true));
34
+ }, []);
35
+
36
+ const filtered = useMemo(() => {
37
+ const q = search.toLowerCase();
38
+ return blueprints.filter((bp) => {
39
+ if (domainFilter !== "all" && bp.domain !== domainFilter) return false;
40
+ if (!q) return true;
41
+ return (
42
+ bp.name.toLowerCase().includes(q) ||
43
+ bp.description.toLowerCase().includes(q) ||
44
+ bp.tags.some((t) => t.toLowerCase().includes(q))
45
+ );
46
+ });
47
+ }, [blueprints, search, domainFilter]);
48
+
49
+ return (
50
+ <div className="space-y-6">
51
+ {/* Header */}
52
+ <div className="flex items-center justify-between">
53
+ <div>
54
+ <h1 className="text-2xl font-bold tracking-tight">Blueprints</h1>
55
+ <p className="text-sm text-muted-foreground mt-1">
56
+ Pre-configured workflow templates — select one, fill in variables, and create a ready-to-run workflow.
57
+ </p>
58
+ </div>
59
+ <Button onClick={() => router.push("/workflows/blueprints/new")}>
60
+ <Plus className="h-4 w-4 mr-2" />
61
+ Create Custom
62
+ </Button>
63
+ </div>
64
+
65
+ {/* Search + Filter */}
66
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center">
67
+ <div className="relative flex-1">
68
+ <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
69
+ <Input
70
+ placeholder="Search blueprints..."
71
+ value={search}
72
+ onChange={(e) => setSearch(e.target.value)}
73
+ className="pl-9"
74
+ />
75
+ </div>
76
+ <Tabs
77
+ value={domainFilter}
78
+ onValueChange={(v) => setDomainFilter(v as "all" | "work" | "personal")}
79
+ >
80
+ <TabsList>
81
+ <TabsTrigger value="all">All</TabsTrigger>
82
+ <TabsTrigger value="work">Work</TabsTrigger>
83
+ <TabsTrigger value="personal">Personal</TabsTrigger>
84
+ </TabsList>
85
+ </Tabs>
86
+ </div>
87
+
88
+ {/* Grid */}
89
+ {!loaded ? (
90
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
91
+ {[1, 2, 3, 4, 5, 6].map((i) => (
92
+ <Skeleton key={i} className="h-32 w-full rounded-xl" />
93
+ ))}
94
+ </div>
95
+ ) : filtered.length === 0 ? (
96
+ <EmptyState
97
+ icon={Layers}
98
+ heading="No blueprints found"
99
+ description="Try adjusting your search or filter."
100
+ />
101
+ ) : (
102
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
103
+ {filtered.map((bp) => (
104
+ <Card
105
+ key={bp.id}
106
+ tabIndex={0}
107
+ className="cursor-pointer transition-colors hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
108
+ onClick={() => router.push(`/workflows/blueprints/${bp.id}`)}
109
+ onKeyDown={(e) => {
110
+ if (e.key === "Enter" || e.key === " ") {
111
+ e.preventDefault();
112
+ router.push(`/workflows/blueprints/${bp.id}`);
113
+ }
114
+ }}
115
+ >
116
+ <CardHeader className="pb-1">
117
+ <div className="flex items-center justify-between">
118
+ <CardTitle className="text-sm font-medium">
119
+ {bp.name}
120
+ </CardTitle>
121
+ <div className="flex items-center gap-1.5">
122
+ <Badge variant={bp.domain === "work" ? "default" : "secondary"}>
123
+ {bp.domain}
124
+ </Badge>
125
+ {bp.difficulty && (
126
+ <Badge variant="outline" className={`text-xs ${difficultyColors[bp.difficulty] ?? ""}`}>
127
+ {bp.difficulty}
128
+ </Badge>
129
+ )}
130
+ </div>
131
+ </div>
132
+ </CardHeader>
133
+ <CardContent>
134
+ <p className="text-xs text-muted-foreground line-clamp-2">
135
+ {bp.description}
136
+ </p>
137
+ <div className="flex flex-wrap items-center gap-2 mt-2 text-xs text-muted-foreground">
138
+ <span>{patternLabels[bp.pattern] ?? bp.pattern}</span>
139
+ <span>&middot;</span>
140
+ <span>{bp.steps.length} steps</span>
141
+ {bp.estimatedDuration && (
142
+ <>
143
+ <span>&middot;</span>
144
+ <span>{bp.estimatedDuration}</span>
145
+ </>
146
+ )}
147
+ </div>
148
+ </CardContent>
149
+ </Card>
150
+ ))}
151
+ </div>
152
+ )}
153
+ </div>
154
+ );
155
+ }
@@ -0,0 +1,240 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Input } from "@/components/ui/input";
8
+ import { Label } from "@/components/ui/label";
9
+ import { Textarea } from "@/components/ui/textarea";
10
+ import { Switch } from "@/components/ui/switch";
11
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
12
+ import {
13
+ Select,
14
+ SelectContent,
15
+ SelectItem,
16
+ SelectTrigger,
17
+ SelectValue,
18
+ } from "@/components/ui/select";
19
+ import { Loader2, Play } from "lucide-react";
20
+ import { toast } from "sonner";
21
+ import { patternLabels } from "@/lib/constants/status-colors";
22
+ import type { WorkflowBlueprint, BlueprintVariable } from "@/lib/workflows/blueprints/types";
23
+
24
+ interface BlueprintPreviewProps {
25
+ blueprint: WorkflowBlueprint;
26
+ projects: { id: string; name: string }[];
27
+ }
28
+
29
+ export function BlueprintPreview({
30
+ blueprint,
31
+ projects,
32
+ }: BlueprintPreviewProps) {
33
+ const router = useRouter();
34
+ const [variables, setVariables] = useState<Record<string, unknown>>(() => {
35
+ const defaults: Record<string, unknown> = {};
36
+ for (const v of blueprint.variables) {
37
+ if (v.default !== undefined) defaults[v.id] = v.default;
38
+ }
39
+ return defaults;
40
+ });
41
+ const [projectId, setProjectId] = useState<string>("none");
42
+ const [submitting, setSubmitting] = useState(false);
43
+
44
+ function setVar(id: string, value: unknown) {
45
+ setVariables((prev) => ({ ...prev, [id]: value }));
46
+ }
47
+
48
+ async function handleCreate() {
49
+ setSubmitting(true);
50
+ try {
51
+ const res = await fetch(`/api/blueprints/${blueprint.id}/instantiate`, {
52
+ method: "POST",
53
+ headers: { "Content-Type": "application/json" },
54
+ body: JSON.stringify({
55
+ variables,
56
+ projectId: projectId !== "none" ? projectId : undefined,
57
+ }),
58
+ });
59
+
60
+ const data = await res.json();
61
+ if (!res.ok) {
62
+ toast.error(data.error || "Failed to create workflow");
63
+ return;
64
+ }
65
+
66
+ toast.success(`Workflow created: ${data.name}`);
67
+ router.push(`/workflows/${data.workflowId}`);
68
+ } catch {
69
+ toast.error("Network error");
70
+ } finally {
71
+ setSubmitting(false);
72
+ }
73
+ }
74
+
75
+ return (
76
+ <div className="space-y-6 max-w-2xl">
77
+ {/* Header */}
78
+ <div>
79
+ <h1 className="text-2xl font-bold tracking-tight">{blueprint.name}</h1>
80
+ <p className="text-sm text-muted-foreground mt-1">{blueprint.description}</p>
81
+ <div className="flex flex-wrap gap-1.5 mt-3">
82
+ <Badge variant={blueprint.domain === "work" ? "default" : "secondary"}>
83
+ {blueprint.domain}
84
+ </Badge>
85
+ <Badge variant="outline">{patternLabels[blueprint.pattern] ?? blueprint.pattern}</Badge>
86
+ {blueprint.estimatedDuration && (
87
+ <Badge variant="outline">{blueprint.estimatedDuration}</Badge>
88
+ )}
89
+ {blueprint.difficulty && (
90
+ <Badge variant="outline">{blueprint.difficulty}</Badge>
91
+ )}
92
+ </div>
93
+ </div>
94
+
95
+ {/* Step Timeline */}
96
+ <Card>
97
+ <CardHeader className="pb-2">
98
+ <CardTitle className="text-sm font-medium">Steps</CardTitle>
99
+ </CardHeader>
100
+ <CardContent>
101
+ <div className="relative space-y-3 pl-4 before:absolute before:left-[7px] before:top-2 before:bottom-2 before:w-px before:bg-border">
102
+ {blueprint.steps.map((step, i) => (
103
+ <div key={i} className="relative flex items-start gap-3">
104
+ <div className="absolute -left-4 top-1.5 h-2 w-2 rounded-full bg-primary" />
105
+ <div className="flex-1">
106
+ <p className="text-sm font-medium">{step.name}</p>
107
+ <p className="text-xs text-muted-foreground">
108
+ Profile: {step.profileId}
109
+ {step.requiresApproval && " · Requires approval"}
110
+ {step.condition && " · Conditional"}
111
+ </p>
112
+ </div>
113
+ </div>
114
+ ))}
115
+ </div>
116
+ </CardContent>
117
+ </Card>
118
+
119
+ {/* Variable Form */}
120
+ <Card>
121
+ <CardHeader className="pb-2">
122
+ <CardTitle className="text-sm font-medium">Configure</CardTitle>
123
+ </CardHeader>
124
+ <CardContent className="space-y-4">
125
+ {/* Project selector */}
126
+ <div className="space-y-1.5">
127
+ <Label>Project (optional)</Label>
128
+ <Select value={projectId} onValueChange={setProjectId}>
129
+ <SelectTrigger>
130
+ <SelectValue placeholder="No project" />
131
+ </SelectTrigger>
132
+ <SelectContent>
133
+ <SelectItem value="none">No project</SelectItem>
134
+ {projects.map((p) => (
135
+ <SelectItem key={p.id} value={p.id}>
136
+ {p.name}
137
+ </SelectItem>
138
+ ))}
139
+ </SelectContent>
140
+ </Select>
141
+ </div>
142
+
143
+ {/* Dynamic variables */}
144
+ {blueprint.variables.map((v) => (
145
+ <VariableInput
146
+ key={v.id}
147
+ variable={v}
148
+ value={variables[v.id]}
149
+ onChange={(val) => setVar(v.id, val)}
150
+ />
151
+ ))}
152
+
153
+ {/* Create button */}
154
+ <Button onClick={handleCreate} disabled={submitting} className="w-full mt-2">
155
+ {submitting ? (
156
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
157
+ ) : (
158
+ <Play className="mr-2 h-4 w-4" />
159
+ )}
160
+ Create Workflow
161
+ </Button>
162
+ </CardContent>
163
+ </Card>
164
+ </div>
165
+ );
166
+ }
167
+
168
+ function VariableInput({
169
+ variable,
170
+ value,
171
+ onChange,
172
+ }: {
173
+ variable: BlueprintVariable;
174
+ value: unknown;
175
+ onChange: (value: unknown) => void;
176
+ }) {
177
+ return (
178
+ <div className="space-y-1.5">
179
+ <Label>
180
+ {variable.label}
181
+ {variable.required && <span className="text-destructive ml-1">*</span>}
182
+ </Label>
183
+ {variable.description && (
184
+ <p className="text-xs text-muted-foreground">{variable.description}</p>
185
+ )}
186
+
187
+ {variable.type === "text" && (
188
+ <Input
189
+ value={String(value ?? "")}
190
+ onChange={(e) => onChange(e.target.value)}
191
+ placeholder={variable.placeholder}
192
+ />
193
+ )}
194
+
195
+ {variable.type === "textarea" && (
196
+ <Textarea
197
+ value={String(value ?? "")}
198
+ onChange={(e) => onChange(e.target.value)}
199
+ placeholder={variable.placeholder}
200
+ rows={3}
201
+ />
202
+ )}
203
+
204
+ {variable.type === "number" && (
205
+ <Input
206
+ type="number"
207
+ value={value !== undefined ? Number(value) : ""}
208
+ onChange={(e) => onChange(e.target.value ? Number(e.target.value) : undefined)}
209
+ min={variable.min}
210
+ max={variable.max}
211
+ />
212
+ )}
213
+
214
+ {variable.type === "boolean" && (
215
+ <Switch
216
+ checked={Boolean(value)}
217
+ onCheckedChange={(checked) => onChange(checked)}
218
+ />
219
+ )}
220
+
221
+ {variable.type === "select" && variable.options && (
222
+ <Select
223
+ value={String(value ?? "")}
224
+ onValueChange={onChange}
225
+ >
226
+ <SelectTrigger>
227
+ <SelectValue placeholder="Select..." />
228
+ </SelectTrigger>
229
+ <SelectContent>
230
+ {variable.options.map((opt) => (
231
+ <SelectItem key={opt.value} value={opt.value}>
232
+ {opt.label}
233
+ </SelectItem>
234
+ ))}
235
+ </SelectContent>
236
+ </Select>
237
+ )}
238
+ </div>
239
+ );
240
+ }
@@ -0,0 +1,272 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ CheckCircle,
8
+ Circle,
9
+ Loader2,
10
+ XCircle,
11
+ Pause,
12
+ Play,
13
+ Clock,
14
+ ChevronDown,
15
+ ChevronRight,
16
+ ExternalLink,
17
+ } from "lucide-react";
18
+ import { toast } from "sonner";
19
+ import type { LoopState, LoopConfig, LoopStopReason } from "@/lib/workflows/types";
20
+
21
+ interface LoopStatusViewProps {
22
+ workflowId: string;
23
+ workflowStatus: string;
24
+ loopConfig: LoopConfig;
25
+ loopState: LoopState | null;
26
+ onRefresh: () => void;
27
+ }
28
+
29
+ const iterationStatusIcons: Record<string, React.ReactNode> = {
30
+ pending: <Circle className="h-4 w-4 text-muted-foreground" />,
31
+ running: <Loader2 className="h-4 w-4 text-status-running animate-spin" />,
32
+ completed: <CheckCircle className="h-4 w-4 text-status-completed" />,
33
+ failed: <XCircle className="h-4 w-4 text-destructive" />,
34
+ };
35
+
36
+ const stopReasonLabels: Record<LoopStopReason, string> = {
37
+ max_iterations: "Max iterations reached",
38
+ time_budget: "Time budget exhausted",
39
+ agent_signaled: "Agent signaled completion",
40
+ human_cancel: "Cancelled by user",
41
+ human_pause: "Paused by user",
42
+ error: "Error",
43
+ };
44
+
45
+ const stopReasonVariant: Record<LoopStopReason, "default" | "secondary" | "destructive" | "outline" | "success"> = {
46
+ max_iterations: "secondary",
47
+ time_budget: "secondary",
48
+ agent_signaled: "success",
49
+ human_cancel: "outline",
50
+ human_pause: "outline",
51
+ error: "destructive",
52
+ };
53
+
54
+ export function LoopStatusView({
55
+ workflowId,
56
+ workflowStatus,
57
+ loopConfig,
58
+ loopState,
59
+ onRefresh,
60
+ }: LoopStatusViewProps) {
61
+ const [pausing, setPausing] = useState(false);
62
+ const [resuming, setResuming] = useState(false);
63
+ const [expandedIterations, setExpandedIterations] = useState<Set<number>>(new Set());
64
+
65
+ const currentIteration = loopState?.currentIteration ?? 0;
66
+ const maxIterations = loopConfig.maxIterations;
67
+ const progressPercent = Math.round((currentIteration / maxIterations) * 100);
68
+
69
+ const isActive = workflowStatus === "active";
70
+ const isPaused = workflowStatus === "paused";
71
+ const isDraft = workflowStatus === "draft";
72
+
73
+ async function handlePause() {
74
+ setPausing(true);
75
+ try {
76
+ const res = await fetch(`/api/workflows/${workflowId}`, {
77
+ method: "PATCH",
78
+ headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify({ status: "paused" }),
80
+ });
81
+ if (res.ok) {
82
+ toast.success("Loop paused");
83
+ onRefresh();
84
+ } else {
85
+ const err = await res.json().catch(() => null);
86
+ toast.error(err?.error ?? "Failed to pause");
87
+ }
88
+ } finally {
89
+ setPausing(false);
90
+ }
91
+ }
92
+
93
+ async function handleResume() {
94
+ setResuming(true);
95
+ try {
96
+ const res = await fetch(`/api/workflows/${workflowId}/execute`, {
97
+ method: "POST",
98
+ });
99
+ if (res.ok) {
100
+ toast.success("Loop resumed");
101
+ onRefresh();
102
+ } else {
103
+ const err = await res.json().catch(() => null);
104
+ toast.error(err?.error ?? "Failed to resume");
105
+ }
106
+ } finally {
107
+ setResuming(false);
108
+ }
109
+ }
110
+
111
+ function toggleIteration(iteration: number) {
112
+ setExpandedIterations((prev) => {
113
+ const next = new Set(prev);
114
+ if (next.has(iteration)) {
115
+ next.delete(iteration);
116
+ } else {
117
+ next.add(iteration);
118
+ }
119
+ return next;
120
+ });
121
+ }
122
+
123
+ // Time budget display
124
+ let timeDisplay: React.ReactNode = null;
125
+ if (loopConfig.timeBudgetMs && loopState) {
126
+ const startTime = new Date(loopState.startedAt).getTime();
127
+ const elapsed = loopState.completedAt
128
+ ? new Date(loopState.completedAt).getTime() - startTime
129
+ : Date.now() - startTime;
130
+ const remaining = Math.max(0, loopConfig.timeBudgetMs - elapsed);
131
+ timeDisplay = (
132
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
133
+ <Clock className="h-3 w-3" />
134
+ <span>
135
+ {formatDuration(elapsed)} elapsed
136
+ {remaining > 0 && isActive ? ` / ${formatDuration(remaining)} remaining` : ""}
137
+ </span>
138
+ </div>
139
+ );
140
+ }
141
+
142
+ const iterations = loopState?.iterations ?? [];
143
+ const reversedIterations = [...iterations].reverse();
144
+
145
+ return (
146
+ <div className="space-y-4">
147
+ {/* Controls */}
148
+ <div className="flex items-center gap-2">
149
+ {isActive && (
150
+ <Button size="sm" variant="outline" onClick={handlePause} disabled={pausing}>
151
+ <Pause className="h-3 w-3 mr-1" />
152
+ {pausing ? "Pausing..." : "Pause"}
153
+ </Button>
154
+ )}
155
+ {(isPaused || isDraft) && (
156
+ <Button size="sm" onClick={handleResume} disabled={resuming}>
157
+ <Play className="h-3 w-3 mr-1" />
158
+ {resuming ? "Resuming..." : isPaused ? "Resume" : "Execute"}
159
+ </Button>
160
+ )}
161
+ </div>
162
+
163
+ {/* Progress */}
164
+ <div className="space-y-1.5">
165
+ <div className="flex items-center justify-between text-sm">
166
+ <span className="text-muted-foreground">Progress</span>
167
+ <span className="font-mono text-xs">
168
+ {currentIteration} / {maxIterations} iterations ({progressPercent}%)
169
+ </span>
170
+ </div>
171
+ <div className="h-2 rounded-full bg-muted overflow-hidden">
172
+ <div
173
+ className="h-full rounded-full bg-primary transition-all duration-300"
174
+ style={{ width: `${progressPercent}%` }}
175
+ />
176
+ </div>
177
+ {timeDisplay}
178
+ </div>
179
+
180
+ {/* Stop reason */}
181
+ {loopState?.stopReason && (
182
+ <Badge variant={stopReasonVariant[loopState.stopReason]}>
183
+ {stopReasonLabels[loopState.stopReason]}
184
+ </Badge>
185
+ )}
186
+
187
+ {/* Total duration */}
188
+ {loopState?.totalDurationMs && (
189
+ <p className="text-xs text-muted-foreground">
190
+ Total duration: {formatDuration(loopState.totalDurationMs)}
191
+ </p>
192
+ )}
193
+
194
+ {/* Iteration timeline */}
195
+ {reversedIterations.length > 0 && (
196
+ <div className="space-y-2">
197
+ <p className="text-sm font-medium">Iterations</p>
198
+ <div className="space-y-1" aria-live="polite">
199
+ {reversedIterations.map((iter) => {
200
+ const isExpanded = expandedIterations.has(iter.iteration);
201
+ return (
202
+ <div key={iter.iteration} className="border rounded-lg">
203
+ <button
204
+ type="button"
205
+ className="w-full flex items-center gap-3 p-2.5 text-left hover:bg-muted/50 transition-colors rounded-lg"
206
+ onClick={() => toggleIteration(iter.iteration)}
207
+ >
208
+ <div className="flex-shrink-0">
209
+ {iterationStatusIcons[iter.status] ?? iterationStatusIcons.pending}
210
+ </div>
211
+ <div className="flex-1 min-w-0 flex items-center gap-2">
212
+ <span className="text-sm font-medium">
213
+ Iteration {iter.iteration}
214
+ </span>
215
+ {iter.durationMs && (
216
+ <Badge variant="outline" className="text-xs font-mono">
217
+ {formatDuration(iter.durationMs)}
218
+ </Badge>
219
+ )}
220
+ </div>
221
+ <div className="flex-shrink-0">
222
+ {isExpanded ? (
223
+ <ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
224
+ ) : (
225
+ <ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
226
+ )}
227
+ </div>
228
+ </button>
229
+
230
+ {isExpanded && (
231
+ <div className="px-2.5 pb-2.5 pt-0 space-y-1.5">
232
+ {iter.error && (
233
+ <p className="text-xs text-destructive">{iter.error}</p>
234
+ )}
235
+ {iter.result && (
236
+ <p className="text-xs text-muted-foreground whitespace-pre-wrap line-clamp-6">
237
+ {iter.result.slice(0, 1000)}
238
+ {iter.result.length > 1000 ? "..." : ""}
239
+ </p>
240
+ )}
241
+ {iter.taskId && (
242
+ <a
243
+ href={`/monitor?taskId=${iter.taskId}`}
244
+ className="inline-flex items-center gap-1 text-xs text-primary hover:underline"
245
+ >
246
+ <ExternalLink className="h-3 w-3" />
247
+ View task logs
248
+ </a>
249
+ )}
250
+ </div>
251
+ )}
252
+ </div>
253
+ );
254
+ })}
255
+ </div>
256
+ </div>
257
+ )}
258
+ </div>
259
+ );
260
+ }
261
+
262
+ function formatDuration(ms: number): string {
263
+ if (ms < 1000) return `${ms}ms`;
264
+ const seconds = Math.floor(ms / 1000);
265
+ if (seconds < 60) return `${seconds}s`;
266
+ const minutes = Math.floor(seconds / 60);
267
+ const remainingSeconds = seconds % 60;
268
+ if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
269
+ const hours = Math.floor(minutes / 60);
270
+ const remainingMinutes = minutes % 60;
271
+ return `${hours}h ${remainingMinutes}m`;
272
+ }