Stijnus
commited on
Commit
·
723c6a4
1
Parent(s):
a94330e
fixes
Browse files
app/components/settings/features/FeaturesTab.tsx
CHANGED
@@ -1,12 +1,18 @@
|
|
1 |
-
import React, { memo } from 'react';
|
2 |
import { motion } from 'framer-motion';
|
3 |
import { Switch } from '~/components/ui/Switch';
|
4 |
import { useSettings } from '~/lib/hooks/useSettings';
|
5 |
import { classNames } from '~/utils/classNames';
|
6 |
import { toast } from 'react-toastify';
|
7 |
import { PromptLibrary } from '~/lib/common/prompt-library';
|
8 |
-
import {
|
9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
interface FeatureToggle {
|
12 |
id: string;
|
@@ -107,21 +113,102 @@ const FeatureSection = memo(
|
|
107 |
);
|
108 |
|
109 |
export default function FeaturesTab() {
|
110 |
-
const {
|
111 |
-
setEventLogs,
|
112 |
-
isLocalModel,
|
113 |
-
enableLocalModels,
|
114 |
-
isLatestBranch,
|
115 |
-
enableLatestBranch,
|
116 |
-
promptId,
|
117 |
-
setPromptId,
|
118 |
-
autoSelectTemplate,
|
119 |
-
setAutoSelectTemplate,
|
120 |
-
enableContextOptimization,
|
121 |
-
contextOptimizationEnabled,
|
122 |
-
} = useSettings();
|
123 |
|
124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
|
126 |
const features: Record<'stable' | 'beta' | 'experimental', FeatureToggle[]> = {
|
127 |
stable: [
|
@@ -130,7 +217,7 @@ export default function FeaturesTab() {
|
|
130 |
title: 'Auto Select Code Template',
|
131 |
description: 'Let Bolt select the best starter template for your project',
|
132 |
icon: 'i-ph:magic-wand',
|
133 |
-
enabled:
|
134 |
tooltip: 'Automatically choose the most suitable template based on your project type',
|
135 |
},
|
136 |
{
|
@@ -138,7 +225,7 @@ export default function FeaturesTab() {
|
|
138 |
title: 'Context Optimization',
|
139 |
description: 'Optimize chat context by redacting file contents and using system prompts',
|
140 |
icon: 'i-ph:arrows-in',
|
141 |
-
enabled:
|
142 |
tooltip: 'Improve AI responses by optimizing the context window and system prompts',
|
143 |
},
|
144 |
{
|
@@ -146,9 +233,17 @@ export default function FeaturesTab() {
|
|
146 |
title: 'Event Logging',
|
147 |
description: 'Enable detailed event logging and history',
|
148 |
icon: 'i-ph:list-bullets',
|
149 |
-
enabled:
|
150 |
tooltip: 'Record detailed logs of system events and user actions',
|
151 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
],
|
153 |
beta: [
|
154 |
{
|
@@ -156,7 +251,7 @@ export default function FeaturesTab() {
|
|
156 |
title: 'Use Main Branch',
|
157 |
description: 'Check for updates against the main branch instead of stable',
|
158 |
icon: 'i-ph:git-branch',
|
159 |
-
enabled:
|
160 |
beta: true,
|
161 |
tooltip: 'Get the latest features and improvements before they are officially released',
|
162 |
},
|
@@ -167,38 +262,13 @@ export default function FeaturesTab() {
|
|
167 |
title: 'Experimental Providers',
|
168 |
description: 'Enable experimental providers like Ollama, LMStudio, and OpenAILike',
|
169 |
icon: 'i-ph:robot',
|
170 |
-
enabled:
|
171 |
experimental: true,
|
172 |
tooltip: 'Try out new AI providers and models in development',
|
173 |
},
|
174 |
],
|
175 |
};
|
176 |
|
177 |
-
const handleToggleFeature = (featureId: string, enabled: boolean) => {
|
178 |
-
switch (featureId) {
|
179 |
-
case 'latestBranch':
|
180 |
-
enableLatestBranch(enabled);
|
181 |
-
toast.success(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
|
182 |
-
break;
|
183 |
-
case 'autoTemplate':
|
184 |
-
setAutoSelectTemplate(enabled);
|
185 |
-
toast.success(`Auto template selection ${enabled ? 'enabled' : 'disabled'}`);
|
186 |
-
break;
|
187 |
-
case 'contextOptimization':
|
188 |
-
enableContextOptimization(enabled);
|
189 |
-
toast.success(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
|
190 |
-
break;
|
191 |
-
case 'experimentalProviders':
|
192 |
-
enableLocalModels(enabled);
|
193 |
-
toast.success(`Experimental providers ${enabled ? 'enabled' : 'disabled'}`);
|
194 |
-
break;
|
195 |
-
case 'eventLogs':
|
196 |
-
setEventLogs(enabled);
|
197 |
-
toast.success(`Event logging ${enabled ? 'enabled' : 'disabled'}`);
|
198 |
-
break;
|
199 |
-
}
|
200 |
-
};
|
201 |
-
|
202 |
return (
|
203 |
<div className="flex flex-col gap-8">
|
204 |
<FeatureSection
|
@@ -262,9 +332,9 @@ export default function FeaturesTab() {
|
|
262 |
</p>
|
263 |
</div>
|
264 |
<select
|
265 |
-
value={
|
266 |
onChange={(e) => {
|
267 |
-
|
268 |
toast.success('Prompt template updated');
|
269 |
}}
|
270 |
className={classNames(
|
|
|
1 |
+
import React, { memo, useEffect, useState } from 'react';
|
2 |
import { motion } from 'framer-motion';
|
3 |
import { Switch } from '~/components/ui/Switch';
|
4 |
import { useSettings } from '~/lib/hooks/useSettings';
|
5 |
import { classNames } from '~/utils/classNames';
|
6 |
import { toast } from 'react-toastify';
|
7 |
import { PromptLibrary } from '~/lib/common/prompt-library';
|
8 |
+
import {
|
9 |
+
isEventLogsEnabled,
|
10 |
+
isLocalModelsEnabled,
|
11 |
+
latestBranchStore as latestBranchAtom,
|
12 |
+
promptStore as promptAtom,
|
13 |
+
autoSelectStarterTemplate as autoSelectTemplateAtom,
|
14 |
+
enableContextOptimizationStore as contextOptimizationAtom,
|
15 |
+
} from '~/lib/stores/settings';
|
16 |
|
17 |
interface FeatureToggle {
|
18 |
id: string;
|
|
|
113 |
);
|
114 |
|
115 |
export default function FeaturesTab() {
|
116 |
+
const { autoSelectTemplate, isLatestBranch, contextOptimizationEnabled, eventLogs, isLocalModel } = useSettings();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
|
118 |
+
// Setup store setters
|
119 |
+
const setEventLogs = (value: boolean) => isEventLogsEnabled.set(value);
|
120 |
+
const setLocalModels = (value: boolean) => isLocalModelsEnabled.set(value);
|
121 |
+
const setLatestBranch = (value: boolean) => latestBranchAtom.set(value);
|
122 |
+
const setPromptId = (value: string) => promptAtom.set(value);
|
123 |
+
const setAutoSelectTemplate = (value: boolean) => autoSelectTemplateAtom.set(value);
|
124 |
+
const setContextOptimization = (value: boolean) => contextOptimizationAtom.set(value);
|
125 |
+
|
126 |
+
const getLocalStorageBoolean = (key: string, defaultValue: boolean): boolean => {
|
127 |
+
const value = localStorage.getItem(key);
|
128 |
+
|
129 |
+
if (value === null) {
|
130 |
+
return defaultValue;
|
131 |
+
}
|
132 |
+
|
133 |
+
try {
|
134 |
+
return JSON.parse(value);
|
135 |
+
} catch {
|
136 |
+
return defaultValue;
|
137 |
+
}
|
138 |
+
};
|
139 |
+
|
140 |
+
// Initialize state with proper type handling
|
141 |
+
const autoSelectTemplateState = getLocalStorageBoolean('autoSelectTemplate', autoSelectTemplate);
|
142 |
+
const enableLatestBranchState = getLocalStorageBoolean('enableLatestBranch', isLatestBranch);
|
143 |
+
const contextOptimizationState = getLocalStorageBoolean('contextOptimization', contextOptimizationEnabled);
|
144 |
+
const eventLogsState = getLocalStorageBoolean('eventLogs', eventLogs);
|
145 |
+
const experimentalProvidersState = getLocalStorageBoolean('experimentalProviders', isLocalModel);
|
146 |
+
const promptLibraryState = getLocalStorageBoolean('promptLibrary', false);
|
147 |
+
const promptIdState = localStorage.getItem('promptId') ?? '';
|
148 |
+
|
149 |
+
const [autoSelectTemplateLocal, setAutoSelectTemplateLocal] = useState(autoSelectTemplateState);
|
150 |
+
const [enableLatestBranchLocal, setEnableLatestBranchLocal] = useState(enableLatestBranchState);
|
151 |
+
const [contextOptimizationLocal, setContextOptimizationLocal] = useState(contextOptimizationState);
|
152 |
+
const [eventLogsLocal, setEventLogsLocal] = useState(eventLogsState);
|
153 |
+
const [experimentalProvidersLocal, setExperimentalProvidersLocal] = useState(experimentalProvidersState);
|
154 |
+
const [promptLibraryLocal, setPromptLibraryLocal] = useState(promptLibraryState);
|
155 |
+
const [promptIdLocal, setPromptIdLocal] = useState(promptIdState);
|
156 |
+
|
157 |
+
useEffect(() => {
|
158 |
+
// Update localStorage
|
159 |
+
localStorage.setItem('autoSelectTemplate', JSON.stringify(autoSelectTemplateLocal));
|
160 |
+
localStorage.setItem('enableLatestBranch', JSON.stringify(enableLatestBranchLocal));
|
161 |
+
localStorage.setItem('contextOptimization', JSON.stringify(contextOptimizationLocal));
|
162 |
+
localStorage.setItem('eventLogs', JSON.stringify(eventLogsLocal));
|
163 |
+
localStorage.setItem('experimentalProviders', JSON.stringify(experimentalProvidersLocal));
|
164 |
+
localStorage.setItem('promptLibrary', JSON.stringify(promptLibraryLocal));
|
165 |
+
localStorage.setItem('promptId', promptIdLocal);
|
166 |
+
|
167 |
+
// Update global state
|
168 |
+
setEventLogs(eventLogsLocal);
|
169 |
+
setLocalModels(experimentalProvidersLocal);
|
170 |
+
setLatestBranch(enableLatestBranchLocal);
|
171 |
+
setPromptId(promptIdLocal);
|
172 |
+
setAutoSelectTemplate(autoSelectTemplateLocal);
|
173 |
+
setContextOptimization(contextOptimizationLocal);
|
174 |
+
}, [
|
175 |
+
autoSelectTemplateLocal,
|
176 |
+
enableLatestBranchLocal,
|
177 |
+
contextOptimizationLocal,
|
178 |
+
eventLogsLocal,
|
179 |
+
experimentalProvidersLocal,
|
180 |
+
promptLibraryLocal,
|
181 |
+
promptIdLocal,
|
182 |
+
]);
|
183 |
+
|
184 |
+
const handleToggleFeature = (featureId: string, enabled: boolean) => {
|
185 |
+
switch (featureId) {
|
186 |
+
case 'latestBranch':
|
187 |
+
setEnableLatestBranchLocal(enabled);
|
188 |
+
toast.success(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
|
189 |
+
break;
|
190 |
+
case 'autoTemplate':
|
191 |
+
setAutoSelectTemplateLocal(enabled);
|
192 |
+
toast.success(`Auto template selection ${enabled ? 'enabled' : 'disabled'}`);
|
193 |
+
break;
|
194 |
+
case 'contextOptimization':
|
195 |
+
setContextOptimizationLocal(enabled);
|
196 |
+
toast.success(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
|
197 |
+
break;
|
198 |
+
case 'eventLogs':
|
199 |
+
setEventLogsLocal(enabled);
|
200 |
+
toast.success(`Event logging ${enabled ? 'enabled' : 'disabled'}`);
|
201 |
+
break;
|
202 |
+
case 'experimentalProviders':
|
203 |
+
setExperimentalProvidersLocal(enabled);
|
204 |
+
toast.success(`Experimental providers ${enabled ? 'enabled' : 'disabled'}`);
|
205 |
+
break;
|
206 |
+
case 'promptLibrary':
|
207 |
+
setPromptLibraryLocal(enabled);
|
208 |
+
toast.success(`Prompt Library ${enabled ? 'enabled' : 'disabled'}`);
|
209 |
+
break;
|
210 |
+
}
|
211 |
+
};
|
212 |
|
213 |
const features: Record<'stable' | 'beta' | 'experimental', FeatureToggle[]> = {
|
214 |
stable: [
|
|
|
217 |
title: 'Auto Select Code Template',
|
218 |
description: 'Let Bolt select the best starter template for your project',
|
219 |
icon: 'i-ph:magic-wand',
|
220 |
+
enabled: autoSelectTemplateLocal,
|
221 |
tooltip: 'Automatically choose the most suitable template based on your project type',
|
222 |
},
|
223 |
{
|
|
|
225 |
title: 'Context Optimization',
|
226 |
description: 'Optimize chat context by redacting file contents and using system prompts',
|
227 |
icon: 'i-ph:arrows-in',
|
228 |
+
enabled: contextOptimizationLocal,
|
229 |
tooltip: 'Improve AI responses by optimizing the context window and system prompts',
|
230 |
},
|
231 |
{
|
|
|
233 |
title: 'Event Logging',
|
234 |
description: 'Enable detailed event logging and history',
|
235 |
icon: 'i-ph:list-bullets',
|
236 |
+
enabled: eventLogsLocal,
|
237 |
tooltip: 'Record detailed logs of system events and user actions',
|
238 |
},
|
239 |
+
{
|
240 |
+
id: 'promptLibrary',
|
241 |
+
title: 'Prompt Library',
|
242 |
+
description: 'Manage your prompt library settings',
|
243 |
+
icon: 'i-ph:library',
|
244 |
+
enabled: promptLibraryLocal,
|
245 |
+
tooltip: 'Enable or disable the prompt library',
|
246 |
+
},
|
247 |
],
|
248 |
beta: [
|
249 |
{
|
|
|
251 |
title: 'Use Main Branch',
|
252 |
description: 'Check for updates against the main branch instead of stable',
|
253 |
icon: 'i-ph:git-branch',
|
254 |
+
enabled: enableLatestBranchLocal,
|
255 |
beta: true,
|
256 |
tooltip: 'Get the latest features and improvements before they are officially released',
|
257 |
},
|
|
|
262 |
title: 'Experimental Providers',
|
263 |
description: 'Enable experimental providers like Ollama, LMStudio, and OpenAILike',
|
264 |
icon: 'i-ph:robot',
|
265 |
+
enabled: experimentalProvidersLocal,
|
266 |
experimental: true,
|
267 |
tooltip: 'Try out new AI providers and models in development',
|
268 |
},
|
269 |
],
|
270 |
};
|
271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
return (
|
273 |
<div className="flex flex-col gap-8">
|
274 |
<FeatureSection
|
|
|
332 |
</p>
|
333 |
</div>
|
334 |
<select
|
335 |
+
value={promptIdLocal}
|
336 |
onChange={(e) => {
|
337 |
+
setPromptIdLocal(e.target.value);
|
338 |
toast.success('Prompt template updated');
|
339 |
}}
|
340 |
className={classNames(
|
app/components/settings/notifications/NotificationsTab.tsx
CHANGED
@@ -4,6 +4,7 @@ import { logStore } from '~/lib/stores/logs';
|
|
4 |
import { useStore } from '@nanostores/react';
|
5 |
import { formatDistanceToNow } from 'date-fns';
|
6 |
import { classNames } from '~/utils/classNames';
|
|
|
7 |
|
8 |
interface NotificationDetails {
|
9 |
type?: string;
|
@@ -14,8 +15,10 @@ interface NotificationDetails {
|
|
14 |
updateUrl?: string;
|
15 |
}
|
16 |
|
|
|
|
|
17 |
const NotificationsTab = () => {
|
18 |
-
const [filter, setFilter] = useState<
|
19 |
const logs = useStore(logStore.logs);
|
20 |
|
21 |
const handleClearNotifications = () => {
|
@@ -29,42 +32,64 @@ const NotificationsTab = () => {
|
|
29 |
const filteredLogs = Object.values(logs)
|
30 |
.filter((log) => {
|
31 |
if (filter === 'all') {
|
32 |
-
return
|
33 |
}
|
34 |
|
35 |
if (filter === 'update') {
|
36 |
return log.details?.type === 'update';
|
37 |
}
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
return log.level === filter;
|
40 |
})
|
41 |
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
42 |
|
43 |
-
const getNotificationStyle = (
|
44 |
-
if (
|
45 |
return {
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
text: 'text-purple-900 dark:text-purple-300',
|
50 |
};
|
51 |
}
|
52 |
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
}
|
61 |
-
|
62 |
-
return {
|
63 |
-
border: 'border-yellow-200 dark:border-yellow-900/50',
|
64 |
-
bg: 'bg-yellow-50 dark:bg-yellow-900/20',
|
65 |
-
icon: 'i-ph:warning text-yellow-600 dark:text-yellow-400',
|
66 |
-
text: 'text-yellow-900 dark:text-yellow-300',
|
67 |
-
};
|
68 |
};
|
69 |
|
70 |
const renderNotificationDetails = (details: NotificationDetails) => {
|
@@ -79,7 +104,16 @@ const NotificationsTab = () => {
|
|
79 |
</div>
|
80 |
<button
|
81 |
onClick={() => details.updateUrl && handleUpdateAction(details.updateUrl)}
|
82 |
-
className=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
>
|
84 |
<span className="i-ph:git-branch text-lg" />
|
85 |
View Changes
|
@@ -91,54 +125,134 @@ const NotificationsTab = () => {
|
|
91 |
return details.message ? <p className="text-sm text-gray-600 dark:text-gray-400">{details.message}</p> : null;
|
92 |
};
|
93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
return (
|
95 |
<div className="flex h-full flex-col gap-6">
|
96 |
<div className="flex items-center justify-between">
|
97 |
-
<
|
98 |
-
<
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
<button
|
110 |
onClick={handleClearNotifications}
|
111 |
-
className=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
>
|
|
|
113 |
Clear All
|
114 |
</button>
|
115 |
</div>
|
116 |
|
117 |
<div className="flex flex-col gap-4">
|
118 |
{filteredLogs.length === 0 ? (
|
119 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
<span className="i-ph:bell-slash text-4xl text-gray-400 dark:text-gray-600" />
|
121 |
<div className="flex flex-col gap-1">
|
122 |
-
<h3 className="text-sm font-medium text-gray-900 dark:text-
|
123 |
<p className="text-sm text-gray-500 dark:text-gray-400">You're all caught up!</p>
|
124 |
</div>
|
125 |
-
</div>
|
126 |
) : (
|
127 |
filteredLogs.map((log) => {
|
128 |
-
const style = getNotificationStyle(log);
|
129 |
return (
|
130 |
<motion.div
|
131 |
key={log.id}
|
132 |
initial={{ opacity: 0, y: 20 }}
|
133 |
animate={{ opacity: 1, y: 0 }}
|
134 |
-
className={classNames(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
>
|
136 |
<div className="flex items-start justify-between gap-4">
|
137 |
-
<div className="flex items-
|
138 |
-
<span className={classNames('text-lg', style.icon)} />
|
139 |
-
<div>
|
140 |
-
<h3 className=
|
141 |
{log.details && renderNotificationDetails(log.details as NotificationDetails)}
|
|
|
|
|
|
|
|
|
142 |
</div>
|
143 |
</div>
|
144 |
<time className="shrink-0 text-xs text-gray-500 dark:text-gray-400">
|
|
|
4 |
import { useStore } from '@nanostores/react';
|
5 |
import { formatDistanceToNow } from 'date-fns';
|
6 |
import { classNames } from '~/utils/classNames';
|
7 |
+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
8 |
|
9 |
interface NotificationDetails {
|
10 |
type?: string;
|
|
|
15 |
updateUrl?: string;
|
16 |
}
|
17 |
|
18 |
+
type FilterType = 'all' | 'system' | 'error' | 'warning' | 'update' | 'info' | 'provider' | 'network';
|
19 |
+
|
20 |
const NotificationsTab = () => {
|
21 |
+
const [filter, setFilter] = useState<FilterType>('all');
|
22 |
const logs = useStore(logStore.logs);
|
23 |
|
24 |
const handleClearNotifications = () => {
|
|
|
32 |
const filteredLogs = Object.values(logs)
|
33 |
.filter((log) => {
|
34 |
if (filter === 'all') {
|
35 |
+
return true;
|
36 |
}
|
37 |
|
38 |
if (filter === 'update') {
|
39 |
return log.details?.type === 'update';
|
40 |
}
|
41 |
|
42 |
+
if (filter === 'system') {
|
43 |
+
return log.category === 'system';
|
44 |
+
}
|
45 |
+
|
46 |
+
if (filter === 'provider') {
|
47 |
+
return log.category === 'provider';
|
48 |
+
}
|
49 |
+
|
50 |
+
if (filter === 'network') {
|
51 |
+
return log.category === 'network';
|
52 |
+
}
|
53 |
+
|
54 |
return log.level === filter;
|
55 |
})
|
56 |
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
57 |
|
58 |
+
const getNotificationStyle = (level: string, type?: string) => {
|
59 |
+
if (type === 'update') {
|
60 |
return {
|
61 |
+
icon: 'i-ph:arrow-circle-up',
|
62 |
+
color: 'text-purple-500 dark:text-purple-400',
|
63 |
+
bg: 'hover:bg-purple-500/10 dark:hover:bg-purple-500/20',
|
|
|
64 |
};
|
65 |
}
|
66 |
|
67 |
+
switch (level) {
|
68 |
+
case 'error':
|
69 |
+
return {
|
70 |
+
icon: 'i-ph:warning-circle',
|
71 |
+
color: 'text-red-500 dark:text-red-400',
|
72 |
+
bg: 'hover:bg-red-500/10 dark:hover:bg-red-500/20',
|
73 |
+
};
|
74 |
+
case 'warning':
|
75 |
+
return {
|
76 |
+
icon: 'i-ph:warning',
|
77 |
+
color: 'text-yellow-500 dark:text-yellow-400',
|
78 |
+
bg: 'hover:bg-yellow-500/10 dark:hover:bg-yellow-500/20',
|
79 |
+
};
|
80 |
+
case 'info':
|
81 |
+
return {
|
82 |
+
icon: 'i-ph:info',
|
83 |
+
color: 'text-blue-500 dark:text-blue-400',
|
84 |
+
bg: 'hover:bg-blue-500/10 dark:hover:bg-blue-500/20',
|
85 |
+
};
|
86 |
+
default:
|
87 |
+
return {
|
88 |
+
icon: 'i-ph:bell',
|
89 |
+
color: 'text-gray-500 dark:text-gray-400',
|
90 |
+
bg: 'hover:bg-gray-500/10 dark:hover:bg-gray-500/20',
|
91 |
+
};
|
92 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
};
|
94 |
|
95 |
const renderNotificationDetails = (details: NotificationDetails) => {
|
|
|
104 |
</div>
|
105 |
<button
|
106 |
onClick={() => details.updateUrl && handleUpdateAction(details.updateUrl)}
|
107 |
+
className={classNames(
|
108 |
+
'mt-2 inline-flex items-center gap-2',
|
109 |
+
'rounded-lg px-3 py-1.5',
|
110 |
+
'text-sm font-medium',
|
111 |
+
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
112 |
+
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
113 |
+
'text-gray-900 dark:text-white',
|
114 |
+
'hover:bg-purple-500/10 dark:hover:bg-purple-500/20',
|
115 |
+
'transition-all duration-200',
|
116 |
+
)}
|
117 |
>
|
118 |
<span className="i-ph:git-branch text-lg" />
|
119 |
View Changes
|
|
|
125 |
return details.message ? <p className="text-sm text-gray-600 dark:text-gray-400">{details.message}</p> : null;
|
126 |
};
|
127 |
|
128 |
+
const filterOptions: { id: FilterType; label: string; icon: string }[] = [
|
129 |
+
{ id: 'all', label: 'All Notifications', icon: 'i-ph:bell' },
|
130 |
+
{ id: 'system', label: 'System', icon: 'i-ph:gear' },
|
131 |
+
{ id: 'update', label: 'Updates', icon: 'i-ph:arrow-circle-up' },
|
132 |
+
{ id: 'error', label: 'Errors', icon: 'i-ph:warning-circle' },
|
133 |
+
{ id: 'warning', label: 'Warnings', icon: 'i-ph:warning' },
|
134 |
+
{ id: 'info', label: 'Information', icon: 'i-ph:info' },
|
135 |
+
{ id: 'provider', label: 'Providers', icon: 'i-ph:robot' },
|
136 |
+
{ id: 'network', label: 'Network', icon: 'i-ph:wifi-high' },
|
137 |
+
];
|
138 |
+
|
139 |
return (
|
140 |
<div className="flex h-full flex-col gap-6">
|
141 |
<div className="flex items-center justify-between">
|
142 |
+
<DropdownMenu.Root>
|
143 |
+
<DropdownMenu.Trigger asChild>
|
144 |
+
<button
|
145 |
+
className={classNames(
|
146 |
+
'flex items-center gap-2',
|
147 |
+
'rounded-lg px-3 py-1.5',
|
148 |
+
'text-sm text-gray-900 dark:text-white',
|
149 |
+
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
150 |
+
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
151 |
+
'hover:bg-purple-500/10 dark:hover:bg-purple-500/20',
|
152 |
+
'transition-all duration-200',
|
153 |
+
)}
|
154 |
+
>
|
155 |
+
<span
|
156 |
+
className={classNames(
|
157 |
+
filterOptions.find((opt) => opt.id === filter)?.icon || 'i-ph:funnel',
|
158 |
+
'text-lg text-gray-500 dark:text-gray-400',
|
159 |
+
)}
|
160 |
+
/>
|
161 |
+
{filterOptions.find((opt) => opt.id === filter)?.label || 'Filter Notifications'}
|
162 |
+
<span className="i-ph:caret-down text-lg text-gray-500 dark:text-gray-400" />
|
163 |
+
</button>
|
164 |
+
</DropdownMenu.Trigger>
|
165 |
+
|
166 |
+
<DropdownMenu.Portal>
|
167 |
+
<DropdownMenu.Content
|
168 |
+
className="min-w-[200px] bg-white dark:bg-gray-800 rounded-lg shadow-lg py-1 z-[250] animate-in fade-in-0 zoom-in-95"
|
169 |
+
sideOffset={5}
|
170 |
+
align="start"
|
171 |
+
side="bottom"
|
172 |
+
>
|
173 |
+
{filterOptions.map((option) => (
|
174 |
+
<DropdownMenu.Item
|
175 |
+
key={option.id}
|
176 |
+
className="group flex items-center px-4 py-2.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-purple-500/10 dark:hover:bg-purple-500/20 cursor-pointer transition-colors"
|
177 |
+
onClick={() => setFilter(option.id)}
|
178 |
+
>
|
179 |
+
<div className="mr-3 flex h-5 w-5 items-center justify-center">
|
180 |
+
<div
|
181 |
+
className={classNames(
|
182 |
+
option.icon,
|
183 |
+
'text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors',
|
184 |
+
)}
|
185 |
+
/>
|
186 |
+
</div>
|
187 |
+
<span className="group-hover:text-purple-500 transition-colors">{option.label}</span>
|
188 |
+
</DropdownMenu.Item>
|
189 |
+
))}
|
190 |
+
</DropdownMenu.Content>
|
191 |
+
</DropdownMenu.Portal>
|
192 |
+
</DropdownMenu.Root>
|
193 |
+
|
194 |
<button
|
195 |
onClick={handleClearNotifications}
|
196 |
+
className={classNames(
|
197 |
+
'group flex items-center gap-2',
|
198 |
+
'rounded-lg px-3 py-1.5',
|
199 |
+
'text-sm text-gray-900 dark:text-white',
|
200 |
+
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
201 |
+
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
202 |
+
'hover:bg-purple-500/10 dark:hover:bg-purple-500/20',
|
203 |
+
'transition-all duration-200',
|
204 |
+
)}
|
205 |
>
|
206 |
+
<span className="i-ph:trash text-lg text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
|
207 |
Clear All
|
208 |
</button>
|
209 |
</div>
|
210 |
|
211 |
<div className="flex flex-col gap-4">
|
212 |
{filteredLogs.length === 0 ? (
|
213 |
+
<motion.div
|
214 |
+
initial={{ opacity: 0, y: 20 }}
|
215 |
+
animate={{ opacity: 1, y: 0 }}
|
216 |
+
className={classNames(
|
217 |
+
'flex flex-col items-center justify-center gap-4',
|
218 |
+
'rounded-lg p-8 text-center',
|
219 |
+
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
220 |
+
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
221 |
+
)}
|
222 |
+
>
|
223 |
<span className="i-ph:bell-slash text-4xl text-gray-400 dark:text-gray-600" />
|
224 |
<div className="flex flex-col gap-1">
|
225 |
+
<h3 className="text-sm font-medium text-gray-900 dark:text-white">No Notifications</h3>
|
226 |
<p className="text-sm text-gray-500 dark:text-gray-400">You're all caught up!</p>
|
227 |
</div>
|
228 |
+
</motion.div>
|
229 |
) : (
|
230 |
filteredLogs.map((log) => {
|
231 |
+
const style = getNotificationStyle(log.level, log.details?.type);
|
232 |
return (
|
233 |
<motion.div
|
234 |
key={log.id}
|
235 |
initial={{ opacity: 0, y: 20 }}
|
236 |
animate={{ opacity: 1, y: 0 }}
|
237 |
+
className={classNames(
|
238 |
+
'flex flex-col gap-2',
|
239 |
+
'rounded-lg p-4',
|
240 |
+
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
241 |
+
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
242 |
+
style.bg,
|
243 |
+
'transition-all duration-200',
|
244 |
+
)}
|
245 |
>
|
246 |
<div className="flex items-start justify-between gap-4">
|
247 |
+
<div className="flex items-start gap-3">
|
248 |
+
<span className={classNames('text-lg', style.icon, style.color)} />
|
249 |
+
<div className="flex flex-col gap-1">
|
250 |
+
<h3 className="text-sm font-medium text-gray-900 dark:text-white">{log.message}</h3>
|
251 |
{log.details && renderNotificationDetails(log.details as NotificationDetails)}
|
252 |
+
<p className="text-xs text-gray-500 dark:text-gray-400">
|
253 |
+
Category: {log.category}
|
254 |
+
{log.subCategory ? ` > ${log.subCategory}` : ''}
|
255 |
+
</p>
|
256 |
</div>
|
257 |
</div>
|
258 |
<time className="shrink-0 text-xs text-gray-500 dark:text-gray-400">
|
app/lib/stores/settings.ts
CHANGED
@@ -53,13 +53,19 @@ export const isDebugMode = atom(false);
|
|
53 |
const savedEventLogs = Cookies.get('isEventLogsEnabled');
|
54 |
export const isEventLogsEnabled = atom(savedEventLogs === 'true');
|
55 |
|
|
|
56 |
export const isLocalModelsEnabled = atom(true);
|
57 |
|
|
|
58 |
export const promptStore = atom<string>('default');
|
59 |
|
|
|
60 |
export const latestBranchStore = atom(false);
|
61 |
|
|
|
62 |
export const autoSelectStarterTemplate = atom(false);
|
|
|
|
|
63 |
export const enableContextOptimizationStore = atom(false);
|
64 |
|
65 |
// Initialize tab configuration from cookie or default
|
|
|
53 |
const savedEventLogs = Cookies.get('isEventLogsEnabled');
|
54 |
export const isEventLogsEnabled = atom(savedEventLogs === 'true');
|
55 |
|
56 |
+
// Local models settings
|
57 |
export const isLocalModelsEnabled = atom(true);
|
58 |
|
59 |
+
// Prompt settings
|
60 |
export const promptStore = atom<string>('default');
|
61 |
|
62 |
+
// Branch settings
|
63 |
export const latestBranchStore = atom(false);
|
64 |
|
65 |
+
// Template settings
|
66 |
export const autoSelectStarterTemplate = atom(false);
|
67 |
+
|
68 |
+
// Context optimization settings
|
69 |
export const enableContextOptimizationStore = atom(false);
|
70 |
|
71 |
// Initialize tab configuration from cookie or default
|
app/routes/api.system.app-info.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import type { ActionFunctionArgs } from '@remix-run/cloudflare';
|
2 |
import { json } from '@remix-run/cloudflare';
|
3 |
|
4 |
interface PackageJson {
|
@@ -27,6 +27,23 @@ const packageJson = {
|
|
27 |
},
|
28 |
} as PackageJson;
|
29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
export const action = async ({ request: _request }: ActionFunctionArgs) => {
|
31 |
try {
|
32 |
return json({
|
|
|
1 |
+
import type { ActionFunctionArgs, LoaderFunction } from '@remix-run/cloudflare';
|
2 |
import { json } from '@remix-run/cloudflare';
|
3 |
|
4 |
interface PackageJson {
|
|
|
27 |
},
|
28 |
} as PackageJson;
|
29 |
|
30 |
+
export const loader: LoaderFunction = async ({ request: _request }) => {
|
31 |
+
try {
|
32 |
+
return json({
|
33 |
+
name: packageJson.name,
|
34 |
+
version: packageJson.version,
|
35 |
+
description: packageJson.description,
|
36 |
+
license: packageJson.license,
|
37 |
+
nodeVersion: process.version,
|
38 |
+
dependencies: packageJson.dependencies,
|
39 |
+
devDependencies: packageJson.devDependencies,
|
40 |
+
});
|
41 |
+
} catch (error) {
|
42 |
+
console.error('Failed to get webapp info:', error);
|
43 |
+
return json({ error: 'Failed to get webapp information' }, { status: 500 });
|
44 |
+
}
|
45 |
+
};
|
46 |
+
|
47 |
export const action = async ({ request: _request }: ActionFunctionArgs) => {
|
48 |
try {
|
49 |
return json({
|