Add configurable SystemOne batching and release version 4.1.0

This commit is contained in:
Jordan Wages 2026-10-08 09:42:26 -05:00
commit 7edae937b3
13 changed files with 400 additions and 31 deletions

View file

@ -4,6 +4,7 @@ import { validCacheEntry } from '../modules/AiClassifier.js';
const storage = (globalThis.messenger ?? browser).storage;
const KEY_GROUPS = {
settings: [
'systemOneProcessAll', 'systemOneBatchSize',
'apiType', 'decisionThreshold', 'inputCharacterLimit', 'tokenReduction',
'endpoint',
'model',

View file

@ -78,6 +78,15 @@
<option value="systemone">SystemOne</option>
</select></div>
</div>
<div class="field systemone-only">
<label class="checkbox"><input type="checkbox" id="systemone-process-all"> Process all</label>
<p class="help">Group all eligible, uncached rules for each email into one request. Actions still follow rule order and stop-after-match.</p>
</div>
<div class="field systemone-only">
<label class="label" for="systemone-batch-size">Rules per request</label>
<input class="input" id="systemone-batch-size" type="number" min="1" step="1" required>
<p class="help">Turn off Process all to limit each request. Use 1 for one rule at a time.</p>
</div>
<div class="field systemone-only">
<label class="label" for="decision-threshold">Default match probability threshold (%)</label>
<input class="input" id="decision-threshold" type="number" min="0" max="100" step="any">

View file

@ -7,6 +7,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const { DEFAULT_AI_PARAMS } = await import(browser.runtime.getURL('modules/defaultParams.js'));
const { validateSettings, migratedInputLimit } = await import(browser.runtime.getURL('modules/classificationConfig.js'));
const defaults = await storage.local.get([
'systemOneProcessAll', 'systemOneBatchSize',
'apiType', 'decisionThreshold', 'inputCharacterLimit',
'endpoint',
'customSystemPrompt',
@ -90,6 +91,15 @@ document.addEventListener('DOMContentLoaded', async () => {
});
const apiTypeInput = document.getElementById('api-type');
apiTypeInput.value = defaults.apiType || 'chat-completions';
const processAllInput = document.getElementById('systemone-process-all');
const batchSizeInput = document.getElementById('systemone-batch-size');
processAllInput.checked = defaults.systemOneProcessAll ?? true;
let savedBatchSize = defaults.systemOneBatchSize ?? 8;
batchSizeInput.value = savedBatchSize;
function updateBatchControls() {
batchSizeInput.disabled = processAllInput.checked || apiTypeInput.value !== 'systemone';
}
processAllInput.addEventListener('change', updateBatchControls);
const thresholdInput = document.getElementById('decision-threshold');
thresholdInput.value = (defaults.decisionThreshold ?? 0.5) * 100;
const inputLimit = document.getElementById('input-character-limit');
@ -97,6 +107,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const settingsError = document.getElementById('settings-error');
function updateApiControls() {
const decision = apiTypeInput.value === 'systemone';
updateBatchControls();
document.querySelectorAll('.systemone-only').forEach(el => el.classList.toggle('is-hidden', !decision));
for (const id of ['system-instructions', 'openai-organization', 'openai-project', ...Object.keys(DEFAULT_AI_PARAMS)]) {
document.getElementById(id)?.closest('.field')?.classList.toggle('is-hidden', decision);
@ -1097,6 +1108,14 @@ document.addEventListener('DOMContentLoaded', async () => {
document.getElementById('save').addEventListener('click', async () => {
settingsError.textContent = '';
const apiType = apiTypeInput.value;
const systemOneProcessAll = processAllInput.checked;
const enteredBatchSize = Number(batchSizeInput.value);
const validBatchSize = Number.isSafeInteger(enteredBatchSize) && enteredBatchSize > 0;
if (!batchSizeInput.disabled && !validBatchSize) {
settingsError.textContent = 'Rules per request must be a positive whole number.';
return;
}
const systemOneBatchSize = validBatchSize ? enteredBatchSize : savedBatchSize;
const decisionThreshold = thresholdInput.value.trim() === '' ? NaN : Number(thresholdInput.value) / 100;
const inputCharacterLimit = Number(inputLimit.value);
const endpoint = endpointInput.value.trim();
@ -1127,13 +1146,15 @@ document.addEventListener('DOMContentLoaded', async () => {
const tokenReduction = tokenReductionToggle.checked;
const showDebugTab = debugTabToggle.checked;
const theme = themeSelect.value;
try { validateSettings({ apiType, decisionThreshold, inputCharacterLimit, aiRules: rules }); }
try { validateSettings({ systemOneProcessAll, systemOneBatchSize, apiType, decisionThreshold, inputCharacterLimit, aiRules: rules }); }
catch (error) { settingsError.textContent = error.message; return; }
await storage.local.set({ apiType, decisionThreshold, inputCharacterLimit, endpoint, model, apiKey, openaiOrganization, openaiProject, customSystemPrompt, aiParams: aiParamsSave, debugLogging, htmlToMarkdown, stripUrlParams, altTextImages, collapseWhitespace, tokenReduction, aiRules: rules, theme, showDebugTab });
await storage.local.set({ systemOneProcessAll, systemOneBatchSize, apiType, decisionThreshold, inputCharacterLimit, endpoint, model, apiKey, openaiOrganization, openaiProject, customSystemPrompt, aiParams: aiParamsSave, debugLogging, htmlToMarkdown, stripUrlParams, altTextImages, collapseWhitespace, tokenReduction, aiRules: rules, theme, showDebugTab });
savedBatchSize = systemOneBatchSize;
batchSizeInput.value = savedBatchSize;
await storage.local.remove(['templateName', 'customTemplate']);
await applyTheme(theme);
try {
await AiClassifier.setConfig({ apiType, decisionThreshold, inputCharacterLimit, endpoint, model, apiKey, openaiOrganization, openaiProject, customSystemPrompt, aiParams: aiParamsSave, debugLogging });
await AiClassifier.setConfig({ systemOneProcessAll, systemOneBatchSize, apiType, decisionThreshold, inputCharacterLimit, endpoint, model, apiKey, openaiOrganization, openaiProject, customSystemPrompt, aiParams: aiParamsSave, debugLogging });
logger.setDebug(debugLogging);
} catch (e) {
logger.aiLog('[options] failed to apply config', {level: 'error'}, e);