stagent 0.1.12 → 0.1.13
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 +44 -50
- package/package.json +1 -1
- package/public/readme/cost-usage-list.png +0 -0
- package/public/readme/dashboard-bulk-select.png +0 -0
- package/public/readme/dashboard-card-edit.png +0 -0
- package/public/readme/dashboard-create-form-ai-applied.png +0 -0
- package/public/readme/dashboard-create-form-ai-assist.png +0 -0
- package/public/readme/dashboard-create-form-empty.png +0 -0
- package/public/readme/dashboard-create-form-filled.png +0 -0
- package/public/readme/dashboard-filtered.png +0 -0
- package/public/readme/dashboard-list.png +0 -0
- package/public/readme/dashboard-workflow-confirm.png +0 -0
- package/public/readme/home-below-fold.png +0 -0
- package/public/readme/home-list.png +0 -0
- package/public/readme/inbox-list.png +0 -0
- package/public/readme/playbook-list.png +0 -0
- package/public/readme/profiles-list.png +0 -0
- package/public/readme/settings-list.png +0 -0
- package/public/readme/workflows-list.png +0 -0
- package/src/app/api/tasks/[id]/route.ts +54 -3
- package/src/app/api/workflows/[id]/route.ts +43 -4
- package/src/app/api/workflows/[id]/status/route.ts +70 -2
- package/src/app/api/workflows/from-assist/route.ts +6 -32
- package/src/app/dashboard/page.tsx +59 -21
- package/src/app/documents/[id]/page.tsx +10 -8
- package/src/app/globals.css +11 -0
- package/src/app/page.tsx +60 -3
- package/src/app/tasks/[id]/page.tsx +22 -2
- package/src/components/costs/cost-dashboard.tsx +1 -1
- package/src/components/dashboard/greeting.tsx +3 -1
- package/src/components/dashboard/priority-queue.tsx +58 -9
- package/src/components/dashboard/stats-cards.tsx +16 -2
- package/src/components/documents/document-chip-bar.tsx +183 -0
- package/src/components/documents/document-content-renderer.tsx +146 -0
- package/src/components/documents/document-detail-view.tsx +16 -239
- package/src/components/documents/image-zoom-view.tsx +60 -0
- package/src/components/documents/smart-extracted-text.tsx +47 -0
- package/src/components/documents/utils.ts +70 -0
- package/src/components/notifications/inbox-list.tsx +4 -5
- package/src/components/notifications/notification-item.tsx +72 -8
- package/src/components/notifications/pending-approval-host.tsx +7 -4
- package/src/components/playbook/playbook-detail-view.tsx +6 -4
- package/src/components/profiles/profile-browser.tsx +1 -0
- package/src/components/profiles/profile-card.tsx +16 -8
- package/src/components/profiles/profile-detail-view.tsx +6 -1
- package/src/components/shared/app-sidebar.tsx +2 -2
- package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +1 -1
- package/src/components/tasks/ai-assist-panel.tsx +108 -78
- package/src/components/tasks/content-preview.tsx +2 -1
- package/src/components/tasks/kanban-board.tsx +57 -5
- package/src/components/tasks/kanban-column.tsx +34 -23
- package/src/components/tasks/task-bento-cell.tsx +50 -0
- package/src/components/tasks/task-bento-grid.tsx +155 -0
- package/src/components/tasks/task-card.tsx +14 -16
- package/src/components/tasks/task-chip-bar.tsx +207 -0
- package/src/components/tasks/task-detail-view.tsx +42 -190
- package/src/components/tasks/task-result-renderer.tsx +33 -0
- package/src/components/workflows/blueprint-gallery.tsx +19 -12
- package/src/components/workflows/blueprint-preview.tsx +8 -1
- package/src/components/workflows/loop-status-view.tsx +2 -8
- package/src/components/workflows/swarm-dashboard.tsx +2 -3
- package/src/components/workflows/workflow-confirmation-view.tsx +2 -7
- package/src/components/workflows/workflow-full-output.tsx +80 -0
- package/src/components/workflows/workflow-kanban-card.tsx +121 -0
- package/src/components/workflows/workflow-list.tsx +47 -42
- package/src/components/workflows/workflow-status-view.tsx +160 -20
- package/src/lib/agents/learning-session.ts +138 -18
- package/src/lib/constants/card-icons.tsx +202 -0
- package/src/lib/constants/prose-styles.ts +7 -0
- package/src/lib/constants/task-status.ts +3 -0
- package/src/lib/docs/reader.ts +8 -3
- package/src/lib/documents/context-builder.ts +41 -0
- package/src/lib/queries/chart-data.ts +20 -1
- package/src/lib/workflows/engine.ts +57 -61
- package/src/lib/workflows/types.ts +2 -0
- package/tsconfig.json +2 -1
- package/src/components/documents/document-preview.tsx +0 -68
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { Badge } from "@/components/ui/badge";
|
|
4
4
|
import { Card } from "@/components/ui/card";
|
|
5
|
-
import { Shield, MessageCircle, CheckCircle, XCircle, Eye, EyeOff, Trash2, Wallet } from "lucide-react";
|
|
5
|
+
import { Shield, MessageCircle, CheckCircle, XCircle, Eye, EyeOff, Trash2, Wallet, Brain, ChevronRight, ChevronDown } from "lucide-react";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import { LightMarkdown } from "@/components/shared/light-markdown";
|
|
8
8
|
import { useState } from "react";
|
|
9
|
+
import { useRouter } from "next/navigation";
|
|
9
10
|
import { PermissionAction } from "./permission-action";
|
|
10
11
|
import { MessageResponse, type Question } from "./message-response";
|
|
11
12
|
import { FailureAction } from "./failure-action";
|
|
@@ -41,6 +42,8 @@ const typeIcons: Record<string, React.ReactNode> = {
|
|
|
41
42
|
task_completed: <CheckCircle className="h-4 w-4 text-chart-2" aria-hidden="true" />,
|
|
42
43
|
task_failed: <XCircle className="h-4 w-4 text-destructive" aria-hidden="true" />,
|
|
43
44
|
budget_alert: <Wallet className="h-4 w-4 text-status-warning" aria-hidden="true" />,
|
|
45
|
+
context_proposal: <Brain className="h-4 w-4 text-chart-4" aria-hidden="true" />,
|
|
46
|
+
context_proposal_batch: <Brain className="h-4 w-4 text-chart-4" aria-hidden="true" />,
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
const typeLabels: Record<string, string> = {
|
|
@@ -49,6 +52,8 @@ const typeLabels: Record<string, string> = {
|
|
|
49
52
|
task_completed: "Task completed",
|
|
50
53
|
task_failed: "Task failed",
|
|
51
54
|
budget_alert: "Budget alert",
|
|
55
|
+
context_proposal: "Self-learning",
|
|
56
|
+
context_proposal_batch: "Self-learning batch",
|
|
52
57
|
};
|
|
53
58
|
|
|
54
59
|
function formatToolInput(
|
|
@@ -95,12 +100,31 @@ function formatToolInput(
|
|
|
95
100
|
);
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
const navigableTypes = new Set(["task_completed", "task_failed", "permission_required", "agent_message"]);
|
|
104
|
+
|
|
98
105
|
export function NotificationItem({ notification, onUpdated }: NotificationItemProps) {
|
|
106
|
+
const router = useRouter();
|
|
99
107
|
const [toggling, setToggling] = useState(false);
|
|
100
108
|
const [dismissing, setDismissing] = useState(false);
|
|
109
|
+
const [expanded, setExpanded] = useState(false);
|
|
101
110
|
const isUnread = !notification.read;
|
|
102
111
|
const hasResponse = !!notification.response;
|
|
103
112
|
const parsedToolInput = parseNotificationToolInput(notification.toolInput);
|
|
113
|
+
const isNavigable = !!notification.taskId && navigableTypes.has(notification.type);
|
|
114
|
+
|
|
115
|
+
async function handleNavigate() {
|
|
116
|
+
if (!isNavigable) return;
|
|
117
|
+
// Mark as read on click-through
|
|
118
|
+
if (isUnread) {
|
|
119
|
+
await fetch(`/api/notifications/${notification.id}`, {
|
|
120
|
+
method: "PATCH",
|
|
121
|
+
headers: { "Content-Type": "application/json" },
|
|
122
|
+
body: JSON.stringify({ read: true }),
|
|
123
|
+
});
|
|
124
|
+
onUpdated();
|
|
125
|
+
}
|
|
126
|
+
router.push(`/tasks/${notification.taskId}`);
|
|
127
|
+
}
|
|
104
128
|
|
|
105
129
|
async function toggleRead() {
|
|
106
130
|
setToggling(true);
|
|
@@ -134,9 +158,17 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
|
|
|
134
158
|
isUnread
|
|
135
159
|
? "surface-card border-l-4 border-l-primary"
|
|
136
160
|
: "surface-card-muted"
|
|
137
|
-
}`}
|
|
161
|
+
}${isNavigable ? " cursor-pointer hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" : ""}`}
|
|
138
162
|
role="article"
|
|
139
163
|
aria-label={`${typeLabels[notification.type] ?? "Notification"}: ${notification.title}${isUnread ? " (unread)" : ""}`}
|
|
164
|
+
tabIndex={isNavigable ? 0 : undefined}
|
|
165
|
+
onClick={isNavigable ? handleNavigate : undefined}
|
|
166
|
+
onKeyDown={isNavigable ? (e: React.KeyboardEvent) => {
|
|
167
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
handleNavigate();
|
|
170
|
+
}
|
|
171
|
+
} : undefined}
|
|
140
172
|
>
|
|
141
173
|
<div className="flex items-start gap-3">
|
|
142
174
|
<div className="mt-0.5">{typeIcons[notification.type]}</div>
|
|
@@ -175,11 +207,43 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
|
|
|
175
207
|
{notification.body &&
|
|
176
208
|
notification.type !== "permission_required" &&
|
|
177
209
|
notification.type !== "agent_message" && (
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
210
|
+
<div className="mt-1" onClick={(e) => e.stopPropagation()}>
|
|
211
|
+
{expanded ? (
|
|
212
|
+
<>
|
|
213
|
+
<div className="prose prose-sm dark:prose-invert max-w-none max-h-96 overflow-auto rounded-md border bg-muted/30 p-3">
|
|
214
|
+
<LightMarkdown content={notification.body} textSize="sm" />
|
|
215
|
+
</div>
|
|
216
|
+
<Button
|
|
217
|
+
variant="ghost"
|
|
218
|
+
size="sm"
|
|
219
|
+
className="mt-1 h-6 text-xs text-muted-foreground"
|
|
220
|
+
onClick={(e) => { e.stopPropagation(); setExpanded(false); }}
|
|
221
|
+
>
|
|
222
|
+
<ChevronDown className="h-3 w-3 mr-1" />
|
|
223
|
+
Collapse
|
|
224
|
+
</Button>
|
|
225
|
+
</>
|
|
226
|
+
) : (
|
|
227
|
+
<>
|
|
228
|
+
<LightMarkdown
|
|
229
|
+
content={notification.body}
|
|
230
|
+
lineClamp={2}
|
|
231
|
+
textSize="sm"
|
|
232
|
+
/>
|
|
233
|
+
{notification.body.length > 200 && (
|
|
234
|
+
<Button
|
|
235
|
+
variant="ghost"
|
|
236
|
+
size="sm"
|
|
237
|
+
className="mt-1 h-6 text-xs text-muted-foreground"
|
|
238
|
+
onClick={(e) => { e.stopPropagation(); setExpanded(true); }}
|
|
239
|
+
>
|
|
240
|
+
<ChevronRight className="h-3 w-3 mr-1" />
|
|
241
|
+
Expand
|
|
242
|
+
</Button>
|
|
243
|
+
)}
|
|
244
|
+
</>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
183
247
|
)}
|
|
184
248
|
|
|
185
249
|
{/* Actions based on type */}
|
|
@@ -225,7 +289,7 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
|
|
|
225
289
|
)}
|
|
226
290
|
</p>
|
|
227
291
|
</div>
|
|
228
|
-
<div className="flex flex-col gap-1 shrink-0">
|
|
292
|
+
<div className="flex flex-col gap-1 shrink-0" onClick={(e) => e.stopPropagation()}>
|
|
229
293
|
<Button
|
|
230
294
|
variant="ghost"
|
|
231
295
|
size="icon"
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { PermissionResponseActions } from "@/components/notifications/permission-response-actions";
|
|
15
15
|
import { ContextProposalReview } from "@/components/profiles/context-proposal-review";
|
|
16
16
|
import { BatchProposalReview } from "@/components/notifications/batch-proposal-review";
|
|
17
|
+
import { LightMarkdown } from "@/components/shared/light-markdown";
|
|
17
18
|
import { Badge } from "@/components/ui/badge";
|
|
18
19
|
import {
|
|
19
20
|
Dialog,
|
|
@@ -144,10 +145,12 @@ function PendingApprovalDetail({
|
|
|
144
145
|
<p className="mt-2 text-sm text-muted-foreground">
|
|
145
146
|
{selected.compactSummary}
|
|
146
147
|
</p>
|
|
147
|
-
{selected.body &&
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
{selected.body &&
|
|
149
|
+
selected.notificationType !== "context_proposal" &&
|
|
150
|
+
selected.notificationType !== "context_proposal_batch" && (
|
|
151
|
+
<div className="mt-3">
|
|
152
|
+
<LightMarkdown content={selected.body} textSize="sm" />
|
|
153
|
+
</div>
|
|
151
154
|
)}
|
|
152
155
|
<p className="mt-3 text-xs text-muted-foreground">
|
|
153
156
|
Requested {formatTimestamp(selected.createdAt)}
|
|
@@ -7,6 +7,7 @@ import remarkGfm from "remark-gfm";
|
|
|
7
7
|
import { ArrowLeft, ArrowRight, ExternalLink } from "lucide-react";
|
|
8
8
|
import { Button } from "@/components/ui/button";
|
|
9
9
|
import { Badge } from "@/components/ui/badge";
|
|
10
|
+
import { PROSE_READER_FULL } from "@/lib/constants/prose-styles";
|
|
10
11
|
import { PlaybookToc } from "./playbook-toc";
|
|
11
12
|
import { RelatedDocs } from "./related-docs";
|
|
12
13
|
import type { ParsedDoc, DocSection, AdoptionEntry } from "@/lib/docs/types";
|
|
@@ -34,7 +35,8 @@ export function PlaybookDetailView({
|
|
|
34
35
|
}: PlaybookDetailViewProps) {
|
|
35
36
|
const title =
|
|
36
37
|
(doc.frontmatter.title as string) || doc.slug.replace(/-/g, " ");
|
|
37
|
-
const
|
|
38
|
+
const rawTags = doc.frontmatter.tags;
|
|
39
|
+
const tags = Array.isArray(rawTags) ? (rawTags as string[]) : [];
|
|
38
40
|
const route = doc.frontmatter.route as string | undefined;
|
|
39
41
|
const category = doc.frontmatter.category as string | undefined;
|
|
40
42
|
const difficulty = doc.frontmatter.difficulty as string | undefined;
|
|
@@ -126,8 +128,8 @@ export function PlaybookDetailView({
|
|
|
126
128
|
|
|
127
129
|
// Resolve ../screengrabs/ paths to /readme/ (images live in public/readme/)
|
|
128
130
|
let resolvedSrc = src;
|
|
129
|
-
if (src.
|
|
130
|
-
resolvedSrc = `/readme/${src.
|
|
131
|
+
if (src.includes("screengrabs/")) {
|
|
132
|
+
resolvedSrc = `/readme/${src.split("screengrabs/").pop()}`;
|
|
131
133
|
} else if (src.startsWith("./")) {
|
|
132
134
|
resolvedSrc = `/docs/${src.replace("./", "")}`;
|
|
133
135
|
}
|
|
@@ -200,7 +202,7 @@ export function PlaybookDetailView({
|
|
|
200
202
|
|
|
201
203
|
{/* Prose content */}
|
|
202
204
|
<div className="flex-1 min-w-0">
|
|
203
|
-
<div className=
|
|
205
|
+
<div className={PROSE_READER_FULL}>
|
|
204
206
|
<ReactMarkdown
|
|
205
207
|
remarkPlugins={[remarkGfm]}
|
|
206
208
|
components={{
|
|
@@ -4,14 +4,16 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
|
|
4
4
|
import { Badge } from "@/components/ui/badge";
|
|
5
5
|
import { listRuntimeCatalog } from "@/lib/agents/runtime/catalog";
|
|
6
6
|
import { getSupportedRuntimes } from "@/lib/agents/profiles/compatibility";
|
|
7
|
+
import { IconCircle, getProfileIcon, getDomainColors } from "@/lib/constants/card-icons";
|
|
7
8
|
import type { AgentProfile } from "@/lib/agents/profiles/types";
|
|
8
9
|
|
|
9
10
|
interface ProfileCardProps {
|
|
10
11
|
profile: AgentProfile;
|
|
12
|
+
isBuiltin?: boolean;
|
|
11
13
|
onClick: () => void;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
export function ProfileCard({ profile, onClick }: ProfileCardProps) {
|
|
16
|
+
export function ProfileCard({ profile, isBuiltin = true, onClick }: ProfileCardProps) {
|
|
15
17
|
const runtimeLabelMap = new Map(
|
|
16
18
|
listRuntimeCatalog().map((runtime) => [
|
|
17
19
|
runtime.id,
|
|
@@ -32,13 +34,19 @@ export function ProfileCard({ profile, onClick }: ProfileCardProps) {
|
|
|
32
34
|
}
|
|
33
35
|
}}
|
|
34
36
|
>
|
|
35
|
-
<CardHeader className="flex flex-row items-center
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
<CardHeader className="flex flex-row items-center gap-3 space-y-0 pb-2">
|
|
38
|
+
<IconCircle
|
|
39
|
+
icon={getProfileIcon(profile.id)}
|
|
40
|
+
colors={getDomainColors(profile.domain, isBuiltin)}
|
|
41
|
+
/>
|
|
42
|
+
<div className="flex min-w-0 flex-1 items-center justify-between">
|
|
43
|
+
<CardTitle className="truncate text-base font-medium">{profile.name}</CardTitle>
|
|
44
|
+
<Badge
|
|
45
|
+
variant={profile.domain === "work" ? "default" : "secondary"}
|
|
46
|
+
>
|
|
47
|
+
{profile.domain}
|
|
48
|
+
</Badge>
|
|
49
|
+
</div>
|
|
42
50
|
</CardHeader>
|
|
43
51
|
<CardContent className="space-y-3">
|
|
44
52
|
<p className="line-clamp-2 text-sm text-muted-foreground">
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
getSupportedRuntimes,
|
|
41
41
|
} from "@/lib/agents/profiles/compatibility";
|
|
42
42
|
import type { AgentProfile } from "@/lib/agents/profiles/types";
|
|
43
|
+
import { IconCircle, getProfileIcon, getDomainColors } from "@/lib/constants/card-icons";
|
|
43
44
|
|
|
44
45
|
interface TestResult {
|
|
45
46
|
task: string;
|
|
@@ -179,7 +180,11 @@ export function ProfileDetailView({ profileId, isBuiltin, initialProfile }: Prof
|
|
|
179
180
|
<div className="space-y-6" aria-live="polite">
|
|
180
181
|
{/* Header */}
|
|
181
182
|
<div className="flex items-center justify-between">
|
|
182
|
-
<div className="flex items-center gap-
|
|
183
|
+
<div className="flex items-center gap-3">
|
|
184
|
+
<IconCircle
|
|
185
|
+
icon={getProfileIcon(profile.id)}
|
|
186
|
+
colors={getDomainColors(profile.domain, isBuiltin)}
|
|
187
|
+
/>
|
|
183
188
|
<h1 className="text-2xl font-bold">{profile.name}</h1>
|
|
184
189
|
<Badge variant={profile.domain === "work" ? "default" : "secondary"}>
|
|
185
190
|
{profile.domain}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
Inbox,
|
|
9
9
|
Activity,
|
|
10
10
|
FolderKanban,
|
|
11
|
-
|
|
11
|
+
Workflow,
|
|
12
12
|
FileText,
|
|
13
13
|
Bot,
|
|
14
14
|
Clock,
|
|
@@ -40,7 +40,7 @@ const navItems = [
|
|
|
40
40
|
{ title: "Inbox", href: "/inbox", icon: Inbox, badge: true },
|
|
41
41
|
{ title: "Monitor", href: "/monitor", icon: Activity, badge: false },
|
|
42
42
|
{ title: "Projects", href: "/projects", icon: FolderKanban, badge: false },
|
|
43
|
-
{ title: "Workflows", href: "/workflows", icon:
|
|
43
|
+
{ title: "Workflows", href: "/workflows", icon: Workflow, badge: false },
|
|
44
44
|
{ title: "Documents", href: "/documents", icon: FileText, badge: false },
|
|
45
45
|
{ title: "Profiles", href: "/profiles", icon: Bot, badge: false },
|
|
46
46
|
{ title: "Schedules", href: "/schedules", icon: Clock, badge: false },
|
|
@@ -49,7 +49,7 @@ describe("kanban board accessibility", () => {
|
|
|
49
49
|
/>
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
const announcement = screen.getByText("Showing 1
|
|
52
|
+
const announcement = screen.getByText("Showing 1 item on the kanban board.");
|
|
53
53
|
const board = screen.getByRole("region", { name: "Kanban board" });
|
|
54
54
|
|
|
55
55
|
expect(announcement).toHaveAttribute("aria-live", "polite");
|
|
@@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
|
|
|
4
4
|
import { Button } from "@/components/ui/button";
|
|
5
5
|
import { Card, CardContent } from "@/components/ui/card";
|
|
6
6
|
import { Badge } from "@/components/ui/badge";
|
|
7
|
-
import { Sparkles, Check, X, GitBranch } from "lucide-react";
|
|
7
|
+
import { Sparkles, Check, X, GitBranch, Info } from "lucide-react";
|
|
8
8
|
|
|
9
9
|
interface TaskSuggestion {
|
|
10
10
|
title: string;
|
|
@@ -154,9 +154,14 @@ export function AIAssistPanel({
|
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
const showWorkflowCTA =
|
|
158
|
+
onCreateWorkflow &&
|
|
159
|
+
result.breakdown.length >= 2 &&
|
|
160
|
+
result.recommendedPattern !== "single";
|
|
161
|
+
|
|
157
162
|
return (
|
|
158
|
-
<div className="
|
|
159
|
-
{/* Header row
|
|
163
|
+
<div className="p-4 space-y-4">
|
|
164
|
+
{/* Header row */}
|
|
160
165
|
<div className="flex items-center gap-2 flex-wrap">
|
|
161
166
|
<Sparkles className="h-4 w-4 text-primary" />
|
|
162
167
|
<span className="text-sm font-medium">AI Suggestions</span>
|
|
@@ -171,86 +176,111 @@ export function AIAssistPanel({
|
|
|
171
176
|
)}
|
|
172
177
|
</div>
|
|
173
178
|
|
|
174
|
-
{/*
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
179
|
+
{/* Improved description — full width */}
|
|
180
|
+
<Card>
|
|
181
|
+
<CardContent className="p-3">
|
|
182
|
+
<div className="flex items-center justify-between mb-1">
|
|
183
|
+
<span className="text-xs font-medium text-muted-foreground">
|
|
184
|
+
Improved Description
|
|
185
|
+
</span>
|
|
186
|
+
<Button
|
|
187
|
+
type="button"
|
|
188
|
+
variant="ghost"
|
|
189
|
+
size="sm"
|
|
190
|
+
className="h-6 text-xs"
|
|
191
|
+
onClick={() => {
|
|
192
|
+
onApplyDescription(result.improvedDescription);
|
|
193
|
+
setDescriptionApplied(true);
|
|
194
|
+
}}
|
|
195
|
+
disabled={descriptionApplied}
|
|
196
|
+
>
|
|
197
|
+
{descriptionApplied ? (
|
|
198
|
+
<><Check className="h-3 w-3 mr-1" /> Applied</>
|
|
199
|
+
) : (
|
|
200
|
+
"Apply"
|
|
201
|
+
)}
|
|
202
|
+
</Button>
|
|
203
|
+
</div>
|
|
204
|
+
<p className="text-sm">{result.improvedDescription}</p>
|
|
205
|
+
</CardContent>
|
|
206
|
+
</Card>
|
|
207
|
+
|
|
208
|
+
{/* Reasoning + Workflow CTA combined callout */}
|
|
209
|
+
{(result.reasoning || showWorkflowCTA) && (
|
|
210
|
+
<div className="rounded-md border bg-muted/50 p-3 space-y-2.5">
|
|
211
|
+
{result.reasoning && (
|
|
212
|
+
<div className="flex gap-2">
|
|
213
|
+
<Info className="h-3.5 w-3.5 mt-0.5 shrink-0 text-muted-foreground" />
|
|
214
|
+
<p className="text-xs text-muted-foreground">
|
|
215
|
+
{result.reasoning}
|
|
216
|
+
{result.complexity === "complex" && result.recommendedPattern !== "single"
|
|
217
|
+
? " Complex tasks benefit from workflow execution — each step builds on the previous result."
|
|
218
|
+
: ""}
|
|
219
|
+
</p>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
{showWorkflowCTA && (
|
|
223
|
+
<Button
|
|
224
|
+
type="button"
|
|
225
|
+
variant="outline"
|
|
226
|
+
size="sm"
|
|
227
|
+
className="w-full justify-start h-auto py-2 border-primary/50 bg-background"
|
|
228
|
+
onClick={() => onCreateWorkflow(result)}
|
|
229
|
+
>
|
|
230
|
+
<div className="text-left flex-1">
|
|
231
|
+
<div className="flex items-center gap-1.5">
|
|
232
|
+
<GitBranch className="h-3 w-3" />
|
|
233
|
+
<span className="text-xs font-medium">Create as Workflow</span>
|
|
234
|
+
<Badge variant="default" className="text-[10px] h-4 px-1">
|
|
235
|
+
Recommended
|
|
236
|
+
</Badge>
|
|
237
|
+
</div>
|
|
238
|
+
<div className="text-[11px] text-muted-foreground font-normal mt-0.5">
|
|
239
|
+
Runs as {patternLabels[result.recommendedPattern]?.toLowerCase() ?? result.recommendedPattern} — each step receives prior output
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</Button>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
|
|
247
|
+
{/* Breakdown as flat numbered list */}
|
|
248
|
+
{result.breakdown.length > 0 && (
|
|
249
|
+
<>
|
|
250
|
+
{/* "or" divider — only show when workflow CTA is present */}
|
|
251
|
+
{showWorkflowCTA && (
|
|
252
|
+
<div className="flex items-center gap-3">
|
|
253
|
+
<div className="flex-1 border-t" />
|
|
254
|
+
<span className="text-xs text-muted-foreground">or create as individual tasks</span>
|
|
255
|
+
<div className="flex-1 border-t" />
|
|
200
256
|
</div>
|
|
201
|
-
|
|
202
|
-
</CardContent>
|
|
203
|
-
</Card>
|
|
257
|
+
)}
|
|
204
258
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
<span className="text-xs font-medium text-muted-foreground">
|
|
211
|
-
Suggested Breakdown ({result.breakdown.length} sub-tasks)
|
|
259
|
+
<div className="space-y-2.5">
|
|
260
|
+
{result.breakdown.map((sub, i) => (
|
|
261
|
+
<div key={i} className="flex gap-2.5">
|
|
262
|
+
<span className="text-xs font-medium text-muted-foreground mt-0.5 shrink-0 w-4 text-right">
|
|
263
|
+
{i + 1}.
|
|
212
264
|
</span>
|
|
213
|
-
<div className="
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
result.recommendedPattern !== "single" && (
|
|
217
|
-
<Button
|
|
218
|
-
type="button"
|
|
219
|
-
variant="ghost"
|
|
220
|
-
size="sm"
|
|
221
|
-
className="h-6 text-xs"
|
|
222
|
-
onClick={() => onCreateWorkflow(result)}
|
|
223
|
-
>
|
|
224
|
-
<GitBranch className="h-3 w-3 mr-1" />
|
|
225
|
-
Workflow
|
|
226
|
-
</Button>
|
|
227
|
-
)}
|
|
228
|
-
<Button
|
|
229
|
-
type="button"
|
|
230
|
-
variant="ghost"
|
|
231
|
-
size="sm"
|
|
232
|
-
className="h-6 text-xs"
|
|
233
|
-
onClick={() => onCreateSubtasks(result.breakdown)}
|
|
234
|
-
>
|
|
235
|
-
Create All
|
|
236
|
-
</Button>
|
|
265
|
+
<div className="min-w-0">
|
|
266
|
+
<span className="text-sm font-medium">{sub.title}</span>
|
|
267
|
+
<p className="text-xs text-muted-foreground">{sub.description}</p>
|
|
237
268
|
</div>
|
|
238
269
|
</div>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
<div key={i} className="text-sm">
|
|
242
|
-
<span className="font-medium">{sub.title}</span>
|
|
243
|
-
<p className="text-xs text-muted-foreground">{sub.description}</p>
|
|
244
|
-
</div>
|
|
245
|
-
))}
|
|
246
|
-
</div>
|
|
247
|
-
</CardContent>
|
|
248
|
-
</Card>
|
|
249
|
-
)}
|
|
250
|
-
</div>
|
|
270
|
+
))}
|
|
271
|
+
</div>
|
|
251
272
|
|
|
252
|
-
|
|
253
|
-
|
|
273
|
+
<Button
|
|
274
|
+
type="button"
|
|
275
|
+
variant="outline"
|
|
276
|
+
size="sm"
|
|
277
|
+
className="w-full"
|
|
278
|
+
onClick={() => onCreateSubtasks(result.breakdown)}
|
|
279
|
+
>
|
|
280
|
+
Create {result.breakdown.length} Independent Tasks
|
|
281
|
+
</Button>
|
|
282
|
+
</>
|
|
283
|
+
)}
|
|
254
284
|
|
|
255
285
|
<Button
|
|
256
286
|
type="button"
|
|
@@ -260,7 +290,7 @@ export function AIAssistPanel({
|
|
|
260
290
|
setResult(null);
|
|
261
291
|
onResultChange?.(false);
|
|
262
292
|
}}
|
|
263
|
-
className="w-full"
|
|
293
|
+
className="w-full text-muted-foreground"
|
|
264
294
|
>
|
|
265
295
|
<X className="h-3 w-3 mr-1" /> Dismiss
|
|
266
296
|
</Button>
|
|
@@ -7,6 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
8
|
import { Copy, Download, Maximize2, Minimize2 } from "lucide-react";
|
|
9
9
|
import { toast } from "sonner";
|
|
10
|
+
import { PROSE_READER } from "@/lib/constants/prose-styles";
|
|
10
11
|
|
|
11
12
|
interface ContentPreviewProps {
|
|
12
13
|
content: string;
|
|
@@ -75,7 +76,7 @@ export function ContentPreview({ content, contentType }: ContentPreviewProps) {
|
|
|
75
76
|
{content}
|
|
76
77
|
</pre>
|
|
77
78
|
) : contentType === "markdown" ? (
|
|
78
|
-
<div className={
|
|
79
|
+
<div className={`${PROSE_READER} overflow-auto ${heightClass}`}>
|
|
79
80
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
|
|
80
81
|
</div>
|
|
81
82
|
) : (
|