mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 01:14:52 +00:00
* fix(app): make a folded turn's summary line its own chat row The summary line of a collapsed turn was drawn inside its prompt's row, so the row gap landed below the summary and the prompt touched it. Turn collapse now emits a `turn_summary` row between the host and the reply, and one spacing rule covers every prompt: 16 above whatever answers it (reply, summary row or tool row). The summary row spaces like the work rows it stands for, 4 above the reply and flush under a tool host. This removes the per-row summary lookup, the host revision tracking and the fragments in the view. A fold reuses a turn's previous summary row object while its work and open state are unchanged, so mounted rows do not re-render. * fix(app): keep the turn summary row at its host's timeline position The chat outline resolves the active prompt from the timeline position of the row under the reading line. The summary row had none, so the outline lost its mark while the row sat there. The row now carries its host's position and is replaced when that position lands. * fix(app): keep the reader on a folding turn's host, not its summary row When an older page completes the oldest loaded turn and it folds, the reading position moves from the hidden rows to the turn's host row, as on main, so the prompt and its summary row both land in view. The pagination helper now finds the summary line in the row under the prompt.
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
import type { StreamItem } from "@/types/stream";
|
|
import { estimateAssistantMessageHeightFromCache } from "@/utils/assistant-message-height-estimate";
|
|
import {
|
|
DEFAULT_MOUNTED_RECENT_STREAM_ITEMS,
|
|
findMountedWindowStart,
|
|
getMountedRecentStreamItems,
|
|
} from "./history-window";
|
|
|
|
export const DEFAULT_WEB_PARTIAL_VIRTUALIZATION_THRESHOLD = 100;
|
|
export const DEFAULT_WEB_MOUNTED_RECENT_STREAM_ITEMS = DEFAULT_MOUNTED_RECENT_STREAM_ITEMS;
|
|
const COLLAPSED_TOOL_SEQUENCE_ROW_HEIGHT_ESTIMATE = 40;
|
|
|
|
type BottomAnchorE2ETestGlobals = typeof globalThis & {
|
|
__PASEO_E2E_WEB_PARTIAL_VIRTUALIZATION_THRESHOLD?: unknown;
|
|
};
|
|
|
|
function readPositiveIntegerOverride(value: unknown): number | null {
|
|
if (!Number.isFinite(value)) {
|
|
return null;
|
|
}
|
|
const normalized = Math.trunc(value as number);
|
|
return normalized > 0 ? normalized : null;
|
|
}
|
|
|
|
export function getWebPartialVirtualizationThreshold(): number {
|
|
const override = readPositiveIntegerOverride(
|
|
(globalThis as BottomAnchorE2ETestGlobals).__PASEO_E2E_WEB_PARTIAL_VIRTUALIZATION_THRESHOLD,
|
|
);
|
|
return override ?? DEFAULT_WEB_PARTIAL_VIRTUALIZATION_THRESHOLD;
|
|
}
|
|
|
|
export function getWebMountedRecentStreamItems(): number {
|
|
return getMountedRecentStreamItems();
|
|
}
|
|
|
|
export interface IndexedStreamItem {
|
|
item: StreamItem;
|
|
index: number;
|
|
}
|
|
|
|
export interface WebVirtualizedHistoryWindow {
|
|
virtualizedEntries: IndexedStreamItem[];
|
|
mountedEntries: IndexedStreamItem[];
|
|
}
|
|
|
|
interface StreamItemHeightEstimateInput {
|
|
item: StreamItem;
|
|
contentMaxWidth: number;
|
|
}
|
|
|
|
export function estimateStreamItemHeight({
|
|
item,
|
|
contentMaxWidth,
|
|
}: StreamItemHeightEstimateInput): number {
|
|
switch (item.kind) {
|
|
case "user_message":
|
|
return item.images && item.images.length > 0 ? 220 : 96;
|
|
case "assistant_message":
|
|
return (
|
|
estimateAssistantMessageHeightFromCache({
|
|
markdown: item.text,
|
|
contentMaxWidth,
|
|
}) ?? 220
|
|
);
|
|
case "tool_call":
|
|
return COLLAPSED_TOOL_SEQUENCE_ROW_HEIGHT_ESTIMATE;
|
|
case "thought":
|
|
case "turn_summary":
|
|
return COLLAPSED_TOOL_SEQUENCE_ROW_HEIGHT_ESTIMATE;
|
|
case "todo_list":
|
|
return 144;
|
|
case "notification":
|
|
return 88;
|
|
case "compaction":
|
|
return 72;
|
|
default:
|
|
return 120;
|
|
}
|
|
}
|
|
|
|
export { findMountedWindowStart };
|
|
|
|
export function splitWebVirtualizedHistory(input: {
|
|
entries: IndexedStreamItem[];
|
|
minMountedCount: number;
|
|
}): WebVirtualizedHistoryWindow {
|
|
const startIndex = findMountedWindowStart({
|
|
items: input.entries.map((entry) => entry.item),
|
|
minMountedCount: input.minMountedCount,
|
|
});
|
|
return {
|
|
virtualizedEntries: input.entries.slice(0, startIndex),
|
|
mountedEntries: input.entries.slice(startIndex),
|
|
};
|
|
}
|