mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
feat(ui): add ui.history.collapsePreviewCount to show last N turns when resuming collapsed sessions (#5848)
* feat: add ui.history.collapsePreviewCount to show last N turns on resume * chore: regenerate settings schema for collapsePreviewCount --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
This commit is contained in:
parent
7a04512bb9
commit
f3694dde67
8 changed files with 135 additions and 4 deletions
|
|
@ -116,6 +116,7 @@ Settings are organized into categories. Most settings should be placed within th
|
|||
| `ui.renderMode` | string | Default Markdown display mode. Use `"render"` for rich visual previews or `"raw"` to show source-oriented Markdown by default. Toggle during a session with `Alt/Option+M`; on macOS the terminal must send Option as Meta. See [Markdown Rendering](../features/markdown-rendering). | `"render"` |
|
||||
| `ui.showCitations` | boolean | Show citations for generated text in the chat. | `false` |
|
||||
| `ui.history.collapseOnResume` | boolean | Whether to collapse history by default when resuming a session. Can be toggled via `/history collapse-on-resume` and `/history expand-on-resume`. | `false` |
|
||||
| `ui.history.collapsePreviewCount` | number | Number of most recent user turns to keep visible when `ui.history.collapseOnResume` is enabled. `0` collapses all restored history by default; `-1` shows all restored history. | `0` |
|
||||
| `ui.compactMode` | boolean | Hide tool output and thinking for a cleaner view. Toggle with `Ctrl+O` during a session or via the Settings dialog. Tool approval prompts are never hidden, even in compact mode. The setting persists across sessions. | `false` |
|
||||
| `ui.shellOutputMaxLines` | number | Max number of shell output lines shown inline. Set to `0` to disable the cap and show full output. Hidden lines are surfaced via the `+N lines` indicator. Errors, `!`-prefix user-initiated commands, confirming tools, and focused embedded shells always show full output. | `5` |
|
||||
| `ui.enableWelcomeBack` | boolean | Show welcome back dialog when returning to a project with conversation history. When enabled, Qwen Code will automatically detect if you're returning to a project with a previously generated project summary (`.qwen/PROJECT_SUMMARY.md`) and show a dialog allowing you to continue your previous conversation or start fresh. If you choose **Start new chat session**, that choice is remembered for the current project until the project summary changes. This feature integrates with the `/summary` command and quit confirmation dialog. | `true` |
|
||||
|
|
|
|||
|
|
@ -842,6 +842,16 @@ const SETTINGS_SCHEMA = {
|
|||
'Whether to collapse history by default when resuming a session.',
|
||||
showInDialog: false,
|
||||
},
|
||||
collapsePreviewCount: {
|
||||
type: 'number',
|
||||
label: 'Collapse Preview Count',
|
||||
category: 'UI',
|
||||
requiresRestart: false,
|
||||
default: 0,
|
||||
description:
|
||||
'Number of most recent user turns to keep visible when collapsing history on resume. 0 collapses all restored history by default; -1 shows all restored history.',
|
||||
showInDialog: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
showLineNumbers: {
|
||||
|
|
|
|||
|
|
@ -629,10 +629,13 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
const rawItems = buildResumedHistoryItems(resumedSessionData, config);
|
||||
const collapseOnResume =
|
||||
settings.merged.ui?.history?.collapseOnResume ?? false;
|
||||
const collapsePreviewCount =
|
||||
settings.merged.ui?.history?.collapsePreviewCount ?? 0;
|
||||
|
||||
const historyItems = applyCollapsePolicyAndSummary(
|
||||
rawItems,
|
||||
collapseOnResume,
|
||||
collapsePreviewCount,
|
||||
);
|
||||
historyManager.loadHistory(historyItems);
|
||||
|
||||
|
|
|
|||
|
|
@ -157,9 +157,12 @@ export function useBranchCommand(
|
|||
const rawItems = buildResumedHistoryItems(resumed, config);
|
||||
const collapseOnResume =
|
||||
options.settings.merged.ui?.history?.collapseOnResume ?? false;
|
||||
const collapsePreviewCount =
|
||||
options.settings.merged.ui?.history?.collapsePreviewCount ?? 0;
|
||||
const uiHistoryItems = applyCollapsePolicyAndSummary(
|
||||
rawItems,
|
||||
collapseOnResume,
|
||||
collapsePreviewCount,
|
||||
);
|
||||
startNewSession(newSessionId);
|
||||
historyManager.clearItems();
|
||||
|
|
@ -276,6 +279,7 @@ export function useBranchCommand(
|
|||
setSessionName,
|
||||
remount,
|
||||
options.settings.merged.ui?.history?.collapseOnResume,
|
||||
options.settings.merged.ui?.history?.collapsePreviewCount,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -123,10 +123,13 @@ export function useResumeCommand(
|
|||
const rawItems = buildResumedHistoryItems(sessionData, config);
|
||||
const collapseOnResume =
|
||||
settings.merged.ui?.history?.collapseOnResume ?? false;
|
||||
const collapsePreviewCount =
|
||||
settings.merged.ui?.history?.collapsePreviewCount ?? 0;
|
||||
|
||||
const uiHistoryItems = applyCollapsePolicyAndSummary(
|
||||
rawItems,
|
||||
collapseOnResume,
|
||||
collapsePreviewCount,
|
||||
);
|
||||
|
||||
// 1. Swap core first. Matches useBranchCommand's core-before-UI
|
||||
|
|
@ -218,6 +221,7 @@ export function useResumeCommand(
|
|||
setSessionName,
|
||||
remount,
|
||||
settings.merged.ui?.history?.collapseOnResume,
|
||||
settings.merged.ui?.history?.collapsePreviewCount,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@
|
|||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
applyCollapsePolicyAndSummary,
|
||||
buildResumedHistoryItems,
|
||||
stripSuppressOnRestore,
|
||||
expandCollapsedHistory,
|
||||
} from './resumeHistoryUtils.js';
|
||||
import { ToolCallStatus } from '../types.js';
|
||||
import { MessageType, ToolCallStatus } from '../types.js';
|
||||
import type {
|
||||
AnyDeclarativeTool,
|
||||
Config,
|
||||
|
|
@ -496,6 +497,88 @@ describe('resumeHistoryUtils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('applyCollapsePolicyAndSummary', () => {
|
||||
const makeItems = (): HistoryItem[] =>
|
||||
[
|
||||
{ id: 1, type: MessageType.USER, text: 'first' },
|
||||
{ id: 2, type: MessageType.GEMINI, text: 'first response' },
|
||||
{ id: 3, type: MessageType.USER, text: 'second' },
|
||||
{ id: 4, type: MessageType.GEMINI, text: 'second response' },
|
||||
{ id: 5, type: MessageType.USER, text: 'third' },
|
||||
{ id: 6, type: MessageType.GEMINI, text: 'third response' },
|
||||
] as HistoryItem[];
|
||||
|
||||
const expectSuppressed = (item: HistoryItem) => {
|
||||
expect(item.display).toEqual(
|
||||
expect.objectContaining({ suppressOnRestore: true }),
|
||||
);
|
||||
};
|
||||
|
||||
const expectVisible = (item: HistoryItem) => {
|
||||
expect(item.display?.suppressOnRestore).toBeUndefined();
|
||||
};
|
||||
|
||||
it('suppresses all items and shows the full summary count by default', () => {
|
||||
const result = applyCollapsePolicyAndSummary(makeItems(), true);
|
||||
|
||||
expect(result).toHaveLength(7);
|
||||
result.slice(0, 6).forEach(expectSuppressed);
|
||||
expect(result[6]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: 7,
|
||||
type: MessageType.INFO,
|
||||
text: expect.stringContaining('6 messages hidden'),
|
||||
display: { kind: 'collapse-summary' },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the most recent N user turns visible and summarizes only hidden items', () => {
|
||||
const result = applyCollapsePolicyAndSummary(makeItems(), true, 2);
|
||||
|
||||
expect(result).toHaveLength(7);
|
||||
result.slice(0, 2).forEach(expectSuppressed);
|
||||
result.slice(2, 6).forEach(expectVisible);
|
||||
expect(result[6]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: 7,
|
||||
type: MessageType.INFO,
|
||||
text: expect.stringContaining('2 messages hidden'),
|
||||
display: { kind: 'collapse-summary' },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('shows all items without a summary when preview count covers all user turns', () => {
|
||||
const rawItems = makeItems();
|
||||
const result = applyCollapsePolicyAndSummary(rawItems, true, 3);
|
||||
|
||||
expect(result).toEqual(rawItems);
|
||||
expect(
|
||||
result.some((item) => item.display?.kind === 'collapse-summary'),
|
||||
).toBe(false);
|
||||
result.forEach(expectVisible);
|
||||
});
|
||||
|
||||
it('shows all items without a summary when preview count is -1', () => {
|
||||
const rawItems = makeItems();
|
||||
const result = applyCollapsePolicyAndSummary(rawItems, true, -1);
|
||||
|
||||
expect(result).toBe(rawItems);
|
||||
});
|
||||
|
||||
it('returns raw items unchanged when collapseOnResume is false', () => {
|
||||
const rawItems = makeItems();
|
||||
const result = applyCollapsePolicyAndSummary(rawItems, false, 1);
|
||||
|
||||
expect(result).toBe(rawItems);
|
||||
});
|
||||
|
||||
it('returns empty history without a summary', () => {
|
||||
expect(applyCollapsePolicyAndSummary([], true)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripSuppressOnRestore', () => {
|
||||
it('returns item unchanged when display is undefined', () => {
|
||||
const item = { id: 1, type: 'user', text: 'hello' } as HistoryItem;
|
||||
|
|
|
|||
|
|
@ -568,16 +568,37 @@ export function expandCollapsedHistory(items: HistoryItem[]): HistoryItem[] {
|
|||
export function applyCollapsePolicyAndSummary(
|
||||
rawItems: HistoryItem[],
|
||||
collapseOnResume: boolean,
|
||||
collapsePreviewCount: number = 0,
|
||||
): HistoryItem[] {
|
||||
if (!collapseOnResume) return rawItems;
|
||||
if (collapsePreviewCount === -1) return rawItems;
|
||||
|
||||
const uiHistoryItems = applyResumeDisplayPolicy(rawItems);
|
||||
let boundary = rawItems.length;
|
||||
if (collapsePreviewCount > 0) {
|
||||
let userTurnCount = 0;
|
||||
for (let i = rawItems.length - 1; i >= 0; i--) {
|
||||
if (rawItems[i].type === MessageType.USER) {
|
||||
userTurnCount++;
|
||||
if (userTurnCount === collapsePreviewCount) {
|
||||
boundary = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (userTurnCount < collapsePreviewCount) {
|
||||
boundary = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (rawItems.length > 0) {
|
||||
const hiddenItems = applyResumeDisplayPolicy(rawItems.slice(0, boundary));
|
||||
const visibleItems = rawItems.slice(boundary);
|
||||
const uiHistoryItems = [...hiddenItems, ...visibleItems];
|
||||
|
||||
if (boundary > 0) {
|
||||
const nextId = rawItems[rawItems.length - 1].id + 1;
|
||||
return [
|
||||
...uiHistoryItems,
|
||||
{ id: nextId, ...createHistoryCollapseSummaryItem(rawItems.length) },
|
||||
{ id: nextId, ...createHistoryCollapseSummaryItem(boundary) },
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -278,6 +278,11 @@
|
|||
"description": "Whether to collapse history by default when resuming a session.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"collapsePreviewCount": {
|
||||
"description": "Number of most recent user turns to keep visible when collapsing history on resume. 0 collapses all restored history by default; -1 shows all restored history.",
|
||||
"type": "number",
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue