perf(ui): migrate icons to SVG sprite system

Replace @remixicon/react with a shared Icon component that renders
via <use href> references to a single hidden SVG sprite. This reduces
DOM node count by replacing inline SVGs with lightweight references.

- Create Icon component with sprite injection (packages/ui/src/components/icon/)
- Migrate all 164 files from @remixicon/react to Icon component
- Auto-generate sprite data from remixicon bundle (scripts/generate-icon-sprite.mjs)
- Add bun run icons:generate to package.json
- Move @remixicon/react to devDependencies
- Add icon usage instructions to theme-system skill
This commit is contained in:
Bohdan Triapitsyn
2026-05-13 13:26:35 +03:00
parent b4cbd7f0d6
commit 14357257ae
173 changed files with 2342 additions and 2227 deletions
@@ -2,6 +2,7 @@ import React from 'react';
import { SIDEBAR_SECTION_CONFIG_MAP, SIDEBAR_SECTION_DESCRIPTIONS } from '@/constants/sidebar';
import type { SidebarSection } from '@/constants/sidebar';
import { useI18n } from '@/lib/i18n';
import { Icon } from "@/components/icon/Icon";
interface SectionPlaceholderProps {
sectionId: SidebarSection;
@@ -11,13 +12,13 @@ interface SectionPlaceholderProps {
export const SectionPlaceholder: React.FC<SectionPlaceholderProps> = ({ sectionId, variant }) => {
const { t } = useI18n();
const config = SIDEBAR_SECTION_CONFIG_MAP[sectionId];
const Icon = config.icon;
const icon = config.icon;
if (variant === 'sidebar') {
return (
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center">
<div className="rounded-full bg-accent/40 p-3 text-muted-foreground">
<Icon className="h-5 w-5" />
<Icon name={icon} className="h-5 w-5" />
</div>
<h3 className="typography-ui-label font-semibold text-foreground">{config.label}</h3>
<p className="typography-meta max-w-xs text-muted-foreground">
@@ -30,7 +31,7 @@ export const SectionPlaceholder: React.FC<SectionPlaceholderProps> = ({ sectionI
return (
<div className="flex h-full flex-col items-center justify-center gap-4 px-6 text-center">
<div className="rounded-full bg-accent/40 p-4 text-muted-foreground">
<Icon className="h-8 w-8" />
<Icon name={icon} className="h-8 w-8" />
</div>
<div className="flex flex-col gap-2">
<h2 className="typography-h2 font-semibold text-foreground">{config.label}</h2>
@@ -10,7 +10,6 @@ import { useDirectorySync } from '@/sync/sync-context';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { useDeviceInfo } from '@/lib/device';
import { opencodeClient } from '@/lib/opencode/client';
import { RiCloseLine, RiInformationLine, RiRobot2Line, RiSubtractLine, RiUser3Line, RiFolderLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { ModelSelector } from './ModelSelector';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
@@ -24,6 +23,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Icon } from "@/components/icon/Icon";
type PermissionAction = 'allow' | 'ask' | 'deny';
type PermissionRule = { permission: string; pattern: string; action: PermissionAction };
@@ -182,7 +182,6 @@ const buildPermissionConfigWithGlobal = (
return result as AgentConfig['permission'];
};
export const AgentsPage: React.FC = () => {
const { t } = useI18n();
const { isMobile } = useDeviceInfo();
@@ -305,7 +304,6 @@ export const AgentsPage: React.FC = () => {
currentRuleMap.get(buildRuleKey(permissionName, '*'))?.action
), [currentRuleMap]);
const getPatternRules = React.useCallback((permissionName: string): PermissionRule[] => (
permissionRules
.filter((rule) => rule.permission === permissionName && rule.pattern !== '*')
@@ -323,7 +321,6 @@ export const AgentsPage: React.FC = () => {
return Array.from(names).sort((a, b) => a.localeCompare(b));
}, [knownPermissionNames]);
const getPermissionSummary = React.useCallback((permissionName: string) => {
const defaultAction = permissionName === '*'
? globalPermission
@@ -608,12 +605,11 @@ export const AgentsPage: React.FC = () => {
}
};
if (!selectedAgentName) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-muted-foreground">
<RiRobot2Line className="mx-auto mb-3 h-12 w-12 opacity-50" />
<Icon name="robot-2" className="mx-auto mb-3 h-12 w-12 opacity-50" />
<p className="typography-body">{t('settings.agents.page.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.agents.page.empty.description')}</p>
</div>
@@ -669,13 +665,13 @@ export const AgentsPage: React.FC = () => {
<SelectContent align="end">
<SelectItem value="user">
<div className="flex items-center gap-2">
<RiUser3Line className="h-3.5 w-3.5" />
<Icon name="user-3" className="h-3.5 w-3.5" />
<span>{t('settings.common.scope.global')}</span>
</div>
</SelectItem>
<SelectItem value="project">
<div className="flex items-center gap-2">
<RiFolderLine className="h-3.5 w-3.5" />
<Icon name="folder" className="h-3.5 w-3.5" />
<span>{t('settings.common.scope.project')}</span>
</div>
</SelectItem>
@@ -704,7 +700,7 @@ export const AgentsPage: React.FC = () => {
<span className="typography-ui-label text-foreground">{t('settings.agents.page.field.mode')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.agents.page.field.modeTooltip')}
@@ -781,7 +777,7 @@ export const AgentsPage: React.FC = () => {
<span className="typography-ui-label text-foreground">{t('settings.agents.page.field.temperature')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.agents.page.field.temperatureTooltip')}
@@ -813,7 +809,7 @@ export const AgentsPage: React.FC = () => {
aria-label={t('settings.agents.page.field.clearTemperatureAria')}
title={t('settings.common.actions.clear')}
>
<RiCloseLine className="h-3.5 w-3.5" />
<Icon name="close" className="h-3.5 w-3.5" />
</Button>
)}
</div>
@@ -825,7 +821,7 @@ export const AgentsPage: React.FC = () => {
<span className="typography-ui-label text-foreground">{t('settings.agents.page.field.topP')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.agents.page.field.topPTooltip')}
@@ -857,7 +853,7 @@ export const AgentsPage: React.FC = () => {
aria-label={t('settings.agents.page.field.clearTopPAria')}
title={t('settings.common.actions.clear')}
>
<RiCloseLine className="h-3.5 w-3.5" />
<Icon name="close" className="h-3.5 w-3.5" />
</Button>
)}
</div>
@@ -981,7 +977,7 @@ export const AgentsPage: React.FC = () => {
onClick={() => revertRule(permissionName, '*')}
className="px-1.5 py-0 h-5"
>
<RiSubtractLine className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
<Icon name="subtract" className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
</Button>
)}
</div>
@@ -1028,7 +1024,7 @@ export const AgentsPage: React.FC = () => {
onClick={() => isAdded ? removeRule(rule.permission, rule.pattern) : revertRule(rule.permission, rule.pattern)}
className="px-1.5 py-0 h-5"
>
<RiSubtractLine className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
<Icon name="subtract" className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
</Button>
)}
</div>
@@ -17,7 +17,6 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { RiAddLine, RiAiAgentFill, RiAiAgentLine, RiDeleteBinLine, RiFileCopyLine, RiMore2Line, RiRobot2Line, RiRobotLine, RiRestartLine, RiEditLine } from '@remixicon/react';
import { useAgentsStore, isAgentBuiltIn, isAgentHidden, type AgentScope, type AgentDraft } from '@/stores/useAgentsStore';
import { useShallow } from 'zustand/react/shallow';
import { cn } from '@/lib/utils';
@@ -25,6 +24,7 @@ import type { Agent } from '@opencode-ai/sdk/v2';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { SettingsProjectSelector } from '@/components/sections/shared/SettingsProjectSelector';
import { SidebarGroup } from '@/components/sections/shared/SidebarGroup';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface AgentsSidebarProps {
@@ -228,7 +228,6 @@ export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) =>
});
setSelectedAgent(newName);
};
const handleOpenRenameDialog = (agent: Agent) => {
@@ -293,11 +292,11 @@ export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) =>
const getAgentModeIcon = (mode?: string) => {
switch (mode) {
case 'primary':
return <RiAiAgentLine className="h-3 w-3 text-primary" />;
return <Icon name="ai-agent" className="h-3 w-3 text-primary" />;
case 'all':
return <RiAiAgentFill className="h-3 w-3 text-primary" />;
return <Icon name="ai-agent-fill" className="h-3 w-3 text-primary" />;
case 'subagent':
return <RiRobotLine className="h-3 w-3 text-primary" />;
return <Icon name="robot" className="h-3 w-3 text-primary" />;
default:
return null;
}
@@ -339,7 +338,7 @@ export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) =>
className="h-7 w-7 px-0 -my-1 text-muted-foreground"
onClick={handleCreateNew}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -347,7 +346,7 @@ export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) =>
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="space-y-1 px-3 py-2 overflow-x-hidden">
{visibleAgents.length === 0 ? (
<div className="py-12 px-4 text-center text-muted-foreground">
<RiRobot2Line className="mx-auto mb-3 h-10 w-10 opacity-50" />
<Icon name="robot-2" className="mx-auto mb-3 h-10 w-10 opacity-50" />
<p className="typography-ui-label font-medium">{t('settings.agents.sidebar.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.agents.sidebar.empty.description')}</p>
</div>
@@ -580,7 +579,7 @@ const AgentListItem: React.FC<AgentListItemProps> = ({
variant="ghost"
className="h-6 w-6 px-0 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
>
<RiMore2Line className="h-3.5 w-3.5" />
<Icon name="more-2" className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-fit min-w-20">
@@ -591,7 +590,7 @@ const AgentListItem: React.FC<AgentListItemProps> = ({
onRename();
}}
>
<RiEditLine className="h-4 w-4 mr-px" />
<Icon name="edit" className="h-4 w-4 mr-px" />
{t('settings.common.actions.rename')}
</DropdownMenuItem>
)}
@@ -602,7 +601,7 @@ const AgentListItem: React.FC<AgentListItemProps> = ({
onDuplicate();
}}
>
<RiFileCopyLine className="h-4 w-4 mr-px" />
<Icon name="file-copy" className="h-4 w-4 mr-px" />
{t('settings.common.actions.duplicate')}
</DropdownMenuItem>
@@ -613,7 +612,7 @@ const AgentListItem: React.FC<AgentListItemProps> = ({
onReset();
}}
>
<RiRestartLine className="h-4 w-4 mr-px" />
<Icon name="restart" className="h-4 w-4 mr-px" />
{t('settings.common.actions.reset')}
</DropdownMenuItem>
)}
@@ -626,7 +625,7 @@ const AgentListItem: React.FC<AgentListItemProps> = ({
}}
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="h-4 w-4 mr-px" />
<Icon name="delete-bin" className="h-4 w-4 mr-px" />
{t('settings.common.actions.delete')}
</DropdownMenuItem>
)}
@@ -11,11 +11,11 @@ import { Input } from '@/components/ui/input';
import { useConfigStore } from '@/stores/useConfigStore';
import { useUIStore } from '@/stores/useUIStore';
import { useDeviceInfo } from '@/lib/device';
import { RiArrowDownSLine, RiArrowRightSLine, RiCheckLine, RiCloseLine, RiLoader4Line, RiPencilAiLine, RiSearchLine, RiStarFill, RiStarLine, RiTimeLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { Icon } from "@/components/icon/Icon";
import { useModelLists } from '@/hooks/useModelLists';
import type { ModelMetadata } from '@/types';
import { useI18n } from '@/lib/i18n';
@@ -204,7 +204,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
</div>
<div className="flex items-center gap-1 flex-shrink-0">
{isSelected && (
<RiCheckLine className="h-4 w-4 text-primary" />
<Icon name="check" className="h-4 w-4 text-primary" />
)}
<button
onClick={(e) => {
@@ -220,9 +220,9 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
title={isFavorite ? t('settings.agents.modelSelector.actions.removeFromFavorites') : t('settings.agents.modelSelector.actions.addToFavorites')}
>
{isFavorite ? (
<RiStarFill className="h-3.5 w-3.5" />
<Icon name="star-fill" className="h-3.5 w-3.5" />
) : (
<RiStarLine className="h-3.5 w-3.5" />
<Icon name="star" className="h-3.5 w-3.5" />
)}
</button>
</div>
@@ -319,7 +319,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
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={t('settings.agents.modelSelector.actions.unfavorite')}
>
<RiStarFill className="h-4 w-4" />
<Icon name="star-fill" className="h-4 w-4" />
</button>
</div>
);
@@ -373,7 +373,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
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={t('settings.agents.modelSelector.actions.favorite')}
>
<RiStarLine className="h-4 w-4" />
<Icon name="star" className="h-4 w-4" />
</button>
</div>
);
@@ -409,9 +409,9 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
)}
</div>
{isExpanded ? (
<RiArrowDownSLine className="h-3 w-3 text-muted-foreground" />
<Icon name="arrow-down-s" className="h-3 w-3 text-muted-foreground" />
) : (
<RiArrowRightSLine className="h-3 w-3 text-muted-foreground" />
<Icon name="arrow-right-s" className="h-3 w-3 text-muted-foreground" />
)}
</button>
@@ -458,9 +458,9 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
: t('settings.agents.modelSelector.actions.favorite')}
>
{isFavoriteModel(provider.id as string, modelItem.id as string) ? (
<RiStarFill className="h-4 w-4" />
<Icon name="star-fill" className="h-4 w-4" />
) : (
<RiStarLine className="h-4 w-4" />
<Icon name="star" className="h-4 w-4" />
)}
</button>
@@ -508,7 +508,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
<div className="flex items-center gap-2">
{!isReady ? (
<>
<RiLoader4Line className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
<Icon name="loader-4" className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
<span className="typography-meta text-muted-foreground">{isUnavailable ? t('common.unavailable') : t('common.loading')}</span>
</>
) : providerId ? (
@@ -517,7 +517,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
className="h-3.5 w-3.5"
/>
) : (
<RiPencilAiLine className="h-3 w-3 text-muted-foreground" />
<Icon name="pencil-ai" className="h-3 w-3 text-muted-foreground" />
)}
{isReady && (
<span className="typography-meta font-medium text-foreground">
@@ -525,7 +525,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
</span>
)}
</div>
<RiArrowDownSLine className="h-3 w-3 text-muted-foreground" />
<Icon name="arrow-down-s" className="h-3 w-3 text-muted-foreground" />
</button>
) : (
<DropdownMenu open={isReady && isDropdownOpen} onOpenChange={isReady ? setIsDropdownOpen : undefined}>
@@ -536,7 +536,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
)}>
{!isReady ? (
<>
<RiLoader4Line className="h-3.5 w-3.5 animate-spin text-muted-foreground flex-shrink-0" />
<Icon name="loader-4" className="h-3.5 w-3.5 animate-spin text-muted-foreground flex-shrink-0" />
<span className="typography-ui-label font-normal whitespace-nowrap text-muted-foreground">
{isUnavailable ? t('common.unavailable') : t('common.loading')}
</span>
@@ -549,17 +549,17 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
providerId={providerId}
className="h-3.5 w-3.5 flex-shrink-0"
/>
<RiPencilAiLine className="h-3 w-3 text-primary/60 hidden" />
<Icon name="pencil-ai" className="h-3 w-3 text-primary/60 hidden" />
</>
) : (
<RiPencilAiLine className="h-3.5 w-3.5 text-muted-foreground" />
<Icon name="pencil-ai" className="h-3.5 w-3.5 text-muted-foreground" />
)}
<span className="typography-ui-label font-normal whitespace-nowrap text-foreground">
{providerId && modelId ? `${providerId}/${modelId}` : (placeholder || t('settings.agents.modelSelector.notSelected'))}
</span>
</>
)}
<RiArrowDownSLine className="h-4 w-4 flex-shrink-0 text-muted-foreground/50" />
<Icon name="arrow-down-s" className="h-4 w-4 flex-shrink-0 text-muted-foreground/50" />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[min(380px,calc(100vw-2rem))] p-0 flex flex-col" align="start">
@@ -619,7 +619,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
{/* Search Input */}
<div className="p-2 border-b border-border/40">
<div className="relative">
<RiSearchLine className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Icon name="search" className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Input
type="text"
placeholder={t('settings.agents.modelSelector.searchPlaceholder')}
@@ -642,10 +642,10 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
)}
onSelect={() => handleProviderAndModelChange('', '')}
>
<RiCloseLine className="h-3.5 w-3.5 text-muted-foreground" />
<Icon name="close" className="h-3.5 w-3.5 text-muted-foreground" />
<span className="text-muted-foreground">{placeholder || t('settings.agents.modelSelector.notSelected')}</span>
{!providerId && !modelId && (
<RiCheckLine className="h-4 w-4 text-primary ml-auto" />
<Icon name="check" className="h-4 w-4 text-primary ml-auto" />
)}
</DropdownMenuItem>
@@ -661,7 +661,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
{filteredFavorites.length > 0 && (
<div>
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 border-b border-border/30">
<RiStarFill className="h-4 w-4 text-primary" />
<Icon name="star-fill" className="h-4 w-4 text-primary" />
{t('settings.agents.modelSelector.section.favorites')}
</DropdownMenuLabel>
{filteredFavorites.map(({ model, providerID, modelID }) => {
@@ -676,7 +676,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
<div>
{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 border-b border-border/30">
<RiTimeLine className="h-4 w-4" />
<Icon name="time" className="h-4 w-4" />
{t('settings.agents.modelSelector.section.recent')}
</DropdownMenuLabel>
{filteredRecents.map(({ model, providerID, modelID }) => {
@@ -13,7 +13,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { RiInformationLine } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import {
getResponseStylePresetInstructions,
isResponseStylePreset,
@@ -250,7 +250,7 @@ export const BehaviorPage: React.FC = () => {
</h3>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
<div className="space-y-1">
@@ -295,7 +295,7 @@ export const BehaviorPage: React.FC = () => {
</h3>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.behavior.page.responseStyle.tooltip')}
@@ -10,9 +10,9 @@ import { useAgentsStore, filterVisibleAgents } from '@/stores/useAgentsStore';
import { useConfigStore } from '@/stores/useConfigStore';
import { useUIStore } from '@/stores/useUIStore';
import { useDeviceInfo } from '@/lib/device';
import { RiArrowDownSLine, RiLoader4Line, RiRobot2Line } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { useOpenCodeReadiness } from '@/hooks/useOpenCodeReadiness';
@@ -138,26 +138,26 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
<div className="flex items-center gap-2">
{!isReady ? (
<>
<RiLoader4Line className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
<Icon name="loader-4" className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
<span className="typography-meta text-muted-foreground">{isUnavailable ? t('common.unavailable') : t('common.loading')}</span>
</>
) : (
<>
<RiRobot2Line className="h-3.5 w-3.5 text-muted-foreground" />
<Icon name="robot-2" className="h-3.5 w-3.5 text-muted-foreground" />
<span className="typography-meta font-medium text-foreground">
{agentName || t('settings.commands.agentSelector.selectAgentPlaceholder')}
</span>
</>
)}
</div>
<RiArrowDownSLine className="h-3 w-3 text-muted-foreground" />
<Icon name="arrow-down-s" className="h-3 w-3 text-muted-foreground" />
</button>
) : !isReady ? (
<div className={cn(
'flex items-center gap-2 px-2 rounded-lg bg-interactive-selection/20 border border-border/20 h-6 w-fit opacity-60',
className
)}>
<RiLoader4Line className="h-3 w-3 animate-spin text-muted-foreground flex-shrink-0" />
<Icon name="loader-4" className="h-3 w-3 animate-spin text-muted-foreground flex-shrink-0" />
<span className="typography-micro font-medium whitespace-nowrap text-muted-foreground">
{isUnavailable ? t('common.unavailable') : t('common.loading')}
</span>
@@ -169,11 +169,11 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
'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
)}>
<RiRobot2Line className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
<Icon name="robot-2" className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
<span className="typography-micro font-medium whitespace-nowrap">
{agentName || t('settings.commands.agentSelector.notSelected')}
</span>
<RiArrowDownSLine className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
<Icon name="arrow-down-s" className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="max-w-[300px]">
@@ -5,7 +5,6 @@ import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui';
import { useCommandsStore, type CommandConfig, type CommandScope } from '@/stores/useCommandsStore';
import { useShallow } from 'zustand/react/shallow';
import { RiTerminalBoxLine, RiUser3Line, RiFolderLine } from '@remixicon/react';
import { ModelSelector } from '../agents/ModelSelector';
import { AgentSelector } from './AgentSelector';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
@@ -16,6 +15,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { parseModelIdentifier } from '@/lib/modelIdentifier';
@@ -180,7 +180,7 @@ export const CommandsPage: React.FC = () => {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-muted-foreground">
<RiTerminalBoxLine className="mx-auto mb-3 h-12 w-12 opacity-50" />
<Icon name="terminal-box" className="mx-auto mb-3 h-12 w-12 opacity-50" />
<p className="typography-body">{t('settings.commands.page.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.commands.page.empty.description')}</p>
</div>
@@ -236,13 +236,13 @@ export const CommandsPage: React.FC = () => {
<SelectContent align="end">
<SelectItem value="user">
<div className="flex items-center gap-2">
<RiUser3Line className="h-3.5 w-3.5" />
<Icon name="user-3" className="h-3.5 w-3.5" />
<span>{t('settings.common.scope.global')}</span>
</div>
</SelectItem>
<SelectItem value="project">
<div className="flex items-center gap-2">
<RiFolderLine className="h-3.5 w-3.5" />
<Icon name="folder" className="h-3.5 w-3.5" />
<span>{t('settings.common.scope.project')}</span>
</div>
</SelectItem>
@@ -17,13 +17,13 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { RiAddLine, RiTerminalBoxLine, RiMore2Line, RiDeleteBinLine, RiFileCopyLine, RiRestartLine, RiEditLine } from '@remixicon/react';
import { useCommandsStore, isCommandBuiltIn, type Command } from '@/stores/useCommandsStore';
import { useSkillsStore } from '@/stores/useSkillsStore';
import { useShallow } from 'zustand/react/shallow';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { cn } from '@/lib/utils';
import { SettingsProjectSelector } from '@/components/sections/shared/SettingsProjectSelector';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface CommandsSidebarProps {
@@ -97,7 +97,6 @@ export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }
setSelectedCommand(newName);
onItemSelect?.();
};
const handleDeleteCommand = async (command: Command) => {
@@ -169,7 +168,6 @@ export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }
});
setSelectedCommand(newName);
};
const handleOpenRenameDialog = (command: Command) => {
@@ -237,7 +235,7 @@ export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }
className="h-7 w-7 px-0 -my-1 text-muted-foreground"
onClick={handleCreateNew}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -245,7 +243,7 @@ export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="space-y-1 px-3 py-2">
{commandOnlyItems.length === 0 ? (
<div className="py-12 px-4 text-center text-muted-foreground">
<RiTerminalBoxLine className="mx-auto mb-3 h-10 w-10 opacity-50" />
<Icon name="terminal-box" className="mx-auto mb-3 h-10 w-10 opacity-50" />
<p className="typography-ui-label font-medium">{t('settings.commands.sidebar.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.commands.sidebar.empty.description')}</p>
</div>
@@ -440,7 +438,7 @@ const CommandListItem: React.FC<CommandListItemProps> = ({
variant="ghost"
className="h-6 w-6 px-0 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
>
<RiMore2Line className="h-3.5 w-3.5" />
<Icon name="more-2" className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-fit min-w-20">
@@ -451,7 +449,7 @@ const CommandListItem: React.FC<CommandListItemProps> = ({
onRename();
}}
>
<RiEditLine className="h-4 w-4 mr-px" />
<Icon name="edit" className="h-4 w-4 mr-px" />
{t('settings.common.actions.rename')}
</DropdownMenuItem>
)}
@@ -462,7 +460,7 @@ const CommandListItem: React.FC<CommandListItemProps> = ({
onDuplicate();
}}
>
<RiFileCopyLine className="h-4 w-4 mr-px" />
<Icon name="file-copy" className="h-4 w-4 mr-px" />
{t('settings.common.actions.duplicate')}
</DropdownMenuItem>
@@ -473,7 +471,7 @@ const CommandListItem: React.FC<CommandListItemProps> = ({
onReset();
}}
>
<RiRestartLine className="h-4 w-4 mr-px" />
<Icon name="restart" className="h-4 w-4 mr-px" />
{t('settings.common.actions.reset')}
</DropdownMenuItem>
)}
@@ -486,7 +484,7 @@ const CommandListItem: React.FC<CommandListItemProps> = ({
}}
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="h-4 w-4 mr-px" />
<Icon name="delete-bin" className="h-4 w-4 mr-px" />
{t('settings.common.actions.delete')}
</DropdownMenuItem>
)}
@@ -11,18 +11,8 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
import { useGitIdentitiesStore, type GitIdentityProfile, type GitIdentityAuthType } from '@/stores/useGitIdentitiesStore';
import {
RiDeleteBinLine,
RiGitBranchLine,
RiBriefcaseLine,
RiHomeLine,
RiGraduationCapLine,
RiCodeLine,
RiInformationLine,
RiKeyLine,
RiLock2Line,
} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
@@ -35,11 +25,11 @@ const PROFILE_COLORS = [
];
const PROFILE_ICONS = [
{ key: 'branch', Icon: RiGitBranchLine, label: 'Branch' },
{ key: 'briefcase', Icon: RiBriefcaseLine, label: 'Work' },
{ key: 'house', Icon: RiHomeLine, label: 'Personal' },
{ key: 'graduation', Icon: RiGraduationCapLine, label: 'School' },
{ key: 'code', Icon: RiCodeLine, label: 'Code' },
{ key: 'branch', Icon: 'git-branch', label: 'Branch' },
{ key: 'briefcase', Icon: 'briefcase', label: 'Work' },
{ key: 'house', Icon: 'home', label: 'Personal' },
{ key: 'graduation', Icon: 'graduation-cap', label: 'School' },
{ key: 'code', Icon: 'code', label: 'Code' },
];
interface GitIdentityEditorDialogProps {
@@ -261,7 +251,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
<span className="typography-ui-label text-foreground">{t('settings.gitIdentities.editor.field.icon')}</span>
<div className="flex gap-1.5">
{PROFILE_ICONS.map((i) => {
const IconComponent = i.Icon;
const iconName = i.Icon;
return (
<button
key={i.key}
@@ -275,7 +265,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
)}
title={i.label}
>
<IconComponent
<Icon name={iconName}
className="w-3.5 h-3.5"
style={{ color: icon === i.key ? currentColorValue : 'var(--surface-muted-foreground)' }}
/>
@@ -298,7 +288,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
{!isGlobalProfile && <span className="text-[var(--status-error)] text-xs">*</span>}
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.gitIdentities.editor.field.userNameTooltip')}
@@ -322,7 +312,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
{!isGlobalProfile && <span className="text-[var(--status-error)] text-xs">*</span>}
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.gitIdentities.editor.field.emailAddressTooltip')}
@@ -356,7 +346,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
aria-pressed={authType === 'ssh'}
onClick={() => setAuthType('ssh')}
>
<RiLock2Line className="w-3.5 h-3.5 mr-1" /> SSH
<Icon name="lock-2" className="w-3.5 h-3.5 mr-1" /> SSH
</Button>
<Button size="sm"
type="button"
@@ -364,7 +354,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
aria-pressed={authType === 'token'}
onClick={() => setAuthType('token')}
>
<RiKeyLine className="w-3.5 h-3.5 mr-1" /> {t('settings.gitIdentities.editor.field.authToken')}
<Icon name="key" className="w-3.5 h-3.5 mr-1" /> {t('settings.gitIdentities.editor.field.authToken')}
</Button>
</div>
</div>
@@ -375,7 +365,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
<label className="typography-ui-label text-foreground">{t('settings.gitIdentities.editor.field.sshKeyPath')}</label>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.gitIdentities.editor.field.sshKeyPathTooltip')}
@@ -398,7 +388,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
<span className="text-[var(--status-error)] text-xs">*</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.gitIdentities.editor.field.hostTooltip')}
@@ -427,7 +417,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
onClick={() => setIsDeleteDialogOpen(true)}
className="text-[var(--status-error)] hover:text-[var(--status-error)] border-[var(--status-error)]/30 hover:bg-[var(--status-error)]/10 mr-auto"
>
<RiDeleteBinLine className="w-3.5 h-3.5 mr-1" /> {t('settings.common.actions.delete')}
<Icon name="delete-bin" className="w-3.5 h-3.5 mr-1" /> {t('settings.common.actions.delete')}
</Button>
)}
<Button variant="ghost" size="sm" onClick={() => onOpenChange(false)} className="text-foreground hover:bg-interactive-hover hover:text-foreground">
@@ -15,35 +15,23 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
RiAddLine,
RiGitBranchLine,
RiBriefcaseLine,
RiHomeLine,
RiGraduationCapLine,
RiCodeLine,
RiHeartLine,
RiMore2Line,
RiDeleteBinLine,
RiDownloadLine,
RiShieldKeyholeLine,
} from '@remixicon/react';
import { useGitIdentitiesStore, type GitIdentityProfile, type DiscoveredGitCredential } from '@/stores/useGitIdentitiesStore';
import { useShallow } from 'zustand/react/shallow';
import { GitSettings } from '@/components/sections/openchamber/GitSettings';
import { GitHubSettings } from '@/components/sections/openchamber/GitHubSettings';
import { GitIdentityEditorDialog } from './GitIdentityEditorDialog';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
const ICON_MAP: Record<string, React.ComponentType<{ className?: string; style?: React.CSSProperties }>> = {
branch: RiGitBranchLine,
briefcase: RiBriefcaseLine,
house: RiHomeLine,
graduation: RiGraduationCapLine,
code: RiCodeLine,
heart: RiHeartLine,
const ICON_MAP: Record<string, string> = {
branch: 'git-branch',
briefcase: 'briefcase',
house: 'home',
graduation: 'graduation-cap',
code: 'code',
heart: 'heart',
};
const COLOR_MAP: Record<string, string> = {
@@ -137,7 +125,7 @@ export const GitPage: React.FC = () => {
<h3 className="typography-ui-header font-semibold text-foreground">{t('settings.gitIdentities.page.section.title')}</h3>
</div>
<Button size="sm" variant="outline" onClick={() => openEditor('new')}>
<RiAddLine className="w-3.5 h-3.5 mr-1" /> {t('settings.common.badge.new')}
<Icon name="add" className="w-3.5 h-3.5 mr-1" /> {t('settings.common.badge.new')}
</Button>
</div>
@@ -170,7 +158,7 @@ export const GitPage: React.FC = () => {
{/* Empty state */}
{!globalIdentity && profiles.length === 0 && unimportedCredentials.length === 0 && (
<div className="py-8 px-4 text-center text-muted-foreground">
<RiShieldKeyholeLine className="mx-auto mb-2 h-8 w-8 opacity-40" />
<Icon name="shield-keyhole" className="mx-auto mb-2 h-8 w-8 opacity-40" />
<p className="typography-ui-label">{t('settings.gitIdentities.page.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.gitIdentities.page.empty.description')}</p>
</div>
@@ -257,7 +245,7 @@ const IdentityRow: React.FC<IdentityRowProps> = ({
hasBorder,
}) => {
const { t } = useI18n();
const IconComponent = ICON_MAP[profile.icon || 'branch'] || RiGitBranchLine;
const iconName = ICON_MAP[profile.icon || 'branch'] || 'git-branch';
const iconColor = COLOR_MAP[profile.color || ''];
const authType = profile.authType || 'ssh';
@@ -273,7 +261,7 @@ const IdentityRow: React.FC<IdentityRowProps> = ({
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onEdit(); }}
>
<div className="flex items-center gap-3 min-w-0">
<IconComponent className="w-4 h-4 shrink-0" style={{ color: iconColor }} />
<Icon name={iconName} className="w-4 h-4 shrink-0" style={{ color: iconColor }} />
<div className="min-w-0">
<div className="flex items-center gap-1.5">
<span className="typography-ui-label text-foreground truncate">{profile.name}</span>
@@ -305,7 +293,7 @@ const IdentityRow: React.FC<IdentityRowProps> = ({
className="h-6 w-6 shrink-0 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
onClick={(e) => e.stopPropagation()}
>
<RiMore2Line className="h-3.5 w-3.5" />
<Icon name="more-2" className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-fit min-w-28">
@@ -317,7 +305,7 @@ const IdentityRow: React.FC<IdentityRowProps> = ({
onClick={(e) => { e.stopPropagation(); onDelete(); }}
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="h-4 w-4 mr-px" />
<Icon name="delete-bin" className="h-4 w-4 mr-px" />
{t('settings.common.actions.delete')}
</DropdownMenuItem>
)}
@@ -355,7 +343,7 @@ const DiscoveredRow: React.FC<DiscoveredRowProps> = ({ credential, onImport, has
</span>
</div>
<Button size="sm" variant="ghost" onClick={onImport} className="gap-1 shrink-0">
<RiDownloadLine className="h-3 w-3" />
<Icon name="download" className="h-3 w-3" />
{t('settings.gitIdentities.page.actions.import')}
</Button>
</div>
@@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { RiInformationLine } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import {
fetchMagicPromptOverrides,
getDefaultMagicPromptTemplate,
@@ -269,7 +269,7 @@ export const MagicPromptsPage: React.FC = () => {
<h2 className="typography-ui-header font-semibold text-foreground">{tUnsafe(pageConfig.titleKey)}</h2>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{tUnsafe(pageConfig.descriptionKey)}
@@ -306,7 +306,7 @@ export const MagicPromptsPage: React.FC = () => {
<h3 className="typography-ui-label text-foreground">{tUnsafe(block.titleKey)}</h3>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{definition.description}
@@ -20,20 +20,6 @@ import {
} from './mcpImport';
import { useMcpStore } from '@/stores/useMcpStore';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
import {
RiAddLine,
RiArrowDownSLine,
RiArrowRightSLine,
RiClipboardLine,
RiDeleteBinLine,
RiEyeLine,
RiEyeOffLine,
RiExternalLinkLine,
RiFileCodeLine,
RiFolderLine,
RiPlugLine,
RiUser3Line,
} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { MCP_OAUTH_CALLBACK_PATH, parseMcpOAuthCallbackContext, parseMcpOAuthCallbackStateKey } from '@/components/sections/mcp/mcpOAuth';
@@ -52,6 +38,7 @@ import {
SelectItem,
SelectTrigger,
} from '@/components/ui/select';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
// ─────────────────────────────────────────────────────────────
@@ -175,7 +162,7 @@ const CommandTextarea: React.FC<CommandTextareaProps> = ({
type="button"
title={pasteCommandTitle}
>
<RiClipboardLine className="h-3 w-3" />
<Icon name="clipboard" className="h-3 w-3" />
{pasteCommandLabel}
</Button>
</div>
@@ -354,7 +341,7 @@ const EnvEditor: React.FC<EnvEditorProps> = ({
type="button"
title={pasteTitle}
>
<RiClipboardLine className="h-3 w-3" />
<Icon name="clipboard" className="h-3 w-3" />
{pasteLabel}
</Button>
</div>
@@ -395,8 +382,8 @@ const EnvEditor: React.FC<EnvEditorProps> = ({
title={revealedKeys.has(idx) ? hideValueTitle : showValueTitle}
>
{revealedKeys.has(idx)
? <RiEyeOffLine className="h-3.5 w-3.5" />
: <RiEyeLine className="h-3.5 w-3.5" />}
? <Icon name="eye-off" className="h-3.5 w-3.5" />
: <Icon name="eye" className="h-3.5 w-3.5" />}
</button>
</div>
{/* Remove */}
@@ -406,7 +393,7 @@ const EnvEditor: React.FC<EnvEditorProps> = ({
onClick={() => removeRow(idx)}
aria-label={removeVariableAria}
>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
</Button>
</div>
))}
@@ -419,7 +406,7 @@ const EnvEditor: React.FC<EnvEditorProps> = ({
onClick={addRow}
type="button"
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
{addVariable}
</Button>
@@ -1299,7 +1286,7 @@ export const McpPage: React.FC = () => {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-muted-foreground">
<RiPlugLine className="mx-auto mb-3 h-12 w-12 opacity-50" />
<Icon name="plug" className="mx-auto mb-3 h-12 w-12 opacity-50" />
<p className="typography-body">{t('settings.mcp.page.empty.selectServer')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.mcp.page.empty.addNewOne')}</p>
</div>
@@ -1447,11 +1434,11 @@ export const McpPage: React.FC = () => {
<div className="break-all typography-micro text-foreground font-mono">{authUrl}</div>
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline" size="xs" className="!font-normal" onClick={() => void openExternalUrl(authUrl)}>
<RiExternalLinkLine className="h-3.5 w-3.5" />
<Icon name="external-link" className="h-3.5 w-3.5" />
{t('settings.mcp.page.actions.openInBrowser')}
</Button>
<Button variant="outline" size="xs" className="!font-normal" onClick={() => void handleCopyAuthUrl()}>
<RiClipboardLine className="h-3.5 w-3.5" />
<Icon name="clipboard" className="h-3.5 w-3.5" />
{t('settings.mcp.page.actions.copyLink')}
</Button>
</div>
@@ -1526,18 +1513,18 @@ export const McpPage: React.FC = () => {
/>
<Select value={draftScope} onValueChange={(value) => setDraftScope(value as McpScope)}>
<SelectTrigger className="!h-7 !w-7 !min-w-0 !px-0 !py-0 justify-center [&>svg:last-child]:hidden" title={draftScope === 'user' ? t('settings.common.scope.global') : t('settings.common.scope.project')}>
{draftScope === 'user' ? <RiUser3Line className="h-3.5 w-3.5" /> : <RiFolderLine className="h-3.5 w-3.5" />}
{draftScope === 'user' ? <Icon name="user-3" className="h-3.5 w-3.5" /> : <Icon name="folder" className="h-3.5 w-3.5" />}
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="user">
<div className="flex items-center gap-2">
<RiUser3Line className="h-3.5 w-3.5" />
<Icon name="user-3" className="h-3.5 w-3.5" />
<span>{t('settings.common.scope.global')}</span>
</div>
</SelectItem>
<SelectItem value="project">
<div className="flex items-center gap-2">
<RiFolderLine className="h-3.5 w-3.5" />
<Icon name="folder" className="h-3.5 w-3.5" />
<span>{t('settings.common.scope.project')}</span>
</div>
</SelectItem>
@@ -1558,7 +1545,7 @@ export const McpPage: React.FC = () => {
type="button"
title={t('settings.mcp.page.server.importJsonTitle')}
>
<RiFileCodeLine className="h-3.5 w-3.5" />
<Icon name="file-code" className="h-3.5 w-3.5" />
{t('settings.mcp.page.server.importJson')}
</Button>
</div>
@@ -1663,9 +1650,9 @@ export const McpPage: React.FC = () => {
</span>
</div>
{isAdvancedRemoteOptionsOpen ? (
<RiArrowDownSLine className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
) : (
<RiArrowRightSLine className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
)}
</CollapsibleTrigger>
<CollapsibleContent className="pt-2">
@@ -1818,7 +1805,7 @@ export const McpPage: React.FC = () => {
className="!font-normal gap-1.5"
onClick={() => setEnvEntries([{ key: '', value: '' }])}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
{t('settings.mcp.page.env.addEnvironmentVariable')}
</Button>
) : (
@@ -1913,7 +1900,7 @@ export const McpPage: React.FC = () => {
onClick={handlePasteImportClipboard}
type="button"
>
<RiClipboardLine className="h-3.5 w-3.5" />
<Icon name="clipboard" className="h-3.5 w-3.5" />
{t('settings.mcp.page.importDialog.pasteFromClipboard')}
</Button>
</div>
@@ -8,7 +8,6 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { RiAddLine, RiDeleteBinLine, RiMore2Line, RiPlugLine, RiRefreshLine, RiServerLine, RiGlobalLine } from '@remixicon/react';
import { useMcpConfigStore, type McpDraft, type McpServerConfig } from '@/stores/useMcpConfigStore';
import { useShallow } from 'zustand/react/shallow';
import { useMcpStore } from '@/stores/useMcpStore';
@@ -178,7 +177,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
aria-label={t('settings.mcp.sidebar.actions.refreshStatusAria')}
title={t('settings.mcp.sidebar.actions.refreshStatusTitle')}
>
<RiRefreshLine className={cn('h-4 w-4', isRefreshingStatus && 'animate-spin')} />
<Icon name="refresh" className={cn('h-4 w-4', isRefreshingStatus && 'animate-spin')} />
</button>
</div>
<SettingsProjectSelector className="mb-3" />
@@ -192,7 +191,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
onClick={handleCreateNew}
title={t('settings.mcp.sidebar.actions.addServerTitle')}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -201,7 +200,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="space-y-1 px-3 py-2 overflow-x-hidden">
{mcpServers.length === 0 ? (
<div className="py-12 px-4 text-center text-muted-foreground">
<RiPlugLine className="mx-auto mb-3 h-10 w-10 opacity-50" />
<Icon name="plug" className="mx-auto mb-3 h-10 w-10 opacity-50" />
<p className="typography-ui-label font-medium">{t('settings.mcp.sidebar.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.mcp.sidebar.empty.description')}</p>
</div>
@@ -246,9 +245,9 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
: t('settings.mcp.sidebar.serverType.remoteTitle')}
>
{server.type === 'local' ? (
<RiServerLine className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
<Icon name="server" className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
) : (
<RiGlobalLine className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
<Icon name="global" className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
)}
</span>
</div>
@@ -262,7 +261,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
<DropdownMenu open={openMenuMcp === server.name} onOpenChange={(open) => setOpenMenuMcp(open ? server.name : null)}>
<DropdownMenuTrigger asChild>
<Button size="xs" variant="ghost" className="flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100">
<RiMore2Line className="h-3.5 w-3.5" />
<Icon name="more-2" className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-fit min-w-20">
@@ -273,7 +272,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
}}
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="h-4 w-4 mr-px" />
<Icon name="delete-bin" className="h-4 w-4 mr-px" />
{t('settings.common.actions.delete')}
</DropdownMenuItem>
</DropdownMenuContent>
@@ -323,9 +322,9 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
: t('settings.mcp.sidebar.serverType.remoteTitle')}
>
{server.type === 'local' ? (
<RiServerLine className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
<Icon name="server" className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
) : (
<RiGlobalLine className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
<Icon name="global" className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
)}
</span>
</div>
@@ -339,7 +338,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
<DropdownMenu open={openMenuMcp === server.name} onOpenChange={(open) => setOpenMenuMcp(open ? server.name : null)}>
<DropdownMenuTrigger asChild>
<Button size="xs" variant="ghost" className="flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100">
<RiMore2Line className="h-3.5 w-3.5" />
<Icon name="more-2" className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-fit min-w-20">
@@ -350,7 +349,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
}}
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="h-4 w-4 mr-px" />
<Icon name="delete-bin" className="h-4 w-4 mr-px" />
{t('settings.common.actions.delete')}
</DropdownMenuItem>
</DropdownMenuContent>
@@ -398,3 +397,4 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
// Re-export for easy sidebar icon usage
export { McpIcon } from '@/components/icons/McpIcon';
import { Icon } from "@/components/icon/Icon";
@@ -1,5 +1,4 @@
import React from 'react';
import { RiDiscordFill, RiDownloadLine, RiGithubFill, RiLoaderLine, RiTwitterXFill } from '@remixicon/react';
import { useUpdateStore } from '@/stores/useUpdateStore';
import { useShallow } from 'zustand/react/shallow';
import { UpdateDialog } from '@/components/ui/UpdateDialog';
@@ -7,6 +6,7 @@ import { useDeviceInfo } from '@/lib/device';
import { toast } from '@/components/ui';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
const GITHUB_URL = 'https://github.com/btriapitsyn/openchamber';
@@ -85,7 +85,7 @@ export const AboutSettings: React.FC = () => {
onClick={() => setUpdateDialogOpen(true)}
className="flex items-center gap-1 typography-meta text-[var(--primary-base)] hover:underline"
>
<RiDownloadLine className="h-3.5 w-3.5" />
<Icon name="download" className="h-3.5 w-3.5" />
{t('settings.openchamber.about.actions.update')}
</button>
)}
@@ -103,7 +103,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
<RiGithubFill className="h-3.5 w-3.5" />
<Icon name="github-fill" className="h-3.5 w-3.5" />
<span>GitHub</span>
</a>
@@ -113,7 +113,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
<RiDiscordFill className="h-3.5 w-3.5" />
<Icon name="discord-fill" className="h-3.5 w-3.5" />
<span>Discord</span>
</a>
@@ -123,7 +123,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
<RiTwitterXFill className="h-3.5 w-3.5" />
<Icon name="twitter-xfill" className="h-3.5 w-3.5" />
<span>@btriapitsyn</span>
</a>
</div>
@@ -144,7 +144,6 @@ export const AboutSettings: React.FC = () => {
);
}
// Desktop layout (redesigned)
return (
<div className="mb-8">
@@ -164,7 +163,7 @@ export const AboutSettings: React.FC = () => {
<div className="flex items-center gap-3">
{updateStore.checking && (
<div className="flex items-center gap-2 text-muted-foreground">
<RiLoaderLine className="h-4 w-4 animate-spin" />
<Icon name="loader" className="h-4 w-4 animate-spin" />
<span className="typography-meta">{t('settings.openchamber.about.state.checking')}</span>
</div>
)}
@@ -174,7 +173,7 @@ export const AboutSettings: React.FC = () => {
variant="default"
onClick={() => setUpdateDialogOpen(true)}
>
<RiDownloadLine className="h-4 w-4 mr-1" />
<Icon name="download" className="h-4 w-4 mr-1" />
{t('settings.openchamber.about.actions.updateToVersion', { version: updateStore.info?.version || '' })}
</Button>
)}
@@ -206,7 +205,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground typography-meta transition-colors"
>
<RiGithubFill className="h-4 w-4" />
<Icon name="github-fill" className="h-4 w-4" />
<span>GitHub</span>
</a>
@@ -216,7 +215,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground typography-meta transition-colors"
>
<RiTwitterXFill className="h-4 w-4" />
<Icon name="twitter-xfill" className="h-4 w-4" />
<span>@btriapitsyn</span>
</a>
</div>
@@ -8,8 +8,8 @@ import { useDeviceInfo } from '@/lib/device';
import { cn } from '@/lib/utils';
import { openExternalUrl } from '@/lib/url';
import { useI18n } from '@/lib/i18n';
import { RiGithubFill, RiInformationLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
type GitHubUser = {
login: string;
@@ -246,7 +246,7 @@ export const GitHubSettings: React.FC = () => {
<h3 className="typography-ui-header font-semibold text-foreground">GitHub</h3>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.github.page.tooltip.connectAccount')}
@@ -276,7 +276,7 @@ export const GitHubSettings: React.FC = () => {
{user?.name?.trim() || user?.login || 'GitHub'}
</div>
<div className={cn("flex items-center gap-2 typography-meta text-muted-foreground mt-0.5", isMobile ? "flex-wrap" : "truncate")}>
<RiGithubFill className="h-3.5 w-3.5 shrink-0" />
<Icon name="github-fill" className="h-3.5 w-3.5 shrink-0" />
<span className="font-mono">{user?.login || t('settings.github.page.label.unknownUser')}</span>
{user?.email && <span className="opacity-50"></span>}
{user?.email && <span>{user.email}</span>}
@@ -329,7 +329,7 @@ export const GitHubSettings: React.FC = () => {
/>
) : (
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[var(--interactive-border)] bg-[var(--surface-muted)]">
<RiGithubFill className="h-3 w-3 text-muted-foreground" />
<Icon name="github-fill" className="h-3 w-3 text-muted-foreground" />
</div>
)}
<div className="min-w-0 flex flex-col">
@@ -2,7 +2,7 @@ import React from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { RiInformationLine } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { useUIStore } from '@/stores/useUIStore';
import { cn } from '@/lib/utils';
import {
@@ -155,7 +155,7 @@ export const KeyboardShortcutsSettings: React.FC = () => {
</Button>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.keyboardShortcuts.tooltip')}
@@ -1,5 +1,4 @@
import React from 'react';
import { RiInformationLine, RiRestartLine } from '@remixicon/react';
import { useUIStore } from '@/stores/useUIStore';
import { useConfigStore } from '@/stores/useConfigStore';
import { isDesktopShell, isVSCodeRuntime } from '@/lib/desktop';
@@ -13,6 +12,7 @@ import { NumberInput } from '@/components/ui/number-input';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
@@ -806,7 +806,7 @@ export const NotificationSettings: React.FC = () => {
<span className="typography-ui-label text-foreground">{t('settings.notifications.page.summary.modelLabel')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.notifications.page.summary.modelTooltip')}
@@ -859,7 +859,7 @@ export const NotificationSettings: React.FC = () => {
aria-label={t('settings.notifications.page.summary.resetThresholdAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -886,7 +886,7 @@ export const NotificationSettings: React.FC = () => {
aria-label={t('settings.notifications.page.summary.resetLengthAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -915,7 +915,7 @@ export const NotificationSettings: React.FC = () => {
aria-label={t('settings.notifications.page.summary.resetMaxLengthAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1,5 +1,4 @@
import React from 'react';
import { RiRestartLine, RiInformationLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useThemeSystem } from '@/contexts/useThemeSystem';
@@ -19,6 +18,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Icon } from "@/components/icon/Icon";
import { isDesktopShell, isVSCodeRuntime, isWebRuntime } from '@/lib/desktop';
import { useDeviceInfo } from '@/lib/device';
import { usePwaDetection } from '@/hooks/usePwaDetection';
@@ -724,7 +724,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
className="flex items-center justify-center rounded-md p-1 text-muted-foreground/70 hover:text-foreground"
aria-label={t('settings.openchamber.visual.field.themeImportInfoAria')}
>
<RiInformationLine className="h-3.5 w-3.5" />
<Icon name="information" className="h-3.5 w-3.5" />
</button>
</TooltipTrigger>
<TooltipContent sideOffset={8}>
@@ -835,7 +835,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetInstallAppNameAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -881,7 +881,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetInstallOrientationAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -927,7 +927,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetMobileKeyboardModeAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -971,7 +971,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetInterfaceFontAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1004,7 +1004,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetCodeFontAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1034,7 +1034,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetFontSizeAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1063,7 +1063,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetTerminalFontSizeAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1092,7 +1092,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetSpacingAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1105,7 +1105,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
<span className="typography-ui-label text-foreground">{t('settings.openchamber.visual.field.inputBarOffset')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.visual.field.inputBarOffsetTooltip')}
@@ -1131,7 +1131,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
aria-label={t('settings.openchamber.visual.actions.resetInputBarOffsetAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1171,7 +1171,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
<span className="typography-ui-label text-foreground">{t('settings.openchamber.visual.field.terminalQuickKeys')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.visual.field.terminalQuickKeysTooltip')}
@@ -1187,8 +1187,6 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
{hasBehaviorSettings && (
<div className="space-y-3">
{(shouldShow('userMessageRendering') || shouldShow('mermaidRendering') || shouldShow('chatRenderMode') || shouldShow('messageTransport') || (shouldShow('activityRenderMode') && chatRenderMode === 'sorted') || (shouldShow('diffLayout') && !isVSCode)) && (
<div className="grid grid-cols-1 gap-y-2 md:grid-cols-[minmax(0,16rem)_minmax(0,16rem)] md:justify-start md:gap-x-2">
{shouldShow('chatRenderMode') && (
@@ -1627,7 +1625,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
<span className="typography-ui-label text-foreground">{t('settings.openchamber.visual.field.showSplitAssistantMessageActions')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 cursor-help text-muted-foreground/60" />
<Icon name="information" className="h-3.5 w-3.5 cursor-help text-muted-foreground/60" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip')}
@@ -1729,7 +1727,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
<span className="typography-ui-label text-foreground">{t('settings.openchamber.visual.field.queueMessagesByDefault')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.visual.field.queueMessagesByDefaultTooltip', { modifier: getModifierLabel() })}
@@ -2,7 +2,7 @@ import * as React from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { RiFolderLine, RiInformationLine } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { isDesktopShell, isTauriShell } from '@/lib/desktop';
import { updateDesktopSettings } from '@/lib/persistence';
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
@@ -95,7 +95,7 @@ export const OpenCodeCliSettings: React.FC = () => {
</h3>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.opencodeCli.tooltipPrefix')}
@@ -130,7 +130,7 @@ export const OpenCodeCliSettings: React.FC = () => {
aria-label={t('settings.openchamber.opencodeCli.actions.browseAria')}
title={t('settings.openchamber.opencodeCli.actions.browse')}
>
<RiFolderLine className="h-4 w-4" />
<Icon name="folder" className="h-4 w-4" />
</Button>
</div>
</div>
@@ -1,10 +1,10 @@
import React from 'react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { RiInformationLine, RiRestartLine } from '@remixicon/react';
import { toast } from '@/components/ui';
import { NumberInput } from '@/components/ui/number-input';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Icon } from "@/components/icon/Icon";
import { useUIStore } from '@/stores/useUIStore';
import { useSessionAutoCleanup } from '@/hooks/useSessionAutoCleanup';
import { useI18n } from '@/lib/i18n';
@@ -65,7 +65,7 @@ export const SessionRetentionSettings: React.FC = () => {
</h3>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.sessionRetention.tooltip')}
@@ -120,7 +120,7 @@ export const SessionRetentionSettings: React.FC = () => {
aria-label={t('settings.openchamber.sessionRetention.actions.resetRetentionAria')}
title={t('settings.common.actions.reset')}
>
<RiRestartLine className="h-3.5 w-3.5" />
<Icon name="restart" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1,28 +1,12 @@
import React from 'react';
import QRCode from 'qrcode';
import {
RiAddLine,
RiArrowDownSLine,
RiArrowRightSLine,
RiCheckboxBlankCircleFill,
RiCheckLine,
RiCloseLine,
RiCloudLine,
RiDeleteBinLine,
RiErrorWarningLine,
RiExternalLinkLine,
RiFileCopyLine,
RiFolderLine,
RiInformationLine,
RiLoader4Line,
RiRestartLine,
} from '@remixicon/react';
import { toast } from '@/components/ui';
import { Button } from '@/components/ui/button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
import { requestFileAccess } from '@/lib/desktop';
import { updateDesktopSettings } from '@/lib/persistence';
import { useI18n } from '@/lib/i18n';
@@ -174,7 +158,7 @@ const ProviderOptionLabel: React.FC<{ provider: string }> = ({ provider }) => {
return (
<span className="flex items-center gap-2">
<RiCloudLine className={cn('size-4 shrink-0', isCloudflare ? 'text-[var(--status-warning)]' : 'text-muted-foreground')} />
<Icon name="cloud" className={cn('size-4 shrink-0', isCloudflare ? 'text-[var(--status-warning)]' : 'text-muted-foreground')} />
<span>{label}</span>
</span>
);
@@ -1109,7 +1093,7 @@ export const TunnelSettings: React.FC = () => {
<section className="space-y-2 px-2 pb-2 pt-0">
<div className="rounded-lg border border-[var(--status-info-border)] bg-[var(--status-info-background)]/30 p-3">
<div className="mb-2 flex items-center gap-2">
<RiInformationLine className="size-4 text-[var(--status-info)]" />
<Icon name="information" className="size-4 text-[var(--status-info)]" />
<p className="typography-ui-label text-foreground">{t('settings.openchamber.tunnel.section.redeemedAccessLinks')}</p>
</div>
<div className="space-y-1">
@@ -1135,7 +1119,7 @@ export const TunnelSettings: React.FC = () => {
key={record.sessionId}
className="flex flex-wrap items-center gap-x-2 gap-y-1 rounded border border-[var(--surface-subtle)] bg-[var(--surface-elevated)] px-2 py-1.5"
>
<RiCheckboxBlankCircleFill className={cn('size-2.5 shrink-0', statusDotClass)} />
<Icon name="checkbox-blank-circle-fill" className={cn('size-2.5 shrink-0', statusDotClass)} />
<span className={cn('typography-micro rounded border px-1.5 py-0.5 uppercase', modeBadgeClass)}>
{modeLabel}
</span>
@@ -1160,7 +1144,7 @@ export const TunnelSettings: React.FC = () => {
{state === 'not-available' && (
<section className="space-y-2 px-2 pb-2 pt-0">
<div className="flex items-start gap-2 rounded-lg border border-[var(--status-warning)]/30 bg-[var(--status-warning)]/5 p-3">
<RiErrorWarningLine className="mt-0.5 size-4 shrink-0 text-[var(--status-warning)]" />
<Icon name="error-warning" className="mt-0.5 size-4 shrink-0 text-[var(--status-warning)]" />
<div className="space-y-1">
<p className="typography-meta font-medium text-foreground">{t('settings.openchamber.tunnel.notAvailable.cloudflaredNotFound')}</p>
<p className="typography-meta text-muted-foreground/70">{t('settings.openchamber.tunnel.notAvailable.installHint')}</p>
@@ -1279,7 +1263,7 @@ export const TunnelSettings: React.FC = () => {
{tunnelMode === 'quick' && (
<div className="rounded-lg border border-[var(--status-warning)]/35 bg-[var(--status-warning)]/10 p-3">
<div className="flex items-start gap-2">
<RiErrorWarningLine className="mt-0.5 size-4 shrink-0 text-[var(--status-warning)]" />
<Icon name="error-warning" className="mt-0.5 size-4 shrink-0 text-[var(--status-warning)]" />
<div>
<p className="typography-meta text-[var(--status-warning)]">
{t('settings.openchamber.tunnel.option.mode.quick.tooltip')}
@@ -1311,7 +1295,7 @@ export const TunnelSettings: React.FC = () => {
onClick={() => setIsAddingPreset((prev) => !prev)}
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
{t('settings.common.actions.create')}
</Button>
</div>
@@ -1345,8 +1329,8 @@ export const TunnelSettings: React.FC = () => {
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
{isOpen
? <RiArrowDownSLine className="h-4 w-4 text-muted-foreground" />
: <RiArrowRightSLine className="h-4 w-4 text-muted-foreground" />}
? <Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground" />
: <Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground" />}
<span className="typography-ui-label min-w-0 flex-1 truncate text-foreground">{preset.name}</span>
</CollapsibleTrigger>
@@ -1360,7 +1344,7 @@ export const TunnelSettings: React.FC = () => {
}}
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
</Button>
</div>
@@ -1488,7 +1472,7 @@ export const TunnelSettings: React.FC = () => {
className="rounded p-0.5 text-muted-foreground/70 hover:text-foreground"
aria-label={t('settings.openchamber.tunnel.field.managedRemoteTokenInfoAria')}
>
<RiInformationLine className="h-3.5 w-3.5" />
<Icon name="information" className="h-3.5 w-3.5" />
</button>
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
@@ -1539,7 +1523,7 @@ export const TunnelSettings: React.FC = () => {
}}
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
<RiFolderLine className="size-3.5" />
<Icon name="folder" className="size-3.5" />
</Button>
{managedLocalConfigPath && (
<Button
@@ -1552,7 +1536,7 @@ export const TunnelSettings: React.FC = () => {
}}
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
<RiCloseLine className="size-3.5" />
<Icon name="close" className="size-3.5" />
</Button>
)}
</div>
@@ -1572,7 +1556,7 @@ export const TunnelSettings: React.FC = () => {
<div className="space-y-6">
<div className="rounded-lg border border-[var(--status-info-border)] bg-[var(--status-info-background)] p-3">
<div className="flex items-start gap-2">
<RiInformationLine className="mt-0.5 size-4 shrink-0 text-[var(--status-info)]" />
<Icon name="information" className="mt-0.5 size-4 shrink-0 text-[var(--status-info)]" />
<div className="space-y-1">
{tunnelMode === 'managed-remote' && (
<>
@@ -1587,7 +1571,7 @@ export const TunnelSettings: React.FC = () => {
}}
>
{t('settings.openchamber.tunnel.actions.openManagedRemoteDocs')}
<RiExternalLinkLine className="size-3.5" />
<Icon name="external-link" className="size-3.5" />
</button>
</>
)}
@@ -1604,7 +1588,7 @@ export const TunnelSettings: React.FC = () => {
}}
>
{t('settings.openchamber.tunnel.actions.openManagedLocalDocs')}
<RiExternalLinkLine className="size-3.5" />
<Icon name="external-link" className="size-3.5" />
</button>
</>
)}
@@ -1649,7 +1633,7 @@ export const TunnelSettings: React.FC = () => {
{willReplaceActiveTunnel && (
<div className="rounded-lg border border-[var(--status-warning-border)] bg-[var(--status-warning-background)] p-3">
<div className="flex items-start gap-2">
<RiErrorWarningLine className="mt-0.5 size-4 shrink-0 text-[var(--status-warning)]" />
<Icon name="error-warning" className="mt-0.5 size-4 shrink-0 text-[var(--status-warning)]" />
<p className="typography-meta text-[var(--status-warning)]">
{t('settings.openchamber.tunnel.warning.replacesActiveTunnel')}
</p>
@@ -1669,7 +1653,7 @@ export const TunnelSettings: React.FC = () => {
className={cn(primaryCtaClass, state === 'starting' && 'opacity-70')}
>
{state === 'starting'
? <><RiLoader4Line className="size-3.5 animate-spin" /> {t('settings.openchamber.tunnel.actions.startingTunnel')}</>
? <><Icon name="loader-4" className="size-3.5 animate-spin" /> {t('settings.openchamber.tunnel.actions.startingTunnel')}</>
: t('settings.openchamber.tunnel.actions.startTunnel')}
</Button>
</div>
@@ -1703,8 +1687,8 @@ export const TunnelSettings: React.FC = () => {
</code>
<Button size="sm" variant="ghost" onClick={handleCopyUrl} className="shrink-0 gap-1.5">
{copied
? <RiCheckLine className="size-3.5 text-[var(--status-success)]" />
: <RiFileCopyLine className="size-3.5" />}
? <Icon name="check" className="size-3.5 text-[var(--status-success)]" />
: <Icon name="file-copy" className="size-3.5" />}
{copied ? t('settings.openchamber.tunnel.actions.copied') : t('settings.common.actions.copyAll')}
</Button>
</div>
@@ -1731,7 +1715,7 @@ export const TunnelSettings: React.FC = () => {
disabled={state === 'stopping' || isSavingMode || (tunnelMode === 'managed-local' && isManagedLocalConfigPathInvalid)}
className={primaryCtaClass}
>
<RiRestartLine className="size-3.5" />
<Icon name="restart" className="size-3.5" />
{t('settings.openchamber.tunnel.actions.newConnectLink')}
</Button>
@@ -1742,7 +1726,7 @@ export const TunnelSettings: React.FC = () => {
className="gap-2 text-[var(--status-error)]"
>
{state === 'stopping'
? <><RiLoader4Line className="size-3.5 animate-spin" /> {t('settings.openchamber.tunnel.actions.stopping')}</>
? <><Icon name="loader-4" className="size-3.5 animate-spin" /> {t('settings.openchamber.tunnel.actions.stopping')}</>
: t('settings.openchamber.tunnel.actions.stopTunnel')}
</Button>
</div>
@@ -13,8 +13,8 @@ import {
import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
import { NumberInput } from '@/components/ui/number-input';
import { RiPlayLine, RiStopLine, RiCloseLine, RiAppleLine, RiInformationLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
import { browserVoiceService } from '@/lib/voice/browserVoiceService';
import { audioStreamService } from '@/lib/voice/audioStreamService';
import { cn } from '@/lib/utils';
@@ -445,7 +445,7 @@ export const VoiceSettings: React.FC = () => {
<span className="typography-ui-label text-foreground">{t('settings.voice.page.field.provider')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
<ul className="space-y-1">
@@ -493,7 +493,7 @@ export const VoiceSettings: React.FC = () => {
onClick={() => setVoiceProvider('say')}
className="!font-normal"
>
<RiAppleLine className="w-3.5 h-3.5 mr-0.5" />
<Icon name="apple" className="w-3.5 h-3.5 mr-0.5" />
{t('settings.voice.page.provider.say')}
</Button>
)}
@@ -528,7 +528,7 @@ export const VoiceSettings: React.FC = () => {
onClick={() => setOpenaiApiKey('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<RiCloseLine className="w-3.5 h-3.5" />
<Icon name="close" className="w-3.5 h-3.5" />
</button>
)}
</div>
@@ -559,7 +559,7 @@ export const VoiceSettings: React.FC = () => {
onClick={() => setOpenaiCompatibleUrl('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<RiCloseLine className="w-3.5 h-3.5" />
<Icon name="close" className="w-3.5 h-3.5" />
</button>
)}
</div>
@@ -592,7 +592,7 @@ export const VoiceSettings: React.FC = () => {
/>
</div>
<Button size="xs" variant="ghost" onClick={previewCompatibleVoice} title={t('settings.voice.page.actions.preview')} disabled={!openaiCompatibleUrl.trim()}>
{isCompatiblePreviewPlaying ? <RiStopLine className="w-3.5 h-3.5" /> : <RiPlayLine className="w-3.5 h-3.5" />}
{isCompatiblePreviewPlaying ? <Icon name="stop" className="w-3.5 h-3.5" /> : <Icon name="play" className="w-3.5 h-3.5" />}
</Button>
</div>
</div>
@@ -616,7 +616,7 @@ export const VoiceSettings: React.FC = () => {
</SelectContent>
</Select>
<Button size="xs" variant="ghost" onClick={previewOpenAIVoice} title={t('settings.voice.page.actions.preview')}>
{isOpenAIPreviewPlaying ? <RiStopLine className="w-3.5 h-3.5" /> : <RiPlayLine className="w-3.5 h-3.5" />}
{isOpenAIPreviewPlaying ? <Icon name="stop" className="w-3.5 h-3.5" /> : <Icon name="play" className="w-3.5 h-3.5" />}
</Button>
</>
)}
@@ -638,7 +638,7 @@ export const VoiceSettings: React.FC = () => {
</SelectContent>
</Select>
<Button size="xs" variant="ghost" onClick={previewVoice} title={t('settings.voice.page.actions.preview')}>
{isPreviewPlaying ? <RiStopLine className="w-3.5 h-3.5" /> : <RiPlayLine className="w-3.5 h-3.5" />}
{isPreviewPlaying ? <Icon name="stop" className="w-3.5 h-3.5" /> : <Icon name="play" className="w-3.5 h-3.5" />}
</Button>
</>
)}
@@ -657,7 +657,7 @@ export const VoiceSettings: React.FC = () => {
</SelectContent>
</Select>
<Button size="xs" variant="ghost" onClick={previewBrowserVoice} title={t('settings.voice.page.actions.preview')}>
{isBrowserPreviewPlaying ? <RiStopLine className="w-3.5 h-3.5" /> : <RiPlayLine className="w-3.5 h-3.5" />}
{isBrowserPreviewPlaying ? <Icon name="stop" className="w-3.5 h-3.5" /> : <Icon name="play" className="w-3.5 h-3.5" />}
</Button>
</>
)}
@@ -734,7 +734,7 @@ export const VoiceSettings: React.FC = () => {
<span className="typography-ui-label text-foreground">{t('settings.voice.page.field.provider')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
<ul className="space-y-1">
@@ -795,7 +795,7 @@ export const VoiceSettings: React.FC = () => {
onClick={() => setSttServerUrl('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<RiCloseLine className="w-3.5 h-3.5" />
<Icon name="close" className="w-3.5 h-3.5" />
</button>
)}
</div>
@@ -1,8 +1,8 @@
import React from 'react';
import { RiAddLine, RiCloseLine, RiDeleteBinLine, RiInformationLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useSessions } from '@/sync/sync-context';
@@ -241,8 +241,6 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
});
}, [sessions, getWorktreeMetadata]);
// Refresh worktrees when sessions change (after deletion)
const sessionsKey = React.useMemo(() => sessions.map(s => s.id).join(','), [sessions]);
React.useEffect(() => {
@@ -276,7 +274,7 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
<h3 className="typography-ui-header font-normal text-foreground">{t('settings.openchamber.worktrees.setup.title')}</h3>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.worktrees.setup.tooltipPrefix')}
@@ -310,7 +308,7 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
className="flex-shrink-0 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
aria-label={t('settings.openchamber.worktrees.setup.removeCommandAria')}
>
<RiCloseLine className="h-4 w-4" />
<Icon name="close" className="h-4 w-4" />
</button>
</div>
))}
@@ -321,7 +319,7 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
className="!font-normal"
onClick={handleAddCommand}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
{t('settings.openchamber.worktrees.setup.addCommand')}
</Button>
</div>
@@ -335,7 +333,7 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
<h3 className="typography-ui-header font-normal text-foreground">{t('settings.openchamber.worktrees.list.title')}</h3>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.openchamber.worktrees.list.tooltip')}
@@ -379,7 +377,7 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
)}
aria-label={t('settings.openchamber.worktrees.list.deleteWorktreeAria', { name: worktree.branch || worktree.label || worktree.path })}
>
<RiDeleteBinLine className="h-4 w-4" />
<Icon name="delete-bin" className="h-4 w-4" />
</button>
</div>
))}
@@ -1,12 +1,4 @@
import React from 'react';
import {
RiAddLine,
RiArrowDownSLine,
RiArrowRightSLine,
RiDeleteBinLine,
RiInformationLine,
RiPlayLine,
} from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
@@ -30,6 +22,7 @@ import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from '@/components/ui';
import { Icon } from "@/components/icon/Icon";
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
import { isDesktopShell } from '@/lib/desktop';
import {
@@ -201,7 +194,7 @@ export const ProjectActionsSection: React.FC<ProjectActionsSectionProps> = ({ pr
<p className="typography-meta text-muted-foreground">{t('settings.projects.actions.description')}</p>
</div>
<Button type="button" variant="outline" size="xs" className="!font-normal" onClick={handleAddAction}>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
{t('settings.projects.actions.actions.add')}
</Button>
</div>
@@ -217,7 +210,7 @@ export const ProjectActionsSection: React.FC<ProjectActionsSectionProps> = ({ pr
<div className="space-y-0 max-w-[30rem]">
{actions.map((action) => {
const selectedIconKey = (action.icon as keyof typeof PROJECT_ACTION_ICON_MAP) || 'play';
const SelectedIcon = PROJECT_ACTION_ICON_MAP[selectedIconKey] || RiPlayLine;
const selectedIconName = PROJECT_ACTION_ICON_MAP[selectedIconKey] || 'play';
const isOpen = expandedActions[action.id] ?? false;
const title = action.name.trim() || t('settings.projects.actions.state.untitled');
@@ -238,11 +231,11 @@ export const ProjectActionsSection: React.FC<ProjectActionsSectionProps> = ({ pr
<div className="flex items-start gap-2">
<CollapsibleTrigger className="group flex-1 justify-start gap-2 rounded-md px-0 pr-1 py-1 hover:bg-[var(--interactive-hover)] focus-visible:ring-2 focus-visible:ring-[var(--interactive-focus-ring)]">
{isOpen ? (
<RiArrowDownSLine className="h-4 w-4 text-muted-foreground" />
<Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground" />
) : (
<RiArrowRightSLine className="h-4 w-4 text-muted-foreground" />
<Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground" />
)}
<SelectedIcon className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<Icon name={selectedIconName} className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="typography-ui-label text-foreground truncate">{title}</span>
@@ -257,7 +250,7 @@ export const ProjectActionsSection: React.FC<ProjectActionsSectionProps> = ({ pr
className="!font-normal h-7 w-7 px-0 text-muted-foreground hover:text-[var(--status-error)]"
onClick={() => handleRemoveAction(action.id)}
>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
</Button>
</div>
@@ -271,13 +264,13 @@ export const ProjectActionsSection: React.FC<ProjectActionsSectionProps> = ({ pr
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-[var(--interactive-border)] text-foreground hover:bg-[var(--interactive-hover)]"
aria-label={t('settings.projects.actions.field.selectIconAria')}
>
<SelectedIcon className="h-4 w-4" />
<Icon name={selectedIconName} className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56 p-2">
<div className="grid grid-cols-6 gap-1">
{PROJECT_ACTION_ICONS.map((entry) => {
const Icon = entry.Icon;
const iconName = entry.Icon;
const selected = (action.icon || 'play') === entry.key;
return (
<button
@@ -290,7 +283,7 @@ export const ProjectActionsSection: React.FC<ProjectActionsSectionProps> = ({ pr
)}
aria-label={t('settings.projects.actions.field.iconAria', { icon: entry.label })}
>
<Icon className="h-4 w-4" />
<Icon name={iconName} className="h-4 w-4" />
</button>
);
})}
@@ -364,7 +357,7 @@ export const ProjectActionsSection: React.FC<ProjectActionsSectionProps> = ({ pr
/>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 shrink-0 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 shrink-0 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.projects.actions.field.overrideUrlTooltip')}
@@ -7,9 +7,9 @@ import { cn } from '@/lib/utils';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useUIStore } from '@/stores/useUIStore';
import { PROJECT_COLORS, PROJECT_ICONS, PROJECT_COLOR_MAP as COLOR_MAP, getProjectIconImageUrl } from '@/lib/projectMeta';
import { RiCloseLine } from '@remixicon/react';
import { WorktreeSectionContent } from '@/components/sections/openchamber/WorktreeSectionContent';
import { ProjectActionsSection } from '@/components/sections/projects/ProjectActionsSection';
import { Icon } from "@/components/icon/Icon";
import { useThemeSystem } from '@/contexts/useThemeSystem';
import { useI18n } from '@/lib/i18n';
@@ -298,7 +298,7 @@ export const ProjectsPage: React.FC = () => {
)}
title={t('settings.projects.page.field.none')}
>
<RiCloseLine className="h-4 w-4 text-muted-foreground" />
<Icon name="close" className="h-4 w-4 text-muted-foreground" />
</button>
{PROJECT_COLORS.map((c) => (
<button
@@ -346,10 +346,10 @@ export const ProjectsPage: React.FC = () => {
)}
title={t('settings.projects.page.field.none')}
>
<RiCloseLine className="h-4 w-4 text-muted-foreground" />
<Icon name="close" className="h-4 w-4 text-muted-foreground" />
</button>
{PROJECT_ICONS.map((i) => {
const IconComponent = i.Icon;
const iconName = i.Icon;
return (
<button
key={i.key}
@@ -363,7 +363,7 @@ export const ProjectsPage: React.FC = () => {
)}
title={i.label}
>
<IconComponent className="w-4 h-4" style={currentColorVar && icon === i.key ? { color: currentColorVar } : undefined} />
<Icon name={iconName} className="w-4 h-4" style={currentColorVar && icon === i.key ? { color: currentColorVar } : undefined} />
</button>
);
})}
@@ -412,7 +412,7 @@ export const ProjectsPage: React.FC = () => {
title={t('settings.projects.page.field.clearBackground')}
disabled={!iconBackground}
>
<RiCloseLine className="h-3.5 w-3.5" />
<Icon name="close" className="h-3.5 w-3.5" />
</Button>
</div>
)}
@@ -4,9 +4,9 @@ import { useUIStore } from '@/stores/useUIStore';
import { Button } from '@/components/ui/button';
import { SettingsSidebarLayout } from '@/components/sections/shared/SettingsSidebarLayout';
import { SettingsSidebarItem } from '@/components/sections/shared/SettingsSidebarItem';
import { Icon } from "@/components/icon/Icon";
import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, getProjectIconImageUrl } from '@/lib/projectMeta';
import { cn } from '@/lib/utils';
import { RiAddLine, RiFolderLine } from '@remixicon/react';
import { isVSCodeRuntime } from '@/lib/desktop';
import { sessionEvents } from '@/lib/sessionEvents';
import { useThemeSystem } from '@/contexts/useThemeSystem';
@@ -56,7 +56,7 @@ export const ProjectsSidebar: React.FC<{ onItemSelect?: () => void }> = ({ onIte
onClick={handleAddProject}
aria-label={t('settings.projects.sidebar.actions.addProject')}
>
<RiAddLine className="size-4" />
<Icon name="add" className="size-4" />
</Button>
)}
</div>
@@ -65,7 +65,7 @@ export const ProjectsSidebar: React.FC<{ onItemSelect?: () => void }> = ({ onIte
>
{projects.map((project) => {
const selected = project.id === selectedId;
const Icon = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
const iconName = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
const imageFailureKey = `${project.id}:${project.iconImage?.updatedAt ?? 0}`;
const imageUrl = brokenIconIds.has(imageFailureKey)
? null
@@ -98,12 +98,12 @@ export const ProjectsSidebar: React.FC<{ onItemSelect?: () => void }> = ({ onIte
/>
</span>
)
: Icon
: iconName
? (
<Icon className={cn('h-4 w-4', selected ? 'text-foreground' : 'text-muted-foreground/70')} style={color ? { color } : undefined} />
<Icon name={iconName} className={cn('h-4 w-4', selected ? 'text-foreground' : 'text-muted-foreground/70')} style={color ? { color } : undefined} />
)
: (
<RiFolderLine className={cn('h-4 w-4', selected ? 'text-foreground' : 'text-muted-foreground/70')} style={color ? { color } : undefined} />
<Icon name="folder" className={cn('h-4 w-4', selected ? 'text-foreground' : 'text-muted-foreground/70')} style={color ? { color } : undefined} />
);
return (
@@ -12,8 +12,8 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { toast } from '@/components/ui';
import { RiStackLine, RiToolsLine, RiBrainAi3Line, RiFileImageLine, RiArrowDownSLine, RiCheckLine, RiSearchLine, RiInformationLine, RiEyeLine, RiEyeOffLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
import { cn } from '@/lib/utils';
import { copyTextToClipboard } from '@/lib/clipboard';
@@ -511,7 +511,7 @@ export const ProvidersPage: React.FC = () => {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-muted-foreground">
<RiStackLine className="mx-auto mb-3 h-12 w-12 opacity-50" />
<Icon name="stack" className="mx-auto mb-3 h-12 w-12 opacity-50" />
<p className="typography-body">{t('settings.providers.page.empty.noProvidersDetected')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.providers.page.empty.checkOpenCodeConfiguration')}</p>
</div>
@@ -561,7 +561,7 @@ export const ProvidersPage: React.FC = () => {
: t('settings.providers.page.connect.selectProviderPlaceholder')}
</span>
</span>
<RiArrowDownSLine className="h-4 w-4 flex-shrink-0 text-muted-foreground/50" />
<Icon name="arrow-down-s" className="h-4 w-4 flex-shrink-0 text-muted-foreground/50" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
@@ -573,7 +573,7 @@ export const ProvidersPage: React.FC = () => {
className="flex items-center gap-2 border-b border-[var(--surface-subtle)] px-3 py-2"
onKeyDown={(e) => e.stopPropagation()}
>
<RiSearchLine className="h-4 w-4 text-muted-foreground" />
<Icon name="search" className="h-4 w-4 text-muted-foreground" />
<input
type="text"
value={providerSearchQuery}
@@ -608,7 +608,7 @@ export const ProvidersPage: React.FC = () => {
<span className="truncate">{provider.name || provider.id}</span>
</span>
{candidateProviderId === provider.id && (
<RiCheckLine className="h-4 w-4 text-[var(--primary-base)]" />
<Icon name="check" className="h-4 w-4 text-[var(--primary-base)]" />
)}
</DropdownMenuItem>
));
@@ -636,7 +636,7 @@ export const ProvidersPage: React.FC = () => {
{t('settings.providers.page.auth.apiKeyLabel')}
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.providers.page.auth.apiKeyTooltip')}
@@ -772,7 +772,7 @@ export const ProvidersPage: React.FC = () => {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-muted-foreground">
<RiStackLine className="mx-auto mb-3 h-12 w-12 opacity-50" />
<Icon name="stack" className="mx-auto mb-3 h-12 w-12 opacity-50" />
<p className="typography-body">{t('settings.providers.page.empty.selectProviderFromSidebar')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.providers.page.empty.reviewDetailsAndConfigureAuth')}</p>
</div>
@@ -826,7 +826,7 @@ export const ProvidersPage: React.FC = () => {
<section className="px-2 pb-2 pt-0">
{!showAuthPanel ? (
<div className="flex items-center gap-1.5 py-1.5">
<RiCheckLine className="w-4 h-4 text-[var(--status-success)] shrink-0" />
<Icon name="check" className="w-4 h-4 text-[var(--status-success)] shrink-0" />
<span className="typography-ui-label text-foreground">{t('settings.providers.page.auth.connected')}</span>
<span className="typography-meta text-muted-foreground ml-1">{t('settings.providers.page.auth.useReconnectHint')}</span>
</div>
@@ -839,7 +839,7 @@ export const ProvidersPage: React.FC = () => {
{t('settings.providers.page.auth.apiKeyLabel')}
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.providers.page.auth.apiKeyTooltip')}
@@ -1031,7 +1031,7 @@ export const ProvidersPage: React.FC = () => {
<section className="px-2 pb-2 pt-0">
<div className="relative mb-2">
<RiSearchLine className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Icon name="search" className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Input
value={modelQuery}
onChange={(event) => setModelQuery(event.target.value)}
@@ -1055,10 +1055,10 @@ export const ProvidersPage: React.FC = () => {
const contextTokens = formatTokens(metadata?.limit?.context);
const outputTokens = formatTokens(metadata?.limit?.output);
const capabilityIcons: Array<{ key: string; icon: typeof RiToolsLine; label: string }> = [];
if (metadata?.tool_call) capabilityIcons.push({ key: 'tools', icon: RiToolsLine, label: t('settings.providers.page.models.capability.toolCalling') });
if (metadata?.reasoning) capabilityIcons.push({ key: 'reasoning', icon: RiBrainAi3Line, label: t('settings.providers.page.models.capability.reasoning') });
if (metadata?.attachment) capabilityIcons.push({ key: 'image', icon: RiFileImageLine, label: t('settings.providers.page.models.capability.imageInput') });
const capabilityIcons: Array<{ key: string; icon: string; label: string }> = [];
if (metadata?.tool_call) capabilityIcons.push({ key: 'tools', icon: "tools", label: t('settings.providers.page.models.capability.toolCalling') });
if (metadata?.reasoning) capabilityIcons.push({ key: 'reasoning', icon: "brain-ai-3", label: t('settings.providers.page.models.capability.reasoning') });
if (metadata?.attachment) capabilityIcons.push({ key: 'image', icon: "file-image", label: t('settings.providers.page.models.capability.imageInput') });
return (
<div key={modelId} className="py-1.5">
@@ -1081,14 +1081,14 @@ export const ProvidersPage: React.FC = () => {
)}
{capabilityIcons.length > 0 && (
<div className="flex items-center gap-1 flex-shrink-0">
{capabilityIcons.map(({ key, icon: Icon, label }) => (
{capabilityIcons.map(({ key, icon: iconName, label }) => (
<span
key={key}
className="flex h-5 w-5 rounded items-center justify-center text-muted-foreground bg-[var(--surface-muted)]"
title={label}
aria-label={label}
>
<Icon className="h-3 w-3" />
<Icon name={iconName} className="h-3 w-3" />
</span>
))}
</div>
@@ -1100,7 +1100,7 @@ export const ProvidersPage: React.FC = () => {
title={isHidden ? t('settings.providers.page.models.actions.showModelInSelectors') : t('settings.providers.page.models.actions.hideModelFromSelectors')}
aria-label={isHidden ? t('settings.providers.page.models.actions.showModel') : t('settings.providers.page.models.actions.hideModel')}
>
{isHidden ? <RiEyeOffLine className="h-3.5 w-3.5" /> : <RiEyeLine className="h-3.5 w-3.5" />}
{isHidden ? <Icon name="eye-off" className="h-3.5 w-3.5" /> : <Icon name="eye" className="h-3.5 w-3.5" />}
</button>
</div>
</div>
@@ -4,9 +4,9 @@ import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { Button } from '@/components/ui/button';
import { useConfigStore } from '@/stores/useConfigStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { RiAddLine, RiStackLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { SettingsProjectSelector } from '@/components/sections/shared/SettingsProjectSelector';
import { Icon } from "@/components/icon/Icon";
import { opencodeClient } from '@/lib/opencode/client';
import { useI18n } from '@/lib/i18n';
@@ -122,7 +122,7 @@ export const ProvidersSidebar: React.FC<ProvidersSidebarProps> = ({ onItemSelect
aria-label={t('settings.providers.sidebar.actions.connectProviderAria')}
title={t('settings.providers.sidebar.actions.connectProviderTitle')}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -130,7 +130,7 @@ export const ProvidersSidebar: React.FC<ProvidersSidebarProps> = ({ onItemSelect
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="space-y-1 px-3 py-2 overflow-x-hidden">
{providers.length === 0 ? (
<div className="py-12 px-4 text-center text-muted-foreground">
<RiStackLine className="mx-auto mb-3 h-10 w-10 opacity-50" />
<Icon name="stack" className="mx-auto mb-3 h-10 w-10 opacity-50" />
<p className="typography-ui-label font-medium">{t('settings.providers.sidebar.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.providers.sidebar.empty.description')}</p>
</div>
@@ -10,22 +10,6 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
RiAddLine,
RiArrowDownSLine,
RiArrowRightLine,
RiComputerLine,
RiExternalLinkLine,
RiFileCopyLine,
RiInformationLine,
RiPlug2Line,
RiRefreshLine,
RiServerLine,
RiShuffleLine,
RiTerminalWindowLine,
RiDeleteBinLine,
RiStopLine,
} from '@remixicon/react';
import {
Dialog,
DialogContent,
@@ -39,6 +23,7 @@ import { SettingsPageLayout } from '@/components/sections/shared/SettingsPageLay
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
import { useUIStore } from '@/stores/useUIStore';
import { toast } from '@/components/ui';
import { Icon } from "@/components/icon/Icon";
import { copyTextToClipboard } from '@/lib/clipboard';
import { openExternalUrl } from '@/lib/url';
import { useI18n, type I18nKey } from '@/lib/i18n';
@@ -152,7 +137,7 @@ const HintLabel: React.FC<{ label: string; hint: React.ReactNode }> = ({ label,
<span>{label}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
<div className="typography-meta text-foreground">{hint}</div>
@@ -808,7 +793,7 @@ export const RemoteInstancesPage: React.FC = () => {
onClick={handlePrimaryConnectionAction}
disabled={isPrimaryActionPending || isRetryPending}
>
{canDisconnect ? <RiStopLine className="h-3.5 w-3.5" /> : <RiPlug2Line className="h-3.5 w-3.5" />}
{canDisconnect ? <Icon name="stop" className="h-3.5 w-3.5" /> : <Icon name="plug-2" className="h-3.5 w-3.5" />}
{primaryButtonLabel}
</Button>
<Button
@@ -819,7 +804,7 @@ export const RemoteInstancesPage: React.FC = () => {
onClick={handleRetryAction}
disabled={!canRetry}
>
<RiRefreshLine className={`h-3.5 w-3.5 ${isConnecting || (isReconnecting && !reconnectAppearsStuck) ? 'animate-spin' : ''}`} />
<Icon name="refresh" className={`h-3.5 w-3.5 ${isConnecting || (isReconnecting && !reconnectAppearsStuck) ? 'animate-spin' : ''}`} />
{retryButtonLabel}
</Button>
<Button
@@ -831,7 +816,7 @@ export const RemoteInstancesPage: React.FC = () => {
void handleOpenLogs();
}}
>
<RiTerminalWindowLine className="h-3.5 w-3.5" />
<Icon name="terminal-window" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.logs')}
</Button>
<Button
@@ -854,7 +839,7 @@ export const RemoteInstancesPage: React.FC = () => {
});
}}
>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.sidebar.actions.remove')}
</Button>
</div>
@@ -1148,7 +1133,7 @@ export const RemoteInstancesPage: React.FC = () => {
}))
}
>
<RiShuffleLine className="h-3.5 w-3.5" />
<Icon name="shuffle" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1265,7 +1250,7 @@ export const RemoteInstancesPage: React.FC = () => {
<div className="flex items-center justify-between gap-2">
<div className="min-w-0 flex items-center gap-2">
<CollapsibleTrigger className="flex items-center gap-2 group">
<RiArrowDownSLine className={`h-4 w-4 text-muted-foreground transition-transform ${isForwardOpen ? 'rotate-180' : ''}`} />
<Icon name="arrow-down-s" className={`h-4 w-4 text-muted-foreground transition-transform ${isForwardOpen ? 'rotate-180' : ''}`} />
<span className="typography-ui-label text-foreground truncate">{buildForwardLabel(forward)}</span>
<span className="typography-micro text-muted-foreground/70 shrink-0">{typeLabel}</span>
</CollapsibleTrigger>
@@ -1284,7 +1269,7 @@ export const RemoteInstancesPage: React.FC = () => {
}))
}
>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1406,27 +1391,27 @@ export const RemoteInstancesPage: React.FC = () => {
<div className="flex flex-wrap items-center gap-1 typography-micro text-muted-foreground/80">
{forward.type === 'dynamic' ? (
<>
<RiComputerLine className="h-3.5 w-3.5" />
<Icon name="computer" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{localEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.localSocks5')}</span>
</>
) : forward.type === 'remote' ? (
<>
<RiServerLine className="h-3.5 w-3.5" />
<Icon name="server" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{remoteEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.remote')}</span>
<RiArrowRightLine className="h-3.5 w-3.5" />
<RiComputerLine className="h-3.5 w-3.5" />
<Icon name="arrow-right" className="h-3.5 w-3.5" />
<Icon name="computer" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{localEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.local')}</span>
</>
) : (
<>
<RiComputerLine className="h-3.5 w-3.5" />
<Icon name="computer" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{localEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.local')}</span>
<RiArrowRightLine className="h-3.5 w-3.5" />
<RiServerLine className="h-3.5 w-3.5" />
<Icon name="arrow-right" className="h-3.5 w-3.5" />
<Icon name="server" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{remoteEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.remote')}</span>
</>
@@ -1447,7 +1432,7 @@ export const RemoteInstancesPage: React.FC = () => {
});
}}
>
<RiExternalLinkLine className="h-3.5 w-3.5" />
<Icon name="external-link" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.openLocal')}
</Button>
) : null}
@@ -1475,7 +1460,7 @@ export const RemoteInstancesPage: React.FC = () => {
}));
}}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.addForward')}
</Button>
</section>
@@ -1540,7 +1525,7 @@ export const RemoteInstancesPage: React.FC = () => {
});
}}
>
<RiFileCopyLine className="h-3.5 w-3.5" />
<Icon name="file-copy" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.copyLocalUrl')}
</Button>
<Button
@@ -1552,7 +1537,7 @@ export const RemoteInstancesPage: React.FC = () => {
void handleOpenCurrentInstance();
}}
>
<RiExternalLinkLine className="h-3.5 w-3.5" />
<Icon name="external-link" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.open')}
</Button>
</>
@@ -1571,11 +1556,11 @@ export const RemoteInstancesPage: React.FC = () => {
</DialogHeader>
<div className="flex items-center justify-end gap-2">
<Button type="button" variant="outline" size="xs" className="!font-normal" onClick={handleCopyAllLogs} disabled={logDialogLoading || !logLinesText.trim()}>
<RiFileCopyLine className="h-3.5 w-3.5" />
<Icon name="file-copy" className="h-3.5 w-3.5" />
{t('settings.common.actions.copyAll')}
</Button>
<Button type="button" variant="outline" size="xs" className="!font-normal" onClick={() => void handleClearLogs()} disabled={logDialogLoading}>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
{t('settings.common.actions.clear')}
</Button>
</div>
@@ -1,11 +1,11 @@
import React from 'react';
import { RiAddLine, RiDeleteBinLine, RiPlug2Line, RiRefreshLine, RiStopLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { SettingsSidebarLayout } from '@/components/sections/shared/SettingsSidebarLayout';
import { SettingsSidebarItem } from '@/components/sections/shared/SettingsSidebarItem';
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
import { useUIStore } from '@/stores/useUIStore';
import { toast } from '@/components/ui';
import { Icon } from "@/components/icon/Icon";
import type { DesktopSshInstance } from '@/lib/desktopSsh';
import { useI18n } from '@/lib/i18n';
@@ -145,7 +145,7 @@ export const RemoteInstancesSidebar: React.FC<RemoteInstancesSidebarProps> = ({
onClick={() => void handleAdd()}
aria-label={t('settings.remoteInstances.sidebar.actions.addSshInstance')}
>
<RiAddLine className="size-4" />
<Icon name="add" className="size-4" />
</Button>
</div>
</div>
@@ -172,7 +172,7 @@ export const RemoteInstancesSidebar: React.FC<RemoteInstancesSidebarProps> = ({
actions={[
{
label: isReady ? t('settings.remoteInstances.sidebar.actions.disconnect') : t('settings.remoteInstances.sidebar.actions.connect'),
icon: isReady ? RiStopLine : RiPlug2Line,
icon: isReady ? 'stop' : 'plug-2',
onClick: () => {
const op = isReady ? disconnect(instance.id) : connectWithPortRecovery(instance);
void op.catch((error) => {
@@ -189,7 +189,7 @@ export const RemoteInstancesSidebar: React.FC<RemoteInstancesSidebarProps> = ({
},
{
label: t('settings.remoteInstances.sidebar.actions.retry'),
icon: RiRefreshLine,
icon: "refresh",
onClick: () => {
if (!canRetry) return;
void retry(instance.id).catch((error) => {
@@ -201,7 +201,7 @@ export const RemoteInstancesSidebar: React.FC<RemoteInstancesSidebarProps> = ({
},
{
label: t('settings.remoteInstances.sidebar.actions.remove'),
icon: RiDeleteBinLine,
icon: "delete-bin",
destructive: true,
onClick: () => {
void removeInstance(instance.id).then(() => {
@@ -6,7 +6,7 @@ import {
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { RiArrowDownSLine, RiFolderLine } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { useProjectsStore } from '@/stores/useProjectsStore';
import { isVSCodeRuntime } from '@/lib/desktop';
import { cn } from '@/lib/utils';
@@ -59,9 +59,9 @@ export const SettingsProjectSelector: React.FC<{ className?: string }> = ({ clas
'flex items-center gap-1.5 text-left'
)}
>
<RiFolderLine className="h-4 w-4 opacity-70" />
<Icon name="folder" className="h-4 w-4 opacity-70" />
<span className="min-w-0 flex-1 truncate typography-ui-label font-medium">{label}</span>
<RiArrowDownSLine className="size-4 opacity-50" />
<Icon name="arrow-down-s" className="size-4 opacity-50" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-auto">
@@ -1,6 +1,6 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { RiAddLine } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { useDeviceInfo } from '@/lib/device';
import { cn } from '@/lib/utils';
@@ -54,7 +54,7 @@ export const SettingsSidebarHeader: React.FC<SettingsSidebarHeaderProps> = ({
onClick={onAdd}
aria-label={addButtonLabel}
>
<RiAddLine className="size-4" />
<Icon name="add" className="size-4" />
</Button>
)}
</div>
@@ -6,14 +6,14 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { RiMore2Line } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
export interface SettingsSidebarItemAction {
/** Label shown in dropdown menu */
label: string;
/** Icon component to show before label */
icon?: React.ComponentType<{ className?: string }>;
icon?: string;
/** Callback when action is clicked */
onClick: () => void;
/** If true, uses destructive styling (red text) */
@@ -47,10 +47,10 @@ interface SettingsSidebarItemProps {
* metadata={agent.description}
* selected={selectedId === agent.id}
* onSelect={() => setSelectedId(agent.id)}
* icon={<RiRobotLine className="h-4 w-4" />}
* icon={<'robot' className="h-4 w-4" />}
* actions={[
* { label: 'Duplicate', icon: RiFileCopyLine, onClick: handleDuplicate },
* { label: 'Delete', icon: RiDeleteBinLine, onClick: handleDelete, destructive: true },
* { label: 'Duplicate', icon: 'file-copy', onClick: handleDuplicate },
* { label: 'Delete', icon: 'delete-bin', onClick: handleDelete, destructive: true },
* ]}
* />
*/
@@ -103,12 +103,12 @@ export const SettingsSidebarItem: React.FC<SettingsSidebarItemProps> = ({
variant="ghost"
className="h-6 w-6 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
>
<RiMore2Line className="h-3.5 w-3.5" />
<Icon name="more-2" className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-fit min-w-20">
{actions.map((action) => {
const Icon = action.icon;
const iconName = action.icon;
return (
<DropdownMenuItem
key={action.label}
@@ -120,7 +120,7 @@ export const SettingsSidebarItem: React.FC<SettingsSidebarItemProps> = ({
action.destructive && 'text-destructive focus:text-destructive'
)}
>
{Icon && <Icon className="h-4 w-4 mr-px" />}
{iconName && <Icon name={iconName} className="h-4 w-4 mr-px" />}
{action.label}
</DropdownMenuItem>
);
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { RiArrowDownSLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { Icon } from "@/components/icon/Icon";
interface SidebarGroupProps {
/** Group display label (e.g. "business", "automation-ai") */
@@ -64,7 +64,7 @@ export const SidebarGroup: React.FC<SidebarGroupProps> = ({
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
)}
>
<RiArrowDownSLine
<Icon name="arrow-down-s"
className={cn(
'h-3.5 w-3.5 flex-shrink-0 transition-transform duration-200',
!expanded && '-rotate-90',
@@ -5,7 +5,6 @@ import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui';
import { useSkillsStore, type SkillConfig, type SkillScope, type SupportingFile, type PendingFile } from '@/stores/useSkillsStore';
import { useShallow } from 'zustand/react/shallow';
import { RiAddLine, RiBookOpenLine, RiDeleteBinLine, RiFileLine, RiFolderLine, RiRobot2Line, RiUser3Line } from '@remixicon/react';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import {
Select,
@@ -21,6 +20,7 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Icon } from "@/components/icon/Icon";
import { SkillsCatalogPage } from './catalog/SkillsCatalogPage';
import {
SKILL_LOCATION_OPTIONS,
@@ -359,7 +359,7 @@ const SkillsInstalledPage: React.FC = () => {
return (
<div className="flex h-full items-center justify-center px-4">
<div className="text-center text-muted-foreground">
<RiBookOpenLine className="mx-auto mb-3 h-10 w-10 sm:h-12 sm:w-12 opacity-50" />
<Icon name="book-open" className="mx-auto mb-3 h-10 w-10 sm:h-12 sm:w-12 opacity-50" />
<p className="typography-body">{t('settings.skills.page.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.skills.page.empty.description')}</p>
</div>
@@ -433,11 +433,11 @@ const SkillsInstalledPage: React.FC = () => {
>
<SelectTrigger className="w-fit gap-1.5">
{draftScope === 'user' ? (
<RiUser3Line className="h-3.5 w-3.5" />
<Icon name="user-3" className="h-3.5 w-3.5" />
) : (
<RiFolderLine className="h-3.5 w-3.5" />
<Icon name="folder" className="h-3.5 w-3.5" />
)}
{draftSource === 'agents' ? <RiRobot2Line className="h-3.5 w-3.5" /> : null}
{draftSource === 'agents' ? <Icon name="robot-2" className="h-3.5 w-3.5" /> : null}
<span>{locationLabelText(locationValueFrom(draftScope, draftSource))}</span>
</SelectTrigger>
<SelectContent align="start">
@@ -445,8 +445,8 @@ const SkillsInstalledPage: React.FC = () => {
<SelectItem key={option.value} value={option.value}>
<div className="flex flex-col gap-0.5">
<div className="flex items-center gap-2">
{option.scope === 'user' ? <RiUser3Line className="h-3.5 w-3.5" /> : <RiFolderLine className="h-3.5 w-3.5" />}
{option.source === 'agents' ? <RiRobot2Line className="h-3.5 w-3.5" /> : null}
{option.scope === 'user' ? <Icon name="user-3" className="h-3.5 w-3.5" /> : <Icon name="folder" className="h-3.5 w-3.5" />}
{option.source === 'agents' ? <Icon name="robot-2" className="h-3.5 w-3.5" /> : null}
<span>{locationLabelText(option.value)}</span>
</div>
<span className="typography-micro text-muted-foreground ml-6">{locationDescriptionText(option.value)}</span>
@@ -501,7 +501,7 @@ const SkillsInstalledPage: React.FC = () => {
{t('settings.skills.page.section.supportingFiles')}
</h3>
<Button variant="outline" size="xs" className="!font-normal gap-1" onClick={handleAddFile}>
<RiAddLine className="h-3.5 w-3.5" /> {t('settings.skills.page.actions.addFile')}
<Icon name="add" className="h-3.5 w-3.5" /> {t('settings.skills.page.actions.addFile')}
</Button>
</div>
@@ -525,7 +525,7 @@ const SkillsInstalledPage: React.FC = () => {
className="flex items-center gap-2 py-1.5 cursor-pointer group"
onClick={() => handleEditFile(file.path)}
>
<RiFileLine className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Icon name="file" className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<span className="typography-ui-label text-foreground truncate">{file.path}</span>
{isNewSkill && (
<span className="typography-micro text-[var(--status-warning)] bg-[var(--status-warning)]/10 px-1.5 py-0.5 rounded flex-shrink-0">
@@ -540,7 +540,7 @@ const SkillsInstalledPage: React.FC = () => {
handleDeleteFile(file.path);
}}
>
<RiDeleteBinLine className="h-3 w-3" />
<Icon name="delete-bin" className="h-3 w-3" />
</Button>
</div>
))}
@@ -17,13 +17,13 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { RiAddLine, RiDeleteBinLine, RiFileCopyLine, RiMore2Line, RiEditLine, RiBookOpenLine } from '@remixicon/react';
import { useSkillsStore, type DiscoveredSkill } from '@/stores/useSkillsStore';
import { useShallow } from 'zustand/react/shallow';
import { cn } from '@/lib/utils';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { SettingsProjectSelector } from '@/components/sections/shared/SettingsProjectSelector';
import { SidebarGroup } from '@/components/sections/shared/SidebarGroup';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface SkillsSidebarProps {
@@ -75,7 +75,6 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
setSelectedSkill(newName);
onItemSelect?.();
};
const handleDeleteSkill = async (skill: DiscoveredSkill) => {
@@ -125,7 +124,6 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
});
setSelectedSkill(newName);
};
const handleOpenRenameDialog = (skill: DiscoveredSkill) => {
@@ -226,7 +224,7 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
className="h-7 w-7 px-0 -my-1 text-muted-foreground"
onClick={handleCreateNew}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -234,7 +232,7 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="space-y-1 px-3 py-2 overflow-x-hidden">
{skills.length === 0 ? (
<div className="py-12 px-4 text-center text-muted-foreground">
<RiBookOpenLine className="mx-auto mb-3 h-10 w-10 opacity-50" />
<Icon name="book-open" className="mx-auto mb-3 h-10 w-10 opacity-50" />
<p className="typography-ui-label font-medium">{t('settings.skills.sidebar.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.skills.sidebar.empty.description')}</p>
</div>
@@ -482,7 +480,7 @@ const SkillListItem: React.FC<SkillListItemProps> = ({
variant="ghost"
className="h-6 w-6 px-0 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
>
<RiMore2Line className="h-3.5 w-3.5" />
<Icon name="more-2" className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-fit min-w-20">
@@ -492,7 +490,7 @@ const SkillListItem: React.FC<SkillListItemProps> = ({
onRename();
}}
>
<RiEditLine className="h-4 w-4 mr-px" />
<Icon name="edit" className="h-4 w-4 mr-px" />
{t('settings.common.actions.rename')}
</DropdownMenuItem>
@@ -502,7 +500,7 @@ const SkillListItem: React.FC<SkillListItemProps> = ({
onDuplicate();
}}
>
<RiFileCopyLine className="h-4 w-4 mr-px" />
<Icon name="file-copy" className="h-4 w-4 mr-px" />
{t('settings.common.actions.duplicate')}
</DropdownMenuItem>
@@ -513,7 +511,7 @@ const SkillListItem: React.FC<SkillListItemProps> = ({
}}
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="h-4 w-4 mr-px" />
<Icon name="delete-bin" className="h-4 w-4 mr-px" />
{t('settings.common.actions.delete')}
</DropdownMenuItem>
</DropdownMenuContent>
@@ -17,8 +17,7 @@ import {
SelectItem,
SelectTrigger,
} from '@/components/ui/select';
import { RiGitRepositoryLine } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
import { isVSCodeRuntime } from '@/lib/desktop';
@@ -354,7 +353,7 @@ export const AddCatalogDialog: React.FC<AddCatalogDialogProps> = ({ open, onOpen
onClick={() => void handleScan()}
disabled={isScanning || !source.trim()}
>
<RiGitRepositoryLine className="h-4 w-4" />
<Icon name="git-repository" className="h-4 w-4" />
{isScanning ? t('settings.skills.catalog.shared.actions.scanning') : t('settings.skills.catalog.shared.actions.scan')}
</Button>
<Button
@@ -20,7 +20,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { RiFolderLine, RiGitRepositoryLine, RiRobot2Line, RiUser3Line } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { isVSCodeRuntime } from '@/lib/desktop';
import type { SkillsCatalogItem } from '@/lib/api/types';
@@ -364,7 +364,7 @@ export const InstallFromRepoDialog: React.FC<InstallFromRepoDialogProps> = ({ op
disabled={isScanning || !source.trim()}
className="gap-2"
>
<RiGitRepositoryLine className="h-4 w-4" />
<Icon name="git-repository" className="h-4 w-4" />
{isScanning ? t('settings.skills.catalog.shared.actions.scanning') : t('settings.skills.catalog.shared.actions.scan')}
</Button>
</div>
@@ -401,8 +401,8 @@ export const InstallFromRepoDialog: React.FC<InstallFromRepoDialogProps> = ({ op
}}
>
<SelectTrigger size="lg" className="w-full gap-1.5">
{scope === 'user' ? <RiUser3Line className="h-4 w-4" /> : <RiFolderLine className="h-4 w-4" />}
{targetSource === 'agents' ? <RiRobot2Line className="h-4 w-4" /> : null}
{scope === 'user' ? <Icon name="user-3" className="h-4 w-4" /> : <Icon name="folder" className="h-4 w-4" />}
{targetSource === 'agents' ? <Icon name="robot-2" className="h-4 w-4" /> : null}
<span>{locationLabelText(locationValueFrom(scope, targetSource))}</span>
</SelectTrigger>
<SelectContent align="start">
@@ -410,8 +410,8 @@ export const InstallFromRepoDialog: React.FC<InstallFromRepoDialogProps> = ({ op
<SelectItem key={option.value} value={option.value} className="pr-2 [&>span:first-child]:hidden">
<div className="flex flex-col gap-0.5">
<div className="flex items-center gap-2">
{option.scope === 'user' ? <RiUser3Line className="h-4 w-4" /> : <RiFolderLine className="h-4 w-4" />}
{option.source === 'agents' ? <RiRobot2Line className="h-4 w-4" /> : null}
{option.scope === 'user' ? <Icon name="user-3" className="h-4 w-4" /> : <Icon name="folder" className="h-4 w-4" />}
{option.source === 'agents' ? <Icon name="robot-2" className="h-4 w-4" /> : null}
<span>{locationLabelText(option.value)}</span>
</div>
<span className="typography-micro text-muted-foreground ml-6">{locationDescriptionText(option.value)}</span>
@@ -17,7 +17,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { RiFolderLine, RiRobot2Line, RiUser3Line } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import type { SkillsCatalogItem } from '@/lib/api/types';
@@ -208,8 +208,8 @@ export const InstallSkillDialog: React.FC<InstallSkillDialogProps> = ({ open, on
}}
>
<SelectTrigger className="w-fit gap-1.5">
{scope === 'user' ? <RiUser3Line className="h-3.5 w-3.5" /> : <RiFolderLine className="h-3.5 w-3.5" />}
{targetSource === 'agents' ? <RiRobot2Line className="h-3.5 w-3.5" /> : null}
{scope === 'user' ? <Icon name="user-3" className="h-3.5 w-3.5" /> : <Icon name="folder" className="h-3.5 w-3.5" />}
{targetSource === 'agents' ? <Icon name="robot-2" className="h-3.5 w-3.5" /> : null}
<span>{locationLabelText(locationValueFrom(scope, targetSource))}</span>
</SelectTrigger>
<SelectContent align="start">
@@ -217,8 +217,8 @@ export const InstallSkillDialog: React.FC<InstallSkillDialogProps> = ({ open, on
<SelectItem key={option.value} value={option.value} className="pr-2 [&>span:first-child]:hidden">
<div className="flex flex-col gap-0.5">
<div className="flex items-center gap-2">
{option.scope === 'user' ? <RiUser3Line className="h-3.5 w-3.5" /> : <RiFolderLine className="h-3.5 w-3.5" />}
{option.source === 'agents' ? <RiRobot2Line className="h-3.5 w-3.5" /> : null}
{option.scope === 'user' ? <Icon name="user-3" className="h-3.5 w-3.5" /> : <Icon name="folder" className="h-3.5 w-3.5" />}
{option.source === 'agents' ? <Icon name="robot-2" className="h-3.5 w-3.5" /> : null}
<span>{locationLabelText(option.value)}</span>
</div>
<span className="typography-micro text-muted-foreground ml-5">{locationDescriptionText(option.value)}</span>
@@ -19,8 +19,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { RiAddLine, RiDeleteBinLine, RiRefreshLine, RiDownloadLine, RiStarLine, RiSearchLine } from '@remixicon/react';
import { Icon } from "@/components/icon/Icon";
import { useSkillsCatalogStore } from '@/stores/useSkillsCatalogStore';
import { useShallow } from 'zustand/react/shallow';
@@ -225,7 +224,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
disabled={isLoadingCatalog || isLoadingSource}
title={t('settings.skills.catalog.page.actions.refreshTitle')}
>
<RiRefreshLine className={cn("h-3.5 w-3.5", (isLoadingCatalog || isLoadingSource) && "animate-spin")} />
<Icon name="refresh" className={cn("h-3.5 w-3.5", (isLoadingCatalog || isLoadingSource) && "animate-spin")} />
</Button>
{isCustomSource && (
@@ -237,7 +236,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
disabled={isRemovingCatalog}
title={t('settings.skills.catalog.page.actions.removeCatalogTitle')}
>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
</Button>
)}
@@ -246,13 +245,13 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
className="!font-normal gap-1"
onClick={() => setAddCatalogOpen(true)}
>
<RiAddLine className="h-3.5 w-3.5" /> {t('settings.skills.catalog.page.actions.addCatalog')}
<Icon name="add" className="h-3.5 w-3.5" /> {t('settings.skills.catalog.page.actions.addCatalog')}
</Button>
</div>
<div className="py-1.5">
<div className="relative">
<RiSearchLine className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Icon name="search" className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
@@ -287,7 +286,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
</div>
) : isLoadingSource ? (
<div className="py-8 text-center text-muted-foreground">
<RiRefreshLine className="mx-auto mb-3 h-5 w-5 animate-spin opacity-50" />
<Icon name="refresh" className="mx-auto mb-3 h-5 w-5 animate-spin opacity-50" />
<p className="typography-meta">{t('settings.skills.catalog.page.loading.skills')}</p>
</div>
) : (
@@ -326,12 +325,12 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
<span>{t('settings.skills.catalog.page.byOwnerPrefix')} <span className="font-medium text-foreground/80">{item.clawdhub.owner}</span></span>
)}
<span className="flex items-center gap-1">
<RiDownloadLine className="h-3 w-3" />
<Icon name="download" className="h-3 w-3" />
{item.clawdhub.downloads?.toLocaleString() ?? 0}
</span>
{(item.clawdhub.stars ?? 0) > 0 && (
<span className="flex items-center gap-1">
<RiStarLine className="h-3 w-3" />
<Icon name="star" className="h-3 w-3" />
{item.clawdhub.stars}
</span>
)}
@@ -11,10 +11,10 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { RiArrowDownSLine, RiArrowRightSLine, RiInformationLine } from '@remixicon/react';
import type { UsageWindows, QuotaProviderId } from '@/types';
import { getAllModelFamilies, getDisplayModelName, sortModelFamilies, groupModelsByFamilyWithGetter } from '@/lib/quota/model-families';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
const formatTime = (timestamp: number | null) => {
@@ -57,7 +57,6 @@ export const UsagePage: React.FC = () => {
void fetchAllQuotas();
}, [loadSettings, fetchAllQuotas]);
React.useEffect(() => {
if (selectedProviderId) {
return;
@@ -194,7 +193,7 @@ export const UsagePage: React.FC = () => {
<span className="typography-ui-label text-foreground">{t('settings.usage.page.options.showInHeader')}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
{t('settings.usage.page.options.showInHeaderTooltip')}
@@ -269,9 +268,9 @@ export const UsagePage: React.FC = () => {
</span>
</div>
{isCollapsed ? (
<RiArrowRightSLine className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
) : (
<RiArrowDownSLine className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
)}
</CollapsibleTrigger>
<CollapsibleContent>
@@ -322,9 +321,9 @@ export const UsagePage: React.FC = () => {
</span>
</div>
{isCollapsed ? (
<RiArrowRightSLine className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
) : (
<RiArrowDownSLine className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
<Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
)}
</CollapsibleTrigger>
<CollapsibleContent>
@@ -5,11 +5,11 @@ import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { QUOTA_PROVIDERS, resolveUsageTone } from '@/lib/quota';
import { useQuotaStore } from '@/stores/useQuotaStore';
import { updateDesktopSettings } from '@/lib/persistence';
import { RiRefreshLine } from '@remixicon/react';
import { useI18n } from '@/lib/i18n';
interface UsageSidebarProps {
@@ -121,7 +121,7 @@ export const UsageSidebar: React.FC<UsageSidebarProps> = ({ onItemSelect }) => {
title={t('settings.usage.sidebar.actions.refreshTitle')}
disabled={isLoading}
>
<RiRefreshLine className={cn('h-3.5 w-3.5', isLoading && 'animate-spin')} />
<Icon name="refresh" className={cn('h-3.5 w-3.5', isLoading && 'animate-spin')} />
</Button>
</div>
</div>