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,250 @@
1
+ "use client";
2
+
3
+ import type { ComponentProps, ReactNode } from "react";
4
+ import type { TProps as JsxParserProps } from "react-jsx-parser";
5
+
6
+ import { cn } from "@/lib/utils";
7
+ import { AlertCircle } from "lucide-react";
8
+ import {
9
+ createContext,
10
+ memo,
11
+ useCallback,
12
+ useContext,
13
+ useEffect,
14
+ useMemo,
15
+ useRef,
16
+ useState,
17
+ } from "react";
18
+ import JsxParser from "react-jsx-parser";
19
+
20
+ interface JSXPreviewContextValue {
21
+ jsx: string;
22
+ processedJsx: string;
23
+ error: Error | null;
24
+ setError: (error: Error | null) => void;
25
+ components: JsxParserProps["components"];
26
+ bindings: JsxParserProps["bindings"];
27
+ onErrorProp?: (error: Error) => void;
28
+ }
29
+
30
+ const JSXPreviewContext = createContext<JSXPreviewContextValue | null>(null);
31
+
32
+ const TAG_REGEX = /<\/?([a-zA-Z][a-zA-Z0-9]*)\s*([^>]*?)(\/)?>/;
33
+
34
+ export const useJSXPreview = () => {
35
+ const context = useContext(JSXPreviewContext);
36
+ if (!context) {
37
+ throw new Error("JSXPreview components must be used within JSXPreview");
38
+ }
39
+ return context;
40
+ };
41
+
42
+ const matchJsxTag = (code: string) => {
43
+ if (code.trim() === "") {
44
+ return null;
45
+ }
46
+
47
+ const match = code.match(TAG_REGEX);
48
+
49
+ if (!match || match.index === undefined) {
50
+ return null;
51
+ }
52
+
53
+ const [fullMatch, tagName, attributes, selfClosing] = match;
54
+
55
+ let type: "self-closing" | "closing" | "opening";
56
+ if (selfClosing) {
57
+ type = "self-closing";
58
+ } else if (fullMatch.startsWith("</")) {
59
+ type = "closing";
60
+ } else {
61
+ type = "opening";
62
+ }
63
+
64
+ return {
65
+ attributes: attributes.trim(),
66
+ endIndex: match.index + fullMatch.length,
67
+ startIndex: match.index,
68
+ tag: fullMatch,
69
+ tagName,
70
+ type,
71
+ };
72
+ };
73
+
74
+ const completeJsxTag = (code: string) => {
75
+ const stack: string[] = [];
76
+ let result = "";
77
+ let currentPosition = 0;
78
+
79
+ while (currentPosition < code.length) {
80
+ const match = matchJsxTag(code.slice(currentPosition));
81
+ if (!match) {
82
+ // No more tags found, append remaining content
83
+ result += code.slice(currentPosition);
84
+ break;
85
+ }
86
+ const { tagName, type, endIndex } = match;
87
+
88
+ // Include any text content before this tag
89
+ result += code.slice(currentPosition, currentPosition + endIndex);
90
+
91
+ if (type === "opening") {
92
+ stack.push(tagName);
93
+ } else if (type === "closing") {
94
+ stack.pop();
95
+ }
96
+
97
+ currentPosition += endIndex;
98
+ }
99
+
100
+ return (
101
+ result +
102
+ stack
103
+ .toReversed()
104
+ .map((tag) => `</${tag}>`)
105
+ .join("")
106
+ );
107
+ };
108
+
109
+ export type JSXPreviewProps = ComponentProps<"div"> & {
110
+ jsx: string;
111
+ isStreaming?: boolean;
112
+ components?: JsxParserProps["components"];
113
+ bindings?: JsxParserProps["bindings"];
114
+ onError?: (error: Error) => void;
115
+ };
116
+
117
+ export const JSXPreview = memo(
118
+ ({
119
+ jsx,
120
+ isStreaming = false,
121
+ components,
122
+ bindings,
123
+ onError,
124
+ className,
125
+ children,
126
+ ...props
127
+ }: JSXPreviewProps) => {
128
+ const [prevJsx, setPrevJsx] = useState(jsx);
129
+ const [error, setError] = useState<Error | null>(null);
130
+
131
+ // Clear error when jsx changes (derived state pattern)
132
+ if (jsx !== prevJsx) {
133
+ setPrevJsx(jsx);
134
+ setError(null);
135
+ }
136
+
137
+ const processedJsx = useMemo(
138
+ () => (isStreaming ? completeJsxTag(jsx) : jsx),
139
+ [jsx, isStreaming]
140
+ );
141
+
142
+ return (
143
+ <JSXPreviewContext.Provider
144
+ value={{
145
+ bindings,
146
+ components,
147
+ error,
148
+ jsx,
149
+ onErrorProp: onError,
150
+ processedJsx,
151
+ setError,
152
+ }}
153
+ >
154
+ <div className={cn("relative", className)} {...props}>
155
+ {children}
156
+ </div>
157
+ </JSXPreviewContext.Provider>
158
+ );
159
+ }
160
+ );
161
+
162
+ JSXPreview.displayName = "JSXPreview";
163
+
164
+ export type JSXPreviewContentProps = Omit<ComponentProps<"div">, "children">;
165
+
166
+ export const JSXPreviewContent = memo(
167
+ ({ className, ...props }: JSXPreviewContentProps) => {
168
+ const { processedJsx, components, bindings, setError, onErrorProp } =
169
+ useJSXPreview();
170
+ const errorReportedRef = useRef<string | null>(null);
171
+
172
+ // Reset error tracking when jsx changes
173
+ // biome-ignore lint/correctness/useExhaustiveDependencies: processedJsx change should reset tracking
174
+ useEffect(() => {
175
+ errorReportedRef.current = null;
176
+ }, [processedJsx]);
177
+
178
+ const handleError = useCallback(
179
+ (err: Error) => {
180
+ // Prevent duplicate error reports for the same jsx
181
+ if (errorReportedRef.current === processedJsx) {
182
+ return;
183
+ }
184
+ errorReportedRef.current = processedJsx;
185
+ setError(err);
186
+ onErrorProp?.(err);
187
+ },
188
+ [processedJsx, onErrorProp, setError]
189
+ );
190
+
191
+ return (
192
+ <div className={cn("jsx-preview-content", className)} {...props}>
193
+ <JsxParser
194
+ bindings={bindings}
195
+ components={components}
196
+ jsx={processedJsx}
197
+ onError={handleError}
198
+ renderInWrapper={false}
199
+ />
200
+ </div>
201
+ );
202
+ }
203
+ );
204
+
205
+ JSXPreviewContent.displayName = "JSXPreviewContent";
206
+
207
+ export type JSXPreviewErrorProps = ComponentProps<"div"> & {
208
+ children?: ReactNode | ((error: Error) => ReactNode);
209
+ };
210
+
211
+ const renderChildren = (
212
+ children: ReactNode | ((error: Error) => ReactNode),
213
+ error: Error
214
+ ): ReactNode => {
215
+ if (typeof children === "function") {
216
+ return children(error);
217
+ }
218
+ return children;
219
+ };
220
+
221
+ export const JSXPreviewError = memo(
222
+ ({ className, children, ...props }: JSXPreviewErrorProps) => {
223
+ const { error } = useJSXPreview();
224
+
225
+ if (!error) {
226
+ return null;
227
+ }
228
+
229
+ return (
230
+ <div
231
+ className={cn(
232
+ "flex items-center gap-2 rounded-md border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm",
233
+ className
234
+ )}
235
+ {...props}
236
+ >
237
+ {children ? (
238
+ renderChildren(children, error)
239
+ ) : (
240
+ <>
241
+ <AlertCircle className="size-4 shrink-0" />
242
+ <span>{error.message}</span>
243
+ </>
244
+ )}
245
+ </div>
246
+ );
247
+ }
248
+ );
249
+
250
+ JSXPreviewError.displayName = "JSXPreviewError";
@@ -0,0 +1,367 @@
1
+ "use client";
2
+
3
+ import type { UIMessage } from "ai";
4
+ import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
5
+
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ ButtonGroup,
9
+ ButtonGroupText,
10
+ } from "@/components/ui/button-group";
11
+ import {
12
+ Tooltip,
13
+ TooltipContent,
14
+ TooltipProvider,
15
+ TooltipTrigger,
16
+ } from "@/components/ui/tooltip";
17
+ import { cn } from "@/lib/utils";
18
+ import { cjk } from "@streamdown/cjk";
19
+ import { code } from "@streamdown/code";
20
+ import { math } from "@streamdown/math";
21
+ import { mermaid } from "@streamdown/mermaid";
22
+ import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
23
+ import {
24
+ createContext,
25
+ memo,
26
+ useCallback,
27
+ useContext,
28
+ useEffect,
29
+ useMemo,
30
+ useState,
31
+ } from "react";
32
+ import { Streamdown } from "streamdown";
33
+
34
+ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
35
+ from: UIMessage["role"];
36
+ };
37
+
38
+ export const Message = ({ className, from, ...props }: MessageProps) => (
39
+ <div
40
+ className={cn(
41
+ "group flex w-full max-w-[95%] flex-col gap-2",
42
+ from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ );
48
+
49
+ export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
50
+
51
+ export const MessageContent = ({
52
+ children,
53
+ className,
54
+ ...props
55
+ }: MessageContentProps) => (
56
+ <div
57
+ className={cn(
58
+ "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
59
+ "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
60
+ "group-[.is-assistant]:text-foreground",
61
+ className
62
+ )}
63
+ {...props}
64
+ >
65
+ {children}
66
+ </div>
67
+ );
68
+
69
+ export type MessageActionsProps = ComponentProps<"div">;
70
+
71
+ export const MessageActions = ({
72
+ className,
73
+ children,
74
+ ...props
75
+ }: MessageActionsProps) => (
76
+ <div className={cn("flex items-center gap-1", className)} {...props}>
77
+ {children}
78
+ </div>
79
+ );
80
+
81
+ export type MessageActionProps = ComponentProps<typeof Button> & {
82
+ tooltip?: string;
83
+ label?: string;
84
+ };
85
+
86
+ export const MessageAction = ({
87
+ tooltip,
88
+ children,
89
+ label,
90
+ variant = "ghost",
91
+ size = "icon-sm",
92
+ ...props
93
+ }: MessageActionProps) => {
94
+ const button = (
95
+ <Button size={size} type="button" variant={variant} {...props}>
96
+ {children}
97
+ <span className="sr-only">{label || tooltip}</span>
98
+ </Button>
99
+ );
100
+
101
+ if (tooltip) {
102
+ return (
103
+ <TooltipProvider>
104
+ <Tooltip>
105
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
106
+ <TooltipContent>
107
+ <p>{tooltip}</p>
108
+ </TooltipContent>
109
+ </Tooltip>
110
+ </TooltipProvider>
111
+ );
112
+ }
113
+
114
+ return button;
115
+ };
116
+
117
+ interface MessageBranchContextType {
118
+ currentBranch: number;
119
+ totalBranches: number;
120
+ goToPrevious: () => void;
121
+ goToNext: () => void;
122
+ branches: ReactElement[];
123
+ setBranches: (branches: ReactElement[]) => void;
124
+ }
125
+
126
+ const MessageBranchContext = createContext<MessageBranchContextType | null>(
127
+ null
128
+ );
129
+
130
+ const useMessageBranch = () => {
131
+ const context = useContext(MessageBranchContext);
132
+
133
+ if (!context) {
134
+ throw new Error(
135
+ "MessageBranch components must be used within MessageBranch"
136
+ );
137
+ }
138
+
139
+ return context;
140
+ };
141
+
142
+ export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
143
+ defaultBranch?: number;
144
+ onBranchChange?: (branchIndex: number) => void;
145
+ };
146
+
147
+ export const MessageBranch = ({
148
+ defaultBranch = 0,
149
+ onBranchChange,
150
+ className,
151
+ ...props
152
+ }: MessageBranchProps) => {
153
+ const [currentBranch, setCurrentBranch] = useState(defaultBranch);
154
+ const [branches, setBranches] = useState<ReactElement[]>([]);
155
+
156
+ const handleBranchChange = useCallback(
157
+ (newBranch: number) => {
158
+ setCurrentBranch(newBranch);
159
+ onBranchChange?.(newBranch);
160
+ },
161
+ [onBranchChange]
162
+ );
163
+
164
+ const goToPrevious = useCallback(() => {
165
+ const newBranch =
166
+ currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
167
+ handleBranchChange(newBranch);
168
+ }, [currentBranch, branches.length, handleBranchChange]);
169
+
170
+ const goToNext = useCallback(() => {
171
+ const newBranch =
172
+ currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
173
+ handleBranchChange(newBranch);
174
+ }, [currentBranch, branches.length, handleBranchChange]);
175
+
176
+ const contextValue = useMemo<MessageBranchContextType>(
177
+ () => ({
178
+ branches,
179
+ currentBranch,
180
+ goToNext,
181
+ goToPrevious,
182
+ setBranches,
183
+ totalBranches: branches.length,
184
+ }),
185
+ [branches, currentBranch, goToNext, goToPrevious]
186
+ );
187
+
188
+ return (
189
+ <MessageBranchContext.Provider value={contextValue}>
190
+ <div
191
+ className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
192
+ {...props}
193
+ />
194
+ </MessageBranchContext.Provider>
195
+ );
196
+ };
197
+
198
+ export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
199
+
200
+ export const MessageBranchContent = ({
201
+ children,
202
+ ...props
203
+ }: MessageBranchContentProps) => {
204
+ const { currentBranch, setBranches, branches } = useMessageBranch();
205
+ const childrenArray = useMemo(
206
+ () => (Array.isArray(children) ? children : [children]),
207
+ [children]
208
+ );
209
+
210
+ // Use useEffect to update branches when they change
211
+ useEffect(() => {
212
+ if (branches.length !== childrenArray.length) {
213
+ setBranches(childrenArray);
214
+ }
215
+ }, [childrenArray, branches, setBranches]);
216
+
217
+ return childrenArray.map((branch, index) => (
218
+ <div
219
+ className={cn(
220
+ "grid gap-2 overflow-hidden [&>div]:pb-0",
221
+ index === currentBranch ? "block" : "hidden"
222
+ )}
223
+ key={branch.key}
224
+ {...props}
225
+ >
226
+ {branch}
227
+ </div>
228
+ ));
229
+ };
230
+
231
+ export type MessageBranchSelectorProps = ComponentProps<typeof ButtonGroup>;
232
+
233
+ export const MessageBranchSelector = ({
234
+ className,
235
+ ...props
236
+ }: MessageBranchSelectorProps) => {
237
+ const { totalBranches } = useMessageBranch();
238
+
239
+ // Don't render if there's only one branch
240
+ if (totalBranches <= 1) {
241
+ return null;
242
+ }
243
+
244
+ return (
245
+ <ButtonGroup
246
+ className={cn(
247
+ "[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md",
248
+ className
249
+ )}
250
+ orientation="horizontal"
251
+ {...props}
252
+ />
253
+ );
254
+ };
255
+
256
+ export type MessageBranchPreviousProps = ComponentProps<typeof Button>;
257
+
258
+ export const MessageBranchPrevious = ({
259
+ children,
260
+ ...props
261
+ }: MessageBranchPreviousProps) => {
262
+ const { goToPrevious, totalBranches } = useMessageBranch();
263
+
264
+ return (
265
+ <Button
266
+ aria-label="Previous branch"
267
+ disabled={totalBranches <= 1}
268
+ onClick={goToPrevious}
269
+ size="icon-sm"
270
+ type="button"
271
+ variant="ghost"
272
+ {...props}
273
+ >
274
+ {children ?? <ChevronLeftIcon size={14} />}
275
+ </Button>
276
+ );
277
+ };
278
+
279
+ export type MessageBranchNextProps = ComponentProps<typeof Button>;
280
+
281
+ export const MessageBranchNext = ({
282
+ children,
283
+ ...props
284
+ }: MessageBranchNextProps) => {
285
+ const { goToNext, totalBranches } = useMessageBranch();
286
+
287
+ return (
288
+ <Button
289
+ aria-label="Next branch"
290
+ disabled={totalBranches <= 1}
291
+ onClick={goToNext}
292
+ size="icon-sm"
293
+ type="button"
294
+ variant="ghost"
295
+ {...props}
296
+ >
297
+ {children ?? <ChevronRightIcon size={14} />}
298
+ </Button>
299
+ );
300
+ };
301
+
302
+ export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
303
+
304
+ export const MessageBranchPage = ({
305
+ className,
306
+ ...props
307
+ }: MessageBranchPageProps) => {
308
+ const { currentBranch, totalBranches } = useMessageBranch();
309
+
310
+ return (
311
+ <ButtonGroupText
312
+ className={cn(
313
+ "border-none bg-transparent text-muted-foreground shadow-none",
314
+ className
315
+ )}
316
+ {...props}
317
+ >
318
+ {currentBranch + 1} of {totalBranches}
319
+ </ButtonGroupText>
320
+ );
321
+ };
322
+
323
+ export type MessageResponseProps = ComponentProps<typeof Streamdown>;
324
+
325
+ export const MessageResponse = memo(
326
+ ({ className, ...props }: MessageResponseProps) => (
327
+ <Streamdown
328
+ className={cn(
329
+ "size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
330
+ className
331
+ )}
332
+ plugins={{ code, cjk, math, mermaid }}
333
+ controls={{
334
+ code: true,
335
+ table: true,
336
+ mermaid: {
337
+ download: true,
338
+ copy: true,
339
+ fullscreen: true,
340
+ panZoom: true,
341
+ },
342
+ }}
343
+ {...props}
344
+ />
345
+ ),
346
+ (prevProps, nextProps) => prevProps.children === nextProps.children
347
+ );
348
+
349
+ MessageResponse.displayName = "MessageResponse";
350
+
351
+ export type MessageToolbarProps = ComponentProps<"div">;
352
+
353
+ export const MessageToolbar = ({
354
+ className,
355
+ children,
356
+ ...props
357
+ }: MessageToolbarProps) => (
358
+ <div
359
+ className={cn(
360
+ "mt-4 flex w-full items-center justify-between gap-4",
361
+ className
362
+ )}
363
+ {...props}
364
+ >
365
+ {children}
366
+ </div>
367
+ );