|
1 | | -import { useCallback, useMemo, useRef, useState } from "react"; |
| 1 | +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; |
2 | 2 | import ChatInput from "./components/ChatInput.jsx"; |
3 | 3 | import ChatOutput from "./components/ChatOutput.jsx"; |
4 | 4 | import SettingsPanel from "./components/SettingsPanel.jsx"; |
@@ -48,6 +48,20 @@ function resolveMaxNewTokens(input) { |
48 | 48 | } |
49 | 49 |
|
50 | 50 | export default function App() { |
| 51 | + const [isDark, setIsDark] = useState(() => { |
| 52 | + const saved = localStorage.getItem("theme"); |
| 53 | + return saved ? saved === "dark" : false; |
| 54 | + }); |
| 55 | + |
| 56 | + useEffect(() => { |
| 57 | + if (isDark) { |
| 58 | + document.documentElement.classList.add("dark"); |
| 59 | + } else { |
| 60 | + document.documentElement.classList.remove("dark"); |
| 61 | + } |
| 62 | + localStorage.setItem("theme", isDark ? "dark" : "light"); |
| 63 | + }, [isDark]); |
| 64 | + |
51 | 65 | const [prompt, setPrompt] = useState(""); |
52 | 66 | const [settings, setSettings] = useState(DEFAULT_SETTINGS); |
53 | 67 | const [tokens, setTokens] = useState([]); |
@@ -243,6 +257,15 @@ export default function App() { |
243 | 257 | )} |
244 | 258 | </div> |
245 | 259 | <div className="flex items-center gap-2 shrink-0"> |
| 260 | + <button |
| 261 | + type="button" |
| 262 | + className="border border-border px-2 min-h-[36px] hover:bg-panel active:bg-panel touch-manipulation" |
| 263 | + onClick={() => setIsDark((d) => !d)} |
| 264 | + title={isDark ? "Switch to light mode" : "Switch to dark mode"} |
| 265 | + aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"} |
| 266 | + > |
| 267 | + {isDark ? <SunIcon /> : <MoonIcon />} |
| 268 | + </button> |
246 | 269 | <button |
247 | 270 | type="button" |
248 | 271 | className="border border-border px-3 min-h-[36px] uppercase text-xs tracking-wider hover:bg-panel active:bg-panel disabled:opacity-40 disabled:cursor-not-allowed touch-manipulation" |
@@ -403,6 +426,30 @@ function MobileDrawer({ title, onClose, children }) { |
403 | 426 | ); |
404 | 427 | } |
405 | 428 |
|
| 429 | +function SunIcon() { |
| 430 | + return ( |
| 431 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> |
| 432 | + <circle cx="12" cy="12" r="5" /> |
| 433 | + <line x1="12" y1="1" x2="12" y2="3" /> |
| 434 | + <line x1="12" y1="21" x2="12" y2="23" /> |
| 435 | + <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /> |
| 436 | + <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" /> |
| 437 | + <line x1="1" y1="12" x2="3" y2="12" /> |
| 438 | + <line x1="21" y1="12" x2="23" y2="12" /> |
| 439 | + <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /> |
| 440 | + <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" /> |
| 441 | + </svg> |
| 442 | + ); |
| 443 | +} |
| 444 | + |
| 445 | +function MoonIcon() { |
| 446 | + return ( |
| 447 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> |
| 448 | + <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /> |
| 449 | + </svg> |
| 450 | + ); |
| 451 | +} |
| 452 | + |
406 | 453 | function DesktopBarplotPanel({ barplotData, safenudgeActive, onTokenEdit }) { |
407 | 454 | return ( |
408 | 455 | <div className="flex flex-col h-full min-h-0 p-3 overflow-hidden"> |
|
0 commit comments