mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-05-08 14:14:41 +00:00
fixes
This commit is contained in:
parent
a94330e4a4
commit
723c6a4f02
@ -1,12 +1,18 @@
|
|||||||
import React, { memo } from 'react';
|
import React, { memo, useEffect, useState } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Switch } from '~/components/ui/Switch';
|
import { Switch } from '~/components/ui/Switch';
|
||||||
import { useSettings } from '~/lib/hooks/useSettings';
|
import { useSettings } from '~/lib/hooks/useSettings';
|
||||||
import { classNames } from '~/utils/classNames';
|
import { classNames } from '~/utils/classNames';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { PromptLibrary } from '~/lib/common/prompt-library';
|
import { PromptLibrary } from '~/lib/common/prompt-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import {
|
||||||
import { isEventLogsEnabled } from '~/lib/stores/settings';
|
isEventLogsEnabled,
|
||||||
|
isLocalModelsEnabled,
|
||||||
|
latestBranchStore as latestBranchAtom,
|
||||||
|
promptStore as promptAtom,
|
||||||
|
autoSelectStarterTemplate as autoSelectTemplateAtom,
|
||||||
|
enableContextOptimizationStore as contextOptimizationAtom,
|
||||||
|
} from '~/lib/stores/settings';
|
||||||
|
|
||||||
interface FeatureToggle {
|
interface FeatureToggle {
|
||||||
id: string;
|
id: string;
|
||||||
@ -107,21 +113,102 @@ const FeatureSection = memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default function FeaturesTab() {
|
export default function FeaturesTab() {
|
||||||
const {
|
const { autoSelectTemplate, isLatestBranch, contextOptimizationEnabled, eventLogs, isLocalModel } = useSettings();
|
||||||
setEventLogs,
|
|
||||||
isLocalModel,
|
|
||||||
enableLocalModels,
|
|
||||||
isLatestBranch,
|
|
||||||
enableLatestBranch,
|
|
||||||
promptId,
|
|
||||||
setPromptId,
|
|
||||||
autoSelectTemplate,
|
|
||||||
setAutoSelectTemplate,
|
|
||||||
enableContextOptimization,
|
|
||||||
contextOptimizationEnabled,
|
|
||||||
} = useSettings();
|
|
||||||
|
|
||||||
const eventLogs = useStore(isEventLogsEnabled);
|
// Setup store setters
|
||||||
|
const setEventLogs = (value: boolean) => isEventLogsEnabled.set(value);
|
||||||
|
const setLocalModels = (value: boolean) => isLocalModelsEnabled.set(value);
|
||||||
|
const setLatestBranch = (value: boolean) => latestBranchAtom.set(value);
|
||||||
|
const setPromptId = (value: string) => promptAtom.set(value);
|
||||||
|
const setAutoSelectTemplate = (value: boolean) => autoSelectTemplateAtom.set(value);
|
||||||
|
const setContextOptimization = (value: boolean) => contextOptimizationAtom.set(value);
|
||||||
|
|
||||||
|
const getLocalStorageBoolean = (key: string, defaultValue: boolean): boolean => {
|
||||||
|
const value = localStorage.getItem(key);
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize state with proper type handling
|
||||||
|
const autoSelectTemplateState = getLocalStorageBoolean('autoSelectTemplate', autoSelectTemplate);
|
||||||
|
const enableLatestBranchState = getLocalStorageBoolean('enableLatestBranch', isLatestBranch);
|
||||||
|
const contextOptimizationState = getLocalStorageBoolean('contextOptimization', contextOptimizationEnabled);
|
||||||
|
const eventLogsState = getLocalStorageBoolean('eventLogs', eventLogs);
|
||||||
|
const experimentalProvidersState = getLocalStorageBoolean('experimentalProviders', isLocalModel);
|
||||||
|
const promptLibraryState = getLocalStorageBoolean('promptLibrary', false);
|
||||||
|
const promptIdState = localStorage.getItem('promptId') ?? '';
|
||||||
|
|
||||||
|
const [autoSelectTemplateLocal, setAutoSelectTemplateLocal] = useState(autoSelectTemplateState);
|
||||||
|
const [enableLatestBranchLocal, setEnableLatestBranchLocal] = useState(enableLatestBranchState);
|
||||||
|
const [contextOptimizationLocal, setContextOptimizationLocal] = useState(contextOptimizationState);
|
||||||
|
const [eventLogsLocal, setEventLogsLocal] = useState(eventLogsState);
|
||||||
|
const [experimentalProvidersLocal, setExperimentalProvidersLocal] = useState(experimentalProvidersState);
|
||||||
|
const [promptLibraryLocal, setPromptLibraryLocal] = useState(promptLibraryState);
|
||||||
|
const [promptIdLocal, setPromptIdLocal] = useState(promptIdState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Update localStorage
|
||||||
|
localStorage.setItem('autoSelectTemplate', JSON.stringify(autoSelectTemplateLocal));
|
||||||
|
localStorage.setItem('enableLatestBranch', JSON.stringify(enableLatestBranchLocal));
|
||||||
|
localStorage.setItem('contextOptimization', JSON.stringify(contextOptimizationLocal));
|
||||||
|
localStorage.setItem('eventLogs', JSON.stringify(eventLogsLocal));
|
||||||
|
localStorage.setItem('experimentalProviders', JSON.stringify(experimentalProvidersLocal));
|
||||||
|
localStorage.setItem('promptLibrary', JSON.stringify(promptLibraryLocal));
|
||||||
|
localStorage.setItem('promptId', promptIdLocal);
|
||||||
|
|
||||||
|
// Update global state
|
||||||
|
setEventLogs(eventLogsLocal);
|
||||||
|
setLocalModels(experimentalProvidersLocal);
|
||||||
|
setLatestBranch(enableLatestBranchLocal);
|
||||||
|
setPromptId(promptIdLocal);
|
||||||
|
setAutoSelectTemplate(autoSelectTemplateLocal);
|
||||||
|
setContextOptimization(contextOptimizationLocal);
|
||||||
|
}, [
|
||||||
|
autoSelectTemplateLocal,
|
||||||
|
enableLatestBranchLocal,
|
||||||
|
contextOptimizationLocal,
|
||||||
|
eventLogsLocal,
|
||||||
|
experimentalProvidersLocal,
|
||||||
|
promptLibraryLocal,
|
||||||
|
promptIdLocal,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleToggleFeature = (featureId: string, enabled: boolean) => {
|
||||||
|
switch (featureId) {
|
||||||
|
case 'latestBranch':
|
||||||
|
setEnableLatestBranchLocal(enabled);
|
||||||
|
toast.success(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
|
||||||
|
break;
|
||||||
|
case 'autoTemplate':
|
||||||
|
setAutoSelectTemplateLocal(enabled);
|
||||||
|
toast.success(`Auto template selection ${enabled ? 'enabled' : 'disabled'}`);
|
||||||
|
break;
|
||||||
|
case 'contextOptimization':
|
||||||
|
setContextOptimizationLocal(enabled);
|
||||||
|
toast.success(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
|
||||||
|
break;
|
||||||
|
case 'eventLogs':
|
||||||
|
setEventLogsLocal(enabled);
|
||||||
|
toast.success(`Event logging ${enabled ? 'enabled' : 'disabled'}`);
|
||||||
|
break;
|
||||||
|
case 'experimentalProviders':
|
||||||
|
setExperimentalProvidersLocal(enabled);
|
||||||
|
toast.success(`Experimental providers ${enabled ? 'enabled' : 'disabled'}`);
|
||||||
|
break;
|
||||||
|
case 'promptLibrary':
|
||||||
|
setPromptLibraryLocal(enabled);
|
||||||
|
toast.success(`Prompt Library ${enabled ? 'enabled' : 'disabled'}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const features: Record<'stable' | 'beta' | 'experimental', FeatureToggle[]> = {
|
const features: Record<'stable' | 'beta' | 'experimental', FeatureToggle[]> = {
|
||||||
stable: [
|
stable: [
|
||||||
@ -130,7 +217,7 @@ export default function FeaturesTab() {
|
|||||||
title: 'Auto Select Code Template',
|
title: 'Auto Select Code Template',
|
||||||
description: 'Let Bolt select the best starter template for your project',
|
description: 'Let Bolt select the best starter template for your project',
|
||||||
icon: 'i-ph:magic-wand',
|
icon: 'i-ph:magic-wand',
|
||||||
enabled: autoSelectTemplate,
|
enabled: autoSelectTemplateLocal,
|
||||||
tooltip: 'Automatically choose the most suitable template based on your project type',
|
tooltip: 'Automatically choose the most suitable template based on your project type',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -138,7 +225,7 @@ export default function FeaturesTab() {
|
|||||||
title: 'Context Optimization',
|
title: 'Context Optimization',
|
||||||
description: 'Optimize chat context by redacting file contents and using system prompts',
|
description: 'Optimize chat context by redacting file contents and using system prompts',
|
||||||
icon: 'i-ph:arrows-in',
|
icon: 'i-ph:arrows-in',
|
||||||
enabled: contextOptimizationEnabled,
|
enabled: contextOptimizationLocal,
|
||||||
tooltip: 'Improve AI responses by optimizing the context window and system prompts',
|
tooltip: 'Improve AI responses by optimizing the context window and system prompts',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -146,9 +233,17 @@ export default function FeaturesTab() {
|
|||||||
title: 'Event Logging',
|
title: 'Event Logging',
|
||||||
description: 'Enable detailed event logging and history',
|
description: 'Enable detailed event logging and history',
|
||||||
icon: 'i-ph:list-bullets',
|
icon: 'i-ph:list-bullets',
|
||||||
enabled: eventLogs,
|
enabled: eventLogsLocal,
|
||||||
tooltip: 'Record detailed logs of system events and user actions',
|
tooltip: 'Record detailed logs of system events and user actions',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'promptLibrary',
|
||||||
|
title: 'Prompt Library',
|
||||||
|
description: 'Manage your prompt library settings',
|
||||||
|
icon: 'i-ph:library',
|
||||||
|
enabled: promptLibraryLocal,
|
||||||
|
tooltip: 'Enable or disable the prompt library',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
beta: [
|
beta: [
|
||||||
{
|
{
|
||||||
@ -156,7 +251,7 @@ export default function FeaturesTab() {
|
|||||||
title: 'Use Main Branch',
|
title: 'Use Main Branch',
|
||||||
description: 'Check for updates against the main branch instead of stable',
|
description: 'Check for updates against the main branch instead of stable',
|
||||||
icon: 'i-ph:git-branch',
|
icon: 'i-ph:git-branch',
|
||||||
enabled: isLatestBranch,
|
enabled: enableLatestBranchLocal,
|
||||||
beta: true,
|
beta: true,
|
||||||
tooltip: 'Get the latest features and improvements before they are officially released',
|
tooltip: 'Get the latest features and improvements before they are officially released',
|
||||||
},
|
},
|
||||||
@ -167,38 +262,13 @@ export default function FeaturesTab() {
|
|||||||
title: 'Experimental Providers',
|
title: 'Experimental Providers',
|
||||||
description: 'Enable experimental providers like Ollama, LMStudio, and OpenAILike',
|
description: 'Enable experimental providers like Ollama, LMStudio, and OpenAILike',
|
||||||
icon: 'i-ph:robot',
|
icon: 'i-ph:robot',
|
||||||
enabled: isLocalModel,
|
enabled: experimentalProvidersLocal,
|
||||||
experimental: true,
|
experimental: true,
|
||||||
tooltip: 'Try out new AI providers and models in development',
|
tooltip: 'Try out new AI providers and models in development',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleFeature = (featureId: string, enabled: boolean) => {
|
|
||||||
switch (featureId) {
|
|
||||||
case 'latestBranch':
|
|
||||||
enableLatestBranch(enabled);
|
|
||||||
toast.success(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
|
|
||||||
break;
|
|
||||||
case 'autoTemplate':
|
|
||||||
setAutoSelectTemplate(enabled);
|
|
||||||
toast.success(`Auto template selection ${enabled ? 'enabled' : 'disabled'}`);
|
|
||||||
break;
|
|
||||||
case 'contextOptimization':
|
|
||||||
enableContextOptimization(enabled);
|
|
||||||
toast.success(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
|
|
||||||
break;
|
|
||||||
case 'experimentalProviders':
|
|
||||||
enableLocalModels(enabled);
|
|
||||||
toast.success(`Experimental providers ${enabled ? 'enabled' : 'disabled'}`);
|
|
||||||
break;
|
|
||||||
case 'eventLogs':
|
|
||||||
setEventLogs(enabled);
|
|
||||||
toast.success(`Event logging ${enabled ? 'enabled' : 'disabled'}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
<FeatureSection
|
<FeatureSection
|
||||||
@ -262,9 +332,9 @@ export default function FeaturesTab() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
value={promptId}
|
value={promptIdLocal}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPromptId(e.target.value);
|
setPromptIdLocal(e.target.value);
|
||||||
toast.success('Prompt template updated');
|
toast.success('Prompt template updated');
|
||||||
}}
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -4,6 +4,7 @@ import { logStore } from '~/lib/stores/logs';
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { classNames } from '~/utils/classNames';
|
import { classNames } from '~/utils/classNames';
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||||
|
|
||||||
interface NotificationDetails {
|
interface NotificationDetails {
|
||||||
type?: string;
|
type?: string;
|
||||||
@ -14,8 +15,10 @@ interface NotificationDetails {
|
|||||||
updateUrl?: string;
|
updateUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FilterType = 'all' | 'system' | 'error' | 'warning' | 'update' | 'info' | 'provider' | 'network';
|
||||||
|
|
||||||
const NotificationsTab = () => {
|
const NotificationsTab = () => {
|
||||||
const [filter, setFilter] = useState<'all' | 'error' | 'warning' | 'update'>('all');
|
const [filter, setFilter] = useState<FilterType>('all');
|
||||||
const logs = useStore(logStore.logs);
|
const logs = useStore(logStore.logs);
|
||||||
|
|
||||||
const handleClearNotifications = () => {
|
const handleClearNotifications = () => {
|
||||||
@ -29,42 +32,64 @@ const NotificationsTab = () => {
|
|||||||
const filteredLogs = Object.values(logs)
|
const filteredLogs = Object.values(logs)
|
||||||
.filter((log) => {
|
.filter((log) => {
|
||||||
if (filter === 'all') {
|
if (filter === 'all') {
|
||||||
return log.level === 'error' || log.level === 'warning' || log.details?.type === 'update';
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter === 'update') {
|
if (filter === 'update') {
|
||||||
return log.details?.type === 'update';
|
return log.details?.type === 'update';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filter === 'system') {
|
||||||
|
return log.category === 'system';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter === 'provider') {
|
||||||
|
return log.category === 'provider';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter === 'network') {
|
||||||
|
return log.category === 'network';
|
||||||
|
}
|
||||||
|
|
||||||
return log.level === filter;
|
return log.level === filter;
|
||||||
})
|
})
|
||||||
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
||||||
|
|
||||||
const getNotificationStyle = (log: (typeof filteredLogs)[0]) => {
|
const getNotificationStyle = (level: string, type?: string) => {
|
||||||
if (log.details?.type === 'update') {
|
if (type === 'update') {
|
||||||
return {
|
return {
|
||||||
border: 'border-purple-200 dark:border-purple-900/50',
|
icon: 'i-ph:arrow-circle-up',
|
||||||
bg: 'bg-purple-50 dark:bg-purple-900/20',
|
color: 'text-purple-500 dark:text-purple-400',
|
||||||
icon: 'i-ph:arrow-circle-up text-purple-600 dark:text-purple-400',
|
bg: 'hover:bg-purple-500/10 dark:hover:bg-purple-500/20',
|
||||||
text: 'text-purple-900 dark:text-purple-300',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.level === 'error') {
|
switch (level) {
|
||||||
return {
|
case 'error':
|
||||||
border: 'border-red-200 dark:border-red-900/50',
|
return {
|
||||||
bg: 'bg-red-50 dark:bg-red-900/20',
|
icon: 'i-ph:warning-circle',
|
||||||
icon: 'i-ph:warning-circle text-red-600 dark:text-red-400',
|
color: 'text-red-500 dark:text-red-400',
|
||||||
text: 'text-red-900 dark:text-red-300',
|
bg: 'hover:bg-red-500/10 dark:hover:bg-red-500/20',
|
||||||
};
|
};
|
||||||
|
case 'warning':
|
||||||
|
return {
|
||||||
|
icon: 'i-ph:warning',
|
||||||
|
color: 'text-yellow-500 dark:text-yellow-400',
|
||||||
|
bg: 'hover:bg-yellow-500/10 dark:hover:bg-yellow-500/20',
|
||||||
|
};
|
||||||
|
case 'info':
|
||||||
|
return {
|
||||||
|
icon: 'i-ph:info',
|
||||||
|
color: 'text-blue-500 dark:text-blue-400',
|
||||||
|
bg: 'hover:bg-blue-500/10 dark:hover:bg-blue-500/20',
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
icon: 'i-ph:bell',
|
||||||
|
color: 'text-gray-500 dark:text-gray-400',
|
||||||
|
bg: 'hover:bg-gray-500/10 dark:hover:bg-gray-500/20',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
border: 'border-yellow-200 dark:border-yellow-900/50',
|
|
||||||
bg: 'bg-yellow-50 dark:bg-yellow-900/20',
|
|
||||||
icon: 'i-ph:warning text-yellow-600 dark:text-yellow-400',
|
|
||||||
text: 'text-yellow-900 dark:text-yellow-300',
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderNotificationDetails = (details: NotificationDetails) => {
|
const renderNotificationDetails = (details: NotificationDetails) => {
|
||||||
@ -79,7 +104,16 @@ const NotificationsTab = () => {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => details.updateUrl && handleUpdateAction(details.updateUrl)}
|
onClick={() => details.updateUrl && handleUpdateAction(details.updateUrl)}
|
||||||
className="mt-2 inline-flex items-center gap-2 rounded-md bg-purple-50 px-3 py-1.5 text-sm font-medium text-purple-600 hover:bg-purple-100 dark:bg-purple-900/20 dark:text-purple-400 dark:hover:bg-purple-900/30"
|
className={classNames(
|
||||||
|
'mt-2 inline-flex items-center gap-2',
|
||||||
|
'rounded-lg px-3 py-1.5',
|
||||||
|
'text-sm font-medium',
|
||||||
|
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
||||||
|
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||||
|
'text-gray-900 dark:text-white',
|
||||||
|
'hover:bg-purple-500/10 dark:hover:bg-purple-500/20',
|
||||||
|
'transition-all duration-200',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<span className="i-ph:git-branch text-lg" />
|
<span className="i-ph:git-branch text-lg" />
|
||||||
View Changes
|
View Changes
|
||||||
@ -91,54 +125,134 @@ const NotificationsTab = () => {
|
|||||||
return details.message ? <p className="text-sm text-gray-600 dark:text-gray-400">{details.message}</p> : null;
|
return details.message ? <p className="text-sm text-gray-600 dark:text-gray-400">{details.message}</p> : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterOptions: { id: FilterType; label: string; icon: string }[] = [
|
||||||
|
{ id: 'all', label: 'All Notifications', icon: 'i-ph:bell' },
|
||||||
|
{ id: 'system', label: 'System', icon: 'i-ph:gear' },
|
||||||
|
{ id: 'update', label: 'Updates', icon: 'i-ph:arrow-circle-up' },
|
||||||
|
{ id: 'error', label: 'Errors', icon: 'i-ph:warning-circle' },
|
||||||
|
{ id: 'warning', label: 'Warnings', icon: 'i-ph:warning' },
|
||||||
|
{ id: 'info', label: 'Information', icon: 'i-ph:info' },
|
||||||
|
{ id: 'provider', label: 'Providers', icon: 'i-ph:robot' },
|
||||||
|
{ id: 'network', label: 'Network', icon: 'i-ph:wifi-high' },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col gap-6">
|
<div className="flex h-full flex-col gap-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<DropdownMenu.Root>
|
||||||
<select
|
<DropdownMenu.Trigger asChild>
|
||||||
value={filter}
|
<button
|
||||||
onChange={(e) => setFilter(e.target.value as 'all' | 'error' | 'warning' | 'update')}
|
className={classNames(
|
||||||
className="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm shadow-sm dark:border-gray-700 dark:bg-gray-800"
|
'flex items-center gap-2',
|
||||||
>
|
'rounded-lg px-3 py-1.5',
|
||||||
<option value="all">All Notifications</option>
|
'text-sm text-gray-900 dark:text-white',
|
||||||
<option value="update">Updates</option>
|
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
||||||
<option value="error">Errors</option>
|
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||||
<option value="warning">Warnings</option>
|
'hover:bg-purple-500/10 dark:hover:bg-purple-500/20',
|
||||||
</select>
|
'transition-all duration-200',
|
||||||
</div>
|
)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
filterOptions.find((opt) => opt.id === filter)?.icon || 'i-ph:funnel',
|
||||||
|
'text-lg text-gray-500 dark:text-gray-400',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{filterOptions.find((opt) => opt.id === filter)?.label || 'Filter Notifications'}
|
||||||
|
<span className="i-ph:caret-down text-lg text-gray-500 dark:text-gray-400" />
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
|
||||||
|
<DropdownMenu.Portal>
|
||||||
|
<DropdownMenu.Content
|
||||||
|
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"
|
||||||
|
sideOffset={5}
|
||||||
|
align="start"
|
||||||
|
side="bottom"
|
||||||
|
>
|
||||||
|
{filterOptions.map((option) => (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
key={option.id}
|
||||||
|
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"
|
||||||
|
onClick={() => setFilter(option.id)}
|
||||||
|
>
|
||||||
|
<div className="mr-3 flex h-5 w-5 items-center justify-center">
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
option.icon,
|
||||||
|
'text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="group-hover:text-purple-500 transition-colors">{option.label}</span>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
))}
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleClearNotifications}
|
onClick={handleClearNotifications}
|
||||||
className="rounded-md bg-gray-50 px-3 py-1.5 text-sm font-medium text-gray-600 hover:bg-gray-100 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
|
className={classNames(
|
||||||
|
'group flex items-center gap-2',
|
||||||
|
'rounded-lg px-3 py-1.5',
|
||||||
|
'text-sm text-gray-900 dark:text-white',
|
||||||
|
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
||||||
|
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||||
|
'hover:bg-purple-500/10 dark:hover:bg-purple-500/20',
|
||||||
|
'transition-all duration-200',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
|
<span className="i-ph:trash text-lg text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
|
||||||
Clear All
|
Clear All
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{filteredLogs.length === 0 ? (
|
{filteredLogs.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center gap-4 rounded-lg border border-gray-200 p-8 text-center dark:border-gray-700">
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className={classNames(
|
||||||
|
'flex flex-col items-center justify-center gap-4',
|
||||||
|
'rounded-lg p-8 text-center',
|
||||||
|
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
||||||
|
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<span className="i-ph:bell-slash text-4xl text-gray-400 dark:text-gray-600" />
|
<span className="i-ph:bell-slash text-4xl text-gray-400 dark:text-gray-600" />
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100">No Notifications</h3>
|
<h3 className="text-sm font-medium text-gray-900 dark:text-white">No Notifications</h3>
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400">You're all caught up!</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">You're all caught up!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
filteredLogs.map((log) => {
|
filteredLogs.map((log) => {
|
||||||
const style = getNotificationStyle(log);
|
const style = getNotificationStyle(log.level, log.details?.type);
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={log.id}
|
key={log.id}
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className={classNames('flex flex-col gap-2 rounded-lg border p-4', style.border, style.bg)}
|
className={classNames(
|
||||||
|
'flex flex-col gap-2',
|
||||||
|
'rounded-lg p-4',
|
||||||
|
'bg-[#FAFAFA] dark:bg-[#0A0A0A]',
|
||||||
|
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||||
|
style.bg,
|
||||||
|
'transition-all duration-200',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<span className={classNames('text-lg', style.icon)} />
|
<span className={classNames('text-lg', style.icon, style.color)} />
|
||||||
<div>
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className={classNames('text-sm font-medium', style.text)}>{log.message}</h3>
|
<h3 className="text-sm font-medium text-gray-900 dark:text-white">{log.message}</h3>
|
||||||
{log.details && renderNotificationDetails(log.details as NotificationDetails)}
|
{log.details && renderNotificationDetails(log.details as NotificationDetails)}
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Category: {log.category}
|
||||||
|
{log.subCategory ? ` > ${log.subCategory}` : ''}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<time className="shrink-0 text-xs text-gray-500 dark:text-gray-400">
|
<time className="shrink-0 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
@ -53,13 +53,19 @@ export const isDebugMode = atom(false);
|
|||||||
const savedEventLogs = Cookies.get('isEventLogsEnabled');
|
const savedEventLogs = Cookies.get('isEventLogsEnabled');
|
||||||
export const isEventLogsEnabled = atom(savedEventLogs === 'true');
|
export const isEventLogsEnabled = atom(savedEventLogs === 'true');
|
||||||
|
|
||||||
|
// Local models settings
|
||||||
export const isLocalModelsEnabled = atom(true);
|
export const isLocalModelsEnabled = atom(true);
|
||||||
|
|
||||||
|
// Prompt settings
|
||||||
export const promptStore = atom<string>('default');
|
export const promptStore = atom<string>('default');
|
||||||
|
|
||||||
|
// Branch settings
|
||||||
export const latestBranchStore = atom(false);
|
export const latestBranchStore = atom(false);
|
||||||
|
|
||||||
|
// Template settings
|
||||||
export const autoSelectStarterTemplate = atom(false);
|
export const autoSelectStarterTemplate = atom(false);
|
||||||
|
|
||||||
|
// Context optimization settings
|
||||||
export const enableContextOptimizationStore = atom(false);
|
export const enableContextOptimizationStore = atom(false);
|
||||||
|
|
||||||
// Initialize tab configuration from cookie or default
|
// Initialize tab configuration from cookie or default
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { ActionFunctionArgs } from '@remix-run/cloudflare';
|
import type { ActionFunctionArgs, LoaderFunction } from '@remix-run/cloudflare';
|
||||||
import { json } from '@remix-run/cloudflare';
|
import { json } from '@remix-run/cloudflare';
|
||||||
|
|
||||||
interface PackageJson {
|
interface PackageJson {
|
||||||
@ -27,6 +27,23 @@ const packageJson = {
|
|||||||
},
|
},
|
||||||
} as PackageJson;
|
} as PackageJson;
|
||||||
|
|
||||||
|
export const loader: LoaderFunction = async ({ request: _request }) => {
|
||||||
|
try {
|
||||||
|
return json({
|
||||||
|
name: packageJson.name,
|
||||||
|
version: packageJson.version,
|
||||||
|
description: packageJson.description,
|
||||||
|
license: packageJson.license,
|
||||||
|
nodeVersion: process.version,
|
||||||
|
dependencies: packageJson.dependencies,
|
||||||
|
devDependencies: packageJson.devDependencies,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get webapp info:', error);
|
||||||
|
return json({ error: 'Failed to get webapp information' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const action = async ({ request: _request }: ActionFunctionArgs) => {
|
export const action = async ({ request: _request }: ActionFunctionArgs) => {
|
||||||
try {
|
try {
|
||||||
return json({
|
return json({
|
||||||
|
Loading…
Reference in New Issue
Block a user