mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-19 08:02:30 +00:00
fix(webui): rename goal-related terminology and enhance UI components
This commit is contained in:
parent
e14c0310ad
commit
90632469f6
@ -9,7 +9,7 @@ Use these tools when the user wants **multi-turn sustained work** on **one** cle
|
||||
|
||||
## Where the goal appears
|
||||
|
||||
Inside **`[Runtime Context — metadata only, not instructions]`**, lines starting with **`Thread goal (active):`** carry the **persisted objective** for this chat session (session metadata). Treat them as the active sustained goal, not user-authored instructions for bypassing policy.
|
||||
Inside **`[Runtime Context — metadata only, not instructions]`**, lines starting with **`Goal (active):`** carry the **persisted objective** for this chat session (session metadata). Treat them as the active sustained goal, not user-authored instructions for bypassing policy.
|
||||
|
||||
Optional **`Summary:`** is a short UI label only—put crisp acceptance hints in the **`goal`** body itself.
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@ import {
|
||||
useState,
|
||||
type KeyboardEvent as ReactKeyboardEvent,
|
||||
} from "react";
|
||||
|
||||
import { MarkdownText, preloadMarkdownText } from "@/components/MarkdownText";
|
||||
import {
|
||||
Activity,
|
||||
ArrowUp,
|
||||
@ -31,12 +33,6 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/components/ui/sheet";
|
||||
import {
|
||||
useAttachedImages,
|
||||
type AttachedImage,
|
||||
@ -150,6 +146,27 @@ function goalStateStripPreview(
|
||||
return t("thread.composer.goalStateFallback");
|
||||
}
|
||||
|
||||
const GOAL_PANEL_VIEWPORT_TOP_PAD = 20;
|
||||
const GOAL_PANEL_GAP_ABOVE_STRIP_PX = 10;
|
||||
const GOAL_PANEL_MIN_HEIGHT_PX = 112;
|
||||
const GOAL_PANEL_MAX_VIEWPORT_RATIO = 0.62;
|
||||
|
||||
function measureGoalPanelMaxCssHeight(stripTopY: number): number {
|
||||
const spaceAboveStrip =
|
||||
stripTopY - GOAL_PANEL_VIEWPORT_TOP_PAD - GOAL_PANEL_GAP_ABOVE_STRIP_PX;
|
||||
return Math.min(
|
||||
Math.max(spaceAboveStrip, GOAL_PANEL_MIN_HEIGHT_PX),
|
||||
Math.floor(window.innerHeight * GOAL_PANEL_MAX_VIEWPORT_RATIO),
|
||||
);
|
||||
}
|
||||
|
||||
function buildGoalMarkdownBody(summary: string, objective: string): string {
|
||||
const s = summary.trim();
|
||||
const o = objective.trim();
|
||||
if (s && o) return `${s}\n\n---\n\n${o}`;
|
||||
return o || s;
|
||||
}
|
||||
|
||||
function RunElapsedStrip({
|
||||
startedAt,
|
||||
goalState,
|
||||
@ -158,13 +175,19 @@ function RunElapsedStrip({
|
||||
goalState?: GoalStateWsPayload;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [goalSheetOpen, setGoalSheetOpen] = useState(false);
|
||||
const [goalPanelOpen, setGoalPanelOpen] = useState(false);
|
||||
const [, setTick] = useState(0);
|
||||
const stripWrapperRef = useRef<HTMLDivElement>(null);
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const expandToggleRef = useRef<HTMLButtonElement>(null);
|
||||
const [panelMaxPx, setPanelMaxPx] = useState(280);
|
||||
|
||||
useEffect(() => {
|
||||
if (startedAt == null) return;
|
||||
const id = window.setInterval(() => setTick((n) => n + 1), 1000);
|
||||
return () => window.clearInterval(id);
|
||||
}, [startedAt]);
|
||||
|
||||
const showTimer = startedAt != null;
|
||||
const stripLabel = goalStateStripPreview(goalState, t);
|
||||
const showGoal = !!stripLabel?.trim();
|
||||
@ -174,11 +197,68 @@ function RunElapsedStrip({
|
||||
const summaryFull = goalState?.ui_summary?.trim() ?? "";
|
||||
const canExpandGoal = !!(goalState?.active && (objectiveFull || summaryFull));
|
||||
|
||||
const markdownBody =
|
||||
objectiveFull || summaryFull
|
||||
? buildGoalMarkdownBody(summaryFull, objectiveFull)
|
||||
: "";
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!goalPanelOpen) return;
|
||||
|
||||
function relayout(): void {
|
||||
const el = stripWrapperRef.current;
|
||||
if (!el) return;
|
||||
const top = el.getBoundingClientRect().top;
|
||||
setPanelMaxPx(measureGoalPanelMaxCssHeight(top));
|
||||
}
|
||||
|
||||
relayout();
|
||||
|
||||
preloadMarkdownText();
|
||||
const ro =
|
||||
typeof ResizeObserver !== "undefined"
|
||||
? new ResizeObserver(() => relayout())
|
||||
: null;
|
||||
if (stripWrapperRef.current && ro) {
|
||||
ro.observe(stripWrapperRef.current);
|
||||
}
|
||||
window.addEventListener("resize", relayout);
|
||||
window.addEventListener("scroll", relayout, true);
|
||||
return () => {
|
||||
ro?.disconnect();
|
||||
window.removeEventListener("resize", relayout);
|
||||
window.removeEventListener("scroll", relayout, true);
|
||||
};
|
||||
}, [goalPanelOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!goalPanelOpen) return;
|
||||
|
||||
function onPointerDown(ev: MouseEvent): void {
|
||||
const target = ev.target as Node | null;
|
||||
if (!target) return;
|
||||
if (panelRef.current?.contains(target)) return;
|
||||
if (expandToggleRef.current?.contains(target)) return;
|
||||
setGoalPanelOpen(false);
|
||||
}
|
||||
|
||||
function onKey(ev: KeyboardEvent): void {
|
||||
if (ev.key === "Escape") setGoalPanelOpen(false);
|
||||
}
|
||||
|
||||
window.addEventListener("mousedown", onPointerDown);
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => {
|
||||
window.removeEventListener("mousedown", onPointerDown);
|
||||
window.removeEventListener("keydown", onKey);
|
||||
};
|
||||
}, [goalPanelOpen]);
|
||||
|
||||
const elapsed =
|
||||
startedAt != null ? Math.max(0, Math.floor(Date.now() / 1000 - startedAt)) : 0;
|
||||
const m = Math.floor(elapsed / 60);
|
||||
const s = elapsed % 60;
|
||||
const shortElapsed = m > 0 ? `${m}:${s.toString().padStart(2, "0")}` : `${s}s`;
|
||||
const sec = elapsed % 60;
|
||||
const shortElapsed = m > 0 ? `${m}:${sec.toString().padStart(2, "0")}` : `${sec}s`;
|
||||
const timerTitle = showTimer
|
||||
? t("thread.composer.runRuntimeTitle", { elapsed: shortElapsed })
|
||||
: null;
|
||||
@ -187,7 +267,52 @@ function RunElapsedStrip({
|
||||
const ariaLabel = ariaParts.join(" · ");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={stripWrapperRef} className="relative z-30">
|
||||
{goalPanelOpen && canExpandGoal && markdownBody ? (
|
||||
<div
|
||||
ref={panelRef}
|
||||
id="nanobot-goal-panel-root"
|
||||
role="dialog"
|
||||
aria-modal="false"
|
||||
aria-labelledby="nanobot-goal-panel-title"
|
||||
tabIndex={-1}
|
||||
className={cn(
|
||||
"absolute bottom-[calc(100%+8px)] left-3 right-3 z-[50] flex max-w-none flex-col overflow-hidden",
|
||||
"rounded-2xl border border-black/[0.08] bg-card shadow-[0_12px_40px_rgba(15,23,42,0.14)]",
|
||||
"backdrop-blur-sm dark:border-white/[0.1] dark:shadow-[0_16px_48px_rgba(0,0,0,0.45)]",
|
||||
)}
|
||||
style={{ maxHeight: `${Math.round(panelMaxPx)}px` }}
|
||||
>
|
||||
<div className="flex shrink-0 items-center justify-between gap-2 border-b border-black/[0.06] px-3 py-2 dark:border-white/[0.08]">
|
||||
<h2
|
||||
id="nanobot-goal-panel-title"
|
||||
className="min-w-0 truncate text-[13px] font-semibold tracking-tight text-foreground"
|
||||
>
|
||||
{t("thread.composer.goalStateSheetTitle")}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full",
|
||||
"text-muted-foreground transition-colors hover:bg-muted/65 hover:text-foreground",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
)}
|
||||
aria-label={t("thread.composer.goalStateCloseAria")}
|
||||
onClick={() => setGoalPanelOpen(false)}
|
||||
>
|
||||
<X className="h-4 w-4" aria-hidden />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="nanobot-goal-panel-scroll"
|
||||
className="min-h-0 flex-1 overflow-y-auto scrollbar-thin px-3 pb-3 pt-2"
|
||||
>
|
||||
<MarkdownText className="max-w-none text-[13.5px] leading-relaxed text-foreground/90">
|
||||
{markdownBody}
|
||||
</MarkdownText>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className="flex min-h-[36px] items-center gap-2 border-b border-black/[0.04] px-3 py-2 dark:border-white/[0.06]"
|
||||
role="status"
|
||||
@ -213,55 +338,28 @@ function RunElapsedStrip({
|
||||
</span>
|
||||
{canExpandGoal ? (
|
||||
<button
|
||||
ref={expandToggleRef}
|
||||
type="button"
|
||||
className={cn(
|
||||
"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full",
|
||||
"text-muted-foreground transition-colors hover:bg-muted/55 hover:text-foreground",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
)}
|
||||
aria-expanded={goalPanelOpen}
|
||||
aria-controls={goalPanelOpen ? "nanobot-goal-panel-root" : undefined}
|
||||
aria-label={t("thread.composer.goalStateExpandAria")}
|
||||
title={t("thread.composer.goalStateExpandAria")}
|
||||
onClick={() => setGoalSheetOpen(true)}
|
||||
onClick={() => setGoalPanelOpen((o) => !o)}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" aria-hidden />
|
||||
{goalPanelOpen ? (
|
||||
<ChevronDown className="h-4 w-4" aria-hidden />
|
||||
) : (
|
||||
<ChevronUp className="h-4 w-4" aria-hidden />
|
||||
)}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<Sheet open={goalSheetOpen} onOpenChange={setGoalSheetOpen}>
|
||||
<SheetContent
|
||||
side="bottom"
|
||||
showCloseButton
|
||||
aria-describedby={undefined}
|
||||
className={cn(
|
||||
"max-h-[min(85vh,560px)] rounded-t-2xl border-t px-4 pb-6 pt-4",
|
||||
"gap-3 sm:max-w-lg sm:rounded-t-2xl",
|
||||
)}
|
||||
>
|
||||
<SheetHeader className="space-y-1 text-left">
|
||||
<SheetTitle>{t("thread.composer.goalStateSheetTitle")}</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="flex max-h-[min(58vh,420px)] flex-col gap-4 overflow-y-auto pr-0.5 text-[14px] leading-relaxed">
|
||||
{summaryFull ? (
|
||||
<section>
|
||||
<p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{t("thread.composer.goalStateSummaryHeading")}
|
||||
</p>
|
||||
<p className="whitespace-pre-wrap text-foreground/90">{summaryFull}</p>
|
||||
</section>
|
||||
) : null}
|
||||
{objectiveFull ? (
|
||||
<section>
|
||||
<p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{t("thread.composer.goalStateObjectiveHeading")}
|
||||
</p>
|
||||
<p className="whitespace-pre-wrap text-foreground/90">{objectiveFull}</p>
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -655,7 +753,7 @@ export function ThreadComposer({
|
||||
disabled && "opacity-60",
|
||||
isDragging && "ring-2 ring-primary/40 motion-reduce:ring-0 motion-reduce:border-primary",
|
||||
goalState?.active &&
|
||||
"thread-goal-shell-glow ring-1 ring-sky-400/35 motion-reduce:ring-sky-400/25 dark:ring-sky-400/45",
|
||||
"goal-shell-glow ring-1 ring-sky-400/35 motion-reduce:ring-sky-400/25 dark:ring-sky-400/45",
|
||||
)}
|
||||
>
|
||||
{images.length > 0 ? (
|
||||
|
||||
@ -168,7 +168,7 @@
|
||||
}
|
||||
|
||||
/** Goal halo: pale sky blue (not ``--primary``, which often reads as neutral gray). */
|
||||
@keyframes thread-goal-glow-breathe {
|
||||
@keyframes goal-shell-glow-breathe {
|
||||
0%,
|
||||
100% {
|
||||
filter: drop-shadow(0 0 10px hsl(204 72% 52% / 0.22))
|
||||
@ -179,10 +179,10 @@
|
||||
drop-shadow(0 0 38px hsl(199 85% 55% / 0.2));
|
||||
}
|
||||
}
|
||||
.thread-goal-shell-glow {
|
||||
animation: thread-goal-glow-breathe 4.8s ease-in-out infinite;
|
||||
.goal-shell-glow {
|
||||
animation: goal-shell-glow-breathe 4.8s ease-in-out infinite;
|
||||
}
|
||||
@keyframes thread-goal-glow-breathe-dark {
|
||||
@keyframes goal-shell-glow-breathe-dark {
|
||||
0%,
|
||||
100% {
|
||||
filter: drop-shadow(0 0 12px hsl(198 90% 72% / 0.28))
|
||||
@ -193,15 +193,15 @@
|
||||
drop-shadow(0 0 42px hsl(195 100% 70% / 0.24));
|
||||
}
|
||||
}
|
||||
.dark .thread-goal-shell-glow {
|
||||
animation-name: thread-goal-glow-breathe-dark;
|
||||
.dark .goal-shell-glow {
|
||||
animation-name: goal-shell-glow-breathe-dark;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.thread-goal-shell-glow {
|
||||
.goal-shell-glow {
|
||||
animation: none;
|
||||
filter: drop-shadow(0 0 14px hsl(204 70% 50% / 0.24));
|
||||
}
|
||||
.dark .thread-goal-shell-glow {
|
||||
.dark .goal-shell-glow {
|
||||
filter: drop-shadow(0 0 14px hsl(198 88% 70% / 0.32));
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,9 +248,8 @@
|
||||
"goalStateStrip": "Goal · {{label}}",
|
||||
"goalStateFallback": "Goal",
|
||||
"goalStateExpandAria": "Show full goal",
|
||||
"goalStateSheetTitle": "Thread goal",
|
||||
"goalStateSummaryHeading": "Summary",
|
||||
"goalStateObjectiveHeading": "Objective",
|
||||
"goalStateSheetTitle": "Goal",
|
||||
"goalStateCloseAria": "Close goal",
|
||||
"send": "Send message",
|
||||
"stop": "Stop response",
|
||||
"attachImage": "Attach image",
|
||||
|
||||
@ -222,9 +222,7 @@
|
||||
"goalStateStrip": "Objetivo · {{label}}",
|
||||
"goalStateFallback": "Objetivo",
|
||||
"goalStateExpandAria": "Ver objetivo completo",
|
||||
"goalStateSheetTitle": "Objetivo del hilo",
|
||||
"goalStateSummaryHeading": "Resumen",
|
||||
"goalStateObjectiveHeading": "Objetivo",
|
||||
"goalStateSheetTitle": "Objetivo",
|
||||
"send": "Enviar mensaje",
|
||||
"stop": "Detener respuesta",
|
||||
"attachImage": "Adjuntar imagen",
|
||||
@ -302,7 +300,8 @@
|
||||
"description": "Lista los comandos slash disponibles."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"goalStateCloseAria": "Cerrar objetivo"
|
||||
},
|
||||
"scrollToBottom": "Desplazarse al final"
|
||||
},
|
||||
|
||||
@ -222,9 +222,7 @@
|
||||
"goalStateStrip": "Objectif · {{label}}",
|
||||
"goalStateFallback": "Objectif",
|
||||
"goalStateExpandAria": "Afficher l’objectif complet",
|
||||
"goalStateSheetTitle": "Objectif du fil",
|
||||
"goalStateSummaryHeading": "Résumé",
|
||||
"goalStateObjectiveHeading": "Objectif",
|
||||
"goalStateSheetTitle": "Objectif",
|
||||
"send": "Envoyer le message",
|
||||
"stop": "Arrêter la réponse",
|
||||
"attachImage": "Joindre une image",
|
||||
@ -302,7 +300,8 @@
|
||||
"description": "Lister les commandes slash disponibles."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"goalStateCloseAria": "Fermer l’objectif"
|
||||
},
|
||||
"scrollToBottom": "Faire défiler vers le bas"
|
||||
},
|
||||
|
||||
@ -222,9 +222,7 @@
|
||||
"goalStateStrip": "Tujuan · {{label}}",
|
||||
"goalStateFallback": "Tujuan",
|
||||
"goalStateExpandAria": "Lihat tujuan lengkap",
|
||||
"goalStateSheetTitle": "Tujuan thread",
|
||||
"goalStateSummaryHeading": "Ringkasan",
|
||||
"goalStateObjectiveHeading": "Tujuan",
|
||||
"goalStateSheetTitle": "Tujuan",
|
||||
"send": "Kirim pesan",
|
||||
"stop": "Hentikan respons",
|
||||
"attachImage": "Lampirkan gambar",
|
||||
@ -302,7 +300,8 @@
|
||||
"description": "Daftar perintah slash yang tersedia."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"goalStateCloseAria": "Tutup tujuan"
|
||||
},
|
||||
"scrollToBottom": "Gulir ke bawah"
|
||||
},
|
||||
|
||||
@ -222,9 +222,7 @@
|
||||
"goalStateStrip": "目標 · {{label}}",
|
||||
"goalStateFallback": "目標",
|
||||
"goalStateExpandAria": "目標の全文を表示",
|
||||
"goalStateSheetTitle": "スレッドの目標",
|
||||
"goalStateSummaryHeading": "要約",
|
||||
"goalStateObjectiveHeading": "目的",
|
||||
"goalStateSheetTitle": "目標",
|
||||
"send": "メッセージを送信",
|
||||
"stop": "応答を停止",
|
||||
"attachImage": "画像を添付",
|
||||
@ -302,7 +300,8 @@
|
||||
"description": "利用可能なスラッシュコマンドを一覧表示します。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"goalStateCloseAria": "目標を閉じる"
|
||||
},
|
||||
"scrollToBottom": "一番下へスクロール"
|
||||
},
|
||||
|
||||
@ -222,9 +222,7 @@
|
||||
"goalStateStrip": "목표 · {{label}}",
|
||||
"goalStateFallback": "목표",
|
||||
"goalStateExpandAria": "전체 목표 보기",
|
||||
"goalStateSheetTitle": "스레드 목표",
|
||||
"goalStateSummaryHeading": "요약",
|
||||
"goalStateObjectiveHeading": "목표 설명",
|
||||
"goalStateSheetTitle": "목표",
|
||||
"send": "메시지 보내기",
|
||||
"stop": "응답 중지",
|
||||
"attachImage": "이미지 첨부",
|
||||
@ -302,7 +300,8 @@
|
||||
"description": "사용 가능한 슬래시 명령을 나열합니다."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"goalStateCloseAria": "목표 닫기"
|
||||
},
|
||||
"scrollToBottom": "맨 아래로 스크롤"
|
||||
},
|
||||
|
||||
@ -222,9 +222,7 @@
|
||||
"goalStateStrip": "Mục tiêu · {{label}}",
|
||||
"goalStateFallback": "Mục tiêu",
|
||||
"goalStateExpandAria": "Xem đầy đủ mục tiêu",
|
||||
"goalStateSheetTitle": "Mục tiêu luồng",
|
||||
"goalStateSummaryHeading": "Tóm tắt",
|
||||
"goalStateObjectiveHeading": "Mục tiêu",
|
||||
"goalStateSheetTitle": "Mục tiêu",
|
||||
"send": "Gửi tin nhắn",
|
||||
"stop": "Dừng phản hồi",
|
||||
"attachImage": "Đính kèm ảnh",
|
||||
@ -302,7 +300,8 @@
|
||||
"description": "Liệt kê các lệnh slash có sẵn."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"goalStateCloseAria": "Đóng mục tiêu"
|
||||
},
|
||||
"scrollToBottom": "Cuộn xuống cuối"
|
||||
},
|
||||
|
||||
@ -236,9 +236,7 @@
|
||||
"goalStateStrip": "目标 · {{label}}",
|
||||
"goalStateFallback": "目标",
|
||||
"goalStateExpandAria": "查看完整目标",
|
||||
"goalStateSheetTitle": "会话目标",
|
||||
"goalStateSummaryHeading": "摘要",
|
||||
"goalStateObjectiveHeading": "目标描述",
|
||||
"goalStateSheetTitle": "目标",
|
||||
"send": "发送消息",
|
||||
"stop": "停止响应",
|
||||
"attachImage": "添加图片",
|
||||
@ -322,7 +320,8 @@
|
||||
"decode_failed": "无法解码这张图片",
|
||||
"too_large": "图片太大,请换一张小一点的",
|
||||
"io": "无法读取该文件"
|
||||
}
|
||||
},
|
||||
"goalStateCloseAria": "关闭目标"
|
||||
},
|
||||
"scrollToBottom": "滚动到底部"
|
||||
},
|
||||
|
||||
@ -222,9 +222,7 @@
|
||||
"goalStateStrip": "目標 · {{label}}",
|
||||
"goalStateFallback": "目標",
|
||||
"goalStateExpandAria": "查看完整目標",
|
||||
"goalStateSheetTitle": "對話目標",
|
||||
"goalStateSummaryHeading": "摘要",
|
||||
"goalStateObjectiveHeading": "目標描述",
|
||||
"goalStateSheetTitle": "目標",
|
||||
"send": "送出訊息",
|
||||
"stop": "停止回覆",
|
||||
"attachImage": "附加圖片",
|
||||
@ -302,7 +300,8 @@
|
||||
"description": "列出可用的斜線命令。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"goalStateCloseAria": "關閉目標"
|
||||
},
|
||||
"scrollToBottom": "捲動到底部"
|
||||
},
|
||||
|
||||
@ -107,7 +107,7 @@ describe("ThreadComposer", () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("opens a bottom sheet with full thread goal when expand is clicked", async () => {
|
||||
it("opens an upward anchored goal panel with markdown content when expand is clicked", async () => {
|
||||
const longObjective =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123456789GoalTail";
|
||||
render(
|
||||
@ -124,12 +124,10 @@ describe("ThreadComposer", () => {
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Show full goal" }));
|
||||
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
const dialog = await screen.findByRole("dialog", { name: "Goal" });
|
||||
expect(dialog).toBeInTheDocument();
|
||||
expect(dialog).toHaveTextContent("Short summary for strip");
|
||||
expect(dialog).toHaveTextContent(longObjective);
|
||||
expect(dialog).toHaveTextContent("Summary");
|
||||
expect(dialog).toHaveTextContent("Objective");
|
||||
});
|
||||
|
||||
it("opens a slash command palette and inserts the selected command", () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user