feat: introduced a token-based theming system across the UI
* feat: added themes system * feat: smart sidebar auto-hide for files/diff tabs + lower files sidebar threshold * feat: added Checkbox component and update theming - Add reusable Checkbox component for toggles across UI - Replace several inputs with Checkbox in settings and commands panels - Add DiffIcon and apply surface/border theming to key UI areas * feat: Add convert-vscode-theme.cjs to convert VS Code themes to OpenChamber format * refactor: remove unused permission logic from ChatInput - Remove unused permission rules parsing logic from ChatInput - Memoize renderTheme in DiffWorkerProvider to avoid unnecessary recalculations - Remove forceOpaque helper in vscode theme adapter * fix: guard VSCode theme loading in MarkdownRenderer * feat: add custom user themes loading and reload - Load user themes from ~/.config/openchamber/themes at runtime - Expose /api/config/themes to fetch custom themes - Allow theme reloading from Settings → Theme → Reload themes in the UI
This commit is contained in:
committed by
GitHub
parent
5bb2c56bc0
commit
ddedc02687
@@ -756,7 +756,7 @@ export const AgentsPage: React.FC = () => {
|
||||
const newValue = Math.max(0, current - 0.1);
|
||||
setTemperature(parseFloat(newValue.toFixed(1)));
|
||||
}}
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 flex items-center justify-center h-6 w-6 rounded hover:bg-accent text-muted-foreground hover:text-foreground"
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 flex items-center justify-center h-6 w-6 rounded hover:bg-interactive-hover text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RiSubtractLine className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -795,7 +795,7 @@ export const AgentsPage: React.FC = () => {
|
||||
const newValue = Math.min(2, current + 0.1);
|
||||
setTemperature(parseFloat(newValue.toFixed(1)));
|
||||
}}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center justify-center h-6 w-6 rounded hover:bg-accent text-muted-foreground hover:text-foreground"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center justify-center h-6 w-6 rounded hover:bg-interactive-hover text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RiAddLine className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -824,7 +824,7 @@ export const AgentsPage: React.FC = () => {
|
||||
const newValue = Math.max(0, current - 0.1);
|
||||
setTopP(parseFloat(newValue.toFixed(1)));
|
||||
}}
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 flex items-center justify-center h-6 w-6 rounded hover:bg-accent text-muted-foreground hover:text-foreground"
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 flex items-center justify-center h-6 w-6 rounded hover:bg-interactive-hover text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RiSubtractLine className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -863,7 +863,7 @@ export const AgentsPage: React.FC = () => {
|
||||
const newValue = Math.min(1, current + 0.1);
|
||||
setTopP(parseFloat(newValue.toFixed(1)));
|
||||
}}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center justify-center h-6 w-6 rounded hover:bg-accent text-muted-foreground hover:text-foreground"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center justify-center h-6 w-6 rounded hover:bg-interactive-hover text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RiAddLine className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
||||
@@ -405,7 +405,7 @@ export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) =>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setRenameDialogAgent(null)}
|
||||
className="text-foreground hover:bg-muted hover:text-foreground"
|
||||
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -446,7 +446,7 @@ const AgentListItem: React.FC<AgentListItemProps> = ({
|
||||
<div
|
||||
className={cn(
|
||||
'group relative flex items-center rounded-md px-1.5 py-1 transition-all duration-200',
|
||||
isSelected ? 'dark:bg-accent/80 bg-primary/12' : 'hover:dark:bg-accent/40 hover:bg-primary/6'
|
||||
isSelected ? 'bg-interactive-selection' : 'hover:bg-interactive-hover'
|
||||
)}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center">
|
||||
|
||||
@@ -161,7 +161,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
ref={(el) => { itemRefs.current[flatIndex] = el; }}
|
||||
className={cn(
|
||||
"typography-meta group flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer",
|
||||
isHighlighted ? "bg-accent" : "hover:bg-accent/50"
|
||||
isHighlighted ? "bg-interactive-selection" : "hover:bg-interactive-hover/50"
|
||||
)}
|
||||
onClick={() => handleProviderAndModelChange(provID, modID)}
|
||||
onMouseEnter={() => setSelectedIndex(flatIndex)}
|
||||
@@ -190,8 +190,8 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
toggleFavoriteModel(provID, modID);
|
||||
}}
|
||||
className={cn(
|
||||
"model-favorite-button flex h-4 w-4 items-center justify-center hover:text-yellow-600",
|
||||
isFavorite ? "text-yellow-500" : "text-muted-foreground"
|
||||
"model-favorite-button flex h-4 w-4 items-center justify-center hover:text-primary/80",
|
||||
isFavorite ? "text-primary" : "text-muted-foreground"
|
||||
)}
|
||||
aria-label={isFavorite ? "Unfavorite" : "Favorite"}
|
||||
title={isFavorite ? "Remove from favorites" : "Add to favorites"}
|
||||
@@ -253,7 +253,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
<div className="space-y-1">
|
||||
{/* Favorites Section for Mobile */}
|
||||
{favoriteModelsList.length > 0 && (
|
||||
<div className="rounded-xl border border-border/40 bg-background/95 mb-2">
|
||||
<div className="rounded-xl border border-border/40 bg-[var(--surface-elevated)] mb-2">
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
Favorites
|
||||
</div>
|
||||
@@ -293,7 +293,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
e.stopPropagation();
|
||||
toggleFavoriteModel(providerID, modelID);
|
||||
}}
|
||||
className="model-favorite-button flex h-8 w-8 items-center justify-center text-yellow-500 hover:text-yellow-600 active:scale-95 touch-manipulation"
|
||||
className="model-favorite-button flex h-8 w-8 items-center justify-center text-primary hover:text-primary/80 active:scale-95 touch-manipulation"
|
||||
aria-label="Unfavorite"
|
||||
>
|
||||
<RiStarFill className="h-4 w-4" />
|
||||
@@ -307,7 +307,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
|
||||
{/* Recents Section for Mobile */}
|
||||
{recentModelsList.length > 0 && (
|
||||
<div className="rounded-xl border border-border/40 bg-background/95 mb-2">
|
||||
<div className="rounded-xl border border-border/40 bg-[var(--surface-elevated)] mb-2">
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
Recents
|
||||
</div>
|
||||
@@ -347,7 +347,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
e.stopPropagation();
|
||||
toggleFavoriteModel(providerID, modelID);
|
||||
}}
|
||||
className="model-favorite-button flex h-8 w-8 items-center justify-center text-muted-foreground/50 hover:text-yellow-600 active:scale-95 touch-manipulation"
|
||||
className="model-favorite-button flex h-8 w-8 items-center justify-center text-muted-foreground/50 hover:text-primary/80 active:scale-95 touch-manipulation"
|
||||
aria-label="Favorite"
|
||||
>
|
||||
<RiStarLine className="h-4 w-4" />
|
||||
@@ -367,7 +367,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
const isExpanded = expandedMobileProviders.has(provider.id);
|
||||
|
||||
return (
|
||||
<div key={provider.id} className="rounded-xl border border-border/40 bg-background/95">
|
||||
<div key={provider.id} className="rounded-xl border border-border/40 bg-[var(--surface-elevated)]">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between gap-1.5 px-2 py-1.5 text-left"
|
||||
@@ -425,9 +425,9 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
toggleFavoriteModel(provider.id as string, modelItem.id as string);
|
||||
}}
|
||||
className={cn(
|
||||
"flex h-8 w-8 items-center justify-center active:scale-95 touch-manipulation",
|
||||
"flex h-8 w-8 items-center justify-center active:scale-95 touch-manipulation hover:text-primary/80",
|
||||
isFavoriteModel(provider.id as string, modelItem.id as string)
|
||||
? "text-yellow-500"
|
||||
? "text-primary"
|
||||
: "text-muted-foreground/50"
|
||||
)}
|
||||
aria-label={isFavoriteModel(provider.id as string, modelItem.id as string) ? "Unfavorite" : "Favorite"}
|
||||
@@ -454,7 +454,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between rounded-lg border border-border/40 bg-background/95 px-2 py-1.5 text-left"
|
||||
className="flex w-full items-center justify-between rounded-lg border border-border/40 bg-[var(--surface-elevated)] px-2 py-1.5 text-left"
|
||||
onClick={() => {
|
||||
handleProviderAndModelChange('', '');
|
||||
closeMobilePanel();
|
||||
@@ -474,7 +474,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
type="button"
|
||||
onClick={() => setIsMobilePanelOpen(true)}
|
||||
className={cn(
|
||||
'flex w-full items-center justify-between gap-2 rounded-lg border border-border/40 bg-background/95 px-2 py-1.5 text-left',
|
||||
'flex w-full items-center justify-between gap-2 rounded-lg border border-border/40 bg-[var(--surface-elevated)] px-2 py-1.5 text-left',
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -497,7 +497,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className={cn(
|
||||
'flex items-center gap-2 px-2 rounded-lg bg-accent/20 border border-border/20 cursor-pointer hover:bg-accent/30 h-6 w-fit',
|
||||
'flex items-center gap-2 px-2 rounded-lg bg-interactive-selection/20 border border-border/20 cursor-pointer hover:bg-interactive-hover/30 h-6 w-fit',
|
||||
className
|
||||
)}>
|
||||
{providerId ? (
|
||||
@@ -596,7 +596,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
<div
|
||||
className={cn(
|
||||
"typography-meta flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer",
|
||||
"hover:bg-accent/50"
|
||||
"hover:bg-interactive-hover/50"
|
||||
)}
|
||||
onClick={() => handleProviderAndModelChange('', '')}
|
||||
>
|
||||
@@ -618,7 +618,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
{/* Favorites Section */}
|
||||
{filteredFavorites.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||
<DropdownMenuLabel style={{ backgroundColor: 'var(--surface-elevated)' }} className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 border-b border-border/30">
|
||||
<RiStarFill className="h-4 w-4 text-primary" />
|
||||
Favorites
|
||||
</DropdownMenuLabel>
|
||||
@@ -633,7 +633,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
{filteredRecents.length > 0 && (
|
||||
<>
|
||||
{filteredFavorites.length > 0 && <DropdownMenuSeparator />}
|
||||
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||
<DropdownMenuLabel style={{ backgroundColor: 'var(--surface-elevated)' }} className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 border-b border-border/30">
|
||||
<RiTimeLine className="h-4 w-4" />
|
||||
Recent
|
||||
</DropdownMenuLabel>
|
||||
@@ -653,7 +653,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
{filteredProviders.map((provider, index) => (
|
||||
<React.Fragment key={provider.id}>
|
||||
{index > 0 && <DropdownMenuSeparator />}
|
||||
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||
<DropdownMenuLabel style={{ backgroundColor: 'var(--surface-elevated)' }} className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 border-b border-border/30">
|
||||
<ProviderLogo
|
||||
providerId={provider.id}
|
||||
className="h-4 w-4 flex-shrink-0"
|
||||
|
||||
Reference in New Issue
Block a user