Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4d91052
feat(configs): lazy-load config versions on expand & extract inline S…
Ayush8923 Mar 27, 2026
78c33cb
feat(configs): lazy-load config versions on expand & extract inline S…
Ayush8923 Mar 27, 2026
42ad650
fix(*): linting and js comment
Ayush8923 Mar 27, 2026
3274e9d
fix(*): remove the unused files
Ayush8923 Mar 27, 2026
9a2da8f
fix(*): added the infinite scroll hook and update the icons
Ayush8923 Mar 27, 2026
f12d2d6
fix(*): update the constants
Ayush8923 Mar 27, 2026
e1b80be
fix(*): update imports
Ayush8923 Mar 27, 2026
395fabc
fix(*): remove the unused js comment
Ayush8923 Mar 27, 2026
bee1fc3
fix(*): update the tailwind style
Ayush8923 Mar 27, 2026
0c1d83c
fix(*): fix the alignment
Ayush8923 Mar 27, 2026
7a5b11f
fix(*): refactoring loader component
Ayush8923 Mar 27, 2026
133bdc4
feat(*): change grouping name routes->main
Ayush8923 Mar 27, 2026
8a38504
fix(*): formatting
Ayush8923 Mar 27, 2026
034efc3
feat(*): document pagination params & document folder restructuring &…
Ayush8923 Mar 29, 2026
8df6880
fix(*): added the reusable component
Ayush8923 Mar 29, 2026
7cc670d
fix(*): some updates and cleanups
Ayush8923 Mar 29, 2026
a79f4f4
fix(*): create the own modal component and cleanups
Ayush8923 Mar 29, 2026
e60b32f
fix(*): Remove unwanted types
Ayush8923 Mar 29, 2026
86daedc
fix(*): Remove unwanted types
Ayush8923 Mar 29, 2026
f1032ca
fix(*): added the quers params in the api endpoint
Ayush8923 Mar 30, 2026
4936b8b
Feat/document pagination params (#98)
Ayush8923 Mar 30, 2026
7ff52af
Change grouping name `routes` -> `main` (#97)
Ayush8923 Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 52 additions & 87 deletions app/(routes)/configurations/page.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
/**
* Config Library - Central hub for managing configurations
*
* Features:
* - View all configs with their versions
* - Quick actions: Edit, Use in Evaluation, View History
* - Shows evaluation usage count per config
* - Create new config navigation
* 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 } from "react";
import { useState, useEffect, useMemo } from "react";
import { useRouter } from "next/navigation";
import Sidebar from "@/app/components/Sidebar";
import { colors } from "@/app/lib/colors";
import { useConfigs } from "@/app/hooks/useConfigs";
import { SavedConfig } from "@/app/lib/types/configs";
import ConfigCard from "@/app/components/ConfigCard";
import { LoaderBox } from "@/app/components/Loader";
import { EvalJob } from "@/app/components/types";
import { ConfigPublic } from "@/app/lib/types/configs";
import { useAuth } from "@/app/lib/context/AuthContext";
import { useApp } from "@/app/lib/context/AppContext";
import { apiFetch } from "@/app/lib/apiClient";
Expand All @@ -28,20 +23,32 @@ export default function ConfigLibraryPage() {
const { sidebarCollapsed, setSidebarCollapsed } = useApp();
const { activeKey } = useAuth();
const {
configGroups,
isLoading,
error,
refetch,
isCached,
loadMoreConfigs,
hasMoreConfigs,
isLoadingMore,
} = useConfigs({ pageSize: 10 });
allConfigMeta,
loadVersionsForConfig,
loadSingleVersion,
} = useConfigs({ pageSize: 0 });
const [searchQuery, setSearchQuery] = useState("");
const [columnCount, setColumnCount] = useState(3);
const [evaluationCounts, setEvaluationCounts] = useState<
Record<string, number>
>({});

// Responsive column count matching breakpoints (lg:2, xl:3)
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);
}, []);

// Fetch evaluation counts for each config
useEffect(() => {
const fetchEvaluationCounts = async () => {
Expand Down Expand Up @@ -72,27 +79,28 @@ export default function ConfigLibraryPage() {
}, [activeKey]);

// Filter configs based on search query
const filteredConfigs = configGroups.filter(
(group) =>
group.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
group.latestVersion.modelName
.toLowerCase()
.includes(searchQuery.toLowerCase()) ||
group.latestVersion.instructions
const filteredConfigs = allConfigMeta.filter(
(config) =>
config.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
(config.description ?? "")
.toLowerCase()
.includes(searchQuery.toLowerCase()),
);

// Distribute configs into fixed columns (round-robin) so items never shift
const columns = useMemo(() => {
const cols: ConfigPublic[][] = Array.from(
{ length: columnCount },
() => [],
);
filteredConfigs.forEach((config, i) => cols[i % columnCount].push(config));
return cols;
}, [filteredConfigs, columnCount]);

const handleCreateNew = () => {
router.push("/configurations/prompt-editor?new=true");
};

const handleUseInEvaluation = (config: SavedConfig) => {
router.push(
`/evaluations?config=${config.config_id}&version=${config.version}`,
);
};

return (
<div
className="w-full h-screen flex flex-col"
Expand Down Expand Up @@ -427,67 +435,24 @@ export default function ConfigLibraryPage() {
)}
</div>
) : (
<>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4">
{filteredConfigs.map((configGroup) => (
<ConfigCard
key={configGroup.config_id}
configGroup={configGroup}
evaluationCount={
evaluationCounts[configGroup.config_id] || 0
}
onUseInEvaluation={handleUseInEvaluation}
/>
))}
</div>
{hasMoreConfigs && !searchQuery && (
<div className="flex justify-center mt-6">
<button
onClick={loadMoreConfigs}
disabled={isLoadingMore}
className="px-6 py-2.5 rounded-md text-sm font-medium transition-colors flex items-center gap-2"
style={{
backgroundColor: colors.bg.primary,
border: `1px solid ${colors.border}`,
color: colors.text.secondary,
}}
onMouseEnter={(e) => {
if (!isLoadingMore) {
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;
}}
>
{isLoadingMore ? (
<>
<svg
className="w-4 h-4 animate-spin"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
Loading...
</>
) : (
"Load More"
)}
</button>
<div
className="grid gap-4 items-start"
style={{ gridTemplateColumns: `repeat(${columnCount}, 1fr)` }}
>
{columns.map((col, colIdx) => (
<div key={colIdx} className="flex flex-col gap-4">
{col.map((config) => (
<ConfigCard
key={config.id}
config={config}
evaluationCount={evaluationCounts[config.id] || 0}
onLoadVersions={loadVersionsForConfig}
onLoadSingleVersion={loadSingleVersion}
/>
))}
</div>
)}
</>
))}
</div>
)}
</div>
</div>
Expand Down
54 changes: 20 additions & 34 deletions app/(routes)/configurations/prompt-editor/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -15,7 +10,6 @@
import { colors } from "@/app/lib/colors";
import { ConfigBlob, Tool } from "./types";
import { hasConfigChanges } from "./utils";
import { ConfigCreate, ConfigVersionCreate } from "@/app/lib/configTypes";
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 @@ -26,7 +20,12 @@
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";
Expand All @@ -36,20 +35,16 @@
const searchParams = useSearchParams();
const { sidebarCollapsed, setSidebarCollapsed } = 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 = {

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

View workflow job for this annotation

GitHub Actions / lint-and-build

The 'defaultConfig' object makes the dependencies of useEffect Hook (at line 212) change on every render. To fix this, wrap the initialization of 'defaultConfig' in its own useMemo() Hook

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

View workflow job for this annotation

GitHub Actions / lint-and-build

The 'defaultConfig' object makes the dependencies of useCallback Hook (at line 164) change on every render. To fix this, wrap the initialization of 'defaultConfig' in its own useMemo() Hook
completion: {
provider: "openai",
type: "text",
Expand All @@ -76,20 +71,9 @@
const initialLoadComplete = !isLoading;
const editorInitialized = React.useRef(false);
const [editorReady, setEditorReady] = useState<boolean>(!urlConfigId);

const [stableVersionItemsMap, setStableVersionItemsMap] = useState<
Record<string, import("@/app/lib/types/configs").ConfigVersionItems[]>
Record<string, ConfigVersionItems[]>
>({});

useEffect(() => {
if (Object.keys(hookVersionItemsMap).length > 0) {
setStableVersionItemsMap((prev) => ({ ...prev, ...hookVersionItemsMap }));
}
}, [hookVersionItemsMap]);

const versionItemsMap = stableVersionItemsMap;

// Current working state
const [currentContent, setCurrentContent] = useState<string>(
"You are a helpful AI assistant.\nYou provide clear and concise answers.\nYou are polite and professional.",
);
Expand All @@ -106,20 +90,22 @@
const [expandedConfigs, setExpandedConfigs] = useState<Set<string>>(
new Set(),
);

// UI state
const [hasUnsavedChanges, setHasUnsavedChanges] = useState<boolean>(false);
const [commitMessage, setCommitMessage] = useState<string>("");
const [showHistorySidebar, setShowHistorySidebar] = useState<boolean>(true); // Default open, or from URL param
const [showConfigPane, setShowConfigPane] = useState<boolean>(true); // Config pane collapse state

// History viewing state
const [showHistorySidebar, setShowHistorySidebar] = useState<boolean>(true);
const [showConfigPane, setShowConfigPane] = useState<boolean>(true);
const [selectedVersion, setSelectedVersion] = useState<SavedConfig | null>(
null,
);
const [compareWith, setCompareWith] = useState<SavedConfig | null>(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(
Expand Down Expand Up @@ -152,7 +138,7 @@
);
if (selectInHistory) setSelectedVersion(config);
},
[],

Check warning on line 141 in app/(routes)/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)
Expand All @@ -175,7 +161,7 @@
loadVersionsForConfig(config.config_id);
applyConfig(config);
},
[applyConfig, loadVersionsForConfig],
[applyConfig, loadVersionsForConfig, defaultConfig],
);

// Initialize editor from URL params — runs once, on first load completion
Expand Down Expand Up @@ -232,6 +218,7 @@
loadVersionsForConfig,
loadSingleVersion,
applyConfig,
defaultConfig,
]);

// Re-populate version items when missing (e.g. after background cache revalidation wipes versionItemsCache)
Expand Down Expand Up @@ -288,7 +275,7 @@
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;
Expand Down Expand Up @@ -494,7 +481,6 @@
loadSingleVersionForConfig={loadSingleVersion}
/>

{/* Show DiffView only when comparing versions (sidebar open + version selected) */}
{showHistorySidebar && selectedVersion ? (
<DiffView
selectedCommit={selectedVersion}
Expand Down
Loading
Loading