diff --git a/client/src/components/Prompts/Groups/ChatGroupItem.tsx b/client/src/components/Prompts/Groups/ChatGroupItem.tsx index 2ac6fbcfda..b372f8a59b 100644 --- a/client/src/components/Prompts/Groups/ChatGroupItem.tsx +++ b/client/src/components/Prompts/Groups/ChatGroupItem.tsx @@ -27,6 +27,7 @@ function ChatGroupItem({ const [isPreviewDialogOpen, setPreviewDialogOpen] = useState(false); const [isVariableDialogOpen, setVariableDialogOpen] = useState(false); const onEditClick = useCustomLink(`/d/prompts/${group._id}`); + const groupIsGlobal = useMemo( () => instanceProjectId != null && group.projectIds?.includes(instanceProjectId), [group, instanceProjectId], @@ -34,13 +35,14 @@ function ChatGroupItem({ const isOwner = useMemo(() => user?.id === group.author, [user, group]); const onCardClick: React.MouseEventHandler = () => { - const text = group.productionPrompt?.prompt ?? ''; - if (!text) { + const text = group.productionPrompt?.prompt; + if (!text?.trim()) { return; } - const hasVariables = detectVariables(text); - if (hasVariables) { - return setVariableDialogOpen(true); + + if (detectVariables(text)) { + setVariableDialogOpen(true); + return; } submitPrompt(text); @@ -59,33 +61,47 @@ function ChatGroupItem({ } >
- {groupIsGlobal === true && } + {groupIsGlobal === true && ( + + )} { e.stopPropagation(); setPreviewDialogOpen(true); }} className="w-full cursor-pointer rounded-lg text-text-secondary hover:bg-surface-hover focus:bg-surface-hover disabled:cursor-not-allowed" > - + {isOwner && ( @@ -98,7 +114,7 @@ function ChatGroupItem({ onEditClick(e); }} > - +
diff --git a/client/src/components/ui/ThemeSelector.tsx b/client/src/components/ui/ThemeSelector.tsx index 30339bea8b..e643f05d87 100644 --- a/client/src/components/ui/ThemeSelector.tsx +++ b/client/src/components/ui/ThemeSelector.tsx @@ -1,7 +1,13 @@ -import React, { useContext, useCallback, useEffect } from 'react'; +import React, { useContext, useCallback, useEffect, useState } from 'react'; import { Sun, Moon, Monitor } from 'lucide-react'; import { ThemeContext } from '~/hooks'; +declare global { + interface Window { + lastThemeChange?: number; + } +} + const Theme = ({ theme, onChange }: { theme: string; onChange: (value: string) => void }) => { const themeIcons = { system: , @@ -9,32 +15,55 @@ const Theme = ({ theme, onChange }: { theme: string; onChange: (value: string) = light: , }; + const nextTheme = theme === 'dark' ? 'light' : 'dark'; + const label = `Switch to ${nextTheme} theme`; + + useEffect(() => { + const handleKeyPress = (e: KeyboardEvent) => { + if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 't') { + e.preventDefault(); + onChange(nextTheme); + } + }; + window.addEventListener('keydown', handleKeyPress); + return () => window.removeEventListener('keydown', handleKeyPress); + }, [nextTheme, onChange]); + return ( -
- -
+ ); }; const ThemeSelector = ({ returnThemeOnly }: { returnThemeOnly?: boolean }) => { const { theme, setTheme } = useContext(ThemeContext); + const [announcement, setAnnouncement] = useState(''); const changeTheme = useCallback( (value: string) => { + const now = Date.now(); + if (typeof window.lastThemeChange === 'number' && now - window.lastThemeChange < 500) { + return; + } + window.lastThemeChange = now; + setTheme(value); + setAnnouncement(value === 'dark' ? 'Dark theme enabled' : 'Light theme enabled'); }, [setTheme], ); @@ -46,6 +75,13 @@ const ThemeSelector = ({ returnThemeOnly }: { returnThemeOnly?: boolean }) => { } }, [theme, setTheme]); + useEffect(() => { + if (announcement) { + const timeout = setTimeout(() => setAnnouncement(''), 1000); + return () => clearTimeout(timeout); + } + }, [announcement]); + if (returnThemeOnly === true) { return ; } @@ -55,6 +91,11 @@ const ThemeSelector = ({ returnThemeOnly }: { returnThemeOnly?: boolean }) => {
+ {announcement && ( +
+ {announcement} +
+ )} ); };