fix: restore coding history and quota feedback

This commit is contained in:
inman
2026-09-01 11:46:04 +08:00
parent 38f85f6b5e
commit d523b72cf8
14 changed files with 480 additions and 49 deletions

View File

@@ -1265,6 +1265,19 @@ function processItemIsFailed(item: ProcessItem): boolean {
return item.kind === 'notice' && item.node.level === 'error';
}
function persistedProviderFailureMessage(items: ProcessItem[]): string | undefined {
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (item?.kind === 'notice'
&& item.node.level === 'error'
&& (item.node.code === 'CODING_PROVIDER_AUTH_REQUIRED'
|| item.node.code === 'CODING_PROVIDER_QUOTA_EXHAUSTED')) {
return item.node.message;
}
}
return undefined;
}
function processGroupIsFailed(
items: ProcessItem[],
run: ConversationRunState,
@@ -1326,10 +1339,13 @@ const ProcessGroup = memo(function ProcessGroup({
? runningDurationLabel(run, now)
: settledDurationLabel(run)
: null;
const failureMessage = failed
? (latest ? run.error?.message : undefined) ?? persistedProviderFailureMessage(items)
: undefined;
const summary = active
? `处理中${duration ? ` ${duration}` : ''}`
: failed
? `处理失败${duration ? ` · ${duration}` : ''}`
? `${failureMessage ?? '处理失败'}${duration ? ` · ${duration}` : ''}`
: `已处理${duration ? ` ${duration}` : ''}`;
return (
@@ -1491,11 +1507,33 @@ export const CodingConversationTimeline = memo(function CodingConversationTimeli
const nodes = useCodingConversationStore(selectNodes);
const run = useCodingConversationStore(selectRun);
const [visibleLimit, setVisibleLimit] = useState(INITIAL_WINDOW);
const scrollRef = useRef<HTMLDivElement | null>(null);
const stickToBottomRef = useRef(true);
const prependAnchorRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null);
const windowStart = Math.max(0, nodes.length - visibleLimit);
const visibleNodes = nodes.slice(windowStart);
const turns = useMemo(() => timelineTurns(visibleNodes), [visibleNodes]);
const scrollRef = useRef<HTMLDivElement | null>(null);
const stickToBottomRef = useRef(true);
const loadEarlier = useCallback(() => {
if (windowStart === 0 || prependAnchorRef.current) return;
const element = scrollRef.current;
if (!element) return;
prependAnchorRef.current = {
scrollHeight: element.scrollHeight,
scrollTop: element.scrollTop,
};
stickToBottomRef.current = false;
setVisibleLimit((current) => Math.min(nodes.length, current + WINDOW_STEP));
}, [nodes.length, windowStart]);
useLayoutEffect(() => {
const anchor = prependAnchorRef.current;
if (!anchor) return;
prependAnchorRef.current = null;
const element = scrollRef.current;
if (!element) return;
element.scrollTop = anchor.scrollTop + (element.scrollHeight - anchor.scrollHeight);
}, [visibleLimit]);
useLayoutEffect(() => {
if (!stickToBottomRef.current) return;
@@ -1513,6 +1551,7 @@ export const CodingConversationTimeline = memo(function CodingConversationTimeli
onScroll={(event) => {
const element = event.currentTarget;
stickToBottomRef.current = element.scrollHeight - element.scrollTop - element.clientHeight < 64;
if (element.scrollTop <= 48) loadEarlier();
}}
>
<div className="mx-auto flex w-full max-w-[50rem] flex-col gap-8">
@@ -1521,7 +1560,7 @@ export const CodingConversationTimeline = memo(function CodingConversationTimeli
type="button"
variant="ghost"
className="mx-auto min-h-9 rounded-lg px-4 text-xs transition-transform duration-150 ease-out active:scale-[0.96]"
onClick={() => setVisibleLimit((current) => current + WINDOW_STEP)}
onClick={loadEarlier}
>
加载更早内容
</Button>