diff --git a/index.json b/index.json index 2694c08..71a582a 100644 --- a/index.json +++ b/index.json @@ -208,7 +208,7 @@ "id": "prompt-optimize", "name": "Prompt Optimize", "description": "在输入框旁调用外部 LLM 一键优化提示词,支持 OpenAI/Anthropic 兼容 API 与一键还原。", - "version": "1.0.0", + "version": "1.0.2", "author": "Codex++ Community", "tags": [ "prompt", @@ -221,7 +221,7 @@ ], "homepage": "https://github.com/BigPizzaV3/CodexPlusPlusScriptMarket", "script_url": "https://raw.githubusercontent.com/BigPizzaV3/CodexPlusPlusScriptMarket/main/scripts/prompt-optimize.js", - "sha256": "1993a9d5e3717b0d62edd9d1d9933ef2a1d0bd04cc122781032b8c7fc57a49b7" + "sha256": "f86b0a4a83e594f25cc9d624343fa3696a23ce409b32245b21eedc70568b1032" }, { "id": "codex-native-matrix-selector", diff --git a/scripts/prompt-optimize.js b/scripts/prompt-optimize.js index ecab7a2..35603da 100644 --- a/scripts/prompt-optimize.js +++ b/scripts/prompt-optimize.js @@ -2,12 +2,12 @@ @codex-plus-script name: Prompt Optimize description: Optimize the composer prompt with an external LLM; click ✨ to optimize, click again to restore. -version: 1.0.0 +version: 1.0.2 author: Codex++ Community */ (() => { - const SCRIPT_VERSION = "1.0.0"; + const SCRIPT_VERSION = "1.0.2"; const API_KEY = "__codexPlusPromptOptimize"; const MARKET_ID = "prompt-optimize"; const BRIDGE_KEY = "__codexSessionDeleteBridge"; @@ -100,6 +100,7 @@ author: Codex++ Community disposed: false, loading: false, abort: null, + bridgePathUnsupported: false, lastWrittenText: null, writeToken: 0, }; @@ -1158,7 +1159,12 @@ author: Codex++ Community } let replaced = false; try { - replaced = document.execCommand?.("selectAll", false, null) && document.execCommand?.("insertText", false, next); + // Codex's ProseMirror editor can report selectAll=false even after the + // range above is selected. Always try insertText independently. + if (typeof document.execCommand === "function") { + document.execCommand("selectAll", false, null); + replaced = document.execCommand("insertText", false, next) !== false; + } } catch (_) { replaced = false; } @@ -1198,7 +1204,7 @@ author: Codex++ Community } try { await navigator.clipboard.writeText(normalizeText(text)); - showToast("无法直接写入输入框,已复制优化结果,请粘贴替换", "warn"); + showToast("优化结果已复制,输入框未改动;请按 Ctrl+V 粘贴替换", "warn"); return { ok: false, reason: result.reason || "write-failed", clipboard: true }; } catch (_) { showToast("写入输入框失败,且无法复制到剪贴板", "error"); @@ -1690,9 +1696,27 @@ author: Codex++ Community return typeof window[BRIDGE_KEY] === "function"; } + function hasElectronFetchBridge() { + return typeof window.electronBridge?.sendMessageFromView === "function"; + } + + function hasRequestTransport() { + return hasCodexPlusBridge() || hasElectronFetchBridge(); + } + + function bridgeUnsupportedError() { + const error = new Error("当前 Codex++ 的 LLM Bridge 路由不可用"); + error.code = "CPO_BRIDGE_UNSUPPORTED"; + return error; + } + + function isBridgeUnsupportedError(error) { + return error?.code === "CPO_BRIDGE_UNSUPPORTED"; + } + async function bridgeJson(payload, signal) { if (!hasCodexPlusBridge()) { - throw new Error("当前 Codex++ 版本不支持 LLM Bridge,请升级 Codex++"); + throw bridgeUnsupportedError(); } const call = Promise.resolve().then(() => window[BRIDGE_KEY](BRIDGE_PATH, payload || {})); if (!signal) return call; @@ -1721,7 +1745,84 @@ author: Codex++ Community }); } - async function requestJson({ upstreamUrl, method, headers, body, signal }) { + function responseErrorMessage(raw, fallback) { + const text = typeof raw === "string" ? raw : ""; + if (text.trim()) { + try { + const data = JSON.parse(text); + return collapseWs(data?.error?.message || data?.message || data?.error || text).slice(0, 240); + } catch (_) { + return collapseWs(text).slice(0, 240); + } + } + return fallback; + } + + function electronFetchJson({ upstreamUrl, method, headers, body, signal }) { + if (!hasElectronFetchBridge()) { + throw new Error("当前 Codex++ 不提供可用的 LLM 请求通道"); + } + const upstream = normalizeBaseUrl(upstreamUrl); + const requestId = `prompt-optimize-${Date.now()}-${Math.random().toString(36).slice(2)}`; + return new Promise((resolve, reject) => { + let settled = false; + const cleanup = () => { + window.clearTimeout(timer); + window.removeEventListener("message", onMessage); + signal?.removeEventListener?.("abort", onAbort); + }; + const finish = (callback, value) => { + if (settled) return; + settled = true; + cleanup(); + callback(value); + }; + const onAbort = () => { + const error = new Error("aborted"); + error.name = "AbortError"; + finish(reject, error); + }; + const onMessage = (event) => { + const result = event?.data; + if (result?.type !== "fetch-response" || String(result.requestId || "") !== requestId) return; + const httpStatus = Number(result.status || 0); + const rawBody = typeof result.bodyJsonString === "string" ? result.bodyJsonString : ""; + if (result.responseType === "error" || !(httpStatus >= 200 && httpStatus < 300)) { + const message = responseErrorMessage(result.error || rawBody, `HTTP ${httpStatus || "error"}`); + finish(reject, new Error(message)); + return; + } + try { + finish(resolve, rawBody.trim() ? JSON.parse(rawBody) : {}); + } catch (_) { + finish(reject, new Error("模型返回了无法解析的 JSON")); + } + }; + const timer = window.setTimeout(() => { + finish(reject, new Error("LLM 请求超时")); + }, REQUEST_TIMEOUT_MS); + window.addEventListener("message", onMessage); + if (signal?.aborted) { + onAbort(); + return; + } + signal?.addEventListener?.("abort", onAbort, { once: true }); + try { + window.electronBridge.sendMessageFromView({ + type: "fetch", + requestId, + url: upstream, + method: method || "POST", + headers: headers || {}, + body: body == null ? null : typeof body === "string" ? body : JSON.stringify(body), + }); + } catch (error) { + finish(reject, error); + } + }); + } + + async function requestJsonViaCodexBridge({ upstreamUrl, method, headers, body, signal }) { const upstream = normalizeBaseUrl(upstreamUrl); debugLog("bridge request", { upstream, method: method || "POST" }); const result = await bridgeJson( @@ -1737,7 +1838,7 @@ author: Codex++ Community if (!result || result.status !== "ok") { const message = collapseWs(result?.message || result?.error || "LLM Bridge 请求失败"); if (/unknown bridge path|未知.*bridge/i.test(message)) { - throw new Error("当前 Codex++ 版本不支持 LLM Bridge,请升级 Codex++"); + throw bridgeUnsupportedError(); } throw new Error(message || "LLM Bridge 请求失败"); } @@ -1757,6 +1858,20 @@ author: Codex++ Community return data; } + async function requestJson(options) { + if (hasCodexPlusBridge() && !runtime.bridgePathUnsupported) { + try { + const data = await requestJsonViaCodexBridge(options); + return data; + } catch (error) { + if (!isBridgeUnsupportedError(error)) throw error; + runtime.bridgePathUnsupported = true; + debugLog("/llm-proxy unavailable; falling back to Electron fetch bridge"); + } + } + return electronFetchJson(options); + } + async function callOpenAI({ baseUrl, apiKey, model, system, user, signal }) { const upstream = joinUrl(baseUrl, "/chat/completions"); const data = await requestJson({ @@ -1867,8 +1982,8 @@ author: Codex++ Community } async function runOptimize() { - if (!hasCodexPlusBridge()) { - showToast("当前 Codex++ 版本不支持 LLM Bridge,请升级 Codex++", "error"); + if (!hasRequestTransport()) { + showToast("当前 Codex++ 不提供可用的 LLM 请求通道", "error"); return; } const settings = loadSettings(); @@ -1926,7 +2041,7 @@ author: Codex++ Community } } catch (error) { if (userCancelled) { - showToast("已取消优化", "info"); + showToast("已取消本次优化,原输入未改动", "info"); } else if (timeout.didTimeout() || error?.name === "AbortError") { showToast("优化超时", "error"); } else { @@ -2000,7 +2115,7 @@ author: Codex++ Community overlay.innerHTML = `