import { useLocation } from '@docusaurus/router'; import Link from '@docusaurus/Link'; import Layout from '@theme/Layout'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { chatUrl } from '@docuservix-search/config'; import styles from './styles.module.css'; interface Source { file: string; heading: string; anchor: string; score: number; } interface Message { role: 'user' | 'assistant'; content: string; sources?: Source[]; } function stripNumericPrefixes(p: string): string { return p .split('/') .map((seg) => seg.replace(/^\d+-/, '')) .join('/'); } function sourceToUrl(file: string, anchor: string): string { let p = file.replace(/^docs\//, '').replace(/\.md$/, ''); p = stripNumericPrefixes(p); return `/docs/${p}${anchor ? `#${anchor}` : ''}`; } function sourceToPath(file: string): string { const p = file.replace(/^docs\//, '').replace(/\.md$/, ''); return stripNumericPrefixes(p); } function useQuery(): string { const location = useLocation(); const params = new URLSearchParams(location.search); return params.get('q') ?? ''; } export default function ChatPage(): JSX.Element { const urlQuery = useQuery(); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const messagesEndRef = useRef(null); const initialSentRef = useRef(false); const sendMessage = useCallback(async (content: string, history: Message[]) => { if (!content.trim()) return; const userMessage: Message = { role: 'user', content }; const newHistory = [...history, userMessage]; setMessages(newHistory); setInput(''); setLoading(true); setError(null); try { const res = await fetch(chatUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: newHistory }), }); if (!res.ok) { throw new Error(`HTTP ${res.status}`); } const data: { answer: string; sources?: Source[] } = await res.json(); setMessages((prev) => [ ...prev, { role: 'assistant', content: data.answer, sources: data.sources }, ]); } catch (err) { setError(err instanceof Error ? err.message : 'Ошибка при обращении к серверу'); } finally { setLoading(false); } }, []); useEffect(() => { if (urlQuery && !initialSentRef.current) { initialSentRef.current = true; sendMessage(urlQuery, []); } }, [urlQuery, sendMessage]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages, loading]); const handleSend = () => { if (!loading && input.trim()) { sendMessage(input, messages); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; return (
{urlQuery && (
← Назад к поиску
)}
{messages.length === 0 && !loading && (
Задайте вопрос...
)} {messages.map((msg, i) => (
{msg.content}
{msg.sources && msg.sources.length > 0 && (
Источники:
{msg.sources.map((src, j) => ( {src.heading || sourceToPath(src.file)} ))}
)}
))} {loading && (
)} {error && (
{error}
)}