"use client"; import React, { useState, useEffect } from "react"; import { RAGEngine } from "../../../../rag"; // 답변에서 링크를 버튼으로 렌더링하는 함수 function renderAnswerWithLinks(text: string) { if (!text) return text; // 네이버 블로그 링크 패턴 감지 const linkPattern = /(https?:\/\/blog\.naver\.com\/[^\s\n]+)/g; // 링크가 실제로 존재하는지 확인 (새로운 정규식 인스턴스 사용) if (!/(https?:\/\/blog\.naver\.com\/[^\s\n]+)/.test(text)) { return text; // 링크가 없으면 원본 텍스트 그대로 반환 } const parts = text.split(linkPattern); return parts.map((part, index) => { if (part.match(linkPattern)) { return ( 🔗 원문 보기 ); } return part; }); } // RAG 답변 생성 컴포넌트 function RAGAnswerGenerator() { const [query, setQuery] = useState(""); const [answer, setAnswer] = useState(""); const [sources, setSources] = useState([]); const [searchResults, setSearchResults] = useState([]); const [loading, setLoading] = useState(false); const [showDetails, setShowDetails] = useState(false); const handleSearch = async () => { if (!query.trim()) return; try { setLoading(true); setAnswer(""); setSources([]); setSearchResults([]); const response = await fetch("/api/rag/answer/stream", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, k: 5 }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body?.getReader(); if (!reader) { throw new Error("스트림을 읽을 수 없습니다."); } const decoder = new TextDecoder(); let buffer = ""; let currentAnswer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { if (line.trim()) { try { const data = JSON.parse(line); if (data.type === "sources") { setSources(data.content || []); } else if (data.type === "content") { currentAnswer += data.content; setAnswer(currentAnswer); } else if (data.type === "error") { setAnswer(`오류: ${data.content}`); break; } } catch (e) { // JSON 파싱 오류 무시 } } } } } catch (error) { console.error("RAG 답변 생성 중 오류:", error); setAnswer("네트워크 오류가 발생했습니다."); } finally { setLoading(false); } }; return ( {/* 검색 입력 */} setQuery(e.target.value)} placeholder="질문을 입력하세요... (예: 회사 소개, 제품 정보 등)" className="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent" onKeyPress={(e) => e.key === "Enter" && handleSearch()} /> {loading ? "답변 생성 중..." : "질문하기"} {/* AI 답변 */} {answer && ( 🤖 AI 답변 {sources.length > 0 && ( {sources.length}개 문서 참조 )} {renderAnswerWithLinks(answer)} {/* 참조 소스 - RSS가 아닌 문서만 표시 */} {sources.filter((source) => !source.isRSS).length > 0 && ( 📚 참조 문서: {sources .filter((source) => !source.isRSS) .map((source, index) => ( {source.index} {source.title} {(source.score * 100).toFixed(0)}% ))} )} {/* 상세 정보 토글 */} {searchResults.length > 0 && ( setShowDetails(!showDetails)} className="text-sm text-blue-600 hover:text-blue-800 underline" > {showDetails ? "상세 정보 숨기기" : "상세 정보 보기"} {showDetails && ( {searchResults.map((result, index) => ( 문서 #{index + 1} 유사도: {(result.score * 100).toFixed(1)}% {result.content} {result.metadata && ( 메타데이터: {JSON.stringify(result.metadata)} )} ))} )} )} )} ); } interface RAGManagerProps { ragEngine: RAGEngine; onRetrainTriggered?: () => void; } export function RAGManager({ ragEngine, onRetrainTriggered }: RAGManagerProps) { const [activeTab, setActiveTab] = useState("generator"); const tabs = [ { id: "generator", label: "AI 답변 생성", component: RAGAnswerGenerator }, ]; return ( {tabs.map((tab) => ( setActiveTab(tab.id)} className={`py-4 px-1 border-b-2 font-medium text-sm ${ activeTab === tab.id ? "border-blue-500 text-blue-600" : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" }`} > {tab.label} ))} {tabs.map((tab) => { if (tab.id === activeTab) { const Component = tab.component; return ; } return null; })} ); }
{result.content}