VibeGame / src /lib /models /segment-view.ts
dylanebert's picture
improved prompting/UX
db9635c
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(),
};
}