Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions src/components/message/chat-process-collapse.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { useState } from "react"
import { act, fireEvent, render, screen } from "@testing-library/react"
import { NextIntlClientProvider } from "next-intl"
import { afterEach, describe, expect, it } from "vitest"

import enMessages from "@/i18n/messages/en.json"
import { EMPTY_TOOL_APPROVALS } from "@/lib/activity/approval"
import type { AdaptedContentPart } from "@/lib/adapters/ai-elements-adapter"
import { ActivityLiveProvider } from "./activity/activity-live-context"
import { CompletedTurnContent } from "./completed-turn-content"

const KEY = "settings:chat-process-collapse:v1"
function setMode(mode: string) {
localStorage.setItem(KEY, mode)
window.dispatchEvent(new Event("codeg:chat-process-collapse-changed"))
}

function Turn({
completed,
failed = false,
waiting = false,
}: {
completed: boolean
failed?: boolean
waiting?: boolean
}) {
const [open, setOpen] = useState(true)
const [parts] = useState<AdaptedContentPart[]>([
{ type: "text", text: "Inspecting source files" },
{
type: "tool-call",
toolCallId: "collapse-test",
toolName: "Read",
input: '{"file_path":"src/app.tsx"}',
state: failed ? "output-error" : "output-available",
output: "source",
},
{ type: "text", text: "Final answer stays visible" },
])
return (
<NextIntlClientProvider locale="en" messages={enMessages}>
<ActivityLiveProvider
value={{
awaitingUser: waiting,
awaitingToolCallId: waiting ? "collapse-test" : null,
approvals: EMPTY_TOOL_APPROVALS,
}}
>
<CompletedTurnContent
parts={parts}
completed={completed}
currentRound
roundOpen={open}
onRoundOpenChange={setOpen}
/>
</ActivityLiveProvider>
</NextIntlClientProvider>
)
}

const toggle = () =>
screen.getByRole("button", { name: /^(Working|Activity)$/ })

afterEach(() => {
localStorage.removeItem(KEY)
window.dispatchEvent(new Event("codeg:chat-process-collapse-changed"))
})

describe("聊天过程折叠策略", () => {
it("默认保留当前轮次的展开行为", () => {
render(<Turn completed />)
expect(toggle()).toHaveAttribute("aria-expanded", "true")
})

it("执行结束后折叠,并保留最终回答", () => {
setMode("after-completion")
const view = render(<Turn completed={false} />)
expect(toggle()).toHaveAttribute("aria-expanded", "true")
view.rerender(<Turn completed />)
expect(toggle()).toHaveAttribute("aria-expanded", "false")
expect(screen.getByText("Final answer stays visible")).toBeVisible()
fireEvent.click(toggle())
expect(toggle()).toHaveAttribute("aria-expanded", "true")
view.rerender(<Turn completed />)
expect(toggle()).toHaveAttribute("aria-expanded", "true")
})

it("始终折叠模式在执行中也默认收起,仍可手动展开", () => {
setMode("always")
render(<Turn completed={false} />)
expect(toggle()).toHaveAttribute("aria-expanded", "false")
fireEvent.click(toggle())
expect(toggle()).toHaveAttribute("aria-expanded", "true")
})

it("已有会话立即响应设置切换", () => {
render(<Turn completed />)
act(() => setMode("always"))
expect(toggle()).toHaveAttribute("aria-expanded", "false")
act(() => setMode("current"))
expect(toggle()).toHaveAttribute("aria-expanded", "true")
})

it("响应其他窗口的设置变更", () => {
render(<Turn completed />)
act(() => {
localStorage.setItem(KEY, "always")
window.dispatchEvent(new StorageEvent("storage", { key: KEY }))
})
expect(toggle()).toHaveAttribute("aria-expanded", "false")
})

it("始终折叠时仍显示错误", () => {
setMode("always")
render(<Turn completed failed />)
expect(screen.getByText("Inspecting source files")).toBeVisible()
})

it("始终折叠时仍显示待用户确认的过程", () => {
setMode("always")
render(<Turn completed={false} waiting />)
expect(screen.getByText("Inspecting source files")).toBeVisible()
})

it("已有历史会话可展开,虚拟列表重新挂载后保留选择", () => {
setMode("always")
const parts: AdaptedContentPart[] = [
{ type: "reasoning", content: "Checking history", isStreaming: false },
{ type: "text", text: "Historical answer" },
]
const ui = (
<NextIntlClientProvider locale="en" messages={enMessages}>
<CompletedTurnContent parts={parts} completed />
</NextIntlClientProvider>
)
const view = render(ui)
expect(toggle()).toHaveAttribute("aria-expanded", "false")
fireEvent.click(toggle())
view.unmount()
render(ui)
expect(toggle()).toHaveAttribute("aria-expanded", "true")
expect(screen.getByText("Historical answer")).toBeVisible()
})

it("不会折叠唯一的工具结果或丢失最终输出", () => {
setMode("always")
render(
<NextIntlClientProvider locale="en" messages={enMessages}>
<CompletedTurnContent
parts={[
{ type: "text", text: "Important tool result" },
{
type: "tool-call",
toolCallId: "no-final-answer",
toolName: "Read",
input: "{}",
state: "output-available",
output: "source",
},
]}
completed
/>
</NextIntlClientProvider>
)
expect(screen.getByText("Important tool result")).toBeVisible()
expect(screen.queryByRole("button", { name: "Activity" })).toBeNull()
})
})
58 changes: 55 additions & 3 deletions src/components/message/completed-turn-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
} from "@/lib/adapters/ai-elements-adapter"
import { formatElapsedLabel } from "@/lib/format-elapsed"
import { cn } from "@/lib/utils"
import {
useChatProcessCollapse,
type ChatProcessCollapseMode,
} from "@/lib/chat-process-collapse"
import {
Collapsible,
CollapsibleContent,
Expand Down Expand Up @@ -74,6 +78,16 @@
{ epoch: number; open: boolean }
>()

interface ProcessFoldChoice {
mode: ChatProcessCollapseMode
epoch: number
settled: boolean
open?: boolean
}

// 与现有手动折叠相同,保留虚拟列表卸载前的选择,并随 parts 回收。
const processFold = new WeakMap<AdaptedContentPart[], ProcessFoldChoice>()

export const CompletedTurnContent = memo(function CompletedTurnContent({
parts,
durationMs,
Expand Down Expand Up @@ -103,6 +117,7 @@
pending?: boolean
}) {
const tElapsed = useTranslations("Folder.chat.liveTurnStats")
const collapseMode = useChatProcessCollapse()
const split = useMemo(() => splitAssistantTurnParts(parts), [parts])

const [localOpen, setLocalOpen] = useState(() => {
Expand Down Expand Up @@ -135,18 +150,52 @@
}
}

const open = currentRound ? roundOpen : localOpen
// 结束后折叠模式在结束时重置一次;之后用户仍可重新展开。
const settled = collapseMode === "after-completion" && completed
const [processChoice, setProcessChoice] = useState<ProcessFoldChoice>(
() =>
processFold.get(parts) ?? {
mode: collapseMode,
epoch: foldEpoch,
settled,
}
)
const choiceMatches =
processChoice.mode === collapseMode &&
processChoice.epoch === foldEpoch &&
processChoice.settled === settled
if (!choiceMatches) {
setProcessChoice({ mode: collapseMode, epoch: foldEpoch, settled })
}
const open =
collapseMode === "current"
? currentRound
? roundOpen
: localOpen
: ((choiceMatches ? processChoice.open : undefined) ??
(collapseMode === "after-completion" && !completed))

const handleOpenChange = useCallback(
(next: boolean) => {
if (collapseMode !== "current") {
const choice = {
mode: collapseMode,
epoch: foldEpoch,
settled,
open: next,
}
processFold.set(parts, choice)
setProcessChoice(choice)
return
}
if (currentRound) {
onRoundOpenChange?.(next)
return
}
manualFold.set(parts, { epoch: foldEpoch, open: next })
setLocalOpen(next)
},
[currentRound, foldEpoch, onRoundOpenChange, parts]
[collapseMode, settled, currentRound, foldEpoch, onRoundOpenChange, parts]
)

const elapsed =
Expand All @@ -165,11 +214,11 @@
// has not been written yet, so applying it would withhold the toggle for the
// whole stream and hand it over one beat before the turn ends. Folding a live
// reply is then an explicit choice; the round settling re-applies the rule.
const foldable =
const canFold =
split.progress.length > 0 && (!completed || hasVisibleAnswer(split.answer))
// What is drawn: a reply that cannot fold shows its steps whatever `open`
// says.
const bodyOpen = foldable ? open : true

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/completed-turn-content.test.tsx > CompletedTurnContent with nothing left to show > stays expanded when the reply ends on progress

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 不会折叠唯一的工具结果或丢失最终输出

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 已有历史会话可展开,虚拟列表重新挂载后保留选择

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 始终折叠时仍显示待用户确认的过程

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 始终折叠时仍显示错误

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 响应其他窗口的设置变更

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 已有会话立即响应设置切换

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 始终折叠模式在执行中也默认收起,仍可手动展开

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 执行结束后折叠,并保留最终回答

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 221 in src/components/message/completed-turn-content.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint + vitest + build)

src/components/message/chat-process-collapse.test.tsx > 聊天过程折叠策略 > 默认保留当前轮次的展开行为

ReferenceError: Cannot access 'foldable' before initialization ❯ CompletedTurnContent src/components/message/completed-turn-content.tsx:221:20 ❯ Object.react_stack_bottom_frame node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ updateSimpleMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9830:14 ❯ updateMemoComponent node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:9763:13 ❯ beginWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:12204:18 ❯ runWithFiberInDEV node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

// The unfold animation belongs to a real closed→open TOGGLE, never to a mount
// that starts open. This component remounts while staying open: the
Expand Down Expand Up @@ -248,6 +297,9 @@
// reply with its steps folded ends on the turning mark under whatever does
// end it instead.
const { awaitingUser } = useActivityLive()
// 自动折叠不能遮住失败或等待用户处理的交互。
const foldable =
canFold && (collapseMode === "current" || (!failureKey && !awaitingUser))
const working = isStreaming && !awaitingUser
const footShown =
bodyOpen && (working || (foldable && activity.items.length > 0))
Expand Down
25 changes: 25 additions & 0 deletions src/components/settings/chat-process-settings.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react"
import { NextIntlClientProvider } from "next-intl"
import { afterEach, expect, it } from "vitest"

import zhMessages from "@/i18n/messages/zh-CN.json"
import { getChatProcessCollapse } from "@/lib/chat-process-collapse"
import { ChatProcessSettingsSection } from "./chat-process-settings"

afterEach(() => localStorage.removeItem("settings:chat-process-collapse:v1"))

it("在设置中提供三个模式并持久化用户选择", async () => {
render(
<NextIntlClientProvider locale="zh-CN" messages={zhMessages}>
<ChatProcessSettingsSection />
</NextIntlClientProvider>
)
expect(
screen.getByRole("combobox", { name: "聊天过程详情" })
).toHaveTextContent("执行过程全程展开")
fireEvent.click(screen.getByRole("combobox"))
expect(screen.getAllByRole("option")).toHaveLength(3)
fireEvent.click(screen.getByRole("option", { name: "执行结束后折叠" }))
await waitFor(() => expect(getChatProcessCollapse()).toBe("after-completion"))
expect(screen.getByRole("combobox")).toHaveTextContent("执行结束后折叠")
})
52 changes: 52 additions & 0 deletions src/components/settings/chat-process-settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use client"

import { MessageSquareText } from "lucide-react"
import { useTranslations } from "next-intl"

import { SettingsSection } from "@/components/shared/settings-section"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
CHAT_PROCESS_COLLAPSE_MODES,
setChatProcessCollapse,
useChatProcessCollapse,
type ChatProcessCollapseMode,
} from "@/lib/chat-process-collapse"

export function ChatProcessSettingsSection() {
const t = useTranslations("ChatProcessSettings")
const mode = useChatProcessCollapse()

return (
<SettingsSection
icon={MessageSquareText}
title={t("title")}
description={t("description")}
htmlFor="chat-process-collapse"
control={
<Select
value={mode}
onValueChange={(value) =>
setChatProcessCollapse(value as ChatProcessCollapseMode)
}
>
<SelectTrigger id="chat-process-collapse" className="w-52">
<SelectValue />
</SelectTrigger>
<SelectContent>
{CHAT_PROCESS_COLLAPSE_MODES.map((value) => (
<SelectItem key={value} value={value}>
{t(value)}
</SelectItem>
))}
</SelectContent>
</Select>
}
/>
)
}
3 changes: 3 additions & 0 deletions src/components/settings/general-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { DesktopNotificationSettingsSection } from "@/components/settings/deskto
import { MentionPanelSettingsSection } from "@/components/settings/mention-panel-settings"
import { NotificationSoundSettingsSection } from "@/components/settings/notification-sound-settings"
import { WindowAttentionSettingsSection } from "@/components/settings/window-attention-settings"
import { ChatProcessSettingsSection } from "@/components/settings/chat-process-settings"

const TERMINAL_SHELL_OPTION_SYSTEM = "system"
const TERMINAL_SHELL_OPTION_CUSTOM = "custom"
Expand Down Expand Up @@ -515,6 +516,8 @@ export function GeneralSettings() {
</SettingsSection>
)}

<ChatProcessSettingsSection />

<MentionPanelSettingsSection />

<CloseBehaviorSettingsSection />
Expand Down
7 changes: 7 additions & 0 deletions src/i18n/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -7692,5 +7692,12 @@
"probeFailed": "تعذّر على المتصفح المدمج التأكد من أنه يصل إلى المضيف البعيد عبر النفق. حاول مرة أخرى بعد قليل."
}
}
},
"ChatProcessSettings": {
"title": "تفاصيل عملية الدردشة",
"description": "حدد وقت طي المهارات والأوامر واستدعاءات الأدوات. تظل الإجابات النهائية ظاهرة ويمكن توسيع التفاصيل يدوياً. تظل الأخطاء والتفاعلات المعلقة ظاهرة.",
"current": "الإبقاء على السلوك الحالي",
"after-completion": "الطي بعد الاكتمال",
"always": "الطي دائماً بشكل افتراضي"
}
}
Loading
Loading