mirror of
https://github.com/exo-explore/exo.git
synced 2026-02-06 20:21:39 -05:00
Compare commits
2 Commits
ciaran/exp
...
ciaran/mes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31c021aad8 | ||
|
|
9394462e5f |
@@ -13,7 +13,6 @@
|
||||
import type { MessageAttachment } from "$lib/stores/app.svelte";
|
||||
import MarkdownContent from "./MarkdownContent.svelte";
|
||||
import TokenHeatmap from "./TokenHeatmap.svelte";
|
||||
import ImageLightbox from "./ImageLightbox.svelte";
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
@@ -102,9 +101,6 @@
|
||||
let copiedMessageId = $state<string | null>(null);
|
||||
let expandedThinkingMessageIds = $state<Set<string>>(new Set());
|
||||
|
||||
// Lightbox state
|
||||
let expandedImageSrc = $state<string | null>(null);
|
||||
|
||||
// Uncertainty heatmap toggle
|
||||
let heatmapMessageIds = $state<Set<string>>(new Set());
|
||||
|
||||
@@ -225,6 +221,7 @@
|
||||
}
|
||||
|
||||
function handleDeleteClick(messageId: string) {
|
||||
if (loading) return;
|
||||
deleteConfirmId = messageId;
|
||||
}
|
||||
|
||||
@@ -255,7 +252,7 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 sm:gap-6 {className}">
|
||||
{#each messageList as message (message.id)}
|
||||
{#each messageList as message, i (message.id)}
|
||||
<div
|
||||
class="group flex {message.role === 'user'
|
||||
? 'justify-end'
|
||||
@@ -317,9 +314,11 @@
|
||||
<!-- Delete confirmation -->
|
||||
<div class="bg-red-500/10 border border-red-500/30 rounded-lg p-3">
|
||||
<p class="text-xs text-red-400 mb-3">
|
||||
Delete this message{message.role === "user"
|
||||
? " and all responses after it"
|
||||
: ""}?
|
||||
{#if i === messageList.length - 1}
|
||||
Delete this message?
|
||||
{:else}
|
||||
Delete this message and all messages after it?
|
||||
{/if}
|
||||
</p>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<button
|
||||
@@ -393,15 +392,10 @@
|
||||
class="flex items-center gap-2 bg-exo-dark-gray/60 border border-exo-yellow/20 rounded px-2 py-1 text-xs font-mono"
|
||||
>
|
||||
{#if attachment.type === "image" && attachment.preview}
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_click_events_have_key_events -->
|
||||
<img
|
||||
src={attachment.preview}
|
||||
alt={attachment.name}
|
||||
class="w-12 h-12 object-cover rounded border border-exo-yellow/20 cursor-pointer hover:border-exo-yellow/50 transition-colors"
|
||||
onclick={() => {
|
||||
if (attachment.preview)
|
||||
expandedImageSrc = attachment.preview;
|
||||
}}
|
||||
class="w-12 h-12 object-cover rounded border border-exo-yellow/20"
|
||||
/>
|
||||
{:else}
|
||||
<span>{getAttachmentIcon(attachment)}</span>
|
||||
@@ -475,44 +469,15 @@
|
||||
<div class="mb-3">
|
||||
{#each message.attachments.filter((a) => a.type === "generated-image") as attachment}
|
||||
<div class="relative group/img inline-block">
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_click_events_have_key_events -->
|
||||
<img
|
||||
src={attachment.preview}
|
||||
alt=""
|
||||
class="max-w-full max-h-[512px] rounded-lg border border-exo-yellow/20 shadow-lg shadow-black/20 cursor-pointer"
|
||||
onclick={() => {
|
||||
if (attachment.preview)
|
||||
expandedImageSrc = attachment.preview;
|
||||
}}
|
||||
class="max-w-full max-h-[512px] rounded-lg border border-exo-yellow/20 shadow-lg shadow-black/20"
|
||||
/>
|
||||
<!-- Button overlay -->
|
||||
<div
|
||||
class="absolute top-2 right-2 flex gap-1 opacity-0 group-hover/img:opacity-100 transition-opacity"
|
||||
>
|
||||
<!-- Expand button -->
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 rounded-lg bg-exo-dark-gray/80 border border-exo-yellow/30 text-exo-yellow hover:bg-exo-dark-gray hover:border-exo-yellow/50 cursor-pointer"
|
||||
onclick={() => {
|
||||
if (attachment.preview)
|
||||
expandedImageSrc = attachment.preview;
|
||||
}}
|
||||
title="Expand image"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Edit button -->
|
||||
<button
|
||||
type="button"
|
||||
@@ -751,8 +716,13 @@
|
||||
<!-- Delete button -->
|
||||
<button
|
||||
onclick={() => handleDeleteClick(message.id)}
|
||||
class="p-1.5 text-exo-light-gray hover:text-red-400 transition-colors rounded hover:bg-red-500/10 cursor-pointer"
|
||||
title="Delete message"
|
||||
disabled={loading}
|
||||
class="p-1.5 transition-colors rounded {loading
|
||||
? 'text-exo-light-gray/30 cursor-not-allowed'
|
||||
: 'text-exo-light-gray hover:text-red-400 hover:bg-red-500/10 cursor-pointer'}"
|
||||
title={loading
|
||||
? "Cannot delete while generating"
|
||||
: "Delete message"}
|
||||
>
|
||||
<svg
|
||||
class="w-3.5 h-3.5"
|
||||
@@ -827,8 +797,3 @@
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ImageLightbox
|
||||
src={expandedImageSrc}
|
||||
onclose={() => (expandedImageSrc = null)}
|
||||
/>
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { fade, fly } from "svelte/transition";
|
||||
import { cubicOut } from "svelte/easing";
|
||||
|
||||
interface Props {
|
||||
src: string | null;
|
||||
onclose: () => void;
|
||||
}
|
||||
|
||||
let { src, onclose }: Props = $props();
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") {
|
||||
onclose();
|
||||
}
|
||||
}
|
||||
|
||||
function extensionFromSrc(dataSrc: string): string {
|
||||
const match = dataSrc.match(/^data:image\/(\w+)/);
|
||||
if (match) return match[1] === "jpeg" ? "jpg" : match[1];
|
||||
const urlMatch = dataSrc.match(/\.(\w+)(?:\?|$)/);
|
||||
if (urlMatch) return urlMatch[1];
|
||||
return "png";
|
||||
}
|
||||
|
||||
function handleDownload(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
if (!src) return;
|
||||
const link = document.createElement("a");
|
||||
link.href = src;
|
||||
link.download = `image-${Date.now()}.${extensionFromSrc(src)}`;
|
||||
link.click();
|
||||
}
|
||||
|
||||
function handleClose(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
onclose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={src ? handleKeydown : undefined} />
|
||||
|
||||
{#if src}
|
||||
<div
|
||||
class="fixed inset-0 z-50 bg-black/90 backdrop-blur-sm flex items-center justify-center"
|
||||
transition:fade={{ duration: 200 }}
|
||||
onclick={onclose}
|
||||
role="presentation"
|
||||
onintrostart={() => (document.body.style.overflow = "hidden")}
|
||||
onoutroend={() => (document.body.style.overflow = "")}
|
||||
>
|
||||
<div class="absolute top-4 right-4 flex gap-2 z-10">
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 rounded-lg bg-exo-dark-gray/80 border border-exo-yellow/30 text-exo-yellow hover:bg-exo-dark-gray hover:border-exo-yellow/50 cursor-pointer transition-colors"
|
||||
onclick={handleDownload}
|
||||
title="Download image"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 rounded-lg bg-exo-dark-gray/80 border border-exo-yellow/30 text-exo-yellow hover:bg-exo-dark-gray hover:border-exo-yellow/50 cursor-pointer transition-colors"
|
||||
onclick={handleClose}
|
||||
title="Close"
|
||||
>
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_click_events_have_key_events -->
|
||||
<img
|
||||
{src}
|
||||
alt=""
|
||||
class="max-w-[90vw] max-h-[90vh] object-contain rounded-lg shadow-2xl"
|
||||
transition:fly={{ y: 20, duration: 300, easing: cubicOut }}
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user