Add site definition worker inference

This commit is contained in:
Jordan Wages 2026-08-25 17:05:11 -05:00
commit 3ccdaaf953
17 changed files with 250 additions and 50 deletions

View file

@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { DefinitionEngine, matchesUrlPattern, selectDefinition } from "../src/content/definition-engine";
import { DefinitionEngine, hasPotentialDefinitionForUrl, matchesUrlPattern, selectDefinition, selectDefinitionForUrl } from "../src/content/definition-engine";
import { BUILTIN_DEFINITIONS } from "../src/content/definitions";
import type { SiteDefinition } from "../src/shared/types";
@ -84,6 +84,17 @@ describe("DefinitionEngine", () => {
expect(posts.map((post) => post.text)).toEqual(["first threads post with visible text", "a reply in the conversation"]);
});
it("discovers Threads detail-page posts from pagelet containers", () => {
document.body.innerHTML = readFileSync("tests/fixtures/threads-post-page.html", "utf8");
const threads = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "threads")!;
const posts = new DefinitionEngine(threads).discover(document);
expect(posts.map((post) => post.id)).toEqual([
"threads:http://localhost:3000/@alice/post/ABC123",
"threads:http://localhost:3000/@bob/post/DEF456"
]);
expect(posts.map((post) => post.text)).toEqual(["the main post on the threads detail page", "a reply on the threads detail page"]);
});
it("selects Threads on current and legacy domains", () => {
const threads = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "threads")!;
document.body.innerHTML = '<div role="article" data-pressable-container="true"></div>';
@ -92,4 +103,23 @@ describe("DefinitionEngine", () => {
}
expect(selectDefinition([threads], "https://example.com/home")).toBeUndefined();
});
it("can identify a specific site while its SPA markers are still loading", () => {
const threads = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "threads")!;
document.body.innerHTML = "";
expect(selectDefinition([threads], "https://www.threads.com/")).toBeUndefined();
expect(selectDefinitionForUrl([BUILTIN_DEFINITIONS[0]!, threads], "https://www.threads.com/")).toBe(threads);
});
it("keeps broad definitions pending until their hydration markers appear", () => {
const mastodon = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "mastodon")!;
const threads = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "threads")!;
document.body.innerHTML = "";
expect(selectDefinition([mastodon], "https://mastodon.example/@user")).toBeUndefined();
expect(selectDefinitionForUrl([mastodon], "https://mastodon.example/@user")).toBeUndefined();
expect(hasPotentialDefinitionForUrl([mastodon], "https://mastodon.example/@user")).toBe(true);
expect(hasPotentialDefinitionForUrl([threads], "https://example.com/")).toBe(false);
document.body.innerHTML = '<div class="status"><div class="status__content">Ready</div></div>';
expect(selectDefinition([mastodon], "https://mastodon.example/@user")).toBe(mastodon);
});
});

10
tests/fixtures/threads-post-page.html vendored Normal file
View file

@ -0,0 +1,10 @@
<main>
<div data-pagelet="threads_post_page_0">
<a href="/@alice/post/ABC123"><span dir="auto">12h</span></a>
<span dir="auto">The main post on the Threads detail page</span>
</div>
<div data-pagelet="threads_post_page_1">
<a href="/@bob/post/DEF456"><span dir="auto">1h</span></a>
<span dir="auto">A reply on the Threads detail page</span>
</div>
</main>

View file

@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import { InferenceWorkerHost, type InferenceWorker } from "../src/background/inference-worker";
import type { InferenceRequest } from "../src/shared/types";
const request: InferenceRequest = { id: "post", text: "text", site: "threads", navigationId: "nav", priority: 0, requestId: "request" };
class FakeWorker implements InferenceWorker {
readonly listeners = new Map<string, EventListener[]>();
readonly requests: Array<{ requests: InferenceRequest[]; modelBaseUrl: string }> = [];
terminated = false;
addEventListener(type: "message" | "error", listener: EventListener): void {
this.listeners.set(type, [...(this.listeners.get(type) ?? []), listener]);
}
removeEventListener(type: "message" | "error", listener: EventListener): void {
this.listeners.set(type, (this.listeners.get(type) ?? []).filter((candidate) => candidate !== listener));
}
postMessage(message: { requests: InferenceRequest[]; modelBaseUrl: string }): void {
this.requests.push(message);
}
terminate(): void { this.terminated = true; }
reply(): void {
const event = new MessageEvent("message", { data: { results: [{ requestId: request.requestId, id: request.id, textHash: "hash", label: "not_toxic", probability: .1, navigationId: request.navigationId }] } });
this.listeners.get("message")?.forEach((listener) => listener(event));
}
}
describe("InferenceWorkerHost", () => {
it("reuses its worker", async () => {
const workers: FakeWorker[] = [];
const host = new InferenceWorkerHost(() => {
const worker = new FakeWorker();
workers.push(worker);
return worker;
}, "chrome-extension://id/models/toxicity/");
const first = host.run([request]);
workers[0]!.reply();
await first;
const second = host.run([request]);
workers[0]!.reply();
await second;
expect(workers).toHaveLength(1);
expect(workers[0]!.requests).toHaveLength(2);
});
});

View file

@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { setDefinitionPaused } from "../src/shared/settings";
import { mergeSettings, setDefinitionPaused } from "../src/shared/settings";
import { DEFAULT_SETTINGS } from "../src/shared/types";
describe("definition pause settings", () => {
@ -18,3 +18,10 @@ describe("definition pause settings", () => {
expect(settings.disabledDefinitionIds).toEqual(["mastodon"]);
});
});
describe("legacy settings", () => {
it("removes legacy inference settings", () => {
expect(mergeSettings({ cpuThreads: 99 })).not.toHaveProperty("cpuThreads");
expect(mergeSettings({ inferenceDevice: "webgpu" } as never)).not.toHaveProperty("inferenceDevice");
});
});