UNPKG

@restnfeel/agentc-starter-kit

Version:

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

352 lines (349 loc) 28.6 kB
"use client"; import { jsxs, jsx } from 'react/jsx-runtime'; import { useState, useCallback, useEffect } from 'react'; import { Card, CardContent, CardHeader } from '../ui/Card.js'; import { CardTitle } from '../ui/CardTitle.js'; import { Button } from '../ui/Button.js'; import { Modal, ModalContent, ModalHeader, ModalTitle } from '../ui/Modal.js'; import { Input } from '../ui/Input.js'; function RAGManager({ ragEngine, onRetrainTriggered }) { const [metrics, setMetrics] = useState(null); const [retrievalTests, setRetrievalTests] = useState([]); const [knowledgeGaps, setKnowledgeGaps] = useState([]); const [isTestModalOpen, setIsTestModalOpen] = useState(false); const [isGapModalOpen, setIsGapModalOpen] = useState(false); const [isRetesting, setIsRetesting] = useState(false); const [isAnalyzing, setIsAnalyzing] = useState(false); // Test form states const [testQuery, setTestQuery] = useState(""); const [expectedAnswers, setExpectedAnswers] = useState([""]); const [testNotes, setTestNotes] = useState(""); // Gap form states const [gapCategory, setGapCategory] = useState(""); const [gapDescription, setGapDescription] = useState(""); const [gapPriority, setGapPriority] = useState("medium"); const [gapActions, setGapActions] = useState([""]); const [gapQueries, setGapQueries] = useState([""]); // Analytics data const [frequentQueries, setFrequentQueries] = useState([]); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const loadMetrics = useCallback(async () => { try { const stats = await ragEngine.getStats(); const searchStats = ragEngine.getSearchStats(); setMetrics({ totalDocuments: stats.documentCount, totalChunks: stats.chunkCount, avgChunkSize: stats.chunkCount > 0 ? 1000 : 0, // Placeholder embeddingDimensions: 1536, // OpenAI default vectorStoreSize: stats.chunkCount * 1536 * 4, // Approximate bytes lastUpdated: new Date().toISOString(), }); } catch (err) { console.error("메트릭 로드 실패:", err); setError("메트릭을 불러오는데 실패했습니다."); } }, [ragEngine]); const loadTestResults = useCallback(() => { try { const stored = localStorage.getItem("rag-retrieval-tests"); if (stored) { setRetrievalTests(JSON.parse(stored)); } } catch (err) { console.error("테스트 결과 로드 실패:", err); } }, []); const loadKnowledgeGaps = useCallback(() => { try { const stored = localStorage.getItem("rag-knowledge-gaps"); if (stored) { setKnowledgeGaps(JSON.parse(stored)); } } catch (err) { console.error("지식 격차 로드 실패:", err); } }, []); const loadAnalytics = useCallback(() => { try { const stored = localStorage.getItem("rag-query-analytics"); if (stored) { setFrequentQueries(JSON.parse(stored)); } else { // Mock data for demonstration setFrequentQueries([ { query: "AI 모델 훈련 방법", count: 45, avgScore: 0.89, lastUsed: new Date(Date.now() - 86400000).toISOString(), }, { query: "RAG 시스템 구현", count: 32, avgScore: 0.92, lastUsed: new Date(Date.now() - 3600000).toISOString(), }, { query: "벡터 데이터베이스 최적화", count: 28, avgScore: 0.85, lastUsed: new Date(Date.now() - 7200000).toISOString(), }, ]); } } catch (err) { console.error("분석 데이터 로드 실패:", err); } }, []); useEffect(() => { loadMetrics(); loadTestResults(); loadKnowledgeGaps(); loadAnalytics(); }, [loadMetrics, loadTestResults, loadKnowledgeGaps, loadAnalytics]); const clearMessages = () => { setError(null); setSuccess(null); }; const runRetrievalTest = async () => { if (!testQuery.trim()) { setError("테스트 쿼리를 입력하세요."); return; } setIsRetesting(true); clearMessages(); try { const results = await ragEngine.search(testQuery, 5); // Calculate accuracy based on expected answers const validExpectedAnswers = expectedAnswers.filter((answer) => answer.trim()); let accuracy = 0; if (validExpectedAnswers.length > 0) { const foundMatches = results.filter((result) => validExpectedAnswers.some((expected) => result.chunk.content .toLowerCase() .includes(expected.toLowerCase()) || expected .toLowerCase() .includes(result.chunk.content.toLowerCase().substring(0, 100)))); accuracy = foundMatches.length / validExpectedAnswers.length; } const newTest = { id: `test_${Date.now()}`, query: testQuery, expectedAnswers: validExpectedAnswers, results: results.map((r) => ({ content: r.chunk.content, score: r.score, metadata: r.chunk.metadata, })), accuracy, timestamp: new Date().toISOString(), notes: testNotes, }; const updatedTests = [newTest, ...retrievalTests]; setRetrievalTests(updatedTests); localStorage.setItem("rag-retrieval-tests", JSON.stringify(updatedTests)); setSuccess(`검색 테스트 완료. 정확도: ${(accuracy * 100).toFixed(1)}%`); setIsTestModalOpen(false); resetTestForm(); } catch (err) { setError(`테스트 실행 실패: ${err instanceof Error ? err.message : "알 수 없는 오류"}`); } finally { setIsRetesting(false); } }; const resetTestForm = () => { setTestQuery(""); setExpectedAnswers([""]); setTestNotes(""); }; const addKnowledgeGap = () => { if (!gapCategory.trim() || !gapDescription.trim()) { setError("카테고리와 설명을 입력하세요."); return; } const newGap = { id: `gap_${Date.now()}`, category: gapCategory, description: gapDescription, priority: gapPriority, suggestedActions: gapActions.filter((action) => action.trim()), relatedQueries: gapQueries.filter((query) => query.trim()), }; const updatedGaps = [newGap, ...knowledgeGaps]; setKnowledgeGaps(updatedGaps); localStorage.setItem("rag-knowledge-gaps", JSON.stringify(updatedGaps)); setSuccess("지식 격차가 추가되었습니다."); setIsGapModalOpen(false); resetGapForm(); }; const resetGapForm = () => { setGapCategory(""); setGapDescription(""); setGapPriority("medium"); setGapActions([""]); setGapQueries([""]); }; const deleteGap = (gapId) => { if (!confirm("이 지식 격차를 삭제하시겠습니까?")) return; const updatedGaps = knowledgeGaps.filter((gap) => gap.id !== gapId); setKnowledgeGaps(updatedGaps); localStorage.setItem("rag-knowledge-gaps", JSON.stringify(updatedGaps)); setSuccess("지식 격차가 삭제되었습니다."); }; const deleteTest = (testId) => { if (!confirm("이 테스트 결과를 삭제하시겠습니까?")) return; const updatedTests = retrievalTests.filter((test) => test.id !== testId); setRetrievalTests(updatedTests); localStorage.setItem("rag-retrieval-tests", JSON.stringify(updatedTests)); setSuccess("테스트 결과가 삭제되었습니다."); }; const triggerRetrain = async () => { if (!confirm("RAG 시스템을 재훈련하시겠습니까? 이 작업은 시간이 걸릴 수 있습니다.")) return; try { setIsAnalyzing(true); clearMessages(); // Trigger a refresh of embeddings await loadMetrics(); setSuccess("재훈련이 시작되었습니다. 백그라운드에서 처리됩니다."); onRetrainTriggered === null || onRetrainTriggered === void 0 ? void 0 : onRetrainTriggered(); } catch (err) { setError(`재훈련 실패: ${err instanceof Error ? err.message : "알 수 없는 오류"}`); } finally { setIsAnalyzing(false); } }; const analyzeKnowledgeGaps = async () => { setIsAnalyzing(true); clearMessages(); try { // Analyze low-performing queries and suggest gaps const lowAccuracyTests = retrievalTests.filter((test) => test.accuracy < 0.7); if (lowAccuracyTests.length > 0) { const categories = [ ...new Set(lowAccuracyTests.map((test) => { // Extract category from query (simple heuristic) const query = test.query.toLowerCase(); if (query.includes("기술") || query.includes("개발")) return "기술 문서"; if (query.includes("사용법") || query.includes("방법")) return "사용 가이드"; if (query.includes("문제") || query.includes("해결")) return "문제 해결"; return "일반"; })), ]; const autoGaps = categories.map((category) => ({ id: `auto_gap_${Date.now()}_${category}`, category, description: `${category} 관련 질문의 검색 정확도가 낮습니다.`, priority: "medium", suggestedActions: [ `${category} 관련 문서 추가`, "기존 문서의 태그 및 메타데이터 개선", "검색 키워드 최적화", ], relatedQueries: lowAccuracyTests .filter((test) => test.query.toLowerCase().includes(category.split(" ")[0])) .map((test) => test.query), })); const existingGaps = [...knowledgeGaps]; const newGaps = autoGaps.filter((gap) => !existingGaps.some((existing) => existing.category === gap.category)); if (newGaps.length > 0) { const updatedGaps = [...newGaps, ...existingGaps]; setKnowledgeGaps(updatedGaps); localStorage.setItem("rag-knowledge-gaps", JSON.stringify(updatedGaps)); setSuccess(`${newGaps.length}개의 지식 격차가 자동으로 식별되었습니다.`); } else { setSuccess("새로운 지식 격차가 발견되지 않았습니다."); } } else { setSuccess("현재 검색 성능이 양호합니다. 추가 개선이 필요하지 않습니다."); } } catch (err) { setError(`분석 실패: ${err instanceof Error ? err.message : "알 수 없는 오류"}`); } finally { setIsAnalyzing(false); } }; const formatBytes = (bytes) => { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; }; const formatDate = (dateString) => { return new Date(dateString).toLocaleString(); }; const getPriorityColor = (priority) => { switch (priority) { case "high": return "text-red-600 bg-red-50"; case "medium": return "text-yellow-600 bg-yellow-50"; case "low": return "text-green-600 bg-green-50"; default: return "text-gray-600 bg-gray-50"; } }; const getAccuracyColor = (accuracy) => { if (accuracy >= 0.8) return "text-green-600"; if (accuracy >= 0.6) return "text-yellow-600"; return "text-red-600"; }; return (jsxs("div", { className: "space-y-6", children: [jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: [jsx(Card, { children: jsxs(CardContent, { className: "p-4", children: [jsx("div", { className: "text-2xl font-bold", children: (metrics === null || metrics === void 0 ? void 0 : metrics.totalDocuments) || 0 }), jsx("p", { className: "text-sm text-gray-600", children: "\uCD1D \uBB38\uC11C \uC218" })] }) }), jsx(Card, { children: jsxs(CardContent, { className: "p-4", children: [jsx("div", { className: "text-2xl font-bold", children: (metrics === null || metrics === void 0 ? void 0 : metrics.totalChunks) || 0 }), jsx("p", { className: "text-sm text-gray-600", children: "\uCD1D \uCCAD\uD06C \uC218" })] }) }), jsx(Card, { children: jsxs(CardContent, { className: "p-4", children: [jsx("div", { className: "text-2xl font-bold", children: metrics ? formatBytes(metrics.vectorStoreSize) : "0 B" }), jsx("p", { className: "text-sm text-gray-600", children: "\uBCA1\uD130 \uC800\uC7A5\uC18C \uD06C\uAE30" })] }) }), jsx(Card, { children: jsxs(CardContent, { className: "p-4", children: [jsx("div", { className: "text-2xl font-bold", children: retrievalTests.length > 0 ? `${((retrievalTests.reduce((sum, test) => sum + test.accuracy, 0) / retrievalTests.length) * 100).toFixed(1)}%` : "N/A" }), jsx("p", { className: "text-sm text-gray-600", children: "\uD3C9\uADE0 \uAC80\uC0C9 \uC815\uD655\uB3C4" })] }) })] }), jsxs(Card, { children: [jsx(CardHeader, { children: jsx(CardTitle, { children: "RAG \uAD00\uB9AC \uC81C\uC5B4\uD310" }) }), jsx(CardContent, { children: jsxs("div", { className: "flex flex-wrap gap-3", children: [jsx(Button, { onClick: () => setIsTestModalOpen(true), children: "\uAC80\uC0C9 \uD488\uC9C8 \uD14C\uC2A4\uD2B8" }), jsx(Button, { variant: "outline", onClick: analyzeKnowledgeGaps, disabled: isAnalyzing, children: isAnalyzing ? "분석 중..." : "지식 격차 분석" }), jsx(Button, { variant: "outline", onClick: () => setIsGapModalOpen(true), children: "\uC9C0\uC2DD \uACA9\uCC28 \uCD94\uAC00" }), jsx(Button, { variant: "outline", onClick: triggerRetrain, disabled: isAnalyzing, children: isAnalyzing ? "재훈련 중..." : "재훈련 시작" }), jsx(Button, { variant: "outline", onClick: loadMetrics, children: "\uBA54\uD2B8\uB9AD \uC0C8\uB85C\uACE0\uCE68" })] }) })] }), jsxs(Card, { children: [jsx(CardHeader, { children: jsxs(CardTitle, { children: ["\uC9C0\uC2DD \uACA9\uCC28 (", knowledgeGaps.length, "\uAC1C)"] }) }), jsx(CardContent, { children: knowledgeGaps.length === 0 ? (jsx("p", { className: "text-gray-500 text-center py-8", children: "\uC9C0\uC2DD \uACA9\uCC28\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBD84\uC11D\uC744 \uC2E4\uD589\uD558\uAC70\uB098 \uC218\uB3D9\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694." })) : (jsx("div", { className: "space-y-4", children: knowledgeGaps.map((gap) => (jsx("div", { className: "border rounded-lg p-4", children: jsxs("div", { className: "flex items-start justify-between", children: [jsxs("div", { className: "flex-1", children: [jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsx("h4", { className: "font-medium", children: gap.category }), jsx("span", { className: `px-2 py-1 rounded text-xs ${getPriorityColor(gap.priority)}`, children: gap.priority })] }), jsx("p", { className: "text-gray-600 mb-3", children: gap.description }), gap.suggestedActions.length > 0 && (jsxs("div", { className: "mb-2", children: [jsx("strong", { className: "text-sm", children: "\uC81C\uC548 \uC870\uCE58:" }), jsx("ul", { className: "list-disc list-inside text-sm text-gray-600 ml-2", children: gap.suggestedActions.map((action, idx) => (jsx("li", { children: action }, idx))) })] })), gap.relatedQueries.length > 0 && (jsxs("div", { children: [jsx("strong", { className: "text-sm", children: "\uAD00\uB828 \uC9C8\uBB38:" }), jsxs("div", { className: "flex flex-wrap gap-1 mt-1", children: [gap.relatedQueries .slice(0, 3) .map((query, idx) => (jsx("span", { className: "text-xs bg-gray-100 px-2 py-1 rounded", children: query }, idx))), gap.relatedQueries.length > 3 && (jsxs("span", { className: "text-xs text-gray-500", children: ["+", gap.relatedQueries.length - 3, "\uAC1C \uB354"] }))] })] }))] }), jsx(Button, { variant: "outline", size: "sm", onClick: () => deleteGap(gap.id), children: "\uC0AD\uC81C" })] }) }, gap.id))) })) })] }), jsxs(Card, { children: [jsx(CardHeader, { children: jsxs(CardTitle, { children: ["\uAC80\uC0C9 \uD14C\uC2A4\uD2B8 \uACB0\uACFC (", retrievalTests.length, "\uAC1C)"] }) }), jsx(CardContent, { children: retrievalTests.length === 0 ? (jsx("p", { className: "text-gray-500 text-center py-8", children: "\uAC80\uC0C9 \uD14C\uC2A4\uD2B8 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC0C8 \uD14C\uC2A4\uD2B8\uB97C \uC2E4\uD589\uD558\uC138\uC694." })) : (jsxs("div", { className: "space-y-4", children: [retrievalTests.slice(0, 5).map((test) => (jsxs("div", { className: "border rounded-lg p-4", children: [jsxs("div", { className: "flex items-start justify-between mb-3", children: [jsxs("div", { className: "flex-1", children: [jsx("h4", { className: "font-medium mb-1", children: test.query }), jsxs("div", { className: "flex items-center gap-4 text-sm text-gray-600", children: [jsxs("span", { className: `font-medium ${getAccuracyColor(test.accuracy)}`, children: ["\uC815\uD655\uB3C4: ", (test.accuracy * 100).toFixed(1), "%"] }), jsxs("span", { children: ["\uACB0\uACFC: ", test.results.length, "\uAC1C"] }), jsx("span", { children: formatDate(test.timestamp) })] })] }), jsx(Button, { variant: "outline", size: "sm", onClick: () => deleteTest(test.id), children: "\uC0AD\uC81C" })] }), test.notes && (jsx("p", { className: "text-sm text-gray-600 mb-2", children: test.notes })), jsxs("div", { className: "text-sm", children: [jsx("strong", { children: "\uAC80\uC0C9 \uACB0\uACFC \uBBF8\uB9AC\uBCF4\uAE30:" }), jsx("div", { className: "mt-1 space-y-1", children: test.results.slice(0, 2).map((result, idx) => (jsxs("div", { className: "bg-gray-50 p-2 rounded text-xs", children: [jsx("div", { className: "flex justify-between mb-1", children: jsxs("span", { children: ["\uC810\uC218: ", result.score.toFixed(3)] }) }), jsx("p", { className: "line-clamp-2", children: result.content })] }, idx))) })] })] }, test.id))), retrievalTests.length > 5 && (jsxs("p", { className: "text-center text-gray-500", children: [retrievalTests.length - 5, "\uAC1C\uC758 \uCD94\uAC00 \uD14C\uC2A4\uD2B8 \uACB0\uACFC\uAC00 \uC788\uC2B5\uB2C8\uB2E4."] }))] })) })] }), jsxs(Card, { children: [jsx(CardHeader, { children: jsx(CardTitle, { children: "\uC790\uC8FC \uC0AC\uC6A9\uB418\uB294 \uC9C8\uBB38" }) }), jsx(CardContent, { children: frequentQueries.length === 0 ? (jsx("p", { className: "text-gray-500 text-center py-8", children: "\uBD84\uC11D\uD560 \uC9C8\uBB38 \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." })) : (jsx("div", { className: "space-y-3", children: frequentQueries.map((query, idx) => (jsxs("div", { className: "flex items-center justify-between p-3 bg-gray-50 rounded", children: [jsxs("div", { className: "flex-1", children: [jsx("h4", { className: "font-medium", children: query.query }), jsxs("div", { className: "text-sm text-gray-600", children: ["\uC0AC\uC6A9 \uD69F\uC218: ", query.count, "\uD68C | \uD3C9\uADE0 \uC810\uC218:", " ", query.avgScore.toFixed(2), " | \uB9C8\uC9C0\uB9C9 \uC0AC\uC6A9:", " ", formatDate(query.lastUsed)] })] }), jsx(Button, { variant: "outline", size: "sm", onClick: () => { setTestQuery(query.query); setIsTestModalOpen(true); }, children: "\uD14C\uC2A4\uD2B8" })] }, idx))) })) })] }), isTestModalOpen && (jsx(Modal, { open: isTestModalOpen, onOpenChange: setIsTestModalOpen, children: jsxs(ModalContent, { className: "max-w-2xl", children: [jsx(ModalHeader, { children: jsx(ModalTitle, { children: "\uAC80\uC0C9 \uD488\uC9C8 \uD14C\uC2A4\uD2B8" }) }), jsxs("div", { className: "p-6 space-y-4", children: [jsxs("div", { children: [jsx("label", { className: "block text-sm font-medium mb-1", children: "\uD14C\uC2A4\uD2B8 \uCFFC\uB9AC *" }), jsx(Input, { value: testQuery, onChange: (e) => setTestQuery(e.target.value), placeholder: "\uAC80\uC0C9\uD560 \uC9C8\uBB38\uC744 \uC785\uB825\uD558\uC138\uC694" })] }), jsxs("div", { children: [jsx("label", { className: "block text-sm font-medium mb-1", children: "\uC608\uC0C1 \uB2F5\uBCC0 (\uC120\uD0DD\uC0AC\uD56D)" }), expectedAnswers.map((answer, idx) => (jsxs("div", { className: "flex gap-2 mb-2", children: [jsx(Input, { value: answer, onChange: (e) => { const newAnswers = [...expectedAnswers]; newAnswers[idx] = e.target.value; setExpectedAnswers(newAnswers); }, placeholder: `예상 답변 ${idx + 1}` }), idx === expectedAnswers.length - 1 && (jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setExpectedAnswers([...expectedAnswers, ""]), children: "+" })), expectedAnswers.length > 1 && (jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => { const newAnswers = expectedAnswers.filter((_, i) => i !== idx); setExpectedAnswers(newAnswers); }, children: "-" }))] }, idx)))] }), jsxs("div", { children: [jsx("label", { className: "block text-sm font-medium mb-1", children: "\uB178\uD2B8 (\uC120\uD0DD\uC0AC\uD56D)" }), jsx("textarea", { value: testNotes, onChange: (e) => setTestNotes(e.target.value), placeholder: "\uD14C\uC2A4\uD2B8\uC5D0 \uB300\uD55C \uBA54\uBAA8", className: "w-full h-20 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" })] }), jsxs("div", { className: "flex gap-2", children: [jsx(Button, { onClick: runRetrievalTest, disabled: isRetesting, className: "flex-1", children: isRetesting ? "테스트 중..." : "테스트 실행" }), jsx(Button, { variant: "outline", onClick: () => setIsTestModalOpen(false), children: "\uCDE8\uC18C" })] })] })] }) })), isGapModalOpen && (jsx(Modal, { open: isGapModalOpen, onOpenChange: setIsGapModalOpen, children: jsxs(ModalContent, { className: "max-w-2xl", children: [jsx(ModalHeader, { children: jsx(ModalTitle, { children: "\uC9C0\uC2DD \uACA9\uCC28 \uCD94\uAC00" }) }), jsxs("div", { className: "p-6 space-y-4", children: [jsxs("div", { className: "grid grid-cols-2 gap-4", children: [jsxs("div", { children: [jsx("label", { className: "block text-sm font-medium mb-1", children: "\uCE74\uD14C\uACE0\uB9AC *" }), jsx(Input, { value: gapCategory, onChange: (e) => setGapCategory(e.target.value), placeholder: "\uC608: \uAE30\uC220 \uBB38\uC11C, \uC0AC\uC6A9 \uAC00\uC774\uB4DC" })] }), jsxs("div", { children: [jsx("label", { className: "block text-sm font-medium mb-1", children: "\uC6B0\uC120\uC21C\uC704" }), jsxs("select", { value: gapPriority, onChange: (e) => setGapPriority(e.target.value), className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", children: [jsx("option", { value: "high", children: "\uB192\uC74C" }), jsx("option", { value: "medium", children: "\uBCF4\uD1B5" }), jsx("option", { value: "low", children: "\uB0AE\uC74C" })] })] })] }), jsxs("div", { children: [jsx("label", { className: "block text-sm font-medium mb-1", children: "\uC124\uBA85 *" }), jsx("textarea", { value: gapDescription, onChange: (e) => setGapDescription(e.target.value), placeholder: "\uC9C0\uC2DD \uACA9\uCC28\uC5D0 \uB300\uD55C \uC124\uBA85", className: "w-full h-20 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" })] }), jsxs("div", { children: [jsx("label", { className: "block text-sm font-medium mb-1", children: "\uC81C\uC548 \uC870\uCE58" }), gapActions.map((action, idx) => (jsxs("div", { className: "flex gap-2 mb-2", children: [jsx(Input, { value: action, onChange: (e) => { const newActions = [...gapActions]; newActions[idx] = e.target.value; setGapActions(newActions); }, placeholder: `조치 ${idx + 1}` }), idx === gapActions.length - 1 && (jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setGapActions([...gapActions, ""]), children: "+" })), gapActions.length > 1 && (jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => { const newActions = gapActions.filter((_, i) => i !== idx); setGapActions(newActions); }, children: "-" }))] }, idx)))] }), jsxs("div", { children: [jsx("label", { className: "block text-sm font-medium mb-1", children: "\uAD00\uB828 \uC9C8\uBB38" }), gapQueries.map((query, idx) => (jsxs("div", { className: "flex gap-2 mb-2", children: [jsx(Input, { value: query, onChange: (e) => { const newQueries = [...gapQueries]; newQueries[idx] = e.target.value; setGapQueries(newQueries); }, placeholder: `관련 질문 ${idx + 1}` }), idx === gapQueries.length - 1 && (jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setGapQueries([...gapQueries, ""]), children: "+" })), gapQueries.length > 1 && (jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => { const newQueries = gapQueries.filter((_, i) => i !== idx); setGapQueries(newQueries); }, children: "-" }))] }, idx)))] }), jsxs("div", { className: "flex gap-2", children: [jsx(Button, { onClick: addKnowledgeGap, className: "flex-1", children: "\uCD94\uAC00" }), jsx(Button, { variant: "outline", onClick: () => setIsGapModalOpen(false), children: "\uCDE8\uC18C" })] })] })] }) })), error && (jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: jsx("p", { className: "text-red-800", children: error }) })), success && (jsx("div", { className: "p-4 bg-green-50 border border-green-200 rounded-lg", children: jsx("p", { className: "text-green-800", children: success }) }))] })); } export { RAGManager }; //# sourceMappingURL=rag-manager.js.map