Add link to models

This commit is contained in:
WiseGuru 2026-05-30 22:49:23 -07:00
parent 15304c758c
commit 5e98637c76
2 changed files with 45 additions and 3 deletions

View file

@ -107,7 +107,7 @@ The `PipelineSource` union has three variants: `audio` (recorded blob, optional
API keys are stored per profile on `EnvironmentProfile.transcriptionConfig.apiKey` / `llmConfig.apiKey`. Two slots per profile, one for transcription and one for the LLM. No global by-family map; the desktop and mobile profiles each carry their own keys even when both use the same provider (deliberate: per-profile keys make per-function usage tracking easier). Persistence is in [src/secrets.ts](src/secrets.ts) using the key IDs `profile:desktop:transcription`, `profile:desktop:llm`, `profile:mobile:transcription`, `profile:mobile:llm`.
Providers may optionally implement `listModels(config, signal)` returning a string array of model IDs the configured API key can access. Implemented by: OpenAI / Groq / Mistral (via `openai.ts` shared adapter), Mistral Voxtral (own adapter, filters Mistral's `/v1/models` catalog by ID substring `voxtral`), Anthropic, Gemini, Deepgram. Not implemented for `openai-compatible` (URL-specific, list-shape varies), AssemblyAI, Rev.ai. The settings tab caches results to `GlobalSettings.modelCache` per side and provider ID; the Refresh button in the model field triggers `listModels` and updates the cache. The model field is a single adaptive control (`populateModelField` in [src/settings/tab.ts](src/settings/tab.ts), shared by both sides via a `side: 'transcription' | 'llm'` arg): a **dropdown** when the provider supports `listModels` and the cache is non-empty, otherwise a **plain text field**. There is never both at once. The dropdown carries a trailing "Custom..." option (sentinel `CUSTOM_MODEL_OPTION`, never written to `config.model`) that toggles the same control into the text field (the `forceText` arg) so a model ID not in the catalog can still be typed; a "Back to list" button returns to the dropdown. A custom value not in the cache is shown as a selected `<id> (custom)` dropdown option. The Refresh button accompanies the dropdown (and the empty-cache text field, so the first fetch can flip it to a dropdown); on success the field re-renders in dropdown mode. `openai-compatible` / AssemblyAI / Rev.ai (no `listModels`) always show a plain text field with no Refresh. Whichever control is active, the canonical source is `profile.config.model`.
Providers may optionally implement `listModels(config, signal)` returning a string array of model IDs the configured API key can access. Implemented by: OpenAI / Groq / Mistral (via `openai.ts` shared adapter), Mistral Voxtral (own adapter, filters Mistral's `/v1/models` catalog by ID substring `voxtral`), Anthropic, Gemini, Deepgram. Not implemented for `openai-compatible` (URL-specific, list-shape varies), AssemblyAI, Rev.ai. The settings tab caches results to `GlobalSettings.modelCache` per side and provider ID; the Refresh button in the model field triggers `listModels` and updates the cache. The model field is a single adaptive control (`populateModelField` in [src/settings/tab.ts](src/settings/tab.ts), shared by both sides via a `side: 'transcription' | 'llm'` arg): a **dropdown** when the provider supports `listModels` and the cache is non-empty, otherwise a **plain text field**. There is never both at once. The dropdown carries a trailing "Custom..." option (sentinel `CUSTOM_MODEL_OPTION`, never written to `config.model`) that toggles the same control into the text field (the `forceText` arg) so a model ID not in the catalog can still be typed; a "Back to list" button returns to the dropdown. A custom value not in the cache is shown as a selected `<id> (custom)` dropdown option. The Refresh button accompanies the dropdown (and the empty-cache text field, so the first fetch can flip it to a dropdown); on success the field re-renders in dropdown mode. `openai-compatible` / AssemblyAI / Rev.ai (no `listModels`) always show a plain text field with no Refresh. For AssemblyAI and Rev.ai the field description appends a "list of models" external link to the provider's docs (`transcriptionModelDocsUrl` + `applyModelFieldDesc` in [src/settings/tab.ts](src/settings/tab.ts)); `openai-compatible` has none (the model list is the user's own server). Whichever control is active, the canonical source is `profile.config.model`.
## Speaker diarization

View file

@ -555,9 +555,13 @@ export class ReWriteSettingTab extends PluginSettingTab {
? 'empty-cache'
: 'dropdown';
const docsUrl = isTranscription
? transcriptionModelDocsUrl(profile.transcriptionProvider)
: null;
const setting = new Setting(wrapper)
.setName(isTranscription ? 'Transcription model' : 'LLM model')
.setDesc(modelFieldDesc(hint, mode));
.setName(isTranscription ? 'Transcription model' : 'LLM model');
applyModelFieldDesc(setting, hint, mode, docsUrl);
const refresh = async (): Promise<void> => {
if (isTranscription) {
@ -1108,6 +1112,44 @@ function modelFieldDesc(hint: string, mode: ModelFieldMode): string {
}
}
/**
* Sets the model-field description, appending a "list of models" external link when
* the provider has no listModels endpoint but does publish a models doc page
* (assemblyai, revai). Providers without a doc link get the plain string desc.
*/
function applyModelFieldDesc(
setting: Setting,
hint: string,
mode: ModelFieldMode,
docsUrl: string | null,
): void {
const text = modelFieldDesc(hint, mode);
if (!docsUrl) {
setting.setDesc(text);
return;
}
const linkLabel = 'list of models';
const frag = document.createDocumentFragment();
frag.appendText(text ? `${text} See the ` : 'See the ');
const a = frag.createEl('a', { text: linkLabel, href: docsUrl });
a.target = '_blank';
a.rel = 'noopener noreferrer';
frag.appendText('.');
setting.setDesc(frag);
}
/** Models documentation page for providers that have no listModels endpoint. */
function transcriptionModelDocsUrl(id: TranscriptionProviderID): string | null {
switch (id) {
case 'assemblyai':
return 'https://www.assemblyai.com/docs/getting-started/models';
case 'revai':
return 'https://docs.rev.ai/api/asynchronous/transcribers/';
default:
return null;
}
}
function transcriptionModelHint(id: TranscriptionProviderID): string {
switch (id) {
case 'none':