feat(webui): polish agent output and app discovery

This commit is contained in:
Xubin Ren
2026-07-22 22:42:31 +08:00
parent b189a37648
commit aa8387fb4d
87 changed files with 6225 additions and 2379 deletions
+20 -21
View File
@@ -511,13 +511,13 @@ describe("MessageBubble", () => {
const video = screen.getByLabelText(/video attachment/i);
expect(video.tagName).toBe("VIDEO");
expect(video).toHaveAttribute("src", "/api/media/sig/payload");
expect(video).toHaveAttribute("preload", "auto");
expect(video).toHaveAttribute("preload", "metadata");
expect(container.querySelector("video[controls]")).toBeInTheDocument();
expect(screen.queryByText("Preview")).not.toBeInTheDocument();
expect(screen.queryByText("Code")).not.toBeInTheDocument();
});
it("auto-expands the reasoning trace while streaming with a shimmer header", () => {
it("renders streaming reasoning as one compact activity line", () => {
const message: UIMessage = {
id: "a-reasoning-streaming",
role: "assistant",
@@ -529,15 +529,19 @@ describe("MessageBubble", () => {
const { container } = render(<MessageBubble message={message} />);
expect(screen.getByText("Thinking…")).toBeInTheDocument();
expect(screen.getByText(/Step 1: parse intent\./)).toBeInTheDocument();
const preview = screen.getByText("Step 1: parse intent. Step 2: compute.");
expect(preview).toBeInTheDocument();
expect(container.querySelector(".reasoning-sheen-stripe")).not.toBeInTheDocument();
expect(screen.getByText("Thinking…")).toHaveClass("streaming-text-sheen");
expect(screen.getByText("Thinking…")).toHaveAttribute("data-sheen-text", "Thinking…");
expect(screen.getByRole("button", { name: /thinking/i }).parentElement).not.toHaveClass("mb-2");
expect(preview).toHaveClass("streaming-text-sheen");
expect(preview).toHaveAttribute(
"data-sheen-text",
"Step 1: parse intent. Step 2: compute.",
);
expect(screen.queryByText("Thinking…")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /thinking/i })).not.toBeInTheDocument();
});
it("collapses the reasoning section by default once streaming ends", () => {
it("keeps completed reasoning on one line above the answer", () => {
const message: UIMessage = {
id: "a-reasoning-done",
role: "assistant",
@@ -549,17 +553,15 @@ describe("MessageBubble", () => {
render(<MessageBubble message={message} />);
expect(screen.getByText("Thinking")).toBeInTheDocument();
const preview = screen.getByText("hidden until expanded");
expect(preview).toBeInTheDocument();
expect(screen.getByText("The answer is 42.")).toBeInTheDocument();
expect(screen.queryByText("hidden until expanded")).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: /thinking/i }).parentElement).toHaveClass("mb-2");
fireEvent.click(screen.getByRole("button", { name: /thinking/i }));
expect(screen.getByText("hidden until expanded")).toBeInTheDocument();
expect(preview.closest('[data-testid="activity-step"]')).toHaveClass("mb-2");
expect(screen.queryByText("Thinking")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /thinking/i })).not.toBeInTheDocument();
});
it("renders reasoning body as markdown so headings are not left as raw ###", async () => {
await import("@/components/MarkdownTextRenderer");
it("compacts reasoning markdown into plain single-line text", () => {
const message: UIMessage = {
id: "a-reasoning-md",
role: "assistant",
@@ -570,13 +572,10 @@ describe("MessageBubble", () => {
};
const { container } = render(<MessageBubble message={message} />);
fireEvent.click(screen.getByRole("button", { name: /thinking/i }));
await waitFor(() => {
expect(container.querySelector("h3")?.textContent).toBe("Section title");
});
expect(screen.getByText("Section title Body line.")).toBeInTheDocument();
expect(container.textContent).not.toContain("###");
expect(screen.getByText("Body line.")).toBeInTheDocument();
expect(container.querySelector("h3")).not.toBeInTheDocument();
});
it("renders inline file paths as compact file references", async () => {