import { DEFAULT_OPENAI_MODEL } from "@nexus/shared"; import { InfoTooltip } from "~/components/ui/InfoTooltip.js"; import { INPUT_CLASS, LABEL_CLASS, PANEL_CLASS, PRIMARY_BUTTON_CLASS, SECONDARY_BUTTON_CLASS, RuntimeSecretCard, type Provider, type RuntimeSecretStatus, type SaveResult, type UrlParsedType, } from "./shared.js"; type AiProviderPanelProps = { provider: Provider; endpoint: string; model: string; apiVersion: string; urlPasteValue: string; urlParseError: boolean; urlParsedType: UrlParsedType; runtimeSecret: RuntimeSecretStatus; testResult: SaveResult | null; isSaving: boolean; isTesting: boolean; saved: boolean; onProviderChange: (provider: Provider) => void; onEndpointChange: (value: string) => void; onModelChange: (value: string) => void; onApiVersionChange: (value: string) => void; onUrlPaste: (value: string) => void; onSave: () => void; onTest: () => void; }; export function AiProviderPanel({ provider, endpoint, model, apiVersion, urlPasteValue, urlParseError, urlParsedType, runtimeSecret, testResult, isSaving, isTesting, saved, onProviderChange, onEndpointChange, onModelChange, onApiVersionChange, onUrlPaste, onSave, onTest, }: AiProviderPanelProps) { return (

AI Provider{" "}

{provider === "openai" ? "Use a standard OpenAI API key from platform.openai.com." : "Use a deployment on your own Azure OpenAI resource."}

{provider === "azure" ? ( <>

Paste a full completion URL to auto-fill all fields below:

onUrlPaste(event.target.value)} /> {urlParseError ? (

Could not parse URL; expected either a Chat Completions URL ( /openai/deployments/…/chat/completions) or a Responses API URL (/openai/responses).

) : null} {urlParsedType === "responses" ? (

Responses API URL detected. Endpoint and api-version were filled in, but the deployment or model name still has to be entered manually below.

) : null} {urlParsedType === "completions" ? (

All fields filled from URL.

) : null}
onEndpointChange(event.target.value)} />

Everything up to (not including) /openai/…

) : null}
onModelChange(event.target.value)} />

{provider === "azure" ? "The deployment name chosen when deploying the model in Azure." : `The model identifier, for example ${DEFAULT_OPENAI_MODEL} or gpt-5.4-pro.`}

{provider === "azure" ? (
onApiVersionChange(event.target.value)} />

The api-version query parameter from the endpoint URL. Default: 2025-01-01-preview

) : null} {testResult ? (
{testResult.ok ? ( Connection successful. AI summaries are ready to use. ) : (

Connection failed: {testResult.error}

)}
) : null}
{saved ? ( Saved! ) : null}
); } type GenerationSettingsPanelProps = { maxTokens: number; temperature: number; summaryPrompt: string; defaultSummaryPrompt: string; isSaving: boolean; saved: boolean; onMaxTokensChange: (value: number) => void; onTemperatureChange: (value: number) => void; onSummaryPromptChange: (value: string) => void; onSave: () => void; onResetSummaryPrompt: () => void; }; export function GenerationSettingsPanel({ maxTokens, temperature, summaryPrompt, defaultSummaryPrompt, isSaving, saved, onMaxTokensChange, onTemperatureChange, onSummaryPromptChange, onSave, onResetSummaryPrompt, }: GenerationSettingsPanelProps) { return (

Generation Settings{" "}

{maxTokens}
onMaxTokensChange(Number(event.target.value))} className="w-full accent-brand-600" />
500 {maxTokens < 1000 ? "⚠ May be empty for reasoning models (GPT-5, o1, o3)" : maxTokens <= 2000 ? "Recommended for reasoning models ✓" : maxTokens <= 4000 ? "High; allows longer bios" : "Very high"} 16000

Reasoning models consume tokens internally before writing output. Keep this at 2000 or above to avoid empty responses.

{temperature.toFixed(1)}
onTemperatureChange(Number(event.target.value))} className="w-full accent-brand-600" />
0 — deterministic {temperature <= 0.3 ? "Factual & consistent" : temperature <= 0.9 ? "Balanced" : temperature <= 1.0 ? "Default (1) — recommended ✓" : temperature <= 1.2 ? "Creative" : "Very creative / unpredictable"} 2 — creative

Some models only accept the default value of 1. If generation fails with a temperature error, the system retries automatically without it.

{summaryPrompt ? ( ) : null}