diff --git a/sitecompanion/background.js b/sitecompanion/background.js index 5be2496..ebfb14a 100644 --- a/sitecompanion/background.js +++ b/sitecompanion/background.js @@ -414,14 +414,6 @@ async function handleAnalysisRequest(port, payload, signal) { safePost(port, { type: "ERROR", message: "Missing API URL." }); return; } - if (!requestTemplate) { - safePost(port, { type: "ERROR", message: "Missing request template." }); - return; - } - if (apiKeyHeader && !apiKey) { - safePost(port, { type: "ERROR", message: "Missing API key." }); - return; - } } else { if (!apiBaseUrl) { safePost(port, { type: "ERROR", message: "Missing API base URL." }); diff --git a/sitecompanion/popup.js b/sitecompanion/popup.js index d88a601..a16f358 100644 --- a/sitecompanion/popup.js +++ b/sitecompanion/popup.js @@ -186,6 +186,33 @@ function normalizeConfigList(list) { : []; } +const TEMPLATE_PLACEHOLDERS = [ + "PROMPT_GOES_HERE", + "SYSTEM_PROMPT_GOES_HERE", + "API_KEY_GOES_HERE", + "MODEL_GOES_HERE", + "API_BASE_URL_GOES_HERE" +]; + +function buildTemplateValidationSource(template) { + let output = template || ""; + for (const token of TEMPLATE_PLACEHOLDERS) { + output = output.split(`\"${token}\"`).join(JSON.stringify("PLACEHOLDER")); + output = output.split(token).join("null"); + } + return output; +} + +function isValidTemplateJson(template) { + if (!template) return false; + try { + JSON.parse(buildTemplateValidationSource(template)); + return true; + } catch { + return false; + } +} + function resolveScopedItems(parentItems, localItems, disabledNames) { const parent = Array.isArray(parentItems) ? parentItems : []; const local = Array.isArray(localItems) ? localItems : []; @@ -1061,15 +1088,8 @@ async function handleAnalyze() { setStatus("Set an API URL in Settings."); return; } - if (!resolvedTemplate) { - setStatus("Set a request template in Settings."); - return; - } - const needsKey = - Boolean(resolvedApiKeyHeader) || - resolvedTemplate.includes("API_KEY_GOES_HERE"); - if (needsKey && !apiKey) { - setStatus("Add an API key in Settings."); + if (!isValidTemplateJson(resolvedTemplate)) { + setStatus("Request template JSON is invalid."); return; } } else { diff --git a/sitecompanion/settings.js b/sitecompanion/settings.js index 2883e14..a472091 100644 --- a/sitecompanion/settings.js +++ b/sitecompanion/settings.js @@ -475,6 +475,33 @@ function normalizeConfigList(list) { : []; } +const TEMPLATE_PLACEHOLDERS = [ + "PROMPT_GOES_HERE", + "SYSTEM_PROMPT_GOES_HERE", + "API_KEY_GOES_HERE", + "MODEL_GOES_HERE", + "API_BASE_URL_GOES_HERE" +]; + +function buildTemplateValidationSource(template) { + let output = template || ""; + for (const token of TEMPLATE_PLACEHOLDERS) { + output = output.split(`\"${token}\"`).join(JSON.stringify("PLACEHOLDER")); + output = output.split(token).join("null"); + } + return output; +} + +function isValidTemplateJson(template) { + if (!template) return false; + try { + JSON.parse(buildTemplateValidationSource(template)); + return true; + } catch { + return false; + } +} + function normalizeDisabledInherited(source) { const data = source && typeof source === "object" ? source : {}; return { @@ -3440,11 +3467,8 @@ function updateSidebarErrors() { if (!defaultApiConfig) { errors.push("Default environment is missing an API config."); } else if (defaultApiConfig.advanced) { - if (!defaultApiConfig.apiUrl) { - errors.push("Default API config is missing an API URL."); - } - if (!defaultApiConfig.requestTemplate) { - errors.push("Default API config is missing a request template."); + if (!isValidTemplateJson(defaultApiConfig.requestTemplate || "")) { + errors.push("Default API config request template is invalid JSON."); } } else { if (!defaultApiConfig.apiBaseUrl) { @@ -3455,12 +3479,7 @@ function updateSidebarErrors() { } } - const needsKey = - Boolean(defaultApiConfig?.apiKeyHeader) || - Boolean( - defaultApiConfig?.requestTemplate?.includes("API_KEY_GOES_HERE") - ); - if (needsKey) { + if (!defaultApiConfig.advanced && defaultApiConfig?.apiKeyHeader) { const key = enabledApiKeys.find( (entry) => entry.id === defaultApiConfig?.apiKeyId );