diff --git a/agrifine-extension/dist/background.js b/agrifine-extension/dist/background.js index e85c658..a5de481 100644 --- a/agrifine-extension/dist/background.js +++ b/agrifine-extension/dist/background.js @@ -632,7 +632,9 @@ var KEYS = { FIELD_PROFILES: 'agrifine_field_profiles', SETTINGS: 'agrifine_settings', FARM_MEMORY: 'agrifine_farm_memory', - API_KEY: 'agrifine_api_key' // session only + API_KEY: 'agrifine_api_key', + // session storage (always) + API_KEY_SAVED: 'agrifine_api_key_saved' // local storage (when user opts to remember) }; // ── Generic helpers ────────────────────────────────────────────────────────── @@ -1308,9 +1310,14 @@ chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })["catch"](console.error); +// Restore saved API key into session on service worker startup +(0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.localGet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.KEYS.API_KEY_SAVED).then(function (saved) { + if (saved) (0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.sessionSet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.KEYS.API_KEY, saved)["catch"](function () {}); +})["catch"](function () {}); + // ── Message router ──────────────────────────────────────────────────────────── chrome.runtime.onMessage.addListener(function (message, _sender, sendResponse) { - var _message$payload; + var _message$payload2; switch (message.type) { case 'ANTHROPIC_REQUEST': handleAnthropicRequest(message.payload).then(sendResponse)["catch"](function (err) { @@ -1322,16 +1329,27 @@ chrome.runtime.onMessage.addListener(function (message, _sender, sendResponse) { // keep channel open for async response case 'SET_API_KEY': - (0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.sessionSet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.KEYS.API_KEY, message.payload.key).then(function () { - return sendResponse({ - ok: true + { + var _message$payload = message.payload, + key = _message$payload.key, + remember = _message$payload.remember; + var ops = [(0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.sessionSet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.KEYS.API_KEY, key)]; + if (remember) { + ops.push((0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.localSet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.KEYS.API_KEY_SAVED, key)); + } else { + ops.push((0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.localSet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.KEYS.API_KEY_SAVED, null)); + } + Promise.all(ops).then(function () { + return sendResponse({ + ok: true + }); + })["catch"](function (err) { + return sendResponse({ + error: err.message + }); }); - })["catch"](function (err) { - return sendResponse({ - error: err.message - }); - }); - return true; + return true; + } case 'GET_PAGE_CONTENT': sendResponse({ ok: true @@ -1359,7 +1377,7 @@ chrome.runtime.onMessage.addListener(function (message, _sender, sendResponse) { }); return true; case 'READ_TAB_CONTENT': - readTabContent((_message$payload = message.payload) === null || _message$payload === void 0 ? void 0 : _message$payload.tab_id).then(sendResponse)["catch"](function (err) { + readTabContent((_message$payload2 = message.payload) === null || _message$payload2 === void 0 ? void 0 : _message$payload2.tab_id).then(sendResponse)["catch"](function (err) { return sendResponse({ error: err.message }); diff --git a/agrifine-extension/dist/sidebar.css b/agrifine-extension/dist/sidebar.css index c005e84..8197fcb 100644 --- a/agrifine-extension/dist/sidebar.css +++ b/agrifine-extension/dist/sidebar.css @@ -606,6 +606,9 @@ video { .h-10 { height: 2.5rem; } +.h-3 { + height: 0.75rem; +} .h-4 { height: 1rem; } @@ -639,6 +642,9 @@ video { .w-10 { width: 2.5rem; } +.w-3 { + width: 0.75rem; +} .w-4 { width: 1rem; } @@ -673,6 +679,11 @@ video { .cursor-pointer { cursor: pointer; } +.select-none { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} .resize-none { resize: none; } @@ -1015,6 +1026,9 @@ video { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } +.accent-agri-500 { + accent-color: #22c55e; +} .opacity-0 { opacity: 0; } diff --git a/agrifine-extension/dist/sidebar.html b/agrifine-extension/dist/sidebar.html index f8ca317..5fd43ec 100644 --- a/agrifine-extension/dist/sidebar.html +++ b/agrifine-extension/dist/sidebar.html @@ -35,7 +35,14 @@ -

+
+ + +
+

diff --git a/agrifine-extension/dist/sidebar.js b/agrifine-extension/dist/sidebar.js index 9232e5d..62f4644 100644 --- a/agrifine-extension/dist/sidebar.js +++ b/agrifine-extension/dist/sidebar.js @@ -3290,7 +3290,9 @@ var KEYS = { FIELD_PROFILES: 'agrifine_field_profiles', SETTINGS: 'agrifine_settings', FARM_MEMORY: 'agrifine_farm_memory', - API_KEY: 'agrifine_api_key' // session only + API_KEY: 'agrifine_api_key', + // session storage (always) + API_KEY_SAVED: 'agrifine_api_key_saved' // local storage (when user opts to remember) }; // ── Generic helpers ────────────────────────────────────────────────────────── @@ -4202,16 +4204,16 @@ function activateTab(_x) { return _activateTab.apply(this, arguments); } // ── Settings panel ──────────────────────────────────────────────────────────── function _activateTab() { - _activateTab = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(id) { + _activateTab = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(id) { var main; - return _regenerator().w(function (_context5) { - while (1) switch (_context5.n) { + return _regenerator().w(function (_context6) { + while (1) switch (_context6.n) { case 0: if (moduleMap[id]) { - _context5.n = 1; + _context6.n = 1; break; } - return _context5.a(2); + return _context6.a(2); case 1: activeModuleId = id; document.querySelectorAll('.tab-btn').forEach(function (btn) { @@ -4221,12 +4223,12 @@ function _activateTab() { }); main = document.getElementById('main-content'); main.innerHTML = ''; - _context5.n = 2; + _context6.n = 2; return moduleMap[id].render(main); case 2: - return _context5.a(2); + return _context6.a(2); } - }, _callee5); + }, _callee6); })); return _activateTab.apply(this, arguments); } @@ -4236,86 +4238,118 @@ function setupSettings() { var saveBtn = document.getElementById('btn-save-key'); var input = document.getElementById('api-key-input'); var status = document.getElementById('api-key-status'); + var rememberChk = document.getElementById('api-key-remember'); + var forgetBtn = document.getElementById('btn-forget-key'); var agRefineInput = document.getElementById('agrefine-url-input'); var agRefineStatus = document.getElementById('agrefine-url-status'); var agRefineSaveBtn = document.getElementById('btn-save-agrefine-url'); btn.addEventListener('click', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() { - var existing, agUrl; + var sessionKey, savedKey, agUrl; return _regenerator().w(function (_context) { while (1) switch (_context.n) { case 0: panel.classList.toggle('hidden'); if (panel.classList.contains('hidden')) { - _context.n = 3; + _context.n = 4; break; } _context.n = 1; return (0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_7__.sessionGet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_7__.KEYS.API_KEY); case 1: - existing = _context.v; - if (existing) { + sessionKey = _context.v; + _context.n = 2; + return (0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_7__.localGet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_7__.KEYS.API_KEY_SAVED); + case 2: + savedKey = _context.v; + if (sessionKey || savedKey) { input.value = ''; input.placeholder = 'Key set — enter new key to replace'; - status.textContent = '✓ API key is active this session'; + status.textContent = savedKey ? '✓ Key saved across sessions' : '✓ Key active this session only'; + status.style.color = '#4ade80'; } - _context.n = 2; + if (savedKey) { + rememberChk.checked = true; + forgetBtn.classList.remove('hidden'); + } + _context.n = 3; return (0,_utils_agrefine_bridge_js__WEBPACK_IMPORTED_MODULE_8__.getAgRefineUrl)(); - case 2: + case 3: agUrl = _context.v; if (agUrl) agRefineInput.value = agUrl; - case 3: + case 4: return _context.a(2); } }, _callee); }))); - agRefineSaveBtn.addEventListener('click', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() { - var url; + saveBtn.addEventListener('click', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() { + var key, remember; return _regenerator().w(function (_context2) { while (1) switch (_context2.n) { + case 0: + key = input.value.trim(); + if (key.startsWith('sk-ant-')) { + _context2.n = 1; + break; + } + status.textContent = 'Key must start with sk-ant-'; + status.style.color = '#f87171'; + return _context2.a(2); + case 1: + remember = rememberChk.checked; + _context2.n = 2; + return chrome.runtime.sendMessage({ + type: 'SET_API_KEY', + payload: { + key: key, + remember: remember + } + }); + case 2: + input.value = ''; + input.placeholder = 'Key set — enter new key to replace'; + status.textContent = remember ? '✓ Key saved across sessions' : '✓ Saved for this session'; + status.style.color = '#4ade80'; + forgetBtn.classList.toggle('hidden', !remember); + case 3: + return _context2.a(2); + } + }, _callee2); + }))); + forgetBtn.addEventListener('click', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() { + return _regenerator().w(function (_context3) { + while (1) switch (_context3.n) { + case 0: + _context3.n = 1; + return (0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_7__.localSet)(_utils_storage_js__WEBPACK_IMPORTED_MODULE_7__.KEYS.API_KEY_SAVED, null); + case 1: + rememberChk.checked = false; + forgetBtn.classList.add('hidden'); + status.textContent = 'Saved key removed'; + status.style.color = '#3d4f66'; + case 2: + return _context3.a(2); + } + }, _callee3); + }))); + agRefineSaveBtn.addEventListener('click', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() { + var url; + return _regenerator().w(function (_context4) { + while (1) switch (_context4.n) { case 0: url = agRefineInput.value.trim(); - _context2.n = 1; + _context4.n = 1; return (0,_utils_agrefine_bridge_js__WEBPACK_IMPORTED_MODULE_8__.setAgRefineUrl)(url); case 1: - agRefineStatus.textContent = url ? "\u2713 AG-Refine URL saved" : '✓ Cleared'; + agRefineStatus.textContent = url ? '✓ AG-Refine URL saved' : '✓ Cleared'; agRefineStatus.style.color = '#4ade80'; setTimeout(function () { agRefineStatus.style.color = '#3d4f66'; agRefineStatus.textContent = 'Used to sync fields and outputs from your AG-Refine app.'; }, 2500); case 2: - return _context2.a(2); + return _context4.a(2); } - }, _callee2); - }))); - saveBtn.addEventListener('click', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() { - var key; - return _regenerator().w(function (_context3) { - while (1) switch (_context3.n) { - case 0: - key = input.value.trim(); - if (key.startsWith('sk-ant-')) { - _context3.n = 1; - break; - } - status.textContent = 'Key must start with sk-ant-'; - return _context3.a(2); - case 1: - _context3.n = 2; - return chrome.runtime.sendMessage({ - type: 'SET_API_KEY', - payload: { - key: key - } - }); - case 2: - input.value = ''; - input.placeholder = 'Key set — enter new key to replace'; - status.textContent = '✓ Saved for this session'; - case 3: - return _context3.a(2); - } - }, _callee3); + }, _callee4); }))); } @@ -4332,19 +4366,19 @@ function keepAlive() { } // ── Init ────────────────────────────────────────────────────────────────────── -document.addEventListener('DOMContentLoaded', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() { - return _regenerator().w(function (_context4) { - while (1) switch (_context4.n) { +document.addEventListener('DOMContentLoaded', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() { + return _regenerator().w(function (_context5) { + while (1) switch (_context5.n) { case 0: setupTabs(); setupSettings(); keepAlive(); - _context4.n = 1; + _context5.n = 1; return activateTab(activeModuleId); case 1: - return _context4.a(2); + return _context5.a(2); } - }, _callee4); + }, _callee5); }))); })(); diff --git a/agrifine-extension/src/background/index.js b/agrifine-extension/src/background/index.js index 523a159..f88f8d8 100644 --- a/agrifine-extension/src/background/index.js +++ b/agrifine-extension/src/background/index.js @@ -1,10 +1,15 @@ -import { sessionGet, sessionSet, KEYS } from '../utils/storage.js'; +import { sessionGet, sessionSet, localGet, localSet, KEYS } from '../utils/storage.js'; import { fetchAnthropic } from '../utils/api.js'; import { syncFromAgRefine } from '../utils/agrefine-bridge.js'; // Open the side panel when the action icon is clicked chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(console.error); +// Restore saved API key into session on service worker startup +localGet(KEYS.API_KEY_SAVED).then((saved) => { + if (saved) sessionSet(KEYS.API_KEY, saved).catch(() => {}); +}).catch(() => {}); + // ── Message router ──────────────────────────────────────────────────────────── chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { switch (message.type) { @@ -14,11 +19,19 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { ); return true; // keep channel open for async response - case 'SET_API_KEY': - sessionSet(KEYS.API_KEY, message.payload.key) + case 'SET_API_KEY': { + const { key, remember } = message.payload; + const ops = [sessionSet(KEYS.API_KEY, key)]; + if (remember) { + ops.push(localSet(KEYS.API_KEY_SAVED, key)); + } else { + ops.push(localSet(KEYS.API_KEY_SAVED, null)); + } + Promise.all(ops) .then(() => sendResponse({ ok: true })) .catch((err) => sendResponse({ error: err.message })); return true; + } case 'GET_PAGE_CONTENT': sendResponse({ ok: true }); diff --git a/agrifine-extension/src/sidebar/index.js b/agrifine-extension/src/sidebar/index.js index 8483516..6bde897 100644 --- a/agrifine-extension/src/sidebar/index.js +++ b/agrifine-extension/src/sidebar/index.js @@ -5,7 +5,7 @@ import { FieldProfileModule } from '../modules/field-profile/index.js'; import { DashboardModule } from '../modules/dashboard/index.js'; import { CarbonEstimatorModule } from '../modules/carbon-estimator/index.js'; import { AgRefineModule } from '../ag-refine/index.js'; -import { sessionSet, sessionGet, KEYS } from '../utils/storage.js'; +import { sessionSet, sessionGet, localGet, localSet, KEYS } from '../utils/storage.js'; import { getAgRefineUrl, setAgRefineUrl } from '../utils/agrefine-bridge.js'; // ── Module registry ─────────────────────────────────────────────────────────── @@ -50,6 +50,8 @@ function setupSettings() { const saveBtn = document.getElementById('btn-save-key'); const input = document.getElementById('api-key-input'); const status = document.getElementById('api-key-status'); + const rememberChk = document.getElementById('api-key-remember'); + const forgetBtn = document.getElementById('btn-forget-key'); const agRefineInput = document.getElementById('agrefine-url-input'); const agRefineStatus = document.getElementById('agrefine-url-status'); @@ -58,35 +60,58 @@ function setupSettings() { btn.addEventListener('click', async () => { panel.classList.toggle('hidden'); if (!panel.classList.contains('hidden')) { - const existing = await sessionGet(KEYS.API_KEY); - if (existing) { + const sessionKey = await sessionGet(KEYS.API_KEY); + const savedKey = await localGet(KEYS.API_KEY_SAVED); + if (sessionKey || savedKey) { input.value = ''; input.placeholder = 'Key set — enter new key to replace'; - status.textContent = '✓ API key is active this session'; + status.textContent = savedKey + ? '✓ Key saved across sessions' + : '✓ Key active this session only'; + status.style.color = '#4ade80'; + } + if (savedKey) { + rememberChk.checked = true; + forgetBtn.classList.remove('hidden'); } const agUrl = await getAgRefineUrl(); if (agUrl) agRefineInput.value = agUrl; } }); - agRefineSaveBtn.addEventListener('click', async () => { - const url = agRefineInput.value.trim(); - await setAgRefineUrl(url); - agRefineStatus.textContent = url ? `✓ AG-Refine URL saved` : '✓ Cleared'; - agRefineStatus.style.color = '#4ade80'; - setTimeout(() => { agRefineStatus.style.color = '#3d4f66'; agRefineStatus.textContent = 'Used to sync fields and outputs from your AG-Refine app.'; }, 2500); - }); - saveBtn.addEventListener('click', async () => { const key = input.value.trim(); if (!key.startsWith('sk-ant-')) { status.textContent = 'Key must start with sk-ant-'; + status.style.color = '#f87171'; return; } - await chrome.runtime.sendMessage({ type: 'SET_API_KEY', payload: { key } }); + const remember = rememberChk.checked; + await chrome.runtime.sendMessage({ type: 'SET_API_KEY', payload: { key, remember } }); input.value = ''; input.placeholder = 'Key set — enter new key to replace'; - status.textContent = '✓ Saved for this session'; + status.textContent = remember ? '✓ Key saved across sessions' : '✓ Saved for this session'; + status.style.color = '#4ade80'; + forgetBtn.classList.toggle('hidden', !remember); + }); + + forgetBtn.addEventListener('click', async () => { + await localSet(KEYS.API_KEY_SAVED, null); + rememberChk.checked = false; + forgetBtn.classList.add('hidden'); + status.textContent = 'Saved key removed'; + status.style.color = '#3d4f66'; + }); + + agRefineSaveBtn.addEventListener('click', async () => { + const url = agRefineInput.value.trim(); + await setAgRefineUrl(url); + agRefineStatus.textContent = url ? '✓ AG-Refine URL saved' : '✓ Cleared'; + agRefineStatus.style.color = '#4ade80'; + setTimeout(() => { + agRefineStatus.style.color = '#3d4f66'; + agRefineStatus.textContent = 'Used to sync fields and outputs from your AG-Refine app.'; + }, 2500); }); } diff --git a/agrifine-extension/src/sidebar/sidebar.html b/agrifine-extension/src/sidebar/sidebar.html index f8ca317..5fd43ec 100644 --- a/agrifine-extension/src/sidebar/sidebar.html +++ b/agrifine-extension/src/sidebar/sidebar.html @@ -35,7 +35,14 @@
-

+
+ + +
+

diff --git a/agrifine-extension/src/utils/storage.js b/agrifine-extension/src/utils/storage.js index abfe4b1..11d545b 100644 --- a/agrifine-extension/src/utils/storage.js +++ b/agrifine-extension/src/utils/storage.js @@ -32,7 +32,8 @@ export const KEYS = { FIELD_PROFILES: 'agrifine_field_profiles', SETTINGS: 'agrifine_settings', FARM_MEMORY: 'agrifine_farm_memory', - API_KEY: 'agrifine_api_key', // session only + API_KEY: 'agrifine_api_key', // session storage (always) + API_KEY_SAVED: 'agrifine_api_key_saved', // local storage (when user opts to remember) }; // ── Generic helpers ──────────────────────────────────────────────────────────