refactor(ux): consolidate BMAD skills, update design system, and clean up Prisma generated client
This commit is contained in:
141
keep-notes/lib/ai/services/chat.service.ts
Normal file
141
keep-notes/lib/ai/services/chat.service.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Chat Service
|
||||
* Handles conversational AI with context retrieval (RAG)
|
||||
*/
|
||||
|
||||
import { semanticSearchService } from './semantic-search.service'
|
||||
import { getChatProvider } from '../factory'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
import { loadTranslations, getTranslationValue, SupportedLanguage } from '@/lib/i18n'
|
||||
|
||||
// Default untitled text for fallback
|
||||
const DEFAULT_UNTITLED = 'Untitled'
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'user' | 'assistant' | 'system'
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface ChatResponse {
|
||||
message: string
|
||||
conversationId?: string
|
||||
suggestedNotes?: Array<{ id: string; title: string }>
|
||||
}
|
||||
|
||||
export class ChatService {
|
||||
/**
|
||||
* Main chat entry point with context retrieval
|
||||
*/
|
||||
async chat(
|
||||
message: string,
|
||||
conversationId?: string,
|
||||
notebookId?: string,
|
||||
language: SupportedLanguage = 'en'
|
||||
): Promise<ChatResponse> {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
const userId = session.user.id
|
||||
|
||||
// Load translations for the requested language
|
||||
const translations = await loadTranslations(language)
|
||||
const untitledText = getTranslationValue(translations, 'notes.untitled') || DEFAULT_UNTITLED
|
||||
const noNotesFoundText = getTranslationValue(translations, 'chat.noNotesFoundForContext') ||
|
||||
'No relevant notes found for this question. Answer with your general knowledge.'
|
||||
|
||||
// 1. Manage Conversation
|
||||
let conversation: any
|
||||
if (conversationId) {
|
||||
conversation = await prisma.conversation.findUnique({
|
||||
where: { id: conversationId },
|
||||
include: { messages: { orderBy: { createdAt: 'asc' }, take: 10 } }
|
||||
})
|
||||
}
|
||||
|
||||
if (!conversation) {
|
||||
conversation = await prisma.conversation.create({
|
||||
data: {
|
||||
userId,
|
||||
notebookId,
|
||||
title: message.substring(0, 50) + '...'
|
||||
},
|
||||
include: { messages: true }
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Retrieval (RAG)
|
||||
// We search for relevant notes based on the current message or notebook context
|
||||
// Lower threshold for notebook-specific searches to ensure we find relevant content
|
||||
const searchResults = await semanticSearchService.search(message, {
|
||||
notebookId,
|
||||
limit: 10,
|
||||
threshold: notebookId ? 0.3 : 0.5
|
||||
})
|
||||
|
||||
const contextNotes = searchResults.map(r =>
|
||||
`NOTE [${r.title || untitledText}]: ${r.content}`
|
||||
).join('\n\n---\n\n')
|
||||
|
||||
// 3. System Prompt Synthesis
|
||||
const systemPrompt = `Tu es l'Assistant IA de Memento. Tu accompagnes l'utilisateur dans sa réflexion.
|
||||
Tes réponses doivent être concises, premium et utiles.
|
||||
${contextNotes.length > 0 ? `Voici des extraits de notes de l'utilisateur qui pourraient t'aider à répondre :\n\n${contextNotes}\n\nUtilise ces informations si elles sont pertinentes, mais ne les cite pas mot pour mot sauf si demandé.` : noNotesFoundText}
|
||||
Si l'utilisateur pose une question sur un carnet spécifique, reste focalisé sur ce contexte.`
|
||||
|
||||
// 4. Call AI Provider
|
||||
const history = (conversation.messages || []).map((m: any) => ({
|
||||
role: m.role,
|
||||
content: m.content
|
||||
}))
|
||||
|
||||
const currentMessages = [...history, { role: 'user', content: message }]
|
||||
|
||||
const config = await getSystemConfig()
|
||||
const provider = getChatProvider(config)
|
||||
const aiResponse = await provider.chat(currentMessages, systemPrompt)
|
||||
|
||||
// 5. Save Messages to DB
|
||||
await prisma.chatMessage.createMany({
|
||||
data: [
|
||||
{ conversationId: conversation.id, role: 'user', content: message },
|
||||
{ conversationId: conversation.id, role: 'assistant', content: aiResponse.text }
|
||||
]
|
||||
})
|
||||
|
||||
return {
|
||||
message: aiResponse.text,
|
||||
conversationId: conversation.id,
|
||||
suggestedNotes: searchResults.map(r => ({ id: r.noteId, title: r.title || untitledText }))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation history
|
||||
*/
|
||||
async getHistory(conversationId: string) {
|
||||
return prisma.conversation.findUnique({
|
||||
where: { id: conversationId },
|
||||
include: {
|
||||
messages: {
|
||||
orderBy: { createdAt: 'asc' }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* List user conversations
|
||||
*/
|
||||
async listConversations(userId: string) {
|
||||
return prisma.conversation.findMany({
|
||||
where: { userId },
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
take: 20
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const chatService = new ChatService()
|
||||
Reference in New Issue
Block a user