sparkecoder 0.1.83 → 0.1.85
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/dist/agent/index.js +125 -22
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +532 -395
- package/dist/cli.js.map +1 -1
- package/dist/index.js +532 -395
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +532 -395
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +19 -36
- package/dist/tools/index.js +99 -10
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_02a118f9._.js → 2374f_00f7fe07._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ad08e83a._.js → 2374f_2801b766._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_0ed477f8._.js → 2374f_369747ce._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_3b51a934._.js → 2374f_60d8842c._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_acf3dfe4._.js → 2374f_806bd012._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_db3e363b._.js → 2374f_8dc0f9aa._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f0d7e130._.js → 2374f_9adc1edb._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5ebfcf1a._.js → 2374f_b7f45fdf._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_12bad06e._.js → 2374f_c13c8f4f._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_fc992d90._.js → 2374f_cc6c6363._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_3e519469._.js → 2374f_d58d0276._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_a0f483d1._.js → 2374f_ecd2bdca._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c1d54c16._.js → 2374f_f363c084._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2526ca80._.js → 2374f_fdfc7f3d._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__06818a54._.js → [root-of-the-server]__25b25c9d._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__a1877334._.js → [root-of-the-server]__9d3a7cbf._.js} +4 -4
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_cc5f7515._.js → web_08242997._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_2b3a5919._.js → web_123ffe97._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_38156da8._.js → web_99b01335._.js} +2 -2
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/{f95d41079838994a.js → 2624c966c288fd41.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/74b64476a24dd71e.css +1 -0
- package/web/.next/standalone/web/.next/static/{static/chunks/fc39a194539da104.js → chunks/8af263bc97c0c9ee.js} +1 -1
- package/web/.next/{static/chunks/2cafc7cb79454d33.js → standalone/web/.next/static/chunks/cfadc93a98190e5a.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/{f95d41079838994a.js → 2624c966c288fd41.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/74b64476a24dd71e.css +1 -0
- package/web/.next/{static/chunks/fc39a194539da104.js → standalone/web/.next/static/static/chunks/8af263bc97c0c9ee.js} +1 -1
- package/web/.next/standalone/web/.next/static/{chunks/2cafc7cb79454d33.js → static/chunks/cfadc93a98190e5a.js} +1 -1
- package/web/.next/standalone/web/src/components/ai-elements/todo-panel.tsx +194 -110
- package/web/.next/standalone/web/src/components/ai-elements/todo-tool.tsx +78 -1
- package/web/.next/standalone/web/src/components/chat-interface.tsx +15 -9
- package/web/.next/standalone/web/src/lib/api.ts +17 -0
- package/web/.next/static/chunks/{f95d41079838994a.js → 2624c966c288fd41.js} +3 -3
- package/web/.next/static/chunks/74b64476a24dd71e.css +1 -0
- package/web/.next/{standalone/web/.next/static/chunks/fc39a194539da104.js → static/chunks/8af263bc97c0c9ee.js} +1 -1
- package/web/.next/{standalone/web/.next/static/static/chunks/2cafc7cb79454d33.js → static/chunks/cfadc93a98190e5a.js} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/41a5c049931b2c77.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/41a5c049931b2c77.css +0 -1
- package/web/.next/static/chunks/41a5c049931b2c77.css +0 -1
- /package/web/.next/standalone/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_ssgManifest.js +0 -0
- /package/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_buildManifest.js +0 -0
- /package/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{aCZCpTkVv_k-RisOFPegk → J0gen1p9aNjUNIU1NDO5h}/_ssgManifest.js +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
|
-
import { type TodoItem, type TodosResponse } from "@/lib/api";
|
|
4
|
+
import { type TodoItem, type TodosResponse, type PlanItem } from "@/lib/api";
|
|
5
5
|
import {
|
|
6
6
|
CheckCircle2Icon,
|
|
7
7
|
CircleIcon,
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
ChevronDownIcon,
|
|
11
11
|
ChevronRightIcon,
|
|
12
12
|
ListTodoIcon,
|
|
13
|
+
FileTextIcon,
|
|
14
|
+
XIcon,
|
|
13
15
|
} from "lucide-react";
|
|
14
16
|
import { useState } from "react";
|
|
15
17
|
import {
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
|
|
21
23
|
export interface TodoPanelProps {
|
|
22
24
|
todosData: TodosResponse | null;
|
|
25
|
+
plans?: PlanItem[];
|
|
23
26
|
isLoading?: boolean;
|
|
24
27
|
className?: string;
|
|
25
28
|
}
|
|
@@ -38,14 +41,20 @@ const statusColors: Record<string, string> = {
|
|
|
38
41
|
cancelled: "text-muted-foreground/50",
|
|
39
42
|
};
|
|
40
43
|
|
|
41
|
-
export function TodoPanel({ todosData, isLoading, className }: TodoPanelProps) {
|
|
44
|
+
export function TodoPanel({ todosData, plans, isLoading, className }: TodoPanelProps) {
|
|
42
45
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
46
|
+
const [viewingPlan, setViewingPlan] = useState<PlanItem | null>(null);
|
|
47
|
+
|
|
48
|
+
const hasTodos = todosData && todosData.todos.length > 0;
|
|
49
|
+
const hasPlans = plans && plans.length > 0;
|
|
43
50
|
|
|
44
|
-
if (!
|
|
51
|
+
if (!hasTodos && !hasPlans) {
|
|
45
52
|
return null;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
const
|
|
55
|
+
const todos = todosData?.todos || [];
|
|
56
|
+
const stats = todosData?.stats || { total: 0, pending: 0, inProgress: 0, completed: 0, cancelled: 0 };
|
|
57
|
+
const nextTodo = todosData?.nextTodo || null;
|
|
49
58
|
|
|
50
59
|
// Sort: in_progress first, then pending, then completed/cancelled
|
|
51
60
|
const sortedTodos = [...todos].sort((a, b) => {
|
|
@@ -58,121 +67,196 @@ export function TodoPanel({ todosData, isLoading, className }: TodoPanelProps) {
|
|
|
58
67
|
: 0;
|
|
59
68
|
|
|
60
69
|
return (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
{stats.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
70
|
+
<>
|
|
71
|
+
<div className={cn(
|
|
72
|
+
"rounded-lg border bg-card/50 backdrop-blur-sm overflow-hidden",
|
|
73
|
+
className
|
|
74
|
+
)}>
|
|
75
|
+
{/* Header - always shows current task and stats */}
|
|
76
|
+
<button
|
|
77
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
78
|
+
className="flex items-center justify-between w-full px-3 py-2 hover:bg-muted/50 transition-colors"
|
|
79
|
+
>
|
|
80
|
+
<div className="flex items-center gap-2 min-w-0 flex-1">
|
|
81
|
+
<ListTodoIcon className="size-4 text-primary shrink-0" />
|
|
82
|
+
{/* Show current task inline when collapsed */}
|
|
83
|
+
{!isExpanded && nextTodo ? (
|
|
84
|
+
<div className="flex items-center gap-2 min-w-0 flex-1">
|
|
85
|
+
<CircleDotIcon className="size-3.5 text-primary shrink-0" />
|
|
86
|
+
<span className="text-sm truncate">{nextTodo.content}</span>
|
|
87
|
+
</div>
|
|
88
|
+
) : (
|
|
89
|
+
<span className="font-medium text-sm">Tasks</span>
|
|
90
|
+
)}
|
|
91
|
+
{stats.total > 0 && (
|
|
92
|
+
<span className="text-xs text-muted-foreground shrink-0">
|
|
93
|
+
{stats.completed}/{stats.total}
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
{hasPlans && (
|
|
97
|
+
<span className="text-xs text-muted-foreground shrink-0 flex items-center gap-1">
|
|
98
|
+
<FileTextIcon className="size-3" />
|
|
99
|
+
{plans!.length} plan{plans!.length !== 1 ? 's' : ''}
|
|
100
|
+
</span>
|
|
101
|
+
)}
|
|
92
102
|
</div>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{isExpanded && (
|
|
102
|
-
<div className="border-t">
|
|
103
|
-
{/* Next todo highlight */}
|
|
104
|
-
{nextTodo && (
|
|
105
|
-
<div className="px-3 py-2 bg-primary/5 border-b flex items-center gap-2">
|
|
106
|
-
<CircleDotIcon className="size-4 text-primary shrink-0" />
|
|
107
|
-
<div className="min-w-0 flex-1">
|
|
108
|
-
<span className="text-xs text-primary font-medium uppercase tracking-wide">
|
|
109
|
-
Current
|
|
110
|
-
</span>
|
|
111
|
-
<p className="text-sm font-medium truncate">{nextTodo.content}</p>
|
|
103
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
104
|
+
{stats.total > 0 && (
|
|
105
|
+
<div className="w-16 h-1.5 bg-muted rounded-full overflow-hidden">
|
|
106
|
+
<div
|
|
107
|
+
className="h-full bg-primary transition-all duration-300"
|
|
108
|
+
style={{ width: `${progressPercent}%` }}
|
|
109
|
+
/>
|
|
112
110
|
</div>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const Icon = statusIcons[todo.status] || CircleIcon;
|
|
120
|
-
const color = statusColors[todo.status] || "text-muted-foreground";
|
|
121
|
-
const isNext = todo.id === nextTodo?.id;
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<Tooltip key={todo.id}>
|
|
125
|
-
<TooltipTrigger asChild>
|
|
126
|
-
<div
|
|
127
|
-
className={cn(
|
|
128
|
-
"flex items-start gap-2 px-3 py-1.5 hover:bg-muted/30 transition-colors cursor-default",
|
|
129
|
-
isNext && "bg-primary/5"
|
|
130
|
-
)}
|
|
131
|
-
>
|
|
132
|
-
<Icon className={cn("size-4 mt-0.5 shrink-0", color)} />
|
|
133
|
-
<span className={cn(
|
|
134
|
-
"text-sm flex-1 min-w-0 truncate",
|
|
135
|
-
todo.status === "cancelled" && "line-through text-muted-foreground/50",
|
|
136
|
-
todo.status === "completed" && "text-muted-foreground"
|
|
137
|
-
)}>
|
|
138
|
-
{todo.content}
|
|
139
|
-
</span>
|
|
140
|
-
</div>
|
|
141
|
-
</TooltipTrigger>
|
|
142
|
-
<TooltipContent side="left" className="max-w-xs">
|
|
143
|
-
<p className="text-sm">{todo.content}</p>
|
|
144
|
-
<p className="text-xs text-muted-foreground capitalize mt-1">
|
|
145
|
-
{todo.status.replace('_', ' ')}
|
|
146
|
-
</p>
|
|
147
|
-
</TooltipContent>
|
|
148
|
-
</Tooltip>
|
|
149
|
-
);
|
|
150
|
-
})}
|
|
111
|
+
)}
|
|
112
|
+
{isExpanded ? (
|
|
113
|
+
<ChevronDownIcon className="size-4 text-muted-foreground" />
|
|
114
|
+
) : (
|
|
115
|
+
<ChevronRightIcon className="size-4 text-muted-foreground" />
|
|
116
|
+
)}
|
|
151
117
|
</div>
|
|
118
|
+
</button>
|
|
152
119
|
|
|
153
|
-
|
|
154
|
-
<div className="
|
|
155
|
-
{
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{
|
|
159
|
-
|
|
120
|
+
{isExpanded && (
|
|
121
|
+
<div className="border-t">
|
|
122
|
+
{/* Plans section */}
|
|
123
|
+
{hasPlans && (
|
|
124
|
+
<div className="border-b">
|
|
125
|
+
{plans!.map((plan) => (
|
|
126
|
+
<button
|
|
127
|
+
key={plan.name}
|
|
128
|
+
onClick={() => setViewingPlan(plan)}
|
|
129
|
+
className="flex items-center gap-2 w-full px-3 py-1.5 hover:bg-muted/30 transition-colors text-left"
|
|
130
|
+
>
|
|
131
|
+
<FileTextIcon className="size-4 text-blue-500 shrink-0" />
|
|
132
|
+
<span className="text-sm font-medium flex-1 min-w-0 truncate">{plan.name}</span>
|
|
133
|
+
<span className="text-xs text-muted-foreground shrink-0">View</span>
|
|
134
|
+
</button>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
160
137
|
)}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
138
|
+
|
|
139
|
+
{/* Next todo highlight */}
|
|
140
|
+
{nextTodo && (
|
|
141
|
+
<div className="px-3 py-2 bg-primary/5 border-b flex items-center gap-2">
|
|
142
|
+
<CircleDotIcon className="size-4 text-primary shrink-0" />
|
|
143
|
+
<div className="min-w-0 flex-1">
|
|
144
|
+
<span className="text-xs text-primary font-medium uppercase tracking-wide">
|
|
145
|
+
Current
|
|
146
|
+
</span>
|
|
147
|
+
<p className="text-sm font-medium truncate">{nextTodo.content}</p>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
166
150
|
)}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
151
|
+
|
|
152
|
+
{/* Todo list */}
|
|
153
|
+
{sortedTodos.length > 0 && (
|
|
154
|
+
<div className="max-h-[200px] overflow-auto">
|
|
155
|
+
{sortedTodos.map((todo) => {
|
|
156
|
+
const Icon = statusIcons[todo.status] || CircleIcon;
|
|
157
|
+
const color = statusColors[todo.status] || "text-muted-foreground";
|
|
158
|
+
const isNext = todo.id === nextTodo?.id;
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Tooltip key={todo.id}>
|
|
162
|
+
<TooltipTrigger asChild>
|
|
163
|
+
<div
|
|
164
|
+
className={cn(
|
|
165
|
+
"flex items-start gap-2 px-3 py-1.5 hover:bg-muted/30 transition-colors cursor-default",
|
|
166
|
+
isNext && "bg-primary/5"
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
<Icon className={cn("size-4 mt-0.5 shrink-0", color)} />
|
|
170
|
+
<span className={cn(
|
|
171
|
+
"text-sm flex-1 min-w-0 truncate",
|
|
172
|
+
todo.status === "cancelled" && "line-through text-muted-foreground/50",
|
|
173
|
+
todo.status === "completed" && "text-muted-foreground"
|
|
174
|
+
)}>
|
|
175
|
+
{todo.content}
|
|
176
|
+
</span>
|
|
177
|
+
</div>
|
|
178
|
+
</TooltipTrigger>
|
|
179
|
+
<TooltipContent side="left" className="max-w-xs">
|
|
180
|
+
<p className="text-sm">{todo.content}</p>
|
|
181
|
+
<p className="text-xs text-muted-foreground capitalize mt-1">
|
|
182
|
+
{todo.status.replace('_', ' ')}
|
|
183
|
+
</p>
|
|
184
|
+
</TooltipContent>
|
|
185
|
+
</Tooltip>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{/* Stats footer */}
|
|
192
|
+
{stats.total > 0 && (
|
|
193
|
+
<div className="px-3 py-1.5 border-t bg-muted/30 flex items-center gap-3 text-xs text-muted-foreground">
|
|
194
|
+
{stats.inProgress > 0 && (
|
|
195
|
+
<span className="flex items-center gap-1">
|
|
196
|
+
<CircleDotIcon className="size-3 text-primary" />
|
|
197
|
+
{stats.inProgress} active
|
|
198
|
+
</span>
|
|
199
|
+
)}
|
|
200
|
+
{stats.pending > 0 && (
|
|
201
|
+
<span className="flex items-center gap-1">
|
|
202
|
+
<CircleIcon className="size-3" />
|
|
203
|
+
{stats.pending} pending
|
|
204
|
+
</span>
|
|
205
|
+
)}
|
|
206
|
+
{stats.completed > 0 && (
|
|
207
|
+
<span className="flex items-center gap-1">
|
|
208
|
+
<CheckCircle2Icon className="size-3 text-green-500" />
|
|
209
|
+
{stats.completed} done
|
|
210
|
+
</span>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
172
213
|
)}
|
|
173
214
|
</div>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{/* Plan viewer modal */}
|
|
219
|
+
{viewingPlan && (
|
|
220
|
+
<div
|
|
221
|
+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
|
222
|
+
onClick={() => setViewingPlan(null)}
|
|
223
|
+
>
|
|
224
|
+
<div
|
|
225
|
+
className="bg-background rounded-xl border shadow-2xl w-full max-w-2xl max-h-[80vh] flex flex-col mx-4"
|
|
226
|
+
onClick={(e) => e.stopPropagation()}
|
|
227
|
+
>
|
|
228
|
+
{/* Modal header */}
|
|
229
|
+
<div className="flex items-center justify-between px-5 py-4 border-b shrink-0">
|
|
230
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
231
|
+
<FileTextIcon className="size-5 text-blue-500 shrink-0" />
|
|
232
|
+
<h2 className="font-semibold text-lg truncate">{viewingPlan.name}</h2>
|
|
233
|
+
</div>
|
|
234
|
+
<button
|
|
235
|
+
onClick={() => setViewingPlan(null)}
|
|
236
|
+
className="p-1 rounded-md hover:bg-muted transition-colors shrink-0"
|
|
237
|
+
>
|
|
238
|
+
<XIcon className="size-5" />
|
|
239
|
+
</button>
|
|
240
|
+
</div>
|
|
241
|
+
{/* Modal body — markdown content */}
|
|
242
|
+
<div className="overflow-auto flex-1 px-5 py-4">
|
|
243
|
+
<div className="prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap font-mono text-sm leading-relaxed">
|
|
244
|
+
{viewingPlan.content}
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
{/* Modal footer */}
|
|
248
|
+
<div className="px-5 py-3 border-t bg-muted/30 flex items-center justify-between text-xs text-muted-foreground shrink-0">
|
|
249
|
+
<span>{viewingPlan.sizeChars.toLocaleString()} characters</span>
|
|
250
|
+
<button
|
|
251
|
+
onClick={() => setViewingPlan(null)}
|
|
252
|
+
className="px-3 py-1.5 rounded-md bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors"
|
|
253
|
+
>
|
|
254
|
+
Close
|
|
255
|
+
</button>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
174
258
|
</div>
|
|
175
259
|
)}
|
|
176
|
-
|
|
260
|
+
</>
|
|
177
261
|
);
|
|
178
262
|
}
|
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
TrashIcon,
|
|
14
14
|
XCircleIcon,
|
|
15
15
|
ClockIcon,
|
|
16
|
+
FileTextIcon,
|
|
17
|
+
FolderOpenIcon,
|
|
18
|
+
EyeIcon,
|
|
19
|
+
Trash2Icon,
|
|
16
20
|
} from "lucide-react";
|
|
17
21
|
import { useState } from "react";
|
|
18
22
|
|
|
@@ -25,10 +29,12 @@ export interface TodoItem {
|
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export interface TodoInput {
|
|
28
|
-
action: "add" | "list" | "mark" | "clear";
|
|
32
|
+
action: "add" | "list" | "mark" | "clear" | "save_plan" | "list_plans" | "get_plan" | "delete_plan";
|
|
29
33
|
items?: { content: string; order?: number }[];
|
|
30
34
|
todoId?: string;
|
|
31
35
|
status?: "pending" | "in_progress" | "completed" | "cancelled";
|
|
36
|
+
planName?: string;
|
|
37
|
+
planContent?: string;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export interface TodoOutput {
|
|
@@ -46,6 +52,14 @@ export interface TodoOutput {
|
|
|
46
52
|
completed: number;
|
|
47
53
|
cancelled: number;
|
|
48
54
|
};
|
|
55
|
+
planName?: string;
|
|
56
|
+
filename?: string;
|
|
57
|
+
sizeChars?: number;
|
|
58
|
+
content?: string;
|
|
59
|
+
plans?: Array<{ name: string; title: string; filename: string; sizeChars: number }>;
|
|
60
|
+
count?: number;
|
|
61
|
+
autoCreatedTodos?: TodoItem[];
|
|
62
|
+
autoCreatedFromPhase?: string;
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
export interface TodoToolProps {
|
|
@@ -74,6 +88,10 @@ const actionLabels: Record<string, string> = {
|
|
|
74
88
|
list: "List Tasks",
|
|
75
89
|
mark: "Update Task",
|
|
76
90
|
clear: "Clear Tasks",
|
|
91
|
+
save_plan: "Save Plan",
|
|
92
|
+
list_plans: "List Plans",
|
|
93
|
+
get_plan: "View Plan",
|
|
94
|
+
delete_plan: "Delete Plan",
|
|
77
95
|
};
|
|
78
96
|
|
|
79
97
|
const ActionIcon = ({ action }: { action: string }) => {
|
|
@@ -86,6 +104,14 @@ const ActionIcon = ({ action }: { action: string }) => {
|
|
|
86
104
|
return <CheckCircleIcon className="size-3.5" />;
|
|
87
105
|
case "clear":
|
|
88
106
|
return <TrashIcon className="size-3.5" />;
|
|
107
|
+
case "save_plan":
|
|
108
|
+
return <FileTextIcon className="size-3.5" />;
|
|
109
|
+
case "list_plans":
|
|
110
|
+
return <FolderOpenIcon className="size-3.5" />;
|
|
111
|
+
case "get_plan":
|
|
112
|
+
return <EyeIcon className="size-3.5" />;
|
|
113
|
+
case "delete_plan":
|
|
114
|
+
return <Trash2Icon className="size-3.5" />;
|
|
89
115
|
default:
|
|
90
116
|
return <ListTodoIcon className="size-3.5" />;
|
|
91
117
|
}
|
|
@@ -225,6 +251,57 @@ export function TodoTool({
|
|
|
225
251
|
</div>
|
|
226
252
|
)}
|
|
227
253
|
|
|
254
|
+
{/* Plan actions output */}
|
|
255
|
+
{output?.action === "save_plan" && output.planName && (
|
|
256
|
+
<div className="px-3 py-2 space-y-1">
|
|
257
|
+
<div className="flex items-center gap-2 text-sm">
|
|
258
|
+
<FileTextIcon className="size-4 text-blue-500 shrink-0" />
|
|
259
|
+
<span className="font-medium">{output.planName}</span>
|
|
260
|
+
<span className="text-xs text-muted-foreground">
|
|
261
|
+
({(output.sizeChars ?? 0).toLocaleString()} chars)
|
|
262
|
+
</span>
|
|
263
|
+
</div>
|
|
264
|
+
{output.autoCreatedTodos && output.autoCreatedTodos.length > 0 && (
|
|
265
|
+
<div className="mt-2 pt-2 border-t">
|
|
266
|
+
<span className="text-xs text-green-600 dark:text-green-400 font-medium">
|
|
267
|
+
Auto-created {output.autoCreatedTodos.length} todo(s) from plan
|
|
268
|
+
</span>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
{output?.action === "list_plans" && output.plans && (
|
|
274
|
+
<div className="px-3 py-2 max-h-[200px] overflow-auto">
|
|
275
|
+
{output.plans.length === 0 ? (
|
|
276
|
+
<p className="text-sm text-muted-foreground">No plans yet</p>
|
|
277
|
+
) : (
|
|
278
|
+
output.plans.map((plan) => (
|
|
279
|
+
<div key={plan.name} className="flex items-center gap-2 py-1">
|
|
280
|
+
<FileTextIcon className="size-4 text-blue-500 shrink-0" />
|
|
281
|
+
<span className="text-sm font-medium">{plan.title || plan.name}</span>
|
|
282
|
+
<span className="text-xs text-muted-foreground">
|
|
283
|
+
({plan.sizeChars.toLocaleString()} chars)
|
|
284
|
+
</span>
|
|
285
|
+
</div>
|
|
286
|
+
))
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
{output?.action === "get_plan" && output.content && (
|
|
291
|
+
<div className="px-3 py-2 max-h-[300px] overflow-auto">
|
|
292
|
+
<pre className="text-xs font-mono whitespace-pre-wrap leading-relaxed text-muted-foreground">
|
|
293
|
+
{output.content.length > 2000
|
|
294
|
+
? output.content.slice(0, 2000) + '\n... (truncated in UI)'
|
|
295
|
+
: output.content}
|
|
296
|
+
</pre>
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
{output?.action === "delete_plan" && output.planName && (
|
|
300
|
+
<div className="px-3 py-2 text-sm text-muted-foreground">
|
|
301
|
+
Deleted plan: {output.planName}
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
|
|
228
305
|
{/* Empty state */}
|
|
229
306
|
{action === "list" && output?.items?.length === 0 && (
|
|
230
307
|
<div className="px-3 py-4 text-center text-muted-foreground text-sm">
|
|
@@ -97,6 +97,8 @@ import {
|
|
|
97
97
|
updateSession,
|
|
98
98
|
updateToolApproval,
|
|
99
99
|
getSessionTodos,
|
|
100
|
+
getSessionPlans,
|
|
101
|
+
type PlanItem,
|
|
100
102
|
getSessionCheckpoints,
|
|
101
103
|
revertToCheckpoint,
|
|
102
104
|
checkVersion,
|
|
@@ -403,6 +405,7 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
403
405
|
const [currentToolCalls, setCurrentToolCalls] = useState<ToolCallInfo[]>([]);
|
|
404
406
|
const [pendingApprovals, setPendingApprovals] = useState<PendingApproval[]>([]);
|
|
405
407
|
const [todosData, setTodosData] = useState<TodosResponse | null>(null);
|
|
408
|
+
const [plansData, setPlansData] = useState<PlanItem[]>([]);
|
|
406
409
|
const [checkpoints, setCheckpoints] = useState<Checkpoint[]>([]);
|
|
407
410
|
const [isReverting, setIsReverting] = useState(false);
|
|
408
411
|
const currentTextRef = useRef('');
|
|
@@ -1621,21 +1624,24 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
1621
1624
|
let isStale = false;
|
|
1622
1625
|
const currentSessionId = session.id;
|
|
1623
1626
|
|
|
1624
|
-
const
|
|
1627
|
+
const checkTodosAndPlans = async () => {
|
|
1625
1628
|
try {
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1629
|
+
const [todosResult, plansResult] = await Promise.all([
|
|
1630
|
+
getSessionTodos(currentSessionId),
|
|
1631
|
+
getSessionPlans(currentSessionId).catch(() => ({ plans: [] as PlanItem[] })),
|
|
1632
|
+
]);
|
|
1628
1633
|
if (isStale) return;
|
|
1629
|
-
setTodosData(
|
|
1634
|
+
setTodosData(todosResult);
|
|
1635
|
+
setPlansData(plansResult.plans || []);
|
|
1630
1636
|
} catch {
|
|
1631
1637
|
// Ignore errors
|
|
1632
1638
|
}
|
|
1633
1639
|
};
|
|
1634
1640
|
// Initial fetch
|
|
1635
|
-
|
|
1641
|
+
checkTodosAndPlans();
|
|
1636
1642
|
// Poll every 2 seconds when running, 5 seconds otherwise
|
|
1637
1643
|
const pollInterval = isRunning ? 1000 : 5000;
|
|
1638
|
-
const interval = setInterval(
|
|
1644
|
+
const interval = setInterval(checkTodosAndPlans, pollInterval);
|
|
1639
1645
|
return () => {
|
|
1640
1646
|
isStale = true;
|
|
1641
1647
|
clearInterval(interval);
|
|
@@ -2813,10 +2819,10 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
2813
2819
|
</div>
|
|
2814
2820
|
)}
|
|
2815
2821
|
|
|
2816
|
-
{/* Todo Panel - hidden in embed mode */}
|
|
2817
|
-
{!isEmbed && todosData && todosData.todos && todosData.todos.length > 0 && (
|
|
2822
|
+
{/* Todo Panel + Plans - hidden in embed mode */}
|
|
2823
|
+
{!isEmbed && ((todosData && todosData.todos && todosData.todos.length > 0) || plansData.length > 0) && (
|
|
2818
2824
|
<div className="px-4 py-2 border-b border-border/50 bg-muted/20">
|
|
2819
|
-
<TodoPanel todosData={todosData} />
|
|
2825
|
+
<TodoPanel todosData={todosData} plans={plansData} />
|
|
2820
2826
|
</div>
|
|
2821
2827
|
)}
|
|
2822
2828
|
|
|
@@ -187,6 +187,23 @@ export async function getSessionTodos(sessionId: string): Promise<TodosResponse>
|
|
|
187
187
|
return res.json();
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
export interface PlanItem {
|
|
191
|
+
name: string;
|
|
192
|
+
content: string;
|
|
193
|
+
sizeChars: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface PlansResponse {
|
|
197
|
+
sessionId: string;
|
|
198
|
+
plans: PlanItem[];
|
|
199
|
+
count: number;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function getSessionPlans(sessionId: string): Promise<PlansResponse> {
|
|
203
|
+
const res = await fetch(`${getApiBase()}/sessions/${sessionId}/plans`);
|
|
204
|
+
return res.json();
|
|
205
|
+
}
|
|
206
|
+
|
|
190
207
|
export interface PendingInputResponse {
|
|
191
208
|
hasPendingInput: boolean;
|
|
192
209
|
text: string | null;
|