From 75001e78f1f2a65b7235ca633f849ca6b8b949c3 Mon Sep 17 00:00:00 2001
From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com>
Date: Mon, 29 Jun 2026 17:23:16 +0530
Subject: [PATCH 1/6] feat(config): add effort field in the config form
---
.../prompt-editor/ConfigEditorPane.tsx | 33 +++++++++++++++++--
app/components/ui/Select.tsx | 2 +-
app/lib/constants.ts | 11 +++++++
app/lib/types/configs.ts | 11 ++-----
4 files changed, 46 insertions(+), 11 deletions(-)
diff --git a/app/components/prompt-editor/ConfigEditorPane.tsx b/app/components/prompt-editor/ConfigEditorPane.tsx
index a09788d2..d3eb2f65 100644
--- a/app/components/prompt-editor/ConfigEditorPane.tsx
+++ b/app/components/prompt-editor/ConfigEditorPane.tsx
@@ -13,13 +13,18 @@ import {
getModelsForType,
isGpt5Model,
} from "@/app/lib/models";
-import { PROVIDER_TYPES, PROVIDES_OPTIONS } from "@/app/lib/constants";
+import {
+ DEFAULT_EFFORT,
+ EFFORT_OPTIONS,
+ PROVIDER_TYPES,
+ PROVIDES_OPTIONS,
+} from "@/app/lib/constants";
import GuardrailsSection from "./GuardrailsSection";
import SaveConfigModal from "./SaveConfigModal";
import LoadConfigDropdown from "./LoadConfigDropdown";
import ConfigNameSection from "./ConfigNameSection";
import ToolsSection from "./ToolsSection";
-import { Button } from "@/app/components/ui";
+import { Button, Select } from "@/app/components/ui";
const inputClass =
"w-full px-3 py-2 rounded-md text-sm focus:outline-none border border-border bg-bg-primary text-text-primary";
@@ -165,6 +170,19 @@ export default function ConfigEditorPane({
});
};
+ const handleEffortChange = (effort: string) => {
+ onConfigChange({
+ ...configBlob,
+ completion: {
+ ...configBlob.completion,
+ params: {
+ ...params,
+ effort: effort as CompletionConfig["params"]["effort"],
+ },
+ },
+ });
+ };
+
const handleAddTool = () => {
const newTools = [
...tools,
@@ -323,6 +341,17 @@ export default function ConfigEditorPane({
)}
+
+
+
+
{placeholder && }
diff --git a/app/lib/constants.ts b/app/lib/constants.ts
index 8f86fee4..638c2c7c 100644
--- a/app/lib/constants.ts
+++ b/app/lib/constants.ts
@@ -136,6 +136,16 @@ export const MODEL_OPTIONS = {
google: [{ value: "gemini-2.5-pro", label: "Gemini 2.5 Pro" }],
};
+export const EFFORT_OPTIONS: { value: string; label: string }[] = [
+ { value: "none", label: "None" },
+ { value: "low", label: "Low" },
+ { value: "medium", label: "Medium" },
+ { value: "high", label: "High" },
+ { value: "xhigh", label: "Extra high" },
+];
+
+export const DEFAULT_EFFORT = "medium";
+
export const DEFAULT_CONFIG: ConfigBlob = {
completion: {
provider: "openai",
@@ -144,6 +154,7 @@ export const DEFAULT_CONFIG: ConfigBlob = {
model: "gpt-4o-mini",
instructions: "",
temperature: 0.7,
+ effort: "medium",
tools: [],
},
},
diff --git a/app/lib/types/configs.ts b/app/lib/types/configs.ts
index 9bb78496..7db2b472 100644
--- a/app/lib/types/configs.ts
+++ b/app/lib/types/configs.ts
@@ -25,20 +25,16 @@ export interface ConfigGroup {
versions: SavedConfig[]; // fully-loaded entries (have config_blob)
latestVersion: SavedConfig;
totalVersions: number;
- /** True once all historical versions have been fetched (lazy-loaded on demand) */
versionsFullyLoaded: boolean;
- /** Lightweight version list (no config_blob). Populated after initial load; used for history display. */
versionItems: ConfigVersionItems[];
}
export interface ConfigCache {
configs: SavedConfig[];
- // Map of config_id -> { updated_at, version_count }
configMeta: Record;
cachedAt: number;
versionCounts: Record;
totalConfigCount?: number;
- /** True when only a subset of configs have had their version details fetched */
partialFetch?: boolean;
allConfigMeta?: ConfigPublic[];
}
@@ -51,20 +47,20 @@ export interface FetchResult {
partialFetch: boolean;
}
-// Config Blob Structure
export interface Tool {
type: "file_search";
knowledge_base_ids: string[];
max_num_results: number;
}
+export type Effort = "none" | "low" | "medium" | "high" | "xhigh";
+
export interface CompletionParams {
model: string;
instructions: string;
temperature?: number;
- // tools array for UI, but backend expects flattened fields
+ effort?: Effort;
tools?: Tool[];
- // Backend expects these as direct fields (flattened from tools array)
knowledge_base_ids?: string[];
max_num_results?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -90,7 +86,6 @@ export interface ConfigBlob {
output_guardrails?: GuardrailRef[];
}
-// Request Types
export interface ConfigCreate {
name: string;
description?: string | null;
From 2645330d09d2f8fc50f3384f8c9384131e738cb8 Mon Sep 17 00:00:00 2001
From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com>
Date: Mon, 29 Jun 2026 20:04:51 +0530
Subject: [PATCH 2/6] fix(*): show the effort in the ui
---
app/components/ConfigCard.tsx | 3 ++
app/components/ConfigSelector.tsx | 2 +-
.../configurations/SelectedConfigPreview.tsx | 2 ++
.../prompt-editor/ConfigEditorPane.tsx | 28 +++++++++++--------
app/components/ui/ConfigModal.tsx | 8 ++++++
app/lib/constants.ts | 1 -
app/lib/types/configs.ts | 1 +
app/lib/utils.ts | 1 +
8 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/app/components/ConfigCard.tsx b/app/components/ConfigCard.tsx
index 87197888..8abfcce6 100644
--- a/app/components/ConfigCard.tsx
+++ b/app/components/ConfigCard.tsx
@@ -267,6 +267,9 @@ export default function ConfigCard({
value={latestVersion.temperature.toFixed(2)}
/>
)}
+ {latestVersion.effort && (
+
+ )}
{latestVersion.tools && latestVersion.tools.length > 0 && (
diff --git a/app/components/ConfigSelector.tsx b/app/components/ConfigSelector.tsx
index 6844c5bd..4dbb3efe 100644
--- a/app/components/ConfigSelector.tsx
+++ b/app/components/ConfigSelector.tsx
@@ -248,7 +248,7 @@ export default function ConfigSelector({
return (
{full
- ? `${full.provider}/${full.modelName} ${full.temperature !== undefined ? `• T:${full.temperature.toFixed(2)}` : ""} • ${formatRelativeTime(item.inserted_at)}`
+ ? `${full.provider}/${full.modelName} ${full.temperature !== undefined ? `• T:${full.temperature.toFixed(2)}` : ""}${full.effort ? ` • Effort:${full.effort}` : ""} • ${formatRelativeTime(item.inserted_at)}`
: formatRelativeTime(item.inserted_at)}
);
diff --git a/app/components/configurations/SelectedConfigPreview.tsx b/app/components/configurations/SelectedConfigPreview.tsx
index f7014561..1e7830ea 100644
--- a/app/components/configurations/SelectedConfigPreview.tsx
+++ b/app/components/configurations/SelectedConfigPreview.tsx
@@ -42,6 +42,8 @@ export default function SelectedConfigPreview({
/>
)}
+ {config.effort && }
+
{config.tools && config.tools.length > 0 && (
<>
diff --git a/app/components/prompt-editor/ConfigEditorPane.tsx b/app/components/prompt-editor/ConfigEditorPane.tsx
index d3eb2f65..99542b81 100644
--- a/app/components/prompt-editor/ConfigEditorPane.tsx
+++ b/app/components/prompt-editor/ConfigEditorPane.tsx
@@ -151,11 +151,15 @@ export default function ConfigEditorPane({
};
const handleModelChange = (model: string) => {
+ const { effort: _effort, ...rest } = params;
+ const nextParams = isGpt5Model(model)
+ ? { ...rest, model, effort: params.effort ?? DEFAULT_EFFORT }
+ : { ...rest, model };
onConfigChange({
...configBlob,
completion: {
...configBlob.completion,
- params: { ...params, model },
+ params: nextParams,
},
});
};
@@ -341,16 +345,18 @@ export default function ConfigEditorPane({
)}
-
-
-
+ {isGpt5 && (
+
+
+
+ )}
)}
+ {configVersionInfo?.effort && (
+
+ {configVersionInfo.effort}
+
+ )}
+
{configVersionInfo?.knowledge_base_ids &&
configVersionInfo.knowledge_base_ids.length > 0 && (
diff --git a/app/lib/constants.ts b/app/lib/constants.ts
index 638c2c7c..0eb363d9 100644
--- a/app/lib/constants.ts
+++ b/app/lib/constants.ts
@@ -154,7 +154,6 @@ export const DEFAULT_CONFIG: ConfigBlob = {
model: "gpt-4o-mini",
instructions: "",
temperature: 0.7,
- effort: "medium",
tools: [],
},
},
diff --git a/app/lib/types/configs.ts b/app/lib/types/configs.ts
index 7db2b472..f9266f23 100644
--- a/app/lib/types/configs.ts
+++ b/app/lib/types/configs.ts
@@ -11,6 +11,7 @@ export interface SavedConfig {
provider: string;
type: "text" | "stt" | "tts";
temperature: number;
+ effort?: Effort;
vectorStoreIds: string;
tools?: Tool[];
commit_message?: string | null;
diff --git a/app/lib/utils.ts b/app/lib/utils.ts
index 1ac04eb2..e67e469b 100644
--- a/app/lib/utils.ts
+++ b/app/lib/utils.ts
@@ -127,6 +127,7 @@ export const flattenConfigVersion = (
temperature: isGpt5Model(params.model)
? params.temperature
: (params.temperature ?? 0.7),
+ effort: isGpt5Model(params.model) ? params.effort : undefined,
vectorStoreIds: tools[0]?.knowledge_base_ids?.[0] || "",
tools,
commit_message: version.commit_message,
From 212bc9a14ac4e27687f9c1570b517c299eeb7ea4 Mon Sep 17 00:00:00 2001
From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com>
Date: Mon, 29 Jun 2026 23:21:25 +0530
Subject: [PATCH 3/6] fix(*): added the effort in the api payload
---
app/hooks/useConfigPersistence.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/app/hooks/useConfigPersistence.ts b/app/hooks/useConfigPersistence.ts
index cb856a2b..bc80bd66 100644
--- a/app/hooks/useConfigPersistence.ts
+++ b/app/hooks/useConfigPersistence.ts
@@ -10,6 +10,7 @@ import {
ConfigPublic,
} from "@/app/lib/types/configs";
import { ConfigBlob } from "@/app/lib/types/promptEditor";
+import { DEFAULT_EFFORT } from "@/app/lib/constants";
interface UseConfigPersistenceArgs {
allConfigMeta: ConfigPublic[];
@@ -95,6 +96,10 @@ export function useConfigPersistence({
...(!gpt5 && {
temperature: currentConfigBlob.completion.params.temperature,
}),
+ ...(gpt5 && {
+ effort:
+ currentConfigBlob.completion.params.effort ?? DEFAULT_EFFORT,
+ }),
...(allKnowledgeBaseIds.length > 0 && {
knowledge_base_ids: allKnowledgeBaseIds,
...(!gpt5 && { max_num_results: maxNumResults }),
From 2bed68e590fc9cc38d87ce102dfd2ac9e2d8812b Mon Sep 17 00:00:00 2001
From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com>
Date: Mon, 29 Jun 2026 23:31:38 +0530
Subject: [PATCH 4/6] fix(*): few more cleanups
---
app/components/ConfigCard.tsx | 7 +++++--
app/components/ConfigSelector.tsx | 6 ++----
.../configurations/SelectedConfigPreview.tsx | 5 ++++-
app/components/ui/ConfigModal.tsx | 17 +++--------------
app/lib/types/configs.ts | 13 +++++++++++++
app/lib/utils.ts | 7 ++++++-
6 files changed, 33 insertions(+), 22 deletions(-)
diff --git a/app/components/ConfigCard.tsx b/app/components/ConfigCard.tsx
index 8abfcce6..95fd623f 100644
--- a/app/components/ConfigCard.tsx
+++ b/app/components/ConfigCard.tsx
@@ -8,7 +8,7 @@
import { useState, useCallback } from "react";
import { useRouter } from "next/navigation";
import { SavedConfig, ConfigPublic } from "@/app/lib/types/configs";
-import { formatRelativeTime } from "@/app/lib/utils";
+import { formatRelativeTime, getEffortLabel } from "@/app/lib/utils";
import {
CheckIcon,
CopyIcon,
@@ -268,7 +268,10 @@ export default function ConfigCard({
/>
)}
{latestVersion.effort && (
-
+
)}
diff --git a/app/components/ConfigSelector.tsx b/app/components/ConfigSelector.tsx
index 4dbb3efe..75b1ed28 100644
--- a/app/components/ConfigSelector.tsx
+++ b/app/components/ConfigSelector.tsx
@@ -18,16 +18,14 @@ import {
GearIcon,
CheckIcon,
} from "@/app/components/icons";
-import { formatRelativeTime } from "@/app/lib/utils";
+import { formatRelativeTime, getEffortLabel } from "@/app/lib/utils";
interface ConfigSelectorProps {
selectedConfigId: string;
selectedVersion: number;
onConfigSelect: (configId: string, version: number) => void;
disabled?: boolean;
- /** Compact mode: no outer card, smaller heading — for use inside panels */
compact?: boolean;
- // Context to preserve when navigating to Prompt Editor
datasetId?: string;
experimentName?: string;
}
@@ -248,7 +246,7 @@ export default function ConfigSelector({
return (
{full
- ? `${full.provider}/${full.modelName} ${full.temperature !== undefined ? `• T:${full.temperature.toFixed(2)}` : ""}${full.effort ? ` • Effort:${full.effort}` : ""} • ${formatRelativeTime(item.inserted_at)}`
+ ? `${full.provider}/${full.modelName} ${full.temperature !== undefined ? `• T:${full.temperature.toFixed(2)}` : ""}${full.effort ? ` • Effort:${getEffortLabel(full.effort)}` : ""} • ${formatRelativeTime(item.inserted_at)}`
: formatRelativeTime(item.inserted_at)}
);
diff --git a/app/components/configurations/SelectedConfigPreview.tsx b/app/components/configurations/SelectedConfigPreview.tsx
index 1e7830ea..59a7e6f8 100644
--- a/app/components/configurations/SelectedConfigPreview.tsx
+++ b/app/components/configurations/SelectedConfigPreview.tsx
@@ -3,6 +3,7 @@
import { useLayoutEffect, useRef, useState } from "react";
import { ChevronUpIcon, ChevronDownIcon } from "@/app/components/icons";
import { SavedConfig } from "@/app/lib/types/configs";
+import { getEffortLabel } from "@/app/lib/utils";
interface SelectedConfigPreviewProps {
config: SavedConfig;
@@ -42,7 +43,9 @@ export default function SelectedConfigPreview({
/>
)}
- {config.effort && }
+ {config.effort && (
+
+ )}
{config.tools && config.tools.length > 0 && (
<>
diff --git a/app/components/ui/ConfigModal.tsx b/app/components/ui/ConfigModal.tsx
index ba7c0041..996a7c28 100644
--- a/app/components/ui/ConfigModal.tsx
+++ b/app/components/ui/ConfigModal.tsx
@@ -17,8 +17,10 @@ import {
Tool,
ConfigVersionPublic,
CompletionParams,
+ ConfigVersionInfo,
} from "@/app/lib/types/configs";
import Loader from "@/app/components/ui/Loader";
+import { getEffortLabel } from "@/app/lib/utils";
interface ConfigModalProps {
isOpen: boolean;
@@ -27,19 +29,6 @@ interface ConfigModalProps {
assistantConfig?: AssistantConfig | null;
}
-interface ConfigVersionInfo {
- name: string;
- version: number;
- model?: string;
- instructions?: string;
- temperature?: number;
- effort?: string;
- tools?: Tool[];
- provider?: string;
- type?: "text" | "stt" | "tts";
- knowledge_base_ids?: string[];
-}
-
const ConfigField = ({
label,
children,
@@ -208,7 +197,7 @@ export default function ConfigModal({
{configVersionInfo?.effort && (
- {configVersionInfo.effort}
+ {getEffortLabel(configVersionInfo.effort)}
)}
diff --git a/app/lib/types/configs.ts b/app/lib/types/configs.ts
index f9266f23..cfa60f22 100644
--- a/app/lib/types/configs.ts
+++ b/app/lib/types/configs.ts
@@ -136,6 +136,19 @@ export interface ConfigVersionItems {
updated_at: string;
}
+export interface ConfigVersionInfo {
+ name: string;
+ version: number;
+ model?: string;
+ instructions?: string;
+ temperature?: number;
+ effort?: string;
+ tools?: Tool[];
+ provider?: string;
+ type?: "text" | "stt" | "tts";
+ knowledge_base_ids?: string[];
+}
+
export interface APIResponse {
success: boolean;
data: T | null;
diff --git a/app/lib/utils.ts b/app/lib/utils.ts
index e67e469b..8523f2f2 100644
--- a/app/lib/utils.ts
+++ b/app/lib/utils.ts
@@ -9,7 +9,7 @@ import {
} from "@/app/lib/types/configs";
import { SavedConfig, ConfigGroup } from "./types/configs";
import { isGpt5Model } from "@/app/lib/models";
-import { STORAGE_KEYS } from "@/app/lib/constants";
+import { EFFORT_OPTIONS, STORAGE_KEYS } from "@/app/lib/constants";
import { TraceScore } from "@/app/lib/types/evaluation";
export function timeAgo(dateStr: string): string {
@@ -259,3 +259,8 @@ export const formatCostUSD = (cost: number): string => {
}
return `$${cost.toFixed(2)}`;
};
+
+export const getEffortLabel = (value?: string): string => {
+ if (!value) return "";
+ return EFFORT_OPTIONS.find((o) => o.value === value)?.label ?? value;
+};
From 62fde2888ef78f24e8431e54f581c9182c5db6a2 Mon Sep 17 00:00:00 2001
From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com>
Date: Tue, 30 Jun 2026 12:27:46 +0530
Subject: [PATCH 5/6] fix(config): made the full config dynamic
---
.../configurations/prompt-editor/page.tsx | 8 +-
app/api/models/route.ts | 22 ++
app/components/ConfigCard.tsx | 25 +-
app/components/ConfigSelector.tsx | 13 +-
.../configurations/SelectedConfigPreview.tsx | 21 +-
.../prompt-editor/ConfigDiffPane.tsx | 25 +-
.../prompt-editor/ConfigEditorPane.tsx | 355 +++++++++---------
app/components/prompt-editor/DiffView.tsx | 19 +-
app/components/prompt-editor/ToolsSection.tsx | 6 +-
app/components/providers/Providers.tsx | 11 +-
app/components/ui/ConfigModal.tsx | 43 ++-
app/hooks/useConfigPersistence.ts | 27 +-
app/hooks/useModelSchemas.ts | 77 ++++
app/lib/chatClient.ts | 8 +-
app/lib/constants.ts | 10 -
app/lib/modelSchema.ts | 161 ++++++++
app/lib/models.ts | 76 ++--
app/lib/store/modelSchemaStore.ts | 30 ++
app/lib/types/configs.ts | 10 +-
app/lib/types/models.ts | 43 +++
app/lib/types/promptEditor.ts | 31 +-
app/lib/utils.ts | 53 ++-
22 files changed, 742 insertions(+), 332 deletions(-)
create mode 100644 app/api/models/route.ts
create mode 100644 app/hooks/useModelSchemas.ts
create mode 100644 app/lib/modelSchema.ts
create mode 100644 app/lib/store/modelSchemaStore.ts
create mode 100644 app/lib/types/models.ts
diff --git a/app/(main)/configurations/prompt-editor/page.tsx b/app/(main)/configurations/prompt-editor/page.tsx
index 7ecbe214..f60ddb54 100644
--- a/app/(main)/configurations/prompt-editor/page.tsx
+++ b/app/(main)/configurations/prompt-editor/page.tsx
@@ -64,7 +64,6 @@ function PromptEditorContent() {
useState("");
const [currentConfigVersion, setCurrentConfigVersion] = useState(0);
const [provider, setProvider] = useState("openai");
- const [temperature, setTemperature] = useState(0.7);
const [tools, setTools] = useState([]);
const [expandedConfigs, setExpandedConfigs] = useState>(
new Set(),
@@ -100,7 +99,7 @@ function PromptEditorContent() {
params: {
model: config.modelName,
instructions: config.instructions,
- temperature: config.temperature,
+ ...(config.modelParams ?? {}),
tools: config.tools || [],
},
},
@@ -112,7 +111,6 @@ function PromptEditorContent() {
}),
});
setProvider(config.provider);
- setTemperature(config.temperature);
setSelectedConfigId(config.id);
setCurrentConfigName(config.name);
setCurrentConfigParentId(config.config_id);
@@ -132,7 +130,6 @@ function PromptEditorContent() {
setCurrentContent("");
setCurrentConfigBlob(DEFAULT_CONFIG);
setProvider("openai");
- setTemperature(0.7);
setSelectedConfigId("");
setCurrentConfigName("");
setCurrentConfigParentId("");
@@ -221,7 +218,7 @@ function PromptEditorContent() {
params: {
model: selectedConfig.modelName,
instructions: selectedConfig.instructions,
- temperature: selectedConfig.temperature,
+ ...(selectedConfig.modelParams ?? {}),
tools: selectedConfig.tools || [],
},
},
@@ -232,7 +229,6 @@ function PromptEditorContent() {
currentContent,
currentConfigBlob,
provider,
- temperature,
tools,
savedConfigs,
]);
diff --git a/app/api/models/route.ts b/app/api/models/route.ts
new file mode 100644
index 00000000..15a970ab
--- /dev/null
+++ b/app/api/models/route.ts
@@ -0,0 +1,22 @@
+import { NextResponse } from "next/server";
+import { apiClient } from "@/app/lib/apiClient";
+
+export async function GET(request: Request) {
+ try {
+ const { searchParams } = new URL(request.url);
+ const skip = searchParams.get("skip") ?? "0";
+ const limit = searchParams.get("limit") ?? "100";
+ const endpoint = `/api/v1/models/grouped?skip=${skip}&limit=${limit}`;
+ const { status, data } = await apiClient(request, endpoint);
+ return NextResponse.json(data, { status });
+ } catch (error) {
+ return NextResponse.json(
+ {
+ success: false,
+ error: error instanceof Error ? error.message : String(error),
+ data: null,
+ },
+ { status: 500 },
+ );
+ }
+}
diff --git a/app/components/ConfigCard.tsx b/app/components/ConfigCard.tsx
index 95fd623f..49d84294 100644
--- a/app/components/ConfigCard.tsx
+++ b/app/components/ConfigCard.tsx
@@ -8,7 +8,8 @@
import { useState, useCallback } from "react";
import { useRouter } from "next/navigation";
import { SavedConfig, ConfigPublic } from "@/app/lib/types/configs";
-import { formatRelativeTime, getEffortLabel } from "@/app/lib/utils";
+import { formatModelParamValue, formatRelativeTime } from "@/app/lib/utils";
+import { getParamLabel } from "@/app/lib/modelSchema";
import {
CheckIcon,
CopyIcon,
@@ -261,18 +262,16 @@ export default function ConfigCard({
}
/>
- {latestVersion.temperature != null && (
-
- )}
- {latestVersion.effort && (
-
- )}
+ {latestVersion.modelParams &&
+ Object.entries(latestVersion.modelParams).map(
+ ([key, value]) => (
+
+ ),
+ )}
{latestVersion.tools && latestVersion.tools.length > 0 && (
diff --git a/app/components/ConfigSelector.tsx b/app/components/ConfigSelector.tsx
index 75b1ed28..23a596d3 100644
--- a/app/components/ConfigSelector.tsx
+++ b/app/components/ConfigSelector.tsx
@@ -18,7 +18,8 @@ import {
GearIcon,
CheckIcon,
} from "@/app/components/icons";
-import { formatRelativeTime, getEffortLabel } from "@/app/lib/utils";
+import { formatModelParamValue, formatRelativeTime } from "@/app/lib/utils";
+import { getParamLabel } from "@/app/lib/modelSchema";
interface ConfigSelectorProps {
selectedConfigId: string;
@@ -243,10 +244,18 @@ export default function ConfigSelector({
const full = configs.find(
(c) => c.config_id === item.config_id && c.version === item.version,
);
+ const paramsSnippet = full?.modelParams
+ ? Object.entries(full.modelParams)
+ .map(
+ ([key, value]) =>
+ `${getParamLabel(key)}:${formatModelParamValue(key, value)}`,
+ )
+ .join(" • ")
+ : "";
return (
{full
- ? `${full.provider}/${full.modelName} ${full.temperature !== undefined ? `• T:${full.temperature.toFixed(2)}` : ""}${full.effort ? ` • Effort:${getEffortLabel(full.effort)}` : ""} • ${formatRelativeTime(item.inserted_at)}`
+ ? `${full.provider}/${full.modelName}${paramsSnippet ? ` • ${paramsSnippet}` : ""} • ${formatRelativeTime(item.inserted_at)}`
: formatRelativeTime(item.inserted_at)}
);
diff --git a/app/components/configurations/SelectedConfigPreview.tsx b/app/components/configurations/SelectedConfigPreview.tsx
index 59a7e6f8..a5b9af30 100644
--- a/app/components/configurations/SelectedConfigPreview.tsx
+++ b/app/components/configurations/SelectedConfigPreview.tsx
@@ -3,7 +3,8 @@
import { useLayoutEffect, useRef, useState } from "react";
import { ChevronUpIcon, ChevronDownIcon } from "@/app/components/icons";
import { SavedConfig } from "@/app/lib/types/configs";
-import { getEffortLabel } from "@/app/lib/utils";
+import { formatModelParamValue } from "@/app/lib/utils";
+import { getParamLabel } from "@/app/lib/modelSchema";
interface SelectedConfigPreviewProps {
config: SavedConfig;
@@ -36,16 +37,14 @@ export default function SelectedConfigPreview({
value={`${config.provider}/${config.modelName}`}
/>
- {config.temperature !== undefined && (
-
- )}
-
- {config.effort && (
-
- )}
+ {config.modelParams &&
+ Object.entries(config.modelParams).map(([key, value]) => (
+
+ ))}
{config.tools && config.tools.length > 0 && (
<>
diff --git a/app/components/prompt-editor/ConfigDiffPane.tsx b/app/components/prompt-editor/ConfigDiffPane.tsx
index 3903a533..3290ef9d 100644
--- a/app/components/prompt-editor/ConfigDiffPane.tsx
+++ b/app/components/prompt-editor/ConfigDiffPane.tsx
@@ -1,5 +1,6 @@
import { VersionPill } from "@/app/components";
import { SavedConfig } from "@/app/lib/types/configs";
+import { getParamLabel } from "@/app/lib/modelSchema";
interface ConfigDiffPaneProps {
selectedCommit: SavedConfig;
@@ -37,14 +38,22 @@ export default function ConfigDiffPane({
});
}
- if (compareWith.temperature !== selectedCommit.temperature) {
- configDiffs.push({
- field: "Temperature",
- oldValue: compareWith.temperature,
- newValue: selectedCommit.temperature,
- changed: true,
- });
- }
+ const oldParams = compareWith.modelParams ?? {};
+ const newParams = selectedCommit.modelParams ?? {};
+ const allParamKeys = new Set([
+ ...Object.keys(oldParams),
+ ...Object.keys(newParams),
+ ]);
+ allParamKeys.forEach((key) => {
+ if (oldParams[key] !== newParams[key]) {
+ configDiffs.push({
+ field: getParamLabel(key),
+ oldValue: oldParams[key],
+ newValue: newParams[key],
+ changed: true,
+ });
+ }
+ });
const oldTools = compareWith.tools || [];
const newTools = selectedCommit.tools || [];
diff --git a/app/components/prompt-editor/ConfigEditorPane.tsx b/app/components/prompt-editor/ConfigEditorPane.tsx
index 99542b81..73397c62 100644
--- a/app/components/prompt-editor/ConfigEditorPane.tsx
+++ b/app/components/prompt-editor/ConfigEditorPane.tsx
@@ -1,24 +1,17 @@
import { useEffect, useRef, useState } from "react";
import { guardrailsFetch } from "@/app/lib/guardrailsClient";
-import { ConfigBlob, Tool } from "@/app/lib/types/promptEditor";
+import { ConfigEditorPaneProps, Tool } from "@/app/lib/types/promptEditor";
+import { CompletionConfig, CompletionParams } from "@/app/lib/types/configs";
+import { ConfigType, MODEL_OPTIONS, getModelsForType } from "@/app/lib/models";
+import { PROVIDER_TYPES } from "@/app/lib/constants";
import {
- ConfigPublic,
- SavedConfig,
- ConfigVersionItems,
- CompletionConfig,
-} from "@/app/lib/types/configs";
-import {
- ConfigType,
- MODEL_OPTIONS,
- getModelsForType,
- isGpt5Model,
-} from "@/app/lib/models";
-import {
- DEFAULT_EFFORT,
- EFFORT_OPTIONS,
- PROVIDER_TYPES,
- PROVIDES_OPTIONS,
-} from "@/app/lib/constants";
+ getAllProviders,
+ getModelSchema,
+ getParamLabel,
+ getProviderLabel,
+ reconcileParamsForModel,
+} from "@/app/lib/modelSchema";
+import { useModelSchemas } from "@/app/hooks/useModelSchemas";
import GuardrailsSection from "./GuardrailsSection";
import SaveConfigModal from "./SaveConfigModal";
import LoadConfigDropdown from "./LoadConfigDropdown";
@@ -28,30 +21,6 @@ import { Button, Select } from "@/app/components/ui";
const inputClass =
"w-full px-3 py-2 rounded-md text-sm focus:outline-none border border-border bg-bg-primary text-text-primary";
-interface ConfigEditorPaneProps {
- configBlob: ConfigBlob;
- onConfigChange: (blob: ConfigBlob) => void;
- configName: string;
- onConfigNameChange: (name: string) => void;
- savedConfigs: SavedConfig[];
- selectedConfigId: string;
- boundConfigId?: string;
- onRenameConfig?: (configId: string, newName: string) => Promise;
- onLoadConfig: (config: SavedConfig | null) => void;
- commitMessage: string;
- onCommitMessageChange: (message: string) => void;
- onSave: () => void;
- isSaving?: boolean;
- allConfigMeta?: ConfigPublic[];
- versionItemsMap?: Record;
- loadVersionsForConfig?: (config_id: string) => Promise;
- loadSingleVersion?: (
- config_id: string,
- version: number,
- ) => Promise;
- apiKey?: string;
-}
-
export default function ConfigEditorPane({
configBlob,
onConfigChange,
@@ -103,10 +72,21 @@ export default function ConfigEditorPane({
wasSavingRef.current = isSaving;
}, [isSaving, commitMessage]);
+ const { isLoaded: schemasLoaded } = useModelSchemas();
+
const provider = configBlob.completion.provider;
const params = configBlob.completion.params;
- const isGpt5 = isGpt5Model(params.model);
const tools = (params.tools || []) as Tool[];
+ const modelSchema = getModelSchema(provider, params.model);
+ const schemaParamEntries = modelSchema
+ ? Object.entries(modelSchema.config)
+ : [];
+ const acceptsTools =
+ !modelSchema || "max_output_tokens" in modelSchema.config;
+ const providerOptions = (schemasLoaded ? getAllProviders() : []).map((p) => ({
+ value: p,
+ label: getProviderLabel(p),
+ }));
const selectedConfig = savedConfigs.find((c) => c.id === selectedConfigId);
const isBoundToSavedConfig = !!boundConfigId;
@@ -116,119 +96,59 @@ export default function ConfigEditorPane({
const currentType = (configBlob.completion.type || "text") as ConfigType;
- const handleProviderChange = (newProvider: string) => {
- const candidates = getModelsForType(newProvider, currentType);
- const fallback = MODEL_OPTIONS[newProvider]?.[0]?.value ?? "";
- const nextModel = candidates[0]?.value ?? fallback;
+ const handleConfigChange = (patch: {
+ params?: Partial;
+ completion?: Partial;
+ }) => {
onConfigChange({
...configBlob,
completion: {
...configBlob.completion,
- provider: newProvider as CompletionConfig["provider"],
- params: { ...params, model: nextModel },
+ ...patch.completion,
+ params: { ...params, ...patch.params },
},
});
};
+ const handleProviderChange = (newProvider: string) => {
+ const candidates = getModelsForType(newProvider, currentType);
+ const fallback = MODEL_OPTIONS[newProvider]?.[0]?.value ?? "";
+ const nextModel = candidates[0]?.value ?? fallback;
+ handleConfigChange({
+ completion: { provider: newProvider as CompletionConfig["provider"] },
+ params: { model: nextModel },
+ });
+ };
+
const handleTypeChange = (newType: ConfigType) => {
const provider = configBlob.completion.provider;
const candidates = getModelsForType(provider, newType);
- const currentModel = params.model;
- const stillValid = candidates.some((m) => m.value === currentModel);
+ const stillValid = candidates.some((m) => m.value === params.model);
const nextModel = stillValid
- ? currentModel
+ ? params.model
: (candidates[0]?.value ??
MODEL_OPTIONS[provider]?.[0]?.value ??
- currentModel);
- onConfigChange({
- ...configBlob,
- completion: {
- ...configBlob.completion,
- type: newType,
- params: { ...params, model: nextModel },
- },
+ params.model);
+ handleConfigChange({
+ completion: { type: newType },
+ params: { model: nextModel },
});
};
const handleModelChange = (model: string) => {
- const { effort: _effort, ...rest } = params;
- const nextParams = isGpt5Model(model)
- ? { ...rest, model, effort: params.effort ?? DEFAULT_EFFORT }
- : { ...rest, model };
- onConfigChange({
- ...configBlob,
- completion: {
- ...configBlob.completion,
- params: nextParams,
- },
- });
- };
-
- const handleTemperatureChange = (temperature: number) => {
- onConfigChange({
- ...configBlob,
- completion: {
- ...configBlob.completion,
- params: { ...params, temperature },
- },
- });
- };
-
- const handleEffortChange = (effort: string) => {
- onConfigChange({
- ...configBlob,
- completion: {
- ...configBlob.completion,
- params: {
- ...params,
- effort: effort as CompletionConfig["params"]["effort"],
- },
- },
- });
- };
-
- const handleAddTool = () => {
- const newTools = [
- ...tools,
- {
- type: "file_search" as const,
- knowledge_base_ids: [""],
- max_num_results: 20,
- },
- ];
+ const nextSchema = getModelSchema(provider, model);
+ const nextSchemaParams = nextSchema
+ ? reconcileParamsForModel(provider, model, params)
+ : {};
+ const oldSchemaKeys = modelSchema ? Object.keys(modelSchema.config) : [];
+ const carryover = { ...params };
+ delete carryover.model;
+ oldSchemaKeys.forEach((k) => delete carryover[k]);
onConfigChange({
...configBlob,
completion: {
...configBlob.completion,
- params: { ...params, tools: newTools },
- },
- });
- };
-
- const handleRemoveTool = (index: number) => {
- const newTools = tools.filter((_, i) => i !== index);
- onConfigChange({
- ...configBlob,
- completion: {
- ...configBlob.completion,
- params: { ...params, tools: newTools },
- },
- });
- };
-
- const handleUpdateTool = (
- index: number,
- field: K,
- value: Tool[K],
- ) => {
- const newTools = tools.map((t, i) =>
- i === index ? { ...t, [field]: value } : t,
- );
- onConfigChange({
- ...configBlob,
- completion: {
- ...configBlob.completion,
- params: { ...params, tools: newTools },
+ params: { ...carryover, model, ...nextSchemaParams },
},
});
};
@@ -273,7 +193,7 @@ export default function ConfigEditorPane({
onChange={(e) => handleProviderChange(e.target.value)}
className={inputClass}
>
- {PROVIDES_OPTIONS.map((option) => (
+ {providerOptions.map((option) => (
@@ -322,48 +242,135 @@ export default function ConfigEditorPane({
- {!isGpt5 && (
-
-
-
- handleTemperatureChange(parseFloat(e.target.value))
- }
- className="w-full accent-accent-primary"
- />
-
- 0
- 2
-
-
- )}
-
- {isGpt5 && (
-
-
-
- )}
+ {schemaParamEntries.map(([key, spec]) => {
+ const value = params[key] ?? spec.default;
+ const setValue = (v: unknown) =>
+ handleConfigChange({ params: { [key]: v } });
+ const labelText = getParamLabel(key);
+ if (spec.type === "enum" && spec.options) {
+ return (
+
+
+
+ );
+ }
+ if (spec.type === "float" || spec.type === "int") {
+ const numeric = Number(value);
+ const min = spec.min ?? 0;
+ const max = spec.max ?? 1;
+ const step = spec.type === "int" ? 1 : 0.01;
+ return (
+
+
+
+ setValue(
+ spec.type === "int"
+ ? parseInt(e.target.value, 10)
+ : parseFloat(e.target.value),
+ )
+ }
+ className="w-full accent-accent-primary"
+ />
+
+ {min}
+ {max}
+
+
+ );
+ }
+ if (spec.type === "string") {
+ return (
+
+
+ setValue(e.target.value)}
+ className={inputClass}
+ />
+
+ );
+ }
+ if (spec.type === "boolean") {
+ return (
+
+ setValue(e.target.checked)}
+ />
+
+
+ );
+ }
+ return null;
+ })}
+ handleConfigChange({
+ params: {
+ tools: [
+ ...tools,
+ {
+ type: "file_search",
+ knowledge_base_ids: [""],
+ max_num_results: 20,
+ },
+ ],
+ },
+ })
+ }
+ onRemoveTool={(index) =>
+ handleConfigChange({
+ params: { tools: tools.filter((_, i) => i !== index) },
+ })
+ }
+ onUpdateTool={(index, field, value) =>
+ handleConfigChange({
+ params: {
+ tools: tools.map((t, i) =>
+ i === index ? { ...t, [field]: value } : t,
+ ),
+ },
+ })
+ }
/>
- {selectedCommit.temperature != null && (
-
- )}
+ {selectedCommit.modelParams &&
+ Object.entries(selectedCommit.modelParams).map(
+ ([key, value]) => (
+
+ ),
+ )}
{selectedCommit.tools && selectedCommit.tools.length > 0 && (
diff --git a/app/components/prompt-editor/ToolsSection.tsx b/app/components/prompt-editor/ToolsSection.tsx
index b528582d..4f27ef1e 100644
--- a/app/components/prompt-editor/ToolsSection.tsx
+++ b/app/components/prompt-editor/ToolsSection.tsx
@@ -7,7 +7,7 @@ import { Tool } from "@/app/lib/types/promptEditor";
interface ToolsSectionProps {
tools: Tool[];
- isGpt5: boolean;
+ showMaxResults: boolean;
onAddTool: () => void;
onRemoveTool: (index: number) => void;
onUpdateTool: (
@@ -19,7 +19,7 @@ interface ToolsSectionProps {
export default function ToolsSection({
tools,
- isGpt5,
+ showMaxResults,
onAddTool,
onRemoveTool,
onUpdateTool,
@@ -62,7 +62,7 @@ export default function ToolsSection({
/>
- {!isGpt5 && (
+ {showMaxResults && (