-
Notifications
You must be signed in to change notification settings - Fork 861
Fix UI stuck on Stop when request is slow or proxy disconnects #995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -110,6 +110,7 @@ function setPortProxy(port, proxyTabId) { | |||||||||
| console.debug('[background] Main port closed; skipping proxy message.') | ||||||||||
| return | ||||||||||
| } | ||||||||||
| if (msg.done || msg.error) port._generating = false | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||
| try { | ||||||||||
| port.postMessage(msg) | ||||||||||
| } catch (e) { | ||||||||||
|
|
@@ -156,6 +157,16 @@ function setPortProxy(port, proxyTabId) { | |||||||||
| const proxyRef = port.proxy | ||||||||||
| port.proxy = null | ||||||||||
| port._proxyTabId = null | ||||||||||
| if (port._generating) { | ||||||||||
| port._generating = false | ||||||||||
| if (!port._isClosed) { | ||||||||||
| try { | ||||||||||
| port.postMessage({ done: true }) | ||||||||||
| } catch (e) { | ||||||||||
| console.warn('[background] Error posting done on proxy disconnect:', e) | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
Comment on lines
+160
to
+168
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. Done lacks session update When the proxy tab disconnects mid-generation, setPortProxy() posts { done: true } without a `{
session }`, so the UI marks the answer complete and becomes ready while
session.conversationRecords remains stale. As a result, saving/exporting or persisting the
conversation can omit the partial answer the user saw.
Agent Prompt
|
||||||||||
| } | ||||||||||
| if (port._reconnectTimerId) { | ||||||||||
| clearTimeout(port._reconnectTimerId) | ||||||||||
| port._reconnectTimerId = null | ||||||||||
|
|
@@ -257,6 +268,7 @@ function setPortProxy(port, proxyTabId) { | |||||||||
| '[background] Main port disconnected (e.g. popup/sidebar closed). Cleaning up proxy connections and listeners.', | ||||||||||
| ) | ||||||||||
| port._isClosed = true | ||||||||||
| port._generating = false | ||||||||||
| if (port._reconnectTimerId) { | ||||||||||
| clearTimeout(port._reconnectTimerId) | ||||||||||
| port._reconnectTimerId = null | ||||||||||
|
|
@@ -401,6 +413,7 @@ async function executeApi(session, port, config) { | |||||||||
| console.debug('[background] Posting message to proxy tab:', { session: redactedSession }) | ||||||||||
| try { | ||||||||||
| port.proxy.postMessage({ session }) | ||||||||||
| port._generating = true | ||||||||||
| } catch (e) { | ||||||||||
| console.warn( | ||||||||||
| '[background] Error posting message to existing proxy tab in executeApi (ChatGPT Web Model):', | ||||||||||
|
|
@@ -413,6 +426,7 @@ async function executeApi(session, port, config) { | |||||||||
| console.debug('[background] Proxy re-established. Attempting to post message again.') | ||||||||||
| try { | ||||||||||
| port.proxy.postMessage({ session }) | ||||||||||
| port._generating = true | ||||||||||
| console.info('[background] Successfully posted session after proxy reconnection.') | ||||||||||
| } catch (e2) { | ||||||||||
| console.error( | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -66,6 +66,7 @@ function ConversationCard(props) { | |||||||||||||||||||||
| const [session, setSession] = useState(props.session) | ||||||||||||||||||||||
| const windowSize = useClampWindowSize([750, 1500], [250, 1100]) | ||||||||||||||||||||||
| const bodyRef = useRef(null) | ||||||||||||||||||||||
| const replacedPortRef = useRef(null) | ||||||||||||||||||||||
| const [completeDraggable, setCompleteDraggable] = useState(false) | ||||||||||||||||||||||
| const useForegroundFetch = isUsingBingWebModel(session) | ||||||||||||||||||||||
| const [apiModes, setApiModes] = useState([]) | ||||||||||||||||||||||
|
|
@@ -118,7 +119,7 @@ function ConversationCard(props) { | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||
| if (props.onUpdate) props.onUpdate(port, session, conversationItemData) | ||||||||||||||||||||||
| }, [session, conversationItemData]) | ||||||||||||||||||||||
| }, [port, session, conversationItemData]) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||
| const { offsetHeight, scrollHeight, scrollTop } = bodyRef.current | ||||||||||||||||||||||
|
|
@@ -289,6 +290,10 @@ function ConversationCard(props) { | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||
| const portListener = () => { | ||||||||||||||||||||||
| if (replacedPortRef.current === port) { | ||||||||||||||||||||||
| replacedPortRef.current = null | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+293
to
+296
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using a This avoids the need for if (port._replaced) return |
||||||||||||||||||||||
| setPort(Browser.runtime.connect()) | ||||||||||||||||||||||
| setIsReady(true) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
@@ -329,7 +334,7 @@ function ConversationCard(props) { | |||||||||||||||||||||
| port.onMessage.removeListener(portMessageListener) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }, [conversationItemData]) | ||||||||||||||||||||||
| }, [port, conversationItemData]) | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Including
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Including
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Including To fix this, you can use a For example, you can define a ref for the listener: const listenerRef = useRef(portMessageListener)
listenerRef.current = portMessageListenerAnd then in the useEffect(() => {
if (useForegroundFetch) {
return () => {}
} else {
const listener = (msg) => listenerRef.current(msg)
port.onMessage.addListener(listener)
return () => {
port.onMessage.removeListener(listener)
}
}
}, [port])
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const getRetryFn = (session) => async () => { | ||||||||||||||||||||||
| updateAnswer(`<p class="gpt-loading">${t('Waiting for response...')}</p>`, false, 'answer') | ||||||||||||||||||||||
|
|
@@ -493,6 +498,11 @@ function ConversationCard(props) { | |||||||||||||||||||||
| text={t('Clear Conversation')} | ||||||||||||||||||||||
| onConfirm={async () => { | ||||||||||||||||||||||
| await postMessage({ stop: true }) | ||||||||||||||||||||||
| if (!useForegroundFetch) { | ||||||||||||||||||||||
| replacedPortRef.current = port | ||||||||||||||||||||||
| port.disconnect() | ||||||||||||||||||||||
| setPort(Browser.runtime.connect()) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
qodo-code-review[bot] marked this conversation as resolved.
Comment on lines
+501
to
+505
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To accompany the simplified port replacement logic and avoid memory leaks from holding onto the old port in a ref, you can set a flag directly on the
Suggested change
|
||||||||||||||||||||||
| Browser.runtime.sendMessage({ | ||||||||||||||||||||||
| type: 'DELETE_CONVERSATION', | ||||||||||||||||||||||
| data: { | ||||||||||||||||||||||
|
|
@@ -507,6 +517,7 @@ function ConversationCard(props) { | |||||||||||||||||||||
| }) | ||||||||||||||||||||||
| newSession.sessionId = session.sessionId | ||||||||||||||||||||||
| setSession(newSession) | ||||||||||||||||||||||
| setIsReady(true) | ||||||||||||||||||||||
| }} | ||||||||||||||||||||||
| /> | ||||||||||||||||||||||
| {!props.pageMode && ( | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,10 +68,15 @@ export async function generateAnswersWithAzureOpenaiApi(port, question, session) | |
| } | ||
| }, | ||
| async onStart() {}, | ||
| async onEnd() { | ||
| port.postMessage({ done: true }) | ||
| port.onMessage.removeListener(messageListener) | ||
| port.onDisconnect.removeListener(disconnectListener) | ||
| async onEnd(aborted) { | ||
| try { | ||
| if (!aborted) { | ||
| port.postMessage({ done: true }) | ||
| } | ||
| } finally { | ||
| port.onMessage.removeListener(messageListener) | ||
| port.onDisconnect.removeListener(disconnectListener) | ||
| } | ||
| }, | ||
|
Comment on lines
+71
to
80
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To maintain consistency with async onEnd(aborted) {
try {
if (!aborted) {
port.postMessage({ done: true })
} else if (answer) {
pushRecord(session, question, answer)
try {
port.postMessage({ session })
} catch (e) {
console.warn('[azure-openai-api] Failed to post session on abort:', e)
}
}
} finally {
port.onMessage.removeListener(messageListener)
port.onDisconnect.removeListener(disconnectListener)
}
}, |
||
| async onError(resp) { | ||
| port.onMessage.removeListener(messageListener) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -78,10 +78,15 @@ export async function generateAnswersWithClaudeApi(port, question, session) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async onStart() {}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async onEnd() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.postMessage({ done: true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onMessage.removeListener(messageListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onDisconnect.removeListener(disconnectListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async onEnd(aborted) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!aborted) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.postMessage({ done: true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onMessage.removeListener(messageListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onDisconnect.removeListener(disconnectListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+81
to
90
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To maintain consistency with
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async onError(resp) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onMessage.removeListener(messageListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -142,12 +142,30 @@ export async function generateAnswersWithOpenAICompatible({ | |
| } | ||
| }, | ||
| async onStart() {}, | ||
| async onEnd() { | ||
| if (!finished) { | ||
| finish() | ||
| async onEnd(aborted = false) { | ||
| try { | ||
| if (!finished) { | ||
| if (aborted) { | ||
| const shouldPostSession = Boolean(answer) || session.isRetry | ||
| if (shouldPostSession) { | ||
|
Comment on lines
+149
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an OpenAI-compatible stream is aborted after producing any partial text, this branch always posts the old Useful? React with 👍 / 👎. |
||
| if (answer) { | ||
| pushRecord(session, question, answer) | ||
| } | ||
| session.isRetry = false | ||
| try { | ||
| port.postMessage({ session }) | ||
| } catch (e) { | ||
| console.warn('[openai-compatible-core] Failed to post session on abort:', e) | ||
| } | ||
| } | ||
| } else { | ||
| finish() | ||
| } | ||
| } | ||
| } finally { | ||
| port.onMessage.removeListener(messageListener) | ||
| port.onDisconnect.removeListener(disconnectListener) | ||
| } | ||
| port.onMessage.removeListener(messageListener) | ||
| port.onDisconnect.removeListener(disconnectListener) | ||
| }, | ||
| async onError(resp) { | ||
| port.onMessage.removeListener(messageListener) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -63,10 +63,15 @@ export async function generateAnswersWithWaylaidwandererApi(port, question, sess | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async onStart() {}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async onEnd() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.postMessage({ done: true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onMessage.removeListener(messageListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onDisconnect.removeListener(disconnectListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async onEnd(aborted) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!aborted) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.postMessage({ done: true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onMessage.removeListener(messageListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onDisconnect.removeListener(disconnectListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To maintain consistency with
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async onError(resp) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.onMessage.removeListener(messageListener) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use optional chaining
msg?.doneandmsg?.errorto prevent potentialTypeErrorifmsgis null or undefined, adhering to defensive programming guidelines.