90 lines
4.1 KiB
Markdown
90 lines
4.1 KiB
Markdown
# Fetch Models Design
|
|
|
|
**Date:** 2026-03-27
|
|
**Status:** Approved
|
|
|
|
## Summary
|
|
|
|
Add an "Aktualisieren" button to the Model setting in the settings tab. When clicked, it fetches the 3 newest Claude models from the Anthropic Models API and updates the dropdown. Falls back to the hardcoded `MODELS` list if the request fails or returns no models.
|
|
|
|
## ClaudeClient changes
|
|
|
|
Add a new method `fetchModels(apiKey: string): Promise<{id: string, name: string}[]>` to `ClaudeClient`.
|
|
|
|
- URL: `"https://api.anthropic.com/v1/models"` — inline string or a separate private constant; **do not use or modify `baseUrl`** (which points to `/v1/messages`)
|
|
- Call `requestUrl` with `throw: false` (same pattern as `streamChat`/`chat`) and `this.headers(apiKey)`
|
|
- Response shape: `{ data: [{ id: string, created: number, display_name: string, ... }] }`
|
|
- Throw on `response.status >= 400` with the response text
|
|
- If `data` is empty, throw an error ("No models returned") — do not return an empty array
|
|
- Sort `data` descending by `created`, take top 3
|
|
- Return `{ id, name: id }` for each — use `id` as the display name (not `display_name`). Note: fetched entries will show raw IDs (e.g. `claude-opus-4-6`) while hardcoded `MODELS` show human-friendly names (e.g. `"Claude Opus 4.6 (Stärkste)"`). This is intentional — keeps the implementation simple and avoids relying on API-provided display strings.
|
|
|
|
## SettingsTab changes
|
|
|
|
**Import addition:** Add `Notice, ButtonComponent, DropdownComponent` to the `import { ... } from "obsidian"` line.
|
|
|
|
Convert the existing "Modell" `Setting` to capture both the `DropdownComponent` and `ButtonComponent` references by chaining `addDropdown()` and `addButton()` on the same `Setting` instance:
|
|
|
|
```typescript
|
|
let modelDrop: DropdownComponent;
|
|
let refreshBtn: ButtonComponent;
|
|
|
|
new Setting(containerEl)
|
|
.setName("Modell")
|
|
.setDesc("Welches Claude-Modell verwenden?")
|
|
.addDropdown((drop) => {
|
|
modelDrop = drop;
|
|
for (const m of MODELS) drop.addOption(m.id, m.name);
|
|
drop.setValue(this.plugin.settings.model).onChange(async (value) => {
|
|
this.plugin.settings.model = value;
|
|
await this.plugin.saveSettings();
|
|
});
|
|
})
|
|
.addButton((btn) => {
|
|
refreshBtn = btn;
|
|
btn.setButtonText("Aktualisieren").onClick(async () => { /* see click flow */ });
|
|
});
|
|
```
|
|
|
|
**Click flow:**
|
|
1. Capture current value: `const prev = modelDrop.getValue()`
|
|
2. `refreshBtn.setDisabled(true)` and `refreshBtn.setButtonText("...")`
|
|
3. In a try/catch/finally:
|
|
- **try:** Call `this.plugin.claude.fetchModels(this.plugin.settings.apiKey)`
|
|
- On success: clear dropdown with `modelDrop.selectEl.empty()`, repopulate via `modelDrop.addOption(id, name)` for each fetched model, then set value to `prev` if it exists among the fetched ids, otherwise the first fetched id; save via `this.plugin.settings.model = modelDrop.getValue(); await this.plugin.saveSettings()`
|
|
- **catch:** `new Notice("Modelle konnten nicht geladen werden: " + err.message)` — dropdown is **not** modified on error (hardcoded options remain)
|
|
- **finally:** `refreshBtn.setDisabled(false)` and `refreshBtn.setButtonText("Aktualisieren")`
|
|
|
|
**Fallback:** The hardcoded `MODELS` array in `SettingsTab.ts` is unchanged and remains the initial population of the dropdown on every settings open.
|
|
|
|
## Data flow
|
|
|
|
```
|
|
[Aktualisieren button click]
|
|
→ capture prev = modelDrop.getValue()
|
|
→ disable button, show "..."
|
|
→ this.plugin.claude.fetchModels(apiKey) [throw: false, separate URL]
|
|
→ throw if status >= 400 or data empty
|
|
→ sort by created desc, take 3
|
|
→ return [{id, name: id}]
|
|
→ clear selectEl, repopulate, restore selection
|
|
→ save model to settings
|
|
→ finally: restore button
|
|
```
|
|
|
|
## Error handling
|
|
|
|
| Scenario | Behaviour |
|
|
|---|---|
|
|
| No API key (401) | Notice shown; dropdown unchanged |
|
|
| Network failure | Notice shown; dropdown unchanged |
|
|
| Empty `data` array | Treated as error; Notice shown; dropdown unchanged |
|
|
| Fewer than 3 models returned | Take all returned (no error) |
|
|
|
|
## Out of scope
|
|
|
|
- Persisting fetched models across restarts
|
|
- Auto-fetching on settings open or plugin startup
|
|
- Configurable count of models to show
|
|
- Updating `DEFAULT_SETTINGS.model` after a fetch
|