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
@@ -1371,7 +1371,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
value={mobileModelQuery}
|
||||
onChange={(event) => setMobileModelQuery(event.target.value)}
|
||||
placeholder="Search providers or models"
|
||||
className="pl-7 h-9 rounded-xl border-border/40 bg-background/95 typography-meta"
|
||||
className="pl-7 h-9 rounded-xl border-border/40 bg-[var(--surface-elevated)] typography-meta"
|
||||
/>
|
||||
{mobileModelQuery && (
|
||||
<button
|
||||
@@ -1394,7 +1394,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
|
||||
{/* Favorites Section for Mobile */}
|
||||
{!mobileModelQuery && favoriteModelsList.length > 0 && (
|
||||
<div className="rounded-xl border border-border/40 bg-background/95 overflow-hidden">
|
||||
<div className="rounded-xl border border-border/40 bg-[var(--surface-elevated)] overflow-hidden">
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
<RiStarFill className="h-3 w-3 inline-block mr-1.5 text-primary" />
|
||||
Favorites
|
||||
@@ -1413,7 +1413,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
'flex w-full items-start gap-2 border-b border-border/30 px-2 py-1.5 text-left last:border-b-0',
|
||||
'focus:outline-none focus-visible:ring-1 focus-visible:ring-primary',
|
||||
'first:rounded-t-xl last:rounded-b-xl transition-colors',
|
||||
isSelected ? 'bg-primary/15 text-primary' : 'hover:bg-muted'
|
||||
isSelected ? 'bg-interactive-selection/15 text-interactive-selection-foreground' : 'hover:bg-interactive-hover'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
@@ -1440,7 +1440,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
|
||||
{/* Recent Section for Mobile */}
|
||||
{!mobileModelQuery && recentModelsList.length > 0 && (
|
||||
<div className="rounded-xl border border-border/40 bg-background/95 overflow-hidden">
|
||||
<div className="rounded-xl border border-border/40 bg-[var(--surface-elevated)] overflow-hidden">
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
<RiTimeLine className="h-3 w-3 inline-block mr-1.5" />
|
||||
Recent
|
||||
@@ -1459,7 +1459,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
'flex w-full items-start gap-2 border-b border-border/30 px-2 py-1.5 text-left last:border-b-0',
|
||||
'focus:outline-none focus-visible:ring-1 focus-visible:ring-primary',
|
||||
'first:rounded-t-xl last:rounded-b-xl transition-colors',
|
||||
isSelected ? 'bg-primary/15 text-primary' : 'hover:bg-muted'
|
||||
isSelected ? 'bg-interactive-selection/15 text-interactive-selection-foreground' : 'hover:bg-interactive-hover'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
@@ -1492,8 +1492,8 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
const isActiveProvider = provider.id === currentProviderId;
|
||||
const isExpanded = expandedMobileProviders.has(provider.id) || normalizedQuery.length > 0;
|
||||
|
||||
return (
|
||||
<div key={provider.id} className="rounded-xl border border-border/40 bg-background/95 overflow-hidden">
|
||||
return (
|
||||
<div key={provider.id} className="rounded-xl border border-border/40 bg-[var(--surface-elevated)] overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleMobileProviderExpansion(provider.id)}
|
||||
@@ -1533,9 +1533,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
className={cn(
|
||||
'flex w-full items-start gap-2 border-b border-border/30 px-2 py-1.5 last:border-b-0',
|
||||
'rounded-lg transition-colors',
|
||||
!isSelected && 'hover:bg-muted',
|
||||
!isSelected && 'hover:bg-interactive-hover',
|
||||
isSelected
|
||||
? 'bg-primary/15 text-primary'
|
||||
? 'bg-interactive-selection/15 text-interactive-selection-foreground'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
@@ -1584,9 +1584,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
toggleFavoriteModel(provider.id as string, model.id as string);
|
||||
}}
|
||||
className={cn(
|
||||
"model-favorite-button flex h-5 w-5 items-center justify-center hover:text-yellow-600 flex-shrink-0",
|
||||
"model-favorite-button flex h-5 w-5 items-center justify-center hover:text-primary/80 flex-shrink-0",
|
||||
isFavoriteModel(provider.id as string, model.id as string)
|
||||
? "text-yellow-500"
|
||||
? "text-primary"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
aria-label={isFavoriteModel(provider.id as string, model.id as string) ? "Unfavorite" : "Favorite"}
|
||||
@@ -1857,7 +1857,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
ref={(el) => { modelItemRefs.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(providerID, modelID)}
|
||||
onMouseEnter={() => setModelSelectedIndex(flatIndex)}
|
||||
@@ -1901,8 +1901,8 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
toggleFavoriteModel(providerID, modelID);
|
||||
}}
|
||||
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"}
|
||||
@@ -2021,7 +2021,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
'model-controls__model-trigger flex items-center gap-1.5 cursor-pointer hover:opacity-70 min-w-0',
|
||||
'model-controls__model-trigger flex items-center gap-1.5 cursor-pointer hover:bg-transparent hover:opacity-70 min-w-0',
|
||||
buttonHeight
|
||||
)}
|
||||
>
|
||||
@@ -2082,7 +2082,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{/* 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>
|
||||
@@ -2097,7 +2100,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{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>
|
||||
@@ -2117,7 +2123,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{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"
|
||||
@@ -2148,7 +2157,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
onTouchCancel={handleLongPressEnd}
|
||||
className={cn(
|
||||
'model-controls__model-trigger flex items-center gap-1.5 min-w-0 focus:outline-none',
|
||||
'cursor-pointer hover:opacity-70',
|
||||
'cursor-pointer hover:bg-transparent hover:opacity-70',
|
||||
buttonHeight
|
||||
)}
|
||||
>
|
||||
@@ -2317,7 +2326,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
className={cn(
|
||||
'model-controls__variant-trigger flex items-center gap-1.5 transition-opacity min-w-0 focus:outline-none',
|
||||
buttonHeight,
|
||||
'cursor-pointer hover:opacity-70',
|
||||
'cursor-pointer hover:bg-transparent hover:opacity-70',
|
||||
)}
|
||||
>
|
||||
<RiBrainAi3Line className={cn(controlIconSize, 'flex-shrink-0', colorClass)} />
|
||||
@@ -2341,7 +2350,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
'model-controls__variant-trigger flex items-center gap-1.5 transition-opacity cursor-pointer hover:opacity-70 min-w-0',
|
||||
'model-controls__variant-trigger flex items-center gap-1.5 transition-colors cursor-pointer hover:bg-transparent hover:opacity-70 min-w-0',
|
||||
buttonHeight,
|
||||
)}
|
||||
>
|
||||
@@ -2403,7 +2412,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className={cn(
|
||||
'flex items-center gap-1.5 transition-opacity cursor-pointer hover:opacity-70 min-w-0',
|
||||
'flex items-center gap-1.5 transition-colors cursor-pointer hover:bg-transparent hover:opacity-70 min-w-0',
|
||||
buttonHeight
|
||||
)}>
|
||||
<RiAiAgentLine
|
||||
@@ -2485,9 +2494,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
onTouchEnd={handleLongPressEnd}
|
||||
onTouchCancel={handleLongPressEnd}
|
||||
className={cn(
|
||||
'model-controls__agent-trigger flex items-center gap-1.5 transition-opacity min-w-0 focus:outline-none',
|
||||
'model-controls__agent-trigger flex items-center gap-1.5 transition-colors min-w-0 focus:outline-none',
|
||||
buttonHeight,
|
||||
'cursor-pointer hover:opacity-70',
|
||||
'cursor-pointer hover:bg-transparent hover:opacity-70',
|
||||
)}
|
||||
>
|
||||
<RiAiAgentLine
|
||||
|
||||
Reference in New Issue
Block a user