Skip to content

Commit e1a05f5

Browse files
authored
Merge pull request #11 from evanmeeks/develop
Develop
2 parents 15861d4 + e722064 commit e1a05f5

File tree

6 files changed

+278
-160
lines changed

6 files changed

+278
-160
lines changed

src/components/LanguageMenu/LanguageMenu.tsx

Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
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(() => {

src/components/LanguageMenu/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ div[wordsmith-editable] {
179179

180180
flex-direction: column !important;
181181
min-height: 100vh;
182-
margin-top: 22px;
182+
margin-top: 39px;
183183
}
184184

185185
.monaco-list-row {

src/components/PromptMenu/PromptMenu.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ const PromptMenu: React.FC = () => {
4545
if (checkInitialized()) {
4646
console.log('Cleaning up existing menu...');
4747
if (rootRef.current) {
48-
rootRef.current.unmount();
4948
rootRef.current = null;
5049
}
5150
if (languageSelectorRef.current) {
@@ -142,7 +141,7 @@ const PromptMenu: React.FC = () => {
142141
promptContainer.classList.remove('prompt-container');
143142
}
144143
if (rootRef.current) {
145-
rootRef.current.unmount();
144+
rootRef.current = null;
146145
}
147146
if (languageSelectorRef.current) {
148147
languageSelectorRef.current.remove();

src/components/Style/BaseStyleSheet.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export const BaseStyleSheet = () => {
2626
max-width: 97%;
2727
min-width: 97%;
2828
}
29+
.mx-auto {
30+
max-width: 100%;
31+
min-width: 100%;
32+
}
2933
`
3034
: ''
3135
}

0 commit comments

Comments
 (0)