743 lines
24 KiB
Svelte
743 lines
24 KiB
Svelte
<script lang="ts">
|
|
import {
|
|
SettingsIcon,
|
|
Trash2Icon,
|
|
XIcon,
|
|
LanguagesIcon,
|
|
StarIcon,
|
|
RefreshCw,
|
|
ScissorsLineDashed,
|
|
CheckIcon,
|
|
} from "lucide-svelte";
|
|
import { tick } from "svelte";
|
|
import TextAreaInput from "../../lib/UI/GUI/TextAreaInput.svelte";
|
|
import { alertConfirm, showHypaV3Alert } from "../../ts/alert";
|
|
import {
|
|
DBState,
|
|
alertStore,
|
|
selectedCharID,
|
|
settingsOpen,
|
|
SettingsMenuIndex,
|
|
} from "../../ts/stores.svelte";
|
|
import { summarize } from "../../ts/process/memory/hypav3";
|
|
import { type OpenAIChat } from "../../ts/process/index.svelte";
|
|
import { type Message } from "../../ts/storage/database.svelte";
|
|
import { translateHTML } from "../../ts/translator/translator";
|
|
|
|
interface SummaryUI {
|
|
isTranslating: boolean;
|
|
translation: string | null;
|
|
translationRef: HTMLDivElement;
|
|
isRerolling: boolean;
|
|
rerolledText: string | null;
|
|
isRerolledTranslating: boolean;
|
|
rerolledTranslation: string | null;
|
|
rerolledTranslationRef: HTMLDivElement;
|
|
}
|
|
|
|
interface ExpandedMessageUI {
|
|
summaryIndex: number;
|
|
selectedChatMemo: string;
|
|
isTranslating: boolean;
|
|
translation: string | null;
|
|
translationRef: HTMLDivElement;
|
|
}
|
|
|
|
const hypaV3DataState = $derived(
|
|
DBState.db.characters[$selectedCharID].chats[
|
|
DBState.db.characters[$selectedCharID].chatPage
|
|
].hypaV3Data
|
|
);
|
|
|
|
let summaryUIStates = $state<SummaryUI[]>([]);
|
|
let expandedMessageUIState = $state<ExpandedMessageUI | null>(null);
|
|
|
|
$effect.pre(() => {
|
|
summaryUIStates = hypaV3DataState.summaries.map(() => ({
|
|
isTranslating: false,
|
|
translation: null,
|
|
translationRef: null,
|
|
isRerolling: false,
|
|
rerolledText: null,
|
|
isRerolledTranslating: false,
|
|
rerolledTranslation: null,
|
|
rerolledTranslationRef: null,
|
|
}));
|
|
|
|
expandedMessageUIState = null;
|
|
});
|
|
|
|
async function alertConfirmTwice(
|
|
firstMessage: string,
|
|
secondMessage: string
|
|
): Promise<boolean> {
|
|
return (
|
|
(await alertConfirm(firstMessage)) && (await alertConfirm(secondMessage))
|
|
);
|
|
}
|
|
|
|
function getFirstMessage(): string | null {
|
|
const char = DBState.db.characters[$selectedCharID];
|
|
const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage];
|
|
|
|
return chat.fmIndex === -1
|
|
? char.firstMessage
|
|
: char.alternateGreetings?.[chat.fmIndex]
|
|
? char.alternateGreetings[chat.fmIndex]
|
|
: null;
|
|
}
|
|
|
|
function getMessageFromChatMemo(chatMemo: string | null): Message | null {
|
|
const char = DBState.db.characters[$selectedCharID];
|
|
const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage];
|
|
const firstMessage = getFirstMessage();
|
|
|
|
if (!firstMessage) {
|
|
return null;
|
|
}
|
|
|
|
if (chatMemo == null) {
|
|
return { role: "char", data: firstMessage };
|
|
}
|
|
|
|
return chat.message.find((m) => m.chatId === chatMemo) || null;
|
|
}
|
|
|
|
async function toggleTranslate(
|
|
summaryIndex: number,
|
|
regenerate?: boolean
|
|
): Promise<void> {
|
|
const summaryUIState = summaryUIStates[summaryIndex];
|
|
|
|
if (summaryUIState.isTranslating) return;
|
|
|
|
if (summaryUIState.translation) {
|
|
summaryUIState.translation = null;
|
|
return;
|
|
}
|
|
|
|
summaryUIState.isTranslating = true;
|
|
summaryUIState.translation = "Loading...";
|
|
|
|
// Focus on translation element after it's rendered
|
|
await tick();
|
|
|
|
if (summaryUIState.translationRef) {
|
|
summaryUIState.translationRef.focus();
|
|
summaryUIState.translationRef.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "nearest",
|
|
});
|
|
}
|
|
|
|
// Translate
|
|
const result = await translate(
|
|
hypaV3DataState.summaries[summaryIndex].text,
|
|
regenerate
|
|
);
|
|
|
|
summaryUIState.translation = result;
|
|
summaryUIState.isTranslating = false;
|
|
}
|
|
|
|
function isRerollable(summaryIndex: number): boolean {
|
|
const summary = hypaV3DataState.summaries[summaryIndex];
|
|
|
|
for (const chatMemo of summary.chatMemos) {
|
|
if (!getMessageFromChatMemo(chatMemo)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async function toggleReroll(summaryIndex: number): Promise<void> {
|
|
const summaryUIState = summaryUIStates[summaryIndex];
|
|
|
|
if (summaryUIState.isRerolling) return;
|
|
if (!isRerollable(summaryIndex)) return;
|
|
|
|
summaryUIState.isRerolling = true;
|
|
summaryUIState.rerolledText = "Loading...";
|
|
|
|
try {
|
|
const summary = hypaV3DataState.summaries[summaryIndex];
|
|
const toSummarize: OpenAIChat[] = summary.chatMemos.map((chatMemo) => {
|
|
const message = getMessageFromChatMemo(chatMemo);
|
|
|
|
return {
|
|
role: (message.role === "char"
|
|
? "assistant"
|
|
: message.role) as OpenAIChat["role"],
|
|
content: message.data,
|
|
};
|
|
});
|
|
|
|
const summarizeResult = await summarize(toSummarize);
|
|
|
|
if (summarizeResult.success) {
|
|
summaryUIState.rerolledText = summarizeResult.data;
|
|
}
|
|
} catch (error) {
|
|
summaryUIState.rerolledText = "Reroll failed";
|
|
} finally {
|
|
summaryUIState.isRerolling = false;
|
|
}
|
|
}
|
|
|
|
async function toggleTranslateRerolled(
|
|
summaryIndex: number,
|
|
regenerate?: boolean
|
|
): Promise<void> {
|
|
const summaryUIState = summaryUIStates[summaryIndex];
|
|
|
|
if (summaryUIState.isRerolledTranslating) return;
|
|
|
|
if (summaryUIState.rerolledTranslation) {
|
|
summaryUIState.rerolledTranslation = null;
|
|
return;
|
|
}
|
|
|
|
if (!summaryUIState.rerolledText) return;
|
|
|
|
summaryUIState.isRerolledTranslating = true;
|
|
summaryUIState.rerolledTranslation = "Loading...";
|
|
|
|
// Focus on rerolled translation element after it's rendered
|
|
await tick();
|
|
|
|
if (summaryUIState.rerolledTranslationRef) {
|
|
summaryUIState.rerolledTranslationRef.focus();
|
|
summaryUIState.rerolledTranslationRef.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "nearest",
|
|
});
|
|
}
|
|
|
|
// Translate
|
|
const result = await translate(summaryUIState.rerolledText, regenerate);
|
|
|
|
summaryUIState.rerolledTranslation = result;
|
|
summaryUIState.isRerolledTranslating = false;
|
|
}
|
|
|
|
function isMessageExpanded(
|
|
summaryIndex: number,
|
|
chatMemo: string | null
|
|
): boolean {
|
|
if (!expandedMessageUIState) return false;
|
|
|
|
const summary = hypaV3DataState.summaries[summaryIndex];
|
|
|
|
return (
|
|
expandedMessageUIState.summaryIndex === summaryIndex &&
|
|
expandedMessageUIState.selectedChatMemo === chatMemo
|
|
);
|
|
}
|
|
|
|
function toggleExpandMessage(
|
|
summaryIndex: number,
|
|
chatMemo: string | null
|
|
): void {
|
|
expandedMessageUIState = isMessageExpanded(summaryIndex, chatMemo)
|
|
? null
|
|
: {
|
|
summaryIndex,
|
|
selectedChatMemo: chatMemo,
|
|
isTranslating: false,
|
|
translation: null,
|
|
translationRef: null,
|
|
};
|
|
}
|
|
|
|
async function toggleTranslateExpandedMessage(
|
|
regenerate?: boolean
|
|
): Promise<void> {
|
|
if (!expandedMessageUIState || expandedMessageUIState.isTranslating) return;
|
|
|
|
if (expandedMessageUIState.translation) {
|
|
expandedMessageUIState.translation = null;
|
|
return;
|
|
}
|
|
|
|
const message = getMessageFromChatMemo(
|
|
expandedMessageUIState.selectedChatMemo
|
|
);
|
|
|
|
if (!message) return;
|
|
|
|
expandedMessageUIState.isTranslating = true;
|
|
expandedMessageUIState.translation = "Loading...";
|
|
|
|
// Focus on translation element after it's rendered
|
|
await tick();
|
|
|
|
if (expandedMessageUIState.translationRef) {
|
|
expandedMessageUIState.translationRef.focus();
|
|
expandedMessageUIState.translationRef.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "nearest",
|
|
});
|
|
}
|
|
|
|
// Translate
|
|
const result = await translate(message.data, regenerate);
|
|
|
|
expandedMessageUIState.translation = result;
|
|
expandedMessageUIState.isTranslating = false;
|
|
}
|
|
|
|
function getNextMessageToSummarize(): Message | null {
|
|
const char = DBState.db.characters[$selectedCharID];
|
|
const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage];
|
|
const firstMessage = getFirstMessage();
|
|
|
|
if (!firstMessage) {
|
|
return null;
|
|
}
|
|
|
|
// Summaries exist
|
|
if (hypaV3DataState.summaries.length > 0) {
|
|
const lastSummary = hypaV3DataState.summaries.at(-1);
|
|
const lastMessageIndex = chat.message.findIndex(
|
|
(msg) => msg.chatId === lastSummary.chatMemos.at(-1)
|
|
);
|
|
|
|
if (lastMessageIndex !== -1) {
|
|
const nextMessage = chat.message[lastMessageIndex + 1];
|
|
|
|
if (nextMessage) {
|
|
return nextMessage;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No summaries
|
|
if (firstMessage?.trim() === "") {
|
|
if (chat.message.length > 0) {
|
|
return chat.message[0];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// will summarize first message
|
|
return { role: "char", chatId: "first message", data: firstMessage };
|
|
}
|
|
|
|
async function translate(
|
|
text: string,
|
|
regenerate?: boolean
|
|
): Promise<string> {
|
|
try {
|
|
return await translateHTML(text, false, "", -1, regenerate);
|
|
} catch (error) {
|
|
return `Translation failed: ${error}`;
|
|
}
|
|
}
|
|
|
|
type DualActionParams = {
|
|
onMainAction?: () => void;
|
|
onAlternativeAction?: () => void;
|
|
};
|
|
|
|
function handleDualAction(node: HTMLElement, params: DualActionParams = {}) {
|
|
const DOUBLE_TAP_DELAY = 300;
|
|
|
|
const state = {
|
|
lastTap: 0,
|
|
tapTimeout: null,
|
|
};
|
|
|
|
const handleTouch = (event: TouchEvent) => {
|
|
const currentTime = new Date().getTime();
|
|
const tapLength = currentTime - state.lastTap;
|
|
|
|
if (tapLength < DOUBLE_TAP_DELAY && tapLength > 0) {
|
|
// Double tap detected
|
|
event.preventDefault();
|
|
clearTimeout(state.tapTimeout); // Cancel the first tap timeout
|
|
params.onAlternativeAction?.();
|
|
state.lastTap = 0; // Reset state
|
|
} else {
|
|
state.lastTap = currentTime; // First tap
|
|
// Delayed single tap execution
|
|
state.tapTimeout = setTimeout(() => {
|
|
if (state.lastTap === currentTime) {
|
|
// If no double tap occurred
|
|
params.onMainAction?.();
|
|
}
|
|
}, DOUBLE_TAP_DELAY);
|
|
}
|
|
};
|
|
|
|
const handleClick = (event: MouseEvent) => {
|
|
if (event.shiftKey) {
|
|
params.onAlternativeAction?.();
|
|
} else {
|
|
params.onMainAction?.();
|
|
}
|
|
};
|
|
|
|
if ("ontouchend" in window) {
|
|
// Mobile environment
|
|
node.addEventListener("touchend", handleTouch);
|
|
} else {
|
|
// Desktop environment
|
|
node.addEventListener("click", handleClick);
|
|
}
|
|
|
|
return {
|
|
destroy() {
|
|
if ("ontouchend" in window) {
|
|
node.removeEventListener("touchend", handleTouch);
|
|
} else {
|
|
node.removeEventListener("click", handleClick);
|
|
}
|
|
|
|
clearTimeout(state.tapTimeout); // Cleanup timeout
|
|
},
|
|
update(newParams: DualActionParams) {
|
|
params = newParams;
|
|
},
|
|
};
|
|
}
|
|
</script>
|
|
|
|
<!-- Modal backdrop -->
|
|
<div class="fixed inset-0 z-50 bg-black/50 p-4">
|
|
<!-- Modal wrapper -->
|
|
<div class="h-full w-full flex justify-center">
|
|
<!-- Modal window -->
|
|
<div
|
|
class="bg-zinc-900 p-6 rounded-lg flex flex-col w-full max-w-3xl {hypaV3DataState
|
|
.summaries.length === 0
|
|
? 'h-fit'
|
|
: 'max-h-full'}"
|
|
>
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center w-full mb-4">
|
|
<h1 class="text-2xl font-semibold text-zinc-100">HypaV3 Data</h1>
|
|
<!-- Button Container -->
|
|
<div class="flex items-center gap-2">
|
|
<!-- Settings Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
|
onclick={() => {
|
|
alertStore.set({
|
|
type: "none",
|
|
msg: "",
|
|
});
|
|
|
|
settingsOpen.set(true);
|
|
SettingsMenuIndex.set(2); // Other bot settings
|
|
}}
|
|
>
|
|
<SettingsIcon size={24} />
|
|
</button>
|
|
|
|
<!-- Reset Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 hover:text-rose-300 transition-colors"
|
|
onclick={async () => {
|
|
if (
|
|
await alertConfirmTwice(
|
|
"This action cannot be undone. Do you want to reset HypaV3 data?",
|
|
"This action is irreversible. Do you really, really want to reset HypaV3 data?"
|
|
)
|
|
) {
|
|
DBState.db.characters[$selectedCharID].chats[
|
|
DBState.db.characters[$selectedCharID].chatPage
|
|
].hypaV3Data = {
|
|
summaries: [],
|
|
lastSelectedSummaries: [],
|
|
};
|
|
} else {
|
|
showHypaV3Alert();
|
|
}
|
|
}}
|
|
>
|
|
<Trash2Icon size={24} />
|
|
</button>
|
|
|
|
<!-- Close Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 hover:text-rose-300 transition-colors"
|
|
onclick={() => {
|
|
alertStore.set({
|
|
type: "none",
|
|
msg: "",
|
|
});
|
|
}}
|
|
>
|
|
<XIcon size={24} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scrollable Container -->
|
|
<div class="flex flex-col gap-3 w-full overflow-y-auto">
|
|
{#if hypaV3DataState.summaries.length === 0}
|
|
<span class="text-textcolor2 text-center p-4">No summaries yet</span>
|
|
{/if}
|
|
|
|
<!-- Summaries List -->
|
|
{#each hypaV3DataState.summaries as summary, i}
|
|
{#if summaryUIStates[i]}
|
|
<!-- Summary Item -->
|
|
<div
|
|
class="flex flex-col p-4 rounded-lg border border-zinc-700 bg-zinc-800/50"
|
|
>
|
|
<!-- Summary Header -->
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-sm text-textcolor2">Summary #{i + 1}</span>
|
|
<div class="flex items-center gap-4">
|
|
<!-- Translate Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
|
use:handleDualAction={{
|
|
onMainAction: () => toggleTranslate(i, false),
|
|
onAlternativeAction: () => toggleTranslate(i, true),
|
|
}}
|
|
>
|
|
<LanguagesIcon size={16} />
|
|
</button>
|
|
|
|
<!-- Important Button -->
|
|
<button
|
|
class="p-2 hover:text-zinc-200 hover:text-amber-300 transition-colors {summary.isImportant
|
|
? 'text-yellow-500'
|
|
: 'text-zinc-400'}"
|
|
onclick={() => {
|
|
summary.isImportant = !summary.isImportant;
|
|
}}
|
|
>
|
|
<StarIcon size={16} />
|
|
</button>
|
|
|
|
<!-- Reroll Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
|
onclick={async () => await toggleReroll(i)}
|
|
disabled={!isRerollable(i)}
|
|
>
|
|
<RefreshCw size={16} />
|
|
</button>
|
|
|
|
<!-- Delete After Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 hover:text-rose-300 transition-colors"
|
|
onclick={async () => {
|
|
if (
|
|
await alertConfirmTwice(
|
|
"Delete all summaries after this one?",
|
|
"This action cannot be undone. Are you really sure?"
|
|
)
|
|
) {
|
|
hypaV3DataState.summaries.splice(i + 1);
|
|
}
|
|
|
|
showHypaV3Alert();
|
|
}}
|
|
>
|
|
<ScissorsLineDashed size={16} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Original Summary -->
|
|
<TextAreaInput
|
|
bind:value={summary.text}
|
|
className="w-full bg-zinc-900 text-zinc-200 rounded-md p-3 min-h-[100px] resize-y"
|
|
/>
|
|
|
|
<!-- Translation (if exists) -->
|
|
{#if summaryUIStates[i].translation}
|
|
<div class="mt-4">
|
|
<span class="text-sm text-textcolor2 mb-2 block"
|
|
>Translation</span
|
|
>
|
|
<div
|
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
|
bind:this={summaryUIStates[i].translationRef}
|
|
tabindex="-1"
|
|
>
|
|
{summaryUIStates[i].translation}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Rerolled Summary (if exists) -->
|
|
{#if summaryUIStates[i].rerolledText}
|
|
<div class="mt-4">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-sm text-textcolor2">Rerolled Summary</span
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<!-- Translate Rerolled Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
|
use:handleDualAction={{
|
|
onMainAction: () => toggleTranslateRerolled(i, false),
|
|
onAlternativeAction: () =>
|
|
toggleTranslateRerolled(i, true),
|
|
}}
|
|
>
|
|
<LanguagesIcon size={16} />
|
|
</button>
|
|
|
|
<!-- Cancel Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 hover:text-rose-300 transition-colors"
|
|
onclick={() => {
|
|
summaryUIStates[i].rerolledText = null;
|
|
summaryUIStates[i].rerolledTranslation = null;
|
|
}}
|
|
>
|
|
<XIcon size={16} />
|
|
</button>
|
|
|
|
<!-- Apply Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
|
onclick={() => {
|
|
summary.text = summaryUIStates[i].rerolledText!;
|
|
summaryUIStates[i].translation = null;
|
|
summaryUIStates[i].rerolledText = null;
|
|
summaryUIStates[i].rerolledTranslation = null;
|
|
}}
|
|
>
|
|
<CheckIcon size={16} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<TextAreaInput
|
|
bind:value={summaryUIStates[i].rerolledText}
|
|
className="w-full bg-zinc-900 text-zinc-200 rounded-md p-3 min-h-[100px] resize-y"
|
|
/>
|
|
|
|
<!-- Rerolled Translation (if exists) -->
|
|
{#if summaryUIStates[i].rerolledTranslation}
|
|
<div class="mt-4">
|
|
<span class="text-sm text-textcolor2 mb-2 block"
|
|
>Rerolled Translation</span
|
|
>
|
|
<div
|
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
|
bind:this={summaryUIStates[i].rerolledTranslationRef}
|
|
tabindex="-1"
|
|
>
|
|
{summaryUIStates[i].rerolledTranslation}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Connected Messages -->
|
|
<div class="mt-4">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-sm text-textcolor2">
|
|
Connected Messages ({summary.chatMemos.length})
|
|
</span>
|
|
<!-- Translate Message Button -->
|
|
<button
|
|
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
|
use:handleDualAction={{
|
|
onMainAction: () => toggleTranslateExpandedMessage(false),
|
|
onAlternativeAction: () =>
|
|
toggleTranslateExpandedMessage(true),
|
|
}}
|
|
>
|
|
<LanguagesIcon size={16} />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Message IDs -->
|
|
<div class="flex flex-wrap gap-1">
|
|
{#each summary.chatMemos as chatMemo}
|
|
<button
|
|
class="text-xs px-3 py-1.5 bg-zinc-900 text-zinc-300 rounded-full hover:bg-zinc-800 transition-colors {isMessageExpanded(
|
|
i,
|
|
chatMemo
|
|
)
|
|
? 'ring-1 ring-blue-500'
|
|
: ''}"
|
|
onclick={() => toggleExpandMessage(i, chatMemo)}
|
|
>
|
|
{chatMemo == null ? "First Message" : chatMemo}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Selected Message Content (if selected) -->
|
|
{#if expandedMessageUIState?.summaryIndex === i}
|
|
{@const message = getMessageFromChatMemo(
|
|
expandedMessageUIState.selectedChatMemo
|
|
)}
|
|
<div class="mt-4">
|
|
{#if message}
|
|
<!-- Role -->
|
|
<div class="text-sm text-textcolor2 mb-2 block">
|
|
{message.role}'s Message
|
|
</div>
|
|
<!-- Content -->
|
|
<div
|
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
|
>
|
|
{message.data}
|
|
</div>
|
|
{:else}
|
|
<div class="text-sm text-red-500">Message not found</div>
|
|
{/if}
|
|
|
|
<!-- Message Translation -->
|
|
{#if expandedMessageUIState.translation}
|
|
<div class="mt-4">
|
|
<span class="text-sm text-textcolor2 mb-2 block"
|
|
>Translation</span
|
|
>
|
|
<div
|
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
|
bind:this={expandedMessageUIState.translationRef}
|
|
tabindex="-1"
|
|
>
|
|
{expandedMessageUIState.translation}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/each}
|
|
|
|
<!-- Next message to be summarized -->
|
|
{#if true}
|
|
{@const nextMessage = getNextMessageToSummarize()}
|
|
{#if nextMessage}
|
|
<div class="mt-4">
|
|
<span class="text-sm text-textcolor2 mb-2 block">
|
|
HypaV3 will summarize [{nextMessage.chatId
|
|
? nextMessage.chatId
|
|
: "no message id"}]
|
|
</span>
|
|
<div
|
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
|
>
|
|
{nextMessage.data}
|
|
</div>
|
|
</div>
|
|
{:else if !getFirstMessage()}
|
|
<div class="text-sm text-red-500">
|
|
WARN: Selected first message is null
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|