refactor(frontend): saved and transcription results show where they were asked, not in a toast

This commit is contained in:
hh committed 2026-10-28 01:56:19 +00:00
1 parent 58b4346166
commit fb869e767b
7 files changed
+127 -36

No files matched your search

@@ -12,6 +12,7 @@
import Reactions from "#lib/components/Reactions.svelte";
import ReplyHeader from "#lib/components/ReplyHeader.svelte";
import MessageReadItem from "#lib/components/read/MessageReadItem.svelte";
import SavedItem from "#lib/components/SavedItem.svelte";
import Avatar from "#lib/components/ui/Avatar.svelte";
import ContextMenu from "#lib/components/ui/ContextMenu.svelte";
import ContextMenuItem from "#lib/components/ui/ContextMenuItem.svelte";
@@ -333,13 +334,11 @@
>
{/if}
{#if savedUi.available && !savedUi.isSavedChat(message.chat_id)}
<ContextMenuItem
icon="forward"
onselect={() => savedUi.forward(message.chat_id, message.message_id)}
>{message.deleted_at || message.is_self_destruct
? t("saved.copy")
: t("saved.forward")}</ContextMenuItem
>
<SavedItem
chatId={message.chat_id}
copy={Boolean(message.deleted_at || message.is_self_destruct)}
messageId={message.message_id}
/>
{/if}
<CopyItem
done={t("chat.menu.linkCopied")}
@@ -11,6 +11,7 @@
let text = $state("");
let sending = $state(false);
let failure = $state<string | null>(null);
let file = $state<File | null>(null);
let caption = $state("");
let voice = $state(false);
@@ -49,11 +50,13 @@
return;
}
sending = true;
if (await savedUi.send({ text: message })) {
const result = await savedUi.send({ text: message });
sending = false;
failure = result.ok ? null : t(result.label);
if (result.ok) {
text = "";
requestAnimationFrame(fit);
}
sending = false;
}
async function sendFile() {
@@ -61,13 +64,14 @@
return;
}
sending = true;
const sent = await savedUi.send({
const result = await savedUi.send({
file,
text: caption.trim(),
voice: isAudio && voice,
});
sending = false;
if (sent) {
failure = result.ok ? null : t(result.label);
if (result.ok) {
file = null;
caption = "";
}
@@ -97,6 +101,7 @@
function onOpenChange(open: boolean) {
if (!(open || sending)) {
failure = null;
if (!text) {
text = caption;
}
@@ -108,6 +113,9 @@
</script>
<div class="SavedComposer">
{#if failure && !file}
<p class="failure">{failure}</p>
{/if}
<div class="field">
<textarea
aria-label={t("saved.message")}
@@ -189,6 +197,9 @@
</span>
</button>
{/if}
{#if failure}
<p class="failure">{failure}</p>
{/if}
</div>
<div class="dialog-actions">
<Dialog.Close>
@@ -212,6 +223,7 @@
.SavedComposer {
display: flex;
flex-shrink: 0;
flex-wrap: wrap;
align-items: flex-end;
gap: 0.5rem;
@@ -225,6 +237,20 @@
}
}
.failure {
flex-basis: 100%;
margin: 0;
padding: 0 0.25rem;
font-size: 0.875rem;
color: var(--color-error);
}
.dialog-body .failure {
margin-top: 0.75rem;
padding: 0;
}
.field {
display: flex;
flex: 1;
@@ -0,0 +1,53 @@
<script lang="ts">
import { ContextMenu } from "bits-ui";
import { menuCloser } from "#lib/components/ui/ContextMenu.svelte";
import Icon from "#lib/components/ui/Icon.svelte";
import Spinner from "#lib/components/ui/Spinner.svelte";
import { t } from "#lib/i18n/locale.svelte.js";
import { type SavedOutcome, savedUi } from "#lib/stores/saved.svelte.js";
interface Props {
chatId: number;
copy: boolean;
messageId: number;
}
const { chatId, copy, messageId }: Props = $props();
const DONE_DELAY = 900;
const FAILED_DELAY = 2400;
const close = menuCloser();
let busy = $state(false);
let result = $state<SavedOutcome | null>(null);
const icon = $derived.by(() => {
if (result === null) {
return "forward";
}
return result.ok ? "check" : "warning";
});
async function send() {
if (busy || result !== null) {
return;
}
busy = true;
result = await savedUi.forward(chatId, messageId);
busy = false;
setTimeout(close, result.ok ? DONE_DELAY : FAILED_DELAY);
}
</script>
<ContextMenu.Item class="bg-menu-item" closeOnSelect={false} onSelect={send}>
{#if busy}
<Spinner color="gray" size="1.125rem" />
{:else}
<Icon name={icon} size="1.125rem" />
{/if}
{#if result}
{t(result.label)}
{:else}
{copy ? t("saved.copy") : t("saved.forward")}
{/if}
</ContextMenu.Item>
@@ -134,6 +134,8 @@
</button>
{#if stt.open}
<div class="transcript">{stt.text}</div>
{:else if stt.failure}
<div class="transcript failed">{stt.failure}</div>
{/if}
</div>
@@ -178,6 +180,11 @@
color: var(--color-text);
white-space: pre-wrap;
}
.failed {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.RoundVideo {
cursor: pointer;
@@ -157,6 +157,8 @@
</div>
{#if stt.open}
<div class="transcript">{stt.text}</div>
{:else if stt.failure}
<div class="transcript failed">{stt.failure}</div>
{/if}
</div>
@@ -223,6 +225,11 @@
color: var(--color-text);
white-space: pre-wrap;
}
.failed {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.toggle {
cursor: pointer;