@youdie006/prodex 0.25.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chatgpt-browser.js +42 -17
- package/package.json +1 -1
package/dist/chatgpt-browser.js
CHANGED
|
@@ -439,6 +439,26 @@ export function classifyTranscriptRead(state, sentPrompt) {
|
|
|
439
439
|
? "pending"
|
|
440
440
|
: "unavailable";
|
|
441
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Should prodex drag the tab back to the thread it pinned?
|
|
444
|
+
*
|
|
445
|
+
* Only when the pin is a real conversation, and only when the page is the only
|
|
446
|
+
* way left to read the answer. Pinning a project or new-chat page (the url a
|
|
447
|
+
* send starts on, before ChatGPT rewrites it to /c/<id>) made prodex treat its
|
|
448
|
+
* OWN conversation as a stray tab and navigate away from the answer it was
|
|
449
|
+
* waiting for. And while the transcript can answer, moving someone else's tab
|
|
450
|
+
* back buys nothing.
|
|
451
|
+
*/
|
|
452
|
+
export function shouldRecoverThreadNavigation(args) {
|
|
453
|
+
const { pinnedThreadUrl, currentUrl, lastTranscriptClassification } = args;
|
|
454
|
+
if (!pinnedThreadUrl || !currentUrl)
|
|
455
|
+
return false;
|
|
456
|
+
if (!conversationIdFromThreadUrl(pinnedThreadUrl))
|
|
457
|
+
return false;
|
|
458
|
+
if (lastTranscriptClassification === "pending" || lastTranscriptClassification === "answer")
|
|
459
|
+
return false;
|
|
460
|
+
return !chatGptUrlsReferToSameTarget(currentUrl, pinnedThreadUrl);
|
|
461
|
+
}
|
|
442
462
|
export function hasFreshChatGptAnswer(previousAssistantMessageCount, state) {
|
|
443
463
|
return state.assistantMessageCount > previousAssistantMessageCount && isUsableChatGptAnswer(state.answer) && !state.generating;
|
|
444
464
|
}
|
|
@@ -2143,6 +2163,7 @@ export async function sendChatGptPrompt(options) {
|
|
|
2143
2163
|
});
|
|
2144
2164
|
}
|
|
2145
2165
|
let recoveredNavigations = 0;
|
|
2166
|
+
let lastTranscriptClassification;
|
|
2146
2167
|
const answerIsStable = createChatGptAnswerStabilityTracker();
|
|
2147
2168
|
while (Date.now() - started < timeoutMs) {
|
|
2148
2169
|
await sleep(1000);
|
|
@@ -2159,12 +2180,11 @@ export async function sendChatGptPrompt(options) {
|
|
|
2159
2180
|
// instead of caret heuristics, and the model that actually answered.
|
|
2160
2181
|
if (transcriptConversationId && !finalState.generating) {
|
|
2161
2182
|
const transcript = await readTranscriptAnswer(page, transcriptConversationId, options.prompt);
|
|
2183
|
+
lastTranscriptClassification = transcript.classification;
|
|
2162
2184
|
if (transcript.answer)
|
|
2163
2185
|
return transcriptResult(transcript.answer);
|
|
2164
2186
|
}
|
|
2165
|
-
|
|
2166
|
-
// conversation id is known, a wandering tab is harmless.
|
|
2167
|
-
if (!transcriptConversationId && pinnedThreadUrl && finalState?.url && !chatGptUrlsReferToSameTarget(finalState.url, pinnedThreadUrl)) {
|
|
2187
|
+
if (shouldRecoverThreadNavigation({ pinnedThreadUrl, currentUrl: finalState?.url, lastTranscriptClassification })) {
|
|
2168
2188
|
if (recoveredNavigations >= 2) {
|
|
2169
2189
|
throw new ChatGptBrowserBlockerError({
|
|
2170
2190
|
code: "thread_navigated_away",
|
|
@@ -2205,6 +2225,7 @@ export async function sendChatGptPrompt(options) {
|
|
|
2205
2225
|
// code that innerText flattens) and the model that actually answered.
|
|
2206
2226
|
if (transcriptConversationId) {
|
|
2207
2227
|
const transcript = await readTranscriptAnswer(page, transcriptConversationId, options.prompt);
|
|
2228
|
+
lastTranscriptClassification = transcript.classification;
|
|
2208
2229
|
if (transcript.answer)
|
|
2209
2230
|
return transcriptResult(transcript.answer);
|
|
2210
2231
|
// The transcript can read this conversation and says it is not done:
|
|
@@ -3135,7 +3156,14 @@ const NORMALIZED_PROMPT_MATCH_CHARS = 120;
|
|
|
3135
3156
|
* tool prefixes it ("@Deep research ...") and attachments append to it.
|
|
3136
3157
|
*/
|
|
3137
3158
|
export function transcriptMatchesSentPrompt(userText, sentPrompt) {
|
|
3138
|
-
|
|
3159
|
+
// The composer escapes markdown when it stores what was typed ("## File" is
|
|
3160
|
+
// kept as "\\## File", fences as escaped backticks), so undo that before
|
|
3161
|
+
// comparing - otherwise every prompt carrying markdown, which is every
|
|
3162
|
+
// --file send, looks like a different conversation.
|
|
3163
|
+
const normalize = (value) => value
|
|
3164
|
+
.replace(/\\([\\`*_{}[\]()#+\-.!>~|])/g, "$1")
|
|
3165
|
+
.replace(/\s+/g, " ")
|
|
3166
|
+
.trim();
|
|
3139
3167
|
const seen = normalize(userText);
|
|
3140
3168
|
const sent = normalize(sentPrompt);
|
|
3141
3169
|
if (seen.length === 0 || sent.length === 0)
|
|
@@ -3370,17 +3398,11 @@ export function answerExpression() {
|
|
|
3370
3398
|
});
|
|
3371
3399
|
const assistantMessages = messages.filter((message) => message.role === "assistant");
|
|
3372
3400
|
const userMessages = messages.filter((message) => message.role === "user");
|
|
3373
|
-
//
|
|
3374
|
-
//
|
|
3375
|
-
//
|
|
3376
|
-
//
|
|
3377
|
-
|
|
3378
|
-
const turnAnswers = assistantMessages.length > 0 ? [] : [...document.querySelectorAll('[data-testid^="conversation-turn"]')]
|
|
3379
|
-
.filter((turn) => !turn.querySelector('[data-message-author-role="user"]'))
|
|
3380
|
-
.map((turn) => ({ role: "assistant", text: (turn.innerText || "").trim(), modelSlug: undefined }))
|
|
3381
|
-
.filter((turn) => turn.text.length > 0);
|
|
3382
|
-
const effectiveAssistants = assistantMessages.length > 0 ? assistantMessages : turnAnswers;
|
|
3383
|
-
const assistant = effectiveAssistants.at(-1);
|
|
3401
|
+
// A turn without an assistant message is NOT an answer. Treating one as an
|
|
3402
|
+
// answer (a 0.21.3 fallback for deep research, which is read from the
|
|
3403
|
+
// transcript now) turned a tool's progress panel into a 28-character
|
|
3404
|
+
// "answer" that a consult returned as its result.
|
|
3405
|
+
const assistant = assistantMessages.at(-1);
|
|
3384
3406
|
const buttons = [...document.querySelectorAll('button,[role="button"]')]
|
|
3385
3407
|
.filter((node) => !!(node.offsetWidth || node.offsetHeight || node.getClientRects().length))
|
|
3386
3408
|
.filter((node) => !node.closest(excludedTextSelector))
|
|
@@ -3393,13 +3415,16 @@ export function answerExpression() {
|
|
|
3393
3415
|
return {
|
|
3394
3416
|
title: document.title,
|
|
3395
3417
|
url: location.href,
|
|
3396
|
-
|
|
3418
|
+
// No assistant message means no answer. This used to hand back the last
|
|
3419
|
+
// 4000 characters of the page, which reads as sidebar and navigation text
|
|
3420
|
+
// dressed up as a reply.
|
|
3421
|
+
answer,
|
|
3397
3422
|
textSample: text.slice(0, 12000),
|
|
3398
3423
|
blockerTextSample: visibleTextOutsideMessages(excludedTextSelector).slice(0, 12000),
|
|
3399
3424
|
blockerScanTextSample: visibleTextOutsideMessages(blockerScanExcludedSelector).slice(0, 12000),
|
|
3400
3425
|
visibleButtonLabels: buttons,
|
|
3401
3426
|
generating: placeholder || Boolean(document.querySelector(${streamingSelector})) || buttons.some((label) => generatingControlPattern.test(label)),
|
|
3402
|
-
assistantMessageCount:
|
|
3427
|
+
assistantMessageCount: assistantMessages.length,
|
|
3403
3428
|
userMessageCount: userMessages.length,
|
|
3404
3429
|
// ChatGPT tags each assistant message with the model that produced it -
|
|
3405
3430
|
// the only ground truth for "did the Pro selection actually take".
|