diff --git a/.gitignore b/.gitignore index 302d14da..311312a3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ bun.lock # PDF.js extension assets (copied by postinstall from pdfjs-dist) src/public/resources/pdfjs/pdf.worker.min.mjs src/public/resources/pdfjs/pdf.legacy.min.mjs +# ONNX Runtime WASM assets (copied by postinstall from @huggingface/transformers) +src/public/resources/ort/ort-wasm-simd-threaded.jsep.mjs +src/public/resources/ort/ort-wasm-simd-threaded.jsep.wasm # Build extension.zip diff --git a/lib/extensionChunkUrls.ts b/lib/extensionChunkUrls.ts index 6dff51bb..1701505c 100644 --- a/lib/extensionChunkUrls.ts +++ b/lib/extensionChunkUrls.ts @@ -15,8 +15,15 @@ export function extensionChunkUrls(): Plugin { base: "./", experimental: { renderBuiltUrl(filename, { hostType, type }) { + const path = filename.replace(/^\//, ""); if (type === "chunk" && hostType === "js") { - const path = filename.replace(/^\//, ""); + return { + runtime: `chrome.runtime.getURL(${JSON.stringify(path)})`, + }; + } + // Rewrite CSS preloads from JS dynamic imports (content scripts). + // Do not rewrite hostType "css" — extension HTML pages need static hrefs. + if (type === "asset" && hostType === "js" && path.endsWith(".css")) { return { runtime: `chrome.runtime.getURL(${JSON.stringify(path)})`, }; diff --git a/lib/inlineWorker.ts b/lib/inlineWorker.ts index a877a05b..b351339a 100644 --- a/lib/inlineWorker.ts +++ b/lib/inlineWorker.ts @@ -43,12 +43,13 @@ export default function InlineWorkerDevPlugin(): Plugin { // Note: Original code had `await fs.readFile(cleanPath, "utf-8");` but `code` wasn't used. // `esbuild` directly takes `cleanPath` as an entry point. const result = await build({ - entryPoints: [cleanPath], // esbuild uses the file path directly + entryPoints: [cleanPath], bundle: true, - write: false, // We want the output in memory, not written to disk - platform: "browser", // Target environment for the worker code - format: "iife", // Immediately Invoked Function Expression, suitable for workers - target: "esnext", // Transpile to modern JavaScript + write: false, + platform: "browser", + format: "iife", + target: "esnext", + external: ["webextension-polyfill"], }); const workerCode = result.outputFiles[0].text; diff --git a/package.json b/package.json index 62156400..cddffb3b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "browserslist": "> 0.5%, last 2 versions, not dead", "scripts": { "compile:layerchart": "node scripts/compile-layerchart-vendor.mjs", - "postinstall": "node scripts/copy-pdfjs-assets.mjs && npm run compile:layerchart", + "postinstall": "node scripts/copy-pdfjs-assets.mjs && node scripts/copy-ort-wasm-assets.mjs && npm run compile:layerchart", "autoaudit": "npm audit && npm audit fix && npm run build", "dev": "cross-env MODE=chrome vite dev", "dev:firefox": "cross-env MODE=firefox vite build --watch", diff --git a/scripts/copy-ort-wasm-assets.mjs b/scripts/copy-ort-wasm-assets.mjs new file mode 100644 index 00000000..a5345bb7 --- /dev/null +++ b/scripts/copy-ort-wasm-assets.mjs @@ -0,0 +1,24 @@ +import { copyFileSync, mkdirSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const transformersDist = join( + root, + "node_modules", + "@huggingface", + "transformers", + "dist", +); +const outDir = join(root, "src", "public", "resources", "ort"); + +mkdirSync(outDir, { recursive: true }); + +const ortFiles = [ + "ort-wasm-simd-threaded.jsep.mjs", + "ort-wasm-simd-threaded.jsep.wasm", +]; + +for (const file of ortFiles) { + copyFileSync(join(transformersDist, file), join(outDir, file)); +} diff --git a/src/interface/contentShadow.css b/src/interface/contentShadow.css new file mode 100644 index 00000000..d12e9377 --- /dev/null +++ b/src/interface/contentShadow.css @@ -0,0 +1,21 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +button { + @apply cursor-pointer; +} + +::-webkit-scrollbar { + display: none; +} + +input { + &:focus { + box-shadow: unset !important; + } +} + +.no-scrollbar { + scrollbar-width: none !important; +} diff --git a/src/interface/index.ts b/src/interface/index.ts index a760b630..5003af7b 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -2,6 +2,7 @@ import "./index.css"; import Settings from "./pages/settings.svelte"; import IconFamily from "@/resources/fonts/IconFamily.woff"; import browser from "webextension-polyfill"; +import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl"; import renderSvelte from "./main"; import { initializeSettingsState } from "@/seqta/utils/listeners/SettingsState"; import { initVerboseLogging, verboseInfo } from "@/utils/verboseLog"; @@ -14,7 +15,7 @@ function InjectCustomIcons() { style.innerHTML = ` @font-face { font-family: 'IconFamily'; - src: url('${browser.runtime.getURL(IconFamily)}') format('woff'); + src: url('${resolveExtensionAssetUrl(IconFamily)}') format('woff'); font-weight: normal; font-style: normal; }`; diff --git a/src/interface/main.d.ts b/src/interface/main.d.ts index 38431407..0357e4f1 100644 --- a/src/interface/main.d.ts +++ b/src/interface/main.d.ts @@ -1,5 +1,3 @@ -import "./index.css"; - declare module "*.png"; declare module "*.svg"; declare module "*.jpeg"; diff --git a/src/interface/renderInShadow.ts b/src/interface/renderInShadow.ts new file mode 100644 index 00000000..c54f1361 --- /dev/null +++ b/src/interface/renderInShadow.ts @@ -0,0 +1,26 @@ +import { mount } from "svelte"; +import type { SvelteComponent } from "svelte"; +import style from "./contentShadow.css?inline"; + +/** Mount Svelte UI inside a shadow root from content scripts (decoupled from settings popup CSS). */ +export default function renderInShadow( + Component: SvelteComponent | any, + mountPoint: ShadowRoot | HTMLElement, + props: Record = {}, +) { + const app = mount(Component, { + target: mountPoint, + props: { + standalone: false, + ...props, + }, + }); + + if (mountPoint instanceof ShadowRoot) { + const styleElement = document.createElement("style"); + styleElement.textContent = style; + mountPoint.appendChild(styleElement); + } + + return app; +} diff --git a/src/lib/transformersExtension.ts b/src/lib/transformersExtension.ts new file mode 100644 index 00000000..3e872478 --- /dev/null +++ b/src/lib/transformersExtension.ts @@ -0,0 +1,43 @@ +import browser from "webextension-polyfill"; + +const ORT_RESOURCE_DIR = "resources/ort/"; + +let configured = false; + +function extensionAssetUrl(relativePath: string): string { + return browser.runtime.getURL(relativePath.replace(/^\/+/, "")); +} + +/** + * Point HuggingFace transformers / onnxruntime at extension-local WASM files + * instead of CDN (required on SEQTA pages where page CSP blocks jsdelivr). + * Safe to call multiple times; must run before embeddia `initializeModel()`. + */ +export async function ensureTransformersEnv( + ortWasmBase?: string, +): Promise { + if (configured) return; + + const { env } = await import("@huggingface/transformers"); + const base = ortWasmBase ?? extensionAssetUrl(ORT_RESOURCE_DIR); + + env.backends.onnx.wasm = env.backends.onnx.wasm ?? {}; + env.backends.onnx.wasm.wasmPaths = base.endsWith("/") ? base : `${base}/`; + + configured = true; +} + +export function getOrtWasmBaseUrl(): string { + const base = extensionAssetUrl(ORT_RESOURCE_DIR); + return base.endsWith("/") ? base : `${base}/`; +} + +/** For page-origin blob workers that cannot call `browser.runtime.getURL`. */ +export async function configureTransformersEnvForBase( + ortWasmBase: string, +): Promise { + configured = false; + await ensureTransformersEnv(ortWasmBase); +} + +export { ORT_RESOURCE_DIR }; diff --git a/src/manifests/manifest.json b/src/manifests/manifest.json index b4bfe4e4..9ecc5e86 100644 --- a/src/manifests/manifest.json +++ b/src/manifests/manifest.json @@ -36,7 +36,9 @@ "resources/icons/*", "resources/update-image.webp", "resources/pdfjs/pdf.worker.min.mjs", - "resources/pdfjs/pdf.legacy.min.mjs" + "resources/pdfjs/pdf.legacy.min.mjs", + "resources/ort/*", + "assets/*.css" ], "matches": ["*://*/*"] } diff --git a/src/plugins/built-in/globalSearch/lazy.ts b/src/plugins/built-in/globalSearch/lazy.ts index 31a3e379..6ff82be2 100644 --- a/src/plugins/built-in/globalSearch/lazy.ts +++ b/src/plugins/built-in/globalSearch/lazy.ts @@ -7,7 +7,10 @@ import { } from "../../core/settingsHelpers"; import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage"; import styles from "./src/core/styles.css?inline"; -import { resetSearchIndexes } from "./src/indexing/resetIndexes"; +import { + resetSearchIndexes, + notifyOpenTabsResetSearchIndex, +} from "./src/indexing/resetIndexes"; // Platform-aware default hotkey const getDefaultHotkey = () => { @@ -52,9 +55,7 @@ const settings = defineSettings({ if (!confirmed) return; try { - // `resetSearchIndexes` is a tiny statically-imported helper: no - // dynamic chunks to chase, so the button keeps working even when - // the settings page has been open across an extension update. + await notifyOpenTabsResetSearchIndex(); await resetSearchIndexes(); alert( "Search index and storage were reset.\n\nReload this tab to regenerate the index.", diff --git a/src/plugins/built-in/globalSearch/src/core/index.ts b/src/plugins/built-in/globalSearch/src/core/index.ts index d8b14fff..a8d02f54 100644 --- a/src/plugins/built-in/globalSearch/src/core/index.ts +++ b/src/plugins/built-in/globalSearch/src/core/index.ts @@ -11,6 +11,8 @@ import { verboseDebug, verboseInfo, verboseLog } from "@/utils/verboseLog"; import styles from "./styles.css?inline"; import { waitForElm } from "@/seqta/utils/waitForElm"; import { runIndexing, ensureSchemaCurrent } from "../indexing/indexer"; +import { installResetIndexMessageListener } from "../indexing/resetIndexes"; +import { isIndexingPaused } from "../indexing/indexingPause"; import { initVectorSearch } from "../search/vector/vectorSearch"; import { cleanupSearchBar, mountSearchBar } from "./mountSearchBar"; import { IndexedDbManager } from "embeddia"; @@ -168,6 +170,8 @@ const globalSearchPlugin: Plugin = { run: async (api) => { const appRef = { current: null }; + installResetIndexMessageListener(); + // Run the version check BEFORE we open any IndexedDB connections. // On a normal load (no version change) this is just a string compare // and a manifest read, so the cost is negligible. On a real update, @@ -287,8 +291,9 @@ const globalSearchPlugin: Plugin = { } } - if (api.settings.runIndexingOnLoad) { + if (api.settings.runIndexingOnLoad && !isIndexingPaused()) { setTimeout(async () => { + if (isIndexingPaused()) return; await runIndexing(); }, 2000); } diff --git a/src/plugins/built-in/globalSearch/src/core/mountSearchBar.ts b/src/plugins/built-in/globalSearch/src/core/mountSearchBar.ts index 2549220d..aa270be0 100644 --- a/src/plugins/built-in/globalSearch/src/core/mountSearchBar.ts +++ b/src/plugins/built-in/globalSearch/src/core/mountSearchBar.ts @@ -280,7 +280,7 @@ export async function mountSearchBar( }); try { - const { default: renderSvelte } = await import("@/interface/main"); + const { default: renderSvelte } = await import("@/interface/renderInShadow"); appRef.current = renderSvelte(SearchBar, searchRootShadow, { transparencyEffects: api.settings.transparencyEffects ? true : false, showRecentFirst: api.settings.showRecentFirst, diff --git a/src/plugins/built-in/globalSearch/src/indexing/indexer.ts b/src/plugins/built-in/globalSearch/src/indexing/indexer.ts index 5a80310f..72604989 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/indexer.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/indexer.ts @@ -7,6 +7,7 @@ import { loadDynamicItems } from "../utils/dynamicItems"; import { getVectorizedItemIds, pruneOrphanVectorEmbeddings } from "./utils"; import { INDEX_SCHEMA_VERSION, SCHEMA_VERSION_KEY } from "./schemaVersion"; import { resetSearchIndexes } from "./resetIndexes"; +import { isIndexingPaused } from "./indexingPause"; import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog'; const META_STORE = "meta"; @@ -252,8 +253,19 @@ export async function loadAllStoredItems(): Promise { } export async function runIndexing(): Promise { + if (isIndexingPaused()) { + verboseDebug( + "[Indexer] Skipping indexing — index was reset; reload the page to rebuild.", + ); + return; + } + await ensureSchemaCurrent(); + if (isIndexingPaused()) { + return; + } + if (!(await acquireLock())) { verboseDebug( "%c[Indexer] Could not acquire lock - another tab is indexing or this tab is already indexing", @@ -272,6 +284,19 @@ export async function runIndexing(): Promise { dispatchProgress(completedJobs, totalSteps, true, "Starting jobs"); for (const jobId of jobIds) { + if (isIndexingPaused()) { + verboseDebug( + "[Indexer] Indexing stopped — index was reset; reload the page to rebuild.", + ); + dispatchProgress( + completedJobs, + totalSteps, + false, + "Indexing paused — reload to rebuild", + ); + return; + } + dispatchProgress( completedJobs, totalSteps, diff --git a/src/plugins/built-in/globalSearch/src/indexing/indexingPause.ts b/src/plugins/built-in/globalSearch/src/indexing/indexingPause.ts new file mode 100644 index 00000000..56a248c2 --- /dev/null +++ b/src/plugins/built-in/globalSearch/src/indexing/indexingPause.ts @@ -0,0 +1,10 @@ +/** In-memory gate: after a manual reset, skip indexing until the tab reloads. */ +let pausedUntilReload = false; + +export function pauseIndexingUntilReload(): void { + pausedUntilReload = true; +} + +export function isIndexingPaused(): boolean { + return pausedUntilReload; +} diff --git a/src/plugins/built-in/globalSearch/src/indexing/jobs/messages.ts b/src/plugins/built-in/globalSearch/src/indexing/jobs/messages.ts index 6e13d485..aa1ff832 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/jobs/messages.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/jobs/messages.ts @@ -395,7 +395,7 @@ export const messagesJob: Job = { progress.totalEstimated = await estimateMessageCount(); try { - await vectorWorker.startStreamingSession( + progress.streamingStarted = await vectorWorker.startStreamingSession( progress.totalEstimated, (progressData) => { verboseLog( @@ -405,10 +405,11 @@ export const messagesJob: Job = { RATE_LIMIT_CONFIG.vectorBatchSize, "messages", ); - progress.streamingStarted = true; - verboseLog( - `[Messages job] Started streaming vectorization session for ~${progress.totalEstimated} items`, - ); + if (progress.streamingStarted) { + verboseLog( + `[Messages job] Started streaming vectorization session for ~${progress.totalEstimated} items`, + ); + } } catch (error) { console.warn( "[Messages job] Failed to start streaming session:", diff --git a/src/plugins/built-in/globalSearch/src/indexing/jobs/notifications.ts b/src/plugins/built-in/globalSearch/src/indexing/jobs/notifications.ts index 523b00ed..de83fb1c 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/jobs/notifications.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/jobs/notifications.ts @@ -199,7 +199,7 @@ export const notificationsJob: Job = { const estimatedTotal = Math.min(notifications.length * 1.2, 100); try { - await vectorWorker.startStreamingSession( + progress.streamingStarted = await vectorWorker.startStreamingSession( estimatedTotal, (progressData) => { verboseLog( @@ -209,10 +209,11 @@ export const notificationsJob: Job = { NOTIFICATIONS_RATE_LIMIT.vectorBatchSize, "notifications", ); - progress.streamingStarted = true; - verboseLog( - `[Notifications job] Started streaming vectorization session for ~${estimatedTotal} items`, - ); + if (progress.streamingStarted) { + verboseLog( + `[Notifications job] Started streaming vectorization session for ~${estimatedTotal} items`, + ); + } } catch (error) { console.warn( "[Notifications job] Failed to start streaming session:", diff --git a/src/plugins/built-in/globalSearch/src/indexing/passiveObserver.ts b/src/plugins/built-in/globalSearch/src/indexing/passiveObserver.ts index 9b551ba4..e0310a11 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/passiveObserver.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/passiveObserver.ts @@ -10,6 +10,7 @@ import { verboseDebug, verboseInfo, verboseLog } from "@/utils/verboseLog"; import { isSensitiveSeqtaPath, normalizeSeqtaPath } from "./api"; import { mergeDynamicItems } from "../utils/dynamicItems"; import { decorateIndexItems } from "./renderComponents"; +import { isIndexingPaused } from "./indexingPause"; /** * Passive network observer. @@ -380,7 +381,7 @@ function synthesizeItems( /* ------------------------------------------------------------------ */ async function persistItems(items: IndexItem[]): Promise { - if (items.length === 0) return; + if (items.length === 0 || isIndexingPaused()) return; // Dedupe against existing entries. We replace on collision so the latest // observation wins (e.g. if a message changes title). @@ -401,16 +402,27 @@ async function persistItems(items: IndexItem[]): Promise { } function scheduleFlush() { - if (pendingFlush) return; + if (pendingFlush || isIndexingPaused()) return; pendingFlush = setTimeout(() => { pendingFlush = null; - if (!pendingDirty) return; + if (!pendingDirty || isIndexingPaused()) return; pendingDirty = false; void flushDynamicItems(); }, FLUSH_DEBOUNCE_MS); } +/** Drop queued passive captures after a manual index reset. */ +export function pausePassiveObserver(): void { + pendingChangedItems.clear(); + pendingDirty = false; + if (pendingFlush) { + clearTimeout(pendingFlush); + pendingFlush = null; + } +} + async function flushDynamicItems(): Promise { + if (isIndexingPaused()) return; if (pendingChangedItems.size === 0) return; const rawChanged = Array.from(pendingChangedItems.values()); diff --git a/src/plugins/built-in/globalSearch/src/indexing/resetIndexes.ts b/src/plugins/built-in/globalSearch/src/indexing/resetIndexes.ts index 80fe39f1..eb6400f5 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/resetIndexes.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/resetIndexes.ts @@ -1,4 +1,48 @@ import { SCHEMA_VERSION_KEY } from "./schemaVersion"; +import { pauseIndexingUntilReload } from "./indexingPause"; +import { pausePassiveObserver } from "./passiveObserver"; +import browser from "webextension-polyfill"; + +export const RESET_INDEX_MESSAGE = "global-search-reset-index"; + +let resetMessageListenerInstalled = false; + +/** Notify open SEQTA tabs to pause indexing and wipe page-origin stores. */ +export async function notifyOpenTabsResetSearchIndex(): Promise { + const tabs = await browser.tabs.query({}); + await Promise.allSettled( + tabs.map((tab) => + tab.id != null + ? browser.tabs.sendMessage(tab.id, { type: RESET_INDEX_MESSAGE }) + : Promise.resolve(), + ), + ); +} + +/** Content scripts: handle reset broadcast from the settings popup. */ +export function installResetIndexMessageListener(): void { + if (resetMessageListenerInstalled) return; + resetMessageListenerInstalled = true; + + browser.runtime.onMessage.addListener((message) => { + if (message?.type !== RESET_INDEX_MESSAGE) return; + pauseIndexingUntilReload(); + pausePassiveObserver(); + if (typeof window !== "undefined") { + window.dispatchEvent( + new CustomEvent("indexing-progress", { + detail: { + completed: 0, + total: 0, + indexing: false, + status: "Indexing paused — reload to rebuild", + }, + }), + ); + } + void resetSearchIndexes(); + }); +} /** * Hard-reset of all global-search persistence. diff --git a/src/plugins/built-in/globalSearch/src/indexing/worker/vectorWorker.ts b/src/plugins/built-in/globalSearch/src/indexing/worker/vectorWorker.ts index cca5bc45..728e2475 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/worker/vectorWorker.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/worker/vectorWorker.ts @@ -1,7 +1,15 @@ import { EmbeddingIndex, getEmbedding, initializeModel } from "embeddia"; import type { IndexItem } from "../types"; -import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog'; +import { verboseDebug, verboseInfo, verboseLog } from "./workerVerboseLog"; + +let ortWasmBase: string | null = null; + +async function configureOrtWasm(base: string): Promise { + const { env } = await import("@huggingface/transformers"); + env.backends.onnx.wasm = env.backends.onnx.wasm ?? {}; + env.backends.onnx.wasm.wasmPaths = base.endsWith("/") ? base : `${base}/`; +} let vectorIndex: EmbeddingIndex | null = null; let isInitialized = false; let initializationFailed = false; @@ -49,6 +57,9 @@ async function initWorker() { verboseDebug("Initializing vector worker..."); try { + if (ortWasmBase) { + await configureOrtWasm(ortWasmBase); + } await initializeModel(); vectorIndex = new EmbeddingIndex([]); @@ -562,6 +573,9 @@ self.addEventListener("message", async (e) => { switch (type) { case "init": + if (data?.ortWasmBase) { + ortWasmBase = data.ortWasmBase; + } await initWorker(); self.postMessage({ type: "ready" }); break; @@ -594,13 +608,3 @@ self.addEventListener("message", async (e) => { console.warn("Unknown message type:", type); } }); - -initWorker() - .then(() => { - self.postMessage({ type: "ready" }); - }) - .catch((err) => { - console.error("Initial worker initialization failed:", err); - - self.postMessage({ type: "ready" }); - }); diff --git a/src/plugins/built-in/globalSearch/src/indexing/worker/vectorWorkerManager.ts b/src/plugins/built-in/globalSearch/src/indexing/worker/vectorWorkerManager.ts index 01bff330..c8a34a49 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/worker/vectorWorkerManager.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/worker/vectorWorkerManager.ts @@ -1,6 +1,7 @@ import { refreshVectorCache } from "../../search/vector/vectorSearch"; import type { IndexItem } from "../types"; import { isVectorSearchSupported } from "../../utils/browserDetection"; +import { getOrtWasmBaseUrl } from "@/lib/transformersExtension"; import vectorWorker from "./vectorWorker.ts?inlineWorker"; import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog'; @@ -91,7 +92,7 @@ export class VectorWorkerManager { this.isInitialized = false; reject(new Error("Worker initialization timed out")); - }, 10000); + }, 60000); this.worker!.addEventListener("message", (e) => { const { type, data } = e.data; @@ -158,7 +159,10 @@ export class VectorWorkerManager { } }); - this.worker!.postMessage({ type: "init" }); + this.worker!.postMessage({ + type: "init", + data: { ortWasmBase: getOrtWasmBaseUrl() }, + }); }); } @@ -388,7 +392,7 @@ export class VectorWorkerManager { onProgress?: ProgressCallback, batchSize: number = 10, jobId?: string, - ): Promise { + ): Promise { // Skip if vector search is not supported if (!isVectorSearchSupported()) { verboseDebug("[VectorWorker] Vector search not supported - skipping streaming session"); @@ -398,13 +402,13 @@ export class VectorWorkerManager { message: "Vector search not available - using text search only", }); } - return; + return false; } // Only initialize if we expect items to process if (totalExpectedItems === 0) { verboseDebug("[VectorWorker] No items expected, not starting streaming session"); - return; + return false; } await this.ensureReady(); @@ -419,7 +423,7 @@ export class VectorWorkerManager { await new Promise((resolve) => setTimeout(resolve, 100)); } else { verboseDebug(`Streaming session for job ${jobId} already active`); - return; + return true; } } @@ -455,13 +459,20 @@ export class VectorWorkerManager { message: `Starting streaming vectorization for ${jobId}`, }); } + + return true; } async streamItems(items: IndexItem[]): Promise { + if (!isVectorSearchSupported()) { + return; + } + if (!this.streamingSession?.isActive) { - throw new Error( - "No active streaming session. Call startStreamingSession first.", + verboseDebug( + "[VectorWorker] streamItems skipped — no active streaming session", ); + return; } const uniqueItems = items.filter((item, index, arr) => { diff --git a/src/plugins/built-in/globalSearch/src/indexing/worker/workerVerboseLog.ts b/src/plugins/built-in/globalSearch/src/indexing/worker/workerVerboseLog.ts new file mode 100644 index 00000000..cf6c3bdc --- /dev/null +++ b/src/plugins/built-in/globalSearch/src/indexing/worker/workerVerboseLog.ts @@ -0,0 +1,13 @@ +/** Worker-safe logging — no webextension-polyfill or SettingsState. */ + +export function verboseDebug(...args: unknown[]): void { + if (typeof console !== "undefined") console.debug(...args); +} + +export function verboseInfo(...args: unknown[]): void { + if (typeof console !== "undefined") console.info(...args); +} + +export function verboseLog(...args: unknown[]): void { + if (typeof console !== "undefined") console.log(...args); +} diff --git a/src/plugins/built-in/globalSearch/src/search/vector/vectorSearch.ts b/src/plugins/built-in/globalSearch/src/search/vector/vectorSearch.ts index 606a3b9e..cbf6b47d 100644 --- a/src/plugins/built-in/globalSearch/src/search/vector/vectorSearch.ts +++ b/src/plugins/built-in/globalSearch/src/search/vector/vectorSearch.ts @@ -2,6 +2,7 @@ import { EmbeddingIndex, getEmbedding, initializeModel } from "embeddia"; import type { IndexItem } from "../../indexing/types"; import type { SearchResult } from "embeddia"; import { isVectorSearchSupported } from "../../utils/browserDetection"; +import { ensureTransformersEnv } from "@/lib/transformersExtension"; import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog'; let vectorIndex: EmbeddingIndex | null = null; @@ -24,6 +25,7 @@ export async function initVectorSearch() { initializationAttempted = true; try { + await ensureTransformersEnv(); await initializeModel(); vectorIndex = new EmbeddingIndex([]); vectorIndex.preloadIndexedDB(); diff --git a/src/plugins/core/dynamicLoader.ts b/src/plugins/core/dynamicLoader.ts index 339bba3c..665d1ccb 100644 --- a/src/plugins/core/dynamicLoader.ts +++ b/src/plugins/core/dynamicLoader.ts @@ -49,10 +49,16 @@ export function createLazyPlugin - +

No lessons for this day.

`; } else { @@ -310,7 +311,7 @@ function appendEngageNoticeEmptyState(container: HTMLElement, message: string) { const emptyState = document.createElement("div"); emptyState.classList.add("day-empty"); const img = document.createElement("img"); - img.src = browser.runtime.getURL(LogoLight); + img.src = resolveExtensionAssetUrl(LogoLight); const text = document.createElement("p"); text.innerText = message; emptyState.append(img, text); @@ -717,7 +718,7 @@ function showEngageTimetableError(message: string): void { dayContainer.classList.remove("loading"); dayContainer.innerHTML = `
- +

${message}

`; } @@ -728,7 +729,7 @@ function showEngageNoticesSectionError(message: string): void { noticeContainer.classList.remove("loading"); noticeContainer.innerHTML = `
- +

${message}

`; } diff --git a/src/seqta/utils/Loaders/LoadHomePage.ts b/src/seqta/utils/Loaders/LoadHomePage.ts index a28e9f89..52f96c2e 100644 --- a/src/seqta/utils/Loaders/LoadHomePage.ts +++ b/src/seqta/utils/Loaders/LoadHomePage.ts @@ -1,6 +1,7 @@ import { animate, stagger } from "motion"; import browser from "webextension-polyfill"; import LogoLight from "@/resources/icons/betterseqta-light-icon.png"; +import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl"; import assessmentsicon from "@/seqta/icons/assessmentsIcon"; import coursesicon from "@/seqta/icons/coursesIcon"; import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour"; @@ -385,7 +386,7 @@ function appendNoticeEmptyState(container: HTMLElement, message: string) { const emptyState = document.createElement("div"); emptyState.classList.add("day-empty"); const img = document.createElement("img"); - img.src = browser.runtime.getURL(LogoLight); + img.src = resolveExtensionAssetUrl(LogoLight); const text = document.createElement("p"); text.innerText = message; emptyState.append(img, text); @@ -786,7 +787,7 @@ function callHomeTimetable(date: string, change?: any) { const dummyDay = document.createElement("div"); dummyDay.classList.add("day-empty"); const img = document.createElement("img"); - img.src = browser.runtime.getURL(LogoLight); + img.src = resolveExtensionAssetUrl(LogoLight); const text = document.createElement("p"); text.innerText = "No lessons available."; dummyDay.append(img, text); @@ -1096,7 +1097,7 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) { if (assessments.length === 0) { upcomingitemcontainer!.innerHTML = `
- +

No assessments available.

`; } diff --git a/src/seqta/utils/SendNewsPage.ts b/src/seqta/utils/SendNewsPage.ts index a41d4dec..97189abb 100644 --- a/src/seqta/utils/SendNewsPage.ts +++ b/src/seqta/utils/SendNewsPage.ts @@ -4,6 +4,7 @@ import { delay } from "./delay"; import { settingsState } from "./listeners/SettingsState"; import browser from "webextension-polyfill"; import LogoLightOutline from "@/resources/icons/betterseqta-light-outline.png"; +import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl"; import { animate, stagger } from "motion"; import { verboseInfo } from "@/utils/verboseLog"; @@ -65,7 +66,7 @@ export async function SendNewsPage() { const emptyState = document.createElement("div"); emptyState.classList.add("day-empty"); const img = document.createElement("img"); - img.src = browser.runtime.getURL(LogoLightOutline); + img.src = resolveExtensionAssetUrl(LogoLightOutline); const text = document.createElement("p"); text.innerText = "No news articles available right now."; emptyState.append(img, text); @@ -86,7 +87,7 @@ export async function SendNewsPage() { if (article.urlToImage == "null" || article.urlToImage == null) { articleimage.style.cssText = ` - background-image: url(${browser.runtime.getURL(LogoLightOutline)}); + background-image: url(${resolveExtensionAssetUrl(LogoLightOutline)}); width: 20%; margin: 0 7.5%; `;