Still WIP
This commit is contained in:
parent
2109aaa16d
commit
6d28a2f53a
1 changed files with 690 additions and 383 deletions
125
extension/src/entrypoints/content/text_class.ts
Normal file
125
extension/src/entrypoints/content/text_class.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import { TRAVERSE_BEHAVOUR_ATTR, TRAVERSE_BEHAVOUR_IGNORE_MAGIC, traverse_text } from "@/utils/domtraverse";
|
||||
import { SyncingExtensionConfig } from "@/utils/config";
|
||||
import { ExtConsole } from "@/utils/logging";
|
||||
import { sendMessage } from "@/utils/messaging";
|
||||
|
||||
const console = ExtConsole("contentscript::text_class");
|
||||
|
||||
const CONFIG = new SyncingExtensionConfig("ai_text");
|
||||
|
||||
const DOM_OBSERVATORY: { observer: MutationObserver; timer: ReturnType<typeof setTimeout> | null } = {
|
||||
observer: new MutationObserver((_) => {
|
||||
if (DOM_OBSERVATORY.timer) clearTimeout(DOM_OBSERVATORY.timer);
|
||||
DOM_OBSERVATORY.timer = setTimeout(() => {
|
||||
console.debug("observatory trigger");
|
||||
traverse();
|
||||
}, 1000);
|
||||
}),
|
||||
timer: null,
|
||||
};
|
||||
|
||||
const VIEWPORT_OBSERVATORY: { observer: IntersectionObserver } = {
|
||||
observer: new IntersectionObserver((entries, observer) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
const stack = STATE.stack.get(entry.target);
|
||||
if (stack) {
|
||||
sendMessage("inference_aiText_classify", { text: stack.text })
|
||||
.then((resp) => {
|
||||
console.debug("received inf response", resp);
|
||||
observer.unobserve(entry.target);
|
||||
apply_class(entry.target, resp.prob_ai, resp.prob_human);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn("inference failed", e, "\nentry will be rescheduled by observer");
|
||||
});
|
||||
} else {
|
||||
console.warn("viewport_observatory triggered on node, but the stack is absent");
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
const STATE: {
|
||||
stack: WeakMap<Element, { text: string }>;
|
||||
badge: WeakMap<Node, Element>;
|
||||
} = {
|
||||
stack: new WeakMap(),
|
||||
badge: new WeakMap(),
|
||||
};
|
||||
|
||||
export default function setup() {
|
||||
DOM_OBSERVATORY.observer.observe(document.body, {
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function traverse() {
|
||||
CONFIG.sync.then(() => {
|
||||
if (!CONFIG.value.enabled) {
|
||||
console.debug("disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("traverse start");
|
||||
for (const stack of traverse_text(600, 20)) {
|
||||
const target = stack.first_node.parentElement || stack.root.parentElement;
|
||||
if (target) {
|
||||
set_badge(target);
|
||||
STATE.stack.set(target, { text: stack.text });
|
||||
VIEWPORT_OBSERVATORY.observer.observe(target);
|
||||
} else {
|
||||
console.warn("Stack didn't have target");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function apply_class(target: Node, prob_ai: number, prob_human: number) {
|
||||
CONFIG.sync.then(() => {
|
||||
if (CONFIG.value.show_ai && prob_ai >= CONFIG.value.threshold && prob_ai >= prob_human) {
|
||||
set_badge(target, { type: "ai", prob: prob_ai });
|
||||
} else if (CONFIG.value.show_human && prob_human >= CONFIG.value.threshold && prob_human >= prob_ai) {
|
||||
set_badge(target, { type: "human", prob: prob_human });
|
||||
} else {
|
||||
clear_badge(target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function set_badge(target: Node, data?: { type: "ai" | "human"; prob: number }) {
|
||||
if (STATE.badge.has(target)) STATE.badge.delete(target);
|
||||
|
||||
let badge = document.createElement("mark");
|
||||
badge.setAttribute(TRAVERSE_BEHAVOUR_ATTR, TRAVERSE_BEHAVOUR_IGNORE_MAGIC);
|
||||
badge.style.display = "inline";
|
||||
badge.style.borderRadius = "4px";
|
||||
badge.style.textAlign = "center";
|
||||
badge.style.fontFamily = "sans";
|
||||
badge.style.fontSize = "12px";
|
||||
badge.style.padding = "1px";
|
||||
badge.style.marginRight = "4px";
|
||||
if (data?.type === "ai") {
|
||||
badge.style.backgroundColor = "#ffc36d";
|
||||
badge.style.color = "#fff";
|
||||
badge.textContent = `🤖: ${Math.trunc(data.prob * 100)}%`;
|
||||
} else if (data?.type === "human") {
|
||||
badge.style.backgroundColor = "#8fe1a1";
|
||||
badge.style.color = "#fff";
|
||||
badge.textContent = `👨💼: ${Math.trunc(data.prob * 100)}%`;
|
||||
} else {
|
||||
badge.style.backgroundColor = "#ddd";
|
||||
badge.style.color = "#fff";
|
||||
badge.textContent = "🦊 •••• ";
|
||||
}
|
||||
|
||||
badge = target.insertBefore(badge, target.firstChild);
|
||||
STATE.badge.set(target, badge);
|
||||
}
|
||||
|
||||
function clear_badge(target: Node) {
|
||||
STATE.badge.delete(target);
|
||||
}
|
||||
Loading…
Reference in a new issue