From c7234d035e277d24b9f7d5133d071f483ae711d5 Mon Sep 17 00:00:00 2001 From: Mic Date: Mon, 20 Apr 2026 18:39:12 +0200 Subject: [PATCH] special prompts validation for config errors. see #766 --- claude-spec/04-api-integrations.md | 18 +++++++++++++++ js/mzta-special-commands.js | 36 ++++++++++++++++++++++++++++++ mzta-background.js | 12 ++++++---- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/claude-spec/04-api-integrations.md b/claude-spec/04-api-integrations.md index d4ef33e6..5139e4f5 100644 --- a/claude-spec/04-api-integrations.md +++ b/claude-spec/04-api-integrations.md @@ -51,6 +51,24 @@ Content script `js/lib/diff.js` is injected into ChatGPT pages for diff-view sup - Worker: `js/workers/model-worker-anthropic.js` - Settings keys: `anthropic_api_key`, `anthropic_model`, `anthropic_version`, `anthropic_max_tokens`, `anthropic_system_prompt`, `anthropic_temperature` +## Configuration Validation + +For special prompts (`mzta_specialCommand`), required fields are validated in `initWorker()` (`js/mzta-special-commands.js`) **before** the worker is created. If a required field is empty, an `Error` with `isConfigError = true` is thrown. Validation covers: + +| Provider | Required fields | +|----------|----------------| +| `chatgpt_api` | `chatgpt_api_key`, `chatgpt_model` | +| `google_gemini_api` | `google_gemini_api_key`, `google_gemini_model` | +| `ollama_api` | `ollama_host`, `ollama_model` | +| `openai_comp_api` | `openai_comp_host`, `openai_comp_model` | +| `anthropic_api` | `anthropic_api_key`, `anthropic_model`, `anthropic_version` | + +Validation is skipped when `use_specific_api = true` (i.e., the prompt's own `api_type` overrides the global setting — credentials come from the prompt config, not global prefs). + +The `isConfigError` flag on the thrown error tells callers in `mzta-background.js` to display the error in the panel **without saving it to storage** — so the user can fix settings and retry cleanly. + +For regular prompts (`openChatGPT()`), validation still happens inside the listener callback after the API webchat window is created (unchanged behavior). + ## Web Worker Pattern For all API-based providers (everything except ChatGPT Web), the call goes through a Web Worker: diff --git a/js/mzta-special-commands.js b/js/mzta-special-commands.js index 716b232e..15e1d8f4 100644 --- a/js/mzta-special-commands.js +++ b/js/mzta-special-commands.js @@ -22,6 +22,33 @@ integration_options_config } from "../options/mzta-options-default.js"; import { taLogger } from './mzta-logger.js'; + + function validateAPIConfig(llm, prefs) { + switch (llm) { + case 'chatgpt_api': + if (!prefs.chatgpt_api_key) return browser.i18n.getMessage('chatgpt_empty_apikey'); + if (!prefs.chatgpt_model) return browser.i18n.getMessage('chatgpt_empty_model'); + break; + case 'google_gemini_api': + if (!prefs.google_gemini_api_key) return browser.i18n.getMessage('google_gemini_empty_apikey'); + if (!prefs.google_gemini_model) return browser.i18n.getMessage('google_gemini_empty_model'); + break; + case 'ollama_api': + if (!prefs.ollama_host) return browser.i18n.getMessage('ollama_empty_host'); + if (!prefs.ollama_model) return browser.i18n.getMessage('ollama_empty_model'); + break; + case 'openai_comp_api': + if (!prefs.openai_comp_host) return browser.i18n.getMessage('OpenAIComp_empty_host'); + if (!prefs.openai_comp_model) return browser.i18n.getMessage('OpenAIComp_empty_model'); + break; + case 'anthropic_api': + if (!prefs.anthropic_api_key) return browser.i18n.getMessage('anthropic_empty_apikey'); + if (!prefs.anthropic_model) return browser.i18n.getMessage('anthropic_empty_model'); + if (!prefs.anthropic_version) return browser.i18n.getMessage('anthropic_empty_version'); + break; + } + return null; + } export class mzta_specialCommand { @@ -92,6 +119,15 @@ const prefs_api = await browser.storage.sync.get(prefsToGet); + if (!use_specific_api) { + const configError = validateAPIConfig(this.llm, prefs_api); + if (configError) { + const err = new Error(configError); + err.isConfigError = true; + throw err; + } + } + let workerInitMessage = { type: 'init', do_debug: this.do_debug, diff --git a/mzta-background.js b/mzta-background.js index f10abb41..30c68eca 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -705,7 +705,7 @@ async function _generateSummaryForMessage(headerMessageId, tabId = null, options } catch (error) { console.error("[ThunderAI] Error generating summary:", error); - await summaryStore.saveError(headerMessageId, error.message || String(error)); + if (!error.isConfigError) await summaryStore.saveError(headerMessageId, error.message || String(error)); if (tabId) browser.tabs.sendMessage(tabId, { command: "showSummary", data: { error: true, message: error.message || "Failed to generate summary" } }); taWorkingStatus.stopWorking(); } @@ -805,7 +805,7 @@ async function _generateTranslationForMessage(headerMessageId, tabId = null, opt } catch (error) { console.error("[ThunderAI] Error generating translation:", error); - await translationStore.saveError(headerMessageId, error.message || String(error)); + if (!error.isConfigError) await translationStore.saveError(headerMessageId, error.message || String(error)); if (tabId) browser.tabs.sendMessage(tabId, { command: "showTranslation", data: { error: true, message: error.message || "Failed to generate translation" } }); taWorkingStatus.stopWorking(); } @@ -984,8 +984,12 @@ async function _generateSpamReportForMessage(headerMessageId, options = {}) { } catch (error) { console.error("[ThunderAI] Error generating spam report:", error); - let err_data = await spamReport.saveError(headerMessageId, error.message || String(error)); - await updateSpamPanel(headerMessageId, "showSpamReport", err_data); + if (error.isConfigError) { + await updateSpamPanel(headerMessageId, "showSpamReport", { spamValue: -999, explanation: error.message || String(error) }); + } else { + let err_data = await spamReport.saveError(headerMessageId, error.message || String(error)); + await updateSpamPanel(headerMessageId, "showSpamReport", err_data); + } return { success: false }; } }