Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 43 additions & 33 deletions app/components/prompt-editor/ConfigEditorPane.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { useEffect, useRef, useState } from "react";
import { guardrailsFetch } from "@/app/lib/guardrailsClient";
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 {
CompletionConfig,
CompletionParams,
ProviderType,
} from "@/app/lib/types/configs";
import { ConfigType, getModelsForType } from "@/app/lib/models";
import { PROVIDER_TYPES } from "@/app/lib/constants";
import {
getAllProviders,
getCompletionTypesForProvider,
getModelSchema,
getParamLabel,
getProviderLabel,
Expand Down Expand Up @@ -87,6 +92,10 @@ export default function ConfigEditorPane({
value: p,
label: getProviderLabel(p),
}));
const availableTypes = getCompletionTypesForProvider(provider);
const typeOptions = PROVIDER_TYPES.filter((t) =>
availableTypes.includes(t.value as ConfigType),
);

const selectedConfig = savedConfigs.find((c) => c.id === selectedConfigId);
const isBoundToSavedConfig = !!boundConfigId;
Expand All @@ -110,35 +119,14 @@ export default function ConfigEditorPane({
});
};

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 stillValid = candidates.some((m) => m.value === params.model);
const nextModel = stillValid
? params.model
: (candidates[0]?.value ??
MODEL_OPTIONS[provider]?.[0]?.value ??
params.model);
handleConfigChange({
completion: { type: newType },
params: { model: nextModel },
});
};

const handleModelChange = (model: string) => {
const nextSchema = getModelSchema(provider, model);
const applyProviderTypeModel = (
nextProvider: string,
nextType: ConfigType,
nextModel: string,
) => {
const nextSchema = getModelSchema(nextProvider, nextModel);
const nextSchemaParams = nextSchema
? reconcileParamsForModel(provider, model, params)
? reconcileParamsForModel(nextProvider, nextModel, params)
: {};
const oldSchemaKeys = modelSchema ? Object.keys(modelSchema.config) : [];
const carryover = { ...params };
Expand All @@ -148,11 +136,33 @@ export default function ConfigEditorPane({
...configBlob,
completion: {
...configBlob.completion,
params: { ...carryover, model, ...nextSchemaParams },
provider: nextProvider as ProviderType,
type: nextType,
params: { ...carryover, model: nextModel, ...nextSchemaParams },
},
});
};

const handleProviderChange = (newProvider: string) => {
const types = getCompletionTypesForProvider(newProvider);
const nextType = types.includes(currentType)
? currentType
: (types[0] ?? currentType);
const nextModel = getModelsForType(newProvider, nextType)[0]?.value ?? "";
applyProviderTypeModel(newProvider, nextType, nextModel);
};

const handleTypeChange = (newType: ConfigType) => {
const candidates = getModelsForType(provider, newType);
const stillValid = candidates.some((m) => m.value === params.model);
const nextModel = stillValid ? params.model : (candidates[0]?.value ?? "");
applyProviderTypeModel(provider, newType, nextModel);
};

const handleModelChange = (model: string) => {
applyProviderTypeModel(provider, currentType, model);
};

const saveDisabled = !configName.trim() || isSaving;

return (
Expand Down Expand Up @@ -212,14 +222,14 @@ export default function ConfigEditorPane({
}
className={inputClass}
>
{PROVIDER_TYPES.map((option) => (
{typeOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<p className="text-xs mt-1.5 text-text-secondary">
{PROVIDER_TYPES.find(
{typeOptions.find(
(t) => t.value === (configBlob.completion.type || "text"),
)?.description ?? ""}
</p>
Expand Down
13 changes: 10 additions & 3 deletions app/hooks/useConfigPersistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,17 @@ export function useConfigPersistence({
: {};

const acceptsTools = !schema || "max_output_tokens" in schema.config;
const completionType = currentConfigBlob.completion.type || "text";

const configBlob: ConfigBlob = {
completion: {
provider: completionProvider,
type: currentConfigBlob.completion.type || "text",
type: completionType,
params: {
model,
instructions: currentContent,
// TTS params forbid `instructions` on the backend; only send it
// for types that accept it (text, stt).
...(completionType !== "tts" && { instructions: currentContent }),
...modelParams,
...(allKnowledgeBaseIds.length > 0 && {
knowledge_base_ids: allKnowledgeBaseIds,
Expand Down Expand Up @@ -177,7 +180,11 @@ export function useConfigPersistence({
return true;
} catch (e) {
console.error("Failed to save config:", e);
toast.error("Failed to save configuration. Please try again.");
toast.error(
e instanceof Error && e.message
? `Failed to save configuration: ${e.message}`
: "Failed to save configuration. Please try again.",
);
return false;
} finally {
setIsSaving(false);
Expand Down
32 changes: 29 additions & 3 deletions app/lib/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,43 @@ export async function apiClient<
} as ApiClientResponse<TData, TResponseType>;
}

/** Parse an error body into a readable message string. */
function stringifyValidationDetail(value: unknown): string {
if (!Array.isArray(value)) {
return typeof value === "string" ? value : "";
}
return value
.map((item) => {
if (typeof item === "string") return item;
if (item && typeof item === "object") {
const rec = item as Record<string, unknown>;
const field = Array.isArray(rec.loc)
? rec.loc.filter((p) => p !== "body").join(".")
: (rec.field as string) || "";
const msg = (rec.msg as string) || (rec.message as string) || "";
return field ? `${field}: ${msg}` : msg;
}
return "";
})
.filter(Boolean)
.join("; ");
}

function extractErrorMessage(
body: Record<string, unknown>,
fallback: string,
): string {
const detail =
stringifyValidationDetail(body.errors) ||
(typeof body.detail === "string"
? ""
: stringifyValidationDetail(body.detail));
const msg =
(body.error as string) ||
(body.message as string) ||
(body.detail as string) ||
(typeof body.detail === "string" ? (body.detail as string) : "") ||
"";
return msg || fallback;
const combined = [msg, detail].filter(Boolean).join(" — ");
return combined || fallback;
Comment thread
Ayush8923 marked this conversation as resolved.
}

/** Dispatch the auth-expired event (client-side only). */
Expand Down
3 changes: 2 additions & 1 deletion app/lib/chatClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { apiFetch } from "@/app/lib/apiClient";
import {
CompletionParams,
ConfigBlob,
ProviderType,
SavedConfig,
Tool,
} from "@/app/lib/types/configs";
Expand Down Expand Up @@ -243,7 +244,7 @@ export function configToBlob(config: SavedConfig): ConfigBlob {

const blob: ConfigBlob = {
completion: {
provider: config.provider as "openai",
provider: config.provider as ProviderType,
type: config.type ?? "text",
params,
},
Expand Down
32 changes: 29 additions & 3 deletions app/lib/modelSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ export type {
RawModelEntry,
} from "@/app/lib/types/models";

export const SUPPORTED_PROVIDERS = ["openai", "google"] as const;

export const SUPPORTED_PARAMS = new Set(["effort", "temperature"]);
export const SUPPORTED_PROVIDERS = [
"openai",
"google",
"google-aistudio",
] as const;

export const SUPPORTED_PARAMS = new Set([
"effort",
"temperature",
"summary",
"voice",
"thinking_level",
]);

export function flattenGroupedModels(
grouped: Record<string, RawModelEntry[]>,
Expand Down Expand Up @@ -50,6 +60,7 @@ export function flattenGroupedModels(
export const PROVIDER_LABELS: Record<string, string> = {
openai: "OpenAI",
google: "Google",
"google-aistudio": "Google AI Studio",
};

export function getProviderLabel(provider: string): string {
Expand All @@ -59,6 +70,9 @@ export function getProviderLabel(provider: string): string {
export const PARAM_LABELS: Record<string, string> = {
effort: "Effort",
temperature: "Temperature",
summary: "Summary",
voice: "Voice",
thinking_level: "Thinking Level",
};

export const PARAM_VALUE_LABELS: Record<string, Record<string, string>> = {
Expand Down Expand Up @@ -106,6 +120,18 @@ export function getModelsForProviderAndType(
);
}

export function getCompletionTypesForProvider(
provider: string,
): ModelCompletionType[] {
return Array.from(
new Set(
schemas()
.filter((m) => m.provider === provider)
.flatMap((m) => m.completion_type),
),
);
}

export function getParamLabel(key: string): string {
return PARAM_LABELS[key] ?? key;
}
Expand Down
3 changes: 2 additions & 1 deletion app/lib/types/assessment/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
CompletionConfig,
ConfigPublic,
ConfigVersionItems,
ProviderType,
} from "@/app/lib/types/configs";
import type { LabeledValue, ValueSetter } from "./core";

Expand All @@ -29,7 +30,7 @@ export interface ConfigParamDefinition {
}

export interface AssessmentModelConfig {
provider: "openai" | "google" | "google-aistudio" | "anthropic";
provider: ProviderType;
model_name: string;
config: Record<string, ConfigParamDefinition>;
}
Expand Down
8 changes: 7 additions & 1 deletion app/lib/types/configs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { AssessmentTag } from "@/app/lib/types/assessment";

export type ProviderType =
| "openai"
| "google"
| "google-aistudio"
| "anthropic";

export interface SavedConfig {
id: string;
config_id: string;
Expand Down Expand Up @@ -66,7 +72,7 @@ export interface CompletionParams {
}

export interface CompletionConfig {
provider: "openai" | "google" | "google-aistudio" | "anthropic";
provider: ProviderType;
type?: "text" | "stt" | "tts";
params: CompletionParams;
}
Expand Down
Loading