UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

104 lines (101 loc) 7.97 kB
"use client"; import { jsxs, jsx } from 'react/jsx-runtime'; import { useState } from 'react'; import { Button } from '../ui/Button.js'; import { Card, CardHeader, CardContent } from '../ui/Card.js'; // 답변에서 링크를 버튼으로 렌더링하는 함수 function renderAnswerWithLinks(text) { 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 (jsxs("a", { href: part, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 mx-1 px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm hover:bg-green-200 transition-colors font-medium", title: "\uC6D0\uBB38 \uBCF4\uAE30", children: [jsx("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" }) }), "\uD83D\uDD17 \uC6D0\uBB38 \uBCF4\uAE30"] }, index)); } return part; }); } function RAGAnswerGenerator({ apiEndpoint = "/api/rag/answer/stream", placeholder = "질문을 입력하세요... (예: 회사 소개, 제품 정보 등)", maxResults = 5, }) { 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 () => { var _a; 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 = (_a = response.body) === null || _a === void 0 ? void 0 : _a.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 (jsxs("div", { className: "space-y-6", children: [jsxs("div", { className: "flex gap-2", children: [jsx("input", { value: query, onChange: (e) => 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() }), jsx(Button, { onClick: handleSearch, disabled: loading || !query.trim(), loading: loading, children: loading ? "답변 생성 중..." : "질문하기" })] }), answer && (jsxs(Card, { children: [jsx(CardHeader, { children: jsxs("div", { className: "flex items-center justify-between", children: [jsx("h3", { className: "text-lg font-semibold text-gray-900", children: "\uD83E\uDD16 AI \uB2F5\uBCC0" }), sources.length > 0 && (jsxs("span", { className: "text-sm text-gray-500", children: [sources.length, "\uAC1C \uBB38\uC11C \uCC38\uC870"] }))] }) }), jsxs(CardContent, { children: [jsx("div", { className: "prose max-w-none", children: jsx("div", { className: "text-gray-800 leading-relaxed whitespace-pre-wrap", children: renderAnswerWithLinks(answer) }) }), sources.filter((source) => !source.isRSS).length > 0 && (jsxs("div", { className: "mt-6 pt-4 border-t border-gray-200", children: [jsx("h4", { className: "text-sm font-medium text-gray-700 mb-3", children: "\uD83D\uDCDA \uCC38\uC870 \uBB38\uC11C:" }), jsx("div", { className: "space-y-2", children: sources .filter((source) => !source.isRSS) .map((source, index) => (jsxs("div", { className: "flex items-center gap-2 text-sm", children: [jsx("span", { className: "bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs font-medium", children: source.index }), jsx("div", { className: "flex-1", children: jsx("span", { className: "text-gray-700 truncate block max-w-md", children: source.title }) }), jsxs("span", { className: "text-gray-500 text-xs", children: [(source.score * 100).toFixed(0), "%"] })] }, index))) })] })), searchResults.length > 0 && (jsxs("div", { className: "mt-4", children: [jsx(Button, { variant: "ghost", size: "sm", onClick: () => setShowDetails(!showDetails), children: showDetails ? "상세 정보 숨기기" : "검색된 청크 보기" }), showDetails && (jsxs("div", { className: "mt-4 space-y-3", children: [jsx("h4", { className: "text-sm font-medium text-gray-700", children: "\uD83D\uDD0D \uAC80\uC0C9\uB41C \uCCAD\uD06C\uB4E4:" }), searchResults.map((result, index) => { var _a, _b, _c; return (jsxs("div", { className: "p-3 bg-gray-50 rounded border-l-4 border-blue-200", children: [jsxs("div", { className: "text-xs text-gray-600 mb-2", children: ["\uC810\uC218: ", (_a = result.score) === null || _a === void 0 ? void 0 : _a.toFixed(3), " | \uCD9C\uCC98:", " ", (_b = result.document) === null || _b === void 0 ? void 0 : _b.source] }), jsx("div", { className: "text-sm text-gray-800", children: ((_c = result.chunk) === null || _c === void 0 ? void 0 : _c.content) || result.content })] }, index)); })] }))] }))] })] })), loading && (jsxs("div", { className: "text-center py-8", children: [jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2" }), jsx("p", { className: "text-gray-600", children: "AI\uAC00 \uB2F5\uBCC0\uC744 \uC0DD\uC131\uD558\uACE0 \uC788\uC2B5\uB2C8\uB2E4..." })] }))] })); } export { RAGAnswerGenerator }; //# sourceMappingURL=rag-answer-generator.js.map