Prepare text search during media setup and retain query sessions through promotion

This commit is contained in:
karl committed 2026-10-13 09:27:40 +02:00
1 parent af6ce29125
commit 9aae3f8216
15 files changed
+1235 -65

No files matched your search

+41 -11
View File
@@ -16,6 +16,13 @@ import {
SEMANTIC_SETTLE_MS,
} from "./search-state";
import type { SemanticPhase, SearchFilters } from "./search-state";
import {
semanticIndexReady,
semanticIndexKey,
semanticRetryVisible,
semanticMediaReady,
} from "./semantic-setup-state";
import type { SemanticCoverage } from "./semantic-setup-state";
import { fileIcon, icon } from "./icons";
import {
SearchMeasurement,
@@ -102,6 +109,12 @@ interface SemanticStatus {
stale: boolean;
pending: number;
warming: boolean;
text_model_ready?: boolean;
media_model_ready?: boolean;
media_model_error?: string | null;
indexing_ready?: boolean;
requested_coverage?: SemanticCoverage;
coverage?: SemanticCoverage | null;
media_tools_ready?: boolean;
media_setup_error?: string | null;
media_failures?: { path: string; message: string }[];
@@ -160,6 +173,7 @@ let latestSearch: SearchRequest | null = null;
let shownLane: "lexical" | "hybrid" = "lexical";
let folderRevision = 0;
let localSemanticError: string | null = null;
let localModelSetupError: string | null = null;
let querySemanticFailed = false;
let previewPath: string | null = null;
const previewRevision = new PreviewRevision();
@@ -297,6 +311,7 @@ function queueSemanticRefresh(delay = 1000) {
async function buildSemanticIndex() {
if (semanticJobActive || indexingActive) return;
if (semanticStatus && !semanticIndexReady(semanticStatus)) return;
semanticJobActive = true;
localSemanticError = null;
element("semantic-status").textContent =
@@ -330,7 +345,9 @@ async function refreshSemanticStatus() {
const status = await invoke<SemanticStatus>("semantic_status");
semanticStatus = status;
refreshEmptySearchState();
const error = localSemanticError || status.error;
const setupError =
status.ready && status.media_model_error ? null : localModelSetupError;
const error = localSemanticError || status.error || setupError;
if (error && error !== expandedError) {
element<HTMLDetailsElement>("semantic-panel").open = true;
expandedError = error;
@@ -363,13 +380,15 @@ async function refreshSemanticStatus() {
message = "Preparing Google semantic search on your device…";
else if (status.downloading)
message =
status.downloaded_bytes >= status.total_bytes
? "Verifying the downloaded Google model…"
: `Downloading Google model · ${Math.round(status.downloaded_bytes / 1_000_000)} of ${Math.round(status.total_bytes / 1_000_000)} MB`;
status.ready && status.text_model_ready
? `${semanticAvailable(status) ? "Text search ready" : "Text model ready"} · Preparing media model · ${Math.round(status.downloaded_bytes / 1_000_000)} of ${Math.round(status.total_bytes / 1_000_000)} MB`
: status.downloaded_bytes >= status.total_bytes
? "Verifying the downloaded Google model…"
: `Downloading Google model · ${Math.round(status.downloaded_bytes / 1_000_000)} of ${Math.round(status.total_bytes / 1_000_000)} MB`;
else if (status.embedding || semanticJobActive)
message =
"Building your semantic index… Keyword search remains available.";
else if (status.ready && status.chunks > 0)
else if (semanticAvailable(status) && status.chunks > 0)
message = `Semantic search ready · ${status.chunks.toLocaleString()} content segments${status.stale ? " · Updates pending" : ""}${status.media_failures?.length ? ` · ${status.media_failures.length} files could not be indexed` : ""}`;
else if (status.ready)
message = "Google model ready. Index a folder to search by meaning.";
@@ -385,20 +404,29 @@ async function refreshSemanticStatus() {
? `Media decoder setup needs attention: ${status.media_setup_error}. Text search remains available.`
: status.worker_info && !status.worker_info.media_supported
? "The installed worker needs a media-capable runtime update. Text search remains available."
: status.media_tools_ready && status.worker_info?.media_supported
: semanticMediaReady(status) &&
status.media_tools_ready &&
status.worker_info?.media_supported
? "Image, audio, and video encoders are available locally."
: "Preparing local image, audio, and video support.";
element("semantic-detail").textContent += ` ${mediaDetail}`;
if (status.media_model_error)
element("semantic-detail").textContent +=
` Media model setup needs attention: ${status.media_model_error}. Text search remains available.`;
if (status.media_failures?.length) {
element("semantic-detail").textContent +=
` ${status.media_failures.length} files could not be indexed. The first issue: ${status.media_failures[0].path}: ${status.media_failures[0].message}. Filename search remains available; rebuild after fixing the file.`;
}
const retry = element<HTMLButtonElement>("retry-model");
retry.hidden = !error || status.ready;
retry.hidden = !semanticRetryVisible({ ...status, error });
retry.disabled = working || isCaptureSurface();
const rebuild = element<HTMLButtonElement>("rebuild-semantic");
rebuild.hidden = !status.ready || (!status.chunks && !status.pending);
rebuild.disabled = working || indexingActive || isCaptureSurface();
rebuild.disabled =
!semanticIndexReady(status) ||
semanticJobActive ||
indexingActive ||
isCaptureSurface();
element<HTMLSelectElement>("cpu-threads").disabled =
working || indexingActive || isCaptureSurface();
if (
@@ -406,10 +434,11 @@ async function refreshSemanticStatus() {
status.ready &&
status.stale &&
status.pending > 0 &&
!working &&
semanticIndexReady(status) &&
!semanticJobActive &&
!indexingActive
) {
const key = `${status.generation}:${status.pending}:${folderRevision}`;
const key = semanticIndexKey(status, folderRevision);
if (automaticIndex.claim(key)) {
void buildSemanticIndex();
return;
@@ -1276,12 +1305,13 @@ element("retry-model").addEventListener("click", async () => {
const button = element<HTMLButtonElement>("retry-model");
button.disabled = true;
localSemanticError = null;
localModelSetupError = null;
element("semantic-status").textContent = "Retrying Google model setup…";
queueSemanticRefresh();
try {
await invoke("setup_model");
} catch (error) {
localSemanticError = String(error);
localModelSetupError = String(error);
} finally {
button.disabled = false;
queueSemanticRefresh(0);