diff --git a/app/(routes)/coming-soon/guardrails/page.tsx b/app/(main)/coming-soon/guardrails/page.tsx similarity index 100% rename from app/(routes)/coming-soon/guardrails/page.tsx rename to app/(main)/coming-soon/guardrails/page.tsx diff --git a/app/(routes)/coming-soon/model-testing/page.tsx b/app/(main)/coming-soon/model-testing/page.tsx similarity index 100% rename from app/(routes)/coming-soon/model-testing/page.tsx rename to app/(main)/coming-soon/model-testing/page.tsx diff --git a/app/(routes)/coming-soon/redteaming/page.tsx b/app/(main)/coming-soon/redteaming/page.tsx similarity index 100% rename from app/(routes)/coming-soon/redteaming/page.tsx rename to app/(main)/coming-soon/redteaming/page.tsx diff --git a/app/(routes)/coming-soon/text-to-speech/page.tsx b/app/(main)/coming-soon/text-to-speech/page.tsx similarity index 100% rename from app/(routes)/coming-soon/text-to-speech/page.tsx rename to app/(main)/coming-soon/text-to-speech/page.tsx diff --git a/app/(main)/configurations/page.tsx b/app/(main)/configurations/page.tsx new file mode 100644 index 0000000..09a93f3 --- /dev/null +++ b/app/(main)/configurations/page.tsx @@ -0,0 +1,385 @@ +/** + * Config Library: View and manage configs with quick actions (edit/use), + * showing usage count, and lazily loading version details on selection. + */ + +"use client"; + +import { useState, useEffect, useCallback, useMemo } from "react"; +import { useRouter } from "next/navigation"; +import Sidebar from "@/app/components/Sidebar"; +import PageHeader from "@/app/components/PageHeader"; +import { colors } from "@/app/lib/colors"; +import { usePaginatedList } from "@/app/hooks/usePaginatedList"; +import { useInfiniteScroll } from "@/app/hooks/useInfiniteScroll"; +import ConfigCard from "@/app/components/ConfigCard"; +import Loader, { LoaderBox } from "@/app/components/Loader"; +import { EvalJob } from "@/app/components/types"; +import { + ConfigPublic, + ConfigVersionItems, + ConfigVersionResponse, + SavedConfig, +} from "@/app/lib/types/configs"; +import { + configState, + pendingVersionLoads, + pendingSingleVersionLoads, +} from "@/app/lib/store/configStore"; +import { flattenConfigVersion } from "@/app/lib/utils"; +import { + SearchIcon, + RefreshIcon, + PlusIcon, + WarningTriangleIcon, + GearIcon, +} from "@/app/components/icons"; +import { useAuth } from "@/app/lib/context/AuthContext"; +import { useApp } from "@/app/lib/context/AppContext"; +import { apiFetch } from "@/app/lib/apiClient"; + +const SEARCH_DEBOUNCE_MS = 350; + +export default function ConfigLibraryPage() { + const router = useRouter(); + const [evaluationCounts, setEvaluationCounts] = useState< + Record + >({}); + const { sidebarCollapsed } = useApp(); + const { activeKey } = useAuth(); + const apiKey = activeKey?.key; + const [searchInput, setSearchInput] = useState(""); + const [debouncedQuery, setDebouncedQuery] = useState(""); + const [columnCount, setColumnCount] = useState(3); + const { + items: configs, + isLoading, + isLoadingMore, + hasMore, + error, + loadMore, + refetch, + } = usePaginatedList({ + endpoint: "/api/configs", + query: debouncedQuery, + }); + const scrollRef = useInfiniteScroll({ + onLoadMore: loadMore, + hasMore, + isLoading: isLoading || isLoadingMore, + }); + + // Responsive column count (matches Tailwind lg/xl breakpoints) + useEffect(() => { + const update = () => { + if (window.innerWidth >= 1280) setColumnCount(3); + else if (window.innerWidth >= 1024) setColumnCount(2); + else setColumnCount(1); + }; + update(); + window.addEventListener("resize", update); + return () => window.removeEventListener("resize", update); + }, []); + + // Distribute configs into fixed columns so items never shift between columns + const columns = useMemo(() => { + const cols: ConfigPublic[][] = Array.from( + { length: columnCount }, + () => [], + ); + configs.forEach((config, i) => cols[i % columnCount].push(config)); + return cols; + }, [configs, columnCount]); + + useEffect(() => { + const timer = setTimeout( + () => setDebouncedQuery(searchInput.trim()), + SEARCH_DEBOUNCE_MS, + ); + return () => clearTimeout(timer); + }, [searchInput]); + + useEffect(() => { + const fetchEvaluationCounts = async () => { + if (!activeKey) return; + try { + const data = await apiFetch( + "/api/evaluations", + activeKey.key, + ); + const jobs: EvalJob[] = Array.isArray(data) ? data : data.data || []; + const counts: Record = {}; + jobs.forEach((job) => { + if (job.config_id) { + counts[job.config_id] = (counts[job.config_id] || 0) + 1; + } + }); + setEvaluationCounts(counts); + } catch (e) { + console.error("Failed to fetch evaluation counts:", e); + } + }; + fetchEvaluationCounts(); + }, [activeKey]); + + const loadVersionsForConfig = useCallback( + async (configId: string) => { + if (configState.versionItemsCache[configId]) return; + const existing = pendingVersionLoads.get(configId); + if (existing) { + await existing; + return; + } + if (!apiKey) return; + + const loadPromise = (async () => { + const res = await apiFetch<{ + success: boolean; + data: ConfigVersionItems[]; + }>(`/api/configs/${configId}/versions`, apiKey); + if (res.success && res.data) { + configState.versionItemsCache[configId] = res.data; + } + })().finally(() => pendingVersionLoads.delete(configId)); + + pendingVersionLoads.set(configId, loadPromise); + await loadPromise; + }, + [apiKey], + ); + + const loadSingleVersion = useCallback( + async (configId: string, version: number): Promise => { + const key = `${configId}:${version}`; + const existing = pendingSingleVersionLoads.get(key); + if (existing) return existing; + if (!apiKey) return null; + + const configPublic = + configs.find((c) => c.id === configId) ?? + configState.allConfigMeta?.find((m) => m.id === configId); + if (!configPublic) return null; + + const loadPromise: Promise = (async () => { + try { + const res = await apiFetch( + `/api/configs/${configId}/versions/${version}`, + apiKey, + ); + if (!res.success || !res.data) return null; + return flattenConfigVersion(configPublic, res.data); + } catch (e) { + console.error( + `Failed to fetch version ${version} for config ${configId}:`, + e, + ); + return null; + } + })().finally(() => pendingSingleVersionLoads.delete(key)); + + pendingSingleVersionLoads.set(key, loadPromise); + return loadPromise; + }, + [apiKey, configs], + ); + + const handleCreateNew = () => { + router.push("/configurations/prompt-editor?new=true"); + }; + + return ( +
+
+ + +
+ + + {/* Toolbar */} +
+
+ + setSearchInput(e.target.value)} + placeholder="Search configs..." + className="w-full pl-10 pr-4 py-2 rounded-md text-sm focus:outline-none transition-colors" + style={{ + backgroundColor: colors.bg.secondary, + border: `1px solid ${colors.border}`, + color: colors.text.primary, + }} + /> +
+ + + + +
+ +
+ {isLoading ? ( + + ) : error ? ( +
+ +

{error}

+ +
+ ) : configs.length === 0 ? ( +
+ {debouncedQuery ? ( + <> + +

+ No configs match "{debouncedQuery}" +

+ + + ) : ( + <> + +

+ No configurations yet +

+

+ Create your first configuration to get started +

+ + + )} +
+ ) : ( + <> +
+ {columns.map((col, colIdx) => ( +
+ {col.map((config) => ( + + ))} +
+ ))} +
+ + {isLoadingMore && ( +
+ +
+ )} + + )} +
+
+
+
+ ); +} diff --git a/app/(routes)/configurations/prompt-editor/page.tsx b/app/(main)/configurations/prompt-editor/page.tsx similarity index 84% rename from app/(routes)/configurations/prompt-editor/page.tsx rename to app/(main)/configurations/prompt-editor/page.tsx index 250c42f..897c0ec 100644 --- a/app/(routes)/configurations/prompt-editor/page.tsx +++ b/app/(main)/configurations/prompt-editor/page.tsx @@ -1,10 +1,5 @@ /** - * Prompt Editor - Version Controlled Prompt + Config Editor - * - * A WYSIWYG editor for managing prompts and configs with linear versioning. - * Features: save, load, compare configs with backend persistence. - * Uses shared useConfigs hook for caching. - * Supports URL query params for cross-navigation from Config Library/Evaluations. + * Prompt WYSIWYG Editor: Manage prompts and configs with versioning, caching, and URL-based navigation support. */ "use client"; @@ -13,9 +8,8 @@ import React, { useState, useEffect, Suspense } from "react"; import { useSearchParams } from "next/navigation"; import Sidebar from "@/app/components/Sidebar"; import { colors } from "@/app/lib/colors"; -import { ConfigBlob, Tool } from "./types"; -import { hasConfigChanges } from "./utils"; -import { ConfigCreate, ConfigVersionCreate } from "@/app/lib/configTypes"; +import { ConfigBlob, Tool } from "@/app/lib/types/promptEditor"; +import { hasConfigChanges } from "@/app/lib/promptEditorUtils"; import Header from "@/app/components/prompt-editor/Header"; import HistorySidebar from "@/app/components/prompt-editor/HistorySidebar"; import PromptEditorPane from "@/app/components/prompt-editor/PromptEditorPane"; @@ -26,43 +20,42 @@ import Loader from "@/app/components/Loader"; import { useApp } from "@/app/lib/context/AppContext"; import { useAuth } from "@/app/lib/context/AuthContext"; import { useConfigs } from "@/app/hooks/useConfigs"; -import { SavedConfig } from "@/app/lib/types/configs"; +import { + SavedConfig, + ConfigCreate, + ConfigVersionCreate, + ConfigVersionItems, +} from "@/app/lib/types/configs"; import { invalidateConfigCache } from "@/app/lib/utils"; import { configState } from "@/app/lib/store/configStore"; import { apiFetch } from "@/app/lib/apiClient"; +const DEFAULT_CONFIG: ConfigBlob = { + completion: { + provider: "openai", + type: "text", + params: { + model: "gpt-4o-mini", + instructions: "", + temperature: 0.7, + tools: [], + }, + }, +}; + function PromptEditorContent() { const toast = useToast(); const searchParams = useSearchParams(); - const { sidebarCollapsed, setSidebarCollapsed } = useApp(); + const { sidebarCollapsed } = useApp(); const { activeKey } = useAuth(); - - // URL query params for cross-navigation const urlConfigId = searchParams.get("config"); const urlVersion = searchParams.get("version"); const showHistory = searchParams.get("history") === "true"; const isNewConfig = searchParams.get("new") === "true"; - - // Evaluation context to preserve (when coming from evaluations page) const urlDatasetId = searchParams.get("dataset"); const urlExperimentName = searchParams.get("experiment"); const fromEvaluations = searchParams.get("from") === "evaluations"; - // Default config for new versions - const defaultConfig: ConfigBlob = { - completion: { - provider: "openai", - type: "text", - params: { - model: "gpt-4o-mini", - instructions: "", - temperature: 0.7, - tools: [], - }, - }, - }; - - // Use shared configs hook with caching — pageSize:0 means only 1 API call on mount const { configs: savedConfigs, isLoading, @@ -76,29 +69,18 @@ function PromptEditorContent() { const initialLoadComplete = !isLoading; const editorInitialized = React.useRef(false); const [editorReady, setEditorReady] = useState(!urlConfigId); - const [stableVersionItemsMap, setStableVersionItemsMap] = useState< - Record + Record >({}); - - useEffect(() => { - if (Object.keys(hookVersionItemsMap).length > 0) { - setStableVersionItemsMap((prev) => ({ ...prev, ...hookVersionItemsMap })); - } - }, [hookVersionItemsMap]); - - const versionItemsMap = stableVersionItemsMap; - - // Current working state const [currentContent, setCurrentContent] = useState( "You are a helpful AI assistant.\nYou provide clear and concise answers.\nYou are polite and professional.", ); const [currentConfigBlob, setCurrentConfigBlob] = - useState(defaultConfig); + useState(DEFAULT_CONFIG); const [currentConfigName, setCurrentConfigName] = useState(""); const [selectedConfigId, setSelectedConfigId] = useState(""); // Selected version ID const [currentConfigParentId, setCurrentConfigParentId] = - useState(""); // Parent config ID for evaluation + useState(""); const [currentConfigVersion, setCurrentConfigVersion] = useState(0); // Version number for evaluation const [provider, setProvider] = useState("openai"); const [temperature, setTemperature] = useState(0.7); @@ -106,22 +88,23 @@ function PromptEditorContent() { const [expandedConfigs, setExpandedConfigs] = useState>( new Set(), ); - - // UI state const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [commitMessage, setCommitMessage] = useState(""); - const [showHistorySidebar, setShowHistorySidebar] = useState(true); // Default open, or from URL param - const [showConfigPane, setShowConfigPane] = useState(true); // Config pane collapse state - - // History viewing state + const [showHistorySidebar, setShowHistorySidebar] = useState(true); + const [showConfigPane, setShowConfigPane] = useState(true); const [selectedVersion, setSelectedVersion] = useState( null, ); const [compareWith, setCompareWith] = useState(null); - const getApiKey = (): string | null => activeKey?.key ?? null; + useEffect(() => { + if (Object.keys(hookVersionItemsMap).length > 0) { + setStableVersionItemsMap((prev) => ({ ...prev, ...hookVersionItemsMap })); + } + }, [hookVersionItemsMap]); + + const versionItemsMap = stableVersionItemsMap; - // Populate the editor from a fully-loaded SavedConfig const applyConfig = React.useCallback( (config: SavedConfig, selectInHistory?: boolean) => { setCurrentContent(config.promptContent); @@ -155,13 +138,12 @@ function PromptEditorContent() { [], ); - // Load a config directly from a SavedConfig object (no savedConfigs lookup needed) const handleLoadConfig = React.useCallback( (config: SavedConfig | null) => { if (!config) { // Reset to new config setCurrentContent(""); - setCurrentConfigBlob(defaultConfig); + setCurrentConfigBlob(DEFAULT_CONFIG); setProvider("openai"); setTemperature(0.7); setSelectedConfigId(""); @@ -171,11 +153,10 @@ function PromptEditorContent() { setTools([]); return; } - // Load the lightweight version list for the history sidebar (1 call or no-op if cached) loadVersionsForConfig(config.config_id); applyConfig(config); }, - [applyConfig, loadVersionsForConfig], + [applyConfig, loadVersionsForConfig, DEFAULT_CONFIG], ); // Initialize editor from URL params — runs once, on first load completion @@ -187,7 +168,7 @@ function PromptEditorContent() { // If new config is requested, reset to defaults if (isNewConfig) { setCurrentContent(""); - setCurrentConfigBlob(defaultConfig); + setCurrentConfigBlob(DEFAULT_CONFIG); setProvider("openai"); setTemperature(0.7); setSelectedConfigId(""); @@ -205,7 +186,6 @@ function PromptEditorContent() { } (async () => { - // Load version list for history sidebar (1 call, cached on subsequent runs) await loadVersionsForConfig(urlConfigId); const items = configState.versionItemsCache[urlConfigId] ?? []; @@ -214,7 +194,6 @@ function PromptEditorContent() { return; } - // Resolve the target version number (latest if no specific version requested) const versionNum = urlVersion ? parseInt(urlVersion) : items.reduce((a, b) => (b.version > a.version ? b : a)).version; @@ -232,6 +211,7 @@ function PromptEditorContent() { loadVersionsForConfig, loadSingleVersion, applyConfig, + DEFAULT_CONFIG, ]); // Re-populate version items when missing (e.g. after background cache revalidation wipes versionItemsCache) @@ -241,10 +221,8 @@ function PromptEditorContent() { } }, [currentConfigParentId, versionItemsMap, loadVersionsForConfig]); - // Detect unsaved changes useEffect(() => { if (!selectedConfigId) { - // New config - always has unsaved changes until saved setHasUnsavedChanges(true); return; } @@ -281,14 +259,13 @@ function PromptEditorContent() { savedConfigs, ]); - // Save current configuration const handleSaveConfig = async () => { if (!currentConfigName.trim()) { toast.error("Please enter a configuration name"); return; } - const apiKey = getApiKey(); + const apiKey = activeKey?.key; if (!apiKey) { toast.error("No API key found. Please add an API key in the Keystore."); return; @@ -297,16 +274,12 @@ function PromptEditorContent() { setIsSaving(true); try { - // Build config blob (store prompt in instructions field) - // Extract tools array and flatten to direct params fields for backend compatibility const tools = currentConfigBlob.completion.params.tools || []; - // Collect ALL knowledge_base_ids from ALL tools into a single array const allKnowledgeBaseIds: string[] = []; - let maxNumResults = 20; // default + let maxNumResults = 20; tools.forEach((tool) => { - // Add all knowledge_base_ids from this tool allKnowledgeBaseIds.push(...tool.knowledge_base_ids); // Use max_num_results from first tool (could be made configurable) if (allKnowledgeBaseIds.length === tool.knowledge_base_ids.length) { @@ -317,12 +290,11 @@ function PromptEditorContent() { const configBlob: ConfigBlob = { completion: { provider: currentConfigBlob.completion.provider, - type: currentConfigBlob.completion.type || "text", // Default to 'text' + type: currentConfigBlob.completion.type || "text", params: { model: currentConfigBlob.completion.params.model, - instructions: currentContent, // Store prompt as instructions + instructions: currentContent, temperature: currentConfigBlob.completion.params.temperature, - // Flatten tools array to direct fields for backend - support multiple knowledge bases ...(allKnowledgeBaseIds.length > 0 && { knowledge_base_ids: allKnowledgeBaseIds, max_num_results: maxNumResults, @@ -331,13 +303,11 @@ function PromptEditorContent() { }, }; - // Check if updating existing config (same name exists) using allConfigMeta const existingConfigMeta = allConfigMeta.find( (m) => m.name === currentConfigName.trim(), ); if (existingConfigMeta) { - // Create new version for existing config const versionCreate: ConfigVersionCreate = { config_blob: configBlob, commit_message: commitMessage.trim() || `Updated prompt and config`, @@ -363,7 +333,6 @@ function PromptEditorContent() { `Configuration "${currentConfigName}" updated! New version created.`, ); } else { - // Create new config const configCreate: ConfigCreate = { name: currentConfigName.trim(), description: `${provider} configuration with prompt`, @@ -392,11 +361,9 @@ function PromptEditorContent() { ); } - // Invalidate config cache and refresh from shared hook invalidateConfigCache(); await refetchConfigs(true); - // Reset unsaved changes flag and commit message after successful save setHasUnsavedChanges(false); setCommitMessage(""); } catch (e) { @@ -420,8 +387,6 @@ function PromptEditorContent() {
- {/* Show DiffView only when comparing versions (sidebar open + version selected) */} {showHistorySidebar && selectedVersion ? ( ([]); const [selectedFile, setSelectedFile] = useState(null); @@ -232,70 +233,10 @@ export default function Datasets() { {/* Main Content */}
- {/* Title Section with Collapse Button */} -
-
- -
-

- Datasets -

-

- Manage your evaluation datasets -

-
-
-
+ {/* Content Area */}
( + null, + ); + const [isLoadingDocument, setIsLoadingDocument] = useState(false); + const [selectedFile, setSelectedFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); + const { activeKey: apiKey } = useAuth(); + + const { + items: documents, + isLoading, + isLoadingMore, + hasMore, + error, + loadMore, + refetch, + } = usePaginatedList({ + endpoint: "/api/document", + limit: DEFAULT_PAGE_LIMIT, + }); + + const scrollRef = useInfiniteScroll({ + onLoadMore: loadMore, + hasMore, + isLoading: isLoading || isLoadingMore, + }); + + const handleFileSelect = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + setSelectedFile(file); + }; + + const handleUpload = async () => { + if (!apiKey || !selectedFile) return; + + setIsUploading(true); + + try { + const formData = new FormData(); + formData.append("src", selectedFile); + + const data = await apiFetch<{ data?: { id: string } }>( + "/api/document", + apiKey.key, + { method: "POST", body: formData }, + ); + if (selectedFile && data.data?.id) { + const fileSizeMap = JSON.parse( + localStorage.getItem("document_file_sizes") || "{}", + ); + fileSizeMap[data.data.id] = selectedFile.size; + localStorage.setItem( + "document_file_sizes", + JSON.stringify(fileSizeMap), + ); + } + + refetch(); + setSelectedFile(null); + setIsModalOpen(false); + + toast.success("Document uploaded successfully!"); + } catch (error) { + console.error("Upload error:", error); + toast.error( + `Failed to upload document: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + } finally { + setIsUploading(false); + } + }; + + const handleDeleteDocument = async (documentId: string) => { + if (!apiKey) { + toast.error("No API key found"); + return; + } + + if (!confirm("Are you sure you want to delete this document?")) { + return; + } + + try { + await apiFetch(`/api/document/${documentId}`, apiKey.key, { + method: "DELETE", + }); + + if (selectedDocument?.id === documentId) { + setSelectedDocument(null); + } + + refetch(); + toast.success("Document deleted successfully"); + } catch (error) { + console.error("Delete error:", error); + toast.error( + `Failed to delete document: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + } + }; + + const handleSelectDocument = async (doc: Document) => { + if (!apiKey) return; + + setIsLoadingDocument(true); + try { + const data = await apiFetch<{ data?: Document }>( + `/api/document/${doc.id}`, + apiKey.key, + ); + const documentDetails: Document = + data.data ?? (data as unknown as Document); + + const fileSizeMap = JSON.parse( + localStorage.getItem("document_file_sizes") || "{}", + ); + const docWithSize = { + ...documentDetails, + file_size: fileSizeMap[documentDetails.id] || documentDetails.file_size, + }; + + setSelectedDocument(docWithSize); + } catch (err) { + console.error("Failed to fetch document details:", err); + toast.error("Failed to load document preview"); + setSelectedDocument(doc); + } finally { + setIsLoadingDocument(false); + } + }; + + return ( +
+
+ + +
+ + +
+
+ setIsModalOpen(true)} + isLoading={isLoading} + isLoadingMore={isLoadingMore} + error={error} + apiKey={apiKey} + scrollRef={scrollRef} + /> +
+ +
+ +
+
+
+
+ + {isModalOpen && ( + { + setIsModalOpen(false); + setSelectedFile(null); + }} + /> + )} +
+ ); +} diff --git a/app/(routes)/evaluations/[id]/page.tsx b/app/(main)/evaluations/[id]/page.tsx similarity index 100% rename from app/(routes)/evaluations/[id]/page.tsx rename to app/(main)/evaluations/[id]/page.tsx diff --git a/app/(routes)/evaluations/page.tsx b/app/(main)/evaluations/page.tsx similarity index 88% rename from app/(routes)/evaluations/page.tsx rename to app/(main)/evaluations/page.tsx index 12bcf45..e139f92 100644 --- a/app/(routes)/evaluations/page.tsx +++ b/app/(main)/evaluations/page.tsx @@ -10,8 +10,9 @@ import { useState, useEffect, useCallback, Suspense } from "react"; import { colors } from "@/app/lib/colors"; import { useSearchParams } from "next/navigation"; -import { Dataset } from "@/app/(routes)/datasets/page"; +import { Dataset } from "@/app/(main)/datasets/page"; import Sidebar from "@/app/components/Sidebar"; +import PageHeader from "@/app/components/PageHeader"; import TabNavigation from "@/app/components/TabNavigation"; import { useToast } from "@/app/components/Toast"; import { useAuth } from "@/app/lib/context/AuthContext"; @@ -35,7 +36,7 @@ function SimplifiedEvalContent() { : "datasets"; }); - const { sidebarCollapsed, setSidebarCollapsed } = useApp(); + const { sidebarCollapsed } = useApp(); const { apiKeys } = useAuth(); const [selectedKeyId, setSelectedKeyId] = useState(""); const [mounted, setMounted] = useState(false); @@ -306,52 +307,10 @@ function SimplifiedEvalContent() {
- {/* Title Section */} -
-
- -
-

- Text Evaluation -

-

- Compare model response quality on your datasets across - different configs -

-
-
-
+ {/* Tab Navigation */} - {/* Title Section with Collapse Button */} -
-
- -
-

- Keystore -

-

- Manage your API keys securely -

-
-
-
+ {/* Content Area */}
diff --git a/app/(routes)/knowledge-base/page.tsx b/app/(main)/knowledge-base/page.tsx similarity index 83% rename from app/(routes)/knowledge-base/page.tsx rename to app/(main)/knowledge-base/page.tsx index a5bf4c4..3467d5d 100644 --- a/app/(routes)/knowledge-base/page.tsx +++ b/app/(main)/knowledge-base/page.tsx @@ -4,6 +4,8 @@ import { useState, useEffect, useRef } from "react"; import { colors } from "@/app/lib/colors"; import { formatDate } from "@/app/components/utils"; import Sidebar from "@/app/components/Sidebar"; +import PageHeader from "@/app/components/PageHeader"; +import Modal from "@/app/components/Modal"; import { useAuth } from "@/app/lib/context/AuthContext"; import { useApp } from "@/app/lib/context/AppContext"; @@ -30,7 +32,7 @@ export interface Collection { } export default function KnowledgeBasePage() { - const { sidebarCollapsed, setSidebarCollapsed } = useApp(); + const { sidebarCollapsed } = useApp(); const [collections, setCollections] = useState([]); const [availableDocuments, setAvailableDocuments] = useState([]); const [selectedCollection, setSelectedCollection] = @@ -904,70 +906,10 @@ export default function KnowledgeBasePage() { {/* Main Content */}
- {/* Header with Collapse Button */} -
-
- -
-

- Knowledge Base -

-

- Manage your knowledge bases for RAG -

-
-
-
+ {/* Content Area - Split View */}
@@ -1580,7 +1522,6 @@ export default function KnowledgeBasePage() {
- {/* Confirm Delete Modal */} {showConfirmDelete && (
)} - {/* Document Picker Modal */} - {showDocumentPicker && ( -
-
-
-

- Select Documents -

- -
- - {/* Document List */} -
- {availableDocuments.length === 0 ? ( -
- No documents available. Please upload documents first. -
- ) : ( -
- {availableDocuments.map((doc) => ( - - ))} -
- )} -
- - {/* Selected Count and Actions */} -
-

- {selectedDocuments.size} document - {selectedDocuments.size !== 1 ? "s" : ""} selected -

-
- - + No documents available. Please upload documents first.
-
-
-
- )} - - {/* Document Preview Modal */} - {showDocPreviewModal && selectedCollection?.documents && ( -
-
- {/* Modal Header */} -
-

- Document Preview -

- -
- - {/* Two-pane body */} -
- {/* Left pane — doc list */} -
- {selectedCollection.documents.map((doc) => ( - +
+ ))}
+ )} +
- {/* Right pane — preview content */} -
+

+ {selectedDocuments.size} document + {selectedDocuments.size !== 1 ? "s" : ""} selected +

+
+