dylanebert's picture
improved prompting/UX
db9635c
<script lang="ts">
import { onMount, afterUpdate } from "svelte";
import gsap from "gsap";
import type { MessageSegment } from "../../../models/chat-data";
import type { TodoListView } from "../../../models/segment-view";
import { parseTodoList } from "../../../models/segment-view";
export let segment: MessageSegment;
let todoList: TodoListView | null = null;
let containerEl: HTMLDivElement;
let progressBarEl: HTMLDivElement;
let lastCompletedCount = 0;
let taskElements: HTMLDivElement[] = [];
$: {
const content = segment.toolOutput || segment.content;
todoList = parseTodoList(content);
}
$: progressPercentage = todoList
? (todoList.completedCount / todoList.totalCount) * 100
: 0;
onMount(() => {
if (containerEl) {
gsap.from(containerEl, {
opacity: 0,
y: 5,
duration: 0.3,
ease: "power2.out",
});
gsap.from(".todo-task", {
opacity: 0,
x: -10,
duration: 0.2,
stagger: 0.05,
ease: "power2.out",
});
}
});
afterUpdate(() => {
if (progressBarEl && todoList) {
gsap.to(progressBarEl, {
width: `${progressPercentage}%`,
duration: 0.6,
ease: "power2.out",
});
if (todoList.completedCount > lastCompletedCount) {
if (progressPercentage === 100) {
gsap.to(containerEl, {
borderColor: "rgba(76, 175, 80, 0.5)",
duration: 0.3,
yoyo: true,
repeat: 2,
ease: "power2.inOut",
});
} else if (progressPercentage === 50 || progressPercentage === 75) {
gsap.to(progressBarEl, {
backgroundColor: "rgba(76, 175, 80, 0.6)",
duration: 0.3,
yoyo: true,
repeat: 1,
ease: "power2.inOut",
});
}
lastCompletedCount = todoList.completedCount;
}
}
});
</script>
{#if todoList}
<div class="todo-segment" bind:this={containerEl}>
<div class="todo-header">
<span class="todo-title">Tasks</span>
<span class="todo-progress">
{todoList.completedCount}/{todoList.totalCount}
</span>
</div>
<div class="progress-track">
<div class="progress-bar" bind:this={progressBarEl} style="width: {progressPercentage}%"></div>
</div>
<div class="todo-list">
{#each todoList.tasks as task, i}
<div
class="todo-task {task.status}"
bind:this={taskElements[i]}
>
<span class="task-indicator">
{#if task.status === 'completed'}
<span class="checkmark">✓</span>
{:else if task.status === 'in_progress'}
<span class="progress-dot">●</span>
{:else}
<span class="pending-circle">○</span>
{/if}
</span>
<span class="task-text">{task.description}</span>
</div>
{/each}
</div>
</div>
{:else}
<pre class="raw-output">{segment.toolOutput || segment.content}</pre>
{/if}
<style>
.todo-segment {
background: rgba(255, 255, 255, 0.01);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 4px;
overflow: hidden;
margin: 0.125rem 0;
}
.todo-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
background: rgba(255, 255, 255, 0.02);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.todo-title {
font-size: 0.8rem;
font-weight: 500;
color: rgba(255, 255, 255, 0.7);
}
.todo-progress {
font-size: 0.7rem;
color: rgba(255, 255, 255, 0.4);
background: rgba(255, 255, 255, 0.05);
padding: 0.125rem 0.375rem;
border-radius: 10px;
}
.progress-track {
height: 3px;
background: rgba(255, 255, 255, 0.05);
position: relative;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg,
rgba(76, 175, 80, 0.3),
rgba(76, 175, 80, 0.5));
transition: width 0.6s ease-out;
}
.todo-list {
padding: 0.375rem 0.5rem;
}
.todo-task {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.3rem 0;
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.7);
transition: all 0.2s ease;
}
.task-indicator {
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.checkmark {
color: rgba(76, 175, 80, 0.9);
font-weight: bold;
animation: checkPop 0.3s ease-out;
}
@keyframes checkPop {
0% { transform: scale(0); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.progress-dot {
color: rgba(33, 150, 243, 0.9);
animation: progressPulse 1.5s ease-in-out infinite;
}
@keyframes progressPulse {
0%, 100% { opacity: 0.4; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
.pending-circle {
color: rgba(255, 255, 255, 0.3);
font-size: 10px;
}
.task-text {
flex: 1;
}
.todo-task.completed .task-text {
text-decoration: line-through;
opacity: 0.5;
}
.todo-task.in_progress {
color: rgba(255, 255, 255, 0.9);
background: rgba(33, 150, 243, 0.05);
border-left: 2px solid rgba(33, 150, 243, 0.3);
padding-left: 0.5rem;
margin-left: -0.5rem;
}
.todo-task.in_progress .task-text {
font-weight: 500;
}
.raw-output {
margin: 0;
padding: 0.25rem;
white-space: pre-wrap;
word-wrap: break-word;
font-family: "Monaco", "Menlo", "Consolas", monospace;
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.6);
}
</style>