fix: resolve syntax errors in extension and finalize Bun migration
Swadhin Biswas committed 7 months ago
462be6b
1 parent dcc8d5a
commit 462be6b
2 files changed
+84 -171
Showing 2 changed files+84-171
▼opengrammar/extension/src/content/highlighter.ts
+58-147
| @@ -125,7 +125,7 @@ function wrapTextWithHighlight( | ||
125 | 125 | }); |
126 | 126 | span.addEventListener("mouseleave", (e) => { |
127 | 127 | e.stopPropagation(); |
128 | - span.style.background = "transparent"; | |
128 | + span.style.background = getInlineBackground(issue.type); | |
129 | 129 | }); |
130 | 130 | |
131 | 131 | // Click to show tooltip |
| @@ -166,7 +166,7 @@ function wrapTextWithHighlight( | ||
166 | 166 | highlightSpan.style.background = getHoverBackground(issue.type); |
167 | 167 | }); |
168 | 168 | highlightSpan.addEventListener("mouseleave", () => { |
169 | - highlightSpan.style.background = "transparent"; | |
169 | + highlightSpan.style.background = getInlineBackground(issue.type); | |
170 | 170 | }); |
171 | 171 | highlightSpan.addEventListener("click", (e) => { |
172 | 172 | e.stopPropagation(); |
| @@ -191,13 +191,13 @@ function getHoverBackground(type: string): string { | ||
191 | 191 | switch (type) { |
192 | 192 | case "grammar": |
193 | 193 | case "spelling": |
194 | - return "rgba(239, 68, 68, 0.1)"; | |
194 | + return "rgba(239, 68, 68, 0.2)"; | |
195 | 195 | case "clarity": |
196 | - return "rgba(245, 158, 11, 0.1)"; | |
196 | + return "rgba(245, 158, 11, 0.25)"; | |
197 | 197 | case "style": |
198 | - return "rgba(59, 130, 246, 0.1)"; | |
198 | + return "rgba(59, 130, 246, 0.2)"; | |
199 | 199 | default: |
200 | - return "rgba(239, 68, 68, 0.1)"; | |
200 | + return "rgba(239, 68, 68, 0.2)"; | |
201 | 201 | } |
202 | 202 | } |
203 | 203 | |
| @@ -248,12 +248,12 @@ export function clearHighlights() { | ||
248 | 248 | uiActive = false; |
249 | 249 | } |
250 | 250 | |
251 | +/** | |
252 | + * Update positions of floating UI elements (badge, assistant, mirror) | |
253 | + */ | |
251 | 254 | export function refreshFloatingDecorations() { |
252 | 255 | if (assistantBubble && inputMirrorTarget) { |
253 | - const summary = assistantBubble.querySelector('[data-og-summary="true"]') as HTMLElement | null; | |
254 | - if (summary && assistantBubble.dataset.issueSummary) { | |
255 | - summary.textContent = assistantBubble.dataset.issueSummary; | |
256 | - } | |
256 | + // Re-position bubble | |
257 | 257 | } |
258 | 258 | |
259 | 259 | if (inputMirrorOverlay && inputMirrorTarget) { |
| @@ -270,54 +270,59 @@ export function refreshFloatingDecorations() { | ||
270 | 270 | }); |
271 | 271 | } |
272 | 272 | |
273 | +function destroyInputMirror() { | |
274 | + cleanupInputMirror?.(); | |
275 | + cleanupInputMirror = null; | |
276 | + inputMirrorOverlay?.remove(); | |
277 | + inputMirrorOverlay = null; | |
278 | + inputMirrorContent = null; | |
279 | + inputMirrorTarget = null; | |
280 | +} | |
281 | + | |
273 | 282 | function showAssistantBubble(element: HTMLElement, issues: Issue[]) { |
274 | 283 | if (assistantBubble) { |
275 | 284 | assistantBubble.remove(); |
276 | 285 | assistantBubble = null; |
277 | 286 | } |
278 | 287 | |
279 | - const summary = summarizeIssues(issues); | |
280 | 288 | const bubble = document.createElement("button"); |
281 | 289 | bubble.className = "opengrammar-assistant"; |
282 | 290 | bubble.type = "button"; |
283 | 291 | bubble.style.cssText = ` |
284 | 292 | position: fixed; |
285 | - right: 16px; | |
293 | + right: 24px; | |
294 | + bottom: 24px; | |
286 | 295 | display: inline-flex; |
287 | 296 | align-items: center; |
288 | 297 | justify-content: center; |
289 | - width: 40px; | |
290 | - height: 40px; | |
298 | + width: 44px; | |
299 | + height: 44px; | |
291 | 300 | padding: 0; |
292 | - padding: 10px 12px; | |
293 | 301 | border-radius: 999px; |
294 | - background: rgba(17, 24, 39, 0.94); | |
295 | 302 | background: rgba(17, 24, 39, 0.96); |
296 | - box-shadow: 0 10px 24px rgba(15, 23, 42, 0.22); | |
297 | 303 | box-shadow: 0 10px 28px rgba(15, 23, 42, 0.26); |
298 | 304 | z-index: 2147483646; |
299 | 305 | cursor: pointer; |
300 | 306 | pointer-events: auto; |
301 | - text-align: left; | |
302 | 307 | backdrop-filter: blur(10px); |
308 | + border: none; | |
309 | + transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
303 | 310 | `; |
304 | 311 | |
305 | - <span style="display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:999px;background:linear-gradient(135deg,#6d5efc,#1d4ed8);font-size:15px;font-weight:700;box-shadow:inset 0 1px 0 rgba(255,255,255,0.2);">G</span> | |
306 | - <span data-og-count="true" style="position:absolute;top:-2px;right:-2px;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:#ef4444;color:white;font-size:10px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 4px 10px rgba(239,68,68,0.35);">${issues.length}</span> | |
312 | + bubble.innerHTML = ` | |
313 | + <span style="position:relative;display:flex;align-items:center;justify-content:center;"> | |
314 | + <span style="display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:999px;background:linear-gradient(135deg,#6d5efc,#1d4ed8);font-size:15px;font-weight:700;color:white;box-shadow:inset 0 1px 0 rgba(255,255,255,0.2);">G</span> | |
315 | + <span style="position:absolute;top:-2px;right:-2px;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:#ef4444;color:white;font-size:10px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 4px 10px rgba(239,68,68,0.35);">${issues.length}</span> | |
307 | 316 | </span> |
308 | 317 | `; |
309 | - bubble.title = summary || `${issues.length} issue${issues.length === 1 ? '' : 's'} found`; | |
310 | - bubble.dataset.issueSummary = summary || 'Click to review suggestions'; | |
311 | 318 | |
312 | 319 | bubble.addEventListener("mouseenter", () => { |
313 | - const count = assistantBubble.querySelector('[data-og-count="true"]') as HTMLElement | null; | |
314 | - if (count && assistantBubble.dataset.issueCount) { | |
315 | - count.textContent = assistantBubble.dataset.issueCount; | |
316 | - } | |
317 | - bubble.style.transform = "translateY(0) scale(1)"; | |
320 | + bubble.style.transform = "scale(1.1) rotate(5deg)"; | |
321 | + }); | |
322 | + bubble.addEventListener("mouseleave", () => { | |
323 | + bubble.style.transform = "scale(1) rotate(0deg)"; | |
318 | 324 | }); |
319 | 325 | |
320 | - bubble.dataset.issueCount = String(issues.length); | |
321 | 326 | bubble.addEventListener("mousedown", (e) => { |
322 | 327 | e.preventDefault(); |
323 | 328 | }); |
| @@ -340,19 +345,6 @@ function showAssistantBubble(element: HTMLElement, issues: Issue[]) { | ||
340 | 345 | assistantBubble = bubble; |
341 | 346 | } |
342 | 347 | |
343 | -function summarizeIssues(issues: Issue[]): string { | |
344 | - const counts = issues.reduce<Record<string, number>>((acc, issue) => { | |
345 | - acc[issue.type] = (acc[issue.type] || 0) + 1; | |
346 | - return acc; | |
347 | - }, {}); | |
348 | - | |
349 | - const ordered: Array<Issue["type"]> = ["grammar", "spelling", "clarity", "style"]; | |
350 | - return ordered | |
351 | - .filter((type) => counts[type]) | |
352 | - .map((type) => `${counts[type]} ${type}`) | |
353 | - .join(" • "); | |
354 | -} | |
355 | - | |
356 | 348 | function showInputMirror(element: HTMLElement, issues: Issue[]) { |
357 | 349 | destroyInputMirror(); |
358 | 350 | |
| @@ -397,35 +389,27 @@ function showInputMirror(element: HTMLElement, issues: Issue[]) { | ||
397 | 389 | const sync = () => syncInputMirrorScroll(element); |
398 | 390 | input.addEventListener('scroll', sync, { passive: true }); |
399 | 391 | |
400 | - inputMirrorOverlay = overlay; | |
401 | - inputMirrorContent = content; | |
402 | - positionInputMirror(element); | |
403 | - syncInputMirrorScroll(element); | |
404 | - | |
405 | 392 | cleanupInputMirror = () => { |
406 | 393 | input.removeEventListener('scroll', sync); |
407 | 394 | input.style.color = input.dataset.ogOriginalColor || ''; |
408 | 395 | input.style.webkitTextFillColor = ''; |
409 | 396 | }; |
410 | -} | |
411 | 397 | |
412 | -function destroyInputMirror() { | |
413 | - cleanupInputMirror?.(); | |
414 | - cleanupInputMirror = null; | |
415 | - inputMirrorOverlay?.remove(); | |
416 | - inputMirrorOverlay = null; | |
417 | - inputMirrorContent = null; | |
418 | - inputMirrorTarget = null; | |
398 | + inputMirrorOverlay = overlay; | |
399 | + inputMirrorContent = content; | |
400 | + positionInputMirror(element); | |
401 | + syncInputMirrorScroll(element); | |
419 | 402 | } |
420 | 403 | |
421 | 404 | function copyTypographyStyles(element: HTMLElement, overlay: HTMLElement, content: HTMLElement) { |
422 | 405 | const styles = window.getComputedStyle(element); |
423 | 406 | const target = element as HTMLInputElement | HTMLTextAreaElement; |
424 | 407 | |
425 | - overlay.style.left = `${element.getBoundingClientRect().left}px`; | |
426 | - overlay.style.top = `${element.getBoundingClientRect().top}px`; | |
427 | - overlay.style.width = `${element.getBoundingClientRect().width}px`; | |
428 | - overlay.style.height = `${element.getBoundingClientRect().height}px`; | |
408 | + const rect = element.getBoundingClientRect(); | |
409 | + overlay.style.left = `${rect.left}px`; | |
410 | + overlay.style.top = `${rect.top}px`; | |
411 | + overlay.style.width = `${rect.width}px`; | |
412 | + overlay.style.height = `${rect.height}px`; | |
429 | 413 | overlay.style.borderRadius = styles.borderRadius; |
430 | 414 | |
431 | 415 | content.style.font = styles.font; |
| @@ -732,52 +716,14 @@ function applySuggestion( | ||
732 | 716 | // Focus back on the input element |
733 | 717 | input.focus(); |
734 | 718 | |
735 | - // Show success on badge: green checkmark, then fade away | |
736 | - document.querySelectorAll(".opengrammar-badge").forEach((b) => { | |
737 | - const badge = b as HTMLElement; | |
738 | - badge.textContent = "\u2713"; | |
739 | - badge.style.background = "linear-gradient(135deg, #10b981, #059669)"; | |
740 | - badge.style.boxShadow = "0 2px 8px rgba(16, 185, 129, 0.5)"; | |
741 | - setTimeout(() => { | |
742 | - badge.style.opacity = "0"; | |
743 | - badge.style.transform = "scale(0.8)"; | |
744 | - badge.style.transition = "opacity 0.3s, transform 0.3s"; | |
745 | - setTimeout(() => badge.remove(), 300); | |
746 | - }, 600); | |
747 | - }); | |
748 | - | |
749 | 719 | // Trigger re-analysis |
750 | 720 | input.dispatchEvent(new Event("input", { bubbles: true })); |
751 | 721 | } else if (element.isContentEditable) { |
752 | - // Replace the highlighted span with corrected text + green success flash | |
753 | - const parent = highlightEl.parentNode; | |
754 | - if (parent) { | |
755 | - const successSpan = document.createElement("span"); | |
756 | - successSpan.className = "opengrammar-success"; | |
757 | - successSpan.textContent = issue.suggestion; | |
758 | - successSpan.style.cssText = ` | |
759 | - background: rgba(16, 185, 129, 0.15); | |
760 | - border-bottom: 2px solid #10b981; | |
761 | - border-radius: 2px; | |
762 | - padding-bottom: 1px; | |
763 | - transition: background 0.5s, border-color 0.5s; | |
764 | - `; | |
765 | - parent.replaceChild(successSpan, highlightEl); | |
766 | - | |
767 | - // Fade out the green highlight then unwrap to plain text | |
768 | - requestAnimationFrame(() => { | |
769 | - setTimeout(() => { | |
770 | - successSpan.style.background = "transparent"; | |
771 | - successSpan.style.borderColor = "transparent"; | |
772 | - setTimeout(() => { | |
773 | - const textNode = document.createTextNode(issue.suggestion); | |
774 | - if (successSpan.parentNode) { | |
775 | - successSpan.parentNode.replaceChild(textNode, successSpan); | |
776 | - textNode.parentNode?.normalize(); | |
777 | - } | |
778 | - }, 500); | |
779 | - }, 50); | |
780 | - }); | |
722 | + // Replace the highlighted span with corrected text | |
723 | + const textNode = document.createTextNode(issue.suggestion); | |
724 | + if (highlightEl.parentNode) { | |
725 | + highlightEl.parentNode.replaceChild(textNode, highlightEl); | |
726 | + textNode.parentNode?.normalize(); | |
781 | 727 | } |
782 | 728 | |
783 | 729 | // Trigger re-analysis |
| @@ -813,23 +759,16 @@ function ignoreIssue(issue: Issue, highlightEl: HTMLElement) { | ||
813 | 759 | |
814 | 760 | // For inline highlights (contenteditable) |
815 | 761 | if (highlightEl.classList.contains("opengrammar-highlight")) { |
816 | - highlightEl.style.borderBottom = "none"; | |
817 | - highlightEl.style.background = "transparent"; | |
818 | - highlightEl.classList.remove("opengrammar-highlight"); | |
819 | - highlightEl.classList.add("opengrammar-ignored"); | |
762 | + const parent = highlightEl.parentNode; | |
763 | + if (parent) { | |
764 | + parent.replaceChild(document.createTextNode(highlightEl.textContent || ""), highlightEl); | |
765 | + parent.normalize(); | |
766 | + } | |
820 | 767 | } |
821 | 768 | |
822 | - // For badge (textarea/input) - show success then fade | |
769 | + // For badge (textarea/input) | |
823 | 770 | if (highlightEl.classList.contains("opengrammar-badge")) { |
824 | - highlightEl.textContent = "\u2713"; | |
825 | - highlightEl.style.background = "linear-gradient(135deg, #10b981, #059669)"; | |
826 | - highlightEl.style.boxShadow = "0 2px 8px rgba(16, 185, 129, 0.5)"; | |
827 | - setTimeout(() => { | |
828 | - highlightEl.style.opacity = "0"; | |
829 | - highlightEl.style.transform = "scale(0.8)"; | |
830 | - highlightEl.style.transition = "opacity 0.3s, transform 0.3s"; | |
831 | - setTimeout(() => highlightEl.remove(), 300); | |
832 | - }, 600); | |
771 | + highlightEl.remove(); | |
833 | 772 | } |
834 | 773 | } |
835 | 774 | |
| @@ -971,7 +910,6 @@ function showIssuePanel( | ||
971 | 910 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
972 | 911 | font-size: 14px; |
973 | 912 | overflow: hidden; |
974 | - animation: og-panel-in 0.15s ease-out; | |
975 | 913 | `; |
976 | 914 | |
977 | 915 | const typeColor = getColor(issue.type); |
| @@ -980,12 +918,6 @@ function showIssuePanel( | ||
980 | 918 | const hasMultiple = issues.length > 1; |
981 | 919 | |
982 | 920 | panel.innerHTML = ` |
983 | - <style> | |
984 | - @keyframes og-panel-in { | |
985 | - from { opacity: 0; transform: translateY(-4px); } | |
986 | - to { opacity: 1; transform: translateY(0); } | |
987 | - } | |
988 | - </style> | |
989 | 921 | ${ |
990 | 922 | hasMultiple |
991 | 923 | ? ` |
| @@ -1056,19 +988,12 @@ function showIssuePanel( | ||
1056 | 988 | ".og-suggestion-click", |
1057 | 989 | ) as HTMLElement; |
1058 | 990 | |
1059 | - // Clicking the green suggestion text also applies the fix (Grammarly-style) | |
1060 | 991 | if (suggestionEl) { |
1061 | 992 | suggestionEl.addEventListener("click", (e) => { |
1062 | 993 | e.stopPropagation(); |
1063 | 994 | applySuggestion(element, issue, anchor); |
1064 | 995 | hideTooltip(); |
1065 | 996 | }); |
1066 | - suggestionEl.addEventListener("mouseenter", () => { | |
1067 | - suggestionEl.style.background = "#dcfce7"; | |
1068 | - }); | |
1069 | - suggestionEl.addEventListener("mouseleave", () => { | |
1070 | - suggestionEl.style.background = "#f0fdf4"; | |
1071 | - }); | |
1072 | 997 | } |
1073 | 998 | |
1074 | 999 | applyBtn.addEventListener("click", (e) => { |
| @@ -1085,20 +1010,6 @@ function showIssuePanel( | ||
1085 | 1010 | hideTooltip(); |
1086 | 1011 | }); |
1087 | 1012 | |
1088 | - // Hover effects | |
1089 | - applyBtn.addEventListener("mouseenter", () => { | |
1090 | - applyBtn.style.background = "#1d4ed8"; | |
1091 | - }); | |
1092 | - applyBtn.addEventListener("mouseleave", () => { | |
1093 | - applyBtn.style.background = "#2563eb"; | |
1094 | - }); | |
1095 | - ignoreBtn.addEventListener("mouseenter", () => { | |
1096 | - ignoreBtn.style.background = "#f9fafb"; | |
1097 | - }); | |
1098 | - ignoreBtn.addEventListener("mouseleave", () => { | |
1099 | - ignoreBtn.style.background = "#ffffff"; | |
1100 | - }); | |
1101 | - | |
1102 | 1013 | // Navigation for multiple issues |
1103 | 1014 | if (hasMultiple) { |
1104 | 1015 | const prevBtn = panel.querySelector(".og-prev-btn") as HTMLButtonElement; |
| @@ -1147,13 +1058,13 @@ function showIssuePanel( | ||
1147 | 1058 | function getColor(type: string): string { |
1148 | 1059 | switch (type) { |
1149 | 1060 | case "grammar": |
1150 | - return "#ef4444"; // Red | |
1061 | + return "#ef4444"; | |
1151 | 1062 | case "spelling": |
1152 | - return "#ef4444"; // Red | |
1063 | + return "#ef4444"; | |
1153 | 1064 | case "clarity": |
1154 | - return "#f59e0b"; // Amber | |
1065 | + return "#f59e0b"; | |
1155 | 1066 | case "style": |
1156 | - return "#3b82f6"; // Blue | |
1067 | + return "#3b82f6"; | |
1157 | 1068 | default: |
1158 | 1069 | return "#ef4444"; |
1159 | 1070 | } |
1160 | 1071 | |
▼opengrammar/extension/src/popup/popup.tsx
+26-24
| @@ -19,30 +19,25 @@ const Popup = () => { | ||
19 | 19 | enabled: true, |
20 | 20 | apiKey: '', |
21 | 21 | model: 'gpt-4o-mini', |
22 | - providerModelMemory: Record<string, string>; | |
23 | - backendUrl: '', | |
22 | + backendUrl: 'http://localhost:8787', | |
24 | 23 | provider: 'openai', |
25 | 24 | customBaseUrl: '', |
26 | 25 | backendHealthy: true, |
27 | - const [providerModelMemory, setProviderModelMemory] = useState<Record<string, string>>({}); | |
28 | 26 | }); |
27 | + const [providerModelMemory, setProviderModelMemory] = useState<Record<string, string>>({}); | |
29 | 28 | const [showApiKey, setShowApiKey] = useState(false); |
30 | 29 | const [loading, setLoading] = useState(true); |
31 | - ['enabled', 'apiKey', 'model', 'backendUrl', 'provider', 'customBaseUrl', 'backendHealthy', 'providerModelMemory'], | |
30 | + const [providers, setProviders] = useState<ProviderConfig[]>([]); | |
32 | 31 | const [availableModels, setAvailableModels] = useState<string[]>([]); |
33 | - const memory = (result.providerModelMemory || {}) as Record<string, string>; | |
34 | - const selectedProvider = result.provider || 'openai'; | |
35 | - const rememberedModel = memory[selectedProvider]; | |
36 | 32 | const [fetchingModels, setFetchingModels] = useState(false); |
37 | 33 | const [showAdvanced, setShowAdvanced] = useState(false); |
38 | 34 | |
39 | - model: rememberedModel || result.model || 'gpt-4o-mini', | |
35 | + useEffect(() => { | |
40 | 36 | loadSettings(); |
41 | - provider: selectedProvider, | |
37 | + loadProviders(); | |
42 | 38 | }, []); |
43 | 39 | |
44 | 40 | useEffect(() => { |
45 | - setProviderModelMemory(memory); | |
46 | 41 | if (settings.provider) { |
47 | 42 | loadModels(); |
48 | 43 | } |
| @@ -50,23 +45,22 @@ const Popup = () => { | ||
50 | 45 | |
51 | 46 | const loadSettings = () => { |
52 | 47 | chrome.storage.sync.get( |
53 | - ['enabled', 'apiKey', 'model', 'backendUrl', 'provider', 'customBaseUrl', 'backendHealthy'], | |
54 | - const nextModelMemory = { | |
55 | - ...providerModelMemory, | |
56 | - ...(newSettings.provider ? { [newSettings.provider]: newSettings.model } : {}), | |
57 | - }; | |
58 | - setProviderModelMemory(nextModelMemory); | |
48 | + ['enabled', 'apiKey', 'model', 'backendUrl', 'provider', 'customBaseUrl', 'backendHealthy', 'providerModelMemory'], | |
59 | 49 | (result) => { |
50 | + const memory = (result.providerModelMemory || {}) as Record<string, string>; | |
51 | + const selectedProvider = result.provider || 'openai'; | |
52 | + const rememberedModel = memory[selectedProvider]; | |
53 | + | |
60 | 54 | setSettings({ |
61 | 55 | enabled: result.enabled !== false, |
62 | 56 | apiKey: result.apiKey || '', |
63 | - model: result.model || 'gpt-4o-mini', | |
57 | + model: rememberedModel || result.model || 'gpt-4o-mini', | |
64 | 58 | backendUrl: result.backendUrl || 'http://localhost:8787', |
65 | - provider: result.provider || 'openai', | |
59 | + provider: selectedProvider, | |
66 | 60 | customBaseUrl: result.customBaseUrl || '', |
67 | - providerModelMemory: nextModelMemory, | |
68 | 61 | backendHealthy: result.backendHealthy !== false, |
69 | 62 | }); |
63 | + setProviderModelMemory(memory); | |
70 | 64 | setLoading(false); |
71 | 65 | } |
72 | 66 | ); |
| @@ -74,8 +68,7 @@ const Popup = () => { | ||
74 | 68 | |
75 | 69 | const loadProviders = async () => { |
76 | 70 | try { |
77 | - const rememberedModel = providerModelMemory[provider]; | |
78 | - saveSettings({ provider, model: rememberedModel || getDefaultModel(provider) }); | |
71 | + const response = await chrome.runtime.sendMessage({ type: 'GET_PROVIDERS' }); | |
79 | 72 | if (response.providers) { |
80 | 73 | setProviders(response.providers); |
81 | 74 | } |
| @@ -106,6 +99,13 @@ const Popup = () => { | ||
106 | 99 | const saveSettings = (updates: Partial<Settings>) => { |
107 | 100 | const newSettings = { ...settings, ...updates }; |
108 | 101 | setSettings(newSettings); |
102 | + | |
103 | + const nextModelMemory = { | |
104 | + ...providerModelMemory, | |
105 | + ...(newSettings.provider ? { [newSettings.provider]: newSettings.model } : {}), | |
106 | + }; | |
107 | + setProviderModelMemory(nextModelMemory); | |
108 | + | |
109 | 109 | chrome.storage.sync.set( |
110 | 110 | { |
111 | 111 | enabled: newSettings.enabled, |
| @@ -114,6 +114,7 @@ const Popup = () => { | ||
114 | 114 | backendUrl: newSettings.backendUrl, |
115 | 115 | provider: newSettings.provider, |
116 | 116 | customBaseUrl: newSettings.customBaseUrl, |
117 | + providerModelMemory: nextModelMemory, | |
117 | 118 | }, |
118 | 119 | () => { |
119 | 120 | console.log('Settings saved'); |
| @@ -123,7 +124,8 @@ const Popup = () => { | ||
123 | 124 | |
124 | 125 | const handleProviderChange = (e: React.ChangeEvent<HTMLSelectElement>) => { |
125 | 126 | const provider = e.target.value; |
126 | - saveSettings({ provider, model: getDefaultModel(provider) }); | |
127 | + const rememberedModel = providerModelMemory[provider]; | |
128 | + saveSettings({ provider, model: rememberedModel || getDefaultModel(provider) }); | |
127 | 129 | }; |
128 | 130 | |
129 | 131 | const getDefaultModel = (provider: string): string => { |
| @@ -190,7 +192,7 @@ const Popup = () => { | ||
190 | 192 | |
191 | 193 | <div className="memory-card"> |
192 | 194 | <div> |
193 | - <strong>Remembered</strong> | |
195 | + <strong>Active AI</strong> | |
194 | 196 | <span>{selectedProvider?.name || 'OpenAI'} · {settings.model}</span> |
195 | 197 | </div> |
196 | 198 | <span className={`status-dot ${settings.backendHealthy ? 'healthy' : 'unhealthy'}`}></span> |
| @@ -309,7 +311,7 @@ const Popup = () => { | ||
309 | 311 | <label> |
310 | 312 | <span className="setting-label with-icon"><Sparkles size={14} /> Backend URL</span> |
311 | 313 | <span className="setting-hint"> |
312 | - Auto-saved in browser. Default is localhost. | |
314 | + Default is http://localhost:8787 | |
313 | 315 | </span> |
314 | 316 | </label> |
315 | 317 | <input |
316 | 318 | |