Stijnus commited on
Commit
f091409
·
1 Parent(s): f3468d4

Avatar Fix , control pannel UI fix

Browse files
app/components/@settings/core/ControlPanel.tsx CHANGED
@@ -22,6 +22,7 @@ import type { TabType, TabVisibilityConfig, Profile } from './types';
22
  import { TAB_LABELS, DEFAULT_TAB_CONFIG } from './constants';
23
  import { DialogTitle } from '~/components/ui/Dialog';
24
  import { AvatarDropdown } from './AvatarDropdown';
 
25
 
26
  // Import all tab components
27
  import ProfileTab from '~/components/@settings/tabs/profile/ProfileTab';
@@ -83,7 +84,7 @@ const TAB_DESCRIPTIONS: Record<TabType, string> = {
83
  };
84
 
85
  // Beta status for experimental features
86
- const BETA_TABS = new Set<TabType>(['task-manager', 'service-status']);
87
 
88
  const BetaLabel = () => (
89
  <div className="absolute top-2 right-2 px-1.5 py-0.5 rounded-full bg-purple-500/10 dark:bg-purple-500/20">
@@ -415,108 +416,114 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
415
  'rounded-2xl shadow-2xl',
416
  'border border-[#E5E5E5] dark:border-[#1A1A1A]',
417
  'flex flex-col overflow-hidden',
 
418
  )}
419
  initial={{ opacity: 0, scale: 0.95, y: 20 }}
420
  animate={{ opacity: 1, scale: 1, y: 0 }}
421
  exit={{ opacity: 0, scale: 0.95, y: 20 }}
422
  transition={{ duration: 0.2 }}
423
  >
424
- {/* Header */}
425
- <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
426
- <div className="flex items-center space-x-4">
427
- {(activeTab || showTabManagement) && (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  <button
429
- onClick={handleBack}
430
  className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
431
  >
432
- <div className="i-ph:arrow-left w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
433
  </button>
434
- )}
435
- <DialogTitle className="text-xl font-semibold text-gray-900 dark:text-white">
436
- {showTabManagement ? 'Tab Management' : activeTab ? TAB_LABELS[activeTab] : 'Control Panel'}
437
- </DialogTitle>
438
- </div>
439
-
440
- <div className="flex items-center gap-6">
441
- {/* Mode Toggle */}
442
- <div className="flex items-center gap-2 min-w-[140px] border-r border-gray-200 dark:border-gray-800 pr-6">
443
- <AnimatedSwitch
444
- id="developer-mode"
445
- checked={developerMode}
446
- onCheckedChange={handleDeveloperModeChange}
447
- label={developerMode ? 'Developer Mode' : 'User Mode'}
448
- />
449
- </div>
450
-
451
- {/* Avatar and Dropdown */}
452
- <div className="border-l border-gray-200 dark:border-gray-800 pl-6">
453
- <AvatarDropdown onSelectTab={handleTabClick} />
454
  </div>
455
-
456
- {/* Close Button */}
457
- <button
458
- onClick={onClose}
459
- className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
460
- >
461
- <div className="i-ph:x w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
462
- </button>
463
  </div>
464
- </div>
465
 
466
- {/* Content */}
467
- <div
468
- className={classNames(
469
- 'flex-1',
470
- 'overflow-y-auto',
471
- 'hover:overflow-y-auto',
472
- 'scrollbar scrollbar-w-2',
473
- 'scrollbar-track-transparent',
474
- 'scrollbar-thumb-[#E5E5E5] hover:scrollbar-thumb-[#CCCCCC]',
475
- 'dark:scrollbar-thumb-[#333333] dark:hover:scrollbar-thumb-[#444444]',
476
- 'will-change-scroll',
477
- 'touch-auto',
478
- )}
479
- >
480
- <motion.div
481
- key={activeTab || 'home'}
482
- initial={{ opacity: 0 }}
483
- animate={{ opacity: 1 }}
484
- exit={{ opacity: 0 }}
485
- transition={{ duration: 0.2 }}
486
- className="p-6"
487
- >
488
- {showTabManagement ? (
489
- <TabManagement />
490
- ) : activeTab ? (
491
- getTabComponent(activeTab)
492
- ) : (
493
- <motion.div
494
- className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 relative"
495
- variants={gridLayoutVariants}
496
- initial="hidden"
497
- animate="visible"
498
- >
499
- <AnimatePresence mode="popLayout">
500
- {(visibleTabs as TabWithDevType[]).map((tab: TabWithDevType) => (
501
- <motion.div key={tab.id} layout variants={itemVariants} className="aspect-[1.5/1]">
502
- <TabTile
503
- tab={tab}
504
- onClick={() => handleTabClick(tab.id as TabType)}
505
- isActive={activeTab === tab.id}
506
- hasUpdate={getTabUpdateStatus(tab.id)}
507
- statusMessage={getStatusMessage(tab.id)}
508
- description={TAB_DESCRIPTIONS[tab.id]}
509
- isLoading={loadingTab === tab.id}
510
- className="h-full relative"
511
- >
512
- {BETA_TABS.has(tab.id) && <BetaLabel />}
513
- </TabTile>
514
- </motion.div>
515
- ))}
516
- </AnimatePresence>
517
- </motion.div>
518
  )}
519
- </motion.div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  </div>
521
  </motion.div>
522
  </RadixDialog.Content>
 
22
  import { TAB_LABELS, DEFAULT_TAB_CONFIG } from './constants';
23
  import { DialogTitle } from '~/components/ui/Dialog';
24
  import { AvatarDropdown } from './AvatarDropdown';
25
+ import BackgroundRays from '~/components/ui/BackgroundRays';
26
 
27
  // Import all tab components
28
  import ProfileTab from '~/components/@settings/tabs/profile/ProfileTab';
 
84
  };
85
 
86
  // Beta status for experimental features
87
+ const BETA_TABS = new Set<TabType>(['task-manager', 'service-status', 'update', 'local-providers']);
88
 
89
  const BetaLabel = () => (
90
  <div className="absolute top-2 right-2 px-1.5 py-0.5 rounded-full bg-purple-500/10 dark:bg-purple-500/20">
 
416
  'rounded-2xl shadow-2xl',
417
  'border border-[#E5E5E5] dark:border-[#1A1A1A]',
418
  'flex flex-col overflow-hidden',
419
+ 'relative',
420
  )}
421
  initial={{ opacity: 0, scale: 0.95, y: 20 }}
422
  animate={{ opacity: 1, scale: 1, y: 0 }}
423
  exit={{ opacity: 0, scale: 0.95, y: 20 }}
424
  transition={{ duration: 0.2 }}
425
  >
426
+ <div className="absolute inset-0 overflow-hidden rounded-2xl">
427
+ <BackgroundRays />
428
+ </div>
429
+ <div className="relative z-10 flex flex-col h-full">
430
+ {/* Header */}
431
+ <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
432
+ <div className="flex items-center space-x-4">
433
+ {(activeTab || showTabManagement) && (
434
+ <button
435
+ onClick={handleBack}
436
+ className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
437
+ >
438
+ <div className="i-ph:arrow-left w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
439
+ </button>
440
+ )}
441
+ <DialogTitle className="text-xl font-semibold text-gray-900 dark:text-white">
442
+ {showTabManagement ? 'Tab Management' : activeTab ? TAB_LABELS[activeTab] : 'Control Panel'}
443
+ </DialogTitle>
444
+ </div>
445
+
446
+ <div className="flex items-center gap-6">
447
+ {/* Mode Toggle */}
448
+ <div className="flex items-center gap-2 min-w-[140px] border-r border-gray-200 dark:border-gray-800 pr-6">
449
+ <AnimatedSwitch
450
+ id="developer-mode"
451
+ checked={developerMode}
452
+ onCheckedChange={handleDeveloperModeChange}
453
+ label={developerMode ? 'Developer Mode' : 'User Mode'}
454
+ />
455
+ </div>
456
+
457
+ {/* Avatar and Dropdown */}
458
+ <div className="border-l border-gray-200 dark:border-gray-800 pl-6">
459
+ <AvatarDropdown onSelectTab={handleTabClick} />
460
+ </div>
461
+
462
+ {/* Close Button */}
463
  <button
464
+ onClick={onClose}
465
  className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
466
  >
467
+ <div className="i-ph:x w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
468
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  </div>
 
 
 
 
 
 
 
 
470
  </div>
 
471
 
472
+ {/* Content */}
473
+ <div
474
+ className={classNames(
475
+ 'flex-1',
476
+ 'overflow-y-auto',
477
+ 'hover:overflow-y-auto',
478
+ 'scrollbar scrollbar-w-2',
479
+ 'scrollbar-track-transparent',
480
+ 'scrollbar-thumb-[#E5E5E5] hover:scrollbar-thumb-[#CCCCCC]',
481
+ 'dark:scrollbar-thumb-[#333333] dark:hover:scrollbar-thumb-[#444444]',
482
+ 'will-change-scroll',
483
+ 'touch-auto',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  )}
485
+ >
486
+ <motion.div
487
+ key={activeTab || 'home'}
488
+ initial={{ opacity: 0 }}
489
+ animate={{ opacity: 1 }}
490
+ exit={{ opacity: 0 }}
491
+ transition={{ duration: 0.2 }}
492
+ className="p-6"
493
+ >
494
+ {showTabManagement ? (
495
+ <TabManagement />
496
+ ) : activeTab ? (
497
+ getTabComponent(activeTab)
498
+ ) : (
499
+ <motion.div
500
+ className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 relative"
501
+ variants={gridLayoutVariants}
502
+ initial="hidden"
503
+ animate="visible"
504
+ >
505
+ <AnimatePresence mode="popLayout">
506
+ {(visibleTabs as TabWithDevType[]).map((tab: TabWithDevType) => (
507
+ <motion.div key={tab.id} layout variants={itemVariants} className="aspect-[1.5/1]">
508
+ <TabTile
509
+ tab={tab}
510
+ onClick={() => handleTabClick(tab.id as TabType)}
511
+ isActive={activeTab === tab.id}
512
+ hasUpdate={getTabUpdateStatus(tab.id)}
513
+ statusMessage={getStatusMessage(tab.id)}
514
+ description={TAB_DESCRIPTIONS[tab.id]}
515
+ isLoading={loadingTab === tab.id}
516
+ className="h-full relative"
517
+ >
518
+ {BETA_TABS.has(tab.id) && <BetaLabel />}
519
+ </TabTile>
520
+ </motion.div>
521
+ ))}
522
+ </AnimatePresence>
523
+ </motion.div>
524
+ )}
525
+ </motion.div>
526
+ </div>
527
  </div>
528
  </motion.div>
529
  </RadixDialog.Content>
app/components/@settings/shared/components/TabManagement.tsx CHANGED
@@ -44,6 +44,14 @@ const OPTIONAL_USER_TABS: TabType[] = ['profile', 'settings', 'task-manager', 's
44
  // All available tabs for user mode
45
  const ALL_USER_TABS = [...DEFAULT_USER_TABS, ...OPTIONAL_USER_TABS];
46
 
 
 
 
 
 
 
 
 
47
  export const TabManagement = () => {
48
  const [searchQuery, setSearchQuery] = useState('');
49
  const tabConfiguration = useStore(tabConfigurationStore);
@@ -217,9 +225,12 @@ export const TabManagement = () => {
217
  <div className="flex-1 min-w-0">
218
  <div className="flex items-center justify-between gap-4">
219
  <div>
220
- <h4 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-purple-500 transition-colors">
221
- {TAB_LABELS[tab.id]}
222
- </h4>
 
 
 
223
  <p className="text-xs text-bolt-elements-textSecondary mt-0.5">
224
  {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'}
225
  </p>
 
44
  // All available tabs for user mode
45
  const ALL_USER_TABS = [...DEFAULT_USER_TABS, ...OPTIONAL_USER_TABS];
46
 
47
+ // Define which tabs are beta
48
+ const BETA_TABS = new Set<TabType>(['task-manager', 'service-status', 'update', 'local-providers']);
49
+
50
+ // Beta label component
51
+ const BetaLabel = () => (
52
+ <span className="px-1.5 py-0.5 text-[10px] rounded-full bg-purple-500/10 text-purple-500 font-medium">BETA</span>
53
+ );
54
+
55
  export const TabManagement = () => {
56
  const [searchQuery, setSearchQuery] = useState('');
57
  const tabConfiguration = useStore(tabConfigurationStore);
 
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>
app/components/@settings/tabs/debug/DebugTab.tsx CHANGED
@@ -9,6 +9,7 @@ import { ScrollArea } from '~/components/ui/ScrollArea';
9
  import { Badge } from '~/components/ui/Badge';
10
  import { Dialog, DialogRoot, DialogTitle } from '~/components/ui/Dialog';
11
  import { jsPDF } from 'jspdf';
 
12
 
13
  interface SystemInfo {
14
  os: string;
@@ -138,6 +139,11 @@ interface OllamaServiceStatus {
138
  isRunning: boolean;
139
  lastChecked: Date;
140
  error?: string;
 
 
 
 
 
141
  }
142
 
143
  interface ExportFormat {
@@ -232,6 +238,8 @@ export default function DebugTab() {
232
  performance: false,
233
  });
234
 
 
 
235
  // Subscribe to logStore updates
236
  const logs = useStore(logStore.logs);
237
  const errorLogs = useMemo(() => {
@@ -1093,40 +1101,47 @@ export default function DebugTab() {
1093
  ];
1094
 
1095
  // Add Ollama health check function
1096
- const checkOllamaHealth = async () => {
1097
  try {
1098
- const response = await fetch('http://127.0.0.1:11434/api/version');
1099
- const isHealthy = response.ok;
 
 
 
 
 
 
 
 
 
 
 
1100
 
1101
  setOllamaStatus({
1102
- isRunning: isHealthy,
1103
  lastChecked: new Date(),
1104
- error: isHealthy ? undefined : 'Ollama service is not responding',
1105
  });
1106
-
1107
- return isHealthy;
1108
  } catch {
1109
  setOllamaStatus({
1110
  isRunning: false,
 
1111
  lastChecked: new Date(),
1112
- error: 'Failed to connect to Ollama service',
1113
  });
1114
- return false;
1115
  }
1116
- };
1117
 
1118
- // Add Ollama health check effect
1119
  useEffect(() => {
1120
- const checkHealth = async () => {
1121
- await checkOllamaHealth();
1122
- };
1123
 
1124
- checkHealth();
 
1125
 
1126
- const interval = setInterval(checkHealth, 30000); // Check every 30 seconds
1127
-
1128
- return () => clearInterval(interval);
1129
- }, []);
1130
 
1131
  // Replace the existing export button with this new component
1132
  const ExportButton = () => {
@@ -1199,60 +1214,225 @@ export default function DebugTab() {
1199
  );
1200
  };
1201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1202
  return (
1203
  <div className="flex flex-col gap-6 max-w-7xl mx-auto p-4">
1204
  {/* Quick Stats Banner */}
1205
  <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
1206
- {/* Add Ollama Service Status Card */}
1207
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A]">
1208
- <div className="text-sm text-bolt-elements-textSecondary">Ollama Service</div>
 
 
 
1209
  <div className="flex items-center gap-2 mt-2">
1210
  <div
1211
- className={classNames(
1212
- 'w-2 h-2 rounded-full animate-pulse',
1213
- ollamaStatus.isRunning ? 'bg-green-500' : 'bg-red-500',
1214
- )}
1215
  />
1216
- <span
1217
- className={classNames('text-sm font-medium', ollamaStatus.isRunning ? 'text-green-500' : 'text-red-500')}
1218
- >
1219
- {ollamaStatus.isRunning ? 'Running' : 'Not Running'}
 
1220
  </span>
1221
  </div>
1222
- <div className="text-xs text-bolt-elements-textSecondary mt-2">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1223
  Last checked: {ollamaStatus.lastChecked.toLocaleTimeString()}
1224
  </div>
1225
  </div>
1226
 
1227
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A]">
1228
- <div className="text-sm text-bolt-elements-textSecondary">Memory Usage</div>
1229
- <div className="text-2xl font-semibold text-bolt-elements-textPrimary mt-1">
1230
- {systemInfo?.memory.percentage}%
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1231
  </div>
1232
- <Progress value={systemInfo?.memory.percentage || 0} className="mt-2" />
1233
  </div>
1234
 
1235
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A]">
1236
- <div className="text-sm text-bolt-elements-textSecondary">Page Load Time</div>
1237
- <div className="text-2xl font-semibold text-bolt-elements-textPrimary mt-1">
1238
- {systemInfo ? (systemInfo.performance.timing.loadTime / 1000).toFixed(2) + 's' : '-'}
 
1239
  </div>
1240
- <div className="text-xs text-bolt-elements-textSecondary mt-2">
1241
- DOM Ready: {systemInfo ? (systemInfo.performance.timing.domReadyTime / 1000).toFixed(2) + 's' : '-'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1242
  </div>
1243
  </div>
1244
 
1245
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A]">
1246
- <div className="text-sm text-bolt-elements-textSecondary">Network Speed</div>
1247
- <div className="text-2xl font-semibold text-bolt-elements-textPrimary mt-1">
1248
- {systemInfo?.network.downlink || '-'} Mbps
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1249
  </div>
1250
- <div className="text-xs text-bolt-elements-textSecondary mt-2">RTT: {systemInfo?.network.rtt || '-'} ms</div>
1251
  </div>
1252
 
1253
- <div className="p-4 rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A]">
1254
- <div className="text-sm text-bolt-elements-textSecondary">Errors</div>
1255
- <div className="text-2xl font-semibold text-bolt-elements-textPrimary mt-1">{errorLogs.length}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1256
  </div>
1257
  </div>
1258
 
 
9
  import { Badge } from '~/components/ui/Badge';
10
  import { Dialog, DialogRoot, DialogTitle } from '~/components/ui/Dialog';
11
  import { jsPDF } from 'jspdf';
12
+ import { useSettings } from '~/lib/hooks/useSettings';
13
 
14
  interface SystemInfo {
15
  os: string;
 
139
  isRunning: boolean;
140
  lastChecked: Date;
141
  error?: string;
142
+ models?: Array<{
143
+ name: string;
144
+ size: string;
145
+ quantization: string;
146
+ }>;
147
  }
148
 
149
  interface ExportFormat {
 
238
  performance: false,
239
  });
240
 
241
+ const { isLocalModel, providers } = useSettings();
242
+
243
  // Subscribe to logStore updates
244
  const logs = useStore(logStore.logs);
245
  const errorLogs = useMemo(() => {
 
1101
  ];
1102
 
1103
  // Add Ollama health check function
1104
+ const checkOllamaStatus = useCallback(async () => {
1105
  try {
1106
+ // First check if service is running
1107
+ const versionResponse = await fetch('http://127.0.0.1:11434/api/version');
1108
+
1109
+ if (!versionResponse.ok) {
1110
+ throw new Error('Service not running');
1111
+ }
1112
+
1113
+ // Then fetch installed models
1114
+ const modelsResponse = await fetch('http://127.0.0.1:11434/api/tags');
1115
+
1116
+ const modelsData = (await modelsResponse.json()) as {
1117
+ models: Array<{ name: string; size: string; quantization: string }>;
1118
+ };
1119
 
1120
  setOllamaStatus({
1121
+ isRunning: true,
1122
  lastChecked: new Date(),
1123
+ models: modelsData.models,
1124
  });
 
 
1125
  } catch {
1126
  setOllamaStatus({
1127
  isRunning: false,
1128
+ error: 'Connection failed',
1129
  lastChecked: new Date(),
1130
+ models: undefined,
1131
  });
 
1132
  }
1133
+ }, []);
1134
 
1135
+ // Monitor isLocalModel changes and check status periodically
1136
  useEffect(() => {
1137
+ // Check immediately when isLocalModel changes
1138
+ checkOllamaStatus();
 
1139
 
1140
+ // Set up periodic checks every 10 seconds
1141
+ const intervalId = setInterval(checkOllamaStatus, 10000);
1142
 
1143
+ return () => clearInterval(intervalId);
1144
+ }, [isLocalModel, checkOllamaStatus]);
 
 
1145
 
1146
  // Replace the existing export button with this new component
1147
  const ExportButton = () => {
 
1214
  );
1215
  };
1216
 
1217
+ // Add helper function to get Ollama status text and color
1218
+ const getOllamaStatus = () => {
1219
+ const ollamaProvider = providers?.Ollama;
1220
+ const isOllamaEnabled = ollamaProvider?.settings?.enabled;
1221
+
1222
+ if (!isLocalModel) {
1223
+ return {
1224
+ status: 'Disabled',
1225
+ color: 'text-red-500',
1226
+ bgColor: 'bg-red-500',
1227
+ message: 'Local models are disabled in settings',
1228
+ };
1229
+ }
1230
+
1231
+ if (!isOllamaEnabled) {
1232
+ return {
1233
+ status: 'Disabled',
1234
+ color: 'text-red-500',
1235
+ bgColor: 'bg-red-500',
1236
+ message: 'Ollama provider is disabled in settings',
1237
+ };
1238
+ }
1239
+
1240
+ if (!ollamaStatus.isRunning) {
1241
+ return {
1242
+ status: 'Not Running',
1243
+ color: 'text-red-500',
1244
+ bgColor: 'bg-red-500',
1245
+ message: ollamaStatus.error || 'Ollama service is not running',
1246
+ };
1247
+ }
1248
+
1249
+ const modelCount = ollamaStatus.models?.length ?? 0;
1250
+
1251
+ return {
1252
+ status: 'Running',
1253
+ color: 'text-green-500',
1254
+ bgColor: 'bg-green-500',
1255
+ message: `Ollama service is running with ${modelCount} installed models (Provider: Enabled)`,
1256
+ };
1257
+ };
1258
+
1259
+ // Add type for status result
1260
+ type StatusResult = {
1261
+ status: string;
1262
+ color: string;
1263
+ bgColor: string;
1264
+ message: string;
1265
+ };
1266
+
1267
+ const status = getOllamaStatus() as StatusResult;
1268
+
1269
  return (
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>
1330
+ </div>
1331
+ <div className="flex items-center gap-2 mt-2">
1332
+ <span
1333
+ className={classNames(
1334
+ 'text-2xl font-semibold',
1335
+ (systemInfo?.memory?.percentage ?? 0) > 80
1336
+ ? 'text-red-500'
1337
+ : (systemInfo?.memory?.percentage ?? 0) > 60
1338
+ ? 'text-yellow-500'
1339
+ : 'text-green-500',
1340
+ )}
1341
+ >
1342
+ {systemInfo?.memory?.percentage ?? 0}%
1343
+ </span>
1344
+ </div>
1345
+ <Progress
1346
+ value={systemInfo?.memory?.percentage ?? 0}
1347
+ className={classNames(
1348
+ 'mt-2',
1349
+ (systemInfo?.memory?.percentage ?? 0) > 80
1350
+ ? '[&>div]:bg-red-500'
1351
+ : (systemInfo?.memory?.percentage ?? 0) > 60
1352
+ ? '[&>div]:bg-yellow-500'
1353
+ : '[&>div]:bg-green-500',
1354
+ )}
1355
+ />
1356
+ <div className="text-xs text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
1357
+ <div className="i-ph:info w-3.5 h-3.5 text-purple-500" />
1358
+ Used: {systemInfo?.memory.used ?? '0 GB'} / {systemInfo?.memory.total ?? '0 GB'}
1359
  </div>
 
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>
1367
  </div>
1368
+ <div className="flex items-center gap-2 mt-2">
1369
+ <span
1370
+ className={classNames(
1371
+ 'text-2xl font-semibold',
1372
+ (systemInfo?.performance.timing.loadTime ?? 0) > 2000
1373
+ ? 'text-red-500'
1374
+ : (systemInfo?.performance.timing.loadTime ?? 0) > 1000
1375
+ ? 'text-yellow-500'
1376
+ : 'text-green-500',
1377
+ )}
1378
+ >
1379
+ {systemInfo ? (systemInfo.performance.timing.loadTime / 1000).toFixed(2) : '-'}s
1380
+ </span>
1381
+ </div>
1382
+ <div className="text-xs text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
1383
+ <div className="i-ph:code w-3.5 h-3.5 text-purple-500" />
1384
+ DOM Ready: {systemInfo ? (systemInfo.performance.timing.domReadyTime / 1000).toFixed(2) : '-'}s
1385
  </div>
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>
1393
+ </div>
1394
+ <div className="flex items-center gap-2 mt-2">
1395
+ <span
1396
+ className={classNames(
1397
+ 'text-2xl font-semibold',
1398
+ (systemInfo?.network.downlink ?? 0) < 5
1399
+ ? 'text-red-500'
1400
+ : (systemInfo?.network.downlink ?? 0) < 10
1401
+ ? 'text-yellow-500'
1402
+ : 'text-green-500',
1403
+ )}
1404
+ >
1405
+ {systemInfo?.network.downlink ?? '-'} Mbps
1406
+ </span>
1407
+ </div>
1408
+ <div className="text-xs text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
1409
+ <div className="i-ph:activity w-3.5 h-3.5 text-purple-500" />
1410
+ RTT: {systemInfo?.network.rtt ?? '-'} ms
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>
1438
 
app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx CHANGED
@@ -87,19 +87,10 @@ export default function LocalProvidersTab() {
87
  .map(([key, value]) => {
88
  const provider = value as IProviderConfig;
89
  const envKey = providerBaseUrlEnvKeys[key]?.baseUrlKey;
90
-
91
- // Get environment URL safely
92
  const envUrl = envKey ? (import.meta.env[envKey] as string | undefined) : undefined;
93
 
94
- console.log(`Checking env URL for ${key}:`, {
95
- envKey,
96
- envUrl,
97
- currentBaseUrl: provider.settings.baseUrl,
98
- });
99
-
100
- // If there's an environment URL and no base URL set, update it
101
  if (envUrl && !provider.settings.baseUrl) {
102
- console.log(`Setting base URL for ${key} from env:`, envUrl);
103
  updateProviderSettings(key, {
104
  ...provider.settings,
105
  baseUrl: envUrl,
@@ -414,7 +405,9 @@ export default function LocalProvidersTab() {
414
  <BiChip className="w-6 h-6" />
415
  </motion.div>
416
  <div>
417
- <h2 className="text-lg font-semibold text-bolt-elements-textPrimary">Local AI Models</h2>
 
 
418
  <p className="text-sm text-bolt-elements-textSecondary">Configure and manage your local AI providers</p>
419
  </div>
420
  </div>
 
87
  .map(([key, value]) => {
88
  const provider = value as IProviderConfig;
89
  const envKey = providerBaseUrlEnvKeys[key]?.baseUrlKey;
 
 
90
  const envUrl = envKey ? (import.meta.env[envKey] as string | undefined) : undefined;
91
 
92
+ // Set base URL if provided by environment
 
 
 
 
 
 
93
  if (envUrl && !provider.settings.baseUrl) {
 
94
  updateProviderSettings(key, {
95
  ...provider.settings,
96
  baseUrl: envUrl,
 
405
  <BiChip className="w-6 h-6" />
406
  </motion.div>
407
  <div>
408
+ <div className="flex items-center gap-2">
409
+ <h2 className="text-lg font-semibold text-bolt-elements-textPrimary">Local AI Models</h2>
410
+ </div>
411
  <p className="text-sm text-bolt-elements-textSecondary">Configure and manage your local AI providers</p>
412
  </div>
413
  </div>
app/components/chat/Messages.client.tsx CHANGED
@@ -8,6 +8,8 @@ import { db, chatId } from '~/lib/persistence/useChatHistory';
8
  import { forkChat } from '~/lib/persistence/db';
9
  import { toast } from 'react-toastify';
10
  import WithTooltip from '~/components/ui/Tooltip';
 
 
11
 
12
  interface MessagesProps {
13
  id?: string;
@@ -24,6 +26,7 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
24
  const [isUserInteracting, setIsUserInteracting] = useState(false);
25
  const [lastScrollTop, setLastScrollTop] = useState(0);
26
  const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
 
27
 
28
  // Check if we should auto-scroll based on scroll position
29
  const checkShouldAutoScroll = () => {
@@ -166,8 +169,18 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
166
  })}
167
  >
168
  {isUserMessage && (
169
- <div className="flex items-center justify-center w-[34px] h-[34px] overflow-hidden bg-white text-gray-600 rounded-full shrink-0 self-start">
170
- <div className="i-ph:user-fill text-xl"></div>
 
 
 
 
 
 
 
 
 
 
171
  </div>
172
  )}
173
  <div className="grid grid-col-1 w-full">
 
8
  import { forkChat } from '~/lib/persistence/db';
9
  import { toast } from 'react-toastify';
10
  import WithTooltip from '~/components/ui/Tooltip';
11
+ import { useStore } from '@nanostores/react';
12
+ import { profileStore } from '~/lib/stores/profile';
13
 
14
  interface MessagesProps {
15
  id?: string;
 
26
  const [isUserInteracting, setIsUserInteracting] = useState(false);
27
  const [lastScrollTop, setLastScrollTop] = useState(0);
28
  const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
29
+ const profile = useStore(profileStore);
30
 
31
  // Check if we should auto-scroll based on scroll position
32
  const checkShouldAutoScroll = () => {
 
169
  })}
170
  >
171
  {isUserMessage && (
172
+ <div className="flex items-center justify-center w-[40px] h-[40px] overflow-hidden bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-500 rounded-full shrink-0 self-start">
173
+ {profile?.avatar ? (
174
+ <img
175
+ src={profile.avatar}
176
+ alt={profile?.username || 'User'}
177
+ className="w-full h-full object-cover"
178
+ loading="eager"
179
+ decoding="sync"
180
+ />
181
+ ) : (
182
+ <div className="i-ph:user-fill text-2xl" />
183
+ )}
184
  </div>
185
  )}
186
  <div className="grid grid-col-1 w-full">
app/components/sidebar/Menu.client.tsx CHANGED
@@ -12,6 +12,8 @@ import { HistoryItem } from './HistoryItem';
12
  import { binDates } from './date-binning';
13
  import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
14
  import { classNames } from '~/utils/classNames';
 
 
15
 
16
  const menuVariants = {
17
  closed: {
@@ -65,6 +67,7 @@ export const Menu = () => {
65
  const [open, setOpen] = useState(false);
66
  const [dialogContent, setDialogContent] = useState<DialogContent>(null);
67
  const [isSettingsOpen, setIsSettingsOpen] = useState(false);
 
68
 
69
  const { filteredItems: filteredList, handleSearchChange } = useSearchFilter({
70
  items: list,
@@ -169,7 +172,27 @@ export const Menu = () => {
169
  isSettingsOpen ? 'z-40' : 'z-sidebar',
170
  )}
171
  >
172
- <div className="h-12 flex items-center px-4 border-b border-gray-100 dark:border-gray-800/50 bg-gray-50/50 dark:bg-gray-900/50"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  <CurrentDateTime />
174
  <div className="flex-1 flex flex-col h-full w-full overflow-hidden">
175
  <div className="p-4 space-y-3">
 
12
  import { binDates } from './date-binning';
13
  import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
14
  import { classNames } from '~/utils/classNames';
15
+ import { useStore } from '@nanostores/react';
16
+ import { profileStore } from '~/lib/stores/profile';
17
 
18
  const menuVariants = {
19
  closed: {
 
67
  const [open, setOpen] = useState(false);
68
  const [dialogContent, setDialogContent] = useState<DialogContent>(null);
69
  const [isSettingsOpen, setIsSettingsOpen] = useState(false);
70
+ const profile = useStore(profileStore);
71
 
72
  const { filteredItems: filteredList, handleSearchChange } = useSearchFilter({
73
  items: list,
 
172
  isSettingsOpen ? 'z-40' : 'z-sidebar',
173
  )}
174
  >
175
+ <div className="h-12 flex items-center justify-between px-4 border-b border-gray-100 dark:border-gray-800/50 bg-gray-50/50 dark:bg-gray-900/50">
176
+ <div className="text-gray-900 dark:text-white font-medium"></div>
177
+ <div className="flex items-center gap-3">
178
+ <span className="font-medium text-sm text-gray-900 dark:text-white truncate">
179
+ {profile?.username || 'Guest User'}
180
+ </span>
181
+ <div className="flex items-center justify-center w-[32px] h-[32px] overflow-hidden bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-500 rounded-full shrink-0">
182
+ {profile?.avatar ? (
183
+ <img
184
+ src={profile.avatar}
185
+ alt={profile?.username || 'User'}
186
+ className="w-full h-full object-cover"
187
+ loading="eager"
188
+ decoding="sync"
189
+ />
190
+ ) : (
191
+ <div className="i-ph:user-fill text-lg" />
192
+ )}
193
+ </div>
194
+ </div>
195
+ </div>
196
  <CurrentDateTime />
197
  <div className="flex-1 flex flex-col h-full w-full overflow-hidden">
198
  <div className="p-4 space-y-3">
app/lib/stores/settings.ts CHANGED
@@ -90,7 +90,8 @@ const getInitialProviderSettings = (): ProviderSetting => {
90
  initialSettings[provider.name] = {
91
  ...provider,
92
  settings: {
93
- enabled: true,
 
94
  },
95
  };
96
  });
 
90
  initialSettings[provider.name] = {
91
  ...provider,
92
  settings: {
93
+ // Local providers should be disabled by default
94
+ enabled: !LOCAL_PROVIDERS.includes(provider.name),
95
  },
96
  };
97
  });