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/(routes)/configurations/page.tsx b/app/(main)/configurations/page.tsx similarity index 88% rename from app/(routes)/configurations/page.tsx rename to app/(main)/configurations/page.tsx index 12fbf32..09a93f3 100644 --- a/app/(routes)/configurations/page.tsx +++ b/app/(main)/configurations/page.tsx @@ -8,6 +8,7 @@ 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"; @@ -27,7 +28,6 @@ import { } from "@/app/lib/store/configStore"; import { flattenConfigVersion } from "@/app/lib/utils"; import { - SidebarToggleIcon, SearchIcon, RefreshIcon, PlusIcon, @@ -45,7 +45,7 @@ export default function ConfigLibraryPage() { const [evaluationCounts, setEvaluationCounts] = useState< Record >({}); - const { sidebarCollapsed, setSidebarCollapsed } = useApp(); + const { sidebarCollapsed } = useApp(); const { activeKey } = useAuth(); const apiKey = activeKey?.key; const [searchInput, setSearchInput] = useState(""); @@ -196,49 +196,10 @@ export default function ConfigLibraryPage() {
-
-
- -
-

- Configuration Library -

-

- Manage your prompts and model configurations -

-
-
-
+ {/* Toolbar */}
(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); @@ -107,7 +105,6 @@ function PromptEditorContent() { const versionItemsMap = stableVersionItemsMap; - // Populate the editor from a fully-loaded SavedConfig const applyConfig = React.useCallback( (config: SavedConfig, selectInHistory?: boolean) => { setCurrentContent(config.promptContent); @@ -141,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(""); @@ -157,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, defaultConfig], + [applyConfig, loadVersionsForConfig, DEFAULT_CONFIG], ); // Initialize editor from URL params — runs once, on first load completion @@ -173,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(""); @@ -191,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] ?? []; @@ -200,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; @@ -218,7 +211,7 @@ function PromptEditorContent() { loadVersionsForConfig, loadSingleVersion, applyConfig, - defaultConfig, + DEFAULT_CONFIG, ]); // Re-populate version items when missing (e.g. after background cache revalidation wipes versionItemsCache) @@ -228,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; } @@ -268,7 +259,6 @@ function PromptEditorContent() { savedConfigs, ]); - // Save current configuration const handleSaveConfig = async () => { if (!currentConfigName.trim()) { toast.error("Please enter a configuration name"); @@ -284,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) { @@ -304,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, @@ -318,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`, @@ -350,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`, @@ -379,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) { @@ -407,8 +387,6 @@ function PromptEditorContent() {
([]); 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 +

+
+