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
@@ -4184,7 +4184,12 @@ ipcRenderer.on('show-table-generator', () => {
// Show PDF Editor Dialog
ipcRenderer.on('show-pdf-editor-dialog', (event, operation, openedFilePath) => {
currentPDFOperation = operation;
if (!operation) {
// File > Open PDF sends null — legit, but log unexpected variants so
// stray senders are traceable in the console.
console.info('[pdf-editor] opening with default section; operation =', operation);
}
currentPDFOperation = operation || 'merge';
showPDFEditorDialog(operation, openedFilePath);
});
@@ -4373,8 +4378,13 @@ function showPDFEditorDialog(operation, openedFilePath = null) {
break;
}
}
title.textContent = titleText;
document.getElementById(sectionId).classList.remove('hidden');
// Unknown/null operations (e.g. File > Open PDF sends null meaning "open
// the editor with this file") fall back to the merge section instead of
// crashing on a missing element.
const section = document.getElementById(sectionId || 'pdf-merge-section');
title.textContent = titleText || 'PDF Editor';
if (section) section.classList.remove('hidden');
window.modals.pdfEditorModal.open();
}
function hidePDFEditorDialog() {