Compare commits
No commits in common. "main" and "codex/redesign-delete-and-enable/disable-buttons" have entirely different histories.
main
...
codex/rede
|
@ -28,7 +28,7 @@ This file provides guidelines for codex agents contributing to the Sortana proje
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
There are currently no automated tests for this project. If you add tests in the future, specify the commands to run them here. For now, verification must happen manually in Thunderbird. Do **not** run the `ps1` build script or the SVG processing script.
|
There are currently no automated tests for this project. If you add tests in the future, specify the commands to run them here. For now, verification must happen manually in Thunderbird.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ message meets a specified criterion.
|
||||||
- **Advanced parameters** – tune generation settings like temperature, top‑p and more from the options page.
|
- **Advanced parameters** – tune generation settings like temperature, top‑p and more from the options page.
|
||||||
- **Markdown conversion** – optionally convert HTML bodies to Markdown before sending them to the AI service.
|
- **Markdown conversion** – optionally convert HTML bodies to Markdown before sending them to the AI service.
|
||||||
- **Debug logging** – optional colorized logs help troubleshoot interactions with the AI service.
|
- **Debug logging** – optional colorized logs help troubleshoot interactions with the AI service.
|
||||||
- **Debug tab** – view the last request payload and a diff between the unaltered message text and the final prompt.
|
|
||||||
- **Light/Dark themes** – automatically match Thunderbird's appearance with optional manual override.
|
- **Light/Dark themes** – automatically match Thunderbird's appearance with optional manual override.
|
||||||
- **Automatic rules** – create rules that tag, move, copy, forward, reply, delete, archive, mark read/unread or flag/unflag messages based on AI classification. Rules can optionally apply only to unread messages and can ignore messages outside a chosen age range.
|
- **Automatic rules** – create rules that tag, move, copy, forward, reply, delete, archive, mark read/unread or flag/unflag messages based on AI classification. Rules can optionally apply only to unread messages and can ignore messages outside a chosen age range.
|
||||||
- **Rule ordering** – drag rules to prioritize them and optionally stop processing after a match.
|
- **Rule ordering** – drag rules to prioritize them and optionally stop processing after a match.
|
||||||
|
@ -79,9 +78,8 @@ Sortana is implemented entirely with standard WebExtension scripts—no custom e
|
||||||
reorder them, check *Only apply to unread messages* to skip read mail,
|
reorder them, check *Only apply to unread messages* to skip read mail,
|
||||||
set optional minimum or maximum message age limits, select the accounts or
|
set optional minimum or maximum message age limits, select the accounts or
|
||||||
folders a rule should apply to. Use the
|
folders a rule should apply to. Use the
|
||||||
slashed-circle/check button to disable or re-enable a rule. The small
|
slashed-circle/check button to disable or re-enable a rule, and
|
||||||
circle buttons for optional conditions show a filled dot when active, and
|
check *Stop after match* to halt further processing. Forward and reply actions
|
||||||
check *Stop after match* to halt further processing. Forward and reply actions
|
|
||||||
open a compose window using the account that received the message.
|
open a compose window using the account that received the message.
|
||||||
3. Save your settings. New mail will be evaluated automatically using the
|
3. Save your settings. New mail will be evaluated automatically using the
|
||||||
configured rules.
|
configured rules.
|
||||||
|
@ -127,7 +125,6 @@ Sortana requests the following Thunderbird permissions:
|
||||||
- `accountsRead` – list accounts and folders for move or copy actions.
|
- `accountsRead` – list accounts and folders for move or copy actions.
|
||||||
- `menus` – add context menu commands.
|
- `menus` – add context menu commands.
|
||||||
- `tabs` – open new tabs and query the active tab.
|
- `tabs` – open new tabs and query the active tab.
|
||||||
- `notifications` – display error notifications.
|
|
||||||
- `compose` – create reply and forward compose windows for matching rules.
|
- `compose` – create reply and forward compose windows for matching rules.
|
||||||
|
|
||||||
## Thunderbird Add-on Store Disclosures
|
## Thunderbird Add-on Store Disclosures
|
||||||
|
@ -141,8 +138,6 @@ uses the following third party libraries:
|
||||||
- MIT License
|
- MIT License
|
||||||
- [turndown v7.2.0](https://github.com/mixmark-io/turndown/tree/v7.2.0)
|
- [turndown v7.2.0](https://github.com/mixmark-io/turndown/tree/v7.2.0)
|
||||||
- MIT License
|
- MIT License
|
||||||
- [diff](https://github.com/google/diff-match-patch/blob/62f2e689f498f9c92dbc588c58750addec9b1654/javascript/diff_match_patch_uncompressed.js)
|
|
||||||
- Apache-2.0 license
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,7 @@
|
||||||
"options.htmlToMarkdown": { "message": "Convert HTML body to Markdown" },
|
"options.htmlToMarkdown": { "message": "Convert HTML body to Markdown" },
|
||||||
"options.stripUrlParams": { "message": "Remove URL tracking parameters" },
|
"options.stripUrlParams": { "message": "Remove URL tracking parameters" },
|
||||||
"options.altTextImages": { "message": "Replace images with alt text" },
|
"options.altTextImages": { "message": "Replace images with alt text" },
|
||||||
"options.collapseWhitespace": { "message": "Collapse long whitespace" },
|
"options.collapseWhitespace": { "message": "Collapse long whitespace" }
|
||||||
"options.tokenReduction": { "message": "Aggressive token reduction" }
|
|
||||||
,"action.read": { "message": "read" }
|
,"action.read": { "message": "read" }
|
||||||
,"action.flag": { "message": "flag" }
|
,"action.flag": { "message": "flag" }
|
||||||
,"action.copy": { "message": "copy" }
|
,"action.copy": { "message": "copy" }
|
||||||
|
|
|
@ -108,7 +108,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "img", "img", "{F266602F-175
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "js", "js", "{21D2A42C-3F85-465C-9141-C106AFD92B68}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "js", "js", "{21D2A42C-3F85-465C-9141-C106AFD92B68}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
resources\js\diff_match_patch_uncompressed.js = resources\js\diff_match_patch_uncompressed.js
|
|
||||||
resources\js\turndown.js = resources\js\turndown.js
|
resources\js\turndown.js = resources\js\turndown.js
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
|
117
background.js
|
@ -26,14 +26,11 @@ let htmlToMarkdown = false;
|
||||||
let stripUrlParams = false;
|
let stripUrlParams = false;
|
||||||
let altTextImages = false;
|
let altTextImages = false;
|
||||||
let collapseWhitespace = false;
|
let collapseWhitespace = false;
|
||||||
let tokenReduction = false;
|
|
||||||
let maxTokens = 4096;
|
|
||||||
let TurndownService = null;
|
let TurndownService = null;
|
||||||
let userTheme = 'auto';
|
let userTheme = 'auto';
|
||||||
let currentTheme = 'light';
|
let currentTheme = 'light';
|
||||||
let detectSystemTheme;
|
let detectSystemTheme;
|
||||||
let errorPending = false;
|
let errorPending = false;
|
||||||
let showDebugTab = false;
|
|
||||||
const ERROR_NOTIFICATION_ID = 'sortana-error';
|
const ERROR_NOTIFICATION_ID = 'sortana-error';
|
||||||
|
|
||||||
function normalizeRules(rules) {
|
function normalizeRules(rules) {
|
||||||
|
@ -128,16 +125,12 @@ function byteSize(str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceInlineBase64(text) {
|
function replaceInlineBase64(text) {
|
||||||
return text.replace(/(?:data:[^;]+;base64,)?[A-Za-z0-9+/=\r\n]{100,}/g,
|
return text.replace(/[A-Za-z0-9+/]{100,}={0,2}/g,
|
||||||
m => tokenReduction ? '__BASE64__' : `[base64: ${byteSize(m)} bytes]`);
|
m => `[base64: ${byteSize(m)} bytes]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeString(text) {
|
function sanitizeString(text) {
|
||||||
let t = String(text);
|
let t = String(text);
|
||||||
if (tokenReduction) {
|
|
||||||
t = t.replace(/<!--.*?-->/gs, '')
|
|
||||||
.replace(/url\([^\)]*\)/gi, 'url(__IMG__)');
|
|
||||||
}
|
|
||||||
if (stripUrlParams) {
|
if (stripUrlParams) {
|
||||||
t = t.replace(/https?:\/\/[^\s)]+/g, m => {
|
t = t.replace(/https?:\/\/[^\s)]+/g, m => {
|
||||||
const idx = m.indexOf('?');
|
const idx = m.indexOf('?');
|
||||||
|
@ -145,7 +138,7 @@ function sanitizeString(text) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (collapseWhitespace) {
|
if (collapseWhitespace) {
|
||||||
t = t.replace(/[\u200B-\u200D\u2060\s]{2,}/g, ' ').replace(/\n{3,}/g, '\n\n');
|
t = t.replace(/[ \t\u00A0]{2,}/g, ' ').replace(/\n{3,}/g, '\n\n');
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
@ -164,26 +157,12 @@ function collectText(part, bodyParts, attachments) {
|
||||||
attachments.push(`${name} (${ct}, ${part.size || byteSize(body)} bytes)`);
|
attachments.push(`${name} (${ct}, ${part.size || byteSize(body)} bytes)`);
|
||||||
} else if (ct.startsWith("text/html")) {
|
} else if (ct.startsWith("text/html")) {
|
||||||
const doc = new DOMParser().parseFromString(body, 'text/html');
|
const doc = new DOMParser().parseFromString(body, 'text/html');
|
||||||
if (tokenReduction) {
|
if (altTextImages) {
|
||||||
doc.querySelectorAll('script,style').forEach(el => el.remove());
|
doc.querySelectorAll('img').forEach(img => {
|
||||||
const walker = doc.createTreeWalker(doc, NodeFilter.SHOW_COMMENT);
|
const alt = img.getAttribute('alt') || '';
|
||||||
let node;
|
img.replaceWith(doc.createTextNode(alt));
|
||||||
while ((node = walker.nextNode())) {
|
|
||||||
node.parentNode.removeChild(node);
|
|
||||||
}
|
|
||||||
doc.querySelectorAll('*').forEach(el => {
|
|
||||||
for (const attr of Array.from(el.attributes)) {
|
|
||||||
if (!['href','src','alt'].includes(attr.name)) {
|
|
||||||
el.removeAttribute(attr.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
doc.querySelectorAll('img').forEach(img => {
|
|
||||||
const alt = img.getAttribute('alt') || '';
|
|
||||||
const text = altTextImages ? alt : '__IMG__';
|
|
||||||
img.replaceWith(doc.createTextNode(text));
|
|
||||||
});
|
|
||||||
if (stripUrlParams) {
|
if (stripUrlParams) {
|
||||||
doc.querySelectorAll('[href]').forEach(a => {
|
doc.querySelectorAll('[href]').forEach(a => {
|
||||||
const href = a.getAttribute('href');
|
const href = a.getAttribute('href');
|
||||||
|
@ -210,46 +189,17 @@ function collectText(part, bodyParts, attachments) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectRawText(part, bodyParts, attachments) {
|
function buildEmailText(full) {
|
||||||
if (part.parts && part.parts.length) {
|
|
||||||
for (const p of part.parts) collectRawText(p, bodyParts, attachments);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ct = (part.contentType || "text/plain").toLowerCase();
|
|
||||||
const cd = (part.headers?.["content-disposition"]?.[0] || "").toLowerCase();
|
|
||||||
const body = String(part.body || "");
|
|
||||||
if (cd.includes("attachment") || !ct.startsWith("text/")) {
|
|
||||||
const nameMatch = /filename\s*=\s*"?([^";]+)/i.exec(cd) || /name\s*=\s*"?([^";]+)/i.exec(part.headers?.["content-type"]?.[0] || "");
|
|
||||||
const name = nameMatch ? nameMatch[1] : "";
|
|
||||||
attachments.push(`${name} (${ct}, ${part.size || byteSize(body)} bytes)`);
|
|
||||||
} else if (ct.startsWith("text/html")) {
|
|
||||||
const doc = new DOMParser().parseFromString(body, 'text/html');
|
|
||||||
bodyParts.push(doc.body.textContent || "");
|
|
||||||
} else {
|
|
||||||
bodyParts.push(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildEmailText(full, applyTransforms = true) {
|
|
||||||
const bodyParts = [];
|
const bodyParts = [];
|
||||||
const attachments = [];
|
const attachments = [];
|
||||||
const collect = applyTransforms ? collectText : collectRawText;
|
collectText(full, bodyParts, attachments);
|
||||||
collect(full, bodyParts, attachments);
|
|
||||||
const headers = Object.entries(full.headers || {})
|
const headers = Object.entries(full.headers || {})
|
||||||
.map(([k, v]) => `${k}: ${v.join(' ')}`)
|
.map(([k, v]) => `${k}: ${v.join(' ')}`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
const attachInfo = `Attachments: ${attachments.length}` +
|
const attachInfo = `Attachments: ${attachments.length}` +
|
||||||
(attachments.length ? "\n" + attachments.map(a => ` - ${a}`).join('\n') : "");
|
(attachments.length ? "\n" + attachments.map(a => ` - ${a}`).join('\n') : "");
|
||||||
let combined = `${headers}\n${attachInfo}\n\n${bodyParts.join('\n')}`.trim();
|
const combined = `${headers}\n${attachInfo}\n\n${bodyParts.join('\n')}`.trim();
|
||||||
if (applyTransforms && tokenReduction) {
|
return sanitizeString(combined);
|
||||||
const seen = new Set();
|
|
||||||
combined = combined.split('\n').filter(l => {
|
|
||||||
if (seen.has(l)) return false;
|
|
||||||
seen.add(l);
|
|
||||||
return true;
|
|
||||||
}).join('\n');
|
|
||||||
}
|
|
||||||
return applyTransforms ? sanitizeString(combined) : combined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTimingStats(elapsed) {
|
function updateTimingStats(elapsed) {
|
||||||
|
@ -283,17 +233,7 @@ async function processMessage(id) {
|
||||||
updateActionIcon();
|
updateActionIcon();
|
||||||
try {
|
try {
|
||||||
const full = await messenger.messages.getFull(id);
|
const full = await messenger.messages.getFull(id);
|
||||||
const originalText = buildEmailText(full, false);
|
const text = buildEmailText(full);
|
||||||
let text = buildEmailText(full);
|
|
||||||
if (tokenReduction && maxTokens > 0) {
|
|
||||||
const limit = Math.floor(maxTokens * 0.9);
|
|
||||||
if (text.length > limit) {
|
|
||||||
text = text.slice(0, limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (showDebugTab) {
|
|
||||||
await storage.local.set({ lastFullText: originalText, lastPromptText: text });
|
|
||||||
}
|
|
||||||
let hdr;
|
let hdr;
|
||||||
let currentTags = [];
|
let currentTags = [];
|
||||||
let alreadyRead = false;
|
let alreadyRead = false;
|
||||||
|
@ -451,7 +391,7 @@ async function clearCacheForMessages(idsInput) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const store = await storage.local.get(["endpoint", "templateName", "customTemplate", "customSystemPrompt", "aiParams", "debugLogging", "htmlToMarkdown", "stripUrlParams", "altTextImages", "collapseWhitespace", "tokenReduction", "aiRules", "theme", "errorPending", "showDebugTab"]);
|
const store = await storage.local.get(["endpoint", "templateName", "customTemplate", "customSystemPrompt", "aiParams", "debugLogging", "htmlToMarkdown", "stripUrlParams", "altTextImages", "collapseWhitespace", "aiRules", "theme", "errorPending"]);
|
||||||
logger.setDebug(store.debugLogging);
|
logger.setDebug(store.debugLogging);
|
||||||
await AiClassifier.setConfig(store);
|
await AiClassifier.setConfig(store);
|
||||||
userTheme = store.theme || 'auto';
|
userTheme = store.theme || 'auto';
|
||||||
|
@ -461,12 +401,7 @@ async function clearCacheForMessages(idsInput) {
|
||||||
stripUrlParams = store.stripUrlParams === true;
|
stripUrlParams = store.stripUrlParams === true;
|
||||||
altTextImages = store.altTextImages === true;
|
altTextImages = store.altTextImages === true;
|
||||||
collapseWhitespace = store.collapseWhitespace === true;
|
collapseWhitespace = store.collapseWhitespace === true;
|
||||||
tokenReduction = store.tokenReduction === true;
|
|
||||||
if (store.aiParams && typeof store.aiParams.max_tokens !== 'undefined') {
|
|
||||||
maxTokens = parseInt(store.aiParams.max_tokens) || maxTokens;
|
|
||||||
}
|
|
||||||
errorPending = store.errorPending === true;
|
errorPending = store.errorPending === true;
|
||||||
showDebugTab = store.showDebugTab === true;
|
|
||||||
const savedStats = await storage.local.get('classifyStats');
|
const savedStats = await storage.local.get('classifyStats');
|
||||||
if (savedStats.classifyStats && typeof savedStats.classifyStats === 'object') {
|
if (savedStats.classifyStats && typeof savedStats.classifyStats === 'object') {
|
||||||
Object.assign(timingStats, savedStats.classifyStats);
|
Object.assign(timingStats, savedStats.classifyStats);
|
||||||
|
@ -482,25 +417,6 @@ async function clearCacheForMessages(idsInput) {
|
||||||
aiRules = normalizeRules(newRules);
|
aiRules = normalizeRules(newRules);
|
||||||
logger.aiLog("aiRules updated from storage change", { debug: true }, aiRules);
|
logger.aiLog("aiRules updated from storage change", { debug: true }, aiRules);
|
||||||
}
|
}
|
||||||
if (changes.endpoint || changes.templateName || changes.customTemplate || changes.customSystemPrompt || changes.aiParams || changes.debugLogging) {
|
|
||||||
const config = {};
|
|
||||||
if (changes.endpoint) config.endpoint = changes.endpoint.newValue;
|
|
||||||
if (changes.templateName) config.templateName = changes.templateName.newValue;
|
|
||||||
if (changes.customTemplate) config.customTemplate = changes.customTemplate.newValue;
|
|
||||||
if (changes.customSystemPrompt) config.customSystemPrompt = changes.customSystemPrompt.newValue;
|
|
||||||
if (changes.aiParams) {
|
|
||||||
config.aiParams = changes.aiParams.newValue;
|
|
||||||
if (changes.aiParams.newValue && typeof changes.aiParams.newValue.max_tokens !== 'undefined') {
|
|
||||||
maxTokens = parseInt(changes.aiParams.newValue.max_tokens) || maxTokens;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changes.debugLogging) {
|
|
||||||
config.debugLogging = changes.debugLogging.newValue === true;
|
|
||||||
logger.setDebug(config.debugLogging);
|
|
||||||
}
|
|
||||||
await AiClassifier.setConfig(config);
|
|
||||||
logger.aiLog("AiClassifier config updated from storage change", { debug: true }, config);
|
|
||||||
}
|
|
||||||
if (changes.htmlToMarkdown) {
|
if (changes.htmlToMarkdown) {
|
||||||
htmlToMarkdown = changes.htmlToMarkdown.newValue === true;
|
htmlToMarkdown = changes.htmlToMarkdown.newValue === true;
|
||||||
logger.aiLog("htmlToMarkdown updated from storage change", { debug: true }, htmlToMarkdown);
|
logger.aiLog("htmlToMarkdown updated from storage change", { debug: true }, htmlToMarkdown);
|
||||||
|
@ -517,13 +433,6 @@ async function clearCacheForMessages(idsInput) {
|
||||||
collapseWhitespace = changes.collapseWhitespace.newValue === true;
|
collapseWhitespace = changes.collapseWhitespace.newValue === true;
|
||||||
logger.aiLog("collapseWhitespace updated from storage change", { debug: true }, collapseWhitespace);
|
logger.aiLog("collapseWhitespace updated from storage change", { debug: true }, collapseWhitespace);
|
||||||
}
|
}
|
||||||
if (changes.tokenReduction) {
|
|
||||||
tokenReduction = changes.tokenReduction.newValue === true;
|
|
||||||
logger.aiLog("tokenReduction updated from storage change", { debug: true }, tokenReduction);
|
|
||||||
}
|
|
||||||
if (changes.showDebugTab) {
|
|
||||||
showDebugTab = changes.showDebugTab.newValue === true;
|
|
||||||
}
|
|
||||||
if (changes.errorPending) {
|
if (changes.errorPending) {
|
||||||
errorPending = changes.errorPending.newValue === true;
|
errorPending = changes.errorPending.newValue === true;
|
||||||
updateActionIcon();
|
updateActionIcon();
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Sortana",
|
"name": "Sortana",
|
||||||
"version": "2.2.0",
|
"version": "2.1.1",
|
||||||
"default_locale": "en-US",
|
"default_locale": "en-US",
|
||||||
"applications": {
|
"applications": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "ai-filter@jordanwages",
|
"id": "ai-filter@jordanwages",
|
||||||
"strict_min_version": "128.0",
|
"strict_min_version": "128.0",
|
||||||
"strict_max_version": "140.*"
|
"strict_max_version": "139.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
|
@ -32,18 +32,17 @@
|
||||||
"page": "options/options.html",
|
"page": "options/options.html",
|
||||||
"open_in_tab": true
|
"open_in_tab": true
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage",
|
"storage",
|
||||||
"messagesRead",
|
"messagesRead",
|
||||||
"messagesMove",
|
"messagesMove",
|
||||||
"messagesUpdate",
|
"messagesUpdate",
|
||||||
"messagesTagsList",
|
"messagesTagsList",
|
||||||
"accountsRead",
|
"accountsRead",
|
||||||
"menus",
|
"menus",
|
||||||
"notifications",
|
"scripting",
|
||||||
"scripting",
|
"tabs",
|
||||||
"tabs",
|
"theme",
|
||||||
"theme",
|
"compose"
|
||||||
"compose"
|
]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,11 +308,6 @@ async function classifyText(text, criterion, cacheKey = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = buildPayload(text, criterion);
|
const payload = buildPayload(text, criterion);
|
||||||
try {
|
|
||||||
await storage.local.set({ lastPayload: JSON.parse(payload) });
|
|
||||||
} catch (e) {
|
|
||||||
aiLog('failed to save last payload', { level: 'warn' }, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
aiLog(`[AiClassifier] Sending classification request to ${gEndpoint}`, {debug: true});
|
aiLog(`[AiClassifier] Sending classification request to ${gEndpoint}`, {debug: true});
|
||||||
aiLog(`[AiClassifier] Classification request payload:`, { debug: true }, payload);
|
aiLog(`[AiClassifier] Classification request payload:`, { debug: true }, payload);
|
||||||
|
|
|
@ -31,10 +31,6 @@
|
||||||
.tag {
|
.tag {
|
||||||
--bulma-tag-h: 318;
|
--bulma-tag-h: 318;
|
||||||
}
|
}
|
||||||
#diff-display {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -51,7 +47,6 @@
|
||||||
<li class="is-active" data-tab="settings"><a><span class="icon is-small"><img data-icon="settings" data-size="16" src="../resources/img/settings-light-16.png" alt=""></span><span>Settings</span></a></li>
|
<li class="is-active" data-tab="settings"><a><span class="icon is-small"><img data-icon="settings" data-size="16" src="../resources/img/settings-light-16.png" alt=""></span><span>Settings</span></a></li>
|
||||||
<li data-tab="rules"><a><span class="icon is-small"><img data-icon="clipboarddata" data-size="16" src="../resources/img/clipboarddata-light-16.png" alt=""></span><span>Rules</span></a></li>
|
<li data-tab="rules"><a><span class="icon is-small"><img data-icon="clipboarddata" data-size="16" src="../resources/img/clipboarddata-light-16.png" alt=""></span><span>Rules</span></a></li>
|
||||||
<li data-tab="maintenance"><a><span class="icon is-small"><img data-icon="gear" data-size="16" src="../resources/img/gear-light-16.png" alt=""></span><span>Maintenance</span></a></li>
|
<li data-tab="maintenance"><a><span class="icon is-small"><img data-icon="gear" data-size="16" src="../resources/img/gear-light-16.png" alt=""></span><span>Maintenance</span></a></li>
|
||||||
<li id="debug-tab-button" class="is-hidden" data-tab="debug"><a><span class="icon is-small"><img data-icon="average" data-size="16" src="../resources/img/average-light-16.png" alt=""></span><span>Debug</span></a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -149,16 +144,6 @@
|
||||||
<input type="checkbox" id="collapse-whitespace"> Collapse long whitespace
|
<input type="checkbox" id="collapse-whitespace"> Collapse long whitespace
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" id="token-reduction"> Aggressive token reduction
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" id="show-debug-tab"> Show debug information
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="max_tokens">Max tokens</label>
|
<label class="label" for="max_tokens">Max tokens</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
@ -283,21 +268,8 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="debug-tab" class="tab-content is-hidden">
|
|
||||||
<h2 class="title is-4">
|
|
||||||
<span class="icon is-small"><img data-icon="average" data-size="16" src="../resources/img/average-light-16.png" alt=""></span>
|
|
||||||
<span>Debug</span>
|
|
||||||
</h2>
|
|
||||||
<pre id="payload-display"></pre>
|
|
||||||
<div id="diff-container" class="mt-4 is-hidden">
|
|
||||||
<label class="label">Prompt diff</label>
|
|
||||||
<div id="diff-display" class="box content is-family-monospace"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<script src="../resources/js/diff_match_patch_uncompressed.js"></script>
|
|
||||||
<script src="options.js"></script>
|
<script src="options.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -16,14 +16,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
'stripUrlParams',
|
'stripUrlParams',
|
||||||
'altTextImages',
|
'altTextImages',
|
||||||
'collapseWhitespace',
|
'collapseWhitespace',
|
||||||
'tokenReduction',
|
|
||||||
'aiRules',
|
'aiRules',
|
||||||
'aiCache',
|
'aiCache',
|
||||||
'theme',
|
'theme'
|
||||||
'showDebugTab',
|
|
||||||
'lastPayload',
|
|
||||||
'lastFullText',
|
|
||||||
'lastPromptText'
|
|
||||||
]);
|
]);
|
||||||
const tabButtons = document.querySelectorAll('#main-tabs li');
|
const tabButtons = document.querySelectorAll('#main-tabs li');
|
||||||
const tabs = document.querySelectorAll('.tab-content');
|
const tabs = document.querySelectorAll('.tab-content');
|
||||||
|
@ -68,33 +63,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await applyTheme(themeSelect.value);
|
await applyTheme(themeSelect.value);
|
||||||
const payloadDisplay = document.getElementById('payload-display');
|
|
||||||
const diffDisplay = document.getElementById('diff-display');
|
|
||||||
const diffContainer = document.getElementById('diff-container');
|
|
||||||
|
|
||||||
let lastFullText = defaults.lastFullText || '';
|
|
||||||
let lastPromptText = defaults.lastPromptText || '';
|
|
||||||
let lastPayload = defaults.lastPayload ? JSON.stringify(defaults.lastPayload, null, 2) : '';
|
|
||||||
|
|
||||||
if (lastPayload) {
|
|
||||||
payloadDisplay.textContent = lastPayload;
|
|
||||||
}
|
|
||||||
if (lastFullText && lastPromptText && diff_match_patch) {
|
|
||||||
const dmp = new diff_match_patch();
|
|
||||||
dmp.Diff_EditCost = 4;
|
|
||||||
const diffs = dmp.diff_main(lastFullText, lastPromptText);
|
|
||||||
dmp.diff_cleanupEfficiency(diffs);
|
|
||||||
const hasDiff = diffs.some(d => d[0] !== 0);
|
|
||||||
if (hasDiff) {
|
|
||||||
diffDisplay.innerHTML = dmp.diff_prettyHtml(diffs);
|
|
||||||
diffContainer.classList.remove('is-hidden');
|
|
||||||
} else {
|
|
||||||
diffDisplay.innerHTML = '';
|
|
||||||
diffContainer.classList.add('is-hidden');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
diffContainer.classList.add('is-hidden');
|
|
||||||
}
|
|
||||||
themeSelect.addEventListener('change', async () => {
|
themeSelect.addEventListener('change', async () => {
|
||||||
markDirty();
|
markDirty();
|
||||||
await applyTheme(themeSelect.value);
|
await applyTheme(themeSelect.value);
|
||||||
|
@ -147,20 +115,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const collapseWhitespaceToggle = document.getElementById('collapse-whitespace');
|
const collapseWhitespaceToggle = document.getElementById('collapse-whitespace');
|
||||||
collapseWhitespaceToggle.checked = defaults.collapseWhitespace === true;
|
collapseWhitespaceToggle.checked = defaults.collapseWhitespace === true;
|
||||||
|
|
||||||
const tokenReductionToggle = document.getElementById('token-reduction');
|
|
||||||
tokenReductionToggle.checked = defaults.tokenReduction === true;
|
|
||||||
|
|
||||||
const debugTabToggle = document.getElementById('show-debug-tab');
|
|
||||||
const debugTabBtn = document.getElementById('debug-tab-button');
|
|
||||||
function updateDebugTab() {
|
|
||||||
const visible = debugTabToggle.checked;
|
|
||||||
debugTabBtn.classList.toggle('is-hidden', !visible);
|
|
||||||
}
|
|
||||||
debugTabToggle.checked = defaults.showDebugTab === true;
|
|
||||||
debugTabToggle.addEventListener('change', () => { updateDebugTab(); markDirty(); });
|
|
||||||
updateDebugTab();
|
|
||||||
|
|
||||||
|
|
||||||
const aiParams = Object.assign({}, DEFAULT_AI_PARAMS, defaults.aiParams || {});
|
const aiParams = Object.assign({}, DEFAULT_AI_PARAMS, defaults.aiParams || {});
|
||||||
for (const [key, val] of Object.entries(aiParams)) {
|
for (const [key, val] of Object.entries(aiParams)) {
|
||||||
const el = document.getElementById(key);
|
const el = document.getElementById(key);
|
||||||
|
@ -328,39 +282,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createConditionButton(label, sectionEl, checkbox, clearFn) {
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.type = 'button';
|
|
||||||
btn.className = 'button is-small is-light';
|
|
||||||
const icon = document.createElement('img');
|
|
||||||
icon.width = 16;
|
|
||||||
icon.height = 16;
|
|
||||||
icon.className = 'mr-1';
|
|
||||||
btn.appendChild(icon);
|
|
||||||
btn.append(label);
|
|
||||||
|
|
||||||
let active = checkbox ? checkbox.checked : sectionEl && !sectionEl.classList.contains('is-hidden');
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
btn.classList.toggle('is-active', active);
|
|
||||||
icon.src = browser.runtime.getURL(`resources/svg/${active ? 'circledot' : 'circle'}.svg`);
|
|
||||||
if (sectionEl) sectionEl.classList.toggle('is-hidden', !active);
|
|
||||||
if (checkbox) checkbox.checked = active;
|
|
||||||
if (!active && typeof clearFn === 'function') {
|
|
||||||
clearFn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
active = !active;
|
|
||||||
markDirty();
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
|
|
||||||
update();
|
|
||||||
return btn;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderRules(rules = []) {
|
function renderRules(rules = []) {
|
||||||
ruleCountEl.textContent = rules.length;
|
ruleCountEl.textContent = rules.length;
|
||||||
rulesContainer.innerHTML = '';
|
rulesContainer.innerHTML = '';
|
||||||
|
@ -415,7 +336,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const toggleBtn = document.createElement('button');
|
const toggleBtn = document.createElement('button');
|
||||||
toggleBtn.type = 'button';
|
toggleBtn.type = 'button';
|
||||||
toggleBtn.className = 'button is-small is-light rule-toggle';
|
toggleBtn.className = 'button is-small rule-toggle';
|
||||||
const toggleIcon = document.createElement('img');
|
const toggleIcon = document.createElement('img');
|
||||||
toggleIcon.width = 16;
|
toggleIcon.width = 16;
|
||||||
toggleIcon.height = 16;
|
toggleIcon.height = 16;
|
||||||
|
@ -470,7 +391,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
addAction.addEventListener('click', () => actionsContainer.appendChild(createActionRow()));
|
addAction.addEventListener('click', () => actionsContainer.appendChild(createActionRow()));
|
||||||
|
|
||||||
const stopLabel = document.createElement('label');
|
const stopLabel = document.createElement('label');
|
||||||
stopLabel.className = 'checkbox mt-2 is-hidden';
|
stopLabel.className = 'checkbox mt-2';
|
||||||
const stopCheck = document.createElement('input');
|
const stopCheck = document.createElement('input');
|
||||||
stopCheck.type = 'checkbox';
|
stopCheck.type = 'checkbox';
|
||||||
stopCheck.className = 'stop-processing';
|
stopCheck.className = 'stop-processing';
|
||||||
|
@ -479,7 +400,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
stopLabel.append(' Stop after match');
|
stopLabel.append(' Stop after match');
|
||||||
|
|
||||||
const unreadLabel = document.createElement('label');
|
const unreadLabel = document.createElement('label');
|
||||||
unreadLabel.className = 'checkbox mt-2 ml-4 is-hidden';
|
unreadLabel.className = 'checkbox mt-2 ml-4';
|
||||||
const unreadCheck = document.createElement('input');
|
const unreadCheck = document.createElement('input');
|
||||||
unreadCheck.type = 'checkbox';
|
unreadCheck.type = 'checkbox';
|
||||||
unreadCheck.className = 'unread-only';
|
unreadCheck.className = 'unread-only';
|
||||||
|
@ -488,7 +409,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
unreadLabel.append(' Only apply to unread messages');
|
unreadLabel.append(' Only apply to unread messages');
|
||||||
|
|
||||||
const ageBox = document.createElement('div');
|
const ageBox = document.createElement('div');
|
||||||
ageBox.className = 'field is-grouped mt-2 is-hidden';
|
ageBox.className = 'field is-grouped mt-2';
|
||||||
const minInput = document.createElement('input');
|
const minInput = document.createElement('input');
|
||||||
minInput.type = 'number';
|
minInput.type = 'number';
|
||||||
minInput.placeholder = 'Min days';
|
minInput.placeholder = 'Min days';
|
||||||
|
@ -505,7 +426,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
ageBox.appendChild(maxInput);
|
ageBox.appendChild(maxInput);
|
||||||
|
|
||||||
const acctBox = document.createElement('div');
|
const acctBox = document.createElement('div');
|
||||||
acctBox.className = 'field mt-2 is-hidden';
|
acctBox.className = 'field mt-2';
|
||||||
const acctLabel = document.createElement('label');
|
const acctLabel = document.createElement('label');
|
||||||
acctLabel.className = 'label';
|
acctLabel.className = 'label';
|
||||||
acctLabel.textContent = 'Accounts';
|
acctLabel.textContent = 'Accounts';
|
||||||
|
@ -529,7 +450,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
acctBox.appendChild(acctControl);
|
acctBox.appendChild(acctControl);
|
||||||
|
|
||||||
const folderBox = document.createElement('div');
|
const folderBox = document.createElement('div');
|
||||||
folderBox.className = 'field mt-2 is-hidden';
|
folderBox.className = 'field mt-2';
|
||||||
const folderLabel = document.createElement('label');
|
const folderLabel = document.createElement('label');
|
||||||
folderLabel.className = 'label';
|
folderLabel.className = 'label';
|
||||||
folderLabel.textContent = 'Folders';
|
folderLabel.textContent = 'Folders';
|
||||||
|
@ -552,51 +473,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
folderBox.appendChild(folderLabel);
|
folderBox.appendChild(folderLabel);
|
||||||
folderBox.appendChild(folderControl);
|
folderBox.appendChild(folderControl);
|
||||||
|
|
||||||
if (typeof rule.minAgeDays === 'number' || typeof rule.maxAgeDays === 'number') {
|
|
||||||
ageBox.classList.remove('is-hidden');
|
|
||||||
}
|
|
||||||
if ((rule.accounts || []).length) {
|
|
||||||
acctBox.classList.remove('is-hidden');
|
|
||||||
}
|
|
||||||
if ((rule.folders || []).length) {
|
|
||||||
folderBox.classList.remove('is-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
const condButtons = document.createElement('div');
|
|
||||||
condButtons.className = 'field is-grouped is-grouped-multiline mb-2';
|
|
||||||
|
|
||||||
function addCond(btn) {
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.className = 'control';
|
|
||||||
p.appendChild(btn);
|
|
||||||
condButtons.appendChild(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
addCond(createConditionButton('Stop', null, stopCheck, () => {
|
|
||||||
stopCheck.checked = false;
|
|
||||||
}));
|
|
||||||
addCond(createConditionButton('Unread', null, unreadCheck, () => {
|
|
||||||
unreadCheck.checked = false;
|
|
||||||
}));
|
|
||||||
addCond(createConditionButton('Age', ageBox, null, () => {
|
|
||||||
minInput.value = '';
|
|
||||||
maxInput.value = '';
|
|
||||||
}));
|
|
||||||
addCond(createConditionButton('Accounts', acctBox, null, () => {
|
|
||||||
for (const opt of acctSel.options) opt.selected = false;
|
|
||||||
}));
|
|
||||||
addCond(createConditionButton('Folders', folderBox, null, () => {
|
|
||||||
for (const opt of folderSel.options) opt.selected = false;
|
|
||||||
}));
|
|
||||||
|
|
||||||
const body = document.createElement('div');
|
const body = document.createElement('div');
|
||||||
body.className = 'message-body';
|
body.className = 'message-body';
|
||||||
body.appendChild(actionsContainer);
|
body.appendChild(actionsContainer);
|
||||||
body.appendChild(addAction);
|
body.appendChild(addAction);
|
||||||
const condDivider = document.createElement('hr');
|
|
||||||
condDivider.className = 'mt-3 mb-2';
|
|
||||||
body.appendChild(condDivider);
|
|
||||||
body.appendChild(condButtons);
|
|
||||||
body.appendChild(stopLabel);
|
body.appendChild(stopLabel);
|
||||||
body.appendChild(unreadLabel);
|
body.appendChild(unreadLabel);
|
||||||
body.appendChild(ageBox);
|
body.appendChild(ageBox);
|
||||||
|
@ -645,9 +525,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value);
|
const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value);
|
||||||
const accounts = [...(ruleEl.querySelector('.account-select')?.selectedOptions || [])].map(o => o.value);
|
const accounts = [...(ruleEl.querySelector('.account-select')?.selectedOptions || [])].map(o => o.value);
|
||||||
const folders = [...(ruleEl.querySelector('.folder-filter-select')?.selectedOptions || [])].map(o => o.value);
|
const folders = [...(ruleEl.querySelector('.folder-filter-select')?.selectedOptions || [])].map(o => o.value);
|
||||||
const rule = { criterion, actions, enabled };
|
const rule = { criterion, actions, unreadOnly, stopProcessing, enabled };
|
||||||
if (unreadOnly) rule.unreadOnly = true;
|
|
||||||
if (stopProcessing) rule.stopProcessing = true;
|
|
||||||
if (!isNaN(minAgeDays)) rule.minAgeDays = minAgeDays;
|
if (!isNaN(minAgeDays)) rule.minAgeDays = minAgeDays;
|
||||||
if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays;
|
if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays;
|
||||||
if (accounts.length) rule.accounts = accounts;
|
if (accounts.length) rule.accounts = accounts;
|
||||||
|
@ -744,38 +622,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
} catch {
|
} catch {
|
||||||
cacheCountEl.textContent = '?';
|
cacheCountEl.textContent = '?';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
if (debugTabToggle.checked) {
|
|
||||||
const latest = await storage.local.get(['lastPayload', 'lastFullText', 'lastPromptText']);
|
|
||||||
const payloadStr = latest.lastPayload ? JSON.stringify(latest.lastPayload, null, 2) : '';
|
|
||||||
if (payloadStr !== lastPayload) {
|
|
||||||
lastPayload = payloadStr;
|
|
||||||
payloadDisplay.textContent = payloadStr;
|
|
||||||
}
|
|
||||||
if (latest.lastFullText !== lastFullText || latest.lastPromptText !== lastPromptText) {
|
|
||||||
lastFullText = latest.lastFullText || '';
|
|
||||||
lastPromptText = latest.lastPromptText || '';
|
|
||||||
if (lastFullText && lastPromptText && diff_match_patch) {
|
|
||||||
const dmp = new diff_match_patch();
|
|
||||||
dmp.Diff_EditCost = 4;
|
|
||||||
const diffs = dmp.diff_main(lastFullText, lastPromptText);
|
|
||||||
dmp.diff_cleanupEfficiency(diffs);
|
|
||||||
const hasDiff = diffs.some(d => d[0] !== 0);
|
|
||||||
if (hasDiff) {
|
|
||||||
diffDisplay.innerHTML = dmp.diff_prettyHtml(diffs);
|
|
||||||
diffContainer.classList.remove('is-hidden');
|
|
||||||
} else {
|
|
||||||
diffDisplay.innerHTML = '';
|
|
||||||
diffContainer.classList.add('is-hidden');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
diffDisplay.innerHTML = '';
|
|
||||||
diffContainer.classList.add('is-hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshMaintenance();
|
refreshMaintenance();
|
||||||
|
@ -857,9 +703,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value);
|
const maxAgeDays = parseFloat(ruleEl.querySelector('.max-age')?.value);
|
||||||
const accounts = [...(ruleEl.querySelector('.account-select')?.selectedOptions || [])].map(o => o.value);
|
const accounts = [...(ruleEl.querySelector('.account-select')?.selectedOptions || [])].map(o => o.value);
|
||||||
const folders = [...(ruleEl.querySelector('.folder-filter-select')?.selectedOptions || [])].map(o => o.value);
|
const folders = [...(ruleEl.querySelector('.folder-filter-select')?.selectedOptions || [])].map(o => o.value);
|
||||||
const rule = { criterion, actions, enabled };
|
const rule = { criterion, actions, unreadOnly, stopProcessing, enabled };
|
||||||
if (unreadOnly) rule.unreadOnly = true;
|
|
||||||
if (stopProcessing) rule.stopProcessing = true;
|
|
||||||
if (!isNaN(minAgeDays)) rule.minAgeDays = minAgeDays;
|
if (!isNaN(minAgeDays)) rule.minAgeDays = minAgeDays;
|
||||||
if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays;
|
if (!isNaN(maxAgeDays)) rule.maxAgeDays = maxAgeDays;
|
||||||
if (accounts.length) rule.accounts = accounts;
|
if (accounts.length) rule.accounts = accounts;
|
||||||
|
@ -869,10 +713,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const stripUrlParams = stripUrlToggle.checked;
|
const stripUrlParams = stripUrlToggle.checked;
|
||||||
const altTextImages = altTextToggle.checked;
|
const altTextImages = altTextToggle.checked;
|
||||||
const collapseWhitespace = collapseWhitespaceToggle.checked;
|
const collapseWhitespace = collapseWhitespaceToggle.checked;
|
||||||
const tokenReduction = tokenReductionToggle.checked;
|
|
||||||
const showDebugTab = debugTabToggle.checked;
|
|
||||||
const theme = themeSelect.value;
|
const theme = themeSelect.value;
|
||||||
await storage.local.set({ endpoint, templateName, customTemplate: customTemplateText, customSystemPrompt, aiParams: aiParamsSave, debugLogging, htmlToMarkdown, stripUrlParams, altTextImages, collapseWhitespace, tokenReduction, aiRules: rules, theme, showDebugTab });
|
await storage.local.set({ endpoint, templateName, customTemplate: customTemplateText, customSystemPrompt, aiParams: aiParamsSave, debugLogging, htmlToMarkdown, stripUrlParams, altTextImages, collapseWhitespace, aiRules: rules, theme });
|
||||||
await applyTheme(theme);
|
await applyTheme(theme);
|
||||||
try {
|
try {
|
||||||
await AiClassifier.setConfig({ endpoint, templateName, customTemplate: customTemplateText, customSystemPrompt, aiParams: aiParamsSave, debugLogging });
|
await AiClassifier.setConfig({ endpoint, templateName, customTemplate: customTemplateText, customSystemPrompt, aiParams: aiParamsSave, debugLogging });
|
||||||
|
|
Before Width: | Height: | Size: 413 B |
Before Width: | Height: | Size: 828 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 408 B |
Before Width: | Height: | Size: 813 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 392 B |
Before Width: | Height: | Size: 768 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 393 B |
Before Width: | Height: | Size: 764 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 301 B |
Before Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 215 B |
Before Width: | Height: | Size: 305 B |
Before Width: | Height: | Size: 457 B |
|
@ -1,4 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" data-attribution="cc0-icons" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Z"/>
|
|
||||||
<path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 357 B |