diff options
| -rw-r--r-- | src/background/message-router.js | 17 | ||||
| -rw-r--r-- | src/background/ollama-client.js | 26 | ||||
| -rw-r--r-- | src/content/content.js | 12 |
3 files changed, 46 insertions, 9 deletions
diff --git a/src/background/message-router.js b/src/background/message-router.js index c93fc9d..9428312 100644 --- a/src/background/message-router.js +++ b/src/background/message-router.js @@ -31,13 +31,16 @@ const ERROR_MESSAGES = { [ERROR_CODES.TEXT_EMPTY]: '選択文字列が空です。', [ERROR_CODES.TEXT_TOO_LONG]: `選択文字列が長すぎます(上限 ${MAX_SELECTION_LENGTH} 文字)。`, [ERROR_CODES.OLLAMA_UNAVAILABLE]: 'Ollamaに接続できません。Ollamaが起動しているか確認してください。', - [ERROR_CODES.MODEL_NOT_FOUND]: '指定されたモデルが見つかりません。', + [ERROR_CODES.MODEL_NOT_FOUND]: '指定されたモデルが見つかりません。設定画面でモデルを選び直してください。', [ERROR_CODES.INVALID_SETTINGS]: '設定値が不正です。設定画面を確認してください。', [ERROR_CODES.REQUEST_ABORTED]: '翻訳を中断しました。', [ERROR_CODES.STREAM_ERROR]: '翻訳中にエラーが発生しました。', [ERROR_CODES.UNKNOWN_ERROR]: '不明なエラーが発生しました。' }; +/** Message shown when no model is configured at all. */ +const NO_MODEL_MESSAGE = 'モデルが設定されていません。ツールバーアイコンまたは設定画面でモデルを選択してください。'; + /** * Handle one content-script port connection. * @param {object} port runtime.Port @@ -84,7 +87,7 @@ async function runTranslation(port, requestId, text) { return; } if (!settings.model) { - postError(port, requestId, ERROR_CODES.INVALID_SETTINGS, 'No model configured'); + postError(port, requestId, ERROR_CODES.INVALID_SETTINGS, NO_MODEL_MESSAGE); return; } @@ -128,11 +131,15 @@ async function runTranslation(port, requestId, text) { * @param {object} port * @param {string} requestId * @param {string} code - * @param {string} message + * @param {string} detail optional concrete reason shown to the user */ -function postError(port, requestId, code, message) { +function postError(port, requestId, code, detail) { + const base = ERROR_MESSAGES[code] || ERROR_MESSAGES[ERROR_CODES.UNKNOWN_ERROR]; + // Append the concrete server/detail reason when provided so the popover + // shows WHY it failed (e.g. the Ollama error body for HTTP 400). + const message = detail && detail !== base ? `${base}\n(${detail})` : base; try { - port.postMessage({ type: BG_TO_CONTENT.ERROR, requestId, code, message: ERROR_MESSAGES[code] || ERROR_MESSAGES[ERROR_CODES.UNKNOWN_ERROR] }); + port.postMessage({ type: BG_TO_CONTENT.ERROR, requestId, code, message }); } catch { // port may be disconnected } diff --git a/src/background/ollama-client.js b/src/background/ollama-client.js index 0286230..e53d997 100644 --- a/src/background/ollama-client.js +++ b/src/background/ollama-client.js @@ -16,6 +16,24 @@ import { isValidOllamaHost } from '../shared/settings-schema.js'; import { ERROR_CODES } from '../shared/constants.js'; /** + * Extract a short error message from an Ollama JSON error body. + * @param {string} raw + * @returns {string} message or '' when not parseable + */ +function extractOllamaError(raw) { + if (!raw) return ''; + try { + const parsed = JSON.parse(raw); + if (parsed && typeof parsed.error === 'string') { + return parsed.error.slice(0, 200); + } + } catch { + // not JSON + } + return ''; +} + +/** * Translate a fetch/HTTP failure into a stable error code. * @param {Response|undefined} response * @returns {string} error code @@ -73,7 +91,9 @@ export async function listModels(host, signal) { throw toExtensionError(error); } if (!response.ok) { - const e = new Error(`Ollama /api/tags failed: ${response.status}`); + const detail = await response.text().catch(() => ''); + const serverMessage = extractOllamaError(detail); + const e = new Error(`Ollama /api/tags failed: ${response.status}${serverMessage ? ` - ${serverMessage}` : ''}`); e.code = response.status === 404 ? ERROR_CODES.MODEL_NOT_FOUND : ERROR_CODES.STREAM_ERROR; throw e; } @@ -119,7 +139,9 @@ export async function chatStream({ host, model, messages, think, options, onChun } if (!response.ok) { - const e = new Error(`Ollama /api/chat failed: ${response.status}`); + const detail = await response.text().catch(() => ''); + const serverMessage = extractOllamaError(detail); + const e = new Error(`Ollama /api/chat failed: ${response.status}${serverMessage ? ` - ${serverMessage}` : ''}`); e.code = response.status === 404 ? ERROR_CODES.MODEL_NOT_FOUND diff --git a/src/content/content.js b/src/content/content.js index fa5f416..07e6cdc 100644 --- a/src/content/content.js +++ b/src/content/content.js @@ -630,12 +630,20 @@ document.addEventListener('mouseup', onSelectionEvent, { passive: true }); document.addEventListener('keyup', onSelectionEvent, { passive: true }); - // Reposition or hide UI when the viewport changes (Design 5.4). + // Reposition the UI while the viewport changes (Design 5.4). Keep the + // icon visible whenever a selection exists - hiding it on scroll left + // users without any way to translate until they re-selected. window.addEventListener( 'scroll', () => { if (popover.state === POPOVER_STATES.HIDDEN) { - actionButton.hide(); + const selection = readSelection(); + if (selection) { + currentSelectionRect = selection.rect; + actionButton.show(selection.rect); + } else { + actionButton.hide(); + } return; } const selection = readSelection(); |
