diff options
Diffstat (limited to 'src/popup/popup.js')
| -rw-r--r-- | src/popup/popup.js | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/src/popup/popup.js b/src/popup/popup.js new file mode 100644 index 0000000..c07851e --- /dev/null +++ b/src/popup/popup.js @@ -0,0 +1,134 @@ +'use strict'; + +import { LANGUAGES, ERROR_CODES, RUNTIME_MESSAGES } from '../shared/constants.js'; +import { defaultSettings } 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 = result.settings || defaultSettings(); + $enabled.checked = Boolean(settings.enabled); + $language.value = settings.targetLanguage || 'ja'; + fillModels(knownModels, settings.model || ''); + if (settings.model) { + // Preserve the stored model even before the first list load. + fillModels(knownModels.length ? knownModels : [settings.model], settings.model); + } +} + +async function persist(patch) { + const result = await browser.storage.local.get({ settings: null }); + const settings = result.settings || defaultSettings(); + const merged = { ...settings, ...patch }; + await browser.storage.local.set({ settings: merged }); +} + +$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(); +}); + +// Init +for (const language of LANGUAGES) { + const option = document.createElement('option'); + option.value = language.code; + option.textContent = language.label; + $language.append(option); +} +void (async () => { + await loadSettings(); + await loadModels(); +})();
\ No newline at end of file |
