@@ -3,72 +3,8 @@ import { UrlDependency } from "$lib/types/UrlDependency";
33import { redirect } from "@sveltejs/kit" ;
44import { base } from "$app/paths" ;
55import { browser } from "$app/environment" ;
6- import { safeInvalidate } from "$lib/utils/safeInvalidate" ;
76import type { PageLoad } from "./$types" ;
8- import {
9- CONVERSATION_CACHE_FRESH_MS ,
10- conversationFingerprint ,
11- getCachedConversation ,
12- invalidateCachedConversation ,
13- setCachedConversation ,
14- type ConversationData ,
15- } from "$lib/utils/conversationCache" ;
16-
17- // Id of the conversation the previous load() call served. A repeat load for
18- // the same id means invalidate(UrlDependency.Conversation) ran (stream
19- // finished, model switch, background poller) and fresh data is expected; only
20- // a load for a different id is a navigation that may be served from cache.
21- let lastLoadedId : string | undefined ;
22-
23- async function fetchConversation (
24- client : ReturnType < typeof useAPIClient > ,
25- id : string ,
26- fromShare : string | undefined
27- ) : Promise < ConversationData > {
28- return ( await client
29- . conversations ( { id } )
30- . get ( { query : { fromShare } } )
31- . then ( handleResponse ) ) as ConversationData ;
32- }
33-
34- // Serve-stale-then-revalidate: fetch fresh data off the critical path and, if
35- // it differs from what the cache (and thus the page) currently shows, refresh
36- // the cache and re-run the load — which then hits the updated, fresh entry.
37- function revalidateInBackground (
38- client : ReturnType < typeof useAPIClient > ,
39- id : string ,
40- fromShare : string | undefined ,
41- served : ConversationData
42- ) {
43- void client
44- . conversations ( { id } )
45- . get ( { query : { fromShare } } )
46- . then ( ( response ) => {
47- // The conversation is gone or this session can no longer read it
48- // (deleted in another tab, revoked access, expired session): drop the
49- // entry and re-run the load so the page takes its real error path
50- // (redirect home) instead of keeping the stale view on screen.
51- if ( response . status === 401 || response . status === 403 || response . status === 404 ) {
52- invalidateCachedConversation ( id ) ;
53- return safeInvalidate ( UrlDependency . Conversation ) ;
54- }
55- // Throws on remaining errors (5xx etc.), handled as transient below.
56- const fresh = handleResponse ( response ) as ConversationData ;
57- if ( conversationFingerprint ( fresh ) === conversationFingerprint ( served ) ) return ;
58- setCachedConversation ( id , fresh ) ;
59- // safeInvalidate, not invalidate: a raw invalidate() fired from this
60- // background task can cancel an in-flight navigation the user just
61- // started (see safeInvalidate.ts).
62- return safeInvalidate ( UrlDependency . Conversation ) ;
63- } )
64- . catch ( ( ) => {
65- // Transient failure (network blip, server error): only drop the cache
66- // entry; the next real navigation refetches and takes the genuine
67- // error path. Forcing a reload here would bounce the user to the
68- // homepage on a mere network hiccup.
69- invalidateCachedConversation ( id ) ;
70- } ) ;
71- }
7+ import { takePendingConversation , type ConversationData } from "$lib/utils/pendingConversation" ;
728
739export const load : PageLoad = async ( { params, depends, fetch, url, parent } ) => {
7410 depends ( UrlDependency . Conversation ) ;
@@ -102,28 +38,24 @@ export const load: PageLoad = async ({ params, depends, fetch, url, parent }) =>
10238 }
10339
10440 const fromShare = url . searchParams . get ( "fromShare" ) ?? undefined ;
105- const isNavigation = lastLoadedId !== params . id ;
106- lastLoadedId = params . id ;
10741
108- // Share views bypass the cache entirely: their payload depends on the
109- // fromShare parameter, not just the conversation id.
110- if ( browser && isNavigation && ! fromShare ) {
111- const cached = getCachedConversation ( params . id ) ;
112- if ( cached ) {
113- if ( Date . now ( ) - cached . fetchedAt > CONVERSATION_CACHE_FRESH_MS ) {
114- revalidateInBackground ( client , params . id , fromShare , cached . data ) ;
115- }
116- return cached . data ;
42+ // A conversation created by this tab hands its payload over via a one-shot
43+ // seed (see pendingConversation.ts), consumed here so the first load after
44+ // create skips the network round trip. Every other load, including every
45+ // invalidate() re-run, fetches fresh data.
46+ if ( browser && ! fromShare ) {
47+ const seeded = takePendingConversation ( params . id ) ;
48+ if ( seeded ) {
49+ return seeded ;
11750 }
11851 }
11952
12053 // Load conversation (works for both owned and shared conversations)
12154 try {
122- const data = await fetchConversation ( client , params . id , fromShare ) ;
123- if ( browser && ! fromShare ) {
124- setCachedConversation ( params . id , data ) ;
125- }
126- return data ;
55+ return ( await client
56+ . conversations ( { id : params . id } )
57+ . get ( { query : { fromShare } } )
58+ . then ( handleResponse ) ) as ConversationData ;
12759 } catch {
12860 redirect ( 302 , `${ base } /` ) ;
12961 }
0 commit comments