From efdd03ce8e0a2412e6f441f6f4b7bbdfbd70d154 Mon Sep 17 00:00:00 2001 From: SethBurkart123 Date: Thu, 22 May 2025 14:49:13 +1000 Subject: [PATCH] feat: make message items in search open the message --- .../globalSearch/src/indexing/actions.ts | 43 +++++- .../globalSearch/src/indexing/indexer.ts | 128 +++++++----------- 2 files changed, 91 insertions(+), 80 deletions(-) diff --git a/src/plugins/built-in/globalSearch/src/indexing/actions.ts b/src/plugins/built-in/globalSearch/src/indexing/actions.ts index cb654526..9d43071a 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/actions.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/actions.ts @@ -1,4 +1,7 @@ +import { waitForElm } from "@/seqta/utils/waitForElm"; import type { IndexItem } from "./types"; +import ReactFiber from "@/seqta/utils/ReactFiber"; +import { delay } from "@/seqta/utils/delay"; interface MessageMetadata { messageId: number; @@ -26,13 +29,45 @@ interface AssessmentMetadata { type ActionHandler = (item: IndexItem & { metadata: T }) => void; export const actionMap: Record> = { - message: ((item: IndexItem & { metadata: MessageMetadata }) => { - window.location.hash = `#?page=/messages&id=${item.metadata.messageId}`; + message: (async (item: IndexItem & { metadata: MessageMetadata }) => { + window.location.hash = `#?page=/messages`; + + await waitForElm('[class*="Viewer__Viewer___"] > div', true, 20); + + // Select the specific direct message + ReactFiber.find('[class*="Viewer__Viewer___"] > div').setState({ + selected: new Set([item.metadata.messageId]), + }); + + // send a network request to mark as read + fetch('/seqta/student/save/message', { + method: "POST", + credentials: "include", + body: JSON.stringify({ + items: [item.metadata.messageId], + mode: 'x-read', + read: true, + }), + }); + + await delay(10); + + const button = document.querySelector('[class*="MessageList__selected___"]'); + if (button) { + (button as HTMLElement).click(); + } }) as ActionHandler, - assessment: ((item: IndexItem & { metadata: AssessmentMetadata }) => { + assessment: (async (item: IndexItem & { metadata: AssessmentMetadata }) => { if (item.metadata.isMessageBased) { - window.location.hash = `#?page=/messages&id=${item.metadata.messageId}`; + window.location.hash = `#?page=/messages`; + + await waitForElm('[class*="Viewer__Viewer___"] > div', true, 20); + + // Select the specific direct message + ReactFiber.find('[class*="Viewer__Viewer___"] > div').setState({ + selected: new Set([item.metadata.messageId]), + }); } else { window.location.hash = `#?page=/assessments&id=${item.metadata.assessmentId}`; } diff --git a/src/plugins/built-in/globalSearch/src/indexing/indexer.ts b/src/plugins/built-in/globalSearch/src/indexing/indexer.ts index bd7c9aa5..e7b8aabc 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/indexer.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/indexer.ts @@ -100,19 +100,19 @@ export async function loadAllStoredItems(): Promise { item.text && item.category && item.actionId && - job.renderComponentId + job.renderComponentId // job might not be defined if store exists but job was removed ) { all.push(item); } else { - console.warn(`Skipping invalid item from job ${jobId}:`, item); + console.warn(`Skipping invalid item from job store ${jobId}:`, item); } } } catch (error) { - console.error(`Error loading items for job ${jobId}:`, error); + console.error(`Error loading items for job store ${jobId}:`, error); } } console.debug( - `[Indexer] Loaded ${all.length} items from non-vector storage.`, + `[Indexer] Loaded ${all.length} items from all primary stores.`, ); return all; } @@ -135,8 +135,6 @@ export async function runIndexing(): Promise { const totalSteps = jobIds.length + 1; dispatchProgress(completedJobs, totalSteps, true, "Starting jobs"); - const allItemsFromJobs: IndexItem[] = []; - // --- Step 1: Run Fetching/Storing Jobs (Main Thread) --- for (const jobId of jobIds) { dispatchProgress( @@ -204,21 +202,18 @@ export async function runIndexing(): Promise { console.debug(`%c[Indexer] Running job "${jobId}"...`, "color: #4ea1ff"); try { - const newItemsRaw = await job.run(ctx); + const newItemsRaw = await job.run(ctx); // newItemsRaw are items *returned* by the job. + // Some jobs (like messages) might add via ctx.addItem and return []. const stored = await getStoredItems(); let merged = mergeItems(stored, newItemsRaw); if (job.purge) merged = job.purge(merged); - - console.log(`[Indexer] ${job.label}: ${merged.length} items stored in '${jobId}' store (non-vector).`); - + await setStoredItems(merged); await updateLastRunMeta(jobId); - allItemsFromJobs.push(...newItemsRaw); - console.debug( - `%c[Indexer] ${job.label}: ${newItemsRaw.length} new items from run, ${merged.length} total stored in '${jobId}' store (non-vector).`, + `%c[Indexer] ${job.label}: ${newItemsRaw.length} new items reported by run, ${merged.length} total items now in '${jobId}' store.`, "color: #00c46f", ); } catch (err) { @@ -236,18 +231,19 @@ export async function runIndexing(): Promise { } // --- Step 2: Delegate Vectorization to Worker (Off Main Thread) --- - if (allItemsFromJobs.length > 0) { + // Load ALL items from the primary stores. The worker will handle deduplication against its own vector store. + const allItemsInPrimaryStores = await loadAllStoredItems(); + + if (allItemsInPrimaryStores.length > 0) { console.debug( - `%c[Indexer] Sending ${allItemsFromJobs.length} items to worker for vectorization...`, + `%c[Indexer] Sending ${allItemsInPrimaryStores.length} items from primary stores to worker for vectorization check...`, "color: #4ea1ff", ); - dispatchProgress(completedJobs, totalSteps, true, "Starting vectorization"); + dispatchProgress(completedJobs, totalSteps, true, "Starting vectorization of stored items"); try { const workerManager = VectorWorkerManager.getInstance(); - // Pass a progress callback to the worker manager - await workerManager.processItems(allItemsFromJobs, (progress) => { - // Update overall progress based on worker feedback + await workerManager.processItems(allItemsInPrimaryStores, (progress) => { let detailMessage = progress.message || ""; if ( progress.status === "processing" && @@ -255,26 +251,26 @@ export async function runIndexing(): Promise { progress.processed !== undefined ) { detailMessage = `Vectorizing: ${progress.processed} / ${progress.total}`; - // You could potentially update the 'completed' count more granularly here - // For simplicity, we'll just update the detail message } else if (progress.status === "complete") { detailMessage = "Vectorization complete"; // Mark the vectorization step as complete + completedJobs++; // Increment completion count *after* vectorization finishes dispatchProgress( + completedJobs, totalSteps, - totalSteps, - true, - "Vectorization finished", + false, // Indexing finished + "Indexing finished", + detailMessage ); } else if (progress.status === "error") { detailMessage = `Vectorization error: ${progress.message}`; dispatchProgress( completedJobs, totalSteps, - true, + false, // Indexing stopped "Vectorization failed", detailMessage, - ); // Show error + ); } else if (progress.status === "started") { detailMessage = `Vectorization started for ${progress.total} items`; } else if (progress.status === "cancelled") { @@ -282,49 +278,27 @@ export async function runIndexing(): Promise { dispatchProgress( completedJobs, totalSteps, - true, + false, // Indexing stopped "Vectorization cancelled", detailMessage, ); } - // Update the status detail - dispatchProgress( - completedJobs, - totalSteps, - true, - "Vectorization in progress", - detailMessage, - ); - - // When worker signals completion of *its* task, mark the final step complete - if (progress.status === "complete") { - completedJobs++; // Increment completion count *after* vectorization finishes - dispatchProgress( - completedJobs, - totalSteps, - false, - "Indexing finished", - ); // Set indexing to false - } else if ( - progress.status === "error" || - progress.status === "cancelled" - ) { - // Don't increment completed count on failure/cancel, just stop indexing indicator - dispatchProgress( - completedJobs, - totalSteps, - false, - "Indexing stopped due to error/cancel", - ); + // Update the status detail for ongoing vectorization + if (progress.status !== "complete" && progress.status !== "error" && progress.status !== "cancelled") { + dispatchProgress( + completedJobs, // Still on job completion count + totalSteps, + true, // Indexing still active + "Vectorization in progress", + detailMessage, + ); } }); console.debug( - "%c[Indexer] Vectorization task sent to worker.", + "%c[Indexer] Vectorization task for stored items sent to worker.", "color: green", ); - // Note: runIndexing might return *before* vectorization is complete now. - // The progress updates will signal the true end state. } catch (error) { console.error( `%c[Indexer] ❌ Failed to send items to vector worker:`, @@ -334,41 +308,43 @@ export async function runIndexing(): Promise { dispatchProgress( completedJobs, totalSteps, - false, + false, // Indexing stopped "Vectorization failed", String(error), - ); // Stop indexing indicator + ); } } else { console.debug( - "%c[Indexer] No items to send for vectorization.", + "%c[Indexer] No items found in primary stores to send for vectorization.", "color: gray", ); - // If no vectorization needed, indexing is done here. completedJobs++; // Count the "skipped" vectorization step dispatchProgress( completedJobs, totalSteps, - false, - "Indexing finished (no vectorization needed)", + false, // Indexing finished + "Indexing finished (no items for vectorization)", ); } - // Stop heartbeat ONLY when all jobs *and* the vectorization dispatch are done. - // The actual *completion* of vectorization is now asynchronous. stopHeartbeat(); - // Before loading dynamic items, attach renderComponent to each item if available - allItemsFromJobs.forEach(item => { - const renderComponent = renderComponentMap[item.renderComponentId]; - if (renderComponent) { - item.renderComponent = renderComponent; + // Update dynamic items with everything that's now in the primary stores + // These items are either already vectorized or will be by the worker. + allItemsInPrimaryStores.forEach(item => { + // Ensure job still exists for renderComponentId mapping + const jobDef = jobs[item.category] || Object.values(jobs).find(j => j.id === item.category) || jobs[item.renderComponentId]; + if (jobDef) { + const renderComponent = renderComponentMap[jobDef.renderComponentId]; + if (renderComponent) { + item.renderComponent = renderComponent; + } + } else if (renderComponentMap[item.renderComponentId]) { // Fallback if category doesn't match a job id directly + item.renderComponent = renderComponentMap[item.renderComponentId]; } }); - loadDynamicItems(allItemsFromJobs); + loadDynamicItems(allItemsInPrimaryStores); window.dispatchEvent(new Event("dynamic-items-updated")); - // Final progress update might be handled by the worker callback now. - // dispatchProgress(completedJobs, totalSteps, false); // This might be premature } function mergeItems(existing: IndexItem[], incoming: IndexItem[]): IndexItem[] { @@ -381,4 +357,4 @@ function mergeItems(existing: IndexItem[], incoming: IndexItem[]): IndexItem[] { if (item && item.id) map.set(item.id, item); } return Array.from(map.values()); -} +} \ No newline at end of file