diff --git a/src/public/app/widgets/type_widgets/options/ai_settings.ts b/src/public/app/widgets/type_widgets/options/ai_settings.ts
index 749c54a6e..fd666958b 100644
--- a/src/public/app/widgets/type_widgets/options/ai_settings.ts
+++ b/src/public/app/widgets/type_widgets/options/ai_settings.ts
@@ -201,7 +201,10 @@ export default class AiSettingsWidget extends OptionsWidget {
+
+ ${t("ai_llm.progress")}: -
+
+
@@ -456,6 +459,30 @@ export default class AiSettingsWidget extends OptionsWidget {
$progressBar.css('width', `${stats.percentComplete}%`);
$progressBar.attr('aria-valuenow', stats.percentComplete.toString());
$progressBar.text(`${stats.percentComplete}%`);
+
+ // Update status text based on state
+ const $statusText = this.$widget.find('.embedding-status-text');
+ if (stats.queuedNotesCount > 0) {
+ $statusText.text(t("ai_llm.processing", { percentage: stats.percentComplete }));
+ } else if (stats.percentComplete < 100) {
+ $statusText.text(t("ai_llm.incomplete", { percentage: stats.percentComplete }));
+ } else {
+ $statusText.text(t("ai_llm.complete"));
+ }
+
+ // Change progress bar color based on state
+ if (stats.queuedNotesCount > 0) {
+ // Processing in progress - use animated progress bar
+ $progressBar.addClass('progress-bar-striped progress-bar-animated bg-info');
+ $progressBar.removeClass('bg-success');
+ } else if (stats.percentComplete < 100) {
+ // Incomplete - use standard progress bar
+ $progressBar.removeClass('progress-bar-striped progress-bar-animated bg-info bg-success');
+ } else {
+ // Complete - show success color
+ $progressBar.removeClass('progress-bar-striped progress-bar-animated bg-info');
+ $progressBar.addClass('bg-success');
+ }
}
} catch (error) {
console.error("Error fetching embedding stats:", error);
diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json
index 446fa055b..25ab6263d 100644
--- a/src/public/translations/en/translation.json
+++ b/src/public/translations/en/translation.json
@@ -1174,6 +1174,10 @@
"failed_notes": "Failed Notes",
"last_processed": "Last Processed",
"never": "Never",
+ "progress": "Progress",
+ "processing": "Processing ({{percentage}}%)",
+ "incomplete": "Incomplete ({{percentage}}%)",
+ "complete": "Complete (100%)",
"refresh_stats": "Refresh Stats",
"refreshing": "Refreshing...",
"stats_error": "Error fetching embedding statistics",
diff --git a/src/services/llm/embeddings/vector_store.ts b/src/services/llm/embeddings/vector_store.ts
index 568fedbfa..3f17c0227 100644
--- a/src/services/llm/embeddings/vector_store.ts
+++ b/src/services/llm/embeddings/vector_store.ts
@@ -496,13 +496,32 @@ export async function getEmbeddingStats() {
"SELECT utcDateCreated FROM note_embeddings ORDER BY utcDateCreated DESC LIMIT 1"
) as string | null || null;
+ // Calculate the actual completion percentage
+ // When reprocessing, we need to consider notes in the queue as not completed yet
+ // We calculate the percentage of notes that are embedded and NOT in the queue
+
+ // First, get the count of notes that are both in the embeddings table and queue
+ const notesInQueueWithEmbeddings = await sql.getValue(`
+ SELECT COUNT(DISTINCT eq.noteId)
+ FROM embedding_queue eq
+ JOIN note_embeddings ne ON eq.noteId = ne.noteId
+ `) as number;
+
+ // The number of notes with valid, up-to-date embeddings
+ const upToDateEmbeddings = embeddedNotesCount - notesInQueueWithEmbeddings;
+
+ // Calculate the percentage of notes that are properly embedded
+ const percentComplete = totalNotesCount > 0
+ ? Math.round((upToDateEmbeddings / totalNotesCount) * 100)
+ : 0;
+
return {
totalNotesCount,
embeddedNotesCount,
queuedNotesCount,
failedNotesCount,
lastProcessedDate,
- percentComplete: totalNotesCount > 0 ? Math.round((embeddedNotesCount / totalNotesCount) * 100) : 0
+ percentComplete: Math.max(0, Math.min(100, percentComplete)) // Ensure between 0-100
};
}