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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user