mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
feat: make message items in search open the message
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
import type { IndexItem } from "./types";
|
import type { IndexItem } from "./types";
|
||||||
|
import ReactFiber from "@/seqta/utils/ReactFiber";
|
||||||
|
import { delay } from "@/seqta/utils/delay";
|
||||||
|
|
||||||
interface MessageMetadata {
|
interface MessageMetadata {
|
||||||
messageId: number;
|
messageId: number;
|
||||||
@@ -26,13 +29,45 @@ interface AssessmentMetadata {
|
|||||||
type ActionHandler<T = any> = (item: IndexItem & { metadata: T }) => void;
|
type ActionHandler<T = any> = (item: IndexItem & { metadata: T }) => void;
|
||||||
|
|
||||||
export const actionMap: Record<string, ActionHandler<any>> = {
|
export const actionMap: Record<string, ActionHandler<any>> = {
|
||||||
message: ((item: IndexItem & { metadata: MessageMetadata }) => {
|
message: (async (item: IndexItem & { metadata: MessageMetadata }) => {
|
||||||
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]),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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<any>,
|
}) as ActionHandler<any>,
|
||||||
|
|
||||||
assessment: ((item: IndexItem & { metadata: AssessmentMetadata }) => {
|
assessment: (async (item: IndexItem & { metadata: AssessmentMetadata }) => {
|
||||||
if (item.metadata.isMessageBased) {
|
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 {
|
} else {
|
||||||
window.location.hash = `#?page=/assessments&id=${item.metadata.assessmentId}`;
|
window.location.hash = `#?page=/assessments&id=${item.metadata.assessmentId}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,19 +100,19 @@ export async function loadAllStoredItems(): Promise<IndexItem[]> {
|
|||||||
item.text &&
|
item.text &&
|
||||||
item.category &&
|
item.category &&
|
||||||
item.actionId &&
|
item.actionId &&
|
||||||
job.renderComponentId
|
job.renderComponentId // job might not be defined if store exists but job was removed
|
||||||
) {
|
) {
|
||||||
all.push(item);
|
all.push(item);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Skipping invalid item from job ${jobId}:`, item);
|
console.warn(`Skipping invalid item from job store ${jobId}:`, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error loading items for job ${jobId}:`, error);
|
console.error(`Error loading items for job store ${jobId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.debug(
|
console.debug(
|
||||||
`[Indexer] Loaded ${all.length} items from non-vector storage.`,
|
`[Indexer] Loaded ${all.length} items from all primary stores.`,
|
||||||
);
|
);
|
||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
@@ -135,8 +135,6 @@ export async function runIndexing(): Promise<void> {
|
|||||||
const totalSteps = jobIds.length + 1;
|
const totalSteps = jobIds.length + 1;
|
||||||
dispatchProgress(completedJobs, totalSteps, true, "Starting jobs");
|
dispatchProgress(completedJobs, totalSteps, true, "Starting jobs");
|
||||||
|
|
||||||
const allItemsFromJobs: IndexItem[] = [];
|
|
||||||
|
|
||||||
// --- Step 1: Run Fetching/Storing Jobs (Main Thread) ---
|
// --- Step 1: Run Fetching/Storing Jobs (Main Thread) ---
|
||||||
for (const jobId of jobIds) {
|
for (const jobId of jobIds) {
|
||||||
dispatchProgress(
|
dispatchProgress(
|
||||||
@@ -204,21 +202,18 @@ export async function runIndexing(): Promise<void> {
|
|||||||
console.debug(`%c[Indexer] Running job "${jobId}"...`, "color: #4ea1ff");
|
console.debug(`%c[Indexer] Running job "${jobId}"...`, "color: #4ea1ff");
|
||||||
|
|
||||||
try {
|
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();
|
const stored = await getStoredItems();
|
||||||
|
|
||||||
let merged = mergeItems(stored, newItemsRaw);
|
let merged = mergeItems(stored, newItemsRaw);
|
||||||
if (job.purge) merged = job.purge(merged);
|
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 setStoredItems(merged);
|
||||||
await updateLastRunMeta(jobId);
|
await updateLastRunMeta(jobId);
|
||||||
|
|
||||||
allItemsFromJobs.push(...newItemsRaw);
|
|
||||||
|
|
||||||
console.debug(
|
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",
|
"color: #00c46f",
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -236,18 +231,19 @@ export async function runIndexing(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Step 2: Delegate Vectorization to Worker (Off Main Thread) ---
|
// --- 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(
|
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",
|
"color: #4ea1ff",
|
||||||
);
|
);
|
||||||
dispatchProgress(completedJobs, totalSteps, true, "Starting vectorization");
|
dispatchProgress(completedJobs, totalSteps, true, "Starting vectorization of stored items");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const workerManager = VectorWorkerManager.getInstance();
|
const workerManager = VectorWorkerManager.getInstance();
|
||||||
// Pass a progress callback to the worker manager
|
await workerManager.processItems(allItemsInPrimaryStores, (progress) => {
|
||||||
await workerManager.processItems(allItemsFromJobs, (progress) => {
|
|
||||||
// Update overall progress based on worker feedback
|
|
||||||
let detailMessage = progress.message || "";
|
let detailMessage = progress.message || "";
|
||||||
if (
|
if (
|
||||||
progress.status === "processing" &&
|
progress.status === "processing" &&
|
||||||
@@ -255,26 +251,26 @@ export async function runIndexing(): Promise<void> {
|
|||||||
progress.processed !== undefined
|
progress.processed !== undefined
|
||||||
) {
|
) {
|
||||||
detailMessage = `Vectorizing: ${progress.processed} / ${progress.total}`;
|
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") {
|
} else if (progress.status === "complete") {
|
||||||
detailMessage = "Vectorization complete";
|
detailMessage = "Vectorization complete";
|
||||||
// Mark the vectorization step as complete
|
// Mark the vectorization step as complete
|
||||||
|
completedJobs++; // Increment completion count *after* vectorization finishes
|
||||||
dispatchProgress(
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
totalSteps,
|
totalSteps,
|
||||||
totalSteps,
|
false, // Indexing finished
|
||||||
true,
|
"Indexing finished",
|
||||||
"Vectorization finished",
|
detailMessage
|
||||||
);
|
);
|
||||||
} else if (progress.status === "error") {
|
} else if (progress.status === "error") {
|
||||||
detailMessage = `Vectorization error: ${progress.message}`;
|
detailMessage = `Vectorization error: ${progress.message}`;
|
||||||
dispatchProgress(
|
dispatchProgress(
|
||||||
completedJobs,
|
completedJobs,
|
||||||
totalSteps,
|
totalSteps,
|
||||||
true,
|
false, // Indexing stopped
|
||||||
"Vectorization failed",
|
"Vectorization failed",
|
||||||
detailMessage,
|
detailMessage,
|
||||||
); // Show error
|
);
|
||||||
} else if (progress.status === "started") {
|
} else if (progress.status === "started") {
|
||||||
detailMessage = `Vectorization started for ${progress.total} items`;
|
detailMessage = `Vectorization started for ${progress.total} items`;
|
||||||
} else if (progress.status === "cancelled") {
|
} else if (progress.status === "cancelled") {
|
||||||
@@ -282,49 +278,27 @@ export async function runIndexing(): Promise<void> {
|
|||||||
dispatchProgress(
|
dispatchProgress(
|
||||||
completedJobs,
|
completedJobs,
|
||||||
totalSteps,
|
totalSteps,
|
||||||
true,
|
false, // Indexing stopped
|
||||||
"Vectorization cancelled",
|
"Vectorization cancelled",
|
||||||
detailMessage,
|
detailMessage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the status detail
|
// Update the status detail for ongoing vectorization
|
||||||
dispatchProgress(
|
if (progress.status !== "complete" && progress.status !== "error" && progress.status !== "cancelled") {
|
||||||
completedJobs,
|
dispatchProgress(
|
||||||
totalSteps,
|
completedJobs, // Still on job completion count
|
||||||
true,
|
totalSteps,
|
||||||
"Vectorization in progress",
|
true, // Indexing still active
|
||||||
detailMessage,
|
"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",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.debug(
|
console.debug(
|
||||||
"%c[Indexer] Vectorization task sent to worker.",
|
"%c[Indexer] Vectorization task for stored items sent to worker.",
|
||||||
"color: green",
|
"color: green",
|
||||||
);
|
);
|
||||||
// Note: runIndexing might return *before* vectorization is complete now.
|
|
||||||
// The progress updates will signal the true end state.
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`%c[Indexer] ❌ Failed to send items to vector worker:`,
|
`%c[Indexer] ❌ Failed to send items to vector worker:`,
|
||||||
@@ -334,41 +308,43 @@ export async function runIndexing(): Promise<void> {
|
|||||||
dispatchProgress(
|
dispatchProgress(
|
||||||
completedJobs,
|
completedJobs,
|
||||||
totalSteps,
|
totalSteps,
|
||||||
false,
|
false, // Indexing stopped
|
||||||
"Vectorization failed",
|
"Vectorization failed",
|
||||||
String(error),
|
String(error),
|
||||||
); // Stop indexing indicator
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.debug(
|
console.debug(
|
||||||
"%c[Indexer] No items to send for vectorization.",
|
"%c[Indexer] No items found in primary stores to send for vectorization.",
|
||||||
"color: gray",
|
"color: gray",
|
||||||
);
|
);
|
||||||
// If no vectorization needed, indexing is done here.
|
|
||||||
completedJobs++; // Count the "skipped" vectorization step
|
completedJobs++; // Count the "skipped" vectorization step
|
||||||
dispatchProgress(
|
dispatchProgress(
|
||||||
completedJobs,
|
completedJobs,
|
||||||
totalSteps,
|
totalSteps,
|
||||||
false,
|
false, // Indexing finished
|
||||||
"Indexing finished (no vectorization needed)",
|
"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();
|
stopHeartbeat();
|
||||||
|
|
||||||
// Before loading dynamic items, attach renderComponent to each item if available
|
// Update dynamic items with everything that's now in the primary stores
|
||||||
allItemsFromJobs.forEach(item => {
|
// These items are either already vectorized or will be by the worker.
|
||||||
const renderComponent = renderComponentMap[item.renderComponentId];
|
allItemsInPrimaryStores.forEach(item => {
|
||||||
if (renderComponent) {
|
// Ensure job still exists for renderComponentId mapping
|
||||||
item.renderComponent = renderComponent;
|
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"));
|
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[] {
|
function mergeItems(existing: IndexItem[], incoming: IndexItem[]): IndexItem[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user