fix(web): show file summary after task completion (#143)
Co-authored-by: Youhai <185432073+Youhai020616@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
7 files changed
+108
-51
No files matched your search
@@ -215,6 +215,13 @@ function headerStats(
|
||||
*/
|
||||
export const DRAFT_SESSION_ID = "new";
|
||||
|
||||
/**
|
||||
* Server-enforced ceiling on paths per files/stat call (STAT_MAX_PATHS in the sessions routes,
|
||||
* which 400s above it). A Task-level summary aggregates candidates across the whole Task and can
|
||||
* exceed it, so cache misses are checked in chunks of this size.
|
||||
*/
|
||||
const STAT_PATHS_PER_REQUEST = 100;
|
||||
|
||||
export function ChatPage() {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams<{ sessionId?: string }>();
|
||||
@@ -451,10 +458,10 @@ export function ChatPage() {
|
||||
}
|
||||
}, [stream.taskState, reloadSessions, reloadAgents]);
|
||||
|
||||
// Existence cache for message file cards (session-level): normalized relative path -> whether
|
||||
// it exists; while a lookup is in flight, the cache shares a single Promise, so a batch of
|
||||
// concurrent mounts only issues one files/stat call.
|
||||
const statCacheRef = useRef(new Map<string, boolean | Promise<boolean>>());
|
||||
// Positive-only existence cache for file summary cards (session-level): normalized relative
|
||||
// path -> true, or the shared in-flight lookup. Missing files aren't retained — a later Task may
|
||||
// create the same path, so its summary must re-check instead of inheriting stale false state.
|
||||
const statCacheRef = useRef(new Map<string, true | Promise<boolean>>());
|
||||
|
||||
// Session switch: resets the cost, the file-card existence cache, and the per-turn thinking
|
||||
// level (it's per-session UI state), avoiding stale data from the previous Session (Files
|
||||
@@ -466,34 +473,30 @@ export function ChatPage() {
|
||||
statCacheRef.current = new Map();
|
||||
}, [routeSessionId]);
|
||||
|
||||
// Batched existence check (message file cards): merges only the cache-miss paths into a single
|
||||
// files/stat call, and the result lands in the session-level cache — during streaming, the
|
||||
// candidate set is re-checked on every change, and the cache ensures only new paths trigger a
|
||||
// request. On request failure, the placeholder is cleared (don't permanently cache a "couldn't
|
||||
// find" as "doesn't exist"), returned as not-existing this time, and re-checked on the next mount.
|
||||
// Batched existence check for file summaries: cache stable positive results and share in-flight
|
||||
// requests, but never retain a negative result. Each pending lookup mutates the cache only while
|
||||
// it is still the current entry, so an old request can't delete or overwrite a newer one.
|
||||
const statFiles = useCallback(
|
||||
async (paths: string[]): Promise<ReadonlySet<string>> => {
|
||||
const sessionId = selected?.sessionId ?? null;
|
||||
const cache = statCacheRef.current;
|
||||
const misses = sessionId === null ? [] : paths.filter((p) => !cache.has(p));
|
||||
if (sessionId !== null && misses.length > 0) {
|
||||
const batch = api
|
||||
.statSessionFiles(sessionId, misses)
|
||||
.then((res) => new Set(res.existing))
|
||||
.catch(() => null);
|
||||
for (const p of misses) {
|
||||
cache.set(
|
||||
p,
|
||||
batch.then((existing) => {
|
||||
if (existing === null) {
|
||||
cache.delete(p);
|
||||
return false;
|
||||
}
|
||||
const exists = existing.has(p);
|
||||
cache.set(p, exists);
|
||||
return exists;
|
||||
}),
|
||||
);
|
||||
for (let i = 0; i < misses.length; i += STAT_PATHS_PER_REQUEST) {
|
||||
const chunk = misses.slice(i, i + STAT_PATHS_PER_REQUEST);
|
||||
const batch = api
|
||||
.statSessionFiles(sessionId, chunk)
|
||||
.then((res) => new Set(res.existing))
|
||||
.catch(() => null);
|
||||
for (const p of chunk) {
|
||||
const pending = batch.then((existing) => existing?.has(p) ?? false);
|
||||
cache.set(p, pending);
|
||||
void pending.then((exists) => {
|
||||
if (cache.get(p) !== pending) return;
|
||||
if (exists) cache.set(p, true);
|
||||
else cache.delete(p);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = new Set<string>();
|
||||
|
||||
Reference in new issue
Block a user