Files
Khalid Abdi ddf4b49d82 fix: chat works with any configured provider + surfaces errors
The chat defaulted to a Claude model, so a user who saved only a Gemini key got
a silent failure (backend derived Anthropic, found no key, errored — and the UI
showed nothing).

- Backend: resolveModel now falls back to whichever provider actually has a key
  (preferring the configured one), so a Gemini key just works regardless of the
  picked model. Clear 400 if no provider is configured at all.
- Frontend: the chat seeds its model/effort from the saved AI config, and now
  surfaces request failures as both a persistent alert banner and a toast —
  never silent.
- Settings: switching provider auto-selects that provider's default model.
- Refreshed the model catalog to current ids (Gemini 2.5 Pro/Flash + 2.0 Flash;
  dropped the retired gemini-1.5-pro); per-provider default model updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 19:27:37 +03:00

105 lines
3.0 KiB
TypeScript

// Catalog of models the chat can run, shared by the chat-input model picker,
// the Settings → AI panel, and the chat→backend wiring. The actual provider
// call is made by the backend (`backend/src/services/ai/provider.ts`); here we
// only describe the menu of choices. `id` is the provider's model id sent to
// the backend; `descriptionKey` is an i18n key under `chat.input.models.*`.
export type Effort = "low" | "medium" | "high";
export const EFFORT_LEVELS: Effort[] = ["low", "medium", "high"];
export const DEFAULT_EFFORT: Effort = "medium";
// The three supported cloud providers (API key) plus local Ollama.
export type AiProvider = "anthropic" | "openai" | "gemini" | "ollama";
export type AiModel = {
/** Provider model id passed to the backend. */
id: string;
provider: AiProvider;
/** Short display name shown in the picker trigger. */
label: string;
/** i18n key for the one-line description under the label. */
descriptionKey: string;
/** Shown directly in the model list; non-primary live under "More models". */
primary?: boolean;
/** Whether the Effort control applies to this model. */
supportsEffort?: boolean;
};
// Curated catalog. Cloud model ids are best-effort current ids and can be
// adjusted without touching the UI. Ollama is a stand-in for "your local model"
// — the concrete tag is configured in Settings → AI.
export const AI_MODELS: AiModel[] = [
{
id: "claude-opus-4-8",
provider: "anthropic",
label: "Opus 4.8",
descriptionKey: "chat.input.models.opus48",
primary: true,
supportsEffort: true,
},
{
id: "claude-sonnet-4-6",
provider: "anthropic",
label: "Sonnet 4.6",
descriptionKey: "chat.input.models.sonnet46",
primary: true,
supportsEffort: true,
},
{
id: "claude-haiku-4-5",
provider: "anthropic",
label: "Haiku 4.5",
descriptionKey: "chat.input.models.haiku45",
},
{
id: "gemini-2.5-pro",
provider: "gemini",
label: "Gemini 2.5 Pro",
descriptionKey: "chat.input.models.gemini25Pro",
primary: true,
supportsEffort: true,
},
{
id: "gemini-2.5-flash",
provider: "gemini",
label: "Gemini 2.5 Flash",
descriptionKey: "chat.input.models.gemini25Flash",
primary: true,
},
{
id: "gemini-2.0-flash",
provider: "gemini",
label: "Gemini 2.0 Flash",
descriptionKey: "chat.input.models.gemini20Flash",
},
{
id: "gpt-4o",
provider: "openai",
label: "GPT-4o",
descriptionKey: "chat.input.models.gpt4o",
supportsEffort: true,
},
{
id: "gpt-4o-mini",
provider: "openai",
label: "GPT-4o mini",
descriptionKey: "chat.input.models.gpt4oMini",
},
{
id: "ollama",
provider: "ollama",
label: "Local (Ollama)",
descriptionKey: "chat.input.models.ollama",
},
];
export const DEFAULT_MODEL_ID = AI_MODELS[0].id;
export function getModel(id: string): AiModel | undefined {
return AI_MODELS.find((m) => m.id === id);
}
export const PRIMARY_MODELS = AI_MODELS.filter((m) => m.primary);
export const MORE_MODELS = AI_MODELS.filter((m) => !m.primary);