@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
352 lines (349 loc) • 28.6 kB
JavaScript
"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