work-agent 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 (245) hide show
  1. package/README.md +234 -0
  2. package/app/(admin)/approvals/page.tsx +16 -0
  3. package/app/(admin)/audit/page.tsx +18 -0
  4. package/app/(admin)/layout.tsx +47 -0
  5. package/app/(admin)/scheduled-tasks/page.tsx +17 -0
  6. package/app/(admin)/settings/page.tsx +46 -0
  7. package/app/(admin)/skills/[name]/page.tsx +378 -0
  8. package/app/(admin)/skills/page.tsx +406 -0
  9. package/app/(admin)/statistics/page.tsx +416 -0
  10. package/app/(admin)/tickets/[id]/page.tsx +348 -0
  11. package/app/(admin)/tickets/new/page.tsx +309 -0
  12. package/app/(admin)/tickets/page.tsx +27 -0
  13. package/app/api/audit/route.ts +30 -0
  14. package/app/api/auth/feishu/callback/route.ts +72 -0
  15. package/app/api/auth/feishu/login/route.ts +17 -0
  16. package/app/api/auth/feishu/sso/route.ts +78 -0
  17. package/app/api/auth/login/route.ts +85 -0
  18. package/app/api/auth/oauth/route.ts +168 -0
  19. package/app/api/config/providers/route.ts +105 -0
  20. package/app/api/config/route.ts +115 -0
  21. package/app/api/config/status/route.ts +56 -0
  22. package/app/api/config/test/route.ts +212 -0
  23. package/app/api/documents/[id]/route.ts +88 -0
  24. package/app/api/documents/route.ts +53 -0
  25. package/app/api/health/route.ts +32 -0
  26. package/app/api/knowledge/[id]/route.ts +152 -0
  27. package/app/api/knowledge/from-session/route.ts +27 -0
  28. package/app/api/knowledge/route.ts +100 -0
  29. package/app/api/market/knowledge/[id]/route.ts +92 -0
  30. package/app/api/market/knowledge/route.ts +130 -0
  31. package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
  32. package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
  33. package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
  34. package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
  35. package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
  36. package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
  37. package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
  38. package/app/api/marketplace/skills/[id]/route.ts +177 -0
  39. package/app/api/marketplace/skills/route.ts +235 -0
  40. package/app/api/memory/route.ts +40 -0
  41. package/app/api/my/files/[id]/route.ts +52 -0
  42. package/app/api/my/files/route.ts +230 -0
  43. package/app/api/my/knowledge/route.ts +36 -0
  44. package/app/api/pi-chat/route.ts +443 -0
  45. package/app/api/recommend/route.ts +38 -0
  46. package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
  47. package/app/api/scheduled-tasks/[id]/route.ts +165 -0
  48. package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
  49. package/app/api/scheduled-tasks/route.ts +101 -0
  50. package/app/api/sessions/[id]/messages/route.ts +212 -0
  51. package/app/api/sessions/route.ts +101 -0
  52. package/app/api/share/file/[id]/route.ts +37 -0
  53. package/app/api/skills/[name]/execute/route.ts +121 -0
  54. package/app/api/skills/[name]/route.ts +167 -0
  55. package/app/api/skills/create/route.ts +65 -0
  56. package/app/api/skills/generate/route.ts +405 -0
  57. package/app/api/skills/installed/route.ts +151 -0
  58. package/app/api/skills/route.ts +174 -0
  59. package/app/api/skills/translate/route.ts +40 -0
  60. package/app/api/skills/user/[name]/route.ts +159 -0
  61. package/app/api/skills/user/route.ts +90 -0
  62. package/app/api/statistics/route.ts +94 -0
  63. package/app/api/task-executions/[id]/route.ts +34 -0
  64. package/app/api/task-executions/route.ts +29 -0
  65. package/app/api/tickets/[id]/approve/route.ts +129 -0
  66. package/app/api/tickets/[id]/execute/route.ts +201 -0
  67. package/app/api/tickets/[id]/route.ts +127 -0
  68. package/app/api/tickets/route.ts +103 -0
  69. package/app/api/user/skills/route.ts +175 -0
  70. package/app/api/users/route.ts +80 -0
  71. package/app/chat/page.tsx +5 -0
  72. package/app/globals.css +84 -0
  73. package/app/h5/layout.tsx +5 -0
  74. package/app/h5/mobile-approvals-page.tsx +167 -0
  75. package/app/h5/mobile-chat-page.tsx +951 -0
  76. package/app/h5/mobile-profile-page.tsx +147 -0
  77. package/app/h5/mobile-tickets-page.tsx +121 -0
  78. package/app/h5/page.tsx +23 -0
  79. package/app/h5/ticket-action-buttons.tsx +80 -0
  80. package/app/layout.tsx +26 -0
  81. package/app/login/page.tsx +318 -0
  82. package/app/market/knowledge/[id]/page.tsx +77 -0
  83. package/app/market/knowledge/page.tsx +358 -0
  84. package/app/market/layout.tsx +29 -0
  85. package/app/market/page.tsx +18 -0
  86. package/app/market/skills/page.tsx +397 -0
  87. package/app/my/files/page.tsx +511 -0
  88. package/app/my/knowledge/[id]/page.tsx +271 -0
  89. package/app/my/knowledge/new/page.tsx +234 -0
  90. package/app/my/knowledge/page.tsx +248 -0
  91. package/app/my/layout.tsx +32 -0
  92. package/app/my/memory/page.tsx +164 -0
  93. package/app/my/page.tsx +18 -0
  94. package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
  95. package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
  96. package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
  97. package/app/my/scheduled-tasks/new/page.tsx +230 -0
  98. package/app/my/scheduled-tasks/page.tsx +27 -0
  99. package/app/my/skills/[name]/page.tsx +320 -0
  100. package/app/my/skills/new/page.tsx +394 -0
  101. package/app/my/skills/page.tsx +303 -0
  102. package/app/page.tsx +2288 -0
  103. package/app/share/[sessionId]/page.tsx +226 -0
  104. package/app/share/file/[id]/page.tsx +140 -0
  105. package/bin/README.md +63 -0
  106. package/bin/generate-api-system +300 -0
  107. package/bin/postinstall.js +95 -0
  108. package/bin/work-agent.js +173 -0
  109. package/components/ai-elements/agent.tsx +142 -0
  110. package/components/ai-elements/artifact.tsx +149 -0
  111. package/components/ai-elements/attachments.tsx +427 -0
  112. package/components/ai-elements/audio-player.tsx +232 -0
  113. package/components/ai-elements/canvas.tsx +26 -0
  114. package/components/ai-elements/chain-of-thought.tsx +223 -0
  115. package/components/ai-elements/checkpoint.tsx +72 -0
  116. package/components/ai-elements/code-block.tsx +555 -0
  117. package/components/ai-elements/commit.tsx +449 -0
  118. package/components/ai-elements/confirmation.tsx +173 -0
  119. package/components/ai-elements/connection.tsx +28 -0
  120. package/components/ai-elements/context.tsx +410 -0
  121. package/components/ai-elements/controls.tsx +19 -0
  122. package/components/ai-elements/conversation.tsx +167 -0
  123. package/components/ai-elements/edge.tsx +144 -0
  124. package/components/ai-elements/environment-variables.tsx +325 -0
  125. package/components/ai-elements/file-tree.tsx +298 -0
  126. package/components/ai-elements/image.tsx +25 -0
  127. package/components/ai-elements/inline-citation.tsx +294 -0
  128. package/components/ai-elements/jsx-preview.tsx +250 -0
  129. package/components/ai-elements/message.tsx +367 -0
  130. package/components/ai-elements/mic-selector.tsx +372 -0
  131. package/components/ai-elements/model-selector.tsx +214 -0
  132. package/components/ai-elements/node.tsx +72 -0
  133. package/components/ai-elements/open-in-chat.tsx +367 -0
  134. package/components/ai-elements/package-info.tsx +235 -0
  135. package/components/ai-elements/panel.tsx +16 -0
  136. package/components/ai-elements/persona.tsx +280 -0
  137. package/components/ai-elements/plan.tsx +144 -0
  138. package/components/ai-elements/prompt-input.tsx +1341 -0
  139. package/components/ai-elements/queue.tsx +275 -0
  140. package/components/ai-elements/reasoning.tsx +355 -0
  141. package/components/ai-elements/sandbox.tsx +133 -0
  142. package/components/ai-elements/schema-display.tsx +473 -0
  143. package/components/ai-elements/shimmer.tsx +78 -0
  144. package/components/ai-elements/snippet.tsx +141 -0
  145. package/components/ai-elements/sources.tsx +78 -0
  146. package/components/ai-elements/speech-input.tsx +324 -0
  147. package/components/ai-elements/stack-trace.tsx +531 -0
  148. package/components/ai-elements/suggestion.tsx +58 -0
  149. package/components/ai-elements/task.tsx +88 -0
  150. package/components/ai-elements/terminal.tsx +277 -0
  151. package/components/ai-elements/test-results.tsx +497 -0
  152. package/components/ai-elements/tool.tsx +174 -0
  153. package/components/ai-elements/toolbar.tsx +17 -0
  154. package/components/ai-elements/transcription.tsx +126 -0
  155. package/components/ai-elements/voice-selector.tsx +525 -0
  156. package/components/ai-elements/web-preview.tsx +282 -0
  157. package/components/audit-log-list.tsx +114 -0
  158. package/components/chat/EmptyPreviewState.tsx +12 -0
  159. package/components/chat/KnowledgePickerDialog.tsx +464 -0
  160. package/components/chat/KnowledgePreview.tsx +70 -0
  161. package/components/chat/KnowledgePreviewPanel.tsx +86 -0
  162. package/components/chat/MentionInput.tsx +309 -0
  163. package/components/chat/OrganizeDialog.tsx +258 -0
  164. package/components/chat/RecommendationBanner.tsx +94 -0
  165. package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
  166. package/components/chat/SkillSelector.tsx +305 -0
  167. package/components/chat/SkillSwitcher.tsx +163 -0
  168. package/components/client-layout.tsx +15 -0
  169. package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
  170. package/components/layout-wrapper.tsx +18 -0
  171. package/components/mobile-layout.tsx +62 -0
  172. package/components/scheduled-task-list.tsx +356 -0
  173. package/components/setup-guide.tsx +484 -0
  174. package/components/sub-nav.tsx +54 -0
  175. package/components/ticket-detail-content.tsx +383 -0
  176. package/components/ticket-list.tsx +366 -0
  177. package/components/top-nav.tsx +132 -0
  178. package/components/ui/accordion.tsx +58 -0
  179. package/components/ui/alert.tsx +59 -0
  180. package/components/ui/avatar.tsx +50 -0
  181. package/components/ui/badge.tsx +36 -0
  182. package/components/ui/button-group.tsx +83 -0
  183. package/components/ui/button.tsx +57 -0
  184. package/components/ui/card.tsx +91 -0
  185. package/components/ui/carousel.tsx +262 -0
  186. package/components/ui/collapsible.tsx +11 -0
  187. package/components/ui/command.tsx +153 -0
  188. package/components/ui/dialog.tsx +122 -0
  189. package/components/ui/dropdown-menu.tsx +200 -0
  190. package/components/ui/hover-card.tsx +29 -0
  191. package/components/ui/input-group.tsx +170 -0
  192. package/components/ui/input.tsx +22 -0
  193. package/components/ui/label.tsx +26 -0
  194. package/components/ui/popover.tsx +31 -0
  195. package/components/ui/progress.tsx +28 -0
  196. package/components/ui/scroll-area.tsx +48 -0
  197. package/components/ui/select.tsx +174 -0
  198. package/components/ui/separator.tsx +31 -0
  199. package/components/ui/spinner.tsx +16 -0
  200. package/components/ui/switch.tsx +29 -0
  201. package/components/ui/table.tsx +120 -0
  202. package/components/ui/tabs.tsx +55 -0
  203. package/components/ui/textarea.tsx +22 -0
  204. package/components/ui/tooltip.tsx +30 -0
  205. package/components/welcome-guide.tsx +182 -0
  206. package/components.json +24 -0
  207. package/lib/command-parser.ts +331 -0
  208. package/lib/dangerous-commands.ts +672 -0
  209. package/lib/db.ts +2250 -0
  210. package/lib/feishu-auth.ts +135 -0
  211. package/lib/file-storage.ts +306 -0
  212. package/lib/file-tool.ts +583 -0
  213. package/lib/knowledge-tool.ts +152 -0
  214. package/lib/knowledge-types.ts +66 -0
  215. package/lib/market-client.ts +313 -0
  216. package/lib/market-db.ts +736 -0
  217. package/lib/market-types.ts +51 -0
  218. package/lib/memory-tool.ts +211 -0
  219. package/lib/memory.ts +197 -0
  220. package/lib/pi-config.ts +436 -0
  221. package/lib/pi-session.ts +799 -0
  222. package/lib/pinyin.ts +13 -0
  223. package/lib/recommendation.ts +227 -0
  224. package/lib/risk-estimator.ts +350 -0
  225. package/lib/scheduled-task-tool.ts +184 -0
  226. package/lib/scheduler-init.ts +43 -0
  227. package/lib/scheduler.ts +416 -0
  228. package/lib/secure-bash-tool.ts +413 -0
  229. package/lib/skill-engine.ts +396 -0
  230. package/lib/skill-generator.ts +269 -0
  231. package/lib/skill-loader.ts +234 -0
  232. package/lib/skill-tool.ts +188 -0
  233. package/lib/skill-types.ts +82 -0
  234. package/lib/skills-init.ts +58 -0
  235. package/lib/ticket-tool.ts +246 -0
  236. package/lib/user-skill-types.ts +30 -0
  237. package/lib/user-skills.ts +362 -0
  238. package/lib/utils.ts +6 -0
  239. package/lib/workflow.ts +154 -0
  240. package/lib/zip-tool.ts +191 -0
  241. package/next.config.js +8 -0
  242. package/package.json +106 -0
  243. package/public/.gitkeep +1 -0
  244. package/public/icon.svg +1 -0
  245. package/tsconfig.json +42 -0
@@ -0,0 +1,531 @@
1
+ "use client";
2
+
3
+ import type { ComponentProps } from "react";
4
+
5
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ Collapsible,
9
+ CollapsibleContent,
10
+ CollapsibleTrigger,
11
+ } from "@/components/ui/collapsible";
12
+ import { cn } from "@/lib/utils";
13
+ import {
14
+ AlertTriangleIcon,
15
+ CheckIcon,
16
+ ChevronDownIcon,
17
+ CopyIcon,
18
+ } from "lucide-react";
19
+ import {
20
+ createContext,
21
+ memo,
22
+ useCallback,
23
+ useContext,
24
+ useEffect,
25
+ useMemo,
26
+ useRef,
27
+ useState,
28
+ } from "react";
29
+
30
+ // Regex patterns for parsing stack traces
31
+ const STACK_FRAME_WITH_PARENS_REGEX = /^at\s+(.+?)\s+\((.+):(\d+):(\d+)\)$/;
32
+ const STACK_FRAME_WITHOUT_FN_REGEX = /^at\s+(.+):(\d+):(\d+)$/;
33
+ const ERROR_TYPE_REGEX = /^(\w+Error|Error):\s*(.*)$/;
34
+ const AT_PREFIX_REGEX = /^at\s+/;
35
+
36
+ interface StackFrame {
37
+ raw: string;
38
+ functionName: string | null;
39
+ filePath: string | null;
40
+ lineNumber: number | null;
41
+ columnNumber: number | null;
42
+ isInternal: boolean;
43
+ }
44
+
45
+ interface ParsedStackTrace {
46
+ errorType: string | null;
47
+ errorMessage: string;
48
+ frames: StackFrame[];
49
+ raw: string;
50
+ }
51
+
52
+ interface StackTraceContextValue {
53
+ trace: ParsedStackTrace;
54
+ raw: string;
55
+ isOpen: boolean;
56
+ setIsOpen: (open: boolean) => void;
57
+ onFilePathClick?: (filePath: string, line?: number, column?: number) => void;
58
+ }
59
+
60
+ const StackTraceContext = createContext<StackTraceContextValue | null>(null);
61
+
62
+ const useStackTrace = () => {
63
+ const context = useContext(StackTraceContext);
64
+ if (!context) {
65
+ throw new Error("StackTrace components must be used within StackTrace");
66
+ }
67
+ return context;
68
+ };
69
+
70
+ const parseStackFrame = (line: string): StackFrame => {
71
+ const trimmed = line.trim();
72
+
73
+ // Pattern: at functionName (filePath:line:column)
74
+ const withParensMatch = trimmed.match(STACK_FRAME_WITH_PARENS_REGEX);
75
+ if (withParensMatch) {
76
+ const [, functionName, filePath, lineNum, colNum] = withParensMatch;
77
+ const isInternal =
78
+ filePath.includes("node_modules") ||
79
+ filePath.startsWith("node:") ||
80
+ filePath.includes("internal/");
81
+ return {
82
+ columnNumber: colNum ? Number.parseInt(colNum, 10) : null,
83
+ filePath: filePath ?? null,
84
+ functionName: functionName ?? null,
85
+ isInternal,
86
+ lineNumber: lineNum ? Number.parseInt(lineNum, 10) : null,
87
+ raw: trimmed,
88
+ };
89
+ }
90
+
91
+ // Pattern: at filePath:line:column (no function name)
92
+ const withoutFnMatch = trimmed.match(STACK_FRAME_WITHOUT_FN_REGEX);
93
+ if (withoutFnMatch) {
94
+ const [, filePath, lineNum, colNum] = withoutFnMatch;
95
+ const isInternal =
96
+ (filePath?.includes("node_modules") ?? false) ||
97
+ (filePath?.startsWith("node:") ?? false) ||
98
+ (filePath?.includes("internal/") ?? false);
99
+ return {
100
+ columnNumber: colNum ? Number.parseInt(colNum, 10) : null,
101
+ filePath: filePath ?? null,
102
+ functionName: null,
103
+ isInternal,
104
+ lineNumber: lineNum ? Number.parseInt(lineNum, 10) : null,
105
+ raw: trimmed,
106
+ };
107
+ }
108
+
109
+ // Fallback: unparseable line
110
+ return {
111
+ columnNumber: null,
112
+ filePath: null,
113
+ functionName: null,
114
+ isInternal: trimmed.includes("node_modules") || trimmed.includes("node:"),
115
+ lineNumber: null,
116
+ raw: trimmed,
117
+ };
118
+ };
119
+
120
+ const parseStackTrace = (trace: string): ParsedStackTrace => {
121
+ const lines = trace.split("\n").filter((line) => line.trim());
122
+
123
+ if (lines.length === 0) {
124
+ return {
125
+ errorMessage: trace,
126
+ errorType: null,
127
+ frames: [],
128
+ raw: trace,
129
+ };
130
+ }
131
+
132
+ const firstLine = lines[0].trim();
133
+ let errorType: string | null = null;
134
+ let errorMessage = firstLine;
135
+
136
+ // Try to extract error type from "ErrorType: message" format
137
+ const errorMatch = firstLine.match(ERROR_TYPE_REGEX);
138
+ if (errorMatch) {
139
+ const [, type, msg] = errorMatch;
140
+ errorType = type;
141
+ errorMessage = msg || "";
142
+ }
143
+
144
+ // Parse stack frames (lines starting with "at")
145
+ const frames = lines
146
+ .slice(1)
147
+ .filter((line) => line.trim().startsWith("at "))
148
+ .map(parseStackFrame);
149
+
150
+ return {
151
+ errorMessage,
152
+ errorType,
153
+ frames,
154
+ raw: trace,
155
+ };
156
+ };
157
+
158
+ export type StackTraceProps = ComponentProps<"div"> & {
159
+ trace: string;
160
+ open?: boolean;
161
+ defaultOpen?: boolean;
162
+ onOpenChange?: (open: boolean) => void;
163
+ onFilePathClick?: (filePath: string, line?: number, column?: number) => void;
164
+ };
165
+
166
+ export const StackTrace = memo(
167
+ ({
168
+ trace,
169
+ className,
170
+ open,
171
+ defaultOpen = false,
172
+ onOpenChange,
173
+ onFilePathClick,
174
+ children,
175
+ ...props
176
+ }: StackTraceProps) => {
177
+ const [isOpen, setIsOpen] = useControllableState({
178
+ defaultProp: defaultOpen,
179
+ onChange: onOpenChange,
180
+ prop: open,
181
+ });
182
+
183
+ const parsedTrace = useMemo(() => parseStackTrace(trace), [trace]);
184
+
185
+ const contextValue = useMemo(
186
+ () => ({
187
+ isOpen,
188
+ onFilePathClick,
189
+ raw: trace,
190
+ setIsOpen,
191
+ trace: parsedTrace,
192
+ }),
193
+ [parsedTrace, trace, isOpen, setIsOpen, onFilePathClick]
194
+ );
195
+
196
+ return (
197
+ <StackTraceContext.Provider value={contextValue}>
198
+ <div
199
+ className={cn(
200
+ "not-prose w-full overflow-hidden rounded-lg border bg-background font-mono text-sm",
201
+ className
202
+ )}
203
+ {...props}
204
+ >
205
+ {children}
206
+ </div>
207
+ </StackTraceContext.Provider>
208
+ );
209
+ }
210
+ );
211
+
212
+ export type StackTraceHeaderProps = ComponentProps<typeof CollapsibleTrigger>;
213
+
214
+ export const StackTraceHeader = memo(
215
+ ({ className, children, ...props }: StackTraceHeaderProps) => {
216
+ const { isOpen, setIsOpen } = useStackTrace();
217
+
218
+ return (
219
+ <Collapsible onOpenChange={setIsOpen} open={isOpen}>
220
+ <CollapsibleTrigger asChild {...props}>
221
+ <div
222
+ className={cn(
223
+ "flex w-full cursor-pointer items-center gap-3 p-3 text-left transition-colors hover:bg-muted/50",
224
+ className
225
+ )}
226
+ >
227
+ {children}
228
+ </div>
229
+ </CollapsibleTrigger>
230
+ </Collapsible>
231
+ );
232
+ }
233
+ );
234
+
235
+ export type StackTraceErrorProps = ComponentProps<"div">;
236
+
237
+ export const StackTraceError = memo(
238
+ ({ className, children, ...props }: StackTraceErrorProps) => (
239
+ <div
240
+ className={cn(
241
+ "flex flex-1 items-center gap-2 overflow-hidden",
242
+ className
243
+ )}
244
+ {...props}
245
+ >
246
+ <AlertTriangleIcon className="size-4 shrink-0 text-destructive" />
247
+ {children}
248
+ </div>
249
+ )
250
+ );
251
+
252
+ export type StackTraceErrorTypeProps = ComponentProps<"span">;
253
+
254
+ export const StackTraceErrorType = memo(
255
+ ({ className, children, ...props }: StackTraceErrorTypeProps) => {
256
+ const { trace } = useStackTrace();
257
+
258
+ return (
259
+ <span
260
+ className={cn("shrink-0 font-semibold text-destructive", className)}
261
+ {...props}
262
+ >
263
+ {children ?? trace.errorType}
264
+ </span>
265
+ );
266
+ }
267
+ );
268
+
269
+ export type StackTraceErrorMessageProps = ComponentProps<"span">;
270
+
271
+ export const StackTraceErrorMessage = memo(
272
+ ({ className, children, ...props }: StackTraceErrorMessageProps) => {
273
+ const { trace } = useStackTrace();
274
+
275
+ return (
276
+ <span className={cn("truncate text-foreground", className)} {...props}>
277
+ {children ?? trace.errorMessage}
278
+ </span>
279
+ );
280
+ }
281
+ );
282
+
283
+ export type StackTraceActionsProps = ComponentProps<"div">;
284
+
285
+ const handleActionsClick = (e: React.MouseEvent) => e.stopPropagation();
286
+ const handleActionsKeyDown = (e: React.KeyboardEvent) => {
287
+ if (e.key === "Enter" || e.key === " ") {
288
+ e.stopPropagation();
289
+ }
290
+ };
291
+
292
+ export const StackTraceActions = memo(
293
+ ({ className, children, ...props }: StackTraceActionsProps) => (
294
+ // biome-ignore lint/a11y/noNoninteractiveElementInteractions: stopPropagation required for nested interactions
295
+ // biome-ignore lint/a11y/useSemanticElements: fieldset doesn't fit this UI pattern
296
+ <div
297
+ className={cn("flex shrink-0 items-center gap-1", className)}
298
+ onClick={handleActionsClick}
299
+ onKeyDown={handleActionsKeyDown}
300
+ role="group"
301
+ {...props}
302
+ >
303
+ {children}
304
+ </div>
305
+ )
306
+ );
307
+
308
+ export type StackTraceCopyButtonProps = ComponentProps<typeof Button> & {
309
+ onCopy?: () => void;
310
+ onError?: (error: Error) => void;
311
+ timeout?: number;
312
+ };
313
+
314
+ export const StackTraceCopyButton = memo(
315
+ ({
316
+ onCopy,
317
+ onError,
318
+ timeout = 2000,
319
+ className,
320
+ children,
321
+ ...props
322
+ }: StackTraceCopyButtonProps) => {
323
+ const [isCopied, setIsCopied] = useState(false);
324
+ const timeoutRef = useRef<number>(0);
325
+ const { raw } = useStackTrace();
326
+
327
+ const copyToClipboard = useCallback(async () => {
328
+ if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
329
+ onError?.(new Error("Clipboard API not available"));
330
+ return;
331
+ }
332
+
333
+ try {
334
+ await navigator.clipboard.writeText(raw);
335
+ setIsCopied(true);
336
+ onCopy?.();
337
+ timeoutRef.current = window.setTimeout(
338
+ () => setIsCopied(false),
339
+ timeout
340
+ );
341
+ } catch (error) {
342
+ onError?.(error as Error);
343
+ }
344
+ }, [raw, onCopy, onError, timeout]);
345
+
346
+ useEffect(
347
+ () => () => {
348
+ window.clearTimeout(timeoutRef.current);
349
+ },
350
+ []
351
+ );
352
+
353
+ const Icon = isCopied ? CheckIcon : CopyIcon;
354
+
355
+ return (
356
+ <Button
357
+ className={cn("size-7", className)}
358
+ onClick={copyToClipboard}
359
+ size="icon"
360
+ variant="ghost"
361
+ {...props}
362
+ >
363
+ {children ?? <Icon size={14} />}
364
+ </Button>
365
+ );
366
+ }
367
+ );
368
+
369
+ export type StackTraceExpandButtonProps = ComponentProps<"div">;
370
+
371
+ export const StackTraceExpandButton = memo(
372
+ ({ className, ...props }: StackTraceExpandButtonProps) => {
373
+ const { isOpen } = useStackTrace();
374
+
375
+ return (
376
+ <div
377
+ className={cn("flex size-7 items-center justify-center", className)}
378
+ {...props}
379
+ >
380
+ <ChevronDownIcon
381
+ className={cn(
382
+ "size-4 text-muted-foreground transition-transform",
383
+ isOpen ? "rotate-180" : "rotate-0"
384
+ )}
385
+ />
386
+ </div>
387
+ );
388
+ }
389
+ );
390
+
391
+ export type StackTraceContentProps = ComponentProps<
392
+ typeof CollapsibleContent
393
+ > & {
394
+ maxHeight?: number;
395
+ };
396
+
397
+ export const StackTraceContent = memo(
398
+ ({
399
+ className,
400
+ maxHeight = 400,
401
+ children,
402
+ ...props
403
+ }: StackTraceContentProps) => {
404
+ const { isOpen } = useStackTrace();
405
+
406
+ return (
407
+ <Collapsible open={isOpen}>
408
+ <CollapsibleContent
409
+ className={cn(
410
+ "overflow-auto border-t bg-muted/30",
411
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=open]:animate-in",
412
+ className
413
+ )}
414
+ style={{ maxHeight }}
415
+ {...props}
416
+ >
417
+ {children}
418
+ </CollapsibleContent>
419
+ </Collapsible>
420
+ );
421
+ }
422
+ );
423
+
424
+ export type StackTraceFramesProps = ComponentProps<"div"> & {
425
+ showInternalFrames?: boolean;
426
+ };
427
+
428
+ interface FilePathButtonProps {
429
+ frame: StackFrame;
430
+ onFilePathClick?: (
431
+ filePath: string,
432
+ lineNumber?: number,
433
+ columnNumber?: number
434
+ ) => void;
435
+ }
436
+
437
+ const FilePathButton = memo(
438
+ ({ frame, onFilePathClick }: FilePathButtonProps) => {
439
+ const handleClick = useCallback(() => {
440
+ if (frame.filePath) {
441
+ onFilePathClick?.(
442
+ frame.filePath,
443
+ frame.lineNumber ?? undefined,
444
+ frame.columnNumber ?? undefined
445
+ );
446
+ }
447
+ }, [frame, onFilePathClick]);
448
+
449
+ return (
450
+ <button
451
+ className={cn(
452
+ "underline decoration-dotted hover:text-primary",
453
+ onFilePathClick && "cursor-pointer"
454
+ )}
455
+ disabled={!onFilePathClick}
456
+ onClick={handleClick}
457
+ type="button"
458
+ >
459
+ {frame.filePath}
460
+ {frame.lineNumber !== null && `:${frame.lineNumber}`}
461
+ {frame.columnNumber !== null && `:${frame.columnNumber}`}
462
+ </button>
463
+ );
464
+ }
465
+ );
466
+
467
+ FilePathButton.displayName = "FilePathButton";
468
+
469
+ export const StackTraceFrames = memo(
470
+ ({
471
+ className,
472
+ showInternalFrames = true,
473
+ ...props
474
+ }: StackTraceFramesProps) => {
475
+ const { trace, onFilePathClick } = useStackTrace();
476
+
477
+ const framesToShow = showInternalFrames
478
+ ? trace.frames
479
+ : trace.frames.filter((f) => !f.isInternal);
480
+
481
+ return (
482
+ <div className={cn("space-y-1 p-3", className)} {...props}>
483
+ {framesToShow.map((frame, index) => (
484
+ <div
485
+ className={cn(
486
+ "text-xs",
487
+ frame.isInternal
488
+ ? "text-muted-foreground/50"
489
+ : "text-foreground/90"
490
+ )}
491
+ key={`${frame.raw}-${index}`}
492
+ >
493
+ <span className="text-muted-foreground">at </span>
494
+ {frame.functionName && (
495
+ <span className={frame.isInternal ? "" : "text-foreground"}>
496
+ {frame.functionName}{" "}
497
+ </span>
498
+ )}
499
+ {frame.filePath && (
500
+ <>
501
+ <span className="text-muted-foreground">(</span>
502
+ <FilePathButton
503
+ frame={frame}
504
+ onFilePathClick={onFilePathClick}
505
+ />
506
+ <span className="text-muted-foreground">)</span>
507
+ </>
508
+ )}
509
+ {!(frame.filePath || frame.functionName) && (
510
+ <span>{frame.raw.replace(AT_PREFIX_REGEX, "")}</span>
511
+ )}
512
+ </div>
513
+ ))}
514
+ {framesToShow.length === 0 && (
515
+ <div className="text-muted-foreground text-xs">No stack frames</div>
516
+ )}
517
+ </div>
518
+ );
519
+ }
520
+ );
521
+
522
+ StackTrace.displayName = "StackTrace";
523
+ StackTraceHeader.displayName = "StackTraceHeader";
524
+ StackTraceError.displayName = "StackTraceError";
525
+ StackTraceErrorType.displayName = "StackTraceErrorType";
526
+ StackTraceErrorMessage.displayName = "StackTraceErrorMessage";
527
+ StackTraceActions.displayName = "StackTraceActions";
528
+ StackTraceCopyButton.displayName = "StackTraceCopyButton";
529
+ StackTraceExpandButton.displayName = "StackTraceExpandButton";
530
+ StackTraceContent.displayName = "StackTraceContent";
531
+ StackTraceFrames.displayName = "StackTraceFrames";
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import type { ComponentProps } from "react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ ScrollArea,
8
+ ScrollBar,
9
+ } from "@/components/ui/scroll-area";
10
+ import { cn } from "@/lib/utils";
11
+ import { useCallback } from "react";
12
+
13
+ export type SuggestionsProps = ComponentProps<typeof ScrollArea>;
14
+
15
+ export const Suggestions = ({
16
+ className,
17
+ children,
18
+ ...props
19
+ }: SuggestionsProps) => (
20
+ <ScrollArea className="w-full overflow-x-auto whitespace-nowrap" {...props}>
21
+ <div className={cn("flex w-max flex-nowrap items-center gap-2", className)}>
22
+ {children}
23
+ </div>
24
+ <ScrollBar className="hidden" orientation="horizontal" />
25
+ </ScrollArea>
26
+ );
27
+
28
+ export type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {
29
+ suggestion: string;
30
+ onClick?: (suggestion: string) => void;
31
+ };
32
+
33
+ export const Suggestion = ({
34
+ suggestion,
35
+ onClick,
36
+ className,
37
+ variant = "outline",
38
+ size = "sm",
39
+ children,
40
+ ...props
41
+ }: SuggestionProps) => {
42
+ const handleClick = useCallback(() => {
43
+ onClick?.(suggestion);
44
+ }, [onClick, suggestion]);
45
+
46
+ return (
47
+ <Button
48
+ className={cn("cursor-pointer rounded-full px-4", className)}
49
+ onClick={handleClick}
50
+ size={size}
51
+ type="button"
52
+ variant={variant}
53
+ {...props}
54
+ >
55
+ {children || suggestion}
56
+ </Button>
57
+ );
58
+ };
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+ import type { ComponentProps } from "react";
4
+
5
+ import {
6
+ Collapsible,
7
+ CollapsibleContent,
8
+ CollapsibleTrigger,
9
+ } from "@/components/ui/collapsible";
10
+ import { cn } from "@/lib/utils";
11
+ import { ChevronDownIcon, SearchIcon } from "lucide-react";
12
+
13
+ export type TaskItemFileProps = ComponentProps<"div">;
14
+
15
+ export const TaskItemFile = ({
16
+ children,
17
+ className,
18
+ ...props
19
+ }: TaskItemFileProps) => (
20
+ <div
21
+ className={cn(
22
+ "inline-flex items-center gap-1 rounded-md border bg-secondary px-1.5 py-0.5 text-foreground text-xs",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ </div>
29
+ );
30
+
31
+ export type TaskItemProps = ComponentProps<"div">;
32
+
33
+ export const TaskItem = ({ children, className, ...props }: TaskItemProps) => (
34
+ <div className={cn("text-muted-foreground text-sm", className)} {...props}>
35
+ {children}
36
+ </div>
37
+ );
38
+
39
+ export type TaskProps = ComponentProps<typeof Collapsible>;
40
+
41
+ export const Task = ({
42
+ defaultOpen = true,
43
+ className,
44
+ ...props
45
+ }: TaskProps) => (
46
+ <Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />
47
+ );
48
+
49
+ export type TaskTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
50
+ title: string;
51
+ };
52
+
53
+ export const TaskTrigger = ({
54
+ children,
55
+ className,
56
+ title,
57
+ ...props
58
+ }: TaskTriggerProps) => (
59
+ <CollapsibleTrigger asChild className={cn("group", className)} {...props}>
60
+ {children ?? (
61
+ <div className="flex w-full cursor-pointer items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground">
62
+ <SearchIcon className="size-4" />
63
+ <p className="text-sm">{title}</p>
64
+ <ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />
65
+ </div>
66
+ )}
67
+ </CollapsibleTrigger>
68
+ );
69
+
70
+ export type TaskContentProps = ComponentProps<typeof CollapsibleContent>;
71
+
72
+ export const TaskContent = ({
73
+ children,
74
+ className,
75
+ ...props
76
+ }: TaskContentProps) => (
77
+ <CollapsibleContent
78
+ className={cn(
79
+ "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
80
+ className
81
+ )}
82
+ {...props}
83
+ >
84
+ <div className="mt-4 space-y-2 border-muted border-l-2 pl-4">
85
+ {children}
86
+ </div>
87
+ </CollapsibleContent>
88
+ );