Skip to content

Commit be18f15

Browse files
committed
Add heuristics settings for editor and send button; implement UI toggles and state management
1 parent 26d3eae commit be18f15

File tree

7 files changed

+224
-4
lines changed

7 files changed

+224
-4
lines changed

code-notes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ Current architectural reference for the OneClickPrompts Chrome extension (Manife
4545
- **`modules/selector-auto-detector/selector-guard.js`** ("The Guard"): Adapter that replaces direct `document.querySelector` calls. Tries configured selectors, skips disabled/custom buttons, reports success/failure to the Brain.
4646
- **`modules/selector-auto-detector/base-heuristics.js`**: Generic heuristics (textarea + any `[contenteditable]`, shadow-root walk; buttons/role=button scoring). Also hosts `OneClickPromptsSiteHeuristics` registry and resolver.
4747
- **Site heuristics modules**: e.g., `modules/selector-auto-detector/heuristics-deepseek.js` registers DeepSeek-specific editor/button scoring (ignores OCP UI). If present, the Brain picks the site module by `InjectionTargetsOnWebsite.activeSite`; otherwise it falls back to the base heuristics.
48-
- **Integration**: Site scripts call `SelectorGuard.findEditor()/findSendButton()`; on repeated failures the Brain runs heuristics, shows toasts on success/error, and returns the guessed element to the caller.
48+
- **Integration**: Site scripts call `SelectorGuard.findEditor()/findSendButton()`; on repeated failures the Brain runs heuristics, shows toasts on success/error, and returns the guessed element to the caller. When both editor and send-button selectors are missing, the Guard now surfaces the editor failure toast first to match dependency order.
49+
- **Auto-detect toggles**: If heuristics are disabled in the popup toggles, the Brain still shows a "not found; auto-detect off" toast so the user sees which element is missing even without running scans.
4950

5051
## 3. UI Surfaces
5152

modules/selector-auto-detector/index.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ window.OneClickPromptsSelectorAutoDetector = {
2727
failureThreshold: 1, // Number of failures before triggering recovery (can be >1 to debounce)
2828
cooldownMs: 2000, // Time to wait before re-alerting or re-trying
2929
},
30+
settings: {
31+
enableEditorHeuristics: true,
32+
enableSendButtonHeuristics: true,
33+
loaded: false
34+
},
3035

3136
/**
3237
* Reports a failure to find a specific element type.
@@ -81,9 +86,23 @@ window.OneClickPromptsSelectorAutoDetector = {
8186
const s = this.state[type];
8287
s.recovering = true;
8388

89+
const heuristicsAllowed = type === 'editor'
90+
? this.settings.enableEditorHeuristics !== false
91+
: this.settings.enableSendButtonHeuristics !== false;
92+
8493
// Readable name for the type
8594
const typeName = type === 'editor' ? 'Text input area' : 'send button';
8695

96+
// If heuristics are disabled, still notify the user but skip the scan.
97+
if (!heuristicsAllowed) {
98+
logConCgp(`[SelectorAutoDetector] Heuristics disabled for ${type}; skipping recovery.`);
99+
if (window.showToast) {
100+
window.showToast(`OneClickPrompts: ${typeName} not found. Auto-detect is off.`, 'error');
101+
}
102+
s.recovering = false;
103+
return null;
104+
}
105+
87106
// Notify user
88107
if (window.showToast) {
89108
window.showToast(`OneClickPrompts: ${typeName} not found. Trying to find it...`, 'info');
@@ -119,5 +138,38 @@ window.OneClickPromptsSelectorAutoDetector = {
119138

120139
s.recovering = false;
121140
return result;
141+
},
142+
143+
loadSettings: async function () {
144+
if (!chrome?.runtime?.sendMessage) {
145+
return;
146+
}
147+
try {
148+
const response = await chrome.runtime.sendMessage({ type: 'getSelectorAutoDetectorSettings' });
149+
if (response && response.settings) {
150+
this.settings = {
151+
enableEditorHeuristics: response.settings.enableEditorHeuristics !== false,
152+
enableSendButtonHeuristics: response.settings.enableSendButtonHeuristics !== false,
153+
loaded: true
154+
};
155+
}
156+
} catch (error) {
157+
logConCgp('[SelectorAutoDetector] Failed to load settings, falling back to defaults.', error);
158+
}
122159
}
123160
};
161+
162+
// Initial settings sync and live updates
163+
window.OneClickPromptsSelectorAutoDetector.loadSettings();
164+
165+
if (chrome?.runtime?.onMessage?.addListener) {
166+
chrome.runtime.onMessage.addListener((message) => {
167+
if (message?.type === 'selectorAutoDetectorSettingsChanged' && message.settings) {
168+
window.OneClickPromptsSelectorAutoDetector.settings = {
169+
enableEditorHeuristics: message.settings.enableEditorHeuristics !== false,
170+
enableSendButtonHeuristics: message.settings.enableSendButtonHeuristics !== false,
171+
loaded: true
172+
};
173+
}
174+
});
175+
}

modules/selector-auto-detector/selector-guard.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ window.OneClickPromptsSelectorGuard = {
3737
*/
3838
findSendButton: async function () {
3939
const selectors = window.InjectionTargetsOnWebsite?.selectors?.sendButtons || [];
40+
const editorSelectors = window.InjectionTargetsOnWebsite?.selectors?.editors || [];
4041

4142
// 1. Try standard selectors
4243
let element = this._querySelectors(selectors, { requireEnabled: true });
@@ -46,6 +47,11 @@ window.OneClickPromptsSelectorGuard = {
4647
window.OneClickPromptsSelectorAutoDetector.reportRecovery('sendButton');
4748
return element;
4849
} else {
50+
// If editor is also missing, surface that failure first to guide the user.
51+
const editorElement = this._querySelectors(editorSelectors);
52+
if (!editorElement && window.OneClickPromptsSelectorAutoDetector) {
53+
await window.OneClickPromptsSelectorAutoDetector.reportFailure('editor', { selectors: editorSelectors });
54+
}
4955
// Try to recover
5056
return await window.OneClickPromptsSelectorAutoDetector.reportFailure('sendButton', { selectors });
5157
}

modules/service-worker-auxiliary-state-store.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const KEYS = {
5050
crossChat: 'modules.crossChat', // object { settings: {...}, storedPrompt: string }
5151
inlineProfileSelector: 'modules.inlineProfileSelector', // object { enabled:boolean, placement:'before'|'after' }
5252
tokenApproximator: 'modules.tokenApproximator', // object { enabled:boolean, calibration:number, threadMode:string, showEditorCounter:boolean, placement:'before'|'after' }
53+
selectorAutoDetector: 'modules.selectorAutoDetector', // object { enableEditorHeuristics:boolean, enableSendButtonHeuristics:boolean }
5354
},
5455
floatingPanel: 'floatingPanel', // object map { [hostname]: settings }
5556
global: {
@@ -79,6 +80,11 @@ const CROSS_CHAT_DEFAULT_SETTINGS = {
7980
hideStandardButtons: false,
8081
};
8182

83+
const SELECTOR_AUTO_DETECTOR_DEFAULTS = {
84+
enableEditorHeuristics: true,
85+
enableSendButtonHeuristics: true,
86+
};
87+
8288
// Utilities to get/set nested key paths by flattening as separate storage entries
8389
// We store each top-level namespace as a whole object where applicable to limit storage ops:
8490
async function getValue(path) {
@@ -193,6 +199,17 @@ async function getValue(path) {
193199
}
194200
};
195201
}
202+
if (path === KEYS.modules.selectorAutoDetector) {
203+
const r = await lsGet([KEYS.modules.selectorAutoDetector]);
204+
const obj = r[KEYS.modules.selectorAutoDetector];
205+
if (obj && typeof obj === 'object') {
206+
return {
207+
enableEditorHeuristics: !!obj.enableEditorHeuristics,
208+
enableSendButtonHeuristics: !!obj.enableSendButtonHeuristics,
209+
};
210+
}
211+
return { ...SELECTOR_AUTO_DETECTOR_DEFAULTS };
212+
}
196213
if (path === KEYS.floatingPanel) {
197214
// Build map from structured store if exists, else from legacy scattered keys
198215
const all = await lsGet(null);
@@ -306,6 +323,15 @@ async function setValue(path, value) {
306323
await lsSet({ [KEYS.modules.tokenApproximator]: normalized });
307324
return;
308325
}
326+
if (path === KEYS.modules.selectorAutoDetector) {
327+
const settings = value && typeof value === 'object' ? value : {};
328+
const normalized = {
329+
enableEditorHeuristics: settings.enableEditorHeuristics !== false,
330+
enableSendButtonHeuristics: settings.enableSendButtonHeuristics !== false,
331+
};
332+
await lsSet({ [KEYS.modules.selectorAutoDetector]: normalized });
333+
return;
334+
}
309335
if (path.startsWith(KEYS.floatingPanel)) {
310336
// We maintain a structured map and legacy per-host keys
311337
if (path === KEYS.floatingPanel) {
@@ -487,6 +513,15 @@ export const StateStore = {
487513
this.broadcast({ type: 'tokenApproximatorSettingsChanged', settings });
488514
},
489515

516+
// ===== Selector Auto-Detector (Global Module) =====
517+
async getSelectorAutoDetectorSettings() {
518+
return await getValue(KEYS.modules.selectorAutoDetector);
519+
},
520+
async saveSelectorAutoDetectorSettings(settings) {
521+
await setValue(KEYS.modules.selectorAutoDetector, settings);
522+
this.broadcast({ type: 'selectorAutoDetectorSettingsChanged', settings });
523+
},
524+
490525
// Broadcast utility
491526
async broadcast(payload) {
492527
try {

modules/service-worker-message-router.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,34 @@ export function handleMessage(request, sender, sendResponse) {
494494
return true;
495495
// ===== End Token Approximator Cases =====
496496

497+
// ===== Selector Auto-Detector Cases =====
498+
case 'getSelectorAutoDetectorSettings':
499+
(async () => {
500+
try {
501+
const settings = await StateStore.getSelectorAutoDetectorSettings();
502+
logConfigurationRelatedStuff('Retrieved Selector Auto-Detector settings:', settings);
503+
sendResponse({ settings });
504+
} catch (error) {
505+
handleStorageError(error);
506+
sendResponse({ error: error.message });
507+
}
508+
})();
509+
return true;
510+
511+
case 'saveSelectorAutoDetectorSettings':
512+
(async () => {
513+
try {
514+
await StateStore.saveSelectorAutoDetectorSettings(request.settings);
515+
logConfigurationRelatedStuff('Saved Selector Auto-Detector settings:', request.settings);
516+
sendResponse({ success: true });
517+
} catch (error) {
518+
handleStorageError(error);
519+
sendResponse({ error: error.message });
520+
}
521+
})();
522+
return true;
523+
// ===== End Selector Auto-Detector Cases =====
524+
497525
case 'openSettingsPage':
498526
(async () => {
499527
try {
@@ -514,4 +542,4 @@ export function handleMessage(request, sender, sendResponse) {
514542
sendResponse({ error: 'Unknown message type' });
515543
return false;
516544
}
517-
}
545+
}

popup-page-scripts/popup-page-advanced.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,43 @@ document.addEventListener('DOMContentLoaded', () => {
4242
const selectorConfig = document.getElementById('selectorConfig');
4343
const saveButton = document.getElementById('saveSelectors');
4444
const resetButton = document.getElementById('resetSelectors');
45+
const editorHeuristicsToggle = document.getElementById('editorHeuristicsToggle');
46+
const sendButtonHeuristicsToggle = document.getElementById('sendButtonHeuristicsToggle');
47+
48+
const heuristicsDefaults = {
49+
enableEditorHeuristics: true,
50+
enableSendButtonHeuristics: true,
51+
};
52+
let heuristicsSettings = { ...heuristicsDefaults };
53+
54+
async function loadHeuristicsSettings() {
55+
if (!editorHeuristicsToggle || !sendButtonHeuristicsToggle) return;
56+
try {
57+
const response = await chrome.runtime.sendMessage({ type: 'getSelectorAutoDetectorSettings' });
58+
if (response && response.settings) {
59+
heuristicsSettings = {
60+
...heuristicsDefaults,
61+
...response.settings,
62+
};
63+
}
64+
} catch (error) {
65+
console.error('[advanced selectors] Failed to load heuristics settings:', error);
66+
heuristicsSettings = { ...heuristicsDefaults };
67+
}
68+
editorHeuristicsToggle.checked = !!heuristicsSettings.enableEditorHeuristics;
69+
sendButtonHeuristicsToggle.checked = !!heuristicsSettings.enableSendButtonHeuristics;
70+
}
71+
72+
async function saveHeuristicsSettings() {
73+
try {
74+
await chrome.runtime.sendMessage({
75+
type: 'saveSelectorAutoDetectorSettings',
76+
settings: heuristicsSettings,
77+
});
78+
} catch (error) {
79+
console.error('[advanced selectors] Failed to save heuristics settings:', error);
80+
}
81+
}
4582

4683
// Load selectors for the selected website
4784
async function loadSelectors() {
@@ -163,9 +200,22 @@ document.addEventListener('DOMContentLoaded', () => {
163200
websiteSelect.addEventListener('change', loadSelectors);
164201
saveButton.addEventListener('click', saveSelectors);
165202
resetButton.addEventListener('click', resetSelectors);
203+
if (editorHeuristicsToggle) {
204+
editorHeuristicsToggle.addEventListener('change', () => {
205+
heuristicsSettings.enableEditorHeuristics = editorHeuristicsToggle.checked;
206+
saveHeuristicsSettings();
207+
});
208+
}
209+
if (sendButtonHeuristicsToggle) {
210+
sendButtonHeuristicsToggle.addEventListener('change', () => {
211+
heuristicsSettings.enableSendButtonHeuristics = sendButtonHeuristicsToggle.checked;
212+
saveHeuristicsSettings();
213+
});
214+
}
166215

167216
// Initial load
168217
loadSelectors();
218+
loadHeuristicsSettings();
169219

170220
// Make the selectorConfig textarea auto-resizable by reusing the global function
171221
if (typeof textareaInputAreaResizerFun === 'function') {
@@ -208,4 +258,4 @@ document.addEventListener('DOMContentLoaded', () => {
208258
if (selectorConfig && typeof resizeVerticalTextarea === 'function') {
209259
selectorConfig.addEventListener('input', () => resizeVerticalTextarea(selectorConfig));
210260
}
211-
});
261+
});

popup.html

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- popup.html -->
12
<html lang="en">
23
<head>
34
<meta charset="UTF-8" />
@@ -1370,7 +1371,31 @@ <h2>
13701371
Reset to Defaults
13711372
</button>
13721373
</div>
1373-
1374+
<br />
1375+
<div class="switch-container">
1376+
<label class="switch">
1377+
<input
1378+
type="checkbox"
1379+
id="editorHeuristicsToggle"
1380+
aria-label="Enable heuristics when the text editor is not found"
1381+
/>
1382+
<span class="slider"></span>
1383+
</label>
1384+
<span class="switch-label">Use heuristics when editor is not found</span>
1385+
</div>
1386+
<br />
1387+
<div class="switch-container">
1388+
<label class="switch">
1389+
<input
1390+
type="checkbox"
1391+
id="sendButtonHeuristicsToggle"
1392+
aria-label="Enable heuristics when the send button is not found"
1393+
/>
1394+
<span class="slider"></span>
1395+
</label>
1396+
<span class="switch-label">Use heuristics when send button is not found</span>
1397+
</div>
1398+
<br />
13741399
<!-- Collapsible Help Section -->
13751400
<div class="collapsible" id="advancedHelpSection">
13761401
<div class="section-header subsection-header">
@@ -1414,6 +1439,29 @@ <h3>Help <span class="expand-text">(click to expand)</span></h3>
14141439
Format: JSON array for containers/sendButtons/editors, string
14151440
for buttonsContainerId
14161441
</p>
1442+
<br />
1443+
<p><strong>Auto-Detection (Heuristics)</strong></p>
1444+
<p>
1445+
If the configured selectors fail (e.g., after a website update),
1446+
the extension attempts to "guess" the correct elements using
1447+
smart heuristics.
1448+
</p>
1449+
<ul>
1450+
<li>
1451+
<strong>Editor Heuristics:</strong> Scans for visible text
1452+
areas and content-editable fields, usually picking the
1453+
largest one near the bottom.
1454+
</li>
1455+
<li>
1456+
<strong>Send Button Heuristics:</strong> Looks for buttons
1457+
with "Send" icons or text, especially those positioned near
1458+
the found editor.
1459+
</li>
1460+
</ul>
1461+
<p>
1462+
Use the toggles above to disable this behavior if it
1463+
incorrectly identifies elements.
1464+
</p>
14171465
</div>
14181466
</div>
14191467
</div>

0 commit comments

Comments
 (0)