fix(memory-echo): fix broken AI provider config and auto-generate missing embeddings
- Fix critical bug: used prisma.systemConfig.findFirst() which returned
a single {key,value} record instead of the full config object needed
by getAIProvider(). Replaced with getSystemConfig() that returns all
config as a proper Record<string,string>.
- Add ensureEmbeddings() to auto-generate embeddings for notes that
lack them before searching for connections. This fixes the case where
notes created without an AI provider configured never got embeddings,
making Memory Echo silently return zero connections.
- Restore demo mode polling (15s interval after dismiss) in the
notification component.
- Integrate ComparisonModal and FusionModal in the notification card
with merge button for direct note fusion from the notification.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { getAIProvider } from '../factory'
|
||||
import { cosineSimilarity } from '@/lib/utils'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import prisma from '@/lib/prisma'
|
||||
|
||||
export interface NoteConnection {
|
||||
@@ -52,10 +53,53 @@ export class MemoryEchoService {
|
||||
private readonly MIN_DAYS_APART_DEMO = 0 // No delay for demo mode
|
||||
private readonly MAX_INSIGHTS_PER_USER = 100 // Prevent spam
|
||||
|
||||
/**
|
||||
* Generate embeddings for notes that don't have one yet
|
||||
*/
|
||||
private async ensureEmbeddings(userId: string): Promise<void> {
|
||||
const notesWithoutEmbeddings = await prisma.note.findMany({
|
||||
where: {
|
||||
userId,
|
||||
isArchived: false,
|
||||
trashedAt: null,
|
||||
noteEmbedding: { is: null }
|
||||
},
|
||||
select: { id: true, content: true }
|
||||
})
|
||||
|
||||
if (notesWithoutEmbeddings.length === 0) return
|
||||
|
||||
try {
|
||||
const config = await getSystemConfig()
|
||||
const provider = getAIProvider(config)
|
||||
|
||||
for (const note of notesWithoutEmbeddings) {
|
||||
if (!note.content || note.content.trim().length === 0) continue
|
||||
try {
|
||||
const embedding = await provider.getEmbeddings(note.content)
|
||||
if (embedding && embedding.length > 0) {
|
||||
await prisma.noteEmbedding.upsert({
|
||||
where: { noteId: note.id },
|
||||
create: { noteId: note.id, embedding: JSON.stringify(embedding) },
|
||||
update: { embedding: JSON.stringify(embedding) }
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// Skip this note, continue with others
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Provider not configured — nothing we can do
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find meaningful connections between user's notes
|
||||
*/
|
||||
async findConnections(userId: string, demoMode: boolean = false): Promise<NoteConnection[]> {
|
||||
// Ensure all notes have embeddings before searching for connections
|
||||
await this.ensureEmbeddings(userId)
|
||||
|
||||
// Get all user's notes with embeddings
|
||||
const notes = await prisma.note.findMany({
|
||||
where: {
|
||||
@@ -151,8 +195,8 @@ export class MemoryEchoService {
|
||||
note2Content: string
|
||||
): Promise<string> {
|
||||
try {
|
||||
const config = await prisma.systemConfig.findFirst()
|
||||
const provider = getAIProvider(config || undefined)
|
||||
const config = await getSystemConfig()
|
||||
const provider = getAIProvider(config)
|
||||
|
||||
const note1Desc = note1Title || 'Untitled note'
|
||||
const note2Desc = note2Title || 'Untitled note'
|
||||
@@ -366,6 +410,9 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu
|
||||
* Get all connections for a specific note
|
||||
*/
|
||||
async getConnectionsForNote(noteId: string, userId: string): Promise<NoteConnection[]> {
|
||||
// Ensure all notes have embeddings before searching
|
||||
await this.ensureEmbeddings(userId)
|
||||
|
||||
// Get the note with embedding
|
||||
const targetNote = await prisma.note.findUnique({
|
||||
where: { id: noteId },
|
||||
|
||||
Reference in New Issue
Block a user