mmh
This commit is contained in:
@@ -94,6 +94,12 @@ const en = {
|
||||
"chat.noModels": "No models",
|
||||
"chat.selectModel": "Select model…",
|
||||
"chat.personalTag": "personal key",
|
||||
"toast.chatDeleted": "Chat “{title}” deleted",
|
||||
"toast.chatArchived": "Chat “{title}” archived",
|
||||
"toast.chatUnarchived": "Chat “{title}” unarchived",
|
||||
"toast.modelChanged": "Model: {label}",
|
||||
"toast.themeChanged": "Theme changed",
|
||||
"toast.accentChanged": "Accent color changed",
|
||||
"chat.you": "You",
|
||||
"chat.greet": "How can I help, {name}?",
|
||||
"chat.greetSub": "Pick a model, then type below. Markdown is supported.",
|
||||
@@ -635,6 +641,12 @@ const es: Dict = {
|
||||
"chat.noModels": "Sin modelos",
|
||||
"chat.selectModel": "Elegir modelo…",
|
||||
"chat.personalTag": "clave personal",
|
||||
"toast.chatDeleted": "Chat “{title}” eliminado",
|
||||
"toast.chatArchived": "Chat “{title}” archivado",
|
||||
"toast.chatUnarchived": "Chat “{title}” desarchivado",
|
||||
"toast.modelChanged": "Modelo: {label}",
|
||||
"toast.themeChanged": "Tema cambiado",
|
||||
"toast.accentChanged": "Color de acento cambiado",
|
||||
"chat.you": "Tú",
|
||||
"chat.greet": "¿En qué te ayudo, {name}?",
|
||||
"chat.greetSub": "Elige un modelo y escribe abajo. Markdown soportado.",
|
||||
@@ -1166,6 +1178,12 @@ const fr: Dict = {
|
||||
"chat.noModels": "Aucun modèle",
|
||||
"chat.selectModel": "Choisir un modèle…",
|
||||
"chat.personalTag": "clé personnelle",
|
||||
"toast.chatDeleted": "Chat “{title}” supprimé",
|
||||
"toast.chatArchived": "Chat “{title}” archivé",
|
||||
"toast.chatUnarchived": "Chat “{title}” désarchivé",
|
||||
"toast.modelChanged": "Modèle : {label}",
|
||||
"toast.themeChanged": "Thème modifié",
|
||||
"toast.accentChanged": "Couleur d’accent modifiée",
|
||||
"chat.you": "Vous",
|
||||
"chat.greet": "Comment puis-je aider, {name} ?",
|
||||
"chat.greetSub":
|
||||
@@ -1697,6 +1715,12 @@ const ru: Dict = {
|
||||
"chat.noModels": "Нет моделей",
|
||||
"chat.selectModel": "Выбрать модель…",
|
||||
"chat.personalTag": "свой ключ",
|
||||
"toast.chatDeleted": "Чат “{title}” удалён",
|
||||
"toast.chatArchived": "Чат “{title}” архивирован",
|
||||
"toast.chatUnarchived": "Чат “{title}” разархивирован",
|
||||
"toast.modelChanged": "Модель: {label}",
|
||||
"toast.themeChanged": "Тема изменена",
|
||||
"toast.accentChanged": "Акцентный цвет изменён",
|
||||
"chat.you": "Вы",
|
||||
"chat.greet": "Чем помочь, {name}?",
|
||||
"chat.greetSub": "Выберите модель и пишите ниже. Поддерживается Markdown.",
|
||||
@@ -2215,6 +2239,12 @@ const it: Dict = {
|
||||
"chat.noModels": "Nessun modello",
|
||||
"chat.selectModel": "Scegli modello…",
|
||||
"chat.personalTag": "chiave personale",
|
||||
"toast.chatDeleted": "Chat “{title}” eliminata",
|
||||
"toast.chatArchived": "Chat “{title}” archiviata",
|
||||
"toast.chatUnarchived": "Chat “{title}” disarchiviata",
|
||||
"toast.modelChanged": "Modello: {label}",
|
||||
"toast.themeChanged": "Tema cambiato",
|
||||
"toast.accentChanged": "Colore accento cambiato",
|
||||
"chat.you": "Tu",
|
||||
"chat.greet": "Come posso aiutare, {name}?",
|
||||
"chat.greetSub": "Scegli un modello e scrivi qui sotto. Markdown supportato.",
|
||||
@@ -2739,6 +2769,12 @@ const pt: Dict = {
|
||||
"chat.noModels": "Sem modelos",
|
||||
"chat.selectModel": "Escolher modelo…",
|
||||
"chat.personalTag": "chave pessoal",
|
||||
"toast.chatDeleted": "Chat “{title}” eliminado",
|
||||
"toast.chatArchived": "Chat “{title}” arquivado",
|
||||
"toast.chatUnarchived": "Chat “{title}” desarquivado",
|
||||
"toast.modelChanged": "Modelo: {label}",
|
||||
"toast.themeChanged": "Tema alterado",
|
||||
"toast.accentChanged": "Cor de destaque alterada",
|
||||
"chat.you": "Tu",
|
||||
"chat.greet": "Como posso ajudar, {name}?",
|
||||
"chat.greetSub": "Escolhe um modelo e escreve abaixo. Markdown suportado.",
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
/**
|
||||
* Bottom-pill notifications (toasts): one-line confirmations when the user
|
||||
* takes an action (chat deleted/archived, model/theme/accent changed…).
|
||||
* Same subscribe pattern as features.ts / i18n.ts — callable from anywhere
|
||||
* (App, settings sections) without prop drilling.
|
||||
*/
|
||||
|
||||
export type ToastIcon =
|
||||
| "trash"
|
||||
| "archive"
|
||||
| "unarchive"
|
||||
| "model"
|
||||
| "theme"
|
||||
| "accent"
|
||||
| "check";
|
||||
|
||||
export interface Toast {
|
||||
id: number;
|
||||
text: string;
|
||||
icon: ToastIcon;
|
||||
/** Same-tag pushes replace the pill instead of stacking (accent, model). */
|
||||
tag: string | null;
|
||||
/** Bumped on replace so the pill restarts its enter/dismiss timers. */
|
||||
nonce: number;
|
||||
}
|
||||
|
||||
/** How long a pill stays fully visible before its exit animation. */
|
||||
export const TOAST_DISPLAY_MS = 3000;
|
||||
|
||||
let nextId = 1;
|
||||
let toasts: Toast[] = [];
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
function emit(): void {
|
||||
listeners.forEach((l) => l());
|
||||
}
|
||||
|
||||
export function pushToast(text: string, opts: { icon?: ToastIcon; tag?: string } = {}): void {
|
||||
const icon = opts.icon ?? "check";
|
||||
const tag = opts.tag ?? null;
|
||||
if (tag) {
|
||||
const existing = toasts.find((t) => t.tag === tag);
|
||||
if (existing) {
|
||||
toasts = toasts.map((t) =>
|
||||
t.id === existing.id ? { ...t, text, icon, nonce: t.nonce + 1 } : t,
|
||||
);
|
||||
emit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Cap the stack so rapid actions never flood the bottom of the screen.
|
||||
toasts = [...toasts.slice(-2), { id: nextId++, text, icon, tag, nonce: 0 }];
|
||||
emit();
|
||||
}
|
||||
|
||||
export function removeToast(id: number): void {
|
||||
if (!toasts.some((t) => t.id === id)) return;
|
||||
toasts = toasts.filter((t) => t.id !== id);
|
||||
emit();
|
||||
}
|
||||
|
||||
export function useToasts(): Toast[] {
|
||||
return useSyncExternalStore(
|
||||
(cb) => {
|
||||
listeners.add(cb);
|
||||
return () => {
|
||||
listeners.delete(cb);
|
||||
};
|
||||
},
|
||||
() => toasts,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user