UNPKG

@restnfeel/agentc-starter-kit

Version:

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

159 lines (156 loc) 13.5 kB
"use client"; import { jsx, jsxs } from 'react/jsx-runtime'; import { useState, useEffect, useCallback } from 'react'; import { DocumentUploader } from './document-uploader.js'; import { KnowledgeEditor } from './knowledge-editor.js'; import { RAGManager } from './rag-manager.js'; import { useKeyboardShortcuts, createAdminShortcuts } from '../../hooks/useKeyboardShortcuts.js'; import { useConfirmationDialog, confirmAction, confirmDelete } from '../ui/confirmation-dialog.js'; import { useDebounce } from '../../hooks/usePerformance.js'; import { RAGEngine } from '../../rag/engine.js'; import { Card, CardHeader, CardContent } from '../ui/Card.js'; import { CardTitle } from '../ui/CardTitle.js'; import { Button } from '../ui/Button.js'; function KnowledgeManagement({ ragConfig }) { const [activeTab, setActiveTab] = useState("overview"); const [ragEngine, setRAGEngine] = useState(null); const [isInitializing, setIsInitializing] = useState(true); const [error, setError] = useState(null); const [stats, setStats] = useState({ totalDocuments: 0, totalKnowledgeEntries: 0, totalChunks: 0, lastActivity: null, }); // Confirmation dialog for destructive actions const { showConfirmation, ConfirmationDialog } = useConfirmationDialog(); // Debounced search for better performance const [searchTerm, setSearchTerm] = useState(""); useDebounce(searchTerm, 300); useEffect(() => { initializeRAGEngine(); }, [ragConfig]); const initializeRAGEngine = async () => { try { setIsInitializing(true); setError(null); // Use provided config or create default const config = ragConfig || { embeddingModel: "text-embedding-ada-002", chunkSize: 1000, chunkOverlap: 200, vectorStorePath: "./vector_store", llmConfig: { modelName: "gpt-4", temperature: 0.7, maxTokens: 2000, apiKey: process.env.OPENAI_API_KEY || "", }, supabaseConfig: { url: process.env.NEXT_PUBLIC_SUPABASE_URL || "", anonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "", bucket: "documents", }, }; const engine = new RAGEngine(config); await engine.initialize(); setRAGEngine(engine); await loadStats(engine); } catch (err) { console.error("RAG Engine 초기화 실패:", err); setError(`RAG 시스템 초기화에 실패했습니다: ${err instanceof Error ? err.message : "알 수 없는 오류"}`); } finally { setIsInitializing(false); } }; const loadStats = async (engine) => { try { const ragStats = await engine.getStats(); // Load knowledge entries from localStorage const knowledgeEntries = localStorage.getItem("knowledge-entries"); const knowledgeCount = knowledgeEntries ? JSON.parse(knowledgeEntries).length : 0; setStats({ totalDocuments: ragStats.documentCount, totalKnowledgeEntries: knowledgeCount, totalChunks: ragStats.chunkCount, lastActivity: new Date().toISOString(), }); } catch (err) { console.error("통계 로드 실패:", err); } }; const handleDocumentUploaded = async () => { if (ragEngine) { await loadStats(ragEngine); } }; const handleKnowledgeUpdated = async () => { if (ragEngine) { await loadStats(ragEngine); } }; const handleRetrain = async () => { if (!ragEngine) return; try { // Clear and reinitialize the vector store await ragEngine.clear(); await ragEngine.initialize(); await loadStats(ragEngine); } catch (err) { console.error("재훈련 실패:", err); setError(`재훈련에 실패했습니다: ${err instanceof Error ? err.message : "알 수 없는 오류"}`); } }; // Enhanced retrain with confirmation const handleRetrainWithConfirmation = useCallback(() => { confirmAction(showConfirmation, "시스템 재훈련", "RAG 시스템을 재훈련하면 모든 벡터 데이터가 다시 생성됩니다. 계속하시겠습니까?", handleRetrain, "재훈련"); }, [showConfirmation]); // Clear all data with confirmation useCallback(() => { confirmDelete(showConfirmation, "모든 데이터", async () => { if (ragEngine) { await ragEngine.clear(); await loadStats(ragEngine); } }); }, [showConfirmation, ragEngine]); // Keyboard shortcuts const shortcuts = createAdminShortcuts({ onRefresh: () => ragEngine && loadStats(ragEngine), onEscape: () => setActiveTab("overview"), onNew: () => setActiveTab("documents"), onSearch: () => { // Focus search input if available const searchInput = document.querySelector('input[type="search"]'); if (searchInput) searchInput.focus(); }, }); useKeyboardShortcuts(shortcuts); const tabs = [ { id: "overview", name: "개요", icon: "📊" }, { id: "documents", name: "문서 관리", icon: "📄" }, { id: "knowledge", name: "지식 편집", icon: "✏️" }, { id: "management", name: "RAG 관리", icon: "⚙️" }, ]; if (isInitializing) { return (jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: jsxs("div", { className: "text-center space-y-4", children: [jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto" }), jsx("p", { className: "text-gray-600", children: "RAG \uC2DC\uC2A4\uD15C\uC744 \uCD08\uAE30\uD654\uD558\uACE0 \uC788\uC2B5\uB2C8\uB2E4..." })] }) })); } if (error || !ragEngine) { return (jsx("div", { className: "space-y-4", children: jsxs(Card, { children: [jsx(CardHeader, { children: jsx(CardTitle, { className: "text-red-600", children: "\uCD08\uAE30\uD654 \uC624\uB958" }) }), jsxs(CardContent, { children: [jsx("p", { className: "text-red-800 mb-4", children: error || "RAG 엔진을 초기화할 수 없습니다." }), jsx(Button, { onClick: initializeRAGEngine, children: "\uB2E4\uC2DC \uC2DC\uB3C4" })] })] }) })); } return (jsxs("div", { className: "space-y-6", children: [jsxs(Card, { children: [jsx(CardHeader, { children: jsx(CardTitle, { className: "flex items-center gap-2", children: "\uD83E\uDDE0 \uC9C0\uC2DD \uBCA0\uC774\uC2A4 \uAD00\uB9AC \uC2DC\uC2A4\uD15C" }) }), jsxs(CardContent, { children: [jsx("p", { className: "text-gray-600 mb-4", children: "RAG \uAE30\uBC18 \uCC57\uBD07\uC744 \uC704\uD55C \uD1B5\uD569 \uC9C0\uC2DD \uBCA0\uC774\uC2A4 \uAD00\uB9AC \uC2DC\uC2A4\uD15C\uC785\uB2C8\uB2E4. \uBB38\uC11C \uC5C5\uB85C\uB4DC, \uC9C0\uC2DD \uD3B8\uC9D1, \uD488\uC9C8 \uBAA8\uB2C8\uD130\uB9C1\uC744 \uD55C \uACF3\uC5D0\uC11C \uAD00\uB9AC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4." }), jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [jsxs("div", { className: "bg-blue-50 p-3 rounded-lg", children: [jsx("div", { className: "text-2xl font-bold text-blue-600", children: stats.totalDocuments }), jsx("div", { className: "text-sm text-blue-800", children: "\uCD1D \uBB38\uC11C" })] }), jsxs("div", { className: "bg-green-50 p-3 rounded-lg", children: [jsx("div", { className: "text-2xl font-bold text-green-600", children: stats.totalKnowledgeEntries }), jsx("div", { className: "text-sm text-green-800", children: "\uC9C0\uC2DD \uD56D\uBAA9" })] }), jsxs("div", { className: "bg-purple-50 p-3 rounded-lg", children: [jsx("div", { className: "text-2xl font-bold text-purple-600", children: stats.totalChunks }), jsx("div", { className: "text-sm text-purple-800", children: "\uD14D\uC2A4\uD2B8 \uCCAD\uD06C" })] }), jsxs("div", { className: "bg-orange-50 p-3 rounded-lg", children: [jsx("div", { className: "text-2xl font-bold text-orange-600", children: stats.lastActivity ? "활성" : "비활성" }), jsx("div", { className: "text-sm text-orange-800", children: "\uC2DC\uC2A4\uD15C \uC0C1\uD0DC" })] })] })] })] }), jsx(Card, { children: jsx(CardContent, { className: "p-0", children: jsx("div", { className: "flex border-b", children: tabs.map((tab) => (jsxs("button", { onClick: () => setActiveTab(tab.id), className: `flex-1 flex items-center justify-center gap-2 py-4 px-6 border-b-2 transition-colors ${activeTab === tab.id ? "border-blue-500 text-blue-600 bg-blue-50" : "border-transparent text-gray-600 hover:text-gray-800 hover:bg-gray-50"}`, children: [jsx("span", { className: "text-lg", children: tab.icon }), jsx("span", { className: "font-medium", children: tab.name })] }, tab.id))) }) }) }), jsxs("div", { className: "min-h-[600px]", children: [activeTab === "overview" && (jsxs("div", { className: "space-y-6", children: [jsxs(Card, { children: [jsx(CardHeader, { children: jsx(CardTitle, { children: "\uC2DC\uC2A4\uD15C \uAC1C\uC694" }) }), jsx(CardContent, { children: jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [jsxs("div", { children: [jsx("h3", { className: "font-semibold mb-3", children: "\uC8FC\uC694 \uAE30\uB2A5" }), jsxs("ul", { className: "space-y-2 text-sm", children: [jsxs("li", { className: "flex items-center gap-2", children: [jsx("span", { className: "w-2 h-2 bg-blue-500 rounded-full" }), "\uBB38\uC11C \uC5C5\uB85C\uB4DC \uBC0F \uC790\uB3D9 \uCC98\uB9AC"] }), jsxs("li", { className: "flex items-center gap-2", children: [jsx("span", { className: "w-2 h-2 bg-green-500 rounded-full" }), "\uB9C8\uD06C\uB2E4\uC6B4 \uAE30\uBC18 \uC9C0\uC2DD \uD3B8\uC9D1"] }), jsxs("li", { className: "flex items-center gap-2", children: [jsx("span", { className: "w-2 h-2 bg-purple-500 rounded-full" }), "\uAC80\uC0C9 \uD488\uC9C8 \uBAA8\uB2C8\uD130\uB9C1"] }), jsxs("li", { className: "flex items-center gap-2", children: [jsx("span", { className: "w-2 h-2 bg-orange-500 rounded-full" }), "\uC790\uB3D9 \uC9C0\uC2DD \uACA9\uCC28 \uBD84\uC11D"] })] })] }), jsxs("div", { children: [jsx("h3", { className: "font-semibold mb-3", children: "\uC2DC\uC2A4\uD15C \uC0C1\uD0DC" }), jsxs("div", { className: "space-y-2 text-sm", children: [jsxs("div", { className: "flex justify-between", children: [jsx("span", { children: "\uBCA1\uD130 \uC800\uC7A5\uC18C:" }), jsx("span", { className: "text-green-600", children: "\uD65C\uC131" })] }), jsxs("div", { className: "flex justify-between", children: [jsx("span", { children: "\uC784\uBCA0\uB529 \uBAA8\uB378:" }), jsx("span", { children: "text-embedding-ada-002" })] }), jsxs("div", { className: "flex justify-between", children: [jsx("span", { children: "\uAC80\uC0C9 \uC5D4\uC9C4:" }), jsx("span", { className: "text-green-600", children: "\uD558\uC774\uBE0C\uB9AC\uB4DC" })] }), jsxs("div", { className: "flex justify-between", children: [jsx("span", { children: "\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:" }), jsx("span", { children: stats.lastActivity ? new Date(stats.lastActivity).toLocaleString() : "N/A" })] })] })] })] }) })] }), jsxs(Card, { children: [jsx(CardHeader, { children: jsx(CardTitle, { children: "\uBE60\uB978 \uC561\uC158" }) }), jsx(CardContent, { children: jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [jsxs(Button, { onClick: () => setActiveTab("documents"), className: "h-20 flex-col gap-2", children: [jsx("span", { className: "text-2xl", children: "\uD83D\uDCC4" }), jsx("span", { children: "\uBB38\uC11C \uC5C5\uB85C\uB4DC" })] }), jsxs(Button, { onClick: () => setActiveTab("knowledge"), variant: "outline", className: "h-20 flex-col gap-2", children: [jsx("span", { className: "text-2xl", children: "\u270F\uFE0F" }), jsx("span", { children: "\uC9C0\uC2DD \uCD94\uAC00" })] }), jsxs(Button, { onClick: () => setActiveTab("management"), variant: "outline", className: "h-20 flex-col gap-2", children: [jsx("span", { className: "text-2xl", children: "\uD83D\uDD0D" }), jsx("span", { children: "\uD488\uC9C8 \uD14C\uC2A4\uD2B8" })] }), jsxs(Button, { onClick: handleRetrainWithConfirmation, variant: "outline", className: "h-20 flex-col gap-2", children: [jsx("span", { className: "text-2xl", children: "\uD83D\uDD04" }), jsx("span", { children: "\uC7AC\uD6C8\uB828" })] })] }) })] })] })), activeTab === "documents" && (jsx(DocumentUploader, { ragEngine: ragEngine, onDocumentUploaded: handleDocumentUploaded, onDocumentDeleted: handleDocumentUploaded })), activeTab === "knowledge" && (jsx(KnowledgeEditor, { ragEngine: ragEngine, onEntryCreated: handleKnowledgeUpdated, onEntryUpdated: handleKnowledgeUpdated, onEntryDeleted: handleKnowledgeUpdated })), activeTab === "management" && (jsx(RAGManager, { ragEngine: ragEngine, onRetrainTriggered: handleRetrain }))] }), jsx(ConfirmationDialog, {})] })); } export { KnowledgeManagement }; //# sourceMappingURL=knowledge-management.js.map