"use client"; import React, { useState } from "react"; import { Button } from "../ui/Button"; import { Input } from "../ui/Input"; import { Card, CardContent, CardHeader } from "../ui/Card"; // 답변에서 링크를 버튼으로 렌더링하는 함수 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; }); } export interface RAGAnswerGeneratorProps { apiEndpoint?: string; placeholder?: string; maxResults?: number; } export function RAGAnswerGenerator({ apiEndpoint = "/api/rag/answer/stream", placeholder = "질문을 입력하세요... (예: 회사 소개, 제품 정보 등)", maxResults = 5, }: RAGAnswerGeneratorProps) { 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(apiEndpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, k: maxResults }), }); 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={placeholder} className="flex-1 h-10 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" onKeyDown={(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)} > {showDetails ? "상세 정보 숨기기" : "검색된 청크 보기"} {showDetails && ( 🔍 검색된 청크들: {searchResults.map((result, index) => ( 점수: {result.score?.toFixed(3)} | 출처:{" "} {result.document?.source} {result.chunk?.content || result.content} ))} )} )} )} {/* 로딩 상태 */} {loading && ( AI가 답변을 생성하고 있습니다... )} ); }
AI가 답변을 생성하고 있습니다...