44 TURN_PARENT ,
55 GROUPED_TURN_CONTAINER ,
66 ROLE_CONTAINER ,
7+ HAS_USER_PSUEUDO ,
78 USER_DATA_ATTR ,
89 CONV_EDIT_BUTTON_ARIA_LABEL ,
910 LANGUAGE_MENU_CONTAINER ,
@@ -26,6 +27,12 @@ const LanguageMenu: React.FC<LanguageMenuProps> = React.memo(
2627 ) ;
2728 const { settings, updateSettings } = useSettings ( ) ;
2829 const attachedSelectorsRef = useRef < Set < string > > ( new Set ( ) ) ;
30+ const isNewChatRef = useRef < boolean > ( false ) ;
31+ const pendingTurnsRef = useRef < Element [ ] > ( [ ] ) ;
32+
33+ const checkIfNewChat = useCallback ( ( ) => {
34+ return window . location . href . includes ( 'chatgpt.com/?model=' ) ;
35+ } , [ ] ) ;
2936
3037 const handleEditClick = useCallback (
3138 ( turnElement : Element ) : void => {
@@ -55,12 +62,15 @@ const LanguageMenu: React.FC<LanguageMenuProps> = React.memo(
5562 ) ;
5663
5764 const createContainer = useCallback ( ( turnElement : Element ) => {
58- const container = document . createElement ( 'div' ) ;
59- container . className = LANGUAGE_MENU_CONTAINER ;
60-
6165 // Find the group container for the turn
6266 const groupContainer = turnElement . closest ( GROUPED_TURN_CONTAINER ) ;
63- if ( ! groupContainer ) return container ;
67+ if ( ! groupContainer ) return null ;
68+
69+ // Check if this is a user turn by checking for the user pseudo-class
70+ if ( ! groupContainer . matches ( HAS_USER_PSUEUDO ) ) return null ;
71+
72+ const container = document . createElement ( 'div' ) ;
73+ container . className = LANGUAGE_MENU_CONTAINER ;
6474
6575 // Check if level label exists and insert before it
6676 const levelLabel = groupContainer . querySelector ( '.level-label' ) ;
@@ -132,53 +142,130 @@ const LanguageMenu: React.FC<LanguageMenuProps> = React.memo(
132142 [ createContainer , renderLanguageSelector , attachButtonListener ] ,
133143 ) ;
134144
145+ const processPendingTurns = useCallback ( ( ) => {
146+ console . log (
147+ 'Processing pending turns for language menu:' ,
148+ pendingTurnsRef . current . length ,
149+ ) ;
150+
151+ const turns = Array . from (
152+ document . querySelectorAll ( GROUPED_TURN_CONTAINER ) ,
153+ ) ;
154+
155+ pendingTurnsRef . current = [ ] ; // Clear pending turns
156+
157+ // Sort and process turns
158+ turns . sort ( ( a , b ) => {
159+ const position = a . compareDocumentPosition ( b ) ;
160+ return position & Node . DOCUMENT_POSITION_FOLLOWING ? - 1 : 1 ;
161+ } ) ;
162+
163+ turns . forEach ( ( turn ) => attachLanguageSelector ( turn ) ) ;
164+ isNewChatRef . current = false ;
165+ } , [ attachLanguageSelector ] ) ;
166+
135167 const handleMutations = useCallback (
136168 ( mutations : MutationRecord [ ] ) => {
169+ // Check for new chat condition
170+ if ( checkIfNewChat ( ) && ! isNewChatRef . current ) {
171+ isNewChatRef . current = true ;
172+ pendingTurnsRef . current = [ ] ;
173+ }
174+
137175 mutations . forEach ( ( mutation ) => {
176+ // Check for completion signal
177+ if (
178+ isNewChatRef . current &&
179+ mutation . type === 'childList' &&
180+ mutation . target instanceof Element &&
181+ mutation . target . matches ( '.sr-only' ) &&
182+ mutation . removedNodes . length === 1 &&
183+ mutation . addedNodes . length === 0
184+ ) {
185+ console . log (
186+ 'Chat completion detected in LanguageMenu - processing turns' ,
187+ ) ;
188+ processPendingTurns ( ) ;
189+ return ;
190+ }
191+
138192 if ( mutation . type === 'childList' ) {
193+ mutation . removedNodes . forEach ( ( node ) => {
194+ if ( node instanceof Element ) {
195+ const id =
196+ node . getAttribute ( 'data-message-id' ) ||
197+ node
198+ . querySelector ( ROLE_CONTAINER )
199+ ?. getAttribute ( 'data-message-id' ) ;
200+ if ( id ) {
201+ attachedSelectorsRef . current . delete ( id ) ;
202+ setRenderedElements ( ( prev ) => {
203+ const newSet = new Set ( prev ) ;
204+ newSet . delete ( id ) ;
205+ return newSet ;
206+ } ) ;
207+ }
208+ }
209+ } ) ;
210+
139211 mutation . addedNodes . forEach ( ( node ) => {
140212 if ( node . nodeType === Node . ELEMENT_NODE ) {
141- const elementNode = node as Element ;
142-
143- // Check for turn parent or group container
144- if (
145- elementNode . matches ( TURN_PARENT ) ||
146- elementNode . matches ( GROUPED_TURN_CONTAINER )
147- ) {
148- attachLanguageSelector ( elementNode ) ;
213+ const element = node as Element ;
214+
215+ // Find conversation turns
216+ const turns = element . matches ( GROUPED_TURN_CONTAINER )
217+ ? [ element ]
218+ : Array . from (
219+ element . querySelectorAll ( GROUPED_TURN_CONTAINER ) ,
220+ ) ;
221+
222+ if ( isNewChatRef . current ) {
223+ // Queue turns for later processing
224+ pendingTurnsRef . current . push ( ...turns ) ;
225+ console . log ( 'Queued turns for language menu:' , turns . length ) ;
149226 } else {
150- // Search for nested turn elements
151- elementNode
152- . querySelectorAll (
153- `${ TURN_PARENT } , ${ GROUPED_TURN_CONTAINER } ` ,
154- )
155- . forEach ( attachLanguageSelector ) ;
227+ // Process turns immediately for existing chats
228+ turns . forEach ( ( turn ) => attachLanguageSelector ( turn ) ) ;
156229 }
157230 }
158231 } ) ;
159232 }
160233 } ) ;
161234 } ,
162- [ attachLanguageSelector ] ,
235+ [ checkIfNewChat , processPendingTurns , attachLanguageSelector ] ,
163236 ) ;
164237
165238 useEffect ( ( ) => {
239+ // Check if we're starting with a new chat
240+ isNewChatRef . current = checkIfNewChat ( ) ;
241+
166242 observerRef . current = new MutationObserver ( handleMutations ) ;
167243 observerRef . current . observe ( document . body , {
168244 childList : true ,
169245 subtree : true ,
170246 } ) ;
171247
172- // Initial scan for existing elements
173- document
174- . querySelectorAll ( `${ TURN_PARENT } , ${ GROUPED_TURN_CONTAINER } ` )
175- . forEach ( attachLanguageSelector ) ;
248+ if ( ! isNewChatRef . current ) {
249+ // Process existing elements only for existing chats
250+ const existingTurns = Array . from (
251+ document . querySelectorAll ( GROUPED_TURN_CONTAINER ) ,
252+ ) ;
253+
254+ existingTurns . sort ( ( a , b ) => {
255+ const position = a . compareDocumentPosition ( b ) ;
256+ return position & Node . DOCUMENT_POSITION_FOLLOWING ? - 1 : 1 ;
257+ } ) ;
258+
259+ existingTurns . forEach ( ( turn ) => attachLanguageSelector ( turn ) ) ;
260+ }
176261
177262 return ( ) => {
178263 observerRef . current ?. disconnect ( ) ;
179264 timerRef . current && clearInterval ( timerRef . current ) ;
265+ pendingTurnsRef . current = [ ] ;
266+ isNewChatRef . current = false ;
180267 } ;
181- } , [ handleMutations , attachLanguageSelector ] ) ;
268+ } , [ handleMutations , attachLanguageSelector , checkIfNewChat ] ) ;
182269
183270 // Handle settings changes
184271 useEffect ( ( ) => {
0 commit comments