Thomas G. Lopes
UX adjustments (#76)
28faefd unverified
raw
history blame
5.22 kB
<script lang="ts">
import { checkpoints } from "$lib/state/checkpoints.svelte";
import { session } from "$lib/state/session.svelte.js";
import { Popover } from "melt/builders";
import { Tooltip } from "melt/components";
import { fly } from "svelte/transition";
import IconHistory from "~icons/carbon/recently-viewed";
import IconDelete from "~icons/carbon/trash-can";
import IconStar from "~icons/carbon/star";
import IconStarFilled from "~icons/carbon/star-filled";
import IconCompare from "~icons/carbon/compare";
const popover = new Popover({
floatingConfig: {
offset: { crossAxis: -12 },
},
});
const projCheckpoints = $derived(checkpoints.for(session.project.id));
</script>
<button class="btn relative size-[32px] p-0" {...popover.trigger}>
<IconHistory />
{#if projCheckpoints.length > 0}
<div class="absolute -top-1 -right-1 size-2.5 rounded-full bg-amber-500" aria-label="Project has checkpoints"></div>
{/if}
</button>
<div
class="mb-2 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800"
{...popover.content}
>
<div class="max-h-120 w-80 overflow-x-clip overflow-y-auto p-3 pb-1">
<div class="mb-2 flex items-center justify-between px-1">
<h3 class="text-sm font-medium dark:text-white">Checkpoints</h3>
<button
class="rounded-lg bg-blue-600 px-2 py-1 text-xs font-medium text-white transition-colors hover:bg-blue-700"
onclick={() => checkpoints.commit(session.project.id)}
>
Create new
</button>
</div>
{#each projCheckpoints as checkpoint (checkpoint.id)}
{@const state = checkpoint.projectState}
{@const multiple = state.conversations.length > 1}
<Tooltip
openDelay={0}
floatingConfig={{
computePosition: {
placement: "right",
},
offset: {
mainAxis: 16,
},
}}
forceVisible
>
{#snippet children(tooltip)}
<div
class="mb-2 flex w-full items-center rounded-md px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700"
{...tooltip.trigger}
>
<button
class="flex flex-1 flex-col text-left text-sm transition-colors"
onclick={() => checkpoints.restore(session.project.id, checkpoint)}
>
<span class="font-medium text-gray-400">{checkpoint.timestamp}</span>
<p class="mt-0.5 flex items-center gap-2 text-sm">
{#if multiple}
<IconCompare class="text-xs text-gray-400" />
{/if}
{#each state.conversations as { messages }, i}
<span class={["text-gray-800 dark:text-gray-200"]}>
{messages.length} message{messages.length === 1 ? "" : "s"}
</span>
{#if multiple && i === 0}
<span class="text-gray-500">|</span>
{/if}
{/each}
</p>
</button>
<button
class="mr-0.5 grid place-items-center rounded-md p-1 text-xs hover:bg-gray-300 dark:hover:bg-gray-600"
onclick={e => {
e.stopPropagation();
checkpoints.toggleFavorite(session.project.id, checkpoint);
}}
>
{#if checkpoint.favorite}
<IconStarFilled class="text-yellow-500" />
{:else}
<IconStar />
{/if}
</button>
<button
class="grid place-items-center rounded-md p-1 text-xs hover:bg-gray-300 dark:hover:bg-gray-600"
onclick={e => {
e.stopPropagation();
checkpoints.delete(session.project.id, checkpoint);
}}
>
<IconDelete />
</button>
</div>
{#if tooltip.open}
<div
class={[
"flex rounded-xl border border-gray-100 bg-gray-50 p-2 shadow dark:border-gray-700 dark:bg-gray-800",
]}
{...tooltip.content}
transition:fly={{ x: -2 }}
>
<div class="size-4 rounded-tl border-t border-l border-gray-700" {...tooltip.arrow}></div>
{#each state.conversations as conversation, i}
{@const msgs = conversation.messages}
{@const sliced = msgs.slice(0, 4)}
<div
class={[
"p-2",
multiple ? "w-52" : "w-72",
i === 0 && multiple && "border-r border-gray-200 dark:border-gray-700",
]}
>
<p class="text-2xs pl-1.5 font-mono font-medium text-gray-500 uppercase">
temp: {conversation.config.temperature}
| max tokens: {conversation.config.max_tokens}
</p>
{#each sliced as msg, i}
{@const isLast = i === sliced.length - 1}
<div class="flex flex-col gap-1 p-2">
<p class="font-mono text-xs font-medium text-gray-400 uppercase">{msg.role}</p>
{#if msg.content?.trim()}
<p class="line-clamp-2 text-sm">{msg.content.trim()}</p>
{:else}
<p class="text-sm text-gray-500 italic">No content</p>
{/if}
</div>
{#if !isLast}
<div class="my-2 h-px w-full bg-gray-200 dark:bg-gray-700"></div>
{/if}
{/each}
</div>
{/each}
</div>
{/if}
{/snippet}
</Tooltip>
{:else}
<div class="flex flex-col items-center gap-2 py-3">
<span class="text-gray-500 text-sm">No checkpoints available</span>
</div>
{/each}
</div>
</div>