Show the language model's opinion on a message
When inbuxa-server's AI spam classification is on, it records the model's answer in an X-Spam-LLM header: a tag (LLM_<category>[_<confidence>]) and, in parentheses, the model's explanation. The full message now asks for it, and where it's there: - the message details show "Language model's opinion" beside the spam filter's own working, with category, confidence and explanation; - a message in Junk carries a banner saying the same. Both say it's one of several signals the spam filter weighed, never the reason on its own, as the server's spec requires. The explanation is model output and is only ever rendered as text. Nothing shows without the header, so a server without the feature, or with it off, looks as before. Translations: two new strings, "Language model's opinion" and "One of several signals the spam filter weighed", in all eight catalogues (16 entries). Category and confidence come from the server and aren't translated.
This commit is contained in:
1 parent
c0c892dd33
commit
e4926cfa7d
17 files changed
+319
No files matched your search
@@ -15,6 +15,8 @@ import { emlFilename } from "@/lib/text/emlName";
|
||||
import { isTnef, parseTnef, type TnefAttachment } from "@/lib/tnef";
|
||||
import { internalDomains, isExternalSender, linkVerdict } from "@/lib/warnings";
|
||||
import { spamReport, type SpamReport } from "@/lib/spamScore";
|
||||
import { llmOpinion } from "@/lib/llmOpinion";
|
||||
import { LlmOpinionBanner, LlmOpinionDetail, llmBannerOpinion } from "./LlmOpinion";
|
||||
import { formatFullDate, formatListDate, formatSize } from "@/lib/format";
|
||||
import { displayName, domainOf, formatAddress } from "@/lib/address";
|
||||
import { EMAIL_BASE_CSS, TEXT_EMAIL_CSS, hasHtmlAlternative, htmlDeclaresColors, markKeptSurfaces, sanitizeEmailHtml } from "@/lib/text/html";
|
||||
@@ -187,6 +189,10 @@ export const MessageView = memo(function MessageView({ email: e, expanded, wasUn
|
||||
const receiptRequested = Boolean(e["header:Disposition-Notification-To:asAddresses"]?.length);
|
||||
const authFailed = /\b(dkim|spf|dmarc)=fail\b/i.test(e["header:Authentication-Results:asText"] ?? "");
|
||||
const spam = useMemo(() => spamReport(e), [e]);
|
||||
// inbuxa: the language model's opinion, where the server's AI spam classification wrote one
|
||||
const llm = useMemo(() => llmOpinion(e), [e]);
|
||||
const junkId = useMail((st) => st.roleId("junk"));
|
||||
const llmBanner = llmBannerOpinion(llm, e.mailboxIds, junkId);
|
||||
const identities = useMail((st) => st.identities);
|
||||
/*
|
||||
* Only computed when the warning is on, because the domains it compares
|
||||
@@ -374,6 +380,7 @@ export const MessageView = memo(function MessageView({ email: e, expanded, wasUn
|
||||
{e["header:List-Id:asText"] && <><dt>{translate("List")}</dt><dd>{e["header:List-Id:asText"]}</dd></>}
|
||||
<dt>{translate("Size")}</dt><dd>{formatSize(e.size)}</dd>
|
||||
{spam && <><dt>{translate("Spam filter")}</dt><dd><SpamSummary report={spam} /></dd></>}
|
||||
{llm && <><dt>{translate("Language model's opinion")}</dt><dd><LlmOpinionDetail opinion={llm} /></dd></>}
|
||||
{receiptRequested && <><dt>{translate("Receipt")}</dt><dd>{receipt.offer ? translate("Requested, to {address}. Never sent automatically.", { address: receipt.to!.email }) : translate(refusalText(receipt.refusal!))}</dd></>}
|
||||
</dl>
|
||||
)}
|
||||
@@ -425,6 +432,7 @@ export const MessageView = memo(function MessageView({ email: e, expanded, wasUn
|
||||
</div>
|
||||
)}
|
||||
<SignatureBanner state={signature} />
|
||||
{llmBanner && <LlmOpinionBanner opinion={llmBanner} />}
|
||||
{externalSender && (
|
||||
<div className="remote-banner external-banner" style={{ margin: "0 16px 8px" }}>
|
||||
<ShieldAlert size={16} />
|
||||
|
||||
Reference in new issue
Block a user