Merge pull request 'fix: cap huge selections before shortening' (#9) from fix/long-selection-cap into develop
Reviewed-on: https://git.zavage.net/Zavage-Software/search-with-chatgpt-powered-by-openai-extension/pulls/9
This commit is contained in:
@@ -11,6 +11,8 @@ All notable changes to this project will be documented in this file. See [commit
|
|||||||
actions, with optional translation and a preferred language or language dialog.
|
actions, with optional translation and a preferred language or language dialog.
|
||||||
- Preserve dedicated action instructions when shortening selections; ask before
|
- Preserve dedicated action instructions when shortening selections; ask before
|
||||||
sending shortened text and reject URLs that cannot be sent intact.
|
sending shortened text and reject URLs that cannot be sent intact.
|
||||||
|
- Stay responsive with very large selections from the shortcut or toolbar: only
|
||||||
|
the part that can fit in the ChatGPT link is processed.
|
||||||
- Add accessible action dialogs with cancellation and retry, remove the temporary
|
- Add accessible action dialogs with cancellation and retry, remove the temporary
|
||||||
model/effort URL probe, and document URL sharing. No new permissions.
|
model/effort URL probe, and document URL sharing. No new permissions.
|
||||||
- Show up front, in the options panel and README, that ChatGPT may require
|
- Show up front, in the options panel and README, that ChatGPT may require
|
||||||
|
|||||||
+12
-1
@@ -25,12 +25,23 @@ export function prepareAction(request) {
|
|||||||
}
|
}
|
||||||
const resolved = { ...request, language };
|
const resolved = { ...request, language };
|
||||||
const serialize = text => serializePrompt(request.settings, renderPrompt(resolved, text));
|
const serialize = text => serializePrompt(request.settings, renderPrompt(resolved, text));
|
||||||
|
// Each code point adds at least one URL character, so none past the first
|
||||||
|
// MAX_URL_CHARS can fit. Capping first keeps a select-all on a huge page from
|
||||||
|
// costing seconds of re-encoding. A capped source is never sent as ready: its
|
||||||
|
// kept prefix may trim down to something that fits and silently drop the rest.
|
||||||
|
const points = [];
|
||||||
|
let capped = false;
|
||||||
|
for (const point of source) {
|
||||||
|
if (points.length === MAX_URL_CHARS) { capped = true; break; }
|
||||||
|
points.push(point);
|
||||||
|
}
|
||||||
|
if (!capped) {
|
||||||
const url = serialize(source);
|
const url = serialize(source);
|
||||||
if (url.length <= MAX_URL_CHARS) return { kind: 'ready', url };
|
if (url.length <= MAX_URL_CHARS) return { kind: 'ready', url };
|
||||||
|
}
|
||||||
if (isUrl) return error('This URL is too long to send intact. No part of it has been sent.');
|
if (isUrl) return error('This URL is too long to send intact. No part of it has been sent.');
|
||||||
const marker = '\n[Selected text shortened]';
|
const marker = '\n[Selected text shortened]';
|
||||||
if (serialize(marker).length >= MAX_URL_CHARS) return error('The prompt instructions are too long. Shorten your prompt template and try again.');
|
if (serialize(marker).length >= MAX_URL_CHARS) return error('The prompt instructions are too long. Shorten your prompt template and try again.');
|
||||||
const points = Array.from(source);
|
|
||||||
let low = 0, high = points.length;
|
let low = 0, high = points.length;
|
||||||
while (low < high) {
|
while (low < high) {
|
||||||
const mid = Math.ceil((low + high) / 2);
|
const mid = Math.ceil((low + high) / 2);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { DEFAULT_SETTINGS } from '../lib/defaults.js';
|
import { DEFAULT_SETTINGS } from '../lib/defaults.js';
|
||||||
|
import { MAX_URL_CHARS } from '../lib/build-url.js';
|
||||||
const { prepareAction } = await import('../lib/prepare-action.js').catch(()=>({}));
|
const { prepareAction } = await import('../lib/prepare-action.js').catch(()=>({}));
|
||||||
const req = (overrides={}) => ({actionId:'ask',source:'Hello',settings:{...DEFAULT_SETTINGS},...overrides});
|
const req = (overrides={}) => ({actionId:'ask',source:'Hello',settings:{...DEFAULT_SETTINGS},...overrides});
|
||||||
test('ready actions preserve source and hints, translation asks only when needed', () => {
|
test('ready actions preserve source and hints, translation asks only when needed', () => {
|
||||||
@@ -38,3 +39,22 @@ test('long selections preserve instructions, repetition and Unicode with explici
|
|||||||
const translated=prepareAction(req({actionId:'translate',language:'Spanish',source:'x'.repeat(20000)}));
|
const translated=prepareAction(req({actionId:'translate',language:'Spanish',source:'x'.repeat(20000)}));
|
||||||
assert.equal(translated.kind,'shortened');assert.match(new URL(translated.url).searchParams.get('q'), /Spanish/);
|
assert.equal(translated.kind,'shortened');assert.match(new URL(translated.url).searchParams.get('q'), /Spanish/);
|
||||||
});
|
});
|
||||||
|
test('huge selections are capped before shortening, with identical output', () => {
|
||||||
|
assert.equal(typeof prepareAction, 'function');
|
||||||
|
// Select-all on a very large page; shortening this took over a second uncapped.
|
||||||
|
const source='😀 word '.repeat(625000);
|
||||||
|
const started=performance.now();
|
||||||
|
const out=prepareAction(req({source}));
|
||||||
|
const elapsed=performance.now()-started;
|
||||||
|
assert.equal(out.kind,'shortened');
|
||||||
|
assert.ok(elapsed<250,`took ${elapsed.toFixed(0)} ms`);
|
||||||
|
const capped=Array.from(source).slice(0,MAX_URL_CHARS).join('');
|
||||||
|
assert.deepEqual(prepareAction(req({source:capped})),out);
|
||||||
|
});
|
||||||
|
test('a selection too long to send whole always needs consent, even if its start would fit', () => {
|
||||||
|
assert.equal(typeof prepareAction, 'function');
|
||||||
|
// The first MAX_URL_CHARS code points trim down to "a"; sending just that would drop the rest unasked.
|
||||||
|
const out=prepareAction(req({source:'a'+' '.repeat(MAX_URL_CHARS)+'b'}));
|
||||||
|
assert.equal(out.kind,'shortened');
|
||||||
|
assert.ok(new URL(out.url).searchParams.get('q').includes('[Selected text shortened]'));
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user