140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
import { PanelFrame } from "./PanelFrame";
|
|
import type { ConnectionState } from "../state/types";
|
|
|
|
interface ConnectionPanelProps {
|
|
value: ConnectionState;
|
|
modelOptions: string[];
|
|
modelsBusy: boolean;
|
|
onChange: (next: ConnectionState) => void;
|
|
onReloadModels: () => Promise<void> | void;
|
|
onTestConnection: () => Promise<void> | void;
|
|
onSaveLocalConfig: () => void;
|
|
lastStatus: string;
|
|
busy: boolean;
|
|
}
|
|
|
|
export function ConnectionPanel({
|
|
value,
|
|
modelOptions,
|
|
modelsBusy,
|
|
onChange,
|
|
onReloadModels,
|
|
onTestConnection,
|
|
onSaveLocalConfig,
|
|
lastStatus,
|
|
busy
|
|
}: ConnectionPanelProps) {
|
|
const isLocal = value.llmProvider === "local";
|
|
const modelInCatalog = modelOptions.includes(value.model);
|
|
|
|
return (
|
|
<PanelFrame
|
|
title="LLM Connection"
|
|
subtitle="Switch between OpenAI cloud and local OpenAI-compatible server."
|
|
actions={<span className="status-chip">{lastStatus || "Status: not checked"}</span>}
|
|
>
|
|
<div className="grid-two">
|
|
<label>
|
|
Provider
|
|
<select
|
|
value={value.llmProvider}
|
|
onChange={(event) => {
|
|
const nextProvider = event.target.value === "local" ? "local" : "openai";
|
|
onChange({
|
|
...value,
|
|
llmProvider: nextProvider,
|
|
baseUrl: nextProvider === "local" ? "http://127.0.0.1:1234/v1" : "https://api.openai.com/v1"
|
|
});
|
|
}}
|
|
>
|
|
<option value="openai">OpenAI (token)</option>
|
|
<option value="local">Local (LM Studio / OpenAI-compatible)</option>
|
|
</select>
|
|
</label>
|
|
|
|
<label>
|
|
Model
|
|
<select
|
|
value={modelInCatalog ? value.model : "__manual__"}
|
|
onChange={(event) => {
|
|
const selected = event.target.value;
|
|
if (selected === "__manual__") {
|
|
return;
|
|
}
|
|
onChange({ ...value, model: selected });
|
|
}}
|
|
>
|
|
<option value="__manual__">Manual input</option>
|
|
{modelOptions.map((modelId) => (
|
|
<option key={modelId} value={modelId}>
|
|
{modelId}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label>
|
|
Model ID (manual)
|
|
<input
|
|
value={value.model}
|
|
onChange={(event) => onChange({ ...value, model: event.target.value })}
|
|
placeholder="qwen2.5-14b-instruct or lmstudio loaded model id"
|
|
/>
|
|
</label>
|
|
|
|
{!isLocal ? (
|
|
<label className="full-width">
|
|
OpenAI API Key
|
|
<input
|
|
type="password"
|
|
value={value.apiKey}
|
|
onChange={(event) => onChange({ ...value, apiKey: event.target.value })}
|
|
placeholder="sk-..."
|
|
/>
|
|
</label>
|
|
) : null}
|
|
|
|
<label className={isLocal ? "full-width" : undefined}>
|
|
{isLocal ? "Local server base URL" : "Base URL"}
|
|
<input
|
|
value={value.baseUrl}
|
|
onChange={(event) => onChange({ ...value, baseUrl: event.target.value })}
|
|
placeholder={isLocal ? "http://127.0.0.1:1234/v1" : "https://api.openai.com/v1"}
|
|
/>
|
|
</label>
|
|
|
|
<label>
|
|
Temperature
|
|
<input
|
|
type="number"
|
|
step="0.1"
|
|
value={value.temperature}
|
|
onChange={(event) => onChange({ ...value, temperature: Number(event.target.value) })}
|
|
/>
|
|
</label>
|
|
|
|
<label>
|
|
Max output tokens
|
|
<input
|
|
type="number"
|
|
value={value.maxOutputTokens}
|
|
onChange={(event) => onChange({ ...value, maxOutputTokens: Number(event.target.value) })}
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="button-row">
|
|
<button type="button" onClick={() => onSaveLocalConfig()}>
|
|
Save local config
|
|
</button>
|
|
<button type="button" onClick={() => onReloadModels()} disabled={busy || modelsBusy}>
|
|
{modelsBusy ? "Loading models..." : "Load model list"}
|
|
</button>
|
|
<button type="button" onClick={() => onTestConnection()} disabled={busy}>
|
|
{busy ? "Checking..." : "Test connection"}
|
|
</button>
|
|
</div>
|
|
</PanelFrame>
|
|
);
|
|
}
|