From c0078ce3393970edecbe74eafaa9696ad4d4c9ad Mon Sep 17 00:00:00 2001 From: muena Date: Wed, 20 May 2026 07:08:58 +0200 Subject: [PATCH] feat: derive AI model list from Raycast SDK enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate MODEL_OPTIONS at runtime from AI.Model (dedupe by value, drop @deprecated aliases), so the AI-view dropdowns show every model the installed @raycast/api ships — no more hand-maintained six-entry list. Switch the summaryModel preference from a static dropdown to a textfield holding a model ID. The discoverable picker lives in the AI views (now fully dynamic), and the preference just stores the chosen default. Drop duplicate MODEL_OPTIONS / CREATIVITY_OPTIONS constants from summarize-email.tsx and reply-email.tsx; both pull from ./ai now like the briefing and extract views already do. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 +- package.json | 33 ++++----------------------------- src/ai.ts | 26 ++++++++++++++++++-------- src/reply-email.tsx | 12 +----------- src/summarize-email.tsx | 12 +----------- 5 files changed, 25 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 7de9c1d..10b0d2a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Alle Werte sind in den Raycast-Einstellungen pro Extension einstellbar. **Verhalten:** - `Standard-Sitzungsmodus` — `Aktive Sitzung wiederverwenden` (Default), `Neue Sitzung pro Anfrage`, `Tagessitzung` - `Ausgabe der Schnellbefehle` — `In die Zwischenablage kopieren` (Default) oder `Am Cursor einfügen` -- `Standard-Modell für Zusammenfassungen` — Default-Raycast-KI-Modell für alle AI-Befehle (benötigt Raycast Pro); pro AI-View überschreibbar +- `Standard-Modell für AI-Befehle` — Modell-ID, die in den AI-Views vorausgewählt ist (Default `anthropic-claude-sonnet-4-6`, benötigt Raycast Pro). Die vollständige Modellliste leiten die Views dynamisch aus `AI.Model` der Raycast-API ab — sobald ein neues Modell verfügbar ist, taucht es ohne Code-Änderung im View-Dropdown auf. - `Eigener Name` — Default-Signatur für AI-generierte Email-Antworten, im Antwort-Befehl pro Aufruf überschreibbar - `Maximale Anzahl gespeicherter Sitzungen` — älteste werden geprunt (Default 20) - `Raycast nach Kopieren/Einfügen schließen` — Auto-Close und Pop-To-Root nach AI-Workflow-Abschluss (Default an) diff --git a/package.json b/package.json index 2d70941..08597a3 100644 --- a/package.json +++ b/package.json @@ -180,37 +180,12 @@ }, { "name": "summaryModel", - "title": "Standard-Modell für Zusammenfassungen", - "description": "Raycast-KI-Modell für „Email-Konversation zusammenfassen“. Benötigt Raycast Pro.", - "type": "dropdown", + "title": "Standard-Modell für AI-Befehle", + "description": "Raycast-KI-Modell-ID, vorausgewählt in den AI-Views. Die volle Modellliste ist dort als Dropdown verfügbar. Benötigt Raycast Pro.", + "type": "textfield", "required": true, "default": "anthropic-claude-sonnet-4-6", - "data": [ - { - "title": "Claude 4.6 Sonnet", - "value": "anthropic-claude-sonnet-4-6" - }, - { - "title": "Claude 4.7 Opus", - "value": "anthropic-claude-opus-4-7" - }, - { - "title": "Claude 4.5 Haiku", - "value": "anthropic-claude-4-5-haiku" - }, - { - "title": "OpenAI GPT-5.3 Instant", - "value": "openai-gpt-5.3-instant" - }, - { - "title": "OpenAI GPT-4.1", - "value": "openai-gpt-4.1" - }, - { - "title": "OpenAI GPT-4o mini", - "value": "openai-gpt-4o-mini" - } - ] + "placeholder": "anthropic-claude-sonnet-4-6" }, { "name": "maxSessions", diff --git a/src/ai.ts b/src/ai.ts index d7e2b9d..c42b95f 100644 --- a/src/ai.ts +++ b/src/ai.ts @@ -1,3 +1,5 @@ +import { AI } from "@raycast/api"; + export type Creativity = "none" | "low" | "medium" | "high"; export const CREATIVITY_OPTIONS: Creativity[] = [ @@ -7,14 +9,22 @@ export const CREATIVITY_OPTIONS: Creativity[] = [ "high", ]; -export const MODEL_OPTIONS: Array<{ value: string; title: string }> = [ - { value: "anthropic-claude-sonnet-4-6", title: "Claude 4.6 Sonnet" }, - { value: "anthropic-claude-opus-4-7", title: "Claude 4.7 Opus" }, - { value: "anthropic-claude-4-5-haiku", title: "Claude 4.5 Haiku" }, - { value: "openai-gpt-5.3-instant", title: "OpenAI GPT-5.3 Instant" }, - { value: "openai-gpt-4.1", title: "OpenAI GPT-4.1" }, - { value: "openai-gpt-4o-mini", title: "OpenAI GPT-4o mini" }, -]; +export type ModelOption = { value: string; title: string }; + +function buildModelOptions(): ModelOption[] { + const seen = new Set(); + const options: ModelOption[] = []; + for (const [key, value] of Object.entries(AI.Model) as Array< + [string, string] + >) { + if (seen.has(value)) continue; + seen.add(value); + options.push({ value, title: key.replace(/_/g, " ") }); + } + return options; +} + +export const MODEL_OPTIONS: ModelOption[] = buildModelOptions(); export const STRICT_PLACEHOLDER_RULE = [ "STRENGE REGEL: Gib jeden Platzhalter zeichengetreu (inklusive spitzer Klammern, Großbuchstaben und Unterstrich + Nummer) zurück.", diff --git a/src/reply-email.tsx b/src/reply-email.tsx index 5f4ec74..710c4f2 100644 --- a/src/reply-email.tsx +++ b/src/reply-email.tsx @@ -12,6 +12,7 @@ import { } from "@raycast/api"; import { getSelectedTextSafely } from "./selection"; import { useEffect, useMemo, useRef, useState } from "react"; +import { CREATIVITY_OPTIONS, MODEL_OPTIONS } from "./ai"; import { getPreferences } from "./preferences"; import { buildDisclosureContent, @@ -54,17 +55,6 @@ type FormValues = { instructions: string; }; -const CREATIVITY_OPTIONS: Creativity[] = ["none", "low", "medium", "high"]; - -const MODEL_OPTIONS: Array<{ value: string; title: string }> = [ - { value: "anthropic-claude-sonnet-4-6", title: "Claude 4.6 Sonnet" }, - { value: "anthropic-claude-opus-4-7", title: "Claude 4.7 Opus" }, - { value: "anthropic-claude-4-5-haiku", title: "Claude 4.5 Haiku" }, - { value: "openai-gpt-5.3-instant", title: "OpenAI GPT-5.3 Instant" }, - { value: "openai-gpt-4.1", title: "OpenAI GPT-4.1" }, - { value: "openai-gpt-4o-mini", title: "OpenAI GPT-4o mini" }, -]; - export default function Command() { const preferences = getPreferences(); const [text, setText] = useState(""); diff --git a/src/summarize-email.tsx b/src/summarize-email.tsx index b0cc752..027e7e4 100644 --- a/src/summarize-email.tsx +++ b/src/summarize-email.tsx @@ -23,6 +23,7 @@ import { setActiveSession, updateSessionMapping, } from "./sessions"; +import { CREATIVITY_OPTIONS, MODEL_OPTIONS } from "./ai"; import { buildSummaryPrompt, type Creativity } from "./summarize"; import type { EntityType, VelumSession } from "./types"; import { @@ -47,17 +48,6 @@ type FormValues = { instructions: string; }; -const CREATIVITY_OPTIONS: Creativity[] = ["none", "low", "medium", "high"]; - -const MODEL_OPTIONS: Array<{ value: string; title: string }> = [ - { value: "anthropic-claude-sonnet-4-6", title: "Claude 4.6 Sonnet" }, - { value: "anthropic-claude-opus-4-7", title: "Claude 4.7 Opus" }, - { value: "anthropic-claude-4-5-haiku", title: "Claude 4.5 Haiku" }, - { value: "openai-gpt-5.3-instant", title: "OpenAI GPT-5.3 Instant" }, - { value: "openai-gpt-4.1", title: "OpenAI GPT-4.1" }, - { value: "openai-gpt-4o-mini", title: "OpenAI GPT-4o mini" }, -]; - export default function Command() { const preferences = getPreferences(); const [text, setText] = useState("");