Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -27,7 +28,6 @@ import {
} from "@/app/lib/store/configStore";
import { flattenConfigVersion } from "@/app/lib/utils";
import {
SidebarToggleIcon,
SearchIcon,
RefreshIcon,
PlusIcon,
Expand All @@ -45,7 +45,7 @@ export default function ConfigLibraryPage() {
const [evaluationCounts, setEvaluationCounts] = useState<
Record<string, number>
>({});
const { sidebarCollapsed, setSidebarCollapsed } = useApp();
const { sidebarCollapsed } = useApp();
const { activeKey } = useAuth();
const apiKey = activeKey?.key;
const [searchInput, setSearchInput] = useState("");
Expand Down Expand Up @@ -196,49 +196,10 @@ export default function ConfigLibraryPage() {
<Sidebar collapsed={sidebarCollapsed} activeRoute="/configurations" />

<div className="flex-1 flex flex-col overflow-hidden">
<div
className="border-b px-6 py-4"
style={{
backgroundColor: colors.bg.primary,
borderColor: colors.border,
}}
>
<div className="flex items-center gap-3">
<button
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className="p-1.5 rounded flex-shrink-0 transition-colors"
style={{
border: `1px solid ${colors.border}`,
backgroundColor: colors.bg.primary,
color: colors.text.secondary,
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = colors.bg.secondary;
e.currentTarget.style.color = colors.text.primary;
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = colors.bg.primary;
e.currentTarget.style.color = colors.text.secondary;
}}
>
<SidebarToggleIcon collapsed={sidebarCollapsed} />
</button>
<div className="flex-1">
<h1
className="text-2xl font-semibold"
style={{ color: colors.text.primary }}
>
Configuration Library
</h1>
<p
className="text-sm mt-0.5"
style={{ color: colors.text.secondary }}
>
Manage your prompts and model configurations
</p>
</div>
</div>
</div>
<PageHeader
title="Configuration Library"
subtitle="Manage your prompts and model configurations"
/>

{/* Toolbar */}
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
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 { 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";
Expand All @@ -30,10 +30,23 @@
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();
const urlConfigId = searchParams.get("config");
const urlVersion = searchParams.get("version");
Expand All @@ -43,21 +56,6 @@
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,
Expand All @@ -78,11 +76,11 @@
"You are a helpful AI assistant.\nYou provide clear and concise answers.\nYou are polite and professional.",
);
const [currentConfigBlob, setCurrentConfigBlob] =
useState<ConfigBlob>(defaultConfig);
useState<ConfigBlob>(DEFAULT_CONFIG);
const [currentConfigName, setCurrentConfigName] = useState<string>("");
const [selectedConfigId, setSelectedConfigId] = useState<string>(""); // Selected version ID
const [currentConfigParentId, setCurrentConfigParentId] =
useState<string>(""); // Parent config ID for evaluation
useState<string>("");
const [currentConfigVersion, setCurrentConfigVersion] = useState<number>(0); // Version number for evaluation
const [provider, setProvider] = useState<string>("openai");
const [temperature, setTemperature] = useState<number>(0.7);
Expand All @@ -107,7 +105,6 @@

const versionItemsMap = stableVersionItemsMap;

// Populate the editor from a fully-loaded SavedConfig
const applyConfig = React.useCallback(
(config: SavedConfig, selectInHistory?: boolean) => {
setCurrentContent(config.promptContent);
Expand Down Expand Up @@ -138,16 +135,15 @@
);
if (selectInHistory) setSelectedVersion(config);
},
[],

Check warning on line 138 in app/(main)/configurations/prompt-editor/page.tsx

View workflow job for this annotation

GitHub Actions / lint-and-build

React Hook React.useCallback has a missing dependency: 'currentConfigParentId'. Either include it or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setExpandedConfigs' needs the current value of 'currentConfigParentId'
);

// 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("");
Expand All @@ -157,11 +153,10 @@
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],

Check warning on line 159 in app/(main)/configurations/prompt-editor/page.tsx

View workflow job for this annotation

GitHub Actions / lint-and-build

React Hook React.useCallback has an unnecessary dependency: 'DEFAULT_CONFIG'. Either exclude it or remove the dependency array. Outer scope values like 'DEFAULT_CONFIG' aren't valid dependencies because mutating them doesn't re-render the component
);

// Initialize editor from URL params — runs once, on first load completion
Expand All @@ -173,7 +168,7 @@
// If new config is requested, reset to defaults
if (isNewConfig) {
setCurrentContent("");
setCurrentConfigBlob(defaultConfig);
setCurrentConfigBlob(DEFAULT_CONFIG);
setProvider("openai");
setTemperature(0.7);
setSelectedConfigId("");
Expand All @@ -191,7 +186,6 @@
}

(async () => {
// Load version list for history sidebar (1 call, cached on subsequent runs)
await loadVersionsForConfig(urlConfigId);

const items = configState.versionItemsCache[urlConfigId] ?? [];
Expand All @@ -200,7 +194,6 @@
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;
Expand All @@ -209,7 +202,7 @@
if (config) applyConfig(config, showHistory);
setEditorReady(true);
})();
}, [

Check warning on line 205 in app/(main)/configurations/prompt-editor/page.tsx

View workflow job for this annotation

GitHub Actions / lint-and-build

React Hook useEffect has an unnecessary dependency: 'DEFAULT_CONFIG'. Either exclude it or remove the dependency array. Outer scope values like 'DEFAULT_CONFIG' aren't valid dependencies because mutating them doesn't re-render the component
initialLoadComplete,
urlConfigId,
urlVersion,
Expand All @@ -218,7 +211,7 @@
loadVersionsForConfig,
loadSingleVersion,
applyConfig,
defaultConfig,
DEFAULT_CONFIG,
]);

// Re-populate version items when missing (e.g. after background cache revalidation wipes versionItemsCache)
Expand All @@ -228,10 +221,8 @@
}
}, [currentConfigParentId, versionItemsMap, loadVersionsForConfig]);

// Detect unsaved changes
useEffect(() => {
if (!selectedConfigId) {
// New config - always has unsaved changes until saved
setHasUnsavedChanges(true);
return;
}
Expand Down Expand Up @@ -268,7 +259,6 @@
savedConfigs,
]);

// Save current configuration
const handleSaveConfig = async () => {
if (!currentConfigName.trim()) {
toast.error("Please enter a configuration name");
Expand All @@ -284,16 +274,12 @@
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) {
Expand All @@ -304,12 +290,11 @@
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,
Expand All @@ -318,13 +303,11 @@
},
};

// 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`,
Expand All @@ -350,7 +333,6 @@
`Configuration "${currentConfigName}" updated! New version created.`,
);
} else {
// Create new config
const configCreate: ConfigCreate = {
name: currentConfigName.trim(),
description: `${provider} configuration with prompt`,
Expand Down Expand Up @@ -379,11 +361,9 @@
);
}

// 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) {
Expand All @@ -407,8 +387,6 @@

<div className="flex-1 flex flex-col overflow-hidden">
<Header
sidebarCollapsed={sidebarCollapsed}
setSidebarCollapsed={setSidebarCollapsed}
currentConfigId={currentConfigParentId}
currentConfigVersion={currentConfigVersion}
currentConfigName={currentConfigName}
Expand Down
Loading
Loading