Spaces:
Runtime error
Runtime error
File size: 6,096 Bytes
7450ebd fbef5e8 7450ebd 1778c9e f36471e 1778c9e 7450ebd f36471e 7450ebd f36471e 893b0be 7450ebd f36471e 7450ebd f36471e 7450ebd 1778c9e 7450ebd 893b0be 7450ebd f36471e fbef5e8 7450ebd 893b0be 7450ebd f36471e 7450ebd 1778c9e 7450ebd 1778c9e 7450ebd 1778c9e 7450ebd 893b0be 7450ebd 1778c9e 7450ebd 1778c9e 7450ebd 1778c9e 28faefd f9963a5 7450ebd 1778c9e 7450ebd 1778c9e 7450ebd 28faefd 7450ebd f36471e 1778c9e f9963a5 7450ebd d7cd63b 7450ebd f36471e 7450ebd 28faefd 7450ebd f36471e |
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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
<script lang="ts">
import { clickOutside } from "$lib/attachments/click-outside.js";
import { checkpoints } from "$lib/state/checkpoints.svelte";
import { projects } from "$lib/state/projects.svelte";
import { iterate } from "$lib/utils/array.js";
import { formatDateTime } from "$lib/utils/date.js";
import { Popover } from "melt/builders";
import { Tooltip } from "melt/components";
import { fly } from "svelte/transition";
import IconCompare from "~icons/carbon/compare";
import IconHistory from "~icons/carbon/recently-viewed";
import IconStar from "~icons/carbon/star";
import IconStarFilled from "~icons/carbon/star-filled";
import IconDelete from "~icons/carbon/trash-can";
import { TEST_IDS } from "$lib/constants.js";
const popover = new Popover({
floatingConfig: {
offset: { crossAxis: -12 },
},
onOpenChange: open => {
if (open) dialog?.showModal();
else dialog?.close();
},
});
let dialog = $state<HTMLDialogElement>();
const projCheckpoints = $derived(checkpoints.for(projects.activeId));
</script>
<button class="btn relative size-[32px] p-0" {...popover.trigger} data-test-id={TEST_IDS.checkpoints_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>
<dialog
bind:this={dialog}
class="mb-2 !overflow-visible rounded-xl border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800"
{@attach clickOutside(() => (popover.open = false))}
{...popover.content}
data-test-id={TEST_IDS.checkpoints_menu}
>
<div
class="size-4 translate-x-3 rounded-tl border-t border-l border-gray-200 dark:border-gray-700"
{...popover.arrow}
></div>
<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(projects.activeId)}
>
Create new
</button>
</div>
{#each projCheckpoints as checkpoint (checkpoint.id)}
{@const conversations = checkpoint.conversations}
{@const multiple = 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 hover:bg-gray-100 dark:hover:bg-gray-700"
{...tooltip.trigger}
data-test-id={TEST_IDS.checkpoint}
>
<button
class="flex flex-1 flex-col py-2 text-left text-sm transition-colors"
onclick={e => {
e.stopPropagation();
checkpoints.restore(checkpoint);
}}
>
<span class="font-medium text-gray-400">{formatDateTime(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 conversations as { messages }, i}
<span class={["text-gray-800 dark:text-gray-200"]}>
{messages?.length || 0} message{(messages?.length || 0) === 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(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(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-200 dark:border-gray-700"
{...tooltip.arrow}
></div>
{#each 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}
{#if conversation.config.max_tokens}
| max tokens: {conversation.config.max_tokens}
{/if}
{#if conversation.structuredOutput?.enabled}
| structured output
{/if}
</p>
{#each iterate(sliced) as [msg, isLast]}
<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>
</dialog>
|