feat(ai): add Anthropic-compatible provider; fix async plugin backends

Anthropic-compatible provider:
- New 'anthropic-compatible' option for any base URL speaking the Anthropic
  messages schema (LiteLLM proxies, Bedrock gateways, local servers)
- Sends x-api-key AND Bearer auth when a key is set (gateway-friendly,
  harmless for the official API); keyless proxies supported
- Tolerates base URLs with or without a trailing /v1 segment
- Settings modal, manifest, and provider docs updated

Runtime bug fixes found by booting the app (run-to-verify pass):
- PDF editor: File > Open PDF sends operation=null which matched no switch
  case and crashed on getElementById(undefined); now defaults to the merge
  section
- backlinks-panel: wrong require depth (../../utils -> ../utils) threw at
  panel registration time
- writing-studio stack was written against a synchronous settings backend but
  the real one is IPC-backed: GoalTracker/SnapshotManager/ProjectManager and
  all four panels now await; JSON.parse(Promise) crashes eliminated
- manuscript panel used window.prompt (unavailable in Electron); replaced
  with an inline dialog
- collaboration comment-store/save-load made async to match its IPC IO

617/617 tests green; 4 consecutive clean app boots (no uncaught errors)
This commit is contained in:
2026-09-05 21:53:34 +05:30
parent efca458495
commit 7ab5a0ddb4
20 changed files with 426 additions and 225 deletions
+13 -3
View File
@@ -3,7 +3,10 @@
*
* Brings LLM assistance into the editor with multi-provider support:
* - OpenAI / Anthropic (cloud, API key required)
* - Ollama / LM Studio / any OpenAI-compatible endpoint (local, no key)
* - Ollama / LM Studio (local, no key)
* - Any OpenAI-compatible or Anthropic-compatible endpoint (custom base
* URL; Anthropic-compatible gateways like LiteLLM proxies work with or
* without a key)
*
* All provider traffic is proxied through the main process
* (ai-assistant:complete / ai-assistant:status IPC) so API keys never enter
@@ -231,11 +234,18 @@ class AiAssistantPlugin extends PluginAPI {
<option value="openai">OpenAI</option>
<option value="anthropic">Anthropic</option>
<option value="openai-compatible">OpenAI-compatible…</option>
<option value="anthropic-compatible">Anthropic-compatible…</option>
</select>
</label>
<label>Model <input data-role="model" type="text" placeholder="e.g. llama3.1, gpt-4o-mini" /></label>
<label>Base URL <input data-role="baseUrl" type="text" placeholder="http://localhost:11434/v1" /></label>
<label>API Key <input data-role="apiKey" type="password" placeholder="(not needed for local)" /></label>
<label>Base URL
<input
data-role="baseUrl"
type="text"
placeholder="Required for …-compatible providers, e.g. http://localhost:4000"
/>
</label>
<label>API Key <input data-role="apiKey" type="password" placeholder="(not needed for local providers)" /></label>
<label>Temperature <input data-role="temperature" type="number" min="0" max="2" step="0.1" /></label>
<p class="ai-settings-note">Settings are stored locally in settings.json. API keys never leave this machine.</p>
<div class="ai-settings-actions">