diff --git a/CHANGELOG.md b/CHANGELOG.md index 731d446f9..db94297e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Only write entries that are worth mentioning to users. ## Unreleased +- Web: Add todo list display in prompt toolbar — shows task progress with expandable panel when the `SetTodoList` tool is active + ## 1.16.0 (2026-02-27) - Web: Update ASCII logo banner to a new styled design diff --git a/docs/en/release-notes/changelog.md b/docs/en/release-notes/changelog.md index 6b9f481bd..220cf9610 100644 --- a/docs/en/release-notes/changelog.md +++ b/docs/en/release-notes/changelog.md @@ -4,6 +4,8 @@ This page documents the changes in each Kimi Code CLI release. ## Unreleased +- Web: Add todo list display in prompt toolbar — shows task progress with expandable panel when the `SetTodoList` tool is active + ## 1.16.0 (2026-02-27) - Web: Update ASCII logo banner to a new styled design diff --git a/docs/zh/release-notes/changelog.md b/docs/zh/release-notes/changelog.md index 47432d74f..2a2cf90db 100644 --- a/docs/zh/release-notes/changelog.md +++ b/docs/zh/release-notes/changelog.md @@ -4,6 +4,8 @@ ## 未发布 +- Web:在提示输入工具栏中添加待办列表显示——当 `SetTodoList` 工具激活时,显示任务进度并支持展开面板查看详情 + ## 1.16.0 (2026-02-27) - Web:更新 ASCII Logo 横幅为新的样式设计 diff --git a/web/src/features/chat/components/prompt-toolbar/index.tsx b/web/src/features/chat/components/prompt-toolbar/index.tsx index 799ceffd4..ccceef6a0 100644 --- a/web/src/features/chat/components/prompt-toolbar/index.tsx +++ b/web/src/features/chat/components/prompt-toolbar/index.tsx @@ -10,14 +10,16 @@ import { cn } from "@/lib/utils"; import type { GitDiffStats } from "@/lib/api/models"; import type { TokenUsage } from "@/hooks/wireTypes"; import { useQueueStore } from "../../queue-store"; +import { useToolEventsStore } from "@/features/tool/store"; import { ToolbarActivityIndicator, type ActivityDetail } from "../activity-status-indicator"; import { ToolbarQueuePanel, ToolbarQueueTab } from "./toolbar-queue"; import { ToolbarChangesPanel, ToolbarChangesTab } from "./toolbar-changes"; +import { ToolbarTodoPanel, ToolbarTodoTab } from "./toolbar-todo"; import { ToolbarContextIndicator } from "./toolbar-context"; // ─── Types ─────────────────────────────────────────────────── -type TabId = "queue" | "changes"; +type TabId = "queue" | "changes" | "todo"; type PromptToolbarProps = { gitDiffStats?: GitDiffStats | null; @@ -43,14 +45,16 @@ export const PromptToolbar = memo(function PromptToolbarComponent({ tokenUsage, }: PromptToolbarProps): ReactElement | null { const queue = useQueueStore((s) => s.queue); + const todoItems = useToolEventsStore((s) => s.todoItems); const [activeTab, setActiveTab] = useState(null); const prevQueueLenRef = useRef(0); const stats = gitDiffStats; const hasChanges = Boolean(stats?.isGitRepo && stats.hasChanges && stats.files && !stats.error); const hasQueue = queue.length > 0; + const hasTodo = todoItems.length > 0; const hasContext = usagePercent !== undefined && usedTokens !== undefined && maxTokens !== undefined; - const hasTabs = hasQueue || hasChanges; + const hasTabs = hasQueue || hasChanges || hasTodo; // Auto-open queue tab when first item is added useEffect(() => { @@ -64,7 +68,8 @@ export const PromptToolbar = memo(function PromptToolbarComponent({ useEffect(() => { if (activeTab === "queue" && !hasQueue) setActiveTab(null); if (activeTab === "changes" && !hasChanges) setActiveTab(null); - }, [activeTab, hasQueue, hasChanges]); + if (activeTab === "todo" && !hasTodo) setActiveTab(null); + }, [activeTab, hasQueue, hasChanges, hasTodo]); const toggleTab = useCallback((tab: TabId) => { setActiveTab((prev) => (prev === tab ? null : tab)); @@ -81,6 +86,9 @@ export const PromptToolbar = memo(function PromptToolbarComponent({ {activeTab === "changes" && stats && ( )} + {activeTab === "todo" && ( + + )} )} @@ -106,6 +114,14 @@ export const PromptToolbar = memo(function PromptToolbarComponent({ /> )} + {hasTodo && ( + toggleTab("todo")} + /> + )} + {hasContext && ( + {items.map((item, index) => ( +
+ {item.status === "done" && ( + + )} + {item.status === "in_progress" && ( + + )} + {item.status === "pending" && ( + + )} + + {item.title} + +
+ ))} + + ); +}); + +// ─── Tab ───────────────────────────────────────────────────── + +type ToolbarTodoTabProps = { + items: TodoItem[]; + isActive: boolean; + onToggle: () => void; +}; + +export const ToolbarTodoTab = memo(function ToolbarTodoTabComponent({ + items, + isActive, + onToggle, +}: ToolbarTodoTabProps): ReactElement { + const doneCount = items.filter((i) => i.status === "done").length; + const totalCount = items.length; + + return ( + + ); +}); diff --git a/web/src/features/tool/store.ts b/web/src/features/tool/store.ts index 064721ec1..62bcc3676 100644 --- a/web/src/features/tool/store.ts +++ b/web/src/features/tool/store.ts @@ -1,5 +1,10 @@ import { create } from "zustand"; +export type TodoItem = { + title: string; + status: "pending" | "in_progress" | "done"; +}; + type ToolEventsState = { /** Files written during the current session/turn */ newFiles: string[]; @@ -8,6 +13,11 @@ type ToolEventsState = { addNewFile: (path: string) => void; /** Clear all new files (e.g., when opening files panel or starting new turn) */ clearNewFiles: () => void; + + /** Current todo list from SetTodoList tool */ + todoItems: TodoItem[]; + setTodoItems: (items: TodoItem[]) => void; + clearTodoItems: () => void; }; export const useToolEventsStore = create((set) => ({ @@ -17,6 +27,10 @@ export const useToolEventsStore = create((set) => ({ newFiles: [...state.newFiles, path], })), clearNewFiles: () => set({ newFiles: [] }), + + todoItems: [], + setTodoItems: (items) => set({ todoItems: items }), + clearTodoItems: () => set({ todoItems: [] }), })); /** diff --git a/web/src/hooks/useSessionStream.ts b/web/src/hooks/useSessionStream.ts index b34e7f6fe..d016c68ed 100644 --- a/web/src/hooks/useSessionStream.ts +++ b/web/src/hooks/useSessionStream.ts @@ -134,7 +134,7 @@ import { } from "./wireTypes"; import { createMessageId, getApiBaseUrl } from "./utils"; import { kimiCliVersion } from "@/lib/version"; -import { handleToolResult } from "@/features/tool/store"; +import { handleToolResult, useToolEventsStore, type TodoItem } from "@/features/tool/store"; import { v4 as uuidV4 } from "uuid"; // Regex patterns moved to top level for performance @@ -1291,6 +1291,18 @@ export function useSessionStream( isReplay, ); } + + // Extract todo list from display blocks + if (!isReplay && Array.isArray(return_value.display)) { + const todoBlock = return_value.display.find( + (d: { type: string }) => d.type === "todo", + ); + if (todoBlock) { + useToolEventsStore.getState().setTodoItems( + (todoBlock as unknown as { type: string; items: TodoItem[] }).items, + ); + } + } break; } @@ -2558,6 +2570,7 @@ export function useSessionStream( // Reset state for new session resetState(); setMessages([]); + useToolEventsStore.getState().clearTodoItems(); // Auto-connect if we have a valid sessionId if (sessionId) {