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.
- package/README.md +234 -0
- package/app/(admin)/approvals/page.tsx +16 -0
- package/app/(admin)/audit/page.tsx +18 -0
- package/app/(admin)/layout.tsx +47 -0
- package/app/(admin)/scheduled-tasks/page.tsx +17 -0
- package/app/(admin)/settings/page.tsx +46 -0
- package/app/(admin)/skills/[name]/page.tsx +378 -0
- package/app/(admin)/skills/page.tsx +406 -0
- package/app/(admin)/statistics/page.tsx +416 -0
- package/app/(admin)/tickets/[id]/page.tsx +348 -0
- package/app/(admin)/tickets/new/page.tsx +309 -0
- package/app/(admin)/tickets/page.tsx +27 -0
- package/app/api/audit/route.ts +30 -0
- package/app/api/auth/feishu/callback/route.ts +72 -0
- package/app/api/auth/feishu/login/route.ts +17 -0
- package/app/api/auth/feishu/sso/route.ts +78 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/oauth/route.ts +168 -0
- package/app/api/config/providers/route.ts +105 -0
- package/app/api/config/route.ts +115 -0
- package/app/api/config/status/route.ts +56 -0
- package/app/api/config/test/route.ts +212 -0
- package/app/api/documents/[id]/route.ts +88 -0
- package/app/api/documents/route.ts +53 -0
- package/app/api/health/route.ts +32 -0
- package/app/api/knowledge/[id]/route.ts +152 -0
- package/app/api/knowledge/from-session/route.ts +27 -0
- package/app/api/knowledge/route.ts +100 -0
- package/app/api/market/knowledge/[id]/route.ts +92 -0
- package/app/api/market/knowledge/route.ts +130 -0
- package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
- package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
- package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
- package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
- package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
- package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/route.ts +177 -0
- package/app/api/marketplace/skills/route.ts +235 -0
- package/app/api/memory/route.ts +40 -0
- package/app/api/my/files/[id]/route.ts +52 -0
- package/app/api/my/files/route.ts +230 -0
- package/app/api/my/knowledge/route.ts +36 -0
- package/app/api/pi-chat/route.ts +443 -0
- package/app/api/recommend/route.ts +38 -0
- package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
- package/app/api/scheduled-tasks/[id]/route.ts +165 -0
- package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
- package/app/api/scheduled-tasks/route.ts +101 -0
- package/app/api/sessions/[id]/messages/route.ts +212 -0
- package/app/api/sessions/route.ts +101 -0
- package/app/api/share/file/[id]/route.ts +37 -0
- package/app/api/skills/[name]/execute/route.ts +121 -0
- package/app/api/skills/[name]/route.ts +167 -0
- package/app/api/skills/create/route.ts +65 -0
- package/app/api/skills/generate/route.ts +405 -0
- package/app/api/skills/installed/route.ts +151 -0
- package/app/api/skills/route.ts +174 -0
- package/app/api/skills/translate/route.ts +40 -0
- package/app/api/skills/user/[name]/route.ts +159 -0
- package/app/api/skills/user/route.ts +90 -0
- package/app/api/statistics/route.ts +94 -0
- package/app/api/task-executions/[id]/route.ts +34 -0
- package/app/api/task-executions/route.ts +29 -0
- package/app/api/tickets/[id]/approve/route.ts +129 -0
- package/app/api/tickets/[id]/execute/route.ts +201 -0
- package/app/api/tickets/[id]/route.ts +127 -0
- package/app/api/tickets/route.ts +103 -0
- package/app/api/user/skills/route.ts +175 -0
- package/app/api/users/route.ts +80 -0
- package/app/chat/page.tsx +5 -0
- package/app/globals.css +84 -0
- package/app/h5/layout.tsx +5 -0
- package/app/h5/mobile-approvals-page.tsx +167 -0
- package/app/h5/mobile-chat-page.tsx +951 -0
- package/app/h5/mobile-profile-page.tsx +147 -0
- package/app/h5/mobile-tickets-page.tsx +121 -0
- package/app/h5/page.tsx +23 -0
- package/app/h5/ticket-action-buttons.tsx +80 -0
- package/app/layout.tsx +26 -0
- package/app/login/page.tsx +318 -0
- package/app/market/knowledge/[id]/page.tsx +77 -0
- package/app/market/knowledge/page.tsx +358 -0
- package/app/market/layout.tsx +29 -0
- package/app/market/page.tsx +18 -0
- package/app/market/skills/page.tsx +397 -0
- package/app/my/files/page.tsx +511 -0
- package/app/my/knowledge/[id]/page.tsx +271 -0
- package/app/my/knowledge/new/page.tsx +234 -0
- package/app/my/knowledge/page.tsx +248 -0
- package/app/my/layout.tsx +32 -0
- package/app/my/memory/page.tsx +164 -0
- package/app/my/page.tsx +18 -0
- package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
- package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
- package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
- package/app/my/scheduled-tasks/new/page.tsx +230 -0
- package/app/my/scheduled-tasks/page.tsx +27 -0
- package/app/my/skills/[name]/page.tsx +320 -0
- package/app/my/skills/new/page.tsx +394 -0
- package/app/my/skills/page.tsx +303 -0
- package/app/page.tsx +2288 -0
- package/app/share/[sessionId]/page.tsx +226 -0
- package/app/share/file/[id]/page.tsx +140 -0
- package/bin/README.md +63 -0
- package/bin/generate-api-system +300 -0
- package/bin/postinstall.js +95 -0
- package/bin/work-agent.js +173 -0
- package/components/ai-elements/agent.tsx +142 -0
- package/components/ai-elements/artifact.tsx +149 -0
- package/components/ai-elements/attachments.tsx +427 -0
- package/components/ai-elements/audio-player.tsx +232 -0
- package/components/ai-elements/canvas.tsx +26 -0
- package/components/ai-elements/chain-of-thought.tsx +223 -0
- package/components/ai-elements/checkpoint.tsx +72 -0
- package/components/ai-elements/code-block.tsx +555 -0
- package/components/ai-elements/commit.tsx +449 -0
- package/components/ai-elements/confirmation.tsx +173 -0
- package/components/ai-elements/connection.tsx +28 -0
- package/components/ai-elements/context.tsx +410 -0
- package/components/ai-elements/controls.tsx +19 -0
- package/components/ai-elements/conversation.tsx +167 -0
- package/components/ai-elements/edge.tsx +144 -0
- package/components/ai-elements/environment-variables.tsx +325 -0
- package/components/ai-elements/file-tree.tsx +298 -0
- package/components/ai-elements/image.tsx +25 -0
- package/components/ai-elements/inline-citation.tsx +294 -0
- package/components/ai-elements/jsx-preview.tsx +250 -0
- package/components/ai-elements/message.tsx +367 -0
- package/components/ai-elements/mic-selector.tsx +372 -0
- package/components/ai-elements/model-selector.tsx +214 -0
- package/components/ai-elements/node.tsx +72 -0
- package/components/ai-elements/open-in-chat.tsx +367 -0
- package/components/ai-elements/package-info.tsx +235 -0
- package/components/ai-elements/panel.tsx +16 -0
- package/components/ai-elements/persona.tsx +280 -0
- package/components/ai-elements/plan.tsx +144 -0
- package/components/ai-elements/prompt-input.tsx +1341 -0
- package/components/ai-elements/queue.tsx +275 -0
- package/components/ai-elements/reasoning.tsx +355 -0
- package/components/ai-elements/sandbox.tsx +133 -0
- package/components/ai-elements/schema-display.tsx +473 -0
- package/components/ai-elements/shimmer.tsx +78 -0
- package/components/ai-elements/snippet.tsx +141 -0
- package/components/ai-elements/sources.tsx +78 -0
- package/components/ai-elements/speech-input.tsx +324 -0
- package/components/ai-elements/stack-trace.tsx +531 -0
- package/components/ai-elements/suggestion.tsx +58 -0
- package/components/ai-elements/task.tsx +88 -0
- package/components/ai-elements/terminal.tsx +277 -0
- package/components/ai-elements/test-results.tsx +497 -0
- package/components/ai-elements/tool.tsx +174 -0
- package/components/ai-elements/toolbar.tsx +17 -0
- package/components/ai-elements/transcription.tsx +126 -0
- package/components/ai-elements/voice-selector.tsx +525 -0
- package/components/ai-elements/web-preview.tsx +282 -0
- package/components/audit-log-list.tsx +114 -0
- package/components/chat/EmptyPreviewState.tsx +12 -0
- package/components/chat/KnowledgePickerDialog.tsx +464 -0
- package/components/chat/KnowledgePreview.tsx +70 -0
- package/components/chat/KnowledgePreviewPanel.tsx +86 -0
- package/components/chat/MentionInput.tsx +309 -0
- package/components/chat/OrganizeDialog.tsx +258 -0
- package/components/chat/RecommendationBanner.tsx +94 -0
- package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
- package/components/chat/SkillSelector.tsx +305 -0
- package/components/chat/SkillSwitcher.tsx +163 -0
- package/components/client-layout.tsx +15 -0
- package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
- package/components/layout-wrapper.tsx +18 -0
- package/components/mobile-layout.tsx +62 -0
- package/components/scheduled-task-list.tsx +356 -0
- package/components/setup-guide.tsx +484 -0
- package/components/sub-nav.tsx +54 -0
- package/components/ticket-detail-content.tsx +383 -0
- package/components/ticket-list.tsx +366 -0
- package/components/top-nav.tsx +132 -0
- package/components/ui/accordion.tsx +58 -0
- package/components/ui/alert.tsx +59 -0
- package/components/ui/avatar.tsx +50 -0
- package/components/ui/badge.tsx +36 -0
- package/components/ui/button-group.tsx +83 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/card.tsx +91 -0
- package/components/ui/carousel.tsx +262 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +153 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/dropdown-menu.tsx +200 -0
- package/components/ui/hover-card.tsx +29 -0
- package/components/ui/input-group.tsx +170 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +31 -0
- package/components/ui/progress.tsx +28 -0
- package/components/ui/scroll-area.tsx +48 -0
- package/components/ui/select.tsx +174 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/spinner.tsx +16 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/table.tsx +120 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/textarea.tsx +22 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components/welcome-guide.tsx +182 -0
- package/components.json +24 -0
- package/lib/command-parser.ts +331 -0
- package/lib/dangerous-commands.ts +672 -0
- package/lib/db.ts +2250 -0
- package/lib/feishu-auth.ts +135 -0
- package/lib/file-storage.ts +306 -0
- package/lib/file-tool.ts +583 -0
- package/lib/knowledge-tool.ts +152 -0
- package/lib/knowledge-types.ts +66 -0
- package/lib/market-client.ts +313 -0
- package/lib/market-db.ts +736 -0
- package/lib/market-types.ts +51 -0
- package/lib/memory-tool.ts +211 -0
- package/lib/memory.ts +197 -0
- package/lib/pi-config.ts +436 -0
- package/lib/pi-session.ts +799 -0
- package/lib/pinyin.ts +13 -0
- package/lib/recommendation.ts +227 -0
- package/lib/risk-estimator.ts +350 -0
- package/lib/scheduled-task-tool.ts +184 -0
- package/lib/scheduler-init.ts +43 -0
- package/lib/scheduler.ts +416 -0
- package/lib/secure-bash-tool.ts +413 -0
- package/lib/skill-engine.ts +396 -0
- package/lib/skill-generator.ts +269 -0
- package/lib/skill-loader.ts +234 -0
- package/lib/skill-tool.ts +188 -0
- package/lib/skill-types.ts +82 -0
- package/lib/skills-init.ts +58 -0
- package/lib/ticket-tool.ts +246 -0
- package/lib/user-skill-types.ts +30 -0
- package/lib/user-skills.ts +362 -0
- package/lib/utils.ts +6 -0
- package/lib/workflow.ts +154 -0
- package/lib/zip-tool.ts +191 -0
- package/next.config.js +8 -0
- package/package.json +106 -0
- package/public/.gitkeep +1 -0
- package/public/icon.svg +1 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
Collapsible,
|
|
8
|
+
CollapsibleContent,
|
|
9
|
+
CollapsibleTrigger,
|
|
10
|
+
} from "@/components/ui/collapsible";
|
|
11
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
12
|
+
import { cn } from "@/lib/utils";
|
|
13
|
+
import { ChevronDownIcon, PaperclipIcon } from "lucide-react";
|
|
14
|
+
|
|
15
|
+
export interface QueueMessagePart {
|
|
16
|
+
type: string;
|
|
17
|
+
text?: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
filename?: string;
|
|
20
|
+
mediaType?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface QueueMessage {
|
|
24
|
+
id: string;
|
|
25
|
+
parts: QueueMessagePart[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface QueueTodo {
|
|
29
|
+
id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
status?: "pending" | "completed";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type QueueItemProps = ComponentProps<"li">;
|
|
36
|
+
|
|
37
|
+
export const QueueItem = ({ className, ...props }: QueueItemProps) => (
|
|
38
|
+
<li
|
|
39
|
+
className={cn(
|
|
40
|
+
"group flex flex-col gap-1 rounded-md px-3 py-1 text-sm transition-colors hover:bg-muted",
|
|
41
|
+
className
|
|
42
|
+
)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export type QueueItemIndicatorProps = ComponentProps<"span"> & {
|
|
48
|
+
completed?: boolean;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const QueueItemIndicator = ({
|
|
52
|
+
completed = false,
|
|
53
|
+
className,
|
|
54
|
+
...props
|
|
55
|
+
}: QueueItemIndicatorProps) => (
|
|
56
|
+
<span
|
|
57
|
+
className={cn(
|
|
58
|
+
"mt-0.5 inline-block size-2.5 rounded-full border",
|
|
59
|
+
completed
|
|
60
|
+
? "border-muted-foreground/20 bg-muted-foreground/10"
|
|
61
|
+
: "border-muted-foreground/50",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export type QueueItemContentProps = ComponentProps<"span"> & {
|
|
69
|
+
completed?: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const QueueItemContent = ({
|
|
73
|
+
completed = false,
|
|
74
|
+
className,
|
|
75
|
+
...props
|
|
76
|
+
}: QueueItemContentProps) => (
|
|
77
|
+
<span
|
|
78
|
+
className={cn(
|
|
79
|
+
"line-clamp-1 grow break-words",
|
|
80
|
+
completed
|
|
81
|
+
? "text-muted-foreground/50 line-through"
|
|
82
|
+
: "text-muted-foreground",
|
|
83
|
+
className
|
|
84
|
+
)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export type QueueItemDescriptionProps = ComponentProps<"div"> & {
|
|
90
|
+
completed?: boolean;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const QueueItemDescription = ({
|
|
94
|
+
completed = false,
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}: QueueItemDescriptionProps) => (
|
|
98
|
+
<div
|
|
99
|
+
className={cn(
|
|
100
|
+
"ml-6 text-xs",
|
|
101
|
+
completed
|
|
102
|
+
? "text-muted-foreground/40 line-through"
|
|
103
|
+
: "text-muted-foreground",
|
|
104
|
+
className
|
|
105
|
+
)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
export type QueueItemActionsProps = ComponentProps<"div">;
|
|
111
|
+
|
|
112
|
+
export const QueueItemActions = ({
|
|
113
|
+
className,
|
|
114
|
+
...props
|
|
115
|
+
}: QueueItemActionsProps) => (
|
|
116
|
+
<div className={cn("flex gap-1", className)} {...props} />
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
export type QueueItemActionProps = Omit<
|
|
120
|
+
ComponentProps<typeof Button>,
|
|
121
|
+
"variant" | "size"
|
|
122
|
+
>;
|
|
123
|
+
|
|
124
|
+
export const QueueItemAction = ({
|
|
125
|
+
className,
|
|
126
|
+
...props
|
|
127
|
+
}: QueueItemActionProps) => (
|
|
128
|
+
<Button
|
|
129
|
+
className={cn(
|
|
130
|
+
"size-auto rounded p-1 text-muted-foreground opacity-0 transition-opacity hover:bg-muted-foreground/10 hover:text-foreground group-hover:opacity-100",
|
|
131
|
+
className
|
|
132
|
+
)}
|
|
133
|
+
size="icon"
|
|
134
|
+
type="button"
|
|
135
|
+
variant="ghost"
|
|
136
|
+
{...props}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
export type QueueItemAttachmentProps = ComponentProps<"div">;
|
|
141
|
+
|
|
142
|
+
export const QueueItemAttachment = ({
|
|
143
|
+
className,
|
|
144
|
+
...props
|
|
145
|
+
}: QueueItemAttachmentProps) => (
|
|
146
|
+
<div className={cn("mt-1 flex flex-wrap gap-2", className)} {...props} />
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
export type QueueItemImageProps = ComponentProps<"img">;
|
|
150
|
+
|
|
151
|
+
export const QueueItemImage = ({
|
|
152
|
+
className,
|
|
153
|
+
...props
|
|
154
|
+
}: QueueItemImageProps) => (
|
|
155
|
+
<img
|
|
156
|
+
alt=""
|
|
157
|
+
className={cn("h-8 w-8 rounded border object-cover", className)}
|
|
158
|
+
height={32}
|
|
159
|
+
width={32}
|
|
160
|
+
{...props}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
export type QueueItemFileProps = ComponentProps<"span">;
|
|
165
|
+
|
|
166
|
+
export const QueueItemFile = ({
|
|
167
|
+
children,
|
|
168
|
+
className,
|
|
169
|
+
...props
|
|
170
|
+
}: QueueItemFileProps) => (
|
|
171
|
+
<span
|
|
172
|
+
className={cn(
|
|
173
|
+
"flex items-center gap-1 rounded border bg-muted px-2 py-1 text-xs",
|
|
174
|
+
className
|
|
175
|
+
)}
|
|
176
|
+
{...props}
|
|
177
|
+
>
|
|
178
|
+
<PaperclipIcon size={12} />
|
|
179
|
+
<span className="max-w-[100px] truncate">{children}</span>
|
|
180
|
+
</span>
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
export type QueueListProps = ComponentProps<typeof ScrollArea>;
|
|
184
|
+
|
|
185
|
+
export const QueueList = ({
|
|
186
|
+
children,
|
|
187
|
+
className,
|
|
188
|
+
...props
|
|
189
|
+
}: QueueListProps) => (
|
|
190
|
+
<ScrollArea className={cn("mt-2 -mb-1", className)} {...props}>
|
|
191
|
+
<div className="max-h-40 pr-4">
|
|
192
|
+
<ul>{children}</ul>
|
|
193
|
+
</div>
|
|
194
|
+
</ScrollArea>
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// QueueSection - collapsible section container
|
|
198
|
+
export type QueueSectionProps = ComponentProps<typeof Collapsible>;
|
|
199
|
+
|
|
200
|
+
export const QueueSection = ({
|
|
201
|
+
className,
|
|
202
|
+
defaultOpen = true,
|
|
203
|
+
...props
|
|
204
|
+
}: QueueSectionProps) => (
|
|
205
|
+
<Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// QueueSectionTrigger - section header/trigger
|
|
209
|
+
export type QueueSectionTriggerProps = ComponentProps<"button">;
|
|
210
|
+
|
|
211
|
+
export const QueueSectionTrigger = ({
|
|
212
|
+
children,
|
|
213
|
+
className,
|
|
214
|
+
...props
|
|
215
|
+
}: QueueSectionTriggerProps) => (
|
|
216
|
+
<CollapsibleTrigger asChild>
|
|
217
|
+
<button
|
|
218
|
+
className={cn(
|
|
219
|
+
"group flex w-full items-center justify-between rounded-md bg-muted/40 px-3 py-2 text-left font-medium text-muted-foreground text-sm transition-colors hover:bg-muted",
|
|
220
|
+
className
|
|
221
|
+
)}
|
|
222
|
+
type="button"
|
|
223
|
+
{...props}
|
|
224
|
+
>
|
|
225
|
+
{children}
|
|
226
|
+
</button>
|
|
227
|
+
</CollapsibleTrigger>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// QueueSectionLabel - label content with icon and count
|
|
231
|
+
export type QueueSectionLabelProps = ComponentProps<"span"> & {
|
|
232
|
+
count?: number;
|
|
233
|
+
label: string;
|
|
234
|
+
icon?: React.ReactNode;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const QueueSectionLabel = ({
|
|
238
|
+
count,
|
|
239
|
+
label,
|
|
240
|
+
icon,
|
|
241
|
+
className,
|
|
242
|
+
...props
|
|
243
|
+
}: QueueSectionLabelProps) => (
|
|
244
|
+
<span className={cn("flex items-center gap-2", className)} {...props}>
|
|
245
|
+
<ChevronDownIcon className="size-4 transition-transform group-data-[state=closed]:-rotate-90" />
|
|
246
|
+
{icon}
|
|
247
|
+
<span>
|
|
248
|
+
{count} {label}
|
|
249
|
+
</span>
|
|
250
|
+
</span>
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// QueueSectionContent - collapsible content area
|
|
254
|
+
export type QueueSectionContentProps = ComponentProps<
|
|
255
|
+
typeof CollapsibleContent
|
|
256
|
+
>;
|
|
257
|
+
|
|
258
|
+
export const QueueSectionContent = ({
|
|
259
|
+
className,
|
|
260
|
+
...props
|
|
261
|
+
}: QueueSectionContentProps) => (
|
|
262
|
+
<CollapsibleContent className={cn(className)} {...props} />
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
export type QueueProps = ComponentProps<"div">;
|
|
266
|
+
|
|
267
|
+
export const Queue = ({ className, ...props }: QueueProps) => (
|
|
268
|
+
<div
|
|
269
|
+
className={cn(
|
|
270
|
+
"flex flex-col gap-2 rounded-xl border border-border bg-background px-3 pt-2 pb-2 shadow-xs",
|
|
271
|
+
className
|
|
272
|
+
)}
|
|
273
|
+
{...props}
|
|
274
|
+
/>
|
|
275
|
+
);
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
6
|
+
import {
|
|
7
|
+
Collapsible,
|
|
8
|
+
CollapsibleContent,
|
|
9
|
+
CollapsibleTrigger,
|
|
10
|
+
} from "@/components/ui/collapsible";
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import {
|
|
13
|
+
Tooltip,
|
|
14
|
+
TooltipContent,
|
|
15
|
+
TooltipProvider,
|
|
16
|
+
TooltipTrigger,
|
|
17
|
+
} from "@/components/ui/tooltip";
|
|
18
|
+
import { cn } from "@/lib/utils";
|
|
19
|
+
import { cjk } from "@streamdown/cjk";
|
|
20
|
+
import { code } from "@streamdown/code";
|
|
21
|
+
import { math } from "@streamdown/math";
|
|
22
|
+
import { mermaid } from "@streamdown/mermaid";
|
|
23
|
+
import { BrainIcon, ChevronDownIcon, CopyIcon, DownloadIcon } from "lucide-react";
|
|
24
|
+
import {
|
|
25
|
+
createContext,
|
|
26
|
+
memo,
|
|
27
|
+
useCallback,
|
|
28
|
+
useContext,
|
|
29
|
+
useEffect,
|
|
30
|
+
useMemo,
|
|
31
|
+
useRef,
|
|
32
|
+
useState,
|
|
33
|
+
} from "react";
|
|
34
|
+
import { Streamdown } from "streamdown";
|
|
35
|
+
|
|
36
|
+
import { Shimmer } from "./shimmer";
|
|
37
|
+
|
|
38
|
+
interface ReasoningContextValue {
|
|
39
|
+
isStreaming: boolean;
|
|
40
|
+
isOpen: boolean;
|
|
41
|
+
setIsOpen: (open: boolean) => void;
|
|
42
|
+
duration: number | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ReasoningContext = createContext<ReasoningContextValue | null>(null);
|
|
46
|
+
|
|
47
|
+
export const useReasoning = () => {
|
|
48
|
+
const context = useContext(ReasoningContext);
|
|
49
|
+
if (!context) {
|
|
50
|
+
throw new Error("Reasoning components must be used within Reasoning");
|
|
51
|
+
}
|
|
52
|
+
return context;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type ReasoningProps = ComponentProps<typeof Collapsible> & {
|
|
56
|
+
isStreaming?: boolean;
|
|
57
|
+
open?: boolean;
|
|
58
|
+
defaultOpen?: boolean;
|
|
59
|
+
onOpenChange?: (open: boolean) => void;
|
|
60
|
+
duration?: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const AUTO_CLOSE_DELAY = 1000;
|
|
64
|
+
const MS_IN_S = 1000;
|
|
65
|
+
|
|
66
|
+
export const Reasoning = memo(
|
|
67
|
+
({
|
|
68
|
+
className,
|
|
69
|
+
isStreaming = false,
|
|
70
|
+
open,
|
|
71
|
+
defaultOpen,
|
|
72
|
+
onOpenChange,
|
|
73
|
+
duration: durationProp,
|
|
74
|
+
children,
|
|
75
|
+
...props
|
|
76
|
+
}: ReasoningProps) => {
|
|
77
|
+
const resolvedDefaultOpen = defaultOpen ?? isStreaming;
|
|
78
|
+
// Track if defaultOpen was explicitly set to false (to prevent auto-open)
|
|
79
|
+
const isExplicitlyClosed = defaultOpen === false;
|
|
80
|
+
|
|
81
|
+
const [isOpen, setIsOpen] = useControllableState<boolean>({
|
|
82
|
+
defaultProp: resolvedDefaultOpen,
|
|
83
|
+
onChange: onOpenChange,
|
|
84
|
+
prop: open,
|
|
85
|
+
});
|
|
86
|
+
const [duration, setDuration] = useControllableState<number | undefined>({
|
|
87
|
+
defaultProp: undefined,
|
|
88
|
+
prop: durationProp,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const hasEverStreamedRef = useRef(isStreaming);
|
|
92
|
+
const [hasAutoClosed, setHasAutoClosed] = useState(false);
|
|
93
|
+
const startTimeRef = useRef<number | null>(null);
|
|
94
|
+
|
|
95
|
+
// Track when streaming starts and compute duration
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (isStreaming) {
|
|
98
|
+
hasEverStreamedRef.current = true;
|
|
99
|
+
if (startTimeRef.current === null) {
|
|
100
|
+
startTimeRef.current = Date.now();
|
|
101
|
+
}
|
|
102
|
+
} else if (startTimeRef.current !== null) {
|
|
103
|
+
setDuration(Math.ceil((Date.now() - startTimeRef.current) / MS_IN_S));
|
|
104
|
+
startTimeRef.current = null;
|
|
105
|
+
}
|
|
106
|
+
}, [isStreaming, setDuration]);
|
|
107
|
+
|
|
108
|
+
// Auto-open when streaming starts (unless explicitly closed)
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (isStreaming && !isOpen && !isExplicitlyClosed) {
|
|
111
|
+
setIsOpen(true);
|
|
112
|
+
}
|
|
113
|
+
}, [isStreaming, isOpen, setIsOpen, isExplicitlyClosed]);
|
|
114
|
+
|
|
115
|
+
// Auto-close when streaming ends (once only, and only if it ever streamed)
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (
|
|
118
|
+
hasEverStreamedRef.current &&
|
|
119
|
+
!isStreaming &&
|
|
120
|
+
isOpen &&
|
|
121
|
+
!hasAutoClosed
|
|
122
|
+
) {
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
setIsOpen(false);
|
|
125
|
+
setHasAutoClosed(true);
|
|
126
|
+
}, AUTO_CLOSE_DELAY);
|
|
127
|
+
|
|
128
|
+
return () => clearTimeout(timer);
|
|
129
|
+
}
|
|
130
|
+
}, [isStreaming, isOpen, setIsOpen, hasAutoClosed]);
|
|
131
|
+
|
|
132
|
+
const handleOpenChange = useCallback(
|
|
133
|
+
(newOpen: boolean) => {
|
|
134
|
+
setIsOpen(newOpen);
|
|
135
|
+
},
|
|
136
|
+
[setIsOpen]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const contextValue = useMemo(
|
|
140
|
+
() => ({ duration, isOpen, isStreaming, setIsOpen }),
|
|
141
|
+
[duration, isOpen, isStreaming, setIsOpen]
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<ReasoningContext.Provider value={contextValue}>
|
|
146
|
+
<Collapsible
|
|
147
|
+
className={cn("not-prose mb-4", className)}
|
|
148
|
+
onOpenChange={handleOpenChange}
|
|
149
|
+
open={isOpen}
|
|
150
|
+
{...props}
|
|
151
|
+
>
|
|
152
|
+
{children}
|
|
153
|
+
</Collapsible>
|
|
154
|
+
</ReasoningContext.Provider>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
export type ReasoningTriggerProps = ComponentProps<
|
|
160
|
+
typeof CollapsibleTrigger
|
|
161
|
+
> & {
|
|
162
|
+
getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
|
|
166
|
+
if (isStreaming || duration === 0) {
|
|
167
|
+
return <Shimmer duration={1}>Thinking...</Shimmer>;
|
|
168
|
+
}
|
|
169
|
+
if (duration === undefined) {
|
|
170
|
+
return <p>Thought for a few seconds</p>;
|
|
171
|
+
}
|
|
172
|
+
return <p>Thought for {duration} seconds</p>;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const ReasoningTrigger = memo(
|
|
176
|
+
({
|
|
177
|
+
className,
|
|
178
|
+
children,
|
|
179
|
+
getThinkingMessage = defaultGetThinkingMessage,
|
|
180
|
+
...props
|
|
181
|
+
}: ReasoningTriggerProps) => {
|
|
182
|
+
const { isStreaming, isOpen, duration } = useReasoning();
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<CollapsibleTrigger
|
|
186
|
+
className={cn(
|
|
187
|
+
"flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
|
|
188
|
+
className
|
|
189
|
+
)}
|
|
190
|
+
{...props}
|
|
191
|
+
>
|
|
192
|
+
{children ?? (
|
|
193
|
+
<>
|
|
194
|
+
<BrainIcon className="size-4" />
|
|
195
|
+
{getThinkingMessage(isStreaming, duration)}
|
|
196
|
+
<ChevronDownIcon
|
|
197
|
+
className={cn(
|
|
198
|
+
"size-4 transition-transform",
|
|
199
|
+
isOpen ? "rotate-180" : "rotate-0"
|
|
200
|
+
)}
|
|
201
|
+
/>
|
|
202
|
+
</>
|
|
203
|
+
)}
|
|
204
|
+
</CollapsibleTrigger>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
export type ReasoningContentProps = ComponentProps<
|
|
210
|
+
typeof CollapsibleContent
|
|
211
|
+
> & {
|
|
212
|
+
children: string;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Custom code component with working copy and download buttons
|
|
216
|
+
// This works around the bug in streamdown v2.3.0 where code block copy buttons
|
|
217
|
+
// don't receive the code content as a prop
|
|
218
|
+
const CustomCodeBlock = memo(
|
|
219
|
+
({
|
|
220
|
+
children,
|
|
221
|
+
className,
|
|
222
|
+
node,
|
|
223
|
+
...props
|
|
224
|
+
}: ComponentProps<"code"> & {
|
|
225
|
+
node?: { properties?: { className?: string[] } };
|
|
226
|
+
"data-block"?: string;
|
|
227
|
+
}) => {
|
|
228
|
+
const [copied, setCopied] = useState(false);
|
|
229
|
+
|
|
230
|
+
// Check if this is a code block (not inline code)
|
|
231
|
+
const isCodeBlock = props["data-block"] !== undefined;
|
|
232
|
+
|
|
233
|
+
if (!isCodeBlock) {
|
|
234
|
+
// Inline code - render as-is
|
|
235
|
+
return (
|
|
236
|
+
<code
|
|
237
|
+
className={cn(
|
|
238
|
+
"rounded bg-muted px-1.5 py-0.5 font-mono text-sm",
|
|
239
|
+
className
|
|
240
|
+
)}
|
|
241
|
+
{...props}
|
|
242
|
+
>
|
|
243
|
+
{children}
|
|
244
|
+
</code>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Extract language from className (e.g., "language-bash" -> "bash")
|
|
249
|
+
const langClass = className?.match(/language-(\w+)/)?.[1] || "";
|
|
250
|
+
const codeContent = typeof children === "string" ? children : String(children ?? "");
|
|
251
|
+
|
|
252
|
+
const handleCopy = useCallback(async () => {
|
|
253
|
+
try {
|
|
254
|
+
await navigator.clipboard.writeText(codeContent);
|
|
255
|
+
setCopied(true);
|
|
256
|
+
setTimeout(() => setCopied(false), 2000);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
console.error("Failed to copy:", err);
|
|
259
|
+
}
|
|
260
|
+
}, [codeContent]);
|
|
261
|
+
|
|
262
|
+
const handleDownload = useCallback(() => {
|
|
263
|
+
const extension = langClass || "txt";
|
|
264
|
+
const blob = new Blob([codeContent], { type: "text/plain" });
|
|
265
|
+
const url = URL.createObjectURL(blob);
|
|
266
|
+
const a = document.createElement("a");
|
|
267
|
+
a.href = url;
|
|
268
|
+
a.download = `code.${extension}`;
|
|
269
|
+
document.body.appendChild(a);
|
|
270
|
+
a.click();
|
|
271
|
+
document.body.removeChild(a);
|
|
272
|
+
URL.revokeObjectURL(url);
|
|
273
|
+
}, [codeContent, langClass]);
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<div className="group relative my-4 w-fit max-w-full rounded-lg border border-border bg-muted/50">
|
|
277
|
+
{/* Header with language label and action buttons */}
|
|
278
|
+
<div className="flex items-center justify-between border-b border-border px-4 py-2">
|
|
279
|
+
<span className="text-xs font-mono lowercase text-muted-foreground">
|
|
280
|
+
{langClass || "code"}
|
|
281
|
+
</span>
|
|
282
|
+
<div className="flex items-center gap-1">
|
|
283
|
+
<TooltipProvider>
|
|
284
|
+
<Tooltip>
|
|
285
|
+
<TooltipTrigger asChild>
|
|
286
|
+
<Button
|
|
287
|
+
size="icon-sm"
|
|
288
|
+
type="button"
|
|
289
|
+
variant="ghost"
|
|
290
|
+
onClick={handleCopy}
|
|
291
|
+
className="h-7 w-7"
|
|
292
|
+
>
|
|
293
|
+
<CopyIcon size={14} />
|
|
294
|
+
<span className="sr-only">Copy code</span>
|
|
295
|
+
</Button>
|
|
296
|
+
</TooltipTrigger>
|
|
297
|
+
<TooltipContent>
|
|
298
|
+
<p>{copied ? "Copied!" : "Copy code"}</p>
|
|
299
|
+
</TooltipContent>
|
|
300
|
+
</Tooltip>
|
|
301
|
+
</TooltipProvider>
|
|
302
|
+
<TooltipProvider>
|
|
303
|
+
<Tooltip>
|
|
304
|
+
<TooltipTrigger asChild>
|
|
305
|
+
<Button
|
|
306
|
+
size="icon-sm"
|
|
307
|
+
type="button"
|
|
308
|
+
variant="ghost"
|
|
309
|
+
onClick={handleDownload}
|
|
310
|
+
className="h-7 w-7"
|
|
311
|
+
>
|
|
312
|
+
<DownloadIcon size={14} />
|
|
313
|
+
<span className="sr-only">Download code</span>
|
|
314
|
+
</Button>
|
|
315
|
+
</TooltipTrigger>
|
|
316
|
+
<TooltipContent>
|
|
317
|
+
<p>Download code</p>
|
|
318
|
+
</TooltipContent>
|
|
319
|
+
</Tooltip>
|
|
320
|
+
</TooltipProvider>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
{/* Code content */}
|
|
324
|
+
<code className={cn("block overflow-x-auto p-4 text-sm bg-background rounded-b-lg", className)} {...props}>
|
|
325
|
+
{children}
|
|
326
|
+
</code>
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
CustomCodeBlock.displayName = "CustomCodeBlock";
|
|
333
|
+
|
|
334
|
+
const streamdownPlugins = { cjk, code, math, mermaid };
|
|
335
|
+
|
|
336
|
+
export const ReasoningContent = memo(
|
|
337
|
+
({ className, children, ...props }: ReasoningContentProps) => (
|
|
338
|
+
<CollapsibleContent
|
|
339
|
+
className={cn(
|
|
340
|
+
"mt-4 text-sm",
|
|
341
|
+
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
342
|
+
className
|
|
343
|
+
)}
|
|
344
|
+
{...props}
|
|
345
|
+
>
|
|
346
|
+
<Streamdown plugins={streamdownPlugins} components={{ code: CustomCodeBlock }} {...props}>
|
|
347
|
+
{children}
|
|
348
|
+
</Streamdown>
|
|
349
|
+
</CollapsibleContent>
|
|
350
|
+
)
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
Reasoning.displayName = "Reasoning";
|
|
354
|
+
ReasoningTrigger.displayName = "ReasoningTrigger";
|
|
355
|
+
ReasoningContent.displayName = "ReasoningContent";
|