Skip to content

Releases: mikealicea/obsidian-scribe

Scribe 2.4.3 feat: Recordings now continue when the controls modal is closed

Choose a tag to compare

@mikealicea mikealicea released this 15 Jul 19:37

Scribe v2.4.3 Recordings now continue when the controls modal is closed

Previously, clicking outside the "Open Controls" modal (or pressing ESC) silently cancelled an in-progress recording — nothing was saved. This release changes that behavior, as requested in #113.

What's new:

  • Recording survives modal close — dismissing the controls modal no longer stops your recording. It keeps running in the background.
  • Tappable recording notice — when you close the modal mid-recording, the familiar persistent notice (🔴 Scribe: Recording 01:23 — Tap to save) appears with a live timer. Tap it anytime to stop and save.
  • Reopen and pick up where you left off — opening the controls modal again (via the ribbon or command palette) re-attaches to the live recording: the timer, pause/resume state, and your session options are all restored so you can pause, resume, or complete from there.
  • Session options are remembered — options you set in the modal (only transcribe, languages, note template, etc.) now apply however you finish the recording: tapping the notice, the ribbon's "Stop Recording", or the Start/Stop command.
  • "Open Controls" in the ribbon menu while recording — the ribbon menu now includes Open Controls alongside Pause/Cancel/Stop during an active recording.

Behavior notes:

  • Cancelling a recording is now always an explicit action — the modal's Reset button or the ribbon's Cancel Recording. Nothing is discarded just by closing a window.
  • Fixed: opening the modal during a recording started from the command palette previously left the background notice running alongside the modal. The notice now hides while the modal is open.

Thanks to the user who reported #113 🙏

What's Changed

  • 113 open controls modal window stoping in the background by @Mikodin in #114

Full Changelog: 2.4.1...2.4.2

What's Changed

  • 113 open controls modal window stoping in the background by @Mikodin in #114

Full Changelog: 2.4.1...2.4.3

Scribe 2.4.2 feat: Recordings now continue when the controls modal is closed

Choose a tag to compare

@mikealicea mikealicea released this 15 Jul 17:40
5f91b23

Scribe v2.4.2

🎙️ Recordings now continue when the controls modal is closed

Previously, clicking outside the "Open Controls" modal (or pressing ESC) silently cancelled an in-progress recording — nothing was saved. This release changes that behavior, as requested in #113.

What's new:

  • Recording survives modal close — dismissing the controls modal no longer stops your recording. It keeps running in the background.
  • Tappable recording notice — when you close the modal mid-recording, the familiar persistent notice (🔴 Scribe: Recording 01:23 — Tap to save) appears with a live timer. Tap it anytime to stop and save.
  • Reopen and pick up where you left off — opening the controls modal again (via the ribbon or command palette) re-attaches to the live recording: the timer, pause/resume state, and your session options are all restored so you can pause, resume, or complete from there.
  • Session options are remembered — options you set in the modal (only transcribe, languages, note template, etc.) now apply however you finish the recording: tapping the notice, the ribbon's "Stop Recording", or the Start/Stop command.
  • "Open Controls" in the ribbon menu while recording — the ribbon menu now includes Open Controls alongside Pause/Cancel/Stop during an active recording.

Behavior notes:

  • Cancelling a recording is now always an explicit action — the modal's Reset button or the ribbon's Cancel Recording. Nothing is discarded just by closing a window.
  • Fixed: opening the modal during a recording started from the command palette previously left the background notice running alongside the modal. The notice now hides while the modal is open.

Thanks to the user who reported #113 🙏

What's Changed

  • 113 open controls modal window stoping in the background by @Mikodin in #114

Full Changelog: 2.4.1...2.4.2

Scribe v2.4.1 - chore: A health and stability release focused on clearing all warnings

Choose a tag to compare

@mikealicea mikealicea released this 14 Jul 17:54
b5e055c

Scribe v2.4.1

A health and stability release focused on clearing all Obsidian plugin review warnings — no new features, but several real bug fixes surfaced along the way.

⚠️ Requirement change

  • Minimum Obsidian version is now 1.8.7 (released Feb 2025). Scribe already relied on newer APIs like Vault.process and frontmatter processing; the manifest now declares this honestly instead of claiming compatibility with 0.15.0.

🐛 Bug fixes

  • Deleting a recording now respects your "Deleted files" preference — audio cleanup uses Obsidian's trash instead of permanently deleting the file.
  • Fixed a broken filename-collision retry when creating notes: the fallback path compared an error object to a string, silently discarded its own retry, and re-threw anyway. Collisions are handled by the random-suffix path, and the dead code is gone.
  • Append-to-active-file with no active file no longer relies on an unsafe type cast — the fallback (create a new note and open it) is now properly null-checked.
  • Recording stop errors now always reject with a real Error, so failure messages surface correctly instead of as opaque values.

🔒 Security

  • Resolved all npm audit advisories (2 high, 1 moderate):
    • langsmith 0.5.4 → 0.8.2 (prototype pollution, redaction bypass)
    • ws 8.19.0 → 8.21.1 (memory disclosure, DoS)
    • uuid removed from the dependency tree entirely
  • Replaced the banned dotenv package with Node's built-in process.loadEnvFile() (build tooling only).

🧹 Code health

  • Cleared the full Obsidian review-bot
    • All floating promises are now explicitly awaited or fire-and-forget (void) - data: URLs are decoded locally inative fetch
    • Modal onOpen/onClose now match Obsidian's declared signatures - Removed unsafe as TFile / `as HTMd unnecessary assertions
    • Enum comparisons are now type-safe (OBSIDIAN_PATHS converted to a const object; typed language-option helpers)
    • Callback props declare this: void to prevent accidental this rebinding

What's Changed

  • Scribe v2.4.1 - chore: A health and stability release focused on clearing all warnings by @Mikodin in #112

Full Changelog: 2.4.0...2.4.1

Scribe 2.4.0 - Fixes Obsidian plugin review blocker: dynamic `<script>` element creations

Choose a tag to compare

@mikealicea mikealicea released this 14 Jul 16:47
12425a7

v2.4.0 Fixed Obsidian plugin review blocker: dynamic <script> element creations

The automated Obsidian plugin review flagged 4 dynamic <script> element creations in the bundled main.js. All 4 came from third-party dependencies — none from Scribe's own code — and have been fully removed from the bundle.

Changes

  • Downgraded react / react-dom from v19 to v18.3.1 — React 19 introduced "hoistable scripts" support (ReactDOM.preinit), which ships createElement("script") code paths in the production bundle even though Scribe never uses them. Scribe only uses standard hooks and createRoot, all fully supported in React 18.3, so this is a drop-in downgrade with no functional changes.
  • Removed the standardized-audio-context dependency — its AudioWorklet polyfill loads code via a Blob-URL <script> tag. Scribe only used it for AudioContext / decodeAudioData / createBuffer during audio chunking, all of which are natively available in Obsidian's desktop and mobile environments. The audio chunking pipeline (audioDataToChunkedFiles) now uses the native Web Audio API directly, with a webkitAudioContext fallback for older iOS WebViews.

Verification

  • Production bundle now contains 0 dynamic script element creations (previously 4)
  • Also scanned the bundle for eval(, new Function(, and document.write — all clean
  • Typecheck, lint, and production build all pass

No user-facing behavior changes — recording, transcription, and summarization work exactly as before.

A note on where verification stands: I confirmed the bundle is clean via static checks and the full typecheck/build, but the headless-Chrome runtime test of the audio chunking path was blocked by the sandbox (Chrome can't launch from this session). Since decodeAudioData/createBuffer have identical signatures between the removed library and the native API, risk is low — but I'd recommend one manual smoke test: record a short clip with OpenAI transcription selected before publishing the release, since that's the path that exercises the refactored chunking code.

What's Changed

  • Fix: Downgrades react and removes standardized-audio-context by @Mikodin in #111

Full Changelog: 2.3.9...2.4.0

Scribe v2.3.9 — Flexible Transcription Placement & Formatting

Choose a tag to compare

@mikealicea mikealicea released this 14 Jul 16:13

Scribe v2.3.9 — Flexible Transcription Placement & Formatting

This completes the feature requests
#67
#100

You can now control where the audio embed and transcript land in your generated notes, how their headers look, and whether the transcript is collapsed by default. Previously the # Audio block and transcript were always pinned to the top with a fixed H1 header and always sat above your summary sections — now it's all up to you.

✨ What's new

Audio & Transcript are now template sections

Every note template has two new built-in sections — 📌 Audio and 📌 Transcript — that sit right alongside your LLM sections (Summary, Insights, etc.) in the template editor. Their position in the template is their position in the finished note.

Reorder anything with ▲ / ▼

Each section now has move-up/move-down buttons. Want the transcript below your summary? Move it down. Want the audio embed at the very bottom? Drag it there. Placement in the note follows the order in the template.

Custom section headers

Section headers are now the full markdown line, so you decide the heading level. Issue #100's request is covered: change # Audio to ## Audio, or leave a header blank for no heading at all. This applies to your LLM sections too — set them to ##, ###, or nothing.

Collapse the transcript by default

The Transcript section has a new "Collapse transcript by default" toggle that renders it inside an Obsidian foldable callout, so long transcripts stay tucked away until you click to expand them.

🛠 Improvements & fixes

  • Notes now appear instantly and fill in as they go. The full note structure — audio embed, headers, and "in progress" placeholders — is written up front, then each section is replaced in place as transcription and summarization complete, instead of appending at the end.
  • Cleaner failures. If transcription or summarization fails, Scribe now leaves a clear marker and removes the leftover placeholders rather than stranding an "in progress" line in your note.
  • Fixed: Cloning a template no longer shares section data with the original (edits to one used to leak into the other).
  • Fixed: Transcripts or summaries conta or $&) can no longer corrupt notecontent.

🔄 Upgrading

Your existing templates migrate automatically on first launch — the Audio and Transcript sections are inserted at the top in their current poeaders without a level are normalized toH2. Existing notes are unaffected and new notes come out identical to before unless you choose to rearrange things.

Full Changelog: 2.3.8...2.3.9

Scribe 2.3.8 - Bug Fix: Don't make LLM provider request if there is no transcript

Choose a tag to compare

@mikealicea mikealicea released this 12 Jul 21:03
58d4333

Fix: Skip LLM processing when transcription is disabled (#104)

Fixes #104

Problem

The "Transcribe recordings" and "Process transcriptions with LLM" settings were fully independent. With transcription off and processing on, handleTranscription() returned an empty string and the pipeline sent it straight to the LLM — resulting in a pointless (and paid) LLM call that generated garbage note sections.

Changes

  • Settings UI (src/settings/GeneralSettingsTab.tsx): "Process transcriptions with LLM" is now disabled and displayed as off while "Transcribe recordings" is off. The stored preference is left untouched, so re-enabling transcription restores the previous processing state. Description updated to note the dependency.
  • Toggle component (src/settings/components/SettingsControl.tsx): SettingsToggle now supports a disabled prop (click and Enter-key handlers are guarded).
  • Pipeline guard (src/index.ts, handleScribeFile): if the transcript is empty, LLM summarization is skipped with a Scribe: ⚠️ Skipping LLM processing — transcript is empty notice. This covers stale persisted settings, the modal inheriting the disabled-transcription flag, and any provider legitimately returning an empty transcript.

Testing

  • npm run format:write, npm run lint:write, npm run build:prod all pass
  • Manual: with "Transcribe recordings" off, the processing toggle is off/unclickable; completing a recording shows the skip notice instead of calling the LLM. Re-enabling transcription restores the previous processing preference.

What's Changed

  • Bug Fix: Don't make LLM provider request if there is no transcript by @Mikodin in #110

Full Changelog: 2.3.7...2.3.8

Scribe 2.3.7 feat: add AssemblyAI LLM support with model selection and configuration

Choose a tag to compare

@mikealicea mikealicea released this 12 Jul 20:31
d2aff65

Scribe 2.3.7 feat: add AssemblyAI LLM support with model selection and configuration

Add AssemblyAI as an LLM summarization provider

Summary

Adds AssemblyAI's LLM Gateway as a summarization provider, so a user can run both transcription and LLM summarization through AssemblyAI — keeping data within a single vendor's privacy envelope (the motivation behind this ZDR / no-training branch) instead of sending transcripts to a second provider.

Because the gateway is OpenAI-compatible, it routes through the existing createOpenAiCompatibleLlmAdapter (LangChain ChatOpenAI + obsidianFetch + a baseURL override) — the same mechanism already used for Gemini and OpenRouter. No new transcription/LLM call logic was required; this is almost entirely wiring a new provider through the existing data-driven plumbing.

What changed

New provider wiring

  • src/util/consts.ts — added assemblyAi to the PROCESS_PLATFORM enum.
  • src/aiProviders/llm/openAiCompatibleLlm.ts — added the gateway base URL, default model (claude-sonnet-4-6), and a seed list of common model IDs.
  • src/aiProviders/llm/llmAdapter.ts — resolveLlmConfig case mapping the provider to assemblyAiApiKey + assemblyAiLlmModel + the gateway base URL. createLlmAdapter needed no change (non-Anthropic already routes to the OpenAI-compat adapter).
  • src/aiProviders/providerMetadata.ts — LLM_PROVIDERS entry (drives the settings dropdown, privacy card, and missing-key checks).
  • src/settings/settings.tsx — new assemblyAiLlmModel setting, defaulted to claude-sonnet-4-6.

UI (free-text model combobox, mirroring OpenRouter)

  • src/settings/components/ProviderSettingsSections.tsx — AssemblyAI section reusing the existing API-key input + a combobox seeded with known model IDs (any gateway model ID allowed).
  • src/modal/components/options/ModalAiModelOptions.tsx — matching combobox branch in the per-run modal picker.

Bug fixes (gateway-specific)

Summarization initially failed only with AssemblyAI. Two gateway behaviors differ from OpenAI/Gemini/OpenRouter, both fixed in openAiCompatibleLlm.ts:

  1. Output truncation — the gateway defaults max_tokens to 1000 when omitted (unlike OpenAI/Gemini, which use the model's high default). LangChain doesn't send max_tokens, so full structured notes were truncated mid-JSON and failed to parse. Fixed by sending an explicit maxTokens: 8192 for the gateway.
  2. Dropped transcript — the transcript lives in the first system message, and we always send ≥2 system messages (the language instruction defaults to en). When the gateway collapses OpenAI's multiple system messages into Anthropic's single system field for Claude models, the transcript-bearing message could be lost. Fixed by merging the main prompt + extra instructions into a single system message (behavior-equivalent for the other OpenAI-compat providers).

ZDR note

No special request headers are needed — Zero Data Retention on the LLM Gateway is an account-level arrangement (executed BAA + training opt-out + optional TTL) and applies
only when routing to Anthropic or Google models, not GPT/Qwen/Kimi. The de ZDR-eligible; users who select a gpt-* model in the combobox fall outsidethe ZDR envelope.

Testing

  • npm run format:write, npm run lint:write, npm run build:prod all pass cleanly (incl. the mobile-safety check).
  • Manual verification still needed in Obsidian: with an AssemblyAI key ander selected, confirm the note gets its summary sections + LLM-suggestedtitle. If it fails, the gateway's error body surfaces in the console via [obsidianFetch] error body:.

(Note: the branch also switches the AssemblyAI transcription default speech model to universal-3-5-pro in assemblyAiTranscriber.ts.)

What's Changed

  • Scribe 2.3.7 feat: add AssemblyAI LLM support with model selection and configuration by @Mikodin in #109

Full Changelog: 2.3.6...2.3.7

Scribe 2.3.6 Adds a ton of providers for transcription + summarizing

Choose a tag to compare

@mikealicea mikealicea released this 11 Jul 23:47

Scribe 2.3.6 Adds a ton of providers for transcription + summarizing

Multi-provider AI refactor: transcription + summarization

Closes #54 @skorphil

Tested every provider on mobile and desktop

Summary

Replaces the single-LLM (OpenAI only) / two-transcriber (OpenAI + AssemblyAI) setup with a provider-adapter architecture supporting 6 transcription providers and 5 LLM providers, plus inline privacy/data-retention info for each and a searchable model picker for OpenRouter.

  • LLM (summarization + mermaid-fix): OpenAI, Anthropic, Google (Gemini), OpenRouter, custom OpenAI-compatible endpoint
  • Transcription: OpenAI Whisper, AssemblyAI, ElevenLabs Scribe, Deepgram Nova-3, Mistral Voxtral, Google (Gemini), custom OpenAI-compatible endpoint

Why this shape

PR #101 attempted Google support via @langchain/google-genai and was reverted (#102): that package transitively pulls in node:async_hooks, which crashes Obsidian mobile (WebView, no Node runtime). This PR avoids that class of bug entirely:

  • Anthropic uses the native @anthropic-ai/sdk (browser-safe, dangerouslyAllowBrowser: true) — the only new LangChain-adjacent dependency added.
  • Google, OpenRouter, and custom endpoints all route through the existing LangChain ChatOpenAI client via baseURL, since Gemini and OpenRouter both expose OpenAI-compatible chat completions endpoints. No Google SDK, no new provider packages.
  • ElevenLabs, Deepgram, Mistral, and Gemini transcription are raw fetch calls through the existing obsidianFetch CORS wrapper — no SDKs at all.
  • A new scripts/checkMobileSafe.mjs build step greps build/main.js for async_hooks/AsyncLocalStorage and fails the production build if either appears — thithat would have caught the #101 bug before merge.

Architecture

New src/aiProviders/ module:

src/aiProviders/
  providerMetadata.ts          # registry: diseld, keyConsoleUrl,
                                #   models?, supportsDiarization?, privacy info
  prompts.ts                   # shared promptd from the old
                                #   openAiUtils.ts, byte-identical prompt text)
  llm/
    llmAdapter.ts               # LlmAdapter interface + resolveLlmConfig() + createLlmAdapter()
    openAiCompatibleLlm.ts      # LangChain Ch google / openRouter / customOpenAi
    anthropicLlm.ts             # native @anthropic-ai/sdk adapter
    openRouterModels.ts         # fetches + cacatalog
  transcription/
    transcriptionAdapter.ts     # dispatches oPlatform
    openAiTranscriber.ts        # moved from src/util/openAiUtils.ts
    assemblyAiTranscriber.ts    # moved from s unchanged
    elevenLabsTranscriber.ts
    deepgramTranscriber.ts
    mistralTranscriber.ts
    geminiTranscriber.ts
    diarizationFormat.ts        # shared "**Speaker N**: text" formatter

Deleted: src/util/openAiUtils.ts, src/util/split),src/settings/components/AiModelSettings.tsx` (replaced by the registry-driven settings UI).

src/util/obsidianFetch.ts wraps Obsidian's requestUrl() as a CORS-bypassing fetch implementation — used by every
HTTP-based provider client (OpenAI SDK, Anthroetch calls). Since requestUrl() buffers the fullresponse, no provider call streams — all LLM calls use non-streaming structured output.

Provider details

LLM

Provider Transport Models Notes
OpenAI LangChain ChatOpenAI GPT-5.6/5.5/5.1/5/4.1/4o family unchanged from before
Anthropic @anthropic-ai/sdk, client.mestFormat claude-opus-4-8, claude-sonnet-5(default), claude-sonnet-4-6, claude-haiku-4-5 never sends temperature (current Claude models reject non-default
values); surfaces refusal/max_tokens stop
Google (Gemini) LangChain ChatOpenAI via Gemini's OpenAI-compat endpoint
(`generativelanguage.googleapis.com/v1beta/opedefault, GA), gemini-3.1-pro-preview,gemini-3-flash-preview, gemini-3.1-flash-lite, gemini-2.5-pro, gemini-2.5-flash no Google SDK — sidesteps the async_hooks
issue entirely
OpenRouter LangChain ChatOpenAI via openrouter.ai/api/v1 any model id, searchable combobox (see below)
temperature omitted — OpenRouter routes to arbs/models with their own constraints
Custom OpenAI-compatible LangChain ChatOpenAI via user-supplied baseURL free text unchanged from before

Transcription

Provider Transport Diarization Notes
OpenAI Whisper OpenAI SDK, chunked WAV upload No unchanged from before
AssemblyAI assemblyai SDK Yes unchand through obsidianFetch)
ElevenLabs Scribe raw fetch, multipart Yes (word-level → merged into speaker runs)
Deepgram Nova-3 raw fetch, raw audio byt)
Mistral Voxtral raw fetch, multipart No
Google (Gemini) raw fetch to `generateCoo Yes (prompt-driven) no dedicated STT endpoint — audio is chunked to WAV (reusing the existing chunker) to stay under the 20MB inline-request limit, sent with a
transcription prompt

All diarized output converges on the same `**Son regardless of provider.

Settings UI

  • AI Providers tab: separate "Transcription" as, each with a provider dropdown, that provider'sAPI key + model fields inline, and a privacy card — retention summary, whether the provider trains on your API data,
    and a policy link. Facts are July-2026 snapshopolicies drift.
  • OpenRouter model field is now a searchable combobox (native <input list> + <datalist>, no new dependency) — it
    fetches OpenRouter's public model catalog (no ers as you type, while still accepting arbitraryfree text. Same picker is used in the modal's per-run model override, which was previously a disabled read-only input for
    OpenRouter.
  • Settings migrate automatically: the old useCustomOpenAiBaseUrl boolean maps to processPlatform/transcriptPlatform = customOpenAi; everyone else is unaffected. "R all API keys (now 7 of them).

Bug fix (behavior change)

handleTranscription previously read this.se, ignoring the modal's per-runscribeOptions.transcriptPlatform` override — so switching providers in the modal had no effect on the actual request. This
is now fixed; modal overrides take effect. Worince it changes observable behavior for anyone who was relying on (or unknowingly affected by) the old ignored-override behavior.

Build safety

  • Fixed a broken esbuild config: builtin-modules@5 is ESM-only, so its default import silently resolved to undefined
    and Node builtins were being bundled unnoticedle's builtinModules(bare +node:`-prefixedvariants).
  • Added scripts/checkMobileSafe.mjs, wired ifails the build if build/main.js containsasync_hooks or AsyncLocalStorage.

Verification

  • npm run format:write — clean
  • npm run build:prod (tsc + esbuild + mobile
  • Confirmed all provider endpoints present in build/main.js (ElevenLabs, Deepgram, Mistral, Gemini generateContent +
    OpenAI-compat, OpenRouter, Anthropic)
  • Confirmed extracted summary prompt text is unchanged (byte-identical modulo trailing whitespace) vs. the original
    openAiUtils.ts

Not covered by this PR (needs real API keyse)

  • Live provider calls per transcription providses for ElevenLabs/Deepgram/Mistral/Gemini)
  • Anthropic and Gemini structured-output round-trips against real audio/transcripts
  • OpenRouter combobox behavior against the liv
  • Settings migration against a hand-edited data.json with useCustomOpenAiBaseUrl: true
  • Mobile sideload smoke test
  • Mistral Voxtral's file-size limit is unverified (chunking fallback noted in code but untested)

What's Changed

  • Feat: Add support for all major providers by @Mikodin in #108

Full Changelog: 2.3.4...2.3.6

Scribe 2.3.5

Choose a tag to compare

@mikealicea mikealicea released this 11 Jul 23:19
a2f5b39

Scribe 2.3.5 Adds a ton of providers for transcription + summarizing

Multi-provider AI refactor: transcription + summarization

Closes #54 @skorphil

Tested every provider on mobile and desktop

Summary

Replaces the single-LLM (OpenAI only) / two-transcriber (OpenAI + AssemblyAI) setup with a provider-adapter architecture supporting 6 transcription providers and 5 LLM providers, plus inline privacy/data-retention info for each and a searchable model picker for OpenRouter.

  • LLM (summarization + mermaid-fix): OpenAI, Anthropic, Google (Gemini), OpenRouter, custom OpenAI-compatible endpoint
  • Transcription: OpenAI Whisper, AssemblyAI, ElevenLabs Scribe, Deepgram Nova-3, Mistral Voxtral, Google (Gemini), custom OpenAI-compatible endpoint

Why this shape

PR #101 attempted Google support via @langchain/google-genai and was reverted (#102): that package transitively pulls in node:async_hooks, which crashes Obsidian mobile (WebView, no Node runtime). This PR avoids that class of bug entirely:

  • Anthropic uses the native @anthropic-ai/sdk (browser-safe, dangerouslyAllowBrowser: true) — the only new LangChain-adjacent dependency added.
  • Google, OpenRouter, and custom endpoints all route through the existing LangChain ChatOpenAI client via baseURL, since Gemini and OpenRouter both expose OpenAI-compatible chat completions endpoints. No Google SDK, no new provider packages.
  • ElevenLabs, Deepgram, Mistral, and Gemini transcription are raw fetch calls through the existing obsidianFetch CORS wrapper — no SDKs at all.
  • A new scripts/checkMobileSafe.mjs build step greps build/main.js for async_hooks/AsyncLocalStorage and fails the production build if either appears — thithat would have caught the #101 bug before merge.

Architecture

New src/aiProviders/ module:

src/aiProviders/
  providerMetadata.ts          # registry: diseld, keyConsoleUrl,
                                #   models?, supportsDiarization?, privacy info
  prompts.ts                   # shared promptd from the old
                                #   openAiUtils.ts, byte-identical prompt text)
  llm/
    llmAdapter.ts               # LlmAdapter interface + resolveLlmConfig() + createLlmAdapter()
    openAiCompatibleLlm.ts      # LangChain Ch google / openRouter / customOpenAi
    anthropicLlm.ts             # native @anthropic-ai/sdk adapter
    openRouterModels.ts         # fetches + cacatalog
  transcription/
    transcriptionAdapter.ts     # dispatches oPlatform
    openAiTranscriber.ts        # moved from src/util/openAiUtils.ts
    assemblyAiTranscriber.ts    # moved from s unchanged
    elevenLabsTranscriber.ts
    deepgramTranscriber.ts
    mistralTranscriber.ts
    geminiTranscriber.ts
    diarizationFormat.ts        # shared "**Speaker N**: text" formatter

Deleted: src/util/openAiUtils.ts, src/util/split),src/settings/components/AiModelSettings.tsx` (replaced by the registry-driven settings UI).

src/util/obsidianFetch.ts wraps Obsidian's requestUrl() as a CORS-bypassing fetch implementation — used by every
HTTP-based provider client (OpenAI SDK, Anthroetch calls). Since requestUrl() buffers the fullresponse, no provider call streams — all LLM calls use non-streaming structured output.

Provider details

LLM

Provider Transport Models Notes
OpenAI LangChain ChatOpenAI GPT-5.6/5.5/5.1/5/4.1/4o family unchanged from before
Anthropic @anthropic-ai/sdk, client.mestFormat claude-opus-4-8, claude-sonnet-5(default), claude-sonnet-4-6, claude-haiku-4-5 never sends temperature (current Claude models reject non-default
values); surfaces refusal/max_tokens stop
Google (Gemini) LangChain ChatOpenAI via Gemini's OpenAI-compat endpoint
(`generativelanguage.googleapis.com/v1beta/opedefault, GA), gemini-3.1-pro-preview,gemini-3-flash-preview, gemini-3.1-flash-lite, gemini-2.5-pro, gemini-2.5-flash no Google SDK — sidesteps the async_hooks
issue entirely
OpenRouter LangChain ChatOpenAI via openrouter.ai/api/v1 any model id, searchable combobox (see below)
temperature omitted — OpenRouter routes to arbs/models with their own constraints
Custom OpenAI-compatible LangChain ChatOpenAI via user-supplied baseURL free text unchanged from before

Transcription

Provider Transport Diarization Notes
OpenAI Whisper OpenAI SDK, chunked WAV upload No unchanged from before
AssemblyAI assemblyai SDK Yes unchand through obsidianFetch)
ElevenLabs Scribe raw fetch, multipart Yes (word-level → merged into speaker runs)
Deepgram Nova-3 raw fetch, raw audio byt)
Mistral Voxtral raw fetch, multipart No
Google (Gemini) raw fetch to `generateCoo Yes (prompt-driven) no dedicated STT endpoint — audio is chunked to WAV (reusing the existing chunker) to stay under the 20MB inline-request limit, sent with a
transcription prompt

All diarized output converges on the same `**Son regardless of provider.

Settings UI

  • AI Providers tab: separate "Transcription" as, each with a provider dropdown, that provider'sAPI key + model fields inline, and a privacy card — retention summary, whether the provider trains on your API data,
    and a policy link. Facts are July-2026 snapshopolicies drift.
  • OpenRouter model field is now a searchable combobox (native <input list> + <datalist>, no new dependency) — it
    fetches OpenRouter's public model catalog (no ers as you type, while still accepting arbitraryfree text. Same picker is used in the modal's per-run model override, which was previously a disabled read-only input for
    OpenRouter.
  • Settings migrate automatically: the old useCustomOpenAiBaseUrl boolean maps to processPlatform/transcriptPlatform = customOpenAi; everyone else is unaffected. "R all API keys (now 7 of them).

Bug fix (behavior change)

handleTranscription previously read this.se, ignoring the modal's per-runscribeOptions.transcriptPlatform` override — so switching providers in the modal had no effect on the actual request. This
is now fixed; modal overrides take effect. Worince it changes observable behavior for anyone who was relying on (or unknowingly affected by) the old ignored-override behavior.

Build safety

  • Fixed a broken esbuild config: builtin-modules@5 is ESM-only, so its default import silently resolved to undefined
    and Node builtins were being bundled unnoticedle's builtinModules(bare +node:`-prefixedvariants).
  • Added scripts/checkMobileSafe.mjs, wired ifails the build if build/main.js containsasync_hooks or AsyncLocalStorage.

Verification

  • npm run format:write — clean
  • npm run build:prod (tsc + esbuild + mobile
  • Confirmed all provider endpoints present in build/main.js (ElevenLabs, Deepgram, Mistral, Gemini generateContent +
    OpenAI-compat, OpenRouter, Anthropic)
  • Confirmed extracted summary prompt text is unchanged (byte-identical modulo trailing whitespace) vs. the original
    openAiUtils.ts

Not covered by this PR (needs real API keyse)

  • Live provider calls per transcription providses for ElevenLabs/Deepgram/Mistral/Gemini)
  • Anthropic and Gemini structured-output round-trips against real audio/transcripts
  • OpenRouter combobox behavior against the liv
  • Settings migration against a hand-edited data.json with useCustomOpenAiBaseUrl: true
  • Mobile sideload smoke test
  • Mistral Voxtral's file-size limit is unverified (chunking fallback noted in code but untested)

What's Changed

  • Feat: Add support for all major providers by @Mikodin in #108

Full Changelog: 2.3.4...2.3.5

Scribe 2.3.4 - Update OpenAI Model options

Choose a tag to compare

@mikealicea mikealicea released this 11 Jul 21:37
b763e17

Update AI model options to latest OpenAI models

What

  • New models in the summary LLM selector — adds the GPT-5.6 family (gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna) plus gpt-5.5, gpt-5.1, gpt-5, gpt-5-mini, and gpt-5-nano to the LLM_MODELS enum, which drives both the settings dropdown and the modal's model picker. Existing gpt-4.x entries are kept so previously saved selections still resolve.
  • New default model — fresh installs now default to gpt-5.6-terra (OpenAI's recommended balanced tier). Existing users keep their saved setting.
  • Temperature guard — GPT-5-family and o-series models reject any non-default temperature on the Chat Completions API (400 error). Summarization and mermaid-fix calls now only pass their hardcoded temperatures (0.5 / 0.3) to models that support it.
  • Docs touch-up — custom chat model placeholder/example text updated from gpt-4o to gpt-5.6-terra.

Why

The selector topped out at gpt-4.1, several generations behind current OpenAI offerings. Without the temperature guard, simply adding the new models would have broken summarization with a 400 on every GPT-5-family request.

Testing

  • npm run format:write and npm run build:prod (typecheck + build) pass cleanly.
  • No settings migration needed: saved llmModel values remain valid enum members.

What's Changed

  • Feat update model options for OpenAI model selection by @Mikodin in #107

Full Changelog: 2.3.3...2.3.4