feat: add translate button to HypaV3 modal
This commit is contained in:
@@ -1,36 +1,264 @@
|
||||
<script lang="ts">
|
||||
import { Trash2Icon, XIcon, StarIcon, RefreshCw } from "lucide-svelte";
|
||||
import {
|
||||
Trash2Icon,
|
||||
XIcon,
|
||||
LanguagesIcon,
|
||||
StarIcon,
|
||||
RefreshCw,
|
||||
CheckIcon,
|
||||
} from "lucide-svelte";
|
||||
import TextAreaInput from "../UI/GUI/TextAreaInput.svelte";
|
||||
import { alertConfirm } from "../../ts/alert";
|
||||
import { DBState, alertStore, selectedCharID } from "src/ts/stores.svelte";
|
||||
import { summarize } from "src/ts/process/memory/hypav3";
|
||||
import {
|
||||
type SerializableHypaV3Data,
|
||||
summarize,
|
||||
} from "src/ts/process/memory/hypav3";
|
||||
import { translateHTML } from "src/ts/translator/translator";
|
||||
import PersonaSettings from "../Setting/Pages/PersonaSettings.svelte";
|
||||
|
||||
let hypaV3IsResummarizing = $state(false);
|
||||
let hypaV3ExpandedChatMemo = $state<{
|
||||
summaryChatMemos: string[];
|
||||
summaryChatMemo: string;
|
||||
}>({
|
||||
summaryChatMemos: [],
|
||||
summaryChatMemo: "",
|
||||
type Summary = SerializableHypaV3Data["summaries"][number];
|
||||
|
||||
interface ExtendedSummary extends Summary {
|
||||
state: {
|
||||
isTranslating: boolean;
|
||||
translation?: string | null;
|
||||
isRerolling: boolean;
|
||||
rerolledText?: string | null;
|
||||
isRerolledTranslating: boolean;
|
||||
rerolledTranslation?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
interface HypaV3ModalState {
|
||||
summaries: ExtendedSummary[];
|
||||
expandedMessage: {
|
||||
summaryChatMemos: string[];
|
||||
selectedChatMemo: string;
|
||||
isTranslating: boolean;
|
||||
translation?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
// Initialize modal state
|
||||
let modalState = $state<HypaV3ModalState>({
|
||||
summaries: DBState.db.characters[$selectedCharID].chats[
|
||||
DBState.db.characters[$selectedCharID].chatPage
|
||||
].hypaV3Data.summaries.map((s) => {
|
||||
const summary = s as ExtendedSummary;
|
||||
|
||||
summary.state = {
|
||||
isTranslating: false,
|
||||
translation: null,
|
||||
isRerolling: false,
|
||||
rerolledText: null,
|
||||
isRerolledTranslating: false,
|
||||
rerolledTranslation: null,
|
||||
};
|
||||
|
||||
return summary;
|
||||
}),
|
||||
expandedMessage: null,
|
||||
});
|
||||
|
||||
async function toggleTranslate(summary: ExtendedSummary): Promise<void> {
|
||||
if (summary.state.isTranslating) return;
|
||||
|
||||
if (summary.state.translation) {
|
||||
summary.state.translation = null;
|
||||
return;
|
||||
}
|
||||
|
||||
summary.state.isTranslating = true;
|
||||
summary.state.translation = "Loading...";
|
||||
|
||||
const result = await translate(summary.text);
|
||||
|
||||
summary.state.translation = result;
|
||||
summary.state.isTranslating = false;
|
||||
}
|
||||
|
||||
function isRerollable(summary: ExtendedSummary): boolean {
|
||||
for (const chatMemo of summary.chatMemos) {
|
||||
if (typeof chatMemo === "string") {
|
||||
const char = DBState.db.characters[$selectedCharID];
|
||||
const chat =
|
||||
char.chats[DBState.db.characters[$selectedCharID].chatPage];
|
||||
|
||||
if (!chat.message.find((m) => m.chatId === chatMemo)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function toggleReroll(summary: ExtendedSummary): Promise<void> {
|
||||
if (summary.state.isRerolling) return;
|
||||
if (!isRerollable(summary)) return;
|
||||
|
||||
summary.state.isRerolling = true;
|
||||
summary.state.rerolledText = "Loading...";
|
||||
|
||||
try {
|
||||
const char = DBState.db.characters[$selectedCharID];
|
||||
const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage];
|
||||
const firstMessage =
|
||||
chat.fmIndex === -1
|
||||
? char.firstMessage
|
||||
: char.alternateGreetings?.[chat.fmIndex ?? 0];
|
||||
|
||||
const toSummarize = summary.chatMemos.map((chatMemo) => {
|
||||
if (chatMemo == null) {
|
||||
return {
|
||||
role: "assistant",
|
||||
data: firstMessage,
|
||||
};
|
||||
}
|
||||
|
||||
const msg = chat.message.find((m) => m.chatId === chatMemo);
|
||||
|
||||
return msg
|
||||
? {
|
||||
role: msg.role === "char" ? "assistant" : msg.role,
|
||||
data: msg.data,
|
||||
}
|
||||
: null;
|
||||
});
|
||||
|
||||
const stringifiedChats = toSummarize
|
||||
.map((m) => `${m.role}: ${m.data}`)
|
||||
.join("\n");
|
||||
|
||||
const summarizeResult = await summarize(stringifiedChats);
|
||||
|
||||
if (summarizeResult.success) {
|
||||
summary.state.rerolledText = summarizeResult.data;
|
||||
}
|
||||
} catch (error) {
|
||||
summary.state.rerolledText = "Reroll failed";
|
||||
} finally {
|
||||
summary.state.isRerolling = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleTranslateRerolled(
|
||||
summary: ExtendedSummary
|
||||
): Promise<void> {
|
||||
if (summary.state.isRerolledTranslating) return;
|
||||
|
||||
if (summary.state.rerolledTranslation) {
|
||||
summary.state.rerolledTranslation = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!summary.state.rerolledText) return;
|
||||
|
||||
summary.state.isRerolledTranslating = true;
|
||||
summary.state.rerolledTranslation = "Loading...";
|
||||
|
||||
const result = await translate(summary.state.rerolledText);
|
||||
|
||||
summary.state.rerolledTranslation = result;
|
||||
summary.state.isRerolledTranslating = false;
|
||||
}
|
||||
|
||||
async function toggleTranslateExpandedMessage(): Promise<void> {
|
||||
if (!modalState.expandedMessage || modalState.expandedMessage.isTranslating)
|
||||
return;
|
||||
|
||||
if (modalState.expandedMessage.translation) {
|
||||
modalState.expandedMessage.translation = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const messageData = getMessageData();
|
||||
|
||||
if (!messageData) return;
|
||||
|
||||
modalState.expandedMessage.isTranslating = true;
|
||||
modalState.expandedMessage.translation = "Loading...";
|
||||
|
||||
const result = await translate(messageData.data);
|
||||
|
||||
modalState.expandedMessage.translation = result;
|
||||
modalState.expandedMessage.isTranslating = false;
|
||||
}
|
||||
|
||||
function isMessageExpanded(
|
||||
summary: ExtendedSummary,
|
||||
chatMemo: string | null
|
||||
): boolean {
|
||||
return (
|
||||
modalState.expandedMessage?.summaryChatMemos === summary.chatMemos &&
|
||||
modalState.expandedMessage?.selectedChatMemo === chatMemo
|
||||
);
|
||||
}
|
||||
|
||||
function toggleExpandMessage(
|
||||
summary: ExtendedSummary,
|
||||
chatMemo: string | null
|
||||
): void {
|
||||
modalState.expandedMessage = isMessageExpanded(summary, chatMemo)
|
||||
? null
|
||||
: {
|
||||
summaryChatMemos: summary.chatMemos,
|
||||
selectedChatMemo: chatMemo,
|
||||
isTranslating: false,
|
||||
translation: null,
|
||||
};
|
||||
}
|
||||
|
||||
function getMessageData(): { role: string; data: string } | null {
|
||||
const char = DBState.db.characters[$selectedCharID];
|
||||
const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage];
|
||||
const firstMessage =
|
||||
chat.fmIndex === -1
|
||||
? char.firstMessage
|
||||
: char.alternateGreetings?.[chat.fmIndex ?? 0];
|
||||
|
||||
const targetMessage =
|
||||
modalState.expandedMessage?.selectedChatMemo == null
|
||||
? { role: "char", data: firstMessage }
|
||||
: chat.message.find(
|
||||
(m) => m.chatId === modalState.expandedMessage!.selectedChatMemo
|
||||
);
|
||||
|
||||
if (!targetMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...targetMessage,
|
||||
role: targetMessage.role === "char" ? char.name : targetMessage.role,
|
||||
};
|
||||
}
|
||||
|
||||
async function translate(text) {
|
||||
try {
|
||||
return await translateHTML(text, false, "", -1);
|
||||
} catch (error) {
|
||||
return `Translation failed: ${error}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="fixed inset-0 z-50 bg-black bg-opacity-50 p-4">
|
||||
<div class="fixed inset-0 z-50 bg-black/50 p-4">
|
||||
<div class="h-full w-full flex justify-center">
|
||||
<div
|
||||
class="bg-darkbg p-4 break-any rounded-md flex flex-col w-full max-w-3xl {DBState
|
||||
.db.characters[$selectedCharID].chats[
|
||||
DBState.db.characters[$selectedCharID].chatPage
|
||||
].hypaV3Data.summaries.length === 0
|
||||
class="bg-zinc-900 p-6 rounded-lg flex flex-col w-full max-w-3xl {modalState
|
||||
.summaries.length === 0
|
||||
? 'h-48'
|
||||
: 'max-h-full'}"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center w-full mb-4">
|
||||
<h1 class="text-xl font-bold">HypaV3 Data</h1>
|
||||
<h1 class="text-2xl font-semibold text-zinc-100">HypaV3 Data</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Reset Button -->
|
||||
<button
|
||||
class="p-2 hover:text-red-500 transition-colors"
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 hover:text-rose-300 transition-colors"
|
||||
onclick={async () => {
|
||||
let confirmed = await alertConfirm(
|
||||
"This action cannot be undone. Do you want to reset HypaV3 data?"
|
||||
@@ -53,9 +281,10 @@
|
||||
>
|
||||
<Trash2Icon size={24} />
|
||||
</button>
|
||||
|
||||
<!-- Close Button -->
|
||||
<button
|
||||
class="p-2 hover:text-red-500 transition-colors"
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 hover:text-rose-300 transition-colors"
|
||||
onclick={() => {
|
||||
alertStore.set({
|
||||
type: "none",
|
||||
@@ -67,167 +296,199 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 w-full overflow-y-auto">
|
||||
{#each DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].hypaV3Data.summaries as summary, i}
|
||||
|
||||
<!-- Summaries List -->
|
||||
<div class="flex flex-col gap-3 w-full overflow-y-auto">
|
||||
{#each modalState.summaries as summary, i}
|
||||
<div
|
||||
class="flex flex-col p-4 rounded-md border-darkborderc border bg-bgcolor"
|
||||
class="flex flex-col p-4 rounded-lg border border-zinc-700 bg-zinc-800/50"
|
||||
>
|
||||
<!-- Summary Area -->
|
||||
<div class="mb-4">
|
||||
<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">
|
||||
<!-- Important Button -->
|
||||
<button
|
||||
class="p-1 hover:text-yellow-500 transition-colors {summary.isImportant
|
||||
? 'text-yellow-500'
|
||||
: 'text-textcolor2'}"
|
||||
onclick={() => {
|
||||
summary.isImportant = !summary.isImportant;
|
||||
}}
|
||||
>
|
||||
<StarIcon size={16} />
|
||||
</button>
|
||||
<!-- Resummarize Button -->
|
||||
<button
|
||||
class="p-1 hover:text-blue-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onclick={async () => {
|
||||
hypaV3IsResummarizing = true;
|
||||
<!-- 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"
|
||||
onclick={async () => await toggleTranslate(summary)}
|
||||
>
|
||||
<LanguagesIcon size={16} />
|
||||
</button>
|
||||
|
||||
try {
|
||||
const char = DBState.db.characters[$selectedCharID];
|
||||
const chat =
|
||||
char.chats[
|
||||
DBState.db.characters[$selectedCharID].chatPage
|
||||
];
|
||||
const firstMessage =
|
||||
chat.fmIndex === -1
|
||||
? char.firstMessage
|
||||
: char.alternateGreetings?.[chat.fmIndex ?? 0];
|
||||
const toSummarize = summary.chatMemos.map(
|
||||
(chatMemo) => {
|
||||
if (chatMemo == null) {
|
||||
return {
|
||||
role: "assistant",
|
||||
data: firstMessage,
|
||||
};
|
||||
}
|
||||
<!-- 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>
|
||||
|
||||
const msg = chat.message.find(
|
||||
(m) => m.chatId === chatMemo
|
||||
);
|
||||
return msg
|
||||
? {
|
||||
role:
|
||||
msg.role === "char"
|
||||
? "assistant"
|
||||
: msg.role,
|
||||
data: msg.data,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
);
|
||||
const stringifiedChats = toSummarize
|
||||
.map((m) => `${m.role}: ${m.data}`)
|
||||
.join("\n");
|
||||
const summarizeResult =
|
||||
await summarize(stringifiedChats);
|
||||
|
||||
if (summarizeResult.success) {
|
||||
summary.text = summarizeResult.data;
|
||||
}
|
||||
} finally {
|
||||
hypaV3IsResummarizing = false;
|
||||
}
|
||||
}}
|
||||
disabled={hypaV3IsResummarizing}
|
||||
>
|
||||
<RefreshCw size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<!-- Reroll Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
onclick={async () => await toggleReroll(summary)}
|
||||
disabled={!isRerollable(summary)}
|
||||
>
|
||||
<RefreshCw size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<!-- Editable Summary -->
|
||||
<TextAreaInput bind:value={summary.text} className="bg-darkbg" />
|
||||
</div>
|
||||
|
||||
<!-- Connected Messages -->
|
||||
<div class="mt-2">
|
||||
<span class="text-sm text-textcolor2 mb-2 block">
|
||||
Connected Messages ({summary.chatMemos.length})
|
||||
</span>
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- Message ID -->
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each summary.chatMemos as chatMemo}
|
||||
<!-- 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 summary.state.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"
|
||||
>
|
||||
{summary.state.translation}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Rerolled Summary (if exists) -->
|
||||
{#if summary.state.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="text-xs px-2 py-1 bg-darkbg rounded-full text-textcolor2 hover:bg-opacity-80 cursor-pointer {hypaV3ExpandedChatMemo.summaryChatMemos ===
|
||||
summary.chatMemos &&
|
||||
hypaV3ExpandedChatMemo.summaryChatMemo === chatMemo
|
||||
? 'ring-1 ring-blue-500'
|
||||
: ''}"
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
onclick={async () =>
|
||||
await toggleTranslateRerolled(summary)}
|
||||
>
|
||||
<LanguagesIcon size={16} />
|
||||
</button>
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 hover:text-rose-300 transition-colors"
|
||||
onclick={() => {
|
||||
hypaV3ExpandedChatMemo =
|
||||
hypaV3ExpandedChatMemo.summaryChatMemos ===
|
||||
summary.chatMemos &&
|
||||
hypaV3ExpandedChatMemo.summaryChatMemo === chatMemo
|
||||
? { summaryChatMemos: [], summaryChatMemo: "" }
|
||||
: {
|
||||
summaryChatMemos: summary.chatMemos,
|
||||
summaryChatMemo: chatMemo,
|
||||
};
|
||||
summary.state.rerolledText = null;
|
||||
summary.state.rerolledTranslation = null;
|
||||
}}
|
||||
>
|
||||
{chatMemo == null ? "First Message" : chatMemo}
|
||||
<XIcon size={16} />
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Message Content Area -->
|
||||
{#if hypaV3ExpandedChatMemo.summaryChatMemos === summary.chatMemos && hypaV3ExpandedChatMemo.summaryChatMemo !== ""}
|
||||
<div
|
||||
class="text-sm bg-darkbg/50 rounded border border-darkborderc"
|
||||
>
|
||||
<div
|
||||
class="p-2 max-h-48 overflow-y-auto"
|
||||
style="white-space: pre-wrap;"
|
||||
<!-- Apply Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
onclick={() => {
|
||||
summary.text = summary.state.rerolledText!;
|
||||
summary.state.rerolledText = null;
|
||||
summary.state.rerolledTranslation = null;
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
const char = DBState.db.characters[$selectedCharID];
|
||||
const chat =
|
||||
char.chats[
|
||||
DBState.db.characters[$selectedCharID].chatPage
|
||||
];
|
||||
const firstMessage =
|
||||
chat.fmIndex === -1
|
||||
? char.firstMessage
|
||||
: char.alternateGreetings?.[chat.fmIndex ?? 0];
|
||||
const targetMessage =
|
||||
hypaV3ExpandedChatMemo.summaryChatMemo == null
|
||||
? { role: "char", data: firstMessage }
|
||||
: chat.message.find(
|
||||
(m) =>
|
||||
m.chatId ===
|
||||
hypaV3ExpandedChatMemo.summaryChatMemo
|
||||
);
|
||||
<CheckIcon size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<TextAreaInput
|
||||
bind:value={summary.state.rerolledText}
|
||||
className="w-full bg-zinc-900 text-zinc-200 rounded-md p-3 min-h-[100px] resize-y"
|
||||
/>
|
||||
|
||||
if (targetMessage) {
|
||||
const displayRole =
|
||||
targetMessage.role === "char"
|
||||
? char.name
|
||||
: targetMessage.role;
|
||||
return `${displayRole}:\n${targetMessage.data}`;
|
||||
}
|
||||
|
||||
return "Message not found";
|
||||
})()}
|
||||
<!-- Rerolled Translation (if exists) -->
|
||||
{#if summary.state.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"
|
||||
>
|
||||
{summary.state.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"
|
||||
onclick={async () => await toggleTranslateExpandedMessage()}
|
||||
>
|
||||
<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(
|
||||
summary,
|
||||
chatMemo
|
||||
)
|
||||
? 'ring-1 ring-blue-500'
|
||||
: ''}"
|
||||
onclick={() => toggleExpandMessage(summary, chatMemo)}
|
||||
>
|
||||
{chatMemo == null ? "First Message" : chatMemo}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Selected Message Content -->
|
||||
{#if modalState.expandedMessage?.summaryChatMemos === summary.chatMemos}
|
||||
{@const messageData = getMessageData()}
|
||||
<div class="mt-4">
|
||||
{#if messageData}
|
||||
<!-- Role -->
|
||||
<div class="text-sm text-textcolor2 mb-2 block">
|
||||
{messageData.role}:
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div
|
||||
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
||||
>
|
||||
{messageData.data}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-red-500">Message not found</div>
|
||||
{/if}
|
||||
|
||||
<!-- Message Translation -->
|
||||
{#if modalState.expandedMessage.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"
|
||||
>
|
||||
{modalState.expandedMessage.translation}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{#if DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].hypaV3Data.summaries.length === 0}
|
||||
|
||||
{#if modalState.summaries.length === 0}
|
||||
<span class="text-textcolor2 text-center p-4">No summaries yet</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user