Stijnus commited on
Commit
6e89710
·
1 Parent(s): db5f30e

Several UI fixes

Browse files
app/components/@settings/shared/components/TabManagement.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { useState } from 'react';
2
  import { motion } from 'framer-motion';
3
  import { useStore } from '@nanostores/react';
4
- import { Switch } from '@radix-ui/react-switch';
5
  import { classNames } from '~/utils/classNames';
6
  import { tabConfigurationStore } from '~/lib/stores/settings';
7
  import { TAB_LABELS } from '~/components/@settings/core/constants';
@@ -177,92 +177,193 @@ export const TabManagement = () => {
177
 
178
  {/* Tab Grid */}
179
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
180
- {filteredTabs.map((tab, index) => (
181
- <motion.div
182
- key={tab.id}
183
- className={classNames(
184
- 'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary',
185
- 'bg-bolt-elements-background-depth-2',
186
- 'hover:bg-bolt-elements-background-depth-3',
187
- 'transition-all duration-200',
188
- 'relative overflow-hidden group',
189
- )}
190
- initial={{ opacity: 0, y: 20 }}
191
- animate={{ opacity: 1, y: 0 }}
192
- transition={{ delay: index * 0.1 }}
193
- whileHover={{ scale: 1.02 }}
194
- >
195
- {/* Status Badges */}
196
- <div className="absolute top-2 right-2 flex gap-1">
197
- {DEFAULT_USER_TABS.includes(tab.id) && (
198
- <span className="px-2 py-0.5 text-xs rounded-full bg-purple-500/10 text-purple-500 font-medium">
 
 
 
 
 
 
 
 
 
 
199
  Default
200
  </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  )}
202
- {OPTIONAL_USER_TABS.includes(tab.id) && (
203
- <span className="px-2 py-0.5 text-xs rounded-full bg-blue-500/10 text-blue-500 font-medium">
 
 
 
 
 
 
204
  Optional
205
  </span>
206
- )}
207
- </div>
208
 
209
- <div className="flex items-start gap-4 p-4">
210
- <motion.div
211
- className={classNames(
212
- 'w-10 h-10 flex items-center justify-center rounded-xl',
213
- 'bg-bolt-elements-background-depth-3 group-hover:bg-bolt-elements-background-depth-4',
214
- 'transition-all duration-200',
215
- tab.visible ? 'text-purple-500' : 'text-bolt-elements-textSecondary',
216
- )}
217
- whileHover={{ scale: 1.1 }}
218
- whileTap={{ scale: 0.9 }}
219
- >
220
- <div className={classNames('w-6 h-6', 'transition-transform duration-200', 'group-hover:rotate-12')}>
221
- <div className={classNames(TAB_ICONS[tab.id], 'w-full h-full')} />
222
- </div>
223
- </motion.div>
224
-
225
- <div className="flex-1 min-w-0">
226
- <div className="flex items-center justify-between gap-4">
227
- <div>
228
- <div className="flex items-center gap-2">
229
- <h4 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-purple-500 transition-colors">
230
- {TAB_LABELS[tab.id]}
231
- </h4>
232
- {BETA_TABS.has(tab.id) && <BetaLabel />}
233
- </div>
234
- <p className="text-xs text-bolt-elements-textSecondary mt-0.5">
235
- {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'}
236
- </p>
237
  </div>
238
- <Switch
239
- checked={tab.visible}
240
- onCheckedChange={(checked) => handleTabVisibilityChange(tab.id, checked)}
241
- disabled={!DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id)}
242
- className={classNames(
243
- 'relative inline-flex h-5 w-9 items-center rounded-full',
244
- 'transition-colors duration-200',
245
- tab.visible ? 'bg-purple-500' : 'bg-bolt-elements-background-depth-4',
246
- {
247
- 'opacity-50 cursor-not-allowed':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id),
249
- },
250
- )}
251
- />
252
  </div>
253
  </div>
254
- </div>
255
 
256
- <motion.div
257
- className="absolute inset-0 border-2 border-purple-500/0 rounded-lg pointer-events-none"
258
- animate={{
259
- borderColor: tab.visible ? 'rgba(168, 85, 247, 0.2)' : 'rgba(168, 85, 247, 0)',
260
- scale: tab.visible ? 1 : 0.98,
261
- }}
262
- transition={{ duration: 0.2 }}
263
- />
264
- </motion.div>
265
- ))}
266
  </div>
267
  </motion.div>
268
  </div>
 
1
  import { useState } from 'react';
2
  import { motion } from 'framer-motion';
3
  import { useStore } from '@nanostores/react';
4
+ import { Switch } from '~/components/ui/Switch';
5
  import { classNames } from '~/utils/classNames';
6
  import { tabConfigurationStore } from '~/lib/stores/settings';
7
  import { TAB_LABELS } from '~/components/@settings/core/constants';
 
177
 
178
  {/* Tab Grid */}
179
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
180
+ {/* Default Section Header */}
181
+ {filteredTabs.some((tab) => DEFAULT_USER_TABS.includes(tab.id)) && (
182
+ <div className="col-span-full flex items-center gap-2 mt-4 mb-2">
183
+ <div className="i-ph:star-fill w-4 h-4 text-purple-500" />
184
+ <span className="text-sm font-medium text-bolt-elements-textPrimary">Default Tabs</span>
185
+ </div>
186
+ )}
187
+
188
+ {/* Default Tabs */}
189
+ {filteredTabs
190
+ .filter((tab) => DEFAULT_USER_TABS.includes(tab.id))
191
+ .map((tab, index) => (
192
+ <motion.div
193
+ key={tab.id}
194
+ className={classNames(
195
+ 'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary',
196
+ 'bg-bolt-elements-background-depth-2',
197
+ 'hover:bg-bolt-elements-background-depth-3',
198
+ 'transition-all duration-200',
199
+ 'relative overflow-hidden group',
200
+ )}
201
+ initial={{ opacity: 0, y: 20 }}
202
+ animate={{ opacity: 1, y: 0 }}
203
+ transition={{ delay: index * 0.1 }}
204
+ whileHover={{ scale: 1.02 }}
205
+ >
206
+ {/* Status Badges */}
207
+ <div className="absolute top-1 right-1.5 flex gap-1">
208
+ <span className="px-1.5 py-0.25 text-xs rounded-full bg-purple-500/10 text-purple-500 font-medium mr-2">
209
  Default
210
  </span>
211
+ </div>
212
+
213
+ <div className="flex items-start gap-4 p-4">
214
+ <motion.div
215
+ className={classNames(
216
+ 'w-10 h-10 flex items-center justify-center rounded-xl',
217
+ 'bg-bolt-elements-background-depth-3 group-hover:bg-bolt-elements-background-depth-4',
218
+ 'transition-all duration-200',
219
+ tab.visible ? 'text-purple-500' : 'text-bolt-elements-textSecondary',
220
+ )}
221
+ whileHover={{ scale: 1.1 }}
222
+ whileTap={{ scale: 0.9 }}
223
+ >
224
+ <div
225
+ className={classNames('w-6 h-6', 'transition-transform duration-200', 'group-hover:rotate-12')}
226
+ >
227
+ <div className={classNames(TAB_ICONS[tab.id], 'w-full h-full')} />
228
+ </div>
229
+ </motion.div>
230
+
231
+ <div className="flex-1 min-w-0">
232
+ <div className="flex items-center justify-between gap-4">
233
+ <div>
234
+ <div className="flex items-center gap-2">
235
+ <h4 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-purple-500 transition-colors">
236
+ {TAB_LABELS[tab.id]}
237
+ </h4>
238
+ {BETA_TABS.has(tab.id) && <BetaLabel />}
239
+ </div>
240
+ <p className="text-xs text-bolt-elements-textSecondary mt-0.5">
241
+ {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'}
242
+ </p>
243
+ </div>
244
+ <Switch
245
+ checked={tab.visible}
246
+ onCheckedChange={(checked) => {
247
+ const isDisabled =
248
+ !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id);
249
+
250
+ if (!isDisabled) {
251
+ handleTabVisibilityChange(tab.id, checked);
252
+ }
253
+ }}
254
+ className={classNames('data-[state=checked]:bg-purple-500 ml-4', {
255
+ 'opacity-50 pointer-events-none':
256
+ !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id),
257
+ })}
258
+ />
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ <motion.div
264
+ className="absolute inset-0 border-2 border-purple-500/0 rounded-lg pointer-events-none"
265
+ animate={{
266
+ borderColor: tab.visible ? 'rgba(168, 85, 247, 0.2)' : 'rgba(168, 85, 247, 0)',
267
+ scale: tab.visible ? 1 : 0.98,
268
+ }}
269
+ transition={{ duration: 0.2 }}
270
+ />
271
+ </motion.div>
272
+ ))}
273
+
274
+ {/* Optional Section Header */}
275
+ {filteredTabs.some((tab) => OPTIONAL_USER_TABS.includes(tab.id)) && (
276
+ <div className="col-span-full flex items-center gap-2 mt-8 mb-2">
277
+ <div className="i-ph:plus-circle-fill w-4 h-4 text-blue-500" />
278
+ <span className="text-sm font-medium text-bolt-elements-textPrimary">Optional Tabs</span>
279
+ </div>
280
+ )}
281
+
282
+ {/* Optional Tabs */}
283
+ {filteredTabs
284
+ .filter((tab) => OPTIONAL_USER_TABS.includes(tab.id))
285
+ .map((tab, index) => (
286
+ <motion.div
287
+ key={tab.id}
288
+ className={classNames(
289
+ 'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary',
290
+ 'bg-bolt-elements-background-depth-2',
291
+ 'hover:bg-bolt-elements-background-depth-3',
292
+ 'transition-all duration-200',
293
+ 'relative overflow-hidden group',
294
  )}
295
+ initial={{ opacity: 0, y: 20 }}
296
+ animate={{ opacity: 1, y: 0 }}
297
+ transition={{ delay: index * 0.1 }}
298
+ whileHover={{ scale: 1.02 }}
299
+ >
300
+ {/* Status Badges */}
301
+ <div className="absolute top-1 right-1.5 flex gap-1">
302
+ <span className="px-1.5 py-0.25 text-xs rounded-full bg-blue-500/10 text-blue-500 font-medium mr-2">
303
  Optional
304
  </span>
305
+ </div>
 
306
 
307
+ <div className="flex items-start gap-4 p-4">
308
+ <motion.div
309
+ className={classNames(
310
+ 'w-10 h-10 flex items-center justify-center rounded-xl',
311
+ 'bg-bolt-elements-background-depth-3 group-hover:bg-bolt-elements-background-depth-4',
312
+ 'transition-all duration-200',
313
+ tab.visible ? 'text-purple-500' : 'text-bolt-elements-textSecondary',
314
+ )}
315
+ whileHover={{ scale: 1.1 }}
316
+ whileTap={{ scale: 0.9 }}
317
+ >
318
+ <div
319
+ className={classNames('w-6 h-6', 'transition-transform duration-200', 'group-hover:rotate-12')}
320
+ >
321
+ <div className={classNames(TAB_ICONS[tab.id], 'w-full h-full')} />
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  </div>
323
+ </motion.div>
324
+
325
+ <div className="flex-1 min-w-0">
326
+ <div className="flex items-center justify-between gap-4">
327
+ <div>
328
+ <div className="flex items-center gap-2">
329
+ <h4 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-purple-500 transition-colors">
330
+ {TAB_LABELS[tab.id]}
331
+ </h4>
332
+ {BETA_TABS.has(tab.id) && <BetaLabel />}
333
+ </div>
334
+ <p className="text-xs text-bolt-elements-textSecondary mt-0.5">
335
+ {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'}
336
+ </p>
337
+ </div>
338
+ <Switch
339
+ checked={tab.visible}
340
+ onCheckedChange={(checked) => {
341
+ const isDisabled =
342
+ !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id);
343
+
344
+ if (!isDisabled) {
345
+ handleTabVisibilityChange(tab.id, checked);
346
+ }
347
+ }}
348
+ className={classNames('data-[state=checked]:bg-purple-500 ml-4', {
349
+ 'opacity-50 pointer-events-none':
350
  !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id),
351
+ })}
352
+ />
353
+ </div>
354
  </div>
355
  </div>
 
356
 
357
+ <motion.div
358
+ className="absolute inset-0 border-2 border-purple-500/0 rounded-lg pointer-events-none"
359
+ animate={{
360
+ borderColor: tab.visible ? 'rgba(168, 85, 247, 0.2)' : 'rgba(168, 85, 247, 0)',
361
+ scale: tab.visible ? 1 : 0.98,
362
+ }}
363
+ transition={{ duration: 0.2 }}
364
+ />
365
+ </motion.div>
366
+ ))}
367
  </div>
368
  </motion.div>
369
  </div>
app/components/@settings/tabs/debug/DebugTab.tsx CHANGED
@@ -1270,60 +1270,32 @@ export default function DebugTab() {
1270
  <div className="flex flex-col gap-6 max-w-7xl mx-auto p-4">
1271
  {/* Quick Stats Banner */}
1272
  <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
1273
- {/* Ollama Service Status Card */}
1274
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200">
1275
  <div className="flex items-center gap-2">
1276
- <div className="i-ph:robot text-purple-500 w-4 h-4" />
1277
- <div className="text-sm text-bolt-elements-textSecondary">Ollama Service</div>
1278
  </div>
1279
  <div className="flex items-center gap-2 mt-2">
1280
- <div
1281
- className={classNames('w-2 h-2 rounded-full animate-pulse', status.bgColor, {
1282
- 'shadow-lg shadow-green-500/20': status.status === 'Running',
1283
- 'shadow-lg shadow-red-500/20': status.status === 'Not Running',
1284
- })}
1285
- />
1286
- <span className={classNames('text-sm font-medium flex items-center gap-1.5', status.color)}>
1287
- {status.status === 'Running' && <div className="i-ph:check-circle-fill w-3.5 h-3.5" />}
1288
- {status.status === 'Not Running' && <div className="i-ph:x-circle-fill w-3.5 h-3.5" />}
1289
- {status.status === 'Disabled' && <div className="i-ph:prohibit-fill w-3.5 h-3.5" />}
1290
- {status.status}
1291
  </span>
1292
  </div>
1293
  <div className="text-xs text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
1294
  <div
1295
- className={classNames('w-3.5 h-3.5', {
1296
- 'i-ph:info text-green-500': status.status === 'Running',
1297
- 'i-ph:warning text-red-500': status.status === 'Not Running' || status.status === 'Disabled',
1298
- })}
1299
  />
1300
- {status.message}
1301
- </div>
1302
- {ollamaStatus.models && ollamaStatus.models.length > 0 && (
1303
- <div className="mt-3 space-y-1 border-t border-[#E5E5E5] dark:border-[#1A1A1A] pt-2">
1304
- <div className="text-xs font-medium text-bolt-elements-textSecondary flex items-center gap-1.5">
1305
- <div className="i-ph:cube-duotone w-3.5 h-3.5 text-purple-500" />
1306
- Installed Models
1307
- </div>
1308
- {ollamaStatus.models.map((model) => (
1309
- <div key={model.name} className="text-xs text-bolt-elements-textSecondary flex items-center gap-2 pl-5">
1310
- <div className="i-ph:cube w-3 h-3 text-purple-500/70" />
1311
- <span className="font-mono">{model.name}</span>
1312
- <span className="text-bolt-elements-textTertiary">
1313
- ({Math.round(parseInt(model.size) / 1024 / 1024)}MB, {model.quantization})
1314
- </span>
1315
- </div>
1316
- ))}
1317
- </div>
1318
- )}
1319
- <div className="text-xs text-bolt-elements-textTertiary mt-3 flex items-center gap-1.5">
1320
- <div className="i-ph:clock w-3 h-3" />
1321
- Last checked: {ollamaStatus.lastChecked.toLocaleTimeString()}
1322
  </div>
1323
  </div>
1324
 
1325
  {/* Memory Usage Card */}
1326
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200">
1327
  <div className="flex items-center gap-2">
1328
  <div className="i-ph:cpu text-purple-500 w-4 h-4" />
1329
  <div className="text-sm text-bolt-elements-textSecondary">Memory Usage</div>
@@ -1360,7 +1332,7 @@ export default function DebugTab() {
1360
  </div>
1361
 
1362
  {/* Page Load Time Card */}
1363
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200">
1364
  <div className="flex items-center gap-2">
1365
  <div className="i-ph:timer text-purple-500 w-4 h-4" />
1366
  <div className="text-sm text-bolt-elements-textSecondary">Page Load Time</div>
@@ -1386,7 +1358,7 @@ export default function DebugTab() {
1386
  </div>
1387
 
1388
  {/* Network Speed Card */}
1389
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200">
1390
  <div className="flex items-center gap-2">
1391
  <div className="i-ph:wifi-high text-purple-500 w-4 h-4" />
1392
  <div className="text-sm text-bolt-elements-textSecondary">Network Speed</div>
@@ -1411,27 +1383,80 @@ export default function DebugTab() {
1411
  </div>
1412
  </div>
1413
 
1414
- {/* Errors Card */}
1415
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200">
1416
- <div className="flex items-center gap-2">
1417
- <div className="i-ph:warning-octagon text-purple-500 w-4 h-4" />
1418
- <div className="text-sm text-bolt-elements-textSecondary">Errors</div>
1419
- </div>
1420
- <div className="flex items-center gap-2 mt-2">
1421
- <span
1422
- className={classNames('text-2xl font-semibold', errorLogs.length > 0 ? 'text-red-500' : 'text-green-500')}
1423
- >
1424
- {errorLogs.length}
1425
- </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1426
  </div>
1427
- <div className="text-xs text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
1428
- <div
1429
- className={classNames(
1430
- 'w-3.5 h-3.5',
1431
- errorLogs.length > 0 ? 'i-ph:warning text-red-500' : 'i-ph:check-circle text-green-500',
1432
- )}
1433
- />
1434
- {errorLogs.length > 0 ? 'Errors detected' : 'No errors detected'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1435
  </div>
1436
  </div>
1437
  </div>
 
1270
  <div className="flex flex-col gap-6 max-w-7xl mx-auto p-4">
1271
  {/* Quick Stats Banner */}
1272
  <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
1273
+ {/* Errors Card */}
1274
+ <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200 h-[180px] flex flex-col">
1275
  <div className="flex items-center gap-2">
1276
+ <div className="i-ph:warning-octagon text-purple-500 w-4 h-4" />
1277
+ <div className="text-sm text-bolt-elements-textSecondary">Errors</div>
1278
  </div>
1279
  <div className="flex items-center gap-2 mt-2">
1280
+ <span
1281
+ className={classNames('text-2xl font-semibold', errorLogs.length > 0 ? 'text-red-500' : 'text-green-500')}
1282
+ >
1283
+ {errorLogs.length}
 
 
 
 
 
 
 
1284
  </span>
1285
  </div>
1286
  <div className="text-xs text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
1287
  <div
1288
+ className={classNames(
1289
+ 'w-3.5 h-3.5',
1290
+ errorLogs.length > 0 ? 'i-ph:warning text-red-500' : 'i-ph:check-circle text-green-500',
1291
+ )}
1292
  />
1293
+ {errorLogs.length > 0 ? 'Errors detected' : 'No errors detected'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1294
  </div>
1295
  </div>
1296
 
1297
  {/* Memory Usage Card */}
1298
+ <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200 h-[180px] flex flex-col">
1299
  <div className="flex items-center gap-2">
1300
  <div className="i-ph:cpu text-purple-500 w-4 h-4" />
1301
  <div className="text-sm text-bolt-elements-textSecondary">Memory Usage</div>
 
1332
  </div>
1333
 
1334
  {/* Page Load Time Card */}
1335
+ <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200 h-[180px] flex flex-col">
1336
  <div className="flex items-center gap-2">
1337
  <div className="i-ph:timer text-purple-500 w-4 h-4" />
1338
  <div className="text-sm text-bolt-elements-textSecondary">Page Load Time</div>
 
1358
  </div>
1359
 
1360
  {/* Network Speed Card */}
1361
+ <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200 h-[180px] flex flex-col">
1362
  <div className="flex items-center gap-2">
1363
  <div className="i-ph:wifi-high text-purple-500 w-4 h-4" />
1364
  <div className="text-sm text-bolt-elements-textSecondary">Network Speed</div>
 
1383
  </div>
1384
  </div>
1385
 
1386
+ {/* Ollama Service Card - Now spans all 4 columns */}
1387
+ <div className="md:col-span-4 p-6 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] hover:border-purple-500/30 transition-all duration-200 h-[260px] flex flex-col">
1388
+ <div className="flex items-center justify-between">
1389
+ <div className="flex items-center gap-3">
1390
+ <div className="i-ph:robot text-purple-500 w-5 h-5" />
1391
+ <div>
1392
+ <div className="text-base font-medium text-bolt-elements-textPrimary">Ollama Service</div>
1393
+ <div className="text-xs text-bolt-elements-textSecondary mt-0.5">{status.message}</div>
1394
+ </div>
1395
+ </div>
1396
+ <div className="flex items-center gap-3">
1397
+ <div className="flex items-center gap-2 px-2.5 py-1 rounded-full bg-bolt-elements-background-depth-3">
1398
+ <div
1399
+ className={classNames('w-2 h-2 rounded-full animate-pulse', status.bgColor, {
1400
+ 'shadow-lg shadow-green-500/20': status.status === 'Running',
1401
+ 'shadow-lg shadow-red-500/20': status.status === 'Not Running',
1402
+ })}
1403
+ />
1404
+ <span className={classNames('text-xs font-medium flex items-center gap-1', status.color)}>
1405
+ {status.status}
1406
+ </span>
1407
+ </div>
1408
+ <div className="text-[10px] text-bolt-elements-textTertiary flex items-center gap-1.5">
1409
+ <div className="i-ph:clock w-3 h-3" />
1410
+ {ollamaStatus.lastChecked.toLocaleTimeString()}
1411
+ </div>
1412
+ </div>
1413
  </div>
1414
+
1415
+ <div className="mt-6 flex-1 min-h-0 flex flex-col">
1416
+ {status.status === 'Running' && ollamaStatus.models && ollamaStatus.models.length > 0 ? (
1417
+ <>
1418
+ <div className="text-xs font-medium text-bolt-elements-textSecondary flex items-center justify-between mb-3">
1419
+ <div className="flex items-center gap-2">
1420
+ <div className="i-ph:cube-duotone w-4 h-4 text-purple-500" />
1421
+ <span>Installed Models</span>
1422
+ <Badge variant="secondary" className="ml-1">
1423
+ {ollamaStatus.models.length}
1424
+ </Badge>
1425
+ </div>
1426
+ </div>
1427
+ <div className="overflow-y-auto flex-1 scrollbar-thin scrollbar-thumb-gray-300 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-400 dark:hover:scrollbar-thumb-gray-600">
1428
+ <div className="grid grid-cols-2 gap-3 pr-2">
1429
+ {ollamaStatus.models.map((model) => (
1430
+ <div
1431
+ key={model.name}
1432
+ className="text-sm bg-bolt-elements-background-depth-3 hover:bg-bolt-elements-background-depth-4 rounded-lg px-4 py-3 flex items-center justify-between transition-colors group"
1433
+ >
1434
+ <div className="flex items-center gap-2 text-bolt-elements-textSecondary">
1435
+ <div className="i-ph:cube w-4 h-4 text-purple-500/70 group-hover:text-purple-500 transition-colors" />
1436
+ <span className="font-mono truncate">{model.name}</span>
1437
+ </div>
1438
+ <Badge variant="outline" className="ml-2 text-xs font-mono">
1439
+ {Math.round(parseInt(model.size) / 1024 / 1024)}MB
1440
+ </Badge>
1441
+ </div>
1442
+ ))}
1443
+ </div>
1444
+ </div>
1445
+ </>
1446
+ ) : (
1447
+ <div className="flex-1 flex items-center justify-center">
1448
+ <div className="flex flex-col items-center gap-3 max-w-[280px] text-center">
1449
+ <div
1450
+ className={classNames('w-12 h-12', {
1451
+ 'i-ph:warning-circle text-red-500/80':
1452
+ status.status === 'Not Running' || status.status === 'Disabled',
1453
+ 'i-ph:cube-duotone text-purple-500/80': status.status === 'Running',
1454
+ })}
1455
+ />
1456
+ <span className="text-sm text-bolt-elements-textSecondary">{status.message}</span>
1457
+ </div>
1458
+ </div>
1459
+ )}
1460
  </div>
1461
  </div>
1462
  </div>
app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx CHANGED
@@ -471,6 +471,60 @@ export default function LocalProvidersTab() {
471
  />
472
  </div>
473
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  {/* Ollama Models Section */}
475
  {provider.settings.enabled && (
476
  <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-6 space-y-4">
@@ -505,7 +559,19 @@ export default function LocalProvidersTab() {
505
  <div className="text-center py-8 text-bolt-elements-textSecondary">
506
  <div className="i-ph:cube-transparent text-4xl mx-auto mb-2" />
507
  <p>No models installed yet</p>
508
- <p className="text-sm">Install your first model below</p>
 
 
 
 
 
 
 
 
 
 
 
 
509
  </div>
510
  ) : (
511
  ollamaModels.map((model) => (
 
471
  />
472
  </div>
473
 
474
+ {/* URL Configuration Section */}
475
+ <AnimatePresence>
476
+ {provider.settings.enabled && (
477
+ <motion.div
478
+ initial={{ opacity: 0, height: 0 }}
479
+ animate={{ opacity: 1, height: 'auto' }}
480
+ exit={{ opacity: 0, height: 0 }}
481
+ className="mt-4"
482
+ >
483
+ <div className="flex flex-col gap-2">
484
+ <label className="text-sm text-bolt-elements-textSecondary">API Endpoint</label>
485
+ {editingProvider === provider.name ? (
486
+ <input
487
+ type="text"
488
+ defaultValue={provider.settings.baseUrl || OLLAMA_API_URL}
489
+ placeholder="Enter Ollama base URL"
490
+ className={classNames(
491
+ 'w-full px-3 py-2 rounded-lg text-sm',
492
+ 'bg-bolt-elements-background-depth-3 border border-bolt-elements-borderColor',
493
+ 'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary',
494
+ 'focus:outline-none focus:ring-2 focus:ring-purple-500/30',
495
+ 'transition-all duration-200',
496
+ )}
497
+ onKeyDown={(e) => {
498
+ if (e.key === 'Enter') {
499
+ handleUpdateBaseUrl(provider, e.currentTarget.value);
500
+ } else if (e.key === 'Escape') {
501
+ setEditingProvider(null);
502
+ }
503
+ }}
504
+ onBlur={(e) => handleUpdateBaseUrl(provider, e.target.value)}
505
+ autoFocus
506
+ />
507
+ ) : (
508
+ <div
509
+ onClick={() => setEditingProvider(provider.name)}
510
+ className={classNames(
511
+ 'w-full px-3 py-2 rounded-lg text-sm cursor-pointer',
512
+ 'bg-bolt-elements-background-depth-3 border border-bolt-elements-borderColor',
513
+ 'hover:border-purple-500/30 hover:bg-bolt-elements-background-depth-4',
514
+ 'transition-all duration-200',
515
+ )}
516
+ >
517
+ <div className="flex items-center gap-2 text-bolt-elements-textSecondary">
518
+ <div className="i-ph:link text-sm" />
519
+ <span>{provider.settings.baseUrl || OLLAMA_API_URL}</span>
520
+ </div>
521
+ </div>
522
+ )}
523
+ </div>
524
+ </motion.div>
525
+ )}
526
+ </AnimatePresence>
527
+
528
  {/* Ollama Models Section */}
529
  {provider.settings.enabled && (
530
  <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-6 space-y-4">
 
559
  <div className="text-center py-8 text-bolt-elements-textSecondary">
560
  <div className="i-ph:cube-transparent text-4xl mx-auto mb-2" />
561
  <p>No models installed yet</p>
562
+ <p className="text-sm text-bolt-elements-textTertiary px-1">
563
+ Browse models at{' '}
564
+ <a
565
+ href="https://ollama.com/library"
566
+ target="_blank"
567
+ rel="noopener noreferrer"
568
+ className="text-purple-500 hover:underline inline-flex items-center gap-0.5 text-base font-medium"
569
+ >
570
+ ollama.com/library
571
+ <div className="i-ph:arrow-square-out text-xs" />
572
+ </a>{' '}
573
+ and copy model names to install
574
+ </p>
575
  </div>
576
  ) : (
577
  ollamaModels.map((model) => (
app/components/@settings/tabs/providers/local/OllamaModelInstaller.tsx CHANGED
@@ -429,16 +429,16 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn
429
  }}
430
  disabled={isInstalling}
431
  />
432
- <p className="text-xs text-bolt-elements-textTertiary px-1">
433
  Browse models at{' '}
434
  <a
435
  href="https://ollama.com/library"
436
  target="_blank"
437
  rel="noopener noreferrer"
438
- className="text-purple-500 hover:underline inline-flex items-center gap-0.5"
439
  >
440
  ollama.com/library
441
- <div className="i-ph:arrow-square-out text-[10px]" />
442
  </a>{' '}
443
  and copy model names to install
444
  </p>
@@ -448,10 +448,11 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn
448
  onClick={() => handleInstallModel(modelString)}
449
  disabled={!modelString || isInstalling}
450
  className={classNames(
451
- 'rounded-xl px-6 py-3',
452
- 'bg-purple-500 text-white',
453
  'hover:bg-purple-600',
454
  'transition-all duration-200',
 
455
  { 'opacity-50 cursor-not-allowed': !modelString || isInstalling },
456
  )}
457
  whileHover={{ scale: 1.02 }}
@@ -459,7 +460,7 @@ export default function OllamaModelInstaller({ onModelInstalled }: OllamaModelIn
459
  >
460
  {isInstalling ? (
461
  <div className="flex items-center gap-2">
462
- <div className="i-ph:spinner-gap-bold animate-spin" />
463
  <span>Installing...</span>
464
  </div>
465
  ) : (
 
429
  }}
430
  disabled={isInstalling}
431
  />
432
+ <p className="text-sm text-bolt-elements-textSecondary px-1">
433
  Browse models at{' '}
434
  <a
435
  href="https://ollama.com/library"
436
  target="_blank"
437
  rel="noopener noreferrer"
438
+ className="text-purple-500 hover:underline inline-flex items-center gap-1 text-base font-medium"
439
  >
440
  ollama.com/library
441
+ <div className="i-ph:arrow-square-out text-sm" />
442
  </a>{' '}
443
  and copy model names to install
444
  </p>
 
448
  onClick={() => handleInstallModel(modelString)}
449
  disabled={!modelString || isInstalling}
450
  className={classNames(
451
+ 'rounded-lg px-4 py-2',
452
+ 'bg-purple-500 text-white text-sm',
453
  'hover:bg-purple-600',
454
  'transition-all duration-200',
455
+ 'flex items-center gap-2',
456
  { 'opacity-50 cursor-not-allowed': !modelString || isInstalling },
457
  )}
458
  whileHover={{ scale: 1.02 }}
 
460
  >
461
  {isInstalling ? (
462
  <div className="flex items-center gap-2">
463
+ <div className="i-ph:spinner-gap-bold animate-spin w-4 h-4" />
464
  <span>Installing...</span>
465
  </div>
466
  ) : (
app/components/@settings/tabs/settings/SettingsTab.tsx CHANGED
@@ -3,7 +3,6 @@ import { motion } from 'framer-motion';
3
  import { toast } from 'react-toastify';
4
  import { classNames } from '~/utils/classNames';
5
  import { Switch } from '~/components/ui/Switch';
6
- import { themeStore, kTheme } from '~/lib/stores/theme';
7
  import type { UserProfile } from '~/components/@settings/core/types';
8
  import { useStore } from '@nanostores/react';
9
  import { shortcutsStore } from '~/lib/stores/settings';
@@ -41,7 +40,6 @@ export default function SettingsTab() {
41
  return saved
42
  ? JSON.parse(saved)
43
  : {
44
- theme: 'system',
45
  notifications: true,
46
  language: 'en',
47
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
@@ -52,22 +50,6 @@ export default function SettingsTab() {
52
  setCurrentTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone);
53
  }, []);
54
 
55
- // Apply theme when settings changes
56
- useEffect(() => {
57
- if (settings.theme === 'system') {
58
- // Remove theme override
59
- localStorage.removeItem(kTheme);
60
-
61
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
62
- document.querySelector('html')?.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
63
- themeStore.set(prefersDark ? 'dark' : 'light');
64
- } else {
65
- themeStore.set(settings.theme);
66
- localStorage.setItem(kTheme, settings.theme);
67
- document.querySelector('html')?.setAttribute('data-theme', settings.theme);
68
- }
69
- }, [settings.theme]);
70
-
71
  // Save settings automatically when they change
72
  useEffect(() => {
73
  try {
@@ -77,7 +59,6 @@ export default function SettingsTab() {
77
  // Merge with new settings
78
  const updatedProfile = {
79
  ...existingProfile,
80
- theme: settings.theme,
81
  notifications: settings.notifications,
82
  language: settings.language,
83
  timezone: settings.timezone,
@@ -93,7 +74,7 @@ export default function SettingsTab() {
93
 
94
  return (
95
  <div className="space-y-4">
96
- {/* Theme & Language */}
97
  <motion.div
98
  className="bg-white dark:bg-[#0A0A0A] rounded-lg shadow-sm dark:shadow-none p-4 space-y-4"
99
  initial={{ opacity: 0, y: 20 }}
@@ -102,45 +83,7 @@ export default function SettingsTab() {
102
  >
103
  <div className="flex items-center gap-2 mb-4">
104
  <div className="i-ph:palette-fill w-4 h-4 text-purple-500" />
105
- <span className="text-sm font-medium text-bolt-elements-textPrimary">Appearance</span>
106
- </div>
107
-
108
- <div>
109
- <div className="flex items-center gap-2 mb-2">
110
- <div className="i-ph:paint-brush-fill w-4 h-4 text-bolt-elements-textSecondary" />
111
- <label className="block text-sm text-bolt-elements-textSecondary">Theme</label>
112
- </div>
113
- <div className="flex gap-2">
114
- {(['light', 'dark', 'system'] as const).map((theme) => (
115
- <button
116
- key={theme}
117
- onClick={() => {
118
- setSettings((prev) => ({ ...prev, theme }));
119
-
120
- if (theme !== 'system') {
121
- themeStore.set(theme);
122
- }
123
- }}
124
- className={classNames(
125
- 'inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm disabled:opacity-50 disabled:cursor-not-allowed',
126
- settings.theme === theme
127
- ? 'bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-500 dark:text-white dark:hover:bg-purple-600'
128
- : 'bg-bolt-elements-hover dark:bg-[#1A1A1A] text-bolt-elements-textSecondary hover:bg-purple-500/10 hover:text-purple-500 dark:hover:bg-purple-500/20 dark:text-bolt-elements-textPrimary dark:hover:text-purple-500',
129
- )}
130
- >
131
- <div
132
- className={`w-4 h-4 ${
133
- theme === 'light'
134
- ? 'i-ph:sun-fill'
135
- : theme === 'dark'
136
- ? 'i-ph:moon-stars-fill'
137
- : 'i-ph:monitor-fill'
138
- }`}
139
- />
140
- <span className="capitalize">{theme}</span>
141
- </button>
142
- ))}
143
- </div>
144
  </div>
145
 
146
  <div>
 
3
  import { toast } from 'react-toastify';
4
  import { classNames } from '~/utils/classNames';
5
  import { Switch } from '~/components/ui/Switch';
 
6
  import type { UserProfile } from '~/components/@settings/core/types';
7
  import { useStore } from '@nanostores/react';
8
  import { shortcutsStore } from '~/lib/stores/settings';
 
40
  return saved
41
  ? JSON.parse(saved)
42
  : {
 
43
  notifications: true,
44
  language: 'en',
45
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
 
50
  setCurrentTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone);
51
  }, []);
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  // Save settings automatically when they change
54
  useEffect(() => {
55
  try {
 
59
  // Merge with new settings
60
  const updatedProfile = {
61
  ...existingProfile,
 
62
  notifications: settings.notifications,
63
  language: settings.language,
64
  timezone: settings.timezone,
 
74
 
75
  return (
76
  <div className="space-y-4">
77
+ {/* Language & Notifications */}
78
  <motion.div
79
  className="bg-white dark:bg-[#0A0A0A] rounded-lg shadow-sm dark:shadow-none p-4 space-y-4"
80
  initial={{ opacity: 0, y: 20 }}
 
83
  >
84
  <div className="flex items-center gap-2 mb-4">
85
  <div className="i-ph:palette-fill w-4 h-4 text-purple-500" />
86
+ <span className="text-sm font-medium text-bolt-elements-textPrimary">Preferences</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  </div>
88
 
89
  <div>
app/lib/stores/settings.ts CHANGED
@@ -179,7 +179,7 @@ const getInitialSettings = () => {
179
  contextOptimization: getStoredBoolean(SETTINGS_KEYS.CONTEXT_OPTIMIZATION, true),
180
  eventLogs: getStoredBoolean(SETTINGS_KEYS.EVENT_LOGS, true),
181
  localModels: getStoredBoolean(SETTINGS_KEYS.LOCAL_MODELS, true),
182
- promptId: isBrowser ? localStorage.getItem(SETTINGS_KEYS.PROMPT_ID) || 'optimized' : 'optimized',
183
  developerMode: getStoredBoolean(SETTINGS_KEYS.DEVELOPER_MODE, false),
184
  };
185
  };
 
179
  contextOptimization: getStoredBoolean(SETTINGS_KEYS.CONTEXT_OPTIMIZATION, true),
180
  eventLogs: getStoredBoolean(SETTINGS_KEYS.EVENT_LOGS, true),
181
  localModels: getStoredBoolean(SETTINGS_KEYS.LOCAL_MODELS, true),
182
+ promptId: isBrowser ? localStorage.getItem(SETTINGS_KEYS.PROMPT_ID) || 'default' : 'default',
183
  developerMode: getStoredBoolean(SETTINGS_KEYS.DEVELOPER_MODE, false),
184
  };
185
  };