| |
| import React, { memo, useCallback } from 'react'; |
| import { motion } from 'framer-motion'; |
| import { Switch } from '~/components/ui/Switch'; |
| import { useSettings } from '~/lib/hooks/useSettings'; |
| import { classNames } from '~/utils/classNames'; |
| import { toast } from 'react-toastify'; |
| import { PromptLibrary } from '~/lib/common/prompt-library'; |
|
|
| interface FeatureToggle { |
| id: string; |
| title: string; |
| description: string; |
| icon: string; |
| enabled: boolean; |
| beta?: boolean; |
| experimental?: boolean; |
| tooltip?: string; |
| } |
|
|
| const FeatureCard = memo( |
| ({ |
| feature, |
| index, |
| onToggle, |
| }: { |
| feature: FeatureToggle; |
| index: number; |
| onToggle: (id: string, enabled: boolean) => void; |
| }) => ( |
| <motion.div |
| key={feature.id} |
| layoutId={feature.id} |
| className={classNames( |
| 'relative group cursor-pointer', |
| 'bg-bolt-elements-background-depth-2', |
| 'hover:bg-bolt-elements-background-depth-3', |
| 'transition-colors duration-200', |
| 'rounded-lg overflow-hidden', |
| )} |
| initial={{ opacity: 0, y: 20 }} |
| animate={{ opacity: 1, y: 0 }} |
| transition={{ delay: index * 0.1 }} |
| > |
| <div className="p-4"> |
| <div className="flex items-center justify-between"> |
| <div className="flex items-center gap-3"> |
| <div className={classNames(feature.icon, 'w-5 h-5 text-bolt-elements-textSecondary')} /> |
| <div className="flex items-center gap-2"> |
| <h4 className="font-medium text-bolt-elements-textPrimary">{feature.title}</h4> |
| {feature.beta && ( |
| <span className="px-2 py-0.5 text-xs rounded-full bg-blue-500/10 text-blue-500 font-medium">Beta</span> |
| )} |
| {feature.experimental && ( |
| <span className="px-2 py-0.5 text-xs rounded-full bg-orange-500/10 text-orange-500 font-medium"> |
| Experimental |
| </span> |
| )} |
| </div> |
| </div> |
| <Switch checked={feature.enabled} onCheckedChange={(checked) => onToggle(feature.id, checked)} /> |
| </div> |
| <p className="mt-2 text-sm text-bolt-elements-textSecondary">{feature.description}</p> |
| {feature.tooltip && <p className="mt-1 text-xs text-bolt-elements-textTertiary">{feature.tooltip}</p>} |
| </div> |
| </motion.div> |
| ), |
| ); |
|
|
| const FeatureSection = memo( |
| ({ |
| title, |
| features, |
| icon, |
| description, |
| onToggleFeature, |
| }: { |
| title: string; |
| features: FeatureToggle[]; |
| icon: string; |
| description: string; |
| onToggleFeature: (id: string, enabled: boolean) => void; |
| }) => ( |
| <motion.div |
| layout |
| className="flex flex-col gap-4" |
| initial={{ opacity: 0, y: 20 }} |
| animate={{ opacity: 1, y: 0 }} |
| transition={{ duration: 0.3 }} |
| > |
| <div className="flex items-center gap-3"> |
| <div className={classNames(icon, 'text-xl text-purple-500')} /> |
| <div> |
| <h3 className="text-lg font-medium text-bolt-elements-textPrimary">{title}</h3> |
| <p className="text-sm text-bolt-elements-textSecondary">{description}</p> |
| </div> |
| </div> |
| |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| {features.map((feature, index) => ( |
| <FeatureCard key={feature.id} feature={feature} index={index} onToggle={onToggleFeature} /> |
| ))} |
| </div> |
| </motion.div> |
| ), |
| ); |
|
|
| export default function FeaturesTab() { |
| const { |
| autoSelectTemplate, |
| isLatestBranch, |
| contextOptimizationEnabled, |
| eventLogs, |
| setAutoSelectTemplate, |
| enableLatestBranch, |
| enableContextOptimization, |
| setEventLogs, |
| setPromptId, |
| promptId, |
| } = useSettings(); |
|
|
| |
| React.useEffect(() => { |
| |
| if (isLatestBranch === undefined) { |
| enableLatestBranch(false); |
| } |
|
|
| if (contextOptimizationEnabled === undefined) { |
| enableContextOptimization(true); |
| } |
|
|
| if (autoSelectTemplate === undefined) { |
| setAutoSelectTemplate(true); |
| } |
|
|
| if (promptId === undefined) { |
| setPromptId('default'); |
| } |
|
|
| if (eventLogs === undefined) { |
| setEventLogs(true); |
| } |
| }, []); |
|
|
| const handleToggleFeature = useCallback( |
| (id: string, enabled: boolean) => { |
| switch (id) { |
| case 'latestBranch': { |
| enableLatestBranch(enabled); |
| toast.success(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`); |
| break; |
| } |
|
|
| case 'autoSelectTemplate': { |
| setAutoSelectTemplate(enabled); |
| toast.success(`Auto select template ${enabled ? 'enabled' : 'disabled'}`); |
| break; |
| } |
|
|
| case 'contextOptimization': { |
| enableContextOptimization(enabled); |
| toast.success(`Context optimization ${enabled ? 'enabled' : 'disabled'}`); |
| break; |
| } |
|
|
| case 'eventLogs': { |
| setEventLogs(enabled); |
| toast.success(`Event logging ${enabled ? 'enabled' : 'disabled'}`); |
| break; |
| } |
|
|
| default: |
| break; |
| } |
| }, |
| [enableLatestBranch, setAutoSelectTemplate, enableContextOptimization, setEventLogs], |
| ); |
|
|
| const features = { |
| stable: [ |
| { |
| id: 'latestBranch', |
| title: 'Main Branch Updates', |
| description: 'Get the latest updates from the main branch', |
| icon: 'i-ph:git-branch', |
| enabled: isLatestBranch, |
| tooltip: 'Enabled by default to receive updates from the main development branch', |
| }, |
| { |
| id: 'autoSelectTemplate', |
| title: 'Auto Select Template', |
| description: 'Automatically select starter template', |
| icon: 'i-ph:selection', |
| enabled: autoSelectTemplate, |
| tooltip: 'Enabled by default to automatically select the most appropriate starter template', |
| }, |
| { |
| id: 'contextOptimization', |
| title: 'Context Optimization', |
| description: 'Optimize context for better responses', |
| icon: 'i-ph:brain', |
| enabled: contextOptimizationEnabled, |
| tooltip: 'Enabled by default for improved AI responses', |
| }, |
| { |
| id: 'eventLogs', |
| title: 'Event Logging', |
| description: 'Enable detailed event logging and history', |
| icon: 'i-ph:list-bullets', |
| enabled: eventLogs, |
| tooltip: 'Enabled by default to record detailed logs of system events and user actions', |
| }, |
| ], |
| beta: [], |
| }; |
|
|
| return ( |
| <div className="flex flex-col gap-8"> |
| <FeatureSection |
| title="Core Features" |
| features={features.stable} |
| icon="i-ph:check-circle" |
| description="Essential features that are enabled by default for optimal performance" |
| onToggleFeature={handleToggleFeature} |
| /> |
| |
| {features.beta.length > 0 && ( |
| <FeatureSection |
| title="Beta Features" |
| features={features.beta} |
| icon="i-ph:test-tube" |
| description="New features that are ready for testing but may have some rough edges" |
| onToggleFeature={handleToggleFeature} |
| /> |
| )} |
| |
| <motion.div |
| layout |
| className={classNames( |
| 'bg-bolt-elements-background-depth-2', |
| 'hover:bg-bolt-elements-background-depth-3', |
| 'transition-all duration-200', |
| 'rounded-lg p-4', |
| 'group', |
| )} |
| initial={{ opacity: 0, y: 20 }} |
| animate={{ opacity: 1, y: 0 }} |
| transition={{ delay: 0.3 }} |
| > |
| <div className="flex items-center gap-4"> |
| <div |
| className={classNames( |
| 'p-2 rounded-lg text-xl', |
| 'bg-bolt-elements-background-depth-3 group-hover:bg-bolt-elements-background-depth-4', |
| 'transition-colors duration-200', |
| 'text-purple-500', |
| )} |
| > |
| <div className="i-ph:book" /> |
| </div> |
| <div className="flex-1"> |
| <h4 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-purple-500 transition-colors"> |
| Prompt Library |
| </h4> |
| <p className="text-xs text-bolt-elements-textSecondary mt-0.5"> |
| Choose a prompt from the library to use as the system prompt |
| </p> |
| </div> |
| <select |
| value={promptId} |
| onChange={(e) => { |
| setPromptId(e.target.value); |
| toast.success('Prompt template updated'); |
| }} |
| className={classNames( |
| 'p-2 rounded-lg text-sm min-w-[200px]', |
| 'bg-bolt-elements-background-depth-3 border border-bolt-elements-borderColor', |
| 'text-bolt-elements-textPrimary', |
| 'focus:outline-none focus:ring-2 focus:ring-purple-500/30', |
| 'group-hover:border-purple-500/30', |
| 'transition-all duration-200', |
| )} |
| > |
| {PromptLibrary.getList().map((x) => ( |
| <option key={x.id} value={x.id}> |
| {x.label} |
| </option> |
| ))} |
| </select> |
| </div> |
| </motion.div> |
| </div> |
| ); |
| } |
|
|