Spaces:
Runtime error
Runtime error
File size: 5,218 Bytes
7450ebd 28faefd 7450ebd 28faefd 7450ebd 28faefd 7450ebd 28faefd 7450ebd |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
<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>
|