import React, { useEffect, useMemo, useState } from 'react' import { splitThinkBlocks } from './chatText' function cleanAssistantContent(content) { const raw = String(content || '') try { const { answer } = splitThinkBlocks(raw) return String(answer || raw).trim() } catch { return raw.trim() } } function defaultTitle(message) { const prefix = message?.role === 'user' ? 'User note' : 'Saved answer' const content = message?.role === 'assistant' ? cleanAssistantContent(message?.content) : String(message?.content || '').trim() const compact = content.replace(/\s+/g, ' ') if (!compact) return prefix const excerpt = compact.length > 90 ? `${compact.slice(0, 87).trim()}...` : compact return `${prefix}: ${excerpt}` } function defaultKnowledgeContent(message, precedingUserMessage) { const content = message?.role === 'assistant' ? cleanAssistantContent(message?.content) : String(message?.content || '').trim() if (message?.role !== 'assistant' || !precedingUserMessage?.content?.trim()) { return content } return `User question:\n${precedingUserMessage.content.trim()}\n\nAssistant response:\n${content}` } async function expectJson(response) { const raw = await response.text() let data = null try { data = raw ? JSON.parse(raw) : null } catch {} if (!response.ok) { throw new Error(data?.detail || raw || `HTTP ${response.status}`) } return data } export default function SaveMessageToKnowledgeDialog({ apiBase, defaultLibrarySlug, libraries, message, onClose, onSaved, precedingUserMessage, }) { const initialLibrarySlug = useMemo(() => { if (libraries.some(library => library.slug === defaultLibrarySlug)) return defaultLibrarySlug return libraries[0]?.slug || '' }, [defaultLibrarySlug, libraries]) const [librarySlug, setLibrarySlug] = useState(initialLibrarySlug) const [title, setTitle] = useState(() => defaultTitle(message)) const [content, setContent] = useState(() => defaultKnowledgeContent(message, precedingUserMessage)) const [busy, setBusy] = useState(false) const [error, setError] = useState('') useEffect(() => { const onKeyDown = event => { if (event.key === 'Escape' && !busy) onClose() } document.addEventListener('keydown', onKeyDown) return () => document.removeEventListener('keydown', onKeyDown) }, [busy, onClose]) async function submit(event) { event.preventDefault() if (!librarySlug || !message?.message_id) return setBusy(true) setError('') try { const response = await fetch(`${apiBase}/libraries/${librarySlug}/chat-messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message_id: message.message_id, title, content, }), }) const data = await expectJson(response) await onSaved(data) } catch (submitError) { setError(String(submitError?.message || submitError)) } finally { setBusy(false) } } return (
{ if (event.target === event.currentTarget && !busy) onClose() }}>
Save message to knowledge

The saved snapshot becomes a normal RAG item in the selected database.

{libraries.length === 0 ? (
Create a database before saving chat knowledge.
) : ( <>