@@ -172,6 +172,7 @@ import {
172172 canStartThreadTurn ,
173173 collectUserMessageBlobPreviewUrls ,
174174 createLocalDispatchSnapshot ,
175+ createThreadPlanCatalogSelector ,
175176 deriveComposerSendState ,
176177 hasServerAcknowledgedLocalDispatch ,
177178 LAST_INVOKED_SCRIPT_BY_PROJECT_KEY ,
@@ -186,6 +187,7 @@ import {
186187 revokeBlobPreviewUrl ,
187188 revokeUserMessagePreviewUrls ,
188189 shouldWriteThreadErrorToCurrentServerThread ,
190+ type ThreadPlanCatalogEntry ,
189191 waitForStartedServerThread ,
190192} from "./ChatView.logic" ;
191193import { useLocalStorage } from "~/hooks/useLocalStorage" ;
@@ -212,15 +214,12 @@ import { useChatFindHighlight } from "./chat/useChatFindHighlight";
212214const IMAGE_ONLY_BOOTSTRAP_PROMPT =
213215 "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]" ;
214216const EMPTY_ACTIVITIES : OrchestrationThreadActivity [ ] = [ ] ;
215- const EMPTY_PROPOSED_PLANS : Thread [ "proposedPlans" ] = [ ] ;
216217const EMPTY_PROVIDERS : ServerProvider [ ] = [ ] ;
217218const EMPTY_PENDING_USER_INPUT_ANSWERS : Record < string , PendingUserInputDraftAnswer > = { } ;
218219const EMPTY_CHAT_FIND_ROWS : ChatFindRow [ ] = [ ] ;
219220const EMPTY_CHAT_FIND_MATCHES : ChatFindMatch [ ] = [ ] ;
220221const TIMELINE_ROW_ESTIMATED_SIZE_PX = 90 ;
221222
222- type ThreadPlanCatalogEntry = Pick < Thread , "id" | "proposedPlans" > ;
223-
224223function escapeAttributeSelectorValue ( value : string ) : string {
225224 if ( typeof CSS !== "undefined" && typeof CSS . escape === "function" ) {
226225 return CSS . escape ( value ) ;
@@ -341,116 +340,7 @@ export function getCopilotResumeCommand(
341340}
342341
343342function useThreadPlanCatalog ( threadIds : readonly ThreadId [ ] ) : ThreadPlanCatalogEntry [ ] {
344- return useStore (
345- useMemo ( ( ) => {
346- let previousThreadIds : readonly ThreadId [ ] = [ ] ;
347- let previousResult : ThreadPlanCatalogEntry [ ] = [ ] ;
348- let previousEntries = new Map <
349- ThreadId ,
350- {
351- shell : object | null ;
352- proposedPlanIds : readonly string [ ] | undefined ;
353- proposedPlansById : Record < string , Thread [ "proposedPlans" ] [ number ] > | undefined ;
354- entry : ThreadPlanCatalogEntry ;
355- }
356- > ( ) ;
357-
358- return ( state ) => {
359- const sameThreadIds =
360- previousThreadIds . length === threadIds . length &&
361- previousThreadIds . every ( ( id , index ) => id === threadIds [ index ] ) ;
362- const nextEntries = new Map <
363- ThreadId ,
364- {
365- shell : object | null ;
366- proposedPlanIds : readonly string [ ] | undefined ;
367- proposedPlansById : Record < string , Thread [ "proposedPlans" ] [ number ] > | undefined ;
368- entry : ThreadPlanCatalogEntry ;
369- }
370- > ( ) ;
371- const nextResult : ThreadPlanCatalogEntry [ ] = [ ] ;
372- let changed = ! sameThreadIds ;
373-
374- for ( const threadId of threadIds ) {
375- let shell : object | undefined ;
376- let proposedPlanIds : readonly string [ ] | undefined ;
377- let proposedPlansById : Record < string , Thread [ "proposedPlans" ] [ number ] > | undefined ;
378-
379- for ( const environmentState of Object . values ( state . environmentStateById ) ) {
380- const matchedShell = environmentState . threadShellById [ threadId ] ;
381- if ( ! matchedShell ) {
382- continue ;
383- }
384- shell = matchedShell ;
385- proposedPlanIds = environmentState . proposedPlanIdsByThreadId [ threadId ] ;
386- proposedPlansById = environmentState . proposedPlanByThreadId [ threadId ] as
387- | Record < string , Thread [ "proposedPlans" ] [ number ] >
388- | undefined ;
389- break ;
390- }
391-
392- if ( ! shell ) {
393- const previous = previousEntries . get ( threadId ) ;
394- if (
395- previous &&
396- previous . shell === null &&
397- previous . proposedPlanIds === undefined &&
398- previous . proposedPlansById === undefined
399- ) {
400- nextEntries . set ( threadId , previous ) ;
401- continue ;
402- }
403- changed = true ;
404- nextEntries . set ( threadId , {
405- shell : null ,
406- proposedPlanIds : undefined ,
407- proposedPlansById : undefined ,
408- entry : { id : threadId , proposedPlans : EMPTY_PROPOSED_PLANS } ,
409- } ) ;
410- continue ;
411- }
412-
413- const previous = previousEntries . get ( threadId ) ;
414- if (
415- previous &&
416- previous . shell === shell &&
417- previous . proposedPlanIds === proposedPlanIds &&
418- previous . proposedPlansById === proposedPlansById
419- ) {
420- nextEntries . set ( threadId , previous ) ;
421- nextResult . push ( previous . entry ) ;
422- continue ;
423- }
424-
425- changed = true ;
426- const proposedPlans =
427- proposedPlanIds && proposedPlanIds . length > 0 && proposedPlansById
428- ? proposedPlanIds . flatMap ( ( planId ) => {
429- const proposedPlan = proposedPlansById ?. [ planId ] ;
430- return proposedPlan ? [ proposedPlan ] : [ ] ;
431- } )
432- : EMPTY_PROPOSED_PLANS ;
433- const entry = { id : threadId , proposedPlans } ;
434- nextEntries . set ( threadId , {
435- shell,
436- proposedPlanIds,
437- proposedPlansById,
438- entry,
439- } ) ;
440- nextResult . push ( entry ) ;
441- }
442-
443- if ( ! changed && previousResult . length === nextResult . length ) {
444- return previousResult ;
445- }
446-
447- previousThreadIds = threadIds ;
448- previousEntries = nextEntries ;
449- previousResult = nextResult ;
450- return nextResult ;
451- } ;
452- } , [ threadIds ] ) ,
453- ) ;
343+ return useStore ( useMemo ( ( ) => createThreadPlanCatalogSelector ( threadIds ) , [ threadIds ] ) ) ;
454344}
455345
456346function formatOutgoingPrompt ( params : {
0 commit comments