|
2 | 2 | import { onMount } from 'svelte'; |
3 | 3 | import ThemeSelector from '../ui/ThemeSelector.svelte'; |
4 | 4 | import DataManager from '../ui/DataManager.svelte'; |
5 | | - import { User, Key, Settings, CheckCircle, AlertCircle } from 'lucide-svelte'; |
| 5 | + import { ChevronLeft, ChevronRight, User, Key, Settings, CheckCircle, AlertCircle, Terminal } from 'lucide-svelte'; |
6 | 6 | import { guardianHelpers } from '$lib/stores/guardian.js'; |
7 | 7 | import { aiAnalysisHelpers } from '$lib/stores/ai-analysis.js'; |
8 | 8 |
|
|
16 | 16 |
|
17 | 17 | let apiKeyStatus = 'unchecked'; // unchecked, checking, valid, invalid |
18 | 18 | let showDataManager = false; |
| 19 | + |
| 20 | + // CLI-style editing states |
| 21 | + let editingField: string | null = null; |
| 22 | + let themes = ['everforest', 'gruvbox', 'tokyo-night', 'nord']; |
| 23 | + let currentTheme = 'everforest'; |
| 24 | + let themeIndex = 0; |
19 | 25 |
|
20 | 26 | onMount(() => { |
21 | 27 | // Load saved guardian data |
|
25 | 31 | apiKeyInput = saved.apiKey || ''; |
26 | 32 | preferences = { ...preferences, ...saved.preferences }; |
27 | 33 | } |
| 34 | + |
| 35 | + // Get current theme from document |
| 36 | + const savedTheme = localStorage.getItem('petalytics-theme') || 'everforest'; |
| 37 | + currentTheme = savedTheme; |
| 38 | + themeIndex = themes.indexOf(savedTheme); |
28 | 39 | }); |
29 | 40 |
|
| 41 | + function toggleTheme(direction: 'prev' | 'next') { |
| 42 | + if (direction === 'next') { |
| 43 | + themeIndex = (themeIndex + 1) % themes.length; |
| 44 | + } else { |
| 45 | + themeIndex = (themeIndex - 1 + themes.length) % themes.length; |
| 46 | + } |
| 47 | + currentTheme = themes[themeIndex]; |
| 48 | + |
| 49 | + // Apply theme |
| 50 | + document.documentElement.className = currentTheme; |
| 51 | + localStorage.setItem('petalytics-theme', currentTheme); |
| 52 | + } |
| 53 | +
|
| 54 | + function togglePreference(key: keyof typeof preferences) { |
| 55 | + preferences[key] = !preferences[key]; |
| 56 | + handlePreferenceChange(key); |
| 57 | + } |
| 58 | +
|
| 59 | + function startEdit(field: string) { |
| 60 | + editingField = field; |
| 61 | + } |
| 62 | +
|
| 63 | + function stopEdit() { |
| 64 | + editingField = null; |
| 65 | + saveGuardianInfo(); |
| 66 | + if (editingField === 'apiKey') { |
| 67 | + validateApiKey(); |
| 68 | + } |
| 69 | + } |
| 70 | +
|
| 71 | + function handleKeydown(event: KeyboardEvent, field: string) { |
| 72 | + if (event.key === 'Enter') { |
| 73 | + stopEdit(); |
| 74 | + } else if (event.key === 'Escape') { |
| 75 | + editingField = null; |
| 76 | + } |
| 77 | + } |
| 78 | +
|
30 | 79 | async function validateApiKey() { |
31 | 80 | if (!apiKeyInput.trim()) { |
32 | 81 | apiKeyStatus = 'unchecked'; |
|
75 | 124 | } |
76 | 125 | </script> |
77 | 126 |
|
78 | | -<div class="panel-container h-full flex flex-col"> |
79 | | - <!-- Panel Header --> |
80 | | - <div class="panel-header"> |
81 | | - <div class="flex items-center space-x-2"> |
82 | | - <User size={18} style="color: var(--petalytics-accent);" /> |
83 | | - <h2 class="text-lg font-semibold">Guardian Settings</h2> |
| 127 | +<div class="guardian-panel h-full" style="background: var(--petalytics-bg);"> |
| 128 | + <!-- CLI-style header --> |
| 129 | + <div class="cli-header p-3 border-b font-mono text-sm" style="border-color: var(--petalytics-border); background: var(--petalytics-surface);"> |
| 130 | + <div class="flex items-center space-x-2" style="color: var(--petalytics-pine);"> |
| 131 | + <Terminal size={14} /> |
| 132 | + <span>guardian@petalytics:~$</span> |
84 | 133 | </div> |
85 | 134 | </div> |
86 | 135 |
|
87 | | - <!-- Panel Content --> |
88 | | - <div class="panel-content flex-1 p-4 space-y-4 overflow-y-auto"> |
89 | | - <!-- Guardian Info --> |
90 | | - <div class="section"> |
91 | | - <label |
92 | | - for="guardian-name" |
93 | | - class="block text-sm font-medium mb-2" |
94 | | - style="color: var(--petalytics-subtle);" |
95 | | - > |
96 | | - Your Name |
97 | | - </label> |
98 | | - <input |
99 | | - id="guardian-name" |
100 | | - type="text" |
101 | | - bind:value={guardianName} |
102 | | - on:blur={saveGuardianInfo} |
103 | | - class="input w-full" |
104 | | - placeholder="Pet Guardian Name" |
105 | | - /> |
| 136 | + <div class="cli-content p-3 space-y-1 font-mono text-sm overflow-y-auto" style="color: var(--petalytics-text);"> |
| 137 | + |
| 138 | + <!-- Guardian name row --> |
| 139 | + <div class="cli-row flex items-center space-x-2 hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => startEdit('guardian')}> |
| 140 | + <span style="color: var(--petalytics-subtle);">></span> |
| 141 | + <span style="color: var(--petalytics-foam);">guardian:</span> |
| 142 | + {#if editingField === 'guardian'} |
| 143 | + <input |
| 144 | + bind:value={guardianName} |
| 145 | + on:blur={stopEdit} |
| 146 | + on:keydown={(e) => handleKeydown(e, 'guardian')} |
| 147 | + class="bg-transparent border-none outline-none flex-1" |
| 148 | + style="color: var(--petalytics-text);" |
| 149 | + placeholder="Pet Guardian Name" |
| 150 | + autofocus |
| 151 | + /> |
| 152 | + {:else} |
| 153 | + <span class="flex-1" style="color: var(--petalytics-text);"> |
| 154 | + {guardianName || 'Not set'} |
| 155 | + </span> |
| 156 | + {/if} |
106 | 157 | </div> |
107 | 158 |
|
108 | | - <!-- API Key Section --> |
109 | | - <div class="section"> |
110 | | - <label class="block text-sm font-medium mb-2" style="color: var(--petalytics-subtle);"> |
111 | | - <Key size={16} class="inline mr-1" /> |
112 | | - OpenRouter API Key |
113 | | - </label> |
114 | | - <div class="flex space-x-2"> |
| 159 | + <!-- API Key row --> |
| 160 | + <div class="cli-row flex items-center space-x-2 hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => startEdit('apiKey')}> |
| 161 | + <span style="color: var(--petalytics-subtle);">></span> |
| 162 | + <span style="color: var(--petalytics-foam);">api_key:</span> |
| 163 | + {#if editingField === 'apiKey'} |
115 | 164 | <input |
116 | 165 | type="password" |
117 | 166 | bind:value={apiKeyInput} |
118 | | - on:blur={validateApiKey} |
119 | | - class="input flex-1" |
| 167 | + on:blur={stopEdit} |
| 168 | + on:keydown={(e) => handleKeydown(e, 'apiKey')} |
| 169 | + class="bg-transparent border-none outline-none flex-1" |
| 170 | + style="color: var(--petalytics-text);" |
120 | 171 | placeholder="sk-or-..." |
| 172 | + autofocus |
121 | 173 | /> |
122 | | - <div class="flex items-center"> |
123 | | - {#if apiKeyStatus === 'checking'} |
124 | | - <div |
125 | | - class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full" |
126 | | - style="color: var(--petalytics-subtle);" |
127 | | - ></div> |
128 | | - {:else if apiKeyStatus === 'valid'} |
129 | | - <CheckCircle size={16} style="color: var(--petalytics-pine);" /> |
130 | | - {:else if apiKeyStatus === 'invalid'} |
131 | | - <AlertCircle size={16} style="color: var(--petalytics-love);" /> |
132 | | - {/if} |
133 | | - </div> |
134 | | - </div> |
135 | | - <p class="text-xs mt-1" style="color: var(--petalytics-muted);"> |
136 | | - Required for AI analysis features |
137 | | - </p> |
138 | | - {#if apiKeyStatus === 'invalid'} |
139 | | - <p class="text-xs mt-1" style="color: var(--petalytics-love);"> |
140 | | - Invalid API key. Please check your key and try again. |
141 | | - </p> |
| 174 | + {:else} |
| 175 | + <span class="flex-1" style="color: var(--petalytics-text);"> |
| 176 | + {apiKeyInput ? `${apiKeyInput.slice(0, 8)}****` : 'Not set'} |
| 177 | + </span> |
142 | 178 | {/if} |
| 179 | + <div class="flex items-center"> |
| 180 | + {#if apiKeyStatus === 'checking'} |
| 181 | + <span style="color: var(--petalytics-gold);">●</span> |
| 182 | + {:else if apiKeyStatus === 'valid'} |
| 183 | + <span style="color: var(--petalytics-pine);">●</span> |
| 184 | + {:else if apiKeyStatus === 'invalid'} |
| 185 | + <span style="color: var(--petalytics-love);">●</span> |
| 186 | + {:else} |
| 187 | + <span style="color: var(--petalytics-subtle);">○</span> |
| 188 | + {/if} |
| 189 | + </div> |
143 | 190 | </div> |
144 | 191 |
|
145 | | - <!-- Theme Selection --> |
146 | | - <div class="section"> |
147 | | - <label |
148 | | - for="theme-selector" |
149 | | - class="block text-sm font-medium mb-2" |
150 | | - style="color: var(--petalytics-subtle);" |
151 | | - > |
152 | | - Theme |
153 | | - </label> |
154 | | - <ThemeSelector id="theme-selector" /> |
| 192 | + <!-- Theme row --> |
| 193 | + <div class="cli-row flex items-center space-x-2 hover:bg-gray-800/20 px-2 py-1 rounded transition-colors"> |
| 194 | + <span style="color: var(--petalytics-subtle);">></span> |
| 195 | + <span style="color: var(--petalytics-foam);">theme:</span> |
| 196 | + <button on:click={() => toggleTheme('prev')} class="hover:opacity-70"> |
| 197 | + <ChevronLeft size={14} style="color: var(--petalytics-subtle);" /> |
| 198 | + </button> |
| 199 | + <span class="flex-1" style="color: var(--petalytics-text);"> |
| 200 | + {currentTheme} |
| 201 | + </span> |
| 202 | + <button on:click={() => toggleTheme('next')} class="hover:opacity-70"> |
| 203 | + <ChevronRight size={14} style="color: var(--petalytics-subtle);" /> |
| 204 | + </button> |
155 | 205 | </div> |
156 | 206 |
|
157 | | - <!-- Settings --> |
158 | | - <div class="section"> |
159 | | - <label class="block text-sm font-medium mb-2" style="color: var(--petalytics-subtle);"> |
160 | | - <Settings size={16} class="inline mr-1" /> |
161 | | - Preferences |
162 | | - </label> |
163 | | - <div class="space-y-2"> |
164 | | - <label class="flex items-center space-x-2 cursor-pointer"> |
165 | | - <input |
166 | | - type="checkbox" |
167 | | - bind:checked={preferences.dailyReminders} |
168 | | - on:change={() => handlePreferenceChange('dailyReminders')} |
169 | | - class="w-4 h-4" |
170 | | - /> |
171 | | - <span class="text-sm" style="color: var(--petalytics-text);"> Daily reminders </span> |
172 | | - </label> |
173 | | - <label class="flex items-center space-x-2 cursor-pointer"> |
174 | | - <input |
175 | | - type="checkbox" |
176 | | - bind:checked={preferences.aiInsights} |
177 | | - on:change={() => handlePreferenceChange('aiInsights')} |
178 | | - class="w-4 h-4" |
179 | | - /> |
180 | | - <span class="text-sm" style="color: var(--petalytics-text);"> AI insights </span> |
181 | | - </label> |
182 | | - <label class="flex items-center space-x-2 cursor-pointer"> |
183 | | - <input |
184 | | - type="checkbox" |
185 | | - bind:checked={preferences.notifications} |
186 | | - on:change={() => handlePreferenceChange('notifications')} |
187 | | - class="w-4 h-4" |
188 | | - /> |
189 | | - <span class="text-sm" style="color: var(--petalytics-text);"> |
190 | | - Browser notifications |
191 | | - </span> |
192 | | - </label> |
| 207 | + <!-- Preferences section --> |
| 208 | + <div class="cli-section mt-4"> |
| 209 | + <div class="cli-row flex items-center space-x-2 px-2 py-1"> |
| 210 | + <span style="color: var(--petalytics-subtle);">#</span> |
| 211 | + <span style="color: var(--petalytics-gold);">preferences</span> |
| 212 | + </div> |
| 213 | + |
| 214 | + <div class="cli-row flex items-center space-x-2 hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => togglePreference('dailyReminders')}> |
| 215 | + <span style="color: var(--petalytics-subtle);">></span> |
| 216 | + <span style="color: var(--petalytics-foam);">daily_reminders:</span> |
| 217 | + <span style="color: var(--petalytics-text);"> |
| 218 | + {preferences.dailyReminders ? 'enabled' : 'disabled'} |
| 219 | + </span> |
| 220 | + <span style="color: {preferences.dailyReminders ? 'var(--petalytics-pine)' : 'var(--petalytics-subtle)'};"> |
| 221 | + {preferences.dailyReminders ? '●' : '○'} |
| 222 | + </span> |
| 223 | + </div> |
| 224 | + |
| 225 | + <div class="cli-row flex items-center space-x-2 hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => togglePreference('aiInsights')}> |
| 226 | + <span style="color: var(--petalytics-subtle);">></span> |
| 227 | + <span style="color: var(--petalytics-foam);">ai_insights:</span> |
| 228 | + <span style="color: var(--petalytics-text);"> |
| 229 | + {preferences.aiInsights ? 'enabled' : 'disabled'} |
| 230 | + </span> |
| 231 | + <span style="color: {preferences.aiInsights ? 'var(--petalytics-pine)' : 'var(--petalytics-subtle)'};"> |
| 232 | + {preferences.aiInsights ? '●' : '○'} |
| 233 | + </span> |
| 234 | + </div> |
| 235 | + |
| 236 | + <div class="cli-row flex items-center space-x-2 hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => togglePreference('notifications')}> |
| 237 | + <span style="color: var(--petalytics-subtle);">></span> |
| 238 | + <span style="color: var(--petalytics-foam);">notifications:</span> |
| 239 | + <span style="color: var(--petalytics-text);"> |
| 240 | + {preferences.notifications ? 'enabled' : 'disabled'} |
| 241 | + </span> |
| 242 | + <span style="color: {preferences.notifications ? 'var(--petalytics-pine)' : 'var(--petalytics-subtle)'};"> |
| 243 | + {preferences.notifications ? '●' : '○'} |
| 244 | + </span> |
193 | 245 | </div> |
194 | 246 | </div> |
195 | 247 |
|
196 | | - <!-- Status Section --> |
197 | | - <div class="section"> |
198 | | - <div class="text-xs" style="color: var(--petalytics-muted);"> |
199 | | - <div class="flex justify-between"> |
200 | | - <span>API Status:</span> |
201 | | - <span |
202 | | - class:text-green-400={apiKeyStatus === 'valid'} |
203 | | - class:text-red-400={apiKeyStatus === 'invalid'} |
204 | | - class:text-yellow-400={apiKeyStatus === 'checking'} |
205 | | - > |
206 | | - {apiKeyStatus === 'valid' |
207 | | - ? 'Connected' |
208 | | - : apiKeyStatus === 'invalid' |
209 | | - ? 'Invalid' |
210 | | - : apiKeyStatus === 'checking' |
211 | | - ? 'Checking...' |
212 | | - : 'Not set'} |
213 | | - </span> |
214 | | - </div> |
| 248 | + <!-- Status section --> |
| 249 | + <div class="cli-section mt-4"> |
| 250 | + <div class="cli-row flex items-center space-x-2 px-2 py-1"> |
| 251 | + <span style="color: var(--petalytics-subtle);">#</span> |
| 252 | + <span style="color: var(--petalytics-gold);">status</span> |
| 253 | + </div> |
| 254 | + |
| 255 | + <div class="cli-row flex items-center space-x-2 px-2 py-1"> |
| 256 | + <span style="color: var(--petalytics-subtle);">></span> |
| 257 | + <span style="color: var(--petalytics-foam);">api_status:</span> |
| 258 | + <span style="color: {apiKeyStatus === 'valid' ? 'var(--petalytics-pine)' : apiKeyStatus === 'invalid' ? 'var(--petalytics-love)' : 'var(--petalytics-gold)'};"> |
| 259 | + {apiKeyStatus === 'valid' ? 'connected' : apiKeyStatus === 'invalid' ? 'invalid' : apiKeyStatus === 'checking' ? 'checking...' : 'not_set'} |
| 260 | + </span> |
215 | 261 | </div> |
216 | 262 | </div> |
217 | 263 |
|
218 | | - <!-- Data Management Section --> |
219 | | - <div class="section"> |
220 | | - <div class="flex items-center justify-between mb-2"> |
221 | | - <span class="text-sm font-medium" style="color: var(--petalytics-subtle);" |
222 | | - >Data Management</span |
223 | | - > |
224 | | - <button |
225 | | - on:click={() => (showDataManager = !showDataManager)} |
226 | | - class="text-xs px-2 py-1 rounded bg-gray-100 hover:bg-gray-200 text-gray-700 transition-colors" |
227 | | - > |
228 | | - {showDataManager ? 'Hide' : 'Show'} Export/Import |
229 | | - </button> |
| 264 | + <!-- Data management toggle --> |
| 265 | + <div class="cli-section mt-4"> |
| 266 | + <div class="cli-row flex items-center space-x-2 hover:bg-gray-800/20 px-2 py-1 rounded transition-colors cursor-pointer" on:click={() => showDataManager = !showDataManager}> |
| 267 | + <span style="color: var(--petalytics-subtle);">></span> |
| 268 | + <span style="color: var(--petalytics-foam);">data_manager:</span> |
| 269 | + <span style="color: var(--petalytics-text);"> |
| 270 | + {showDataManager ? 'show' : 'hidden'} |
| 271 | + </span> |
| 272 | + <ChevronRight |
| 273 | + size={14} |
| 274 | + style="color: var(--petalytics-subtle); transform: {showDataManager ? 'rotate(90deg)' : 'rotate(0deg)'}; transition: transform 0.2s;" |
| 275 | + /> |
230 | 276 | </div> |
| 277 | + </div> |
231 | 278 |
|
232 | | - {#if showDataManager} |
| 279 | + {#if showDataManager} |
| 280 | + <div class="mt-2 p-2 rounded" style="background: var(--petalytics-overlay);"> |
233 | 281 | <DataManager /> |
234 | | - {/if} |
235 | | - </div> |
| 282 | + </div> |
| 283 | + {/if} |
236 | 284 | </div> |
237 | 285 | </div> |
0 commit comments