diff --git a/.playwright-mcp/console-2026-04-19T18-53-32-857Z.log b/.playwright-mcp/console-2026-04-19T18-53-32-857Z.log new file mode 100644 index 0000000..302db14 --- /dev/null +++ b/.playwright-mcp/console-2026-04-19T18-53-32-857Z.log @@ -0,0 +1,47 @@ +[ 242ms] [VERBOSE] [DOM] Input elements should have autocomplete attributes (suggested: "current-password"): (More info: https://goo.gl/9p2vKq) %o @ http://localhost:3000/login?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F:0 +[ 274ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 312ms] [LOG] [HMR] connected @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 343ms] [LOG] [Prisma] Models loaded: @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 426ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 437ms] [LOG] [Fast Refresh] done in 112ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 180853ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 180883ms] [LOG] [Fast Refresh] done in 131ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 205199ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 205245ms] [LOG] [Fast Refresh] done in 50ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 205472ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 205472ms] [LOG] [Fast Refresh] done in 104ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 218086ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 218512ms] [LOG] [Fast Refresh] done in 150ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 218619ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 218629ms] [LOG] [Fast Refresh] done in 111ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 226896ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 226973ms] [LOG] [Fast Refresh] done in 41ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 250168ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 250197ms] [LOG] [Fast Refresh] done in 44ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300156ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300662ms] [LOG] [Fast Refresh] done in 206ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300662ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300673ms] [LOG] [Fast Refresh] done in 9ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300673ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300673ms] [LOG] [Fast Refresh] done in 1ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300674ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300674ms] [LOG] [Fast Refresh] done in 0ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300775ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 300797ms] [LOG] [Fast Refresh] done in 123ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 330315ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 330346ms] [LOG] [Fast Refresh] done in 132ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 494083ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 494099ms] [LOG] [HMR] connected @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 494128ms] [LOG] [Prisma] Models loaded: @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 494533ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 494534ms] [LOG] [Fast Refresh] done in 312ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 583684ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 583685ms] [LOG] [Fast Refresh] done in 102ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 612080ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 612106ms] [LOG] [Fast Refresh] done in 50ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 620251ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 620280ms] [LOG] [Fast Refresh] done in 42ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 628266ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 628300ms] [LOG] [Fast Refresh] done in 48ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 628402ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 +[ 628423ms] [LOG] [Fast Refresh] done in 122ms @ http://localhost:3000/_next/static/chunks/0sqx_next_dist_0ksjl25._.js:2431 diff --git a/.playwright-mcp/page-2026-04-19T18-53-33-247Z.yml b/.playwright-mcp/page-2026-04-19T18-53-33-247Z.yml new file mode 100644 index 0000000..4c2ada9 --- /dev/null +++ b/.playwright-mcp/page-2026-04-19T18-53-33-247Z.yml @@ -0,0 +1,21 @@ +- generic [active] [ref=e1]: + - main [ref=e4]: + - generic [ref=e7]: + - heading "Sign in to your account" [level=1] [ref=e8] + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e11]: Email + - textbox "Email" [ref=e13]: + - /placeholder: Enter your email address + - generic [ref=e14]: + - generic [ref=e15]: Password + - textbox "Password" [ref=e17]: + - /placeholder: Enter your password + - link "Forgot password?" [ref=e19] [cursor=pointer]: + - /url: /forgot-password + - button "Sign In" [ref=e20] + - generic [ref=e22]: + - text: Don't have an account? + - link "Sign Up" [ref=e23] [cursor=pointer]: + - /url: /register + - region "Notifications alt+T" \ No newline at end of file diff --git a/keep-notes/app/(main)/agents/agents-page-client.tsx b/keep-notes/app/(main)/agents/agents-page-client.tsx index 37d1b4f..1f7fbbc 100644 --- a/keep-notes/app/(main)/agents/agents-page-client.tsx +++ b/keep-notes/app/(main)/agents/agents-page-client.tsx @@ -185,20 +185,20 @@ export function AgentsPageClient({ {/* Agents grid */} {agents.length > 0 && (
-

+

{t('agents.myAgents')}

{/* Search and filter */}
- + setSearchQuery(e.target.value)} placeholder={t('agents.searchPlaceholder')} - className="w-full pl-9 pr-3 py-2 text-sm bg-white border border-slate-200 rounded-lg outline-none focus:border-primary/40 focus:ring-2 focus:ring-primary/10 transition-all" + className="w-full pl-9 pr-3 py-2 text-sm bg-card border border-border rounded-lg outline-none focus:border-primary/40 focus:ring-2 focus:ring-primary/10 transition-all" />
@@ -209,7 +209,7 @@ export function AgentsPageClient({ className={`px-3 py-1.5 text-xs font-medium rounded-full transition-colors ${ typeFilter === opt.value ? 'bg-primary text-white' - : 'bg-slate-100 text-slate-600 hover:bg-slate-200' + : 'bg-muted text-muted-foreground hover:bg-accent' }`} > {t(opt.labelKey)} @@ -232,8 +232,8 @@ export function AgentsPageClient({
) : (
- -

{t('agents.noResults')}

+ +

{t('agents.noResults')}

)}
@@ -242,9 +242,9 @@ export function AgentsPageClient({ {/* Empty state */} {agents.length === 0 && (
- -

{t('agents.noAgents')}

-

+ +

{t('agents.noAgents')}

+

{t('agents.noAgentsDescription')}

diff --git a/keep-notes/app/(main)/agents/page.tsx b/keep-notes/app/(main)/agents/page.tsx index 7e87f89..489782c 100644 --- a/keep-notes/app/(main)/agents/page.tsx +++ b/keep-notes/app/(main)/agents/page.tsx @@ -19,7 +19,7 @@ export default async function AgentsPage() { ]) return ( -
+
-
) diff --git a/keep-notes/app/actions/notes.ts b/keep-notes/app/actions/notes.ts index 40b8b9b..8556e47 100644 --- a/keep-notes/app/actions/notes.ts +++ b/keep-notes/app/actions/notes.ts @@ -435,6 +435,7 @@ export async function createNote(data: { isMarkdown?: boolean size?: 'small' | 'medium' | 'large' autoGenerated?: boolean + aiProvider?: string notebookId?: string | undefined // Assign note to a notebook if provided skipRevalidation?: boolean // Option to prevent full page refresh for smooth optimistic UI updates }) { @@ -459,6 +460,7 @@ export async function createNote(data: { isMarkdown: data.isMarkdown || false, size: data.size || 'small', autoGenerated: data.autoGenerated || null, + aiProvider: data.aiProvider || null, notebookId: data.notebookId || null, } }) @@ -867,7 +869,7 @@ export async function togglePin(id: string, isPinned: boolean) { return updateNo export async function toggleArchive(id: string, isArchived: boolean) { return updateNote(id, { isArchived }) } export async function updateColor(id: string, color: string) { return updateNote(id, { color }) } export async function updateLabels(id: string, labels: string[]) { return updateNote(id, { labels }) } -export async function removeFusedBadge(id: string) { return updateNote(id, { autoGenerated: null }) } +export async function removeFusedBadge(id: string) { return updateNote(id, { autoGenerated: null, aiProvider: null }) } // Update note size WITHOUT revalidation - client uses optimistic updates export async function updateSize(id: string, size: 'small' | 'medium' | 'large') { @@ -941,8 +943,16 @@ export async function updateFullOrderWithoutRevalidation(ids: string[]) { if (!session?.user?.id) throw new Error('Unauthorized'); const userId = session.user.id; try { - const updates = ids.map((id: string, index: number) => - prisma.note.update({ where: { id, userId }, data: { order: index } }) + // Verify all notes belong to the user before updating + const notes = await prisma.note.findMany({ + where: { id: { in: ids }, userId }, + select: { id: true }, + }) + const ownedIds = new Set(notes.map(n => n.id)) + const validIds = ids.filter(id => ownedIds.has(id)) + + const updates = validIds.map((id: string, index: number) => + prisma.note.update({ where: { id }, data: { order: index } }) ) await prisma.$transaction(updates) return { success: true } diff --git a/keep-notes/app/api/chat/route.ts b/keep-notes/app/api/chat/route.ts index 8a58c7a..7bd50c9 100644 --- a/keep-notes/app/api/chat/route.ts +++ b/keep-notes/app/api/chat/route.ts @@ -5,6 +5,8 @@ import { semanticSearchService } from '@/lib/ai/services/semantic-search.service import { prisma } from '@/lib/prisma' import { auth } from '@/auth' import { loadTranslations, getTranslationValue, SupportedLanguage } from '@/lib/i18n' +import { toolRegistry } from '@/lib/ai/tools' +import { stepCountIs } from 'ai' export const maxDuration = 60 @@ -45,11 +47,12 @@ export async function POST(req: Request) { // 2. Parse request body — messages arrive as UIMessage[] from DefaultChatTransport const body = await req.json() - const { messages: rawMessages, conversationId, notebookId, language } = body as { + const { messages: rawMessages, conversationId, notebookId, language, webSearch } = body as { messages: UIMessage[] conversationId?: string notebookId?: string language?: string + webSearch?: boolean } // Convert UIMessages to CoreMessages for streamText @@ -143,7 +146,19 @@ export async function POST(req: Request) { - Natural tone, neither corporate nor too casual. - No unnecessary intro phrases ("Here's what I found", "Based on your notes"). Answer directly. - No upsell questions at the end ("Would you like me to...", "Do you want..."). If you have useful additional info, just give it. -- If the user says "Momento" they mean Memento (this app).`, +- If the user says "Momento" they mean Memento (this app). + +## Available tools +You have access to these tools for deeper research: +- **note_search**: Search the user's notes by keyword or meaning. Use when the initial context above is insufficient or when the user asks about specific content in their notes. If a notebook is selected, pass its ID to restrict results. +- **note_read**: Read a specific note by ID. Use when note_search returns a note you need the full content of. +- **web_search**: Search the web for information. Use when the user asks about something not in their notes. +- **web_scrape**: Scrape a web page and return its content as markdown. Use when web_search returns a URL you need to read. + +## Tool usage rules +- You already have context from the user's notes above. Only use tools if you need more specific or additional information. +- Never invent note IDs, URLs, or notebook IDs. Use the IDs provided in the context or from tool results. +- For simple conversational questions (greetings, opinions, general knowledge), answer directly without using any tools.`, }, fr: { contextWithNotes: `## Notes de l'utilisateur\n\n${contextNotes}\n\nQuand tu utilises une info venant des notes ci-dessus, cite le titre de la note source entre parenthèses, ex: "Le déploiement se fait via Docker (💻 Development Guide)". Ne recopie pas mot pour mot — reformule. Si les notes ne couvrent pas le sujet, dis-le et complète avec tes connaissances générales.`, @@ -159,7 +174,19 @@ export async function POST(req: Request) { - Ton naturel, ni corporate ni trop familier. - Pas de phrase d'intro inutile ("Voici ce que j'ai trouvé", "Basé sur vos notes"). Réponds directement. - Pas de question upsell à la fin ("Souhaitez-vous que je...", "Acceptez-vous que..."). Si tu as une info complémentaire utile, donne-la. -- Si l'utilisateur dit "Momento" il parle de Memento (cette application).`, +- Si l'utilisateur dit "Momento" il parle de Memento (cette application). + +## Outils disponibles +Tu as accès à ces outils pour des recherches approfondies : +- **note_search** : Cherche dans les notes de l'utilisateur par mot-clé ou sens. Utilise quand le contexte initial ci-dessus est insuffisant ou quand l'utilisateur demande du contenu spécifique dans ses notes. Si un carnet est sélectionné, passe son ID pour restreindre les résultats. +- **note_read** : Lit une note spécifique par son ID. Utilise quand note_search retourne une note dont tu as besoin du contenu complet. +- **web_search** : Recherche sur le web. Utilise quand l'utilisateur demande quelque chose qui n'est pas dans ses notes. +- **web_scrape** : Scrape une page web et retourne son contenu en markdown. Utilise quand web_search retourne une URL que tu veux lire. + +## Règles d'utilisation des outils +- Tu as déjà du contexte des notes de l'utilisateur ci-dessus. Utilise les outils seulement si tu as besoin d'informations plus spécifiques. +- N'invente jamais d'IDs de notes, d'URLs ou d'IDs de carnet. Utilise les IDs fournis dans le contexte ou les résultats d'outils. +- Pour les questions conversationnelles simples (salutations, opinions, connaissances générales), réponds directement sans utiliser d'outils.`, }, fa: { contextWithNotes: `## یادداشت‌های کاربر\n\n${contextNotes}\n\nهنگام استفاده از اطلاعات یادداشت‌های بالا، عنوان یادداشت منبع را در پرانتز ذکر کنید. کپی نکنید — بازنویسی کنید. اگر یادداشت‌ها موضوع را پوشش نمی‌دهند، بگویید و با دانش عمومی خود تکمیل کنید.`, @@ -175,7 +202,18 @@ export async function POST(req: Request) { - لحن طبیعی، نه رسمی بیش از حد و نه خیلی غیررسمی. - بدون جمله مقدمه اضافی. مستقیم پاسخ دهید. - بدون سؤال فروشی در انتها. اگر اطلاعات تکمیلی مفید دارید، مستقیم بدهید. -- اگر کاربر "Momento" می‌گوید، منظورش Memento (این برنامه) است.`, +- اگر کاربر "Momento" می‌گوید، منظورش Memento (این برنامه) است. + +## ابزارهای موجود +- **note_search**: جستجو در یادداشت‌های کاربر با کلیدواژه یا معنی. زمانی استفاده کنید که زمینه اولیه کافی نباشد. اگر دفترچه انتخاب شده، شناسه آن را ارسال کنید. +- **note_read**: خواندن یک یادداشت خاص با شناسه. زمانی استفاده کنید که note_search یادداشتی برگرداند که محتوای کامل آن را نیاز دارید. +- **web_search**: جستجو در وب. زمانی استفاده کنید که کاربر درباره چیزی خارج از یادداشت‌هایش می‌پرسد. +- **web_scrape**: استخراج محتوای صفحه وب. زمانی استفاده کنید که web_search نشانی‌ای برگرداند که می‌خواهید بخوانید. + +## قوانین استفاده از ابزارها +- شما از قبل زمینه‌ای از یادداشت‌های کاربر دارید. فقط در صورت نیاز به اطلاعات بیشتر از ابزارها استفاده کنید. +- هرگز شناسه یادداشت، نشانی یا شناسه دفترچه نسازید. از شناسه‌های موجود در زمینه یا نتایج ابزار استفاده کنید. +- برای سؤالات مکالمه‌ای ساده (سلام، نظرات، دانش عمومی)، مستقیم پاسخ دهید.`, }, es: { contextWithNotes: `## Notas del usuario\n\n${contextNotes}\n\nCuando uses información de las notas anteriores, cita el título de la nota fuente entre paréntesis. No copies palabra por palabra — reformula. Si las notas no cubren el tema, dilo y complementa con tu conocimiento general.`, @@ -190,7 +228,18 @@ export async function POST(req: Request) { ## Reglas de tono - Tono natural, ni corporativo ni demasiado informal. - Sin frases de introducción innecesarias. Responde directamente. -- Sin preguntas de venta al final. Si tienes información complementaria útil, dala directamente.`, +- Sin preguntas de venta al final. Si tienes información complementaria útil, dala directamente. + +## Herramientas disponibles +- **note_search**: Busca en las notas del usuario por palabra clave o significado. Úsalo cuando el contexto inicial sea insuficiente. Si hay una libreta seleccionada, pasa su ID para restringir los resultados. +- **note_read**: Lee una nota específica por su ID. Úsalo cuando note_search devuelva una nota cuyo contenido completo necesites. +- **web_search**: Busca en la web. Úsalo cuando el usuario pregunte sobre algo que no está en sus notas. +- **web_scrape**: Extrae el contenido de una página web como markdown. Úsalo cuando web_search devuelva una URL que quieras leer. + +## Reglas de uso de herramientas +- Ya tienes contexto de las notas del usuario arriba. Solo usa herramientas si necesitas información más específica. +- Nunca inventes IDs de notas, URLs o IDs de libreta. Usa los IDs proporcionados en el contexto o en los resultados de herramientas. +- Para preguntas conversacionales simples (saludos, opiniones, conocimiento general), responde directamente sin herramientas.`, }, de: { contextWithNotes: `## Notizen des Benutzers\n\n${contextNotes}\n\nWenn du Infos aus den obigen Notizen verwendest, zitiere den Titel der Quellnotiz in Klammern. Nicht Wort für Wort kopieren — umformulieren. Wenn die Notizen das Thema nicht abdecken, sag es und ergänze mit deinem Allgemeinwissen.`, @@ -205,7 +254,18 @@ export async function POST(req: Request) { ## Tonregeln - Natürlicher Ton, weder zu geschäftsmäßig noch zu umgangssprachlich. - Keine unnötigen Einleitungssätze. Antworte direkt. -- Keine Upsell-Fragen am Ende. Gib nützliche Zusatzinfos einfach direkt.`, +- Keine Upsell-Fragen am Ende. Gib nützliche Zusatzinfos einfach direkt. + +## Verfügbare Werkzeuge +- **note_search**: Durchsuche die Notizen des Benutzers nach Schlagwort oder Bedeutung. Verwende es, wenn der obige Kontext unzureichend ist. Wenn ein Notizbuch ausgewählt ist, gib dessen ID an, um die Ergebnisse einzuschränken. +- **note_read**: Lese eine bestimmte Notiz anhand ihrer ID. Verwende es, wenn note_search eine Notiz zurückgibt, deren vollständigen Inhalt du benötigst. +- **web_search**: Suche im Web. Verwende es, wenn der Benutzer nach etwas fragt, das nicht in seinen Notizen steht. +- **web_scrape**: Lese eine Webseite und gib den Inhalt als Markdown zurück. Verwende es, wenn web_search eine URL zurückgibt, die du lesen möchtest. + +## Werkzeugregeln +- Du hast bereits Kontext aus den Notizen des Benutzers oben. Verwende Werkzeuge nur, wenn du spezifischere Informationen benötigst. +- Erfinde niemals Notiz-IDs, URLs oder Notizbuch-IDs. Verwende die im Kontext oder in Werkzeugergebnissen bereitgestellten IDs. +- Bei einfachen Gesprächsfragen (Begrüßungen, Meinungen, Allgemeinwissen) antworte direkt ohne Werkzeuge.`, }, it: { contextWithNotes: `## Note dell'utente\n\n${contextNotes}\n\nQuando usi informazioni dalle note sopra, cita il titolo della nota fonte tra parentesi. Non copiare parola per parola — riformula. Se le note non coprono l'argomento, dillo e integra con la tua conoscenza generale.`, @@ -220,7 +280,18 @@ export async function POST(req: Request) { ## Regole di tono - Tono naturale, né aziendale né troppo informale. - Nessuna frase introduttiva non necessaria. Rispondi direttamente. -- Nessuna domanda di upsell alla fine. Se hai informazioni aggiuntive utili, dalle direttamente.`, +- Nessuna domanda di upsell alla fine. Se hai informazioni aggiuntive utili, dalle direttamente. + +## Strumenti disponibili +- **note_search**: Cerca nelle note dell'utente per parola chiave o significato. Usa quando il contesto iniziale è insufficiente. Se un quaderno è selezionato, passa il suo ID per restringere i risultati. +- **note_read**: Leggi una nota specifica per ID. Usa quando note_search restituisce una nota di cui hai bisogno del contenuto completo. +- **web_search**: Cerca sul web. Usa quando l'utente chiede qualcosa che non è nelle sue note. +- **web_scrape**: Estrai il contenuto di una pagina web come markdown. Usa quando web_search restituisce un URL che vuoi leggere. + +## Regole di utilizzo degli strumenti +- Hai già contesto dalle note dell'utente sopra. Usa gli strumenti solo se hai bisogno di informazioni più specifiche. +- Non inventare mai ID di note, URL o ID di quaderno. Usa gli ID forniti nel contesto o nei risultati degli strumenti. +- Per domande conversazionali semplici (saluti, opinioni, conoscenza generale), rispondi direttamente senza strumenti.`, }, } @@ -260,6 +331,16 @@ ${lang === 'en' ? 'Respond in the user\'s language.' : lang === 'fr' ? 'Réponds const provider = getChatProvider(config) const model = provider.getModel() + // 7b. Build chat tools + const chatToolContext = { + userId, + conversationId: conversation.id, + notebookId, + webSearch: !!webSearch, + config, + } + const chatTools = toolRegistry.buildToolsForChat(chatToolContext) + // 8. Save user message to DB before streaming if (isNewMessage && lastIncoming) { await prisma.chatMessage.create({ @@ -276,6 +357,8 @@ ${lang === 'en' ? 'Respond in the user\'s language.' : lang === 'fr' ? 'Réponds model, system: systemPrompt, messages: allMessages, + tools: chatTools, + stopWhen: stepCountIs(5), async onFinish({ text }) { // Save assistant message to DB after streaming completes await prisma.chatMessage.create({ diff --git a/keep-notes/components/agents/agent-card.tsx b/keep-notes/components/agents/agent-card.tsx index 01854e0..6a88c57 100644 --- a/keep-notes/components/agents/agent-card.tsx +++ b/keep-notes/components/agents/agent-card.tsx @@ -52,10 +52,10 @@ interface AgentCardProps { // --- Config --- const typeConfig: Record = { - scraper: { icon: Globe, color: 'text-blue-600', bgColor: 'bg-blue-50 border-blue-200' }, - researcher: { icon: Search, color: 'text-purple-600', bgColor: 'bg-purple-50 border-purple-200' }, - monitor: { icon: Eye, color: 'text-amber-600', bgColor: 'bg-amber-50 border-amber-200' }, - custom: { icon: Settings, color: 'text-green-600', bgColor: 'bg-green-50 border-green-200' }, + scraper: { icon: Globe, color: 'text-blue-600 dark:text-blue-400', bgColor: 'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800' }, + researcher: { icon: Search, color: 'text-purple-600 dark:text-purple-400', bgColor: 'bg-purple-50 dark:bg-purple-950 border-purple-200 dark:border-purple-800' }, + monitor: { icon: Eye, color: 'text-amber-600 dark:text-amber-400', bgColor: 'bg-amber-50 dark:bg-amber-950 border-amber-200 dark:border-amber-800' }, + custom: { icon: Settings, color: 'text-green-600 dark:text-green-400', bgColor: 'bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800' }, } const frequencyKeys: Record = { @@ -142,10 +142,10 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps return (
-
+
@@ -155,9 +155,9 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
-

{agent.name}

+

{agent.name}

{mounted && isNew && ( - + {t('agents.newBadge')} )} @@ -171,16 +171,16 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps {agent.isEnabled ? ( ) : ( - + )}
{agent.description && ( -

{agent.description}

+

{agent.description}

)} -
+
{t(frequencyKeys[agent.frequency] || 'agents.frequencies.manual')} @@ -199,9 +199,9 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps {lastAction && (
{lastAction.status === 'success' && } {lastAction.status === 'failure' && } @@ -224,7 +224,7 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps @@ -61,6 +61,16 @@ const TOOL_PRESETS: Record = { custom: ['memory_search'], } +// --- Shared class strings --- +const labelCls = 'block text-sm font-medium text-foreground mb-1.5' +const labelCls2 = 'block text-sm font-medium text-foreground mb-2' +const inputCls = 'w-full px-3 py-2 text-sm border border-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-card text-foreground' +const selectCls = 'w-full px-3 py-2 text-sm border border-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-card text-foreground' +const toggleOffBorder = 'border-border hover:border-primary/30' +const toggleOffIcon = 'text-muted-foreground' +const toggleOffLabel = 'text-sm font-medium text-foreground' +const toggleOffHint = 'text-xs text-muted-foreground' + // --- Component --- export function AgentForm({ agent, notebooks, onSave, onCancel }: AgentFormProps) { @@ -187,26 +197,26 @@ export function AgentForm({ agent, notebooks, onSave, onCancel }: AgentFormProps return (
-
+
{/* Header — editable agent name */} -
+
setName(e.target.value)} - className="text-lg font-semibold text-slate-800 bg-transparent border-none outline-none focus:ring-0 p-0 flex-1 placeholder:text-slate-300" + className="text-lg font-semibold text-card-foreground bg-transparent border-none outline-none focus:ring-0 p-0 flex-1 placeholder:text-muted-foreground/40" placeholder={t('agents.form.namePlaceholder')} required /> -
{/* Agent Type */}
- +
{agentTypes.map(at => ( ))}
@@ -230,12 +240,12 @@ export function AgentForm({ agent, notebooks, onSave, onCancel }: AgentFormProps {/* Research Topic (researcher only) — replaces Description for this type */} {type === 'researcher' && (
- + setDescription(e.target.value)} - className="w-full px-3 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary" + className={inputCls} placeholder={t('agents.form.researchTopicPlaceholder')} />
@@ -244,12 +254,12 @@ export function AgentForm({ agent, notebooks, onSave, onCancel }: AgentFormProps {/* Description (for non-researcher types) */} {type !== 'researcher' && (
- + setDescription(e.target.value)} - className="w-full px-3 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary" + className={inputCls} placeholder={t('agents.form.descriptionPlaceholder')} />
@@ -258,7 +268,7 @@ export function AgentForm({ agent, notebooks, onSave, onCancel }: AgentFormProps {/* URLs (scraper and custom only — researcher uses search, not URLs) */} {(type === 'scraper' || type === 'custom') && (
-