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