@@ -24,16 +24,7 @@ import {
2424} from "@t3tools/shared/usageLimits" ;
2525import { StackActions , useFocusEffect , useNavigation } from "@react-navigation/native" ;
2626import type { ReactNode } from "react" ;
27- import {
28- memo ,
29- useCallback ,
30- useEffect ,
31- useLayoutEffect ,
32- useMemo ,
33- useRef ,
34- useState ,
35- type RefObject ,
36- } from "react" ;
27+ import { useEffect , useLayoutEffect , useRef , useState , type RefObject } from "react" ;
3728import { Keyboard , Platform , Pressable , View , type ViewStyle } from "react-native" ;
3829import { composerImageGallery } from "../../lib/composerImageGallery" ;
3930import { filePreviewGallery } from "../../lib/filePreviewGallery" ;
@@ -332,7 +323,7 @@ export function ComposerSurface(props: {
332323 ) ;
333324}
334325
335- export const ThreadComposer = memo ( function ThreadComposer ( props : ThreadComposerProps ) {
326+ export const ThreadComposer = function ThreadComposer ( props : ThreadComposerProps ) {
336327 const project = useProject ( scopeProjectRef ( props . environmentId , props . selectedThread . projectId ) ) ;
337328 const { themeVariables : materialTheme } = useAppearancePreferences ( ) ;
338329 const composerPanel = materialTheme [ "--color-composer-panel" ] ;
@@ -361,10 +352,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
361352 props . draftMessage . trim ( ) . length > 0 ||
362353 props . draftAttachments . length > 0 ||
363354 ( queuedEdit ?. existingAttachments . length ?? 0 ) > 0 ;
364- const stripAttachments = useMemo (
365- ( ) => composerStripAttachments ( props . draftAttachments ) ,
366- [ props . draftAttachments ] ,
367- ) ;
355+ const stripAttachments = composerStripAttachments ( props . draftAttachments ) ;
368356 const showStopAction = ! hasContent && props . canStopThread && queuedEdit === null ;
369357
370358 const uploadStates = useAtomValue ( composerAttachmentUploadsAtom ) ;
@@ -394,14 +382,14 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
394382 currentModelSelection ,
395383 props . selectedThread . projectId ,
396384 ) ;
397- const selectedProviderStatus = useMemo ( ( ) => {
385+ const selectedProviderStatus = ( ( ) => {
398386 if ( ! props . serverConfig ) return null ;
399387 return (
400388 props . serverConfig . providers . find (
401389 ( p ) => p . instanceId === props . selectedThread . modelSelection . instanceId ,
402390 ) ?? null
403391 ) ;
404- } , [ props . serverConfig , props . selectedThread . modelSelection . instanceId ] ) ;
392+ } ) ( ) ;
405393 const composerOwnerKey = scopedThreadKey ( props . environmentId , props . selectedThread . id ) ;
406394 const composerDraftKey = props . draftKey ?? composerOwnerKey ;
407395 const openDraftDocument = ( attachment : ComposerDocumentAttachment ) => {
@@ -424,7 +412,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
424412 props . serverConfig ?. providers ?? [ ] ,
425413 props . serverConfig ?. usageLimitSources ?? [ ] ,
426414 ) ;
427- const openUsageLimits = useCallback ( ( ) => {
415+ const openUsageLimits = ( ) => {
428416 const report = collectProviderUsageLimits (
429417 currentModelSelection . instanceId ,
430418 props . serverConfig ?. providers ?? [ ] ,
@@ -436,7 +424,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
436424 Dialog . alert ( "Usage limits unavailable" , "This provider does not currently report limits." ) ;
437425 }
438426 return report !== null ;
439- } , [ currentModelSelection . instanceId , onShowUsageLimits , props . serverConfig ] ) ;
427+ } ;
440428
441429 const composerMenu = useComposerCommandMenu ( {
442430 draftMessage : props . draftMessage ,
@@ -504,174 +492,126 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
504492 onExpandedChange ?.( isExpanded ) ;
505493 } , [ isExpanded , onExpandedChange ] ) ;
506494
507- const onPressPreview = useCallback (
508- ( source : FilePreviewSource ) => {
509- wasExpandedBeforePreviewRef . current = isFocused ;
510- setPreviewVideo ( null ) ;
511- setPreviewFile (
512- ( current ) =>
513- current ?? {
514- ...source ,
515- gallery : filePreviewGallery ( source , composerImageGallery ( props . draftAttachments ) ) ,
516- } ,
517- ) ;
518- } ,
519- [ isFocused , props . draftAttachments ] ,
520- ) ;
495+ const onPressPreview = ( source : FilePreviewSource ) => {
496+ wasExpandedBeforePreviewRef . current = isFocused ;
497+ setPreviewVideo ( null ) ;
498+ setPreviewFile (
499+ ( current ) =>
500+ current ?? {
501+ ...source ,
502+ gallery : filePreviewGallery ( source , composerImageGallery ( props . draftAttachments ) ) ,
503+ } ,
504+ ) ;
505+ } ;
521506
522- const closePreview = useCallback ( ( ) => {
507+ const closePreview = ( ) => {
523508 setPreviewFile ( null ) ;
524509 setPreviewVideo ( null ) ;
525510 if ( wasExpandedBeforePreviewRef . current ) {
526511 setTimeout ( ( ) => {
527512 if ( navigation . isFocused ( ) ) inputRef . current ?. focus ( ) ;
528513 } , 100 ) ;
529514 }
530- } , [ inputRef , navigation ] ) ;
515+ } ;
531516
532- const onPressVideo = useCallback (
533- ( attachment : DraftComposerFileAttachment , sourceIdentifier : string ) => {
534- wasExpandedBeforePreviewRef . current = isFocused ;
535- setPreviewFile ( null ) ;
536- setPreviewVideo ( ( current ) => current ?? { type : "local" , attachment, sourceIdentifier } ) ;
537- } ,
538- [ isFocused ] ,
539- ) ;
517+ const onPressVideo = ( attachment : DraftComposerFileAttachment , sourceIdentifier : string ) => {
518+ wasExpandedBeforePreviewRef . current = isFocused ;
519+ setPreviewFile ( null ) ;
520+ setPreviewVideo ( ( current ) => current ?? { type : "local" , attachment, sourceIdentifier } ) ;
521+ } ;
540522
541523 const onEditorFocusChange = props . onEditorFocusChange ;
542- const handleFocus = useCallback ( ( ) => {
524+ const handleFocus = ( ) => {
543525 setIsFocused ( true ) ;
544526 onExpandedChange ?.( true ) ;
545527 onEditorFocusChange ?.( true ) ;
546- } , [ onEditorFocusChange , onExpandedChange ] ) ;
528+ } ;
547529
548- const handleBlur = useCallback ( ( ) => {
530+ const handleBlur = ( ) => {
549531 setIsFocused ( false ) ;
550532 if ( ! settingsSheetPresentation . keepsComposerExpanded ) {
551533 onExpandedChange ?.( false ) ;
552534 }
553535 onEditorFocusChange ?.( false ) ;
554- } , [ onEditorFocusChange , onExpandedChange , settingsSheetPresentation . keepsComposerExpanded ] ) ;
555- const handleSend = useCallback (
556- async ( followUp ?: ActiveTurnComposerAction ) => {
557- if ( voiceInput . blocksSubmission || pendingPastedTextAttachmentCountRef . current > 0 ) return ;
558- if (
559- usageLimitsOffered &&
560- isUsageLimitsCommand ( props . draftMessage ) &&
561- props . draftAttachments . length === 0
562- ) {
563- if ( openUsageLimits ( ) ) onChangeDraftMessage ( "" ) ;
536+ } ;
537+ const handleSend = async ( followUp ?: ActiveTurnComposerAction ) => {
538+ if ( voiceInput . blocksSubmission || pendingPastedTextAttachmentCountRef . current > 0 ) return ;
539+ if (
540+ usageLimitsOffered &&
541+ isUsageLimitsCommand ( props . draftMessage ) &&
542+ props . draftAttachments . length === 0
543+ ) {
544+ if ( openUsageLimits ( ) ) onChangeDraftMessage ( "" ) ;
545+ return ;
546+ }
547+ const threadKey = scopedThreadKey ( props . environmentId , props . selectedThread . id ) ;
548+ if ( inFlightThreadIdsRef . current . has ( threadKey ) ) return ;
549+ inFlightThreadIdsRef . current . add ( threadKey ) ;
550+ return await ( async ( ) => {
551+ const messageId = await onSendMessage ( followUp ) ;
552+ if ( messageId === null ) {
564553 return ;
565554 }
566- const threadKey = scopedThreadKey ( props . environmentId , props . selectedThread . id ) ;
567- if ( inFlightThreadIdsRef . current . has ( threadKey ) ) return ;
568- inFlightThreadIdsRef . current . add ( threadKey ) ;
569- try {
570- const messageId = await onSendMessage ( followUp ) ;
571- if ( messageId === null ) {
572- return ;
573- }
574- // Armed after the send so its preference read and native Activity start don't contend
575- // with the queued-message feedback on the tap frame.
576- armAgentAwarenessLiveActivityForLocalWork ( {
577- environmentId : props . environmentId ,
578- threadTitle : props . selectedThread . title ,
579- projectTitle : props . environmentLabel ?? "T3 Code" ,
580- } ) ;
581- } finally {
582- inFlightThreadIdsRef . current . delete ( threadKey ) ;
583- }
584- } ,
585- [
586- props . draftMessage ,
587- props . draftAttachments . length ,
588- onChangeDraftMessage ,
589- openUsageLimits ,
590- usageLimitsOffered ,
591- onSendMessage ,
592- props . environmentId ,
593- props . environmentLabel ,
594- props . selectedThread . id ,
595- props . selectedThread . title ,
596- voiceInput . blocksSubmission ,
597- ] ,
598- ) ;
555+ // Armed after the send so its preference read and native Activity start don't contend
556+ // with the queued-message feedback on the tap frame.
557+ armAgentAwarenessLiveActivityForLocalWork ( {
558+ environmentId : props . environmentId ,
559+ threadTitle : props . selectedThread . title ,
560+ projectTitle : props . environmentLabel ?? "T3 Code" ,
561+ } ) ;
562+ } ) ( ) . finally ( ( ) => {
563+ inFlightThreadIdsRef . current . delete ( threadKey ) ;
564+ } ) ;
565+ } ;
599566
600567 const lockedProviderInstanceId = props . canSwitchProvider
601568 ? undefined
602569 : currentModelSelection . instanceId ;
603- const modelOptions = useMemo (
604- ( ) =>
605- buildModelOptions (
606- props . serverConfig ,
607- currentModelSelection ,
608- lockedProviderInstanceId ,
609- props . selectedThread . projectId ,
610- ) ,
611- [
612- props . serverConfig ,
613- currentModelSelection ,
614- lockedProviderInstanceId ,
615- props . selectedThread . projectId ,
616- ] ,
570+ const modelOptions = buildModelOptions (
571+ props . serverConfig ,
572+ currentModelSelection ,
573+ lockedProviderInstanceId ,
574+ props . selectedThread . projectId ,
617575 ) ;
618- const threadProviderGroups = useMemo ( ( ) => groupByProvider ( modelOptions ) , [ modelOptions ] ) ;
576+ const threadProviderGroups = groupByProvider ( modelOptions ) ;
619577 const currentModelOption =
620578 modelOptions . find (
621579 ( option ) =>
622580 option . selection . instanceId === currentModelSelection . instanceId &&
623581 option . selection . model === currentModelSelection . model ,
624582 ) ?? null ;
625- const providerOptionDescriptors = useMemo (
626- ( ) =>
627- resolveProviderOptionDescriptors ( {
628- capabilities : currentModelOption ?. capabilities ,
629- selections : currentModelSelection . options ,
630- } ) ,
631- [ currentModelOption ?. capabilities , currentModelSelection . options ] ,
632- ) ;
583+ const providerOptionDescriptors = resolveProviderOptionDescriptors ( {
584+ capabilities : currentModelOption ?. capabilities ,
585+ selections : currentModelSelection . options ,
586+ } ) ;
633587 const settingsOwnerId = composerOwnerKey ;
634- const settingsRouteSession = useMemo < ExistingThreadSettingsRouteSession > (
635- ( ) => ( {
636- ownerId : settingsOwnerId ,
637- environmentId : props . environmentId ,
638- projectId : props . selectedThread . projectId ,
639- providerInstanceId : currentModelSelection . instanceId ,
640- ...( lockedProviderInstanceId ? { lockedProviderInstanceId } : { } ) ,
641- providerGroups : threadProviderGroups ,
642- selectedModel : currentModelSelection ,
643- reportedModelSelection : props . reportedModelSelection ,
644- onSelectModel : ( option ) =>
645- props . onUpdateModelSelection ( withRememberedModelOptions ( option . selection ) ) ,
646- optionDescriptors : providerOptionDescriptors ,
647- onUpdateOptionSelections : ( options ) => {
648- rememberModelOptions (
649- currentModelSelection . instanceId ,
650- currentModelSelection . model ,
651- options ?? [ ] ,
652- ) ;
653- props . onUpdateModelSelection ( { ...currentModelSelection , options } ) ;
654- } ,
655- runtimeMode : currentRuntimeMode ,
656- onUpdateRuntimeMode : props . onUpdateRuntimeMode ,
657- } ) ,
658- [
659- currentModelSelection ,
660- props . reportedModelSelection ,
661- currentRuntimeMode ,
662- props . onUpdateModelSelection ,
663- props . onUpdateRuntimeMode ,
664- providerOptionDescriptors ,
665- settingsOwnerId ,
666- lockedProviderInstanceId ,
667- threadProviderGroups ,
668- props . selectedThread . projectId ,
669- ] ,
670- ) ;
671- const openSettings = useCallback ( ( ) => {
588+ const settingsRouteSession = {
589+ ownerId : settingsOwnerId ,
590+ environmentId : props . environmentId ,
591+ projectId : props . selectedThread . projectId ,
592+ providerInstanceId : currentModelSelection . instanceId ,
593+ ...( lockedProviderInstanceId ? { lockedProviderInstanceId } : { } ) ,
594+ providerGroups : threadProviderGroups ,
595+ selectedModel : currentModelSelection ,
596+ reportedModelSelection : props . reportedModelSelection ,
597+ onSelectModel : ( option ) =>
598+ props . onUpdateModelSelection ( withRememberedModelOptions ( option . selection ) ) ,
599+ optionDescriptors : providerOptionDescriptors ,
600+ onUpdateOptionSelections : ( options ) => {
601+ rememberModelOptions (
602+ currentModelSelection . instanceId ,
603+ currentModelSelection . model ,
604+ options ?? [ ] ,
605+ ) ;
606+ props . onUpdateModelSelection ( { ...currentModelSelection , options } ) ;
607+ } ,
608+ runtimeMode : currentRuntimeMode ,
609+ onUpdateRuntimeMode : props . onUpdateRuntimeMode ,
610+ } satisfies ExistingThreadSettingsRouteSession ;
611+ const openSettings = ( ) => {
672612 settingsRoutePresentation . present ( settingsRouteSession ) ;
673613 settingsSheetPresentation . open ( ) ;
674- } , [ settingsRoutePresentation . present , settingsRouteSession , settingsSheetPresentation . open ] ) ;
614+ } ;
675615
676616 useEffect ( ( ) => {
677617 if ( settingsSheetPresentation . isActive ) {
@@ -688,17 +628,15 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
688628 navigation . dispatch ( StackActions . push ( "ThreadSettingsSheet" ) ) ;
689629 } , [ navigation , settingsSheetPresentation . isVisible ] ) ;
690630
691- useFocusEffect (
692- useCallback ( ( ) => {
693- if ( ! settingsRoutePresentedRef . current ) {
694- return ;
695- }
631+ useFocusEffect ( ( ) => {
632+ if ( ! settingsRoutePresentedRef . current ) {
633+ return ;
634+ }
696635
697- settingsRoutePresentedRef . current = false ;
698- settingsSheetPresentation . onDismissed ( ) ;
699- settingsRoutePresentation . clear ( settingsOwnerId ) ;
700- } , [ settingsOwnerId , settingsRoutePresentation . clear , settingsSheetPresentation . onDismissed ] ) ,
701- ) ;
636+ settingsRoutePresentedRef . current = false ;
637+ settingsSheetPresentation . onDismissed ( ) ;
638+ settingsRoutePresentation . clear ( settingsOwnerId ) ;
639+ } ) ;
702640
703641 useEffect (
704642 ( ) =>
@@ -1140,4 +1078,4 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
11401078 < FilePreviewModal source = { previewFile } onRequestClose = { closePreview } />
11411079 </ Animated . View >
11421080 ) ;
1143- } ) ;
1081+ } ;
0 commit comments