Compare commits

..

No commits in common. "main" and "codex/find-real-time-updates-for-diff-view" have entirely different histories.

5 changed files with 16 additions and 58 deletions

View file

@ -16,7 +16,7 @@ message meets a specified criterion.
- **Advanced parameters** tune generation settings like temperature, topp and more from the options page. - **Advanced parameters** tune generation settings like temperature, topp 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. - **Debug tab** view the last request payload and message diff with live updates.
- **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.

View file

@ -210,38 +210,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(); let combined = `${headers}\n${attachInfo}\n\n${bodyParts.join('\n')}`.trim();
if (applyTransforms && tokenReduction) { if (tokenReduction) {
const seen = new Set(); const seen = new Set();
combined = combined.split('\n').filter(l => { combined = combined.split('\n').filter(l => {
if (seen.has(l)) return false; if (seen.has(l)) return false;
@ -249,7 +228,7 @@ function buildEmailText(full, applyTransforms = true) {
return true; return true;
}).join('\n'); }).join('\n');
} }
return applyTransforms ? sanitizeString(combined) : combined; return sanitizeString(combined);
} }
function updateTimingStats(elapsed) { function updateTimingStats(elapsed) {
@ -283,8 +262,8 @@ 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);
let text = buildEmailText(full); let text = buildEmailText(full);
const originalText = text;
if (tokenReduction && maxTokens > 0) { if (tokenReduction && maxTokens > 0) {
const limit = Math.floor(maxTokens * 0.9); const limit = Math.floor(maxTokens * 0.9);
if (text.length > limit) { if (text.length > limit) {

View file

@ -1,13 +1,13 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Sortana", "name": "Sortana",
"version": "2.2.0", "version": "2.1.2",
"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": {

View file

@ -154,11 +154,6 @@
<input type="checkbox" id="token-reduction"> Aggressive token reduction <input type="checkbox" id="token-reduction"> Aggressive token reduction
</label> </label>
</div> </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">
@ -225,6 +220,11 @@
<input class="input" type="number" step="0.01" id="tfs"> <input class="input" type="number" step="0.01" id="tfs">
</div> </div>
</div> </div>
<div class="field">
<label class="checkbox">
<input type="checkbox" id="show-debug-tab"> Advanced Options
</label>
</div>
</div> </div>
</div> </div>
@ -290,10 +290,7 @@
<span>Debug</span> <span>Debug</span>
</h2> </h2>
<pre id="payload-display"></pre> <pre id="payload-display"></pre>
<div id="diff-container" class="mt-4 is-hidden"> <div id="diff-display" class="mt-4"></div>
<label class="label">Prompt diff</label>
<div id="diff-display" class="box content is-family-monospace"></div>
</div>
</div> </div>
</div> </div>
</section> </section>

View file

@ -70,7 +70,6 @@ document.addEventListener('DOMContentLoaded', async () => {
await applyTheme(themeSelect.value); await applyTheme(themeSelect.value);
const payloadDisplay = document.getElementById('payload-display'); const payloadDisplay = document.getElementById('payload-display');
const diffDisplay = document.getElementById('diff-display'); const diffDisplay = document.getElementById('diff-display');
const diffContainer = document.getElementById('diff-container');
let lastFullText = defaults.lastFullText || ''; let lastFullText = defaults.lastFullText || '';
let lastPromptText = defaults.lastPromptText || ''; let lastPromptText = defaults.lastPromptText || '';
@ -84,16 +83,7 @@ document.addEventListener('DOMContentLoaded', async () => {
dmp.Diff_EditCost = 4; dmp.Diff_EditCost = 4;
const diffs = dmp.diff_main(lastFullText, lastPromptText); const diffs = dmp.diff_main(lastFullText, lastPromptText);
dmp.diff_cleanupEfficiency(diffs); dmp.diff_cleanupEfficiency(diffs);
const hasDiff = diffs.some(d => d[0] !== 0); diffDisplay.innerHTML = dmp.diff_prettyHtml(diffs);
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();
@ -761,17 +751,9 @@ document.addEventListener('DOMContentLoaded', async () => {
dmp.Diff_EditCost = 4; dmp.Diff_EditCost = 4;
const diffs = dmp.diff_main(lastFullText, lastPromptText); const diffs = dmp.diff_main(lastFullText, lastPromptText);
dmp.diff_cleanupEfficiency(diffs); dmp.diff_cleanupEfficiency(diffs);
const hasDiff = diffs.some(d => d[0] !== 0); diffDisplay.innerHTML = dmp.diff_prettyHtml(diffs);
if (hasDiff) {
diffDisplay.innerHTML = dmp.diff_prettyHtml(diffs);
diffContainer.classList.remove('is-hidden');
} else {
diffDisplay.innerHTML = '';
diffContainer.classList.add('is-hidden');
}
} else { } else {
diffDisplay.innerHTML = ''; diffDisplay.innerHTML = '';
diffContainer.classList.add('is-hidden');
} }
} }
} }