'use strict'; import { LANGUAGES, ERROR_CODES, RUNTIME_MESSAGES } from '../shared/constants.js'; import { defaultSettings, sanitizeSettings } from '../shared/settings-schema.js'; const $enabled = /** @type {HTMLInputElement} */ (document.getElementById('enabled-toggle')); const $model = /** @type {HTMLSelectElement} */ (document.getElementById('model-select')); const $reload = /** @type {HTMLButtonElement} */ (document.getElementById('reload-models')); const $modelStatus = /** @type {HTMLParagraphElement} */ (document.getElementById('model-status')); const $language = /** @type {HTMLSelectElement} */ (document.getElementById('language-select')); const $openOptions = /** @type {HTMLButtonElement} */ (document.getElementById('open-options')); /** @type {string[]} current model list for change detection */ let knownModels = []; function fillLanguages(selected) { $language.textContent = ''; for (const lang of LANGUAGES) { const option = document.createElement('option'); option.value = lang.code; option.textContent = lang.label; if (lang.code === selected) option.selected = true; $language.append(option); } } function fillModels(models, selected) { $model.textContent = ''; if (models.length === 0) { const option = document.createElement('option'); option.value = ''; option.textContent = '(モデルなし / Ollama未起動)'; $model.append(option); } for (const name of models) { const option = document.createElement('option'); option.value = name; option.textContent = name; if (name === selected) option.selected = true; $model.append(option); } if (selected && !models.includes(selected)) { const option = document.createElement('option'); option.value = selected; option.textContent = `${selected}(一覧にありません)`; option.selected = true; $model.append(option); } } function showStatus(message, isError) { $modelStatus.hidden = false; $modelStatus.textContent = message; $modelStatus.classList.toggle('error', Boolean(isError)); } async function loadModels() { showStatus('モデル一覧を取得中…', false); try { const response = await browser.runtime.sendMessage({ type: RUNTIME_MESSAGES.LIST_MODELS }); if (response && response.ok && Array.isArray(response.models)) { knownModels = response.models; const current = $model.value; fillModels(response.models, current); if (response.models.length === 0) { $modelStatus.hidden = true; } else { $modelStatus.hidden = true; } } else { const code = response && response.error; if (code === ERROR_CODES.OLLAMA_UNAVAILABLE) { showStatus('Ollamaに接続できません。', true); } else { showStatus('モデル一覧の取得に失敗しました。', true); } fillModels([], $model.value); } } catch { showStatus('モデル一覧の取得に失敗しました。', true); } } async function loadSettings() { const result = await browser.storage.local.get({ settings: null }); const settings = sanitizeSettings(result.settings || defaultSettings()); $enabled.checked = Boolean(settings.enabled); for (const lang of LANGUAGES) { const option = document.createElement('option'); option.value = lang.code; option.textContent = lang.label; $language.append(option); } $language.value = settings.targetLanguage || 'ja'; fillModels(knownModels, settings.model || ''); } async function persist(patch) { const result = await browser.storage.local.get({ settings: null }); const settings = sanitizeSettings(result.settings || defaultSettings()); const merged = { ...settings, ...patch }; // Sanitize before saving so unrelated fields stay valid. const validated = sanitizeSettings(merged); await browser.storage.local.set({ settings: validated }); } $enabled.addEventListener('change', () => { void persist({ enabled: $enabled.checked }); }); $model.addEventListener('change', () => { const value = $model.value; if (value) void persist({ model: value }); }); $language.addEventListener('change', () => { void persist({ targetLanguage: $language.value }); }); $reload.addEventListener('click', () => { void loadModels(); }); $openOptions.addEventListener('click', () => { browser.runtime.openOptionsPage(); }); void (async () => { await loadSettings(); await loadModels(); })();