diff --git a/memento-note/app/(admin)/admin/settings/admin-settings-form.tsx b/memento-note/app/(admin)/admin/settings/admin-settings-form.tsx index 7632bcf..e7b120a 100644 --- a/memento-note/app/(admin)/admin/settings/admin-settings-form.tsx +++ b/memento-note/app/(admin)/admin/settings/admin-settings-form.tsx @@ -446,27 +446,21 @@ export function AdminSettingsForm({ config }: { config: Record }
- - + 0 + ? ollamaTagsModels.map((m) => ({ value: m, label: m })) + : selectedTagsModel + ? [{ value: selectedTagsModel, label: `${selectedTagsModel} (${t('admin.ai.saved')})` }] + : [] + } value={selectedTagsModel} - onChange={(e) => setSelectedTagsModel(e.target.value)} - className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" - > - {ollamaTagsModels.length > 0 ? ( - <> - {!ollamaTagsModels.includes(selectedTagsModel) && selectedTagsModel && ( - - )} - {ollamaTagsModels.map((model) => ( - - ))} - - ) : ( - - )} - + onChange={setSelectedTagsModel} + placeholder={selectedTagsModel || t('admin.ai.clickToLoadModels')} + searchPlaceholder={t('admin.ai.searchModel')} + emptyMessage={t('admin.ai.noModels')} + />

{isLoadingTagsModels ? t('admin.ai.fetchingModels') : t('admin.ai.selectOllamaModel')}

@@ -620,27 +614,21 @@ export function AdminSettingsForm({ config }: { config: Record }
- - + 0 + ? ollamaEmbeddingsModels.map((m) => ({ value: m, label: m })) + : selectedEmbeddingModel + ? [{ value: selectedEmbeddingModel, label: `${selectedEmbeddingModel} (${t('admin.ai.saved')})` }] + : [] + } value={selectedEmbeddingModel} - onChange={(e) => setSelectedEmbeddingModel(e.target.value)} - className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" - > - {ollamaEmbeddingsModels.length > 0 ? ( - <> - {!ollamaEmbeddingsModels.includes(selectedEmbeddingModel) && selectedEmbeddingModel && ( - - )} - {ollamaEmbeddingsModels.map((model) => ( - - ))} - - ) : ( - - )} - + onChange={setSelectedEmbeddingModel} + placeholder={selectedEmbeddingModel || t('admin.ai.clickToLoadModels')} + searchPlaceholder={t('admin.ai.searchModel')} + emptyMessage={t('admin.ai.noModels')} + />

{isLoadingEmbeddingsModels ? t('admin.ai.fetchingModels') : t('admin.ai.selectEmbeddingModel')}

@@ -790,27 +778,21 @@ export function AdminSettingsForm({ config }: { config: Record }
- - + 0 + ? ollamaChatModels.map((m) => ({ value: m, label: m })) + : selectedChatModel + ? [{ value: selectedChatModel, label: `${selectedChatModel} (${t('admin.ai.saved')})` }] + : [] + } value={selectedChatModel} - onChange={(e) => setSelectedChatModel(e.target.value)} - className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" - > - {ollamaChatModels.length > 0 ? ( - <> - {!ollamaChatModels.includes(selectedChatModel) && selectedChatModel && ( - - )} - {ollamaChatModels.map((model) => ( - - ))} - - ) : ( - - )} - + onChange={setSelectedChatModel} + placeholder={selectedChatModel || t('admin.ai.clickToLoadModels')} + searchPlaceholder={t('admin.ai.searchModel')} + emptyMessage={t('admin.ai.noModels')} + />

{isLoadingChatModels ? t('admin.ai.fetchingModels') : t('admin.ai.selectOllamaModel')}

diff --git a/memento-note/app/api/chat/route.ts b/memento-note/app/api/chat/route.ts index 9d46b7d..bc6afdc 100644 --- a/memento-note/app/api/chat/route.ts +++ b/memento-note/app/api/chat/route.ts @@ -99,40 +99,46 @@ export async function POST(req: Request) { // This ensures the AI always has access to the notebook content, // even for vague queries like "what's in this notebook?" let notebookContext = '' - if (notebookId) { - const notebookNotes = await prisma.note.findMany({ - where: { - notebookId, - userId, - trashedAt: null, - }, - orderBy: { updatedAt: 'desc' }, - take: 20, - select: { id: true, title: true, content: true, updatedAt: true }, - }) - if (notebookNotes.length > 0) { - notebookContext = notebookNotes - .map(n => `NOTE [${n.title || untitledText}] (updated ${n.updatedAt.toLocaleDateString()}):\n${(n.content || '').substring(0, 1500)}`) - .join('\n\n---\n\n') + let searchNotes = '' + + // When scope is "this note" (noteContext present), skip RAG retrieval entirely + // The note content is already injected as copilotContext below + if (!noteContext) { + if (notebookId) { + const notebookNotes = await prisma.note.findMany({ + where: { + notebookId, + userId, + trashedAt: null, + }, + orderBy: { updatedAt: 'desc' }, + take: 20, + select: { id: true, title: true, content: true, updatedAt: true }, + }) + if (notebookNotes.length > 0) { + notebookContext = notebookNotes + .map(n => `NOTE [${n.title || untitledText}] (updated ${n.updatedAt.toLocaleDateString()}):\n${(n.content || '').substring(0, 1500)}`) + .join('\n\n---\n\n') + } } - } - // Also run semantic search for the specific query - let searchResults: any[] = [] - try { - searchResults = await semanticSearchService.search(currentMessage, { - notebookId, - limit: notebookId ? 10 : 5, - threshold: notebookId ? 0.3 : 0.5, - defaultTitle: untitledText, - }) - } catch { - // Search failure should not block chat - } + // Also run semantic search for the specific query + let searchResults: any[] = [] + try { + searchResults = await semanticSearchService.search(currentMessage, { + notebookId, + limit: notebookId ? 10 : 5, + threshold: notebookId ? 0.3 : 0.5, + defaultTitle: untitledText, + }) + } catch { + // Search failure should not block chat + } - const searchNotes = searchResults - .map((r) => `NOTE [${r.title || untitledText}]: ${r.content}`) - .join('\n\n---\n\n') + searchNotes = searchResults + .map((r) => `NOTE [${r.title || untitledText}]: ${r.content}`) + .join('\n\n---\n\n') + } // Combine: full notebook context + semantic search results (deduplicated) const contextNotes = [notebookContext, searchNotes].filter(Boolean).join('\n\n---\n\n') @@ -354,7 +360,7 @@ ${noteContext.content || '(empty)'} ${imageContextParts.length > 0 ? `\nImages: ${imageContextParts.length} image(s) attached. When the user asks about images, describe what you see in them.` : ''} The user wants you to write in a **${noteContext.tone || 'professional'}** tone. -Keep your suggestions tailored to this note and tone. You can suggest rewrites, answer questions about the note, or draft new sections.` +IMPORTANT: Focus ONLY on this note. Do NOT reference other notes or external information unless the user explicitly asks. Your job is to help with this specific note — suggest rewrites, answer questions about it, or draft new sections.` } const systemPrompt = `${prompts.system}