Spaces:
Running
Running
| import type { MessageSegment } from "./chat-data"; | |
| export interface SegmentView { | |
| readonly segment: MessageSegment; | |
| readonly isExpanded: boolean; | |
| readonly displayTitle: string; | |
| readonly displayStatus: string; | |
| readonly statusColor: string; | |
| readonly canExpand: boolean; | |
| readonly formattedContent?: string; | |
| readonly metadata?: SegmentMetadata; | |
| } | |
| export interface SegmentMetadata { | |
| readonly toolType?: "todo" | "console" | "file" | "default"; | |
| readonly fileName?: string; | |
| readonly lineCount?: number; | |
| readonly errorMessage?: string; | |
| readonly duration?: number; | |
| } | |
| export interface TodoListView { | |
| readonly tasks: TodoTask[]; | |
| readonly completedCount: number; | |
| readonly totalCount: number; | |
| readonly lastUpdated: number; | |
| } | |
| export interface TodoTask { | |
| readonly id: number; | |
| readonly description: string; | |
| readonly status: "pending" | "in_progress" | "completed"; | |
| readonly emoji: string; | |
| } | |
| export function createSegmentView( | |
| segment: MessageSegment, | |
| isExpanded: boolean = false, | |
| ): SegmentView { | |
| const statusColors: Record<string, string> = { | |
| pending: "rgba(255, 193, 7, 0.8)", | |
| running: "rgba(33, 150, 243, 0.8)", | |
| completed: "rgba(76, 175, 80, 0.8)", | |
| error: "rgba(244, 67, 54, 0.8)", | |
| }; | |
| const displayTitle = getSegmentTitle(segment); | |
| const displayStatus = getSegmentStatus(segment); | |
| const statusColor = | |
| statusColors[segment.toolStatus || "pending"] || "rgba(156, 163, 175, 0.8)"; | |
| const canExpand = | |
| segment.type === "tool-invocation" || segment.type === "tool-result"; | |
| return { | |
| segment, | |
| isExpanded, | |
| displayTitle, | |
| displayStatus, | |
| statusColor, | |
| canExpand, | |
| metadata: extractMetadata(segment), | |
| }; | |
| } | |
| function getSegmentTitle(segment: MessageSegment): string { | |
| if (segment.type === "text") { | |
| return "Text"; | |
| } | |
| if (segment.type === "reasoning") { | |
| return "Reasoning"; | |
| } | |
| if (segment.toolName) { | |
| const toolNames: Record<string, string> = { | |
| plan_tasks: "π Plan Tasks", | |
| update_task: "βοΈ Update Task", | |
| view_tasks: "π View Tasks", | |
| observe_console: "πΊ Console Output", | |
| }; | |
| return toolNames[segment.toolName] || `π§ ${segment.toolName}`; | |
| } | |
| return "Tool"; | |
| } | |
| function getSegmentStatus(segment: MessageSegment): string { | |
| if (segment.streaming) { | |
| return "streaming..."; | |
| } | |
| if (segment.toolStatus) { | |
| const statusLabels: Record<string, string> = { | |
| pending: "Pending", | |
| running: "Running", | |
| completed: "Completed", | |
| error: "Error", | |
| }; | |
| return statusLabels[segment.toolStatus] || segment.toolStatus; | |
| } | |
| if (segment.endTime && segment.startTime) { | |
| const duration = segment.endTime - segment.startTime; | |
| if (duration < 1000) { | |
| return `${duration}ms`; | |
| } | |
| return `${(duration / 1000).toFixed(1)}s`; | |
| } | |
| return ""; | |
| } | |
| function extractMetadata(segment: MessageSegment): SegmentMetadata { | |
| let toolType: SegmentMetadata["toolType"]; | |
| let fileName: string | undefined; | |
| let errorMessage: string | undefined; | |
| let duration: number | undefined; | |
| let lineCount: number | undefined; | |
| if (segment.toolName?.includes("task")) { | |
| toolType = "todo"; | |
| } else if (segment.toolName === "observe_console") { | |
| toolType = "console"; | |
| } else if (segment.toolName?.includes("file")) { | |
| toolType = "file"; | |
| if (segment.toolArgs?.path) { | |
| fileName = segment.toolArgs.path as string; | |
| } | |
| } else if (segment.toolName) { | |
| toolType = "default"; | |
| } | |
| if (segment.toolError) { | |
| errorMessage = segment.toolError; | |
| } | |
| if (segment.endTime && segment.startTime) { | |
| duration = segment.endTime - segment.startTime; | |
| } | |
| if (segment.toolOutput) { | |
| lineCount = segment.toolOutput.split("\n").length; | |
| } | |
| return { | |
| toolType, | |
| fileName, | |
| errorMessage, | |
| duration, | |
| lineCount, | |
| }; | |
| } | |
| export function parseTodoList(content: string): TodoListView | null { | |
| const lines = content.split("\n"); | |
| const tasks: TodoTask[] = []; | |
| for (const line of lines) { | |
| const match = line.match( | |
| /([β³πβ ])\s*\[(\d+)\]\s*(.+?)\s*\((pending|in_progress|completed)\)/u, | |
| ); | |
| if (match) { | |
| const [, emoji, id, description, status] = match; | |
| tasks.push({ | |
| id: parseInt(id, 10), | |
| description: description.trim(), | |
| status: status as TodoTask["status"], | |
| emoji, | |
| }); | |
| } | |
| } | |
| if (tasks.length === 0) { | |
| return null; | |
| } | |
| const completedCount = tasks.filter((t) => t.status === "completed").length; | |
| return { | |
| tasks, | |
| completedCount, | |
| totalCount: tasks.length, | |
| lastUpdated: Date.now(), | |
| }; | |
| } | |