From 88d55de9f2736cdad10c2460b31849cb7234a35d Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Sun, 22 Mar 2026 23:13:10 +0530 Subject: [PATCH 01/10] perf(configs): fix N+1 API calls and refactor config data layer --- app/components/ConfigCard.tsx | 3 +- app/components/ConfigSelector.tsx | 21 +- .../prompt-editor/ConfigDiffPane.tsx | 4 +- .../prompt-editor/ConfigEditorPane.tsx | 18 +- app/components/prompt-editor/DiffView.tsx | 116 ++++- .../prompt-editor/HistorySidebar.tsx | 231 +++++++-- .../prompt-editor/PromptDiffPane.tsx | 3 +- app/configurations/page.tsx | 65 ++- app/configurations/prompt-editor/page.tsx | 24 +- app/hooks/useConfigs.ts | 390 ++++++++++++++ app/lib/configFetchers.ts | 273 ++++++++++ app/lib/constants.ts | 15 + app/lib/store/configStore.ts | 93 ++++ app/lib/types/configs.ts | 66 +++ app/lib/useConfigs.ts | 490 ------------------ app/lib/utils.ts | 143 +++++ 16 files changed, 1362 insertions(+), 593 deletions(-) create mode 100644 app/hooks/useConfigs.ts create mode 100644 app/lib/configFetchers.ts create mode 100644 app/lib/constants.ts create mode 100644 app/lib/store/configStore.ts create mode 100644 app/lib/types/configs.ts delete mode 100644 app/lib/useConfigs.ts diff --git a/app/components/ConfigCard.tsx b/app/components/ConfigCard.tsx index 096d180..73a0444 100644 --- a/app/components/ConfigCard.tsx +++ b/app/components/ConfigCard.tsx @@ -8,7 +8,8 @@ import React, { useState } from 'react'; import { useRouter } from 'next/navigation'; import { colors } from '@/app/lib/colors'; -import { ConfigGroup, SavedConfig, formatRelativeTime } from '@/app/lib/useConfigs'; +import { ConfigGroup, SavedConfig } from '@/app/lib/types/configs'; +import { formatRelativeTime } from '@/app/lib/utils'; interface ConfigCardProps { configGroup: ConfigGroup; diff --git a/app/components/ConfigSelector.tsx b/app/components/ConfigSelector.tsx index 5e47493..3c3a82a 100644 --- a/app/components/ConfigSelector.tsx +++ b/app/components/ConfigSelector.tsx @@ -8,8 +8,10 @@ import { useState, useRef, useLayoutEffect } from 'react'; import { useRouter } from 'next/navigation'; import { colors } from '@/app/lib/colors'; -import { useConfigs, SavedConfig, formatRelativeTime } from '@/app/lib/useConfigs'; +import { useConfigs } from '@/app/hooks/useConfigs'; +import { SavedConfig } from '@/app/lib/types/configs'; import { ChevronUpIcon, ChevronDownIcon, EditIcon, GearIcon, CheckIcon } from '@/app/components/icons'; +import { formatRelativeTime } from '@/app/lib/utils'; interface ConfigSelectorProps { selectedConfigId: string; @@ -33,7 +35,7 @@ export default function ConfigSelector({ experimentName, }: ConfigSelectorProps) { const router = useRouter(); - const { configs, configGroups, isLoading, error } = useConfigs(); + const { configs, configGroups, isLoading, error, loadVersionsForConfig } = useConfigs(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [promptExpanded, setPromptExpanded] = useState(false); @@ -75,6 +77,19 @@ export default function ConfigSelector({ setSearchQuery(''); // Clear search on close }; + // When opening the dropdown, lazily load full version history for all configs + const handleOpenDropdown = () => { + if (disabled) return; + if (!isDropdownOpen) { + configGroups.forEach(group => { + if (!group.versionsFullyLoaded) { + loadVersionsForConfig(group.config_id); + } + }); + } + setIsDropdownOpen(prev => !prev); + }; + // Build URL params preserving evaluation context const buildEditorUrl = (configId?: string, version?: number) => { const params = new URLSearchParams(); @@ -243,7 +258,7 @@ export default function ConfigSelector({ /* Button when dropdown is closed */
- {filteredConfigGroups.length === 0 ? ( -
- {searchQuery ? `No configurations match "${searchQuery}"` : 'No configurations available'} + {filteredDisplayGroups.length === 0 ? ( +
+ {searchQuery + ? `No configurations match "${searchQuery}"` + : "No configurations available"}
) : ( - filteredConfigGroups.map((group) => ( -
- {/* Config group header */} -
- {group.name} ({group.totalVersions} version{group.totalVersions !== 1 ? 's' : ''}) -
- {/* Versions */} - {group.versions.map((version) => ( - + {/* Version items — lightweight list, loaded on first expand */} + {isExpanded && + !isLoadingGroup && + versionItems.map((item) => { + const isSelected = + selectedConfigId === item.config_id && + selectedVersion === item.version; + return ( + + ); + })} + {/* Spinner while version list is being fetched */} + {isExpanded && isLoadingGroup && ( +
+ Loading versions…
-
- {selectedConfig?.id === version.id && ( - )} - - ))} -
- )))} + {/* Empty state: expanded but no versions returned */} + {isExpanded && + !isLoadingGroup && + versionItems.length === 0 && ( +
+ No versions available +
+ )} +
+ ); + }) + )} )} + {/* Preview: loading state while full config details are being fetched */} + {isLoadingPreview && !selectedConfig && ( +
+ + + + + + Loading configuration details… + +
+ )} + {/* Selected Config Preview */} - {selectedConfig && ( + {selectedConfig && !isLoadingPreview && (
-
+
Provider & Model
-
+
{selectedConfig.provider}/{selectedConfig.modelName}
-
+
Temperature
-
+
{selectedConfig.temperature.toFixed(2)}
{selectedConfig.tools && selectedConfig.tools.length > 0 && ( <>
-
+
Knowledge Base IDs
-
- {selectedConfig.tools.map(tool => tool.knowledge_base_ids).flat().join(', ') || 'None'} +
+ {selectedConfig.tools + .map((tool) => tool.knowledge_base_ids) + .flat() + .join(", ") || "None"}
-
+
Max Results
-
+
{selectedConfig.tools[0].max_num_results}
@@ -396,32 +704,34 @@ export default function ConfigSelector({
{/* Prompt Preview */} -
+
-
+
Prompt Preview
{selectedConfig.instructions && isPromptOverflowing && ( )}
- {selectedConfig.instructions || 'No instructions set'} + {selectedConfig.instructions || "No instructions set"}
@@ -431,10 +741,7 @@ export default function ConfigSelector({ {/* Click outside to close dropdown */} {isDropdownOpen && ( -
+
)}
); diff --git a/app/components/prompt-editor/ConfigDiffPane.tsx b/app/components/prompt-editor/ConfigDiffPane.tsx index 7711478..24791e2 100644 --- a/app/components/prompt-editor/ConfigDiffPane.tsx +++ b/app/components/prompt-editor/ConfigDiffPane.tsx @@ -137,7 +137,7 @@ export default function ConfigDiffPane({ Before (v{compareWith.version})
void; + /** Called when user selects a version. Null = new (unsaved) config. */ + onLoadConfig: (config: SavedConfig | null) => void; commitMessage: string; onCommitMessageChange: (message: string) => void; onSave: () => void; @@ -20,25 +23,23 @@ interface ConfigEditorPaneProps { // Collapse functionality collapsed?: boolean; onToggle?: () => void; - /** Lazily loads all historical versions for a given config_id */ + allConfigMeta?: ConfigPublic[]; // Lightweight list of all configs + versionItemsMap?: Record; // Lightweight version items loadVersionsForConfig?: (config_id: string) => Promise; -} - -// Group configs by name for nested dropdown -interface ConfigGroupForDropdown { - config_id: string; - name: string; - versions: SavedConfig[]; + loadSingleVersion?: ( + config_id: string, + version: number, + ) => Promise; } // Provider-specific models const MODEL_OPTIONS = { openai: [ - { value: 'gpt-4o', label: 'GPT-4o' }, - { value: 'gpt-4o-mini', label: 'GPT-4o Mini' }, - { value: 'gpt-4-turbo', label: 'GPT-4 Turbo' }, - { value: 'gpt-4', label: 'GPT-4' }, - { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo' }, + { value: "gpt-4o", label: "GPT-4o" }, + { value: "gpt-4o-mini", label: "GPT-4o Mini" }, + { value: "gpt-4-turbo", label: "GPT-4 Turbo" }, + { value: "gpt-4", label: "GPT-4" }, + { value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" }, ], // anthropic: [ // { value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet' }, @@ -67,16 +68,62 @@ export default function ConfigEditorPane({ isSaving = false, collapsed = false, onToggle, + allConfigMeta = [], + versionItemsMap = {}, loadVersionsForConfig, + loadSingleVersion, }: ConfigEditorPaneProps) { const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [expandedConfigId, setExpandedConfigId] = useState(null); // config group is expanded in the Load dropdown + const [loadingVersionsFor, setLoadingVersionsFor] = useState>( + new Set(), + ); // groups are currently loading their version list const handleOpenLoadDropdown = () => { - // When opening, trigger lazy loading for any config that only has its latest version - if (!isDropdownOpen && loadVersionsForConfig) { - configGroups.forEach(group => loadVersionsForConfig(group.config_id)); + if (!isDropdownOpen) { + // Auto-expand the currently selected config group, load its version list if needed + if (selectedConfigId) { + const selected = savedConfigs.find((c) => c.id === selectedConfigId); + if (selected) { + setExpandedConfigId(selected.config_id); + if (!versionItemsMap[selected.config_id] && loadVersionsForConfig) { + setLoadingVersionsFor((prev) => + new Set(prev).add(selected.config_id), + ); + loadVersionsForConfig(selected.config_id).finally(() => { + setLoadingVersionsFor((prev) => { + const s = new Set(prev); + s.delete(selected.config_id); + return s; + }); + }); + } + } + } + } + setIsDropdownOpen((prev) => !prev); + }; + + const handleToggleGroup = (config_id: string) => { + if (expandedConfigId === config_id) { + setExpandedConfigId(null); + return; + } + setExpandedConfigId(config_id); + if ( + !versionItemsMap[config_id] && + !loadingVersionsFor.has(config_id) && + loadVersionsForConfig + ) { + setLoadingVersionsFor((prev) => new Set(prev).add(config_id)); + loadVersionsForConfig(config_id).finally(() => { + setLoadingVersionsFor((prev) => { + const s = new Set(prev); + s.delete(config_id); + return s; + }); + }); } - setIsDropdownOpen(prev => !prev); }; const [showTooltip, setShowTooltip] = useState(null); @@ -84,26 +131,13 @@ export default function ConfigEditorPane({ const params = configBlob.completion.params; const tools = (params.tools || []) as Tool[]; - // Group configs by config_id for nested dropdown - const configGroups = useMemo(() => { - const grouped = new Map(); - savedConfigs.forEach((config) => { - const existing = grouped.get(config.config_id) || []; - existing.push(config); - grouped.set(config.config_id, existing); - }); - return Array.from(grouped.entries()).map(([config_id, versions]) => { - const sortedVersions = versions.sort((a, b) => b.version - a.version); - return { - config_id, - name: sortedVersions[0].name, - versions: sortedVersions, - } as ConfigGroupForDropdown; - }); - }, [savedConfigs]); + // Find currently selected config from loaded set + const selectedConfig = savedConfigs.find((c) => c.id === selectedConfigId); - // Find currently selected config - const selectedConfig = savedConfigs.find(c => c.id === selectedConfigId); + // Config name hint text: use allConfigMeta for accurate new-vs-existing detection + const existingConfigForHint = configName.trim() + ? allConfigMeta.find((m) => m.name === configName.trim()) + : undefined; const handleProviderChange = (newProvider: string) => { onConfigChange({ @@ -113,13 +147,14 @@ export default function ConfigEditorPane({ provider: newProvider as any, params: { ...params, - model: MODEL_OPTIONS[newProvider as keyof typeof MODEL_OPTIONS][0].value, + model: + MODEL_OPTIONS[newProvider as keyof typeof MODEL_OPTIONS][0].value, }, }, }); }; - const handleTypeChange = (newType: 'text' | 'stt' | 'tts') => { + const handleTypeChange = (newType: "text" | "stt" | "tts") => { onConfigChange({ ...configBlob, completion: { @@ -153,8 +188,8 @@ export default function ConfigEditorPane({ const newTools = [ ...tools, { - type: 'file_search' as const, - knowledge_base_ids: [''], + type: "file_search" as const, + knowledge_base_ids: [""], max_num_results: 20, }, ]; @@ -180,7 +215,7 @@ export default function ConfigEditorPane({ const handleUpdateTool = (index: number, field: keyof Tool, value: any) => { const newTools = [...tools]; - if (field === 'knowledge_base_ids') { + if (field === "knowledge_base_ids") { newTools[index][field] = [value]; } else { (newTools[index] as any)[field] = value; @@ -198,11 +233,11 @@ export default function ConfigEditorPane({
{/* Header */} @@ -210,15 +245,18 @@ export default function ConfigEditorPane({ className="border-b flex items-center flex-shrink-0" style={{ borderColor: colors.border, - padding: collapsed ? '0' : '12px 16px', - justifyContent: collapsed ? 'center' : 'space-between', - height: collapsed ? '40px' : 'auto', - transition: 'padding 0.2s ease-in-out', + padding: collapsed ? "0" : "12px 16px", + justifyContent: collapsed ? "center" : "space-between", + height: collapsed ? "40px" : "auto", + transition: "padding 0.2s ease-in-out", }} > {/* Title - hidden when collapsed, shown first when expanded */} {!collapsed && ( -

+

Configuration

)} @@ -228,13 +266,13 @@ export default function ConfigEditorPane({ onClick={onToggle} className="rounded flex-shrink-0 flex items-center justify-center" style={{ - width: '28px', - height: '28px', - borderWidth: '1px', + width: "28px", + height: "28px", + borderWidth: "1px", borderColor: colors.border, backgroundColor: colors.bg.primary, color: colors.text.secondary, - transition: 'all 0.15s ease', + transition: "all 0.15s ease", }} onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = colors.bg.secondary; @@ -244,7 +282,7 @@ export default function ConfigEditorPane({ e.currentTarget.style.backgroundColor = colors.bg.primary; e.currentTarget.style.color = colors.text.secondary; }} - title={collapsed ? 'Show configuration' : 'Hide configuration'} + title={collapsed ? "Show configuration" : "Hide configuration"} > - + )} @@ -274,8 +317,8 @@ export default function ConfigEditorPane({ Configuration @@ -285,477 +328,666 @@ export default function ConfigEditorPane({ {/* Content - hidden when collapsed */} {!collapsed && ( -
-
- {/* Load Saved Config - Nested dropdown matching Evaluations page pattern */} -
- - - - {/* Dropdown Menu */} - {isDropdownOpen && ( -
+ + + + - {/* Grouped Configs */} - {configGroups.map((group) => ( -
- {/* Config group header */} -
+ {/* New Config Option */} +
- {/* Versions */} - {group.versions.map((version) => ( - + + {/* Grouped Configs — built from lightweight allConfigMeta */} + {allConfigMeta.map((meta) => { + const isExpanded = expandedConfigId === meta.id; + const isLoadingGroup = loadingVersionsFor.has(meta.id); + const items = versionItemsMap[meta.id] ?? []; + return ( +
+ {/* Config group header */} +
-
- {version.provider}/{version.modelName} • {formatRelativeTime(version.timestamp)} + + + + + {/* Version items — only when expanded */} + {isExpanded && + !isLoadingGroup && + items.map((item) => { + const isSelected = + selectedConfig?.config_id === meta.id && + selectedConfig?.version === item.version; + return ( + + ); + })} + {isExpanded && isLoadingGroup && ( +
+ Loading versions…
-
- {selectedConfig?.id === version.id && ( - - - )} - - ))} -
- ))} -
- )} +
+ ); + })} +
+ )} - {/* Click outside to close dropdown */} - {isDropdownOpen && ( -
setIsDropdownOpen(false)} - /> - )} -
+ {/* Click outside to close dropdown */} + {isDropdownOpen && ( +
setIsDropdownOpen(false)} + /> + )} +
- {/* Config Name */} -
- - onConfigNameChange(e.target.value)} - placeholder="e.g., my-config" - className="w-full px-3 py-2 rounded-md text-sm focus:outline-none" - style={{ - border: `1px solid ${colors.border}`, - backgroundColor: colors.bg.primary, - color: colors.text.primary, - }} - /> - {configName.trim() && (() => { - const existingConfig = savedConfigs.find(c => c.name === configName.trim()); - return ( -

- {existingConfig + {/* Config Name */} +

+ + onConfigNameChange(e.target.value)} + placeholder="e.g., my-config" + className="w-full px-3 py-2 rounded-md text-sm focus:outline-none" + style={{ + border: `1px solid ${colors.border}`, + backgroundColor: colors.bg.primary, + color: colors.text.primary, + }} + /> + {configName.trim() && ( +

+ {existingConfigForHint ? `💡 Will create a new version for "${configName}"` - : `✨ Will create a new config "${configName}"` - } + : `✨ Will create a new config "${configName}"`}

- ); - })()} -
- - {/* Provider */} -
- - -
- - {/* Type */} -
- - -

- Standard text-based LLM completion -

-
- - {/* Model */} -
- - -
+ )} +
- {/* Temperature */} -
- - handleTemperatureChange(parseFloat(e.target.value))} - className="w-full" - style={{ accentColor: colors.accent.primary }} - /> -
- 0 - 2 + {/* Provider */} +
+ +
-
- {/* Tools */} -
-
+ {/* Type */} +
- + {PROVIDER_TYPES.map((option) => ( + + ))} + +

+ Standard text-based LLM completion +

- {tools.map((tool, index) => ( -
+ + - handleUpdateTool(index, 'knowledge_base_ids', e.target.value) - } - placeholder="vs_abc123" - className="w-full px-2 py-1 rounded text-xs focus:outline-none" - style={{ - border: `1px solid ${colors.border}`, - backgroundColor: colors.bg.primary, - color: colors.text.primary, - }} - /> -
-
-
-
+ + {/* Temperature */} +
+ + + handleTemperatureChange(parseFloat(e.target.value)) + } + className="w-full" + style={{ accentColor: colors.accent.primary }} + /> +
+ 0 + 2 +
+
+ + {/* Tools */} +
+
+ + +
+ {tools.map((tool, index) => ( +
+
+ + File Search + + +
+
+ -
setShowTooltip(index)} - onMouseLeave={() => setShowTooltip(null)} - > - + handleUpdateTool( + index, + "knowledge_base_ids", + e.target.value, + ) + } + placeholder="vs_abc123" + className="w-full px-2 py-1 rounded text-xs focus:outline-none" + style={{ + border: `1px solid ${colors.border}`, + backgroundColor: colors.bg.primary, + color: colors.text.primary, + }} + /> +
+
+
+
- - handleUpdateTool( - index, - 'max_num_results', - parseInt(e.target.value) || 20 - ) - } - className="w-full px-2 py-1 rounded text-xs focus:outline-none" - style={{ - border: `1px solid ${colors.border}`, - backgroundColor: colors.bg.primary, - color: colors.text.primary, - }} - />
-
- ))} -
+ ))} +
- {/* Commit Message */} -
- - onCommitMessageChange(e.target.value)} - placeholder="Describe your changes..." - className="w-full px-3 py-2 rounded-md text-sm focus:outline-none" + {/* Commit Message */} +
+ + onCommitMessageChange(e.target.value)} + placeholder="Describe your changes..." + className="w-full px-3 py-2 rounded-md text-sm focus:outline-none" + style={{ + border: `1px solid ${colors.border}`, + backgroundColor: colors.bg.primary, + color: colors.text.primary, + }} + /> +
+ + {/* Save Button */} +
- - {/* Save Button */} -
-
)}
); diff --git a/app/components/prompt-editor/DiffView.tsx b/app/components/prompt-editor/DiffView.tsx index aefaf60..0b5b801 100644 --- a/app/components/prompt-editor/DiffView.tsx +++ b/app/components/prompt-editor/DiffView.tsx @@ -10,7 +10,7 @@ interface DiffViewProps { compareWith: SavedConfig | null; commits: SavedConfig[]; onCompareChange: (commit: SavedConfig | null) => void; - onLoadVersion: (versionId: string) => void; + onLoadVersion: (config: SavedConfig) => void; /** Lazily loads the lightweight version list for a given config_id (1 call or no-op) */ loadVersionsForConfig?: (config_id: string) => Promise; /** @@ -166,7 +166,7 @@ export default function DiffView({ {compareWith && (