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,68 @@
1
+ "use client";
2
+
3
+ import ReactMarkdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
5
+ import type { DocumentWithRelations } from "./types";
6
+
7
+ interface DocumentPreviewProps {
8
+ document: DocumentWithRelations;
9
+ }
10
+
11
+ export function DocumentPreview({ document: doc }: DocumentPreviewProps) {
12
+ const isImage = doc.mimeType.startsWith("image/");
13
+ const isPdf = doc.mimeType === "application/pdf";
14
+ const isMarkdown = doc.mimeType === "text/markdown";
15
+ const isText =
16
+ doc.mimeType.startsWith("text/") ||
17
+ doc.mimeType === "application/json";
18
+
19
+ if (isImage) {
20
+ return (
21
+ <div className="rounded-md overflow-hidden border border-border bg-muted/30 flex items-center justify-center">
22
+ <img
23
+ src={`/api/documents/${doc.id}/file?inline=1`}
24
+ alt={doc.originalName}
25
+ className="max-h-64 object-contain"
26
+ />
27
+ </div>
28
+ );
29
+ }
30
+
31
+ if (isPdf) {
32
+ return (
33
+ <div className="rounded-md overflow-hidden border border-border">
34
+ <iframe
35
+ src={`/api/documents/${doc.id}/file?inline=1`}
36
+ className="w-full h-64"
37
+ title={doc.originalName}
38
+ />
39
+ </div>
40
+ );
41
+ }
42
+
43
+ if (isMarkdown && doc.extractedText) {
44
+ return (
45
+ <div className="rounded-md border border-border p-3 prose prose-sm dark:prose-invert max-h-64 overflow-y-auto">
46
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>
47
+ {doc.extractedText.slice(0, 5000)}
48
+ </ReactMarkdown>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ if (isText && doc.extractedText) {
54
+ return (
55
+ <pre className="text-xs bg-muted p-3 rounded-md max-h-64 overflow-y-auto whitespace-pre-wrap break-words border border-border">
56
+ {doc.extractedText.slice(0, 5000)}
57
+ </pre>
58
+ );
59
+ }
60
+
61
+ // Fallback — no preview
62
+ return (
63
+ <div className="rounded-md border border-border p-6 text-center text-muted-foreground">
64
+ <p className="text-sm">No preview available for this file type.</p>
65
+ <p className="text-xs mt-1">Download the file to view its contents.</p>
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,119 @@
1
+ "use client";
2
+
3
+ import { Checkbox } from "@/components/ui/checkbox";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import {
6
+ Table,
7
+ TableBody,
8
+ TableCell,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from "@/components/ui/table";
13
+ import { Card } from "@/components/ui/card";
14
+ import { getFileIcon, formatSize, getStatusColor } from "./utils";
15
+ import type { DocumentWithRelations } from "./types";
16
+
17
+ interface DocumentTableProps {
18
+ documents: DocumentWithRelations[];
19
+ selected: Set<string>;
20
+ onToggleSelect: (id: string) => void;
21
+ onToggleSelectAll: () => void;
22
+ onOpen: (doc: DocumentWithRelations) => void;
23
+ }
24
+
25
+ export function DocumentTable({
26
+ documents,
27
+ selected,
28
+ onToggleSelect,
29
+ onToggleSelectAll,
30
+ onOpen,
31
+ }: DocumentTableProps) {
32
+ const allSelected = selected.size === documents.length && documents.length > 0;
33
+
34
+ return (
35
+ <Card className="overflow-hidden p-0 gap-0">
36
+ <Table>
37
+ <TableHeader>
38
+ <TableRow>
39
+ <TableHead className="w-10">
40
+ <Checkbox
41
+ checked={allSelected}
42
+ onCheckedChange={onToggleSelectAll}
43
+ aria-label="Select all documents"
44
+ />
45
+ </TableHead>
46
+ <TableHead>Name</TableHead>
47
+ <TableHead className="hidden md:table-cell">Size</TableHead>
48
+ <TableHead className="hidden md:table-cell">Direction</TableHead>
49
+ <TableHead className="hidden lg:table-cell">Task</TableHead>
50
+ <TableHead className="hidden lg:table-cell">Project</TableHead>
51
+ <TableHead>Status</TableHead>
52
+ <TableHead className="hidden md:table-cell">Date</TableHead>
53
+ </TableRow>
54
+ </TableHeader>
55
+ <TableBody>
56
+ {documents.map((doc) => {
57
+ const Icon = getFileIcon(doc.mimeType);
58
+ return (
59
+ <TableRow
60
+ key={doc.id}
61
+ className="cursor-pointer"
62
+ onClick={() => onOpen(doc)}
63
+ >
64
+ <TableCell onClick={(e: React.MouseEvent) => e.stopPropagation()}>
65
+ <Checkbox
66
+ checked={selected.has(doc.id)}
67
+ onCheckedChange={() => onToggleSelect(doc.id)}
68
+ aria-label={`Select ${doc.originalName}`}
69
+ />
70
+ </TableCell>
71
+ <TableCell>
72
+ <div className="flex items-center gap-2">
73
+ <Icon className="h-4 w-4 text-muted-foreground shrink-0" />
74
+ <span className="truncate max-w-[200px]">{doc.originalName}</span>
75
+ </div>
76
+ </TableCell>
77
+ <TableCell className="hidden md:table-cell text-muted-foreground text-sm">
78
+ {formatSize(doc.size)}
79
+ </TableCell>
80
+ <TableCell className="hidden md:table-cell">
81
+ <div className="flex items-center gap-2">
82
+ <Badge variant="outline" className="capitalize">
83
+ {doc.direction}
84
+ </Badge>
85
+ {doc.direction === "output" && (
86
+ <span className="text-xs text-muted-foreground">v{doc.version}</span>
87
+ )}
88
+ </div>
89
+ </TableCell>
90
+ <TableCell className="hidden lg:table-cell text-sm">
91
+ {doc.taskTitle ? (
92
+ <span className="truncate max-w-[150px] block">{doc.taskTitle}</span>
93
+ ) : (
94
+ <span className="text-muted-foreground">—</span>
95
+ )}
96
+ </TableCell>
97
+ <TableCell className="hidden lg:table-cell text-sm">
98
+ {doc.projectName ? (
99
+ <span className="truncate max-w-[120px] block">{doc.projectName}</span>
100
+ ) : (
101
+ <span className="text-muted-foreground">—</span>
102
+ )}
103
+ </TableCell>
104
+ <TableCell>
105
+ <Badge variant="outline" className={getStatusColor(doc.status)}>
106
+ {doc.status}
107
+ </Badge>
108
+ </TableCell>
109
+ <TableCell className="hidden md:table-cell text-muted-foreground text-sm">
110
+ {new Date(doc.createdAt).toLocaleDateString()}
111
+ </TableCell>
112
+ </TableRow>
113
+ );
114
+ })}
115
+ </TableBody>
116
+ </Table>
117
+ </Card>
118
+ );
119
+ }
@@ -0,0 +1,153 @@
1
+ "use client";
2
+
3
+ import { useState, useRef } from "react";
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from "@/components/ui/dialog";
11
+ import { Button } from "@/components/ui/button";
12
+ import { Upload, CheckCircle, Loader2 } from "lucide-react";
13
+ import { Badge } from "@/components/ui/badge";
14
+ import { toast } from "sonner";
15
+
16
+ interface DocumentUploadDialogProps {
17
+ open: boolean;
18
+ onClose: () => void;
19
+ onUploaded: () => void;
20
+ restoreFocusElement?: HTMLElement | null;
21
+ }
22
+
23
+ export function DocumentUploadDialog({
24
+ open,
25
+ onClose,
26
+ onUploaded,
27
+ restoreFocusElement,
28
+ }: DocumentUploadDialogProps) {
29
+ const [uploading, setUploading] = useState(false);
30
+ const [uploaded, setUploaded] = useState<string[]>([]);
31
+ const inputRef = useRef<HTMLInputElement>(null);
32
+
33
+ async function handleFiles(files: FileList) {
34
+ setUploading(true);
35
+ const names: string[] = [];
36
+
37
+ for (const file of Array.from(files)) {
38
+ try {
39
+ const formData = new FormData();
40
+ formData.append("file", file);
41
+
42
+ const res = await fetch("/api/uploads", {
43
+ method: "POST",
44
+ body: formData,
45
+ });
46
+
47
+ if (res.ok) {
48
+ names.push(file.name);
49
+ }
50
+ } catch {
51
+ toast.error(`Failed to upload ${file.name}`);
52
+ }
53
+ }
54
+
55
+ setUploaded((prev) => [...prev, ...names]);
56
+ setUploading(false);
57
+
58
+ if (names.length > 0) {
59
+ toast.success(`Uploaded ${names.length} file${names.length !== 1 ? "s" : ""}`);
60
+ onUploaded();
61
+ }
62
+ }
63
+
64
+ function handleClose() {
65
+ setUploaded([]);
66
+ onClose();
67
+ }
68
+
69
+ return (
70
+ <Dialog open={open} onOpenChange={(o) => !o && handleClose()}>
71
+ <DialogContent
72
+ className="sm:max-w-md"
73
+ onCloseAutoFocus={(event) => {
74
+ if (!restoreFocusElement) return;
75
+ event.preventDefault();
76
+ restoreFocusElement.focus();
77
+ }}
78
+ >
79
+ <DialogHeader>
80
+ <DialogTitle>Upload Documents</DialogTitle>
81
+ <DialogDescription>
82
+ Add one or more files to the document library for processing and task attachment.
83
+ </DialogDescription>
84
+ </DialogHeader>
85
+
86
+ <div
87
+ role="button"
88
+ tabIndex={0}
89
+ aria-label="Upload files — click or drag and drop"
90
+ className="border-2 border-dashed border-border rounded-lg p-8 text-center cursor-pointer hover:bg-accent/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
91
+ onClick={() => inputRef.current?.click()}
92
+ onKeyDown={(e) => {
93
+ if (e.key === "Enter" || e.key === " ") {
94
+ e.preventDefault();
95
+ inputRef.current?.click();
96
+ }
97
+ }}
98
+ onDrop={(e) => {
99
+ e.preventDefault();
100
+ if (e.dataTransfer.files.length) handleFiles(e.dataTransfer.files);
101
+ }}
102
+ onDragOver={(e) => e.preventDefault()}
103
+ >
104
+ {uploading ? (
105
+ <Loader2 className="h-8 w-8 mx-auto text-muted-foreground mb-2 animate-spin" />
106
+ ) : (
107
+ <Upload className="h-8 w-8 mx-auto text-muted-foreground mb-2" />
108
+ )}
109
+ <p className="text-sm text-muted-foreground">
110
+ {uploading ? "Uploading..." : "Click or drop files here"}
111
+ </p>
112
+ <p className="text-xs text-muted-foreground mt-1">
113
+ Max 50MB per file. Multiple files supported.
114
+ </p>
115
+ <div className="flex flex-wrap justify-center gap-1 mt-2">
116
+ {["PDF", "DOCX", "XLSX", "TXT", "Images"].map((type) => (
117
+ <Badge key={type} variant="outline" className="text-xs">
118
+ {type}
119
+ </Badge>
120
+ ))}
121
+ </div>
122
+ <input
123
+ ref={inputRef}
124
+ type="file"
125
+ className="hidden"
126
+ multiple
127
+ onChange={(e) => {
128
+ if (e.target.files?.length) handleFiles(e.target.files);
129
+ e.target.value = "";
130
+ }}
131
+ />
132
+ </div>
133
+
134
+ {uploaded.length > 0 && (
135
+ <div className="space-y-1">
136
+ {uploaded.map((name, i) => (
137
+ <div key={i} className="flex items-center gap-2 text-sm">
138
+ <CheckCircle className="h-3.5 w-3.5 text-success shrink-0" />
139
+ <span className="truncate">{name}</span>
140
+ </div>
141
+ ))}
142
+ </div>
143
+ )}
144
+
145
+ <div className="flex justify-end">
146
+ <Button variant="outline" onClick={handleClose}>
147
+ Done
148
+ </Button>
149
+ </div>
150
+ </DialogContent>
151
+ </Dialog>
152
+ );
153
+ }
@@ -0,0 +1,6 @@
1
+ import type { DocumentRow } from "@/lib/db/schema";
2
+
3
+ export type DocumentWithRelations = DocumentRow & {
4
+ taskTitle: string | null;
5
+ projectName: string | null;
6
+ };
@@ -0,0 +1,57 @@
1
+ import { Image, FileCode, FileText, File, FileSpreadsheet } from "lucide-react";
2
+
3
+ export function getFileIcon(mimeType: string) {
4
+ if (mimeType.startsWith("image/")) return Image;
5
+ if (
6
+ mimeType.includes("javascript") ||
7
+ mimeType.includes("typescript") ||
8
+ mimeType.includes("python") ||
9
+ mimeType.includes("json") ||
10
+ mimeType.includes("html") ||
11
+ mimeType.includes("css")
12
+ )
13
+ return FileCode;
14
+ if (mimeType.includes("spreadsheet") || mimeType === "text/csv")
15
+ return FileSpreadsheet;
16
+ if (mimeType.startsWith("text/") || mimeType === "application/pdf")
17
+ return FileText;
18
+ return File;
19
+ }
20
+
21
+ export function formatSize(bytes: number): string {
22
+ if (bytes < 1024) return `${bytes} B`;
23
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
24
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
25
+ }
26
+
27
+ export function formatRelativeTime(timestamp: number): string {
28
+ const now = Date.now();
29
+ const diff = now - timestamp;
30
+ const seconds = Math.floor(diff / 1000);
31
+ const minutes = Math.floor(seconds / 60);
32
+ const hours = Math.floor(minutes / 60);
33
+ const days = Math.floor(hours / 24);
34
+
35
+ if (seconds < 60) return "just now";
36
+ if (minutes < 60) return `${minutes}m ago`;
37
+ if (hours < 24) return `${hours}h ago`;
38
+ if (days < 30) return `${days}d ago`;
39
+ const months = Math.floor(days / 30);
40
+ if (months < 12) return `${months}mo ago`;
41
+ return `${Math.floor(months / 12)}y ago`;
42
+ }
43
+
44
+ export function getStatusColor(status: string): string {
45
+ switch (status) {
46
+ case "ready":
47
+ return "text-status-completed border-status-completed/30";
48
+ case "processing":
49
+ return "text-status-running border-status-running/30";
50
+ case "error":
51
+ return "text-status-failed border-status-failed/30";
52
+ case "uploaded":
53
+ return "text-status-warning border-status-warning/30";
54
+ default:
55
+ return "";
56
+ }
57
+ }
@@ -0,0 +1,14 @@
1
+ "use client";
2
+
3
+ export function ConnectionIndicator({ connected }: { connected: boolean }) {
4
+ return (
5
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
6
+ <span
7
+ className={`h-2 w-2 rounded-full ${
8
+ connected ? "bg-success" : "bg-status-failed"
9
+ }`}
10
+ />
11
+ {connected ? "Connected" : "Disconnected"}
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import Link from "next/link";
5
+ import { formatTime } from "@/lib/utils/format-timestamp";
6
+
7
+ export interface LogEntryData {
8
+ id: string;
9
+ taskId: string | null;
10
+ agentType: string;
11
+ event: string;
12
+ payload: string | null;
13
+ timestamp: string;
14
+ }
15
+
16
+ const eventColors: Record<string, string> = {
17
+ tool_start: "text-primary",
18
+ content_block_start: "text-primary",
19
+ message_start: "text-primary",
20
+ content_block_delta: "text-muted-foreground",
21
+ error: "text-destructive",
22
+ completed: "text-chart-2",
23
+ };
24
+
25
+ export function LogEntry({ entry, taskName }: { entry: LogEntryData; taskName?: string }) {
26
+ const [expanded, setExpanded] = useState(false);
27
+
28
+ const time = formatTime(entry.timestamp);
29
+
30
+ let preview = "";
31
+ try {
32
+ if (entry.payload) {
33
+ const parsed = JSON.parse(entry.payload);
34
+ if (parsed.tool) {
35
+ preview = `${parsed.tool}`;
36
+ } else if (parsed.text) {
37
+ preview = parsed.text.slice(0, 80);
38
+ } else if (parsed.error) {
39
+ preview = parsed.error.slice(0, 80);
40
+ } else if (parsed.result) {
41
+ preview = parsed.result.slice(0, 80);
42
+ }
43
+ }
44
+ } catch {
45
+ preview = entry.payload?.slice(0, 80) ?? "";
46
+ }
47
+
48
+ const color = eventColors[entry.event] ?? "text-muted-foreground";
49
+
50
+ return (
51
+ <button
52
+ type="button"
53
+ className="w-full text-left font-mono text-sm py-0.5 hover:bg-muted/50 px-2 rounded cursor-pointer"
54
+ onClick={() => setExpanded(!expanded)}
55
+ aria-expanded={expanded}
56
+ aria-label={`Log entry: ${entry.event} at ${time}`}
57
+ >
58
+ <span className="text-muted-foreground">[{time}]</span>{" "}
59
+ {taskName && entry.taskId && (
60
+ <>
61
+ <Link
62
+ href={`/tasks/${entry.taskId}`}
63
+ className="text-primary hover:underline"
64
+ onClick={(e) => e.stopPropagation()}
65
+ >
66
+ {taskName}
67
+ </Link>{" "}
68
+ </>
69
+ )}
70
+ <span className={color}>[{entry.event}]</span>{" "}
71
+ <span className="text-foreground">{preview}</span>
72
+ {expanded && entry.payload && (
73
+ <pre className="text-xs text-muted-foreground bg-muted p-2 rounded mt-1 overflow-auto max-h-40 whitespace-pre-wrap">
74
+ {JSON.stringify(JSON.parse(entry.payload), null, 2)}
75
+ </pre>
76
+ )}
77
+ </button>
78
+ );
79
+ }
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectItem,
7
+ SelectTrigger,
8
+ SelectValue,
9
+ } from "@/components/ui/select";
10
+
11
+ interface LogFiltersProps {
12
+ taskId: string;
13
+ eventType: string;
14
+ tasks: { id: string; title: string }[];
15
+ onTaskChange: (value: string) => void;
16
+ onEventTypeChange: (value: string) => void;
17
+ }
18
+
19
+ export function LogFilters({
20
+ taskId,
21
+ eventType,
22
+ tasks,
23
+ onTaskChange,
24
+ onEventTypeChange,
25
+ }: LogFiltersProps) {
26
+ return (
27
+ <div className="flex items-center gap-3">
28
+ <Select value={taskId} onValueChange={onTaskChange}>
29
+ <SelectTrigger className="w-[200px]">
30
+ <SelectValue placeholder="All Tasks" />
31
+ </SelectTrigger>
32
+ <SelectContent>
33
+ <SelectItem value="all">All Tasks</SelectItem>
34
+ {tasks.map((t) => (
35
+ <SelectItem key={t.id} value={t.id}>
36
+ {t.title}
37
+ </SelectItem>
38
+ ))}
39
+ </SelectContent>
40
+ </Select>
41
+
42
+ <Select value={eventType} onValueChange={onEventTypeChange}>
43
+ <SelectTrigger className="w-[160px]">
44
+ <SelectValue placeholder="All Events" />
45
+ </SelectTrigger>
46
+ <SelectContent>
47
+ <SelectItem value="all">All Events</SelectItem>
48
+ <SelectItem value="tool_start">Tool Start</SelectItem>
49
+ <SelectItem value="content_block_start">Content Start</SelectItem>
50
+ <SelectItem value="content_block_delta">Content Delta</SelectItem>
51
+ <SelectItem value="completed">Completed</SelectItem>
52
+ <SelectItem value="error">Error</SelectItem>
53
+ </SelectContent>
54
+ </Select>
55
+ </div>
56
+ );
57
+ }