feat: migrate icon library from Hugeicons to Phosphor + UI polish

Icon library migration:
- Replace @hugeicons/react with @phosphor-icons/react across 60+ components
  (Phosphor supports 6 weight variants: thin/light/regular/bold/fill/duotone)
- Update apps/site (marketing) to use Phosphor icons
- Remove leftover svg.hugeicons CSS rules from globals.css

UI improvements:
- NavRail: selected tab icons use filled weight, unselected use regular
- User chat bubble: dark bg + white text in light mode, light bg + dark text
  in dark mode (new --user-bubble / --user-bubble-foreground CSS variables)
- Fix expand/collapse icons: use CaretRight (triangle) instead of ArrowRight
  in file-tree.tsx and tool-actions-group.tsx

Lint fixes:
- Fix eslint react-hooks/static-components in CodeBlock, ToolCallBlock,
  tool-actions-group — use createElement() instead of PascalCase variable
- Fix react-hooks/rules-of-hooks in MessageItem — move early return after hooks
- Fix react-hooks/set-state-in-effect in GalleryDetail — use ref pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-03-11 11:12:12 +08:00
1 parent 5adeb3548e
commit e5a48ca6af
61 files changed
+606 -752

No files matched your search

+1 -2
View File
@@ -11,8 +11,7 @@
},
"dependencies": {
"@base-ui/react": "^1.2.0",
"@hugeicons/core-free-icons": "^4.0.0",
"@hugeicons/react": "^1.1.6",
"@phosphor-icons/react": "^2.1.7",
"@types/mdx": "^2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+35 -36
View File
@@ -2,23 +2,22 @@
import { useEffect, useRef, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { HugeiconsIcon } from '@hugeicons/react';
import {
Message02Icon,
ZapIcon,
Plug01Icon,
Image01Icon,
Wifi01Icon,
Settings02Icon,
Search01Icon,
PlusSignIcon,
FolderOpenIcon,
ArrowUp02Icon,
ArrowDown01Icon,
CommandLineIcon,
AiUserIcon,
SecurityCheckIcon,
} from '@hugeicons/core-free-icons';
ChatCircle,
Lightning,
Plug,
Image,
WifiHigh,
Gear,
MagnifyingGlass,
Plus,
FolderOpen,
ArrowUp,
CaretDown,
Terminal,
UserCircle,
ShieldCheck,
} from '@phosphor-icons/react';
import { Check, FileText } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -135,10 +134,10 @@ const DEMO_THEME_VARS = {
function BadgeIcon({ type }: { type: string }) {
const cls = 'h-3 w-3';
switch (type) {
case 'skill': return <HugeiconsIcon icon={ZapIcon} className={cls} />;
case 'mcp': return <HugeiconsIcon icon={Plug01Icon} className={cls} />;
case 'bridge': return <HugeiconsIcon icon={Wifi01Icon} className={cls} />;
case 'agent': return <HugeiconsIcon icon={AiUserIcon} className={cls} />;
case 'skill': return <Lightning size={12} />;
case 'mcp': return <Plug size={12} />;
case 'bridge': return <WifiHigh size={12} />;
case 'agent': return <UserCircle size={12} />;
default: return null;
}
}
@@ -172,11 +171,11 @@ function TypingDots() {
/* ------------------------------------------------------------------ */
const NAV_ITEMS = [
{ icon: Message02Icon, label: 'Chats', active: true },
{ icon: ZapIcon, label: 'Skills', active: false },
{ icon: Plug01Icon, label: 'MCP', active: false },
{ icon: Image01Icon, label: 'Gallery', active: false },
{ icon: Wifi01Icon, label: 'Bridge', active: false },
{ icon: ChatCircle, label: 'Chats', active: true },
{ icon: Lightning, label: 'Skills', active: false },
{ icon: Plug, label: 'MCP', active: false },
{ icon: Image, label: 'Gallery', active: false },
{ icon: WifiHigh, label: 'Bridge', active: false },
];
function IconSidebar() {
@@ -194,14 +193,14 @@ function IconSidebar() {
: 'text-muted-foreground/50'
}`}
>
<HugeiconsIcon icon={item.icon} className="h-4 w-4" />
<item.icon size={16} />
</Button>
))}
</nav>
<div className="mt-auto flex flex-col items-center gap-2">
<div className="relative">
<Button variant="ghost" size="icon" className="h-9 w-9 text-muted-foreground/50">
<HugeiconsIcon icon={Settings02Icon} className="h-4 w-4" />
<Gear size={16} />
</Button>
<span className="pointer-events-none absolute right-0.5 top-0.5 h-2 w-2 rounded-full bg-primary" />
</div>
@@ -223,20 +222,20 @@ function SessionSidebar() {
{/* New chat button — real: h-8 outline with PlusSignIcon */}
<div className="px-2.5">
<Button variant="outline" size="sm" className="w-full justify-start gap-1.5 rounded-md pl-2 text-[10px]">
<HugeiconsIcon icon={PlusSignIcon} className="h-3 w-3" />
<Plus size={12} />
New Chat
</Button>
</div>
{/* Search */}
<div className="relative mt-2 px-2.5">
<HugeiconsIcon icon={Search01Icon} className="absolute left-[1.15rem] top-1/2 z-10 h-3 w-3 -translate-y-1/2 text-muted-foreground/40" />
<MagnifyingGlass size={12} className="absolute left-[1.15rem] top-1/2 z-10 -translate-y-1/2 text-muted-foreground/40" />
<Input className="h-7 rounded-md pl-7 !text-[10px]" placeholder="Search..." readOnly />
</div>
{/* Project folder — real: FolderOpenIcon, text-[13px] font-medium */}
<div className="mt-3 flex items-center gap-1.5 px-3 py-1.5">
<HugeiconsIcon icon={FolderOpenIcon} className="h-4 w-4 text-muted-foreground/60" />
<FolderOpen size={16} className="text-muted-foreground/60" />
<span className="text-[13px] font-medium text-sidebar-foreground">My-Project</span>
</div>
@@ -465,21 +464,21 @@ export function ChatDemo() {
<div className="flex items-center px-3.5 py-3">
<span className="flex-1 text-xs text-muted-foreground/50">Message Claude...</span>
<Button size="icon-xs" className="shrink-0 rounded-full bg-primary text-primary-foreground hover:bg-primary/80">
<HugeiconsIcon icon={ArrowUp02Icon} className="h-3.5 w-3.5" />
<ArrowUp size={14} />
</Button>
</div>
{/* PromptInputFooter — real: gap-1, icon buttons h-6 w-6 */}
<div className="flex items-center justify-between gap-1 px-2.5 pb-2.5">
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon-xs" className="text-muted-foreground/50">
<HugeiconsIcon icon={PlusSignIcon} className="h-3.5 w-3.5" />
<Plus size={14} />
</Button>
<Button variant="ghost" size="icon-xs" className="text-muted-foreground/50">
<HugeiconsIcon icon={CommandLineIcon} className="h-3.5 w-3.5" />
<Terminal size={14} />
</Button>
<div className="flex items-center gap-1 rounded-md px-1.5 py-1 font-mono text-[10px] text-muted-foreground/60">
claude-sonnet-4
<HugeiconsIcon icon={ArrowDown01Icon} className="h-2.5 w-2.5" />
<CaretDown size={10} />
</div>
</div>
</div>
@@ -487,11 +486,11 @@ export function ChatDemo() {
{/* ChatComposerActionBar — below input */}
<div className="flex items-center justify-between px-2 pb-1 pt-1.5">
<span className="flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] text-muted-foreground/50">
<HugeiconsIcon icon={AiUserIcon} className="h-3.5 w-3.5" />
<UserCircle size={14} />
Design Agent
</span>
<span className="flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] text-muted-foreground/50">
<HugeiconsIcon icon={SecurityCheckIcon} className="h-3.5 w-3.5" />
<ShieldCheck size={14} />
Default
</span>
</div>
+15 -25
View File
@@ -19,10 +19,9 @@
"@ai-sdk/openai": "^3.0.34",
"@anthropic-ai/claude-agent-sdk": "^0.2.62",
"@google/genai": "^1.43.0",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",
"@larksuiteoapi/node-sdk": "^1.59.0",
"@lobehub/icons": "^4.6.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-use-controllable-state": "^1.2.2",
"@streamdown/cjk": "^1.0.1",
"@streamdown/code": "^1.0.1",
@@ -89,8 +88,7 @@
"version": "0.1.0",
"dependencies": {
"@base-ui/react": "^1.2.0",
"@hugeicons/core-free-icons": "^4.0.0",
"@hugeicons/react": "^1.1.6",
"@phosphor-icons/react": "^2.1.7",
"@types/mdx": "^2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -585,12 +583,6 @@
"node": ">=18"
}
},
"apps/site/node_modules/@hugeicons/core-free-icons": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.0.0.tgz",
"integrity": "sha512-bzfbKumv3ke3ajbe2MyXi9i0I/cdsZ6n/mO9EfIPNSL++pHLqs7nSGRIVUtjF4xrrEyVkfhxssv4Jek8DPA6gA==",
"license": "MIT"
},
"apps/site/node_modules/@next/env": {
"version": "15.3.6",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.6.tgz",
@@ -3910,21 +3902,6 @@
"hono": "^4"
}
},
"node_modules/@hugeicons/core-free-icons": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.1.1.tgz",
"integrity": "sha512-UpS2lUQFi5sKyJSWwM6rO+BnPLvVz1gsyCpPHeZyVuZqi89YH8ksliza4cwaODqKOZyeXmG8juo1ty4QtQofkg==",
"license": "MIT"
},
"node_modules/@hugeicons/react": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@hugeicons/react/-/react-1.1.6.tgz",
"integrity": "sha512-c2LhXJMAW5wN1pC/smBXG0YPqUON6ceR/ZdXHCjEI9KvB+hjtqYjmzIxok5hAQOeXGz0WtORgCQMzqewFKAZwg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.0.0"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -5551,6 +5528,19 @@
"node": ">= 20.0.0"
}
},
"node_modules/@phosphor-icons/react": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
"integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">= 16.8",
"react-dom": ">= 16.8"
}
},
"node_modules/@pierre/diffs": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@pierre/diffs/-/diffs-1.0.11.tgz",
+5 -3
View File
@@ -2,7 +2,10 @@
"name": "codepilot",
"version": "0.31.0",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"workspaces": [
"apps/*",
"packages/*"
],
"author": {
"name": "op7418",
"email": "7418@openclaw.ai"
@@ -40,10 +43,9 @@
"@ai-sdk/openai": "^3.0.34",
"@anthropic-ai/claude-agent-sdk": "^0.2.62",
"@google/genai": "^1.43.0",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",
"@larksuiteoapi/node-sdk": "^1.59.0",
"@lobehub/icons": "^4.6.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-use-controllable-state": "^1.2.2",
"@streamdown/cjk": "^1.0.1",
"@streamdown/code": "^1.0.1",
+2 -3
View File
@@ -1,8 +1,7 @@
"use client";
import { Suspense } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon } from "@hugeicons/core-free-icons";
import { SpinnerGap } from "@phosphor-icons/react";
import { BridgeLayout } from "@/components/bridge/BridgeLayout";
export default function BridgePage() {
@@ -10,7 +9,7 @@ export default function BridgePage() {
<Suspense
fallback={
<div className="flex h-full items-center justify-center">
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-muted-foreground" />
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
</div>
}
>
+3 -4
View File
@@ -4,8 +4,7 @@ import { useEffect, useState, useRef, useCallback, use } from 'react';
import Link from 'next/link';
import type { Message, MessagesResponse, ChatSession } from '@/types';
import { ChatView } from '@/components/chat/ChatView';
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon, PencilEdit01Icon } from "@hugeicons/core-free-icons";
import { SpinnerGap, PencilSimple } from "@phosphor-icons/react";
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { usePanel } from '@/hooks/usePanel';
@@ -156,7 +155,7 @@ export default function ChatSessionPage({ params }: ChatSessionPageProps) {
if (loading) {
return (
<div className="flex h-full items-center justify-center">
<HugeiconsIcon icon={Loading02Icon} className="h-8 w-8 animate-spin text-muted-foreground" />
<SpinnerGap size={32} className="animate-spin text-muted-foreground" />
</div>
);
}
@@ -237,7 +236,7 @@ export default function ChatSessionPage({ params }: ChatSessionPageProps) {
onClick={handleStartEditTitle}
className="opacity-0 group-hover:opacity-100 transition-opacity shrink-0 p-0.5 rounded hover:bg-muted"
>
<HugeiconsIcon icon={PencilEdit01Icon} className="h-3 w-3 text-muted-foreground" />
<PencilSimple size={12} className="text-muted-foreground" />
</button>
</div>
)}
+10 -17
View File
@@ -1,14 +1,7 @@
'use client';
import { useState, useEffect, useCallback, useRef } from 'react';
import { HugeiconsIcon } from '@hugeicons/react';
import {
PaintBrush01Icon,
SortingDownIcon,
FilterIcon,
Loading02Icon,
FavouriteIcon,
} from '@hugeicons/core-free-icons';
import { PaintBrush, SortDescending, Funnel, SpinnerGap, Heart } from '@phosphor-icons/react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { GalleryGrid, type GalleryItem } from '@/components/gallery/GalleryGrid';
@@ -153,10 +146,10 @@ export default function GalleryPage() {
size="sm"
onClick={() => setFavoritesOnly((v) => !v)}
>
<HugeiconsIcon
icon={FavouriteIcon}
className={cn('h-3.5 w-3.5', favoritesOnly && 'text-red-500')}
fill={favoritesOnly ? 'currentColor' : 'none'}
<Heart
size={14}
className={cn(favoritesOnly && 'text-red-500')}
weight={favoritesOnly ? 'fill' : 'regular'}
/>
{t('gallery.favoritesOnly' as TranslationKey)}
</Button>
@@ -167,7 +160,7 @@ export default function GalleryPage() {
size="sm"
onClick={() => setShowFilters(!showFilters)}
>
<HugeiconsIcon icon={FilterIcon} className="h-3.5 w-3.5" />
<Funnel size={14} />
{t('gallery.filters' as TranslationKey)}
</Button>
@@ -177,7 +170,7 @@ export default function GalleryPage() {
size="sm"
onClick={() => setSort((s) => (s === 'newest' ? 'oldest' : 'newest'))}
>
<HugeiconsIcon icon={SortingDownIcon} className="h-3.5 w-3.5" />
<SortDescending size={14} />
{sort === 'newest'
? t('gallery.newestFirst' as TranslationKey)
: t('gallery.oldestFirst' as TranslationKey)}
@@ -229,11 +222,11 @@ export default function GalleryPage() {
<div className="flex-1 overflow-y-auto p-4">
{loading && items.length === 0 ? (
<div className="flex h-full items-center justify-center">
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-muted-foreground" />
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
</div>
) : items.length === 0 ? (
<div className="flex h-full flex-col items-center justify-center gap-3 text-muted-foreground">
<HugeiconsIcon icon={PaintBrush01Icon} className="h-10 w-10 opacity-30" />
<PaintBrush size={40} className="opacity-30" />
<p className="text-sm">{t('gallery.empty' as TranslationKey)}</p>
<p className="text-xs opacity-70">{t('gallery.emptyHint' as TranslationKey)}</p>
</div>
@@ -246,7 +239,7 @@ export default function GalleryPage() {
{/* Sentinel for infinite scroll */}
<div ref={sentinelRef} className="flex justify-center py-4">
{loading && (
<HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin text-muted-foreground" />
<SpinnerGap size={16} className="animate-spin text-muted-foreground" />
)}
</div>
</div>
+4 -10
View File
@@ -79,6 +79,8 @@
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
--sidebar-border: oklch(0.923 0.003 48.717);
--sidebar-ring: oklch(0.546 0.245 262.881);
--user-bubble: oklch(0.22 0.005 250);
--user-bubble-foreground: oklch(0.985 0.001 106.423);
}
.dark {
@@ -113,6 +115,8 @@
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.623 0.214 259.815);
--user-bubble: oklch(0.90 0.003 106);
--user-bubble-foreground: oklch(0.147 0.004 49.25);
}
@layer base {
@@ -197,13 +201,3 @@
font-weight: 600;
}
}
/* Global icon stroke weight — make Hugeicons strokes slightly thicker */
svg.hugeicons,
svg.hugeicons path,
svg.hugeicons line,
svg.hugeicons circle,
svg.hugeicons polyline,
svg.hugeicons rect {
stroke-width: 2;
}
+2 -3
View File
@@ -1,8 +1,7 @@
"use client";
import { Suspense } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon } from "@hugeicons/core-free-icons";
import { SpinnerGap } from "@phosphor-icons/react";
import { SettingsLayout } from "@/components/settings/SettingsLayout";
export default function SettingsPage() {
@@ -10,7 +9,7 @@ export default function SettingsPage() {
<Suspense
fallback={
<div className="flex h-full items-center justify-center">
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-muted-foreground" />
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
</div>
}
>
+13 -14
View File
@@ -8,14 +8,13 @@ import {
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Folder01Icon,
FolderOpenIcon,
File01Icon,
ArrowRight01Icon,
PlusSignIcon,
} from "@hugeicons/core-free-icons";
Folder,
FolderOpen,
File,
CaretRight,
Plus,
} from "@phosphor-icons/react";
import {
createContext,
useCallback,
@@ -155,10 +154,10 @@ export const FileTreeFolder = ({
className="shrink-0 rounded p-0.5 hover:bg-muted"
onClick={(e) => e.stopPropagation()}
>
<HugeiconsIcon
icon={ArrowRight01Icon}
<CaretRight
size={16}
className={cn(
"size-4 text-muted-foreground transition-transform",
"text-muted-foreground transition-transform",
isExpanded && "rotate-90"
)}
/>
@@ -166,9 +165,9 @@ export const FileTreeFolder = ({
</CollapsibleTrigger>
<FileTreeIcon>
{isExpanded ? (
<HugeiconsIcon icon={FolderOpenIcon} className="size-4 text-muted-foreground" />
<FolderOpen size={16} className="text-muted-foreground" />
) : (
<HugeiconsIcon icon={Folder01Icon} className="size-4 text-muted-foreground" />
<Folder size={16} className="text-muted-foreground" />
)}
</FileTreeIcon>
<FileTreeName>{name}</FileTreeName>
@@ -249,7 +248,7 @@ export const FileTreeFile = ({
{children ?? (
<>
<FileTreeIcon>
{icon ?? <HugeiconsIcon icon={File01Icon} className="size-4 text-muted-foreground" />}
{icon ?? <File size={16} className="text-muted-foreground" />}
</FileTreeIcon>
<FileTreeName>{name}</FileTreeName>
{onAdd && (
@@ -259,7 +258,7 @@ export const FileTreeFile = ({
onClick={handleAdd}
title="Add to chat"
>
<HugeiconsIcon icon={PlusSignIcon} className="size-3 text-muted-foreground" />
<Plus size={12} className="text-muted-foreground" />
</button>
)}
</>
+1 -1
View File
@@ -56,7 +56,7 @@ export const MessageContent = ({
<div
className={cn(
"is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-[var(--user-bubble)] group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-[var(--user-bubble-foreground)]",
"group-[.is-assistant]:w-full group-[.is-assistant]:text-foreground",
className
)}
@@ -1,20 +1,19 @@
'use client';
import { useState } from 'react';
import { useState, createElement } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { HugeiconsIcon } from "@hugeicons/react";
import type { IconSvgElement } from "@hugeicons/react";
import type { Icon } from "@phosphor-icons/react";
import {
File01Icon,
FileEditIcon,
CommandLineIcon,
Search01Icon,
Wrench01Icon,
Loading02Icon,
CheckmarkCircle02Icon,
CancelCircleIcon,
ArrowRight01Icon,
} from "@hugeicons/core-free-icons";
File,
NotePencil,
Terminal,
MagnifyingGlass,
Wrench,
SpinnerGap,
CheckCircle,
XCircle,
CaretRight,
} from "@phosphor-icons/react";
import { cn } from '@/lib/utils';
// ---------------------------------------------------------------------------
@@ -61,13 +60,13 @@ function getToolCategory(name: string): ToolCategory {
return 'other';
}
function getToolIcon(category: ToolCategory): IconSvgElement {
function getToolIcon(category: ToolCategory): Icon {
switch (category) {
case 'read': return File01Icon;
case 'write': return FileEditIcon;
case 'bash': return CommandLineIcon;
case 'search': return Search01Icon;
case 'other': return Wrench01Icon;
case 'read': return File;
case 'write': return NotePencil;
case 'bash': return Terminal;
case 'search': return MagnifyingGlass;
case 'other': return Wrench;
}
}
@@ -130,12 +129,12 @@ function StatusDot({ status }: { status: ToolStatus }) {
switch (status) {
case 'running':
return (
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground/50" />
<SpinnerGap size={14} className="shrink-0 animate-spin text-muted-foreground/50" />
);
case 'success':
return <HugeiconsIcon icon={CheckmarkCircle02Icon} className="h-3.5 w-3.5 shrink-0 text-green-500" />;
return <CheckCircle size={14} className="shrink-0 text-green-500" />;
case 'error':
return <HugeiconsIcon icon={CancelCircleIcon} className="h-3.5 w-3.5 shrink-0 text-red-500" />;
return <XCircle size={14} className="shrink-0 text-red-500" />;
}
}
@@ -145,7 +144,7 @@ function StatusDot({ status }: { status: ToolStatus }) {
function ToolActionRow({ tool }: { tool: ToolAction }) {
const category = getToolCategory(tool.name);
const icon = getToolIcon(category);
const toolIcon = getToolIcon(category);
const summary = getToolSummary(tool.name, tool.input, category);
const filePath = getFilePath(tool.input);
const status = getStatus(tool);
@@ -154,7 +153,7 @@ function ToolActionRow({ tool }: { tool: ToolAction }) {
return (
<div className="flex items-center gap-2 px-2 py-1 min-h-[28px] text-xs hover:bg-muted/30 rounded-sm transition-colors">
<HugeiconsIcon icon={icon} className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
{createElement(toolIcon, { size: 14, className: "shrink-0 text-muted-foreground" })}
{label && (
<span className="font-medium text-muted-foreground shrink-0">{label}</span>
@@ -229,10 +228,10 @@ export function ToolActionsGroup({
onClick={handleToggle}
className="flex w-full items-center gap-2 py-1 text-xs rounded-sm hover:bg-muted/30 transition-colors"
>
<HugeiconsIcon
icon={ArrowRight01Icon}
<CaretRight
size={12}
className={cn(
"h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform duration-200",
"shrink-0 text-muted-foreground/60 transition-transform duration-200",
expanded && "rotate-90"
)}
/>
+9 -10
View File
@@ -1,9 +1,8 @@
"use client";
import { useState, useCallback, useSyncExternalStore } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import type { IconSvgElement } from "@hugeicons/react";
import { Wifi01Icon, TelegramIcon, BubbleChatIcon, GameController01Icon, MessageMultiple02Icon } from "@hugeicons/core-free-icons";
import { WifiHigh, TelegramLogo, ChatTeardrop, GameController, ChatsCircle } from "@phosphor-icons/react";
import type { Icon } from "@phosphor-icons/react";
import { cn } from "@/lib/utils";
import { BridgeSection } from "./BridgeSection";
import { TelegramBridgeSection } from "./TelegramBridgeSection";
@@ -18,15 +17,15 @@ type Section = "bridge" | "telegram" | "feishu" | "discord" | "qq";
interface SidebarItem {
id: Section;
label: string;
icon: IconSvgElement;
icon: Icon;
}
const sidebarItems: SidebarItem[] = [
{ id: "bridge", label: "Bridge", icon: Wifi01Icon },
{ id: "telegram", label: "Telegram", icon: TelegramIcon },
{ id: "feishu", label: "Feishu", icon: BubbleChatIcon },
{ id: "discord", label: "Discord", icon: GameController01Icon },
{ id: "qq", label: "QQ", icon: MessageMultiple02Icon },
{ id: "bridge", label: "Bridge", icon: WifiHigh },
{ id: "telegram", label: "Telegram", icon: TelegramLogo },
{ id: "feishu", label: "Feishu", icon: ChatTeardrop },
{ id: "discord", label: "Discord", icon: GameController },
{ id: "qq", label: "QQ", icon: ChatsCircle },
];
function getSectionFromHash(): Section {
@@ -86,7 +85,7 @@ export function BridgeLayout() {
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
)}
>
<HugeiconsIcon icon={item.icon} className="h-4 w-4 shrink-0" />
<item.icon size={16} className="shrink-0" />
{t(bridgeLabelKeys[item.label])}
</button>
))}
+20 -24
View File
@@ -13,8 +13,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon, CheckmarkCircle02Icon, Alert02Icon, TelegramIcon, BubbleChatIcon, GameController01Icon, MessageMultiple02Icon } from "@hugeicons/core-free-icons";
import { SpinnerGap, CheckCircle, Warning, TelegramLogo, ChatTeardrop, GameController, ChatsCircle } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
import type { ProviderModelGroup } from "@/types";
@@ -302,10 +301,7 @@ export function BridgeSection() {
: "bg-muted text-muted-foreground"
}`}
>
<HugeiconsIcon
icon={isRunning ? CheckmarkCircle02Icon : Alert02Icon}
className="h-3.5 w-3.5 shrink-0"
/>
{isRunning ? <CheckCircle size={14} className="shrink-0" /> : <Warning size={14} className="shrink-0" />}
{isRunning
? t("bridge.statusConnected")
: t("bridge.statusDisconnected")}
@@ -318,9 +314,9 @@ export function BridgeSection() {
disabled={stopping}
>
{stopping ? (
<HugeiconsIcon
icon={Loading02Icon}
className="h-3.5 w-3.5 animate-spin mr-1.5"
<SpinnerGap
size={14}
className="animate-spin mr-1.5"
/>
) : null}
{stopping ? t("bridge.stopping") : t("bridge.stop")}
@@ -332,9 +328,9 @@ export function BridgeSection() {
disabled={starting}
>
{starting ? (
<HugeiconsIcon
icon={Loading02Icon}
className="h-3.5 w-3.5 animate-spin mr-1.5"
<SpinnerGap
size={14}
className="animate-spin mr-1.5"
/>
) : null}
{starting ? t("bridge.starting") : t("bridge.start")}
@@ -358,9 +354,9 @@ export function BridgeSection() {
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<HugeiconsIcon
icon={TelegramIcon}
className="h-4 w-4 text-muted-foreground"
<TelegramLogo
size={16}
className="text-muted-foreground"
/>
<div>
<p className="text-sm">{t("bridge.telegramChannel")}</p>
@@ -378,9 +374,9 @@ export function BridgeSection() {
<div className="flex items-center justify-between border-t border-border/30 pt-3">
<div className="flex items-center gap-3">
<HugeiconsIcon
icon={BubbleChatIcon}
className="h-4 w-4 text-muted-foreground"
<ChatTeardrop
size={16}
className="text-muted-foreground"
/>
<div>
<p className="text-sm">{t("bridge.feishuChannel")}</p>
@@ -398,9 +394,9 @@ export function BridgeSection() {
<div className="flex items-center justify-between border-t border-border/30 pt-3">
<div className="flex items-center gap-3">
<HugeiconsIcon
icon={GameController01Icon}
className="h-4 w-4 text-muted-foreground"
<GameController
size={16}
className="text-muted-foreground"
/>
<div>
<p className="text-sm">{t("bridge.discordChannel")}</p>
@@ -418,9 +414,9 @@ export function BridgeSection() {
<div className="flex items-center justify-between border-t border-border/30 pt-3">
<div className="flex items-center gap-3">
<HugeiconsIcon
icon={MessageMultiple02Icon}
className="h-4 w-4 text-muted-foreground"
<ChatsCircle
size={16}
className="text-muted-foreground"
/>
<div>
<p className="text-sm">{t("bridge.qqChannel")}</p>
+5 -13
View File
@@ -11,12 +11,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Loading02Icon,
CheckmarkCircle02Icon,
Alert02Icon,
} from "@hugeicons/core-free-icons";
import { SpinnerGap, CheckCircle, Warning } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
interface DiscordBridgeSettings {
@@ -198,9 +193,9 @@ export function DiscordBridgeSection() {
disabled={verifying || !botToken}
>
{verifying ? (
<HugeiconsIcon
icon={Loading02Icon}
className="h-3.5 w-3.5 animate-spin mr-1.5"
<SpinnerGap
size={14}
className="animate-spin mr-1.5"
/>
) : null}
{t("discord.verify")}
@@ -215,10 +210,7 @@ export function DiscordBridgeSection() {
: "bg-red-500/10 text-red-600 dark:text-red-400"
}`}
>
<HugeiconsIcon
icon={verifyResult.ok ? CheckmarkCircle02Icon : Alert02Icon}
className="h-4 w-4 shrink-0"
/>
{verifyResult.ok ? <CheckCircle size={16} className="shrink-0" /> : <Warning size={16} className="shrink-0" />}
{verifyResult.message}
</div>
)}
+5 -13
View File
@@ -11,12 +11,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Loading02Icon,
CheckmarkCircle02Icon,
Alert02Icon,
} from "@hugeicons/core-free-icons";
import { SpinnerGap, CheckCircle, Warning } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
interface FeishuBridgeSettings {
@@ -233,9 +228,9 @@ export function FeishuBridgeSection() {
disabled={verifying || !appId}
>
{verifying ? (
<HugeiconsIcon
icon={Loading02Icon}
className="h-3.5 w-3.5 animate-spin mr-1.5"
<SpinnerGap
size={14}
className="animate-spin mr-1.5"
/>
) : null}
{t("feishu.verify")}
@@ -250,10 +245,7 @@ export function FeishuBridgeSection() {
: "bg-red-500/10 text-red-600 dark:text-red-400"
}`}
>
<HugeiconsIcon
icon={verifyResult.ok ? CheckmarkCircle02Icon : Alert02Icon}
className="h-4 w-4 shrink-0"
/>
{verifyResult.ok ? <CheckCircle size={16} className="shrink-0" /> : <Warning size={16} className="shrink-0" />}
{verifyResult.message}
</div>
)}
+5 -13
View File
@@ -4,12 +4,7 @@ import { useState, useCallback, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Loading02Icon,
CheckmarkCircle02Icon,
Alert02Icon,
} from "@hugeicons/core-free-icons";
import { SpinnerGap, CheckCircle, Warning } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
interface QqBridgeSettings {
@@ -195,9 +190,9 @@ export function QqBridgeSection() {
disabled={verifying || !appId}
>
{verifying ? (
<HugeiconsIcon
icon={Loading02Icon}
className="h-3.5 w-3.5 animate-spin mr-1.5"
<SpinnerGap
size={14}
className="animate-spin mr-1.5"
/>
) : null}
{t("qq.verify")}
@@ -212,10 +207,7 @@ export function QqBridgeSection() {
: "bg-red-500/10 text-red-600 dark:text-red-400"
}`}
>
<HugeiconsIcon
icon={verifyResult.ok ? CheckmarkCircle02Icon : Alert02Icon}
className="h-4 w-4 shrink-0"
/>
{verifyResult.ok ? <CheckCircle size={16} className="shrink-0" /> : <Warning size={16} className="shrink-0" />}
{verifyResult.message}
</div>
)}
@@ -3,8 +3,7 @@
import { useState, useCallback, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon, CheckmarkCircle02Icon, Alert02Icon } from "@hugeicons/core-free-icons";
import { SpinnerGap, CheckCircle, Warning } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
interface TelegramBridgeSettings {
@@ -214,9 +213,9 @@ export function TelegramBridgeSection() {
className="shrink-0"
>
{detecting ? (
<HugeiconsIcon
icon={Loading02Icon}
className="h-3.5 w-3.5 animate-spin mr-1.5"
<SpinnerGap
size={14}
className="animate-spin mr-1.5"
/>
) : null}
{t("telegram.detectChatId")}
@@ -243,9 +242,9 @@ export function TelegramBridgeSection() {
disabled={verifying || !botToken}
>
{verifying ? (
<HugeiconsIcon
icon={Loading02Icon}
className="h-3.5 w-3.5 animate-spin mr-1.5"
<SpinnerGap
size={14}
className="animate-spin mr-1.5"
/>
) : null}
{t("telegram.verify")}
@@ -260,10 +259,7 @@ export function TelegramBridgeSection() {
: "bg-red-500/10 text-red-600 dark:text-red-400"
}`}
>
<HugeiconsIcon
icon={verifyResult.ok ? CheckmarkCircle02Icon : Alert02Icon}
className="h-4 w-4 shrink-0"
/>
{verifyResult.ok ? <CheckCircle size={16} className="shrink-0" /> : <Warning size={16} className="shrink-0" />}
{verifyResult.message}
</div>
)}
+9 -12
View File
@@ -18,8 +18,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { HugeiconsIcon } from '@hugeicons/react';
import { LockIcon, SquareUnlock02Icon, ArrowDown01Icon } from '@hugeicons/core-free-icons';
import { Lock, LockOpen, CaretDown } from '@phosphor-icons/react';
interface ChatPermissionSelectorProps {
sessionId?: string;
@@ -79,26 +78,24 @@ export function ChatPermissionSelector({
: 'bg-muted text-muted-foreground hover:bg-muted/80'
}`}
>
<HugeiconsIcon
icon={isFullAccess ? SquareUnlock02Icon : LockIcon}
className={`h-3.5 w-3.5 ${isFullAccess ? 'text-red-500' : ''}`}
/>
{isFullAccess ? (
<LockOpen size={14} className="text-red-500" />
) : (
<Lock size={14} />
)}
<span>
{isFullAccess ? t('permission.fullAccess') : t('permission.default')}
</span>
<HugeiconsIcon
icon={ArrowDown01Icon}
className="h-2.5 w-2.5 opacity-60"
/>
<CaretDown size={10} className="opacity-60" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="min-w-[140px]">
<DropdownMenuItem onClick={() => handleSelect('default')}>
<HugeiconsIcon icon={LockIcon} className="h-3.5 w-3.5" />
<Lock size={14} />
<span>{t('permission.default')}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleSelect('full_access')}>
<HugeiconsIcon icon={SquareUnlock02Icon} className="h-3.5 w-3.5 text-red-500" />
<LockOpen size={14} className="text-red-500" />
<span>{t('permission.fullAccess')}</span>
</DropdownMenuItem>
</DropdownMenuContent>
+29 -30
View File
@@ -1,24 +1,23 @@
'use client';
import { useState, useMemo, useRef } from 'react';
import { useState, useMemo, useRef, createElement } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { useThemeFamily } from '@/lib/theme/context';
import { resolveCodeTheme, resolvePrismStyle } from '@/lib/theme/code-themes';
import { useTheme } from 'next-themes';
import { HugeiconsIcon } from "@hugeicons/react";
import type { IconSvgElement } from "@hugeicons/react";
import type { Icon } from "@phosphor-icons/react";
import {
Copy01Icon,
Tick01Icon,
ArrowDown01Icon,
ArrowUp01Icon,
SourceCodeIcon,
CommandLineIcon,
CodeIcon,
File01Icon,
HashtagIcon,
} from "@hugeicons/core-free-icons";
Copy,
Check,
CaretDown,
CaretUp,
FileCode,
Terminal,
Code,
File,
Hash,
} from "@phosphor-icons/react";
import { cn } from '@/lib/utils';
const COLLAPSE_THRESHOLD = 20;
@@ -26,14 +25,14 @@ const VISIBLE_LINES = 10;
const TERMINAL_LANGUAGES = new Set(['bash', 'sh', 'shell', 'terminal', 'zsh', 'console']);
function getLanguageIcon(language: string): IconSvgElement {
function getLanguageIcon(language: string): Icon {
const lower = language.toLowerCase();
if (TERMINAL_LANGUAGES.has(lower)) return CommandLineIcon;
if (['typescript', 'tsx', 'javascript', 'jsx'].includes(lower)) return CodeIcon;
if (['json', 'yaml', 'yml', 'toml', 'xml'].includes(lower)) return CodeIcon;
if (['python', 'ruby', 'go', 'rust', 'java', 'c', 'cpp'].includes(lower)) return HashtagIcon;
if (['css', 'scss', 'html'].includes(lower)) return File01Icon;
return SourceCodeIcon;
if (TERMINAL_LANGUAGES.has(lower)) return Terminal;
if (['typescript', 'tsx', 'javascript', 'jsx'].includes(lower)) return Code;
if (['json', 'yaml', 'yml', 'toml', 'xml'].includes(lower)) return Code;
if (['python', 'ruby', 'go', 'rust', 'java', 'c', 'cpp'].includes(lower)) return Hash;
if (['css', 'scss', 'html'].includes(lower)) return File;
return FileCode;
}
interface CodeBlockProps {
@@ -126,7 +125,7 @@ export function CodeBlock({
}
};
const languageIconData = getLanguageIcon(language);
const languageIcon = getLanguageIcon(language);
const theme = useCodeBlockTheme(isTerminal, isDark);
@@ -145,10 +144,10 @@ export function CodeBlock({
: "bg-muted text-muted-foreground"
)}>
<div className="flex items-center gap-2 min-w-0">
<HugeiconsIcon icon={languageIconData} className={cn(
"h-3.5 w-3.5 shrink-0",
{createElement(languageIcon, { size: 14, className: cn(
"shrink-0",
isTerminal ? "text-green-400" : "text-muted-foreground",
)} />
) })}
{filename && (
<span className={cn(
"truncate font-medium",
@@ -176,12 +175,12 @@ export function CodeBlock({
>
{copied ? (
<>
<HugeiconsIcon icon={Tick01Icon} className="h-3 w-3" />
<Check size={12} />
<span>Copied</span>
</>
) : (
<>
<HugeiconsIcon icon={Copy01Icon} className="h-3 w-3" />
<Copy size={12} />
<span>Copy</span>
</>
)}
@@ -198,12 +197,12 @@ export function CodeBlock({
>
{copiedMarkdown ? (
<>
<HugeiconsIcon icon={Tick01Icon} className="h-3 w-3" />
<Check size={12} />
<span>Copied</span>
</>
) : (
<>
<HugeiconsIcon icon={SourceCodeIcon} className="h-3 w-3" />
<FileCode size={12} />
<span>Markdown</span>
</>
)}
@@ -269,12 +268,12 @@ export function CodeBlock({
>
{expanded ? (
<>
<HugeiconsIcon icon={ArrowUp01Icon} className="h-3 w-3" />
<CaretUp size={12} />
<span>Collapse</span>
</>
) : (
<>
<HugeiconsIcon icon={ArrowDown01Icon} className="h-3 w-3" />
<CaretDown size={12} />
<span>Expand all {totalLines} lines</span>
</>
)}
+2 -3
View File
@@ -1,7 +1,6 @@
'use client';
import { HugeiconsIcon } from "@hugeicons/react";
import { File01Icon } from "@hugeicons/core-free-icons";
import { File } from "@phosphor-icons/react";
import { formatFileSize } from '@/types';
interface FileCardProps {
@@ -12,7 +11,7 @@ interface FileCardProps {
export function FileCard({ name, size }: FileCardProps) {
return (
<div className="rounded-lg border bg-muted/50 p-3 flex items-center gap-3">
<HugeiconsIcon icon={File01Icon} className="h-5 w-5 text-muted-foreground shrink-0" />
<File size={20} className="text-muted-foreground shrink-0" />
<div className="min-w-0">
<div className="text-sm font-medium truncate">{name}</div>
<div className="text-xs text-muted-foreground">{formatFileSize(size)}</div>
+5 -6
View File
@@ -1,8 +1,7 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
import { HugeiconsIcon } from "@hugeicons/react";
import { Folder01Icon, FolderOpenIcon, ArrowRight01Icon, ArrowUp01Icon } from "@hugeicons/core-free-icons";
import { Folder, FolderOpen, ArrowRight, CaretUp } from "@phosphor-icons/react";
import { Button } from '@/components/ui/button';
import {
Dialog,
@@ -128,7 +127,7 @@ export function FolderPicker({ open, onOpenChange, onSelect, initialPath }: Fold
disabled={!parentDir}
className="shrink-0"
>
<HugeiconsIcon icon={ArrowUp01Icon} className="h-4 w-4" />
<CaretUp size={16} />
</Button>
{drives.length > 0 && (
<DropdownMenu>
@@ -178,9 +177,9 @@ export function FolderPicker({ open, onOpenChange, onSelect, initialPath }: Fold
className="flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-sm hover:bg-accent transition-colors text-left"
onClick={() => handleNavigate(dir.path)}
>
<HugeiconsIcon icon={Folder01Icon} className="h-4 w-4 shrink-0 text-primary" />
<Folder size={16} className="shrink-0 text-primary" />
<span className="truncate">{dir.name}</span>
<HugeiconsIcon icon={ArrowRight01Icon} className="ml-auto h-3 w-3 shrink-0 text-muted-foreground" />
<ArrowRight size={12} className="ml-auto shrink-0 text-muted-foreground" />
</button>
))}
</div>
@@ -193,7 +192,7 @@ export function FolderPicker({ open, onOpenChange, onSelect, initialPath }: Fold
{t('folderPicker.cancel')}
</Button>
<Button onClick={handleSelect} className="gap-2">
<HugeiconsIcon icon={FolderOpenIcon} className="h-4 w-4" />
<FolderOpen size={16} />
{t('folderPicker.select')}
</Button>
</DialogFooter>
+4 -5
View File
@@ -1,8 +1,7 @@
'use client';
import { useState, useCallback } from 'react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Download04Icon, RepeatIcon, PaintBrush01Icon } from '@hugeicons/core-free-icons';
import { DownloadSimple, ArrowsCounterClockwise, PaintBrush } from '@phosphor-icons/react';
import { cn } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@@ -142,7 +141,7 @@ export function ImageGenCard({
<div className="flex items-center gap-1.5 flex-wrap">
{model && (
<Badge variant="secondary" className="text-[10px] gap-1">
<HugeiconsIcon icon={PaintBrush01Icon} className="h-3 w-3" />
<PaintBrush size={12} />
{model}
</Badge>
)}
@@ -165,7 +164,7 @@ export function ImageGenCard({
onClick={() => handleDownload(images[0], 0)}
title={t('imageGen.download' as TranslationKey)}
>
<HugeiconsIcon icon={Download04Icon} className="h-3.5 w-3.5" />
<DownloadSimple size={14} />
</Button>
{onRegenerate && (
<Button
@@ -174,7 +173,7 @@ export function ImageGenCard({
onClick={onRegenerate}
title={t('imageGen.regenerate' as TranslationKey)}
>
<HugeiconsIcon icon={RepeatIcon} className="h-3.5 w-3.5" />
<ArrowsCounterClockwise size={14} />
</Button>
)}
</div>
+3 -4
View File
@@ -1,8 +1,7 @@
'use client';
import { useState, useCallback } from 'react';
import { HugeiconsIcon } from "@hugeicons/react";
import { ArrowLeft01Icon, ArrowRight01Icon } from "@hugeicons/core-free-icons";
import { ArrowLeft, ArrowRight } from "@phosphor-icons/react";
import {
Dialog,
DialogContent,
@@ -66,14 +65,14 @@ export function ImageLightbox({ images, initialIndex, open, onOpenChange }: Imag
onClick={goToPrev}
className="absolute left-2 top-1/2 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white hover:bg-black/70 transition"
>
<HugeiconsIcon icon={ArrowLeft01Icon} className="h-6 w-6" />
<ArrowLeft size={24} />
</button>
<button
type="button"
onClick={goToNext}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white hover:bg-black/70 transition"
>
<HugeiconsIcon icon={ArrowRight01Icon} className="h-6 w-6" />
<ArrowRight size={24} />
</button>
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 text-white/70 text-sm">
{currentIndex + 1} / {images.length}
+41 -41
View File
@@ -1,25 +1,25 @@
'use client';
import { useRef, useState, useCallback, useEffect, type KeyboardEvent, type FormEvent } from 'react';
import { HugeiconsIcon } from "@hugeicons/react";
import {
AtIcon,
HelpCircleIcon,
ArrowDown01Icon,
ArrowUp02Icon,
CommandLineIcon,
PlusSignIcon,
Cancel01Icon,
Delete02Icon,
Coins01Icon,
FileZipIcon,
Stethoscope02Icon,
FileEditIcon,
SearchList01Icon,
BrainIcon,
GlobalIcon,
StopIcon,
} from "@hugeicons/core-free-icons";
At,
Question,
CaretDown,
ArrowUp,
Terminal,
Plus,
X,
Trash,
Coins,
FileZip,
Stethoscope,
NotePencil,
ListMagnifyingGlass,
Brain,
GlobeSimple,
Stop,
} from "@phosphor-icons/react";
import type { Icon } from "@phosphor-icons/react";
import { cn } from '@/lib/utils';
import { useTranslation } from '@/hooks/useTranslation';
import type { TranslationKey } from '@/i18n';
@@ -115,7 +115,7 @@ interface PopoverItem {
installedSource?: "agents" | "claude";
source?: "global" | "project" | "plugin" | "installed" | "sdk";
kind?: SkillKind;
icon?: typeof CommandLineIcon;
icon?: Icon;
}
interface CommandBadge {
@@ -137,15 +137,15 @@ const COMMAND_PROMPTS: Record<string, string> = {
};
const BUILT_IN_COMMANDS: PopoverItem[] = [
{ label: 'help', value: '/help', description: 'Show available commands and tips', descriptionKey: 'messageInput.helpDesc', builtIn: true, immediate: true, icon: HelpCircleIcon },
{ label: 'clear', value: '/clear', description: 'Clear conversation history', descriptionKey: 'messageInput.clearDesc', builtIn: true, immediate: true, icon: Delete02Icon },
{ label: 'cost', value: '/cost', description: 'Show token usage statistics', descriptionKey: 'messageInput.costDesc', builtIn: true, immediate: true, icon: Coins01Icon },
{ label: 'compact', value: '/compact', description: 'Compress conversation context', descriptionKey: 'messageInput.compactDesc', builtIn: true, kind: 'sdk_command', icon: FileZipIcon },
{ label: 'doctor', value: '/doctor', description: 'Diagnose project health', descriptionKey: 'messageInput.doctorDesc', builtIn: true, kind: 'codepilot_command', icon: Stethoscope02Icon },
{ label: 'init', value: '/init', description: 'Initialize CLAUDE.md for project', descriptionKey: 'messageInput.initDesc', builtIn: true, kind: 'sdk_command', icon: FileEditIcon },
{ label: 'review', value: '/review', description: 'Review code quality', descriptionKey: 'messageInput.reviewDesc', builtIn: true, kind: 'sdk_command', icon: SearchList01Icon },
{ label: 'terminal-setup', value: '/terminal-setup', description: 'Configure terminal settings', descriptionKey: 'messageInput.terminalSetupDesc', builtIn: true, kind: 'codepilot_command', icon: CommandLineIcon },
{ label: 'memory', value: '/memory', description: 'Edit project memory file', descriptionKey: 'messageInput.memoryDesc', builtIn: true, kind: 'codepilot_command', icon: BrainIcon },
{ label: 'help', value: '/help', description: 'Show available commands and tips', descriptionKey: 'messageInput.helpDesc', builtIn: true, immediate: true, icon: Question },
{ label: 'clear', value: '/clear', description: 'Clear conversation history', descriptionKey: 'messageInput.clearDesc', builtIn: true, immediate: true, icon: Trash },
{ label: 'cost', value: '/cost', description: 'Show token usage statistics', descriptionKey: 'messageInput.costDesc', builtIn: true, immediate: true, icon: Coins },
{ label: 'compact', value: '/compact', description: 'Compress conversation context', descriptionKey: 'messageInput.compactDesc', builtIn: true, kind: 'sdk_command', icon: FileZip },
{ label: 'doctor', value: '/doctor', description: 'Diagnose project health', descriptionKey: 'messageInput.doctorDesc', builtIn: true, kind: 'codepilot_command', icon: Stethoscope },
{ label: 'init', value: '/init', description: 'Initialize CLAUDE.md for project', descriptionKey: 'messageInput.initDesc', builtIn: true, kind: 'sdk_command', icon: NotePencil },
{ label: 'review', value: '/review', description: 'Review code quality', descriptionKey: 'messageInput.reviewDesc', builtIn: true, kind: 'sdk_command', icon: ListMagnifyingGlass },
{ label: 'terminal-setup', value: '/terminal-setup', description: 'Configure terminal settings', descriptionKey: 'messageInput.terminalSetupDesc', builtIn: true, kind: 'codepilot_command', icon: Terminal },
{ label: 'memory', value: '/memory', description: 'Edit project memory file', descriptionKey: 'messageInput.memoryDesc', builtIn: true, kind: 'codepilot_command', icon: Brain },
];
interface ModeOption {
@@ -216,9 +216,9 @@ function FileAwareSubmitButton({
className="rounded-full"
>
{isStreaming ? (
<HugeiconsIcon icon={StopIcon} className="size-4" />
<Stop size={16} />
) : (
<HugeiconsIcon icon={ArrowUp02Icon} className="h-4 w-4" strokeWidth={2} />
<ArrowUp size={16} />
)}
</PromptInputSubmit>
);
@@ -236,7 +236,7 @@ function AttachFileButton() {
onClick={() => attachments.openFileDialog()}
tooltip={t('messageInput.attachFiles')}
>
<HugeiconsIcon icon={PlusSignIcon} className="h-3.5 w-3.5" />
<Plus size={14} />
</PromptInputButton>
);
}
@@ -356,7 +356,7 @@ function FileAttachmentsCapsules() {
onClick={() => attachments.remove(file.id)}
className="ml-0.5 rounded-full p-0.5 hover:bg-emerald-500/20 transition-colors"
>
<HugeiconsIcon icon={Cancel01Icon} className="h-3 w-3" />
<X size={12} />
</button>
</span>
);
@@ -1053,17 +1053,17 @@ export function MessageInput({
onMouseEnter={() => setSelectedIndex(idx)}
>
{popoverMode === 'file' ? (
<HugeiconsIcon icon={AtIcon} className="h-4 w-4 shrink-0 text-muted-foreground" />
<At size={16} className="shrink-0 text-muted-foreground" />
) : item.builtIn && item.icon ? (
<HugeiconsIcon icon={item.icon} className="h-4 w-4 shrink-0 text-muted-foreground" />
(() => { const ItemIcon = item.icon; return <ItemIcon size={16} className="shrink-0 text-muted-foreground" />; })()
) : item.kind === 'agent_skill' ? (
<HugeiconsIcon icon={BrainIcon} className="h-4 w-4 shrink-0 text-muted-foreground" />
<Brain size={16} className="shrink-0 text-muted-foreground" />
) : item.kind === 'slash_command' ? (
<HugeiconsIcon icon={FileEditIcon} className="h-4 w-4 shrink-0 text-muted-foreground" />
<NotePencil size={16} className="shrink-0 text-muted-foreground" />
) : !item.builtIn ? (
<HugeiconsIcon icon={GlobalIcon} className="h-4 w-4 shrink-0 text-muted-foreground" />
<GlobeSimple size={16} className="shrink-0 text-muted-foreground" />
) : (
<HugeiconsIcon icon={CommandLineIcon} className="h-4 w-4 shrink-0 text-muted-foreground" />
<Terminal size={16} className="shrink-0 text-muted-foreground" />
)}
<span className="font-mono text-xs truncate">{item.label}</span>
{(item.descriptionKey || item.description) && (
@@ -1170,7 +1170,7 @@ export function MessageInput({
{(aiSuggestions.length > 0 || aiSearchLoading) && (
<>
<div className="px-3 py-1.5 text-xs font-medium text-muted-foreground flex items-center gap-1.5">
<HugeiconsIcon icon={BrainIcon} className="h-3.5 w-3.5" />
<Brain size={14} />
{t('messageInput.aiSuggested')}
{aiSearchLoading && (
<span className="inline-block h-3 w-3 animate-spin rounded-full border-2 border-current border-t-transparent" />
@@ -1243,7 +1243,7 @@ export function MessageInput({
onClick={() => setModelMenuOpen((prev) => !prev)}
>
<span className="text-xs font-mono">{currentModelOption.label}</span>
<HugeiconsIcon icon={ArrowDown01Icon} className={cn("h-2.5 w-2.5 transition-transform duration-200", modelMenuOpen && "rotate-180")} />
<CaretDown size={10} className={cn("transition-transform duration-200", modelMenuOpen && "rotate-180")} />
</PromptInputButton>
{modelMenuOpen && (
@@ -1295,7 +1295,7 @@ export function MessageInput({
onClick={() => setEffortMenuOpen((prev) => !prev)}
>
<span className="text-xs">{t(`messageInput.effort.${selectedEffort}` as TranslationKey)}</span>
<HugeiconsIcon icon={ArrowDown01Icon} className={cn("h-2.5 w-2.5 transition-transform duration-200", effortMenuOpen && "rotate-180")} />
<CaretDown size={10} className={cn("transition-transform duration-200", effortMenuOpen && "rotate-180")} />
</PromptInputButton>
{effortMenuOpen && (
+10 -11
View File
@@ -8,8 +8,7 @@ import {
MessageResponse,
} from '@/components/ai-elements/message';
import { ToolActionsGroup } from '@/components/ai-elements/tool-actions-group';
import { HugeiconsIcon } from "@hugeicons/react";
import { Copy01Icon, Tick01Icon, ArrowDown01Icon, ArrowUp01Icon } from "@hugeicons/core-free-icons";
import { Copy, Check, CaretDown, CaretUp } from "@phosphor-icons/react";
import { FileAttachmentDisplay } from './FileAttachmentDisplay';
import { ImageGenConfirmation } from './ImageGenConfirmation';
import { ImageGenCard } from './ImageGenCard';
@@ -288,9 +287,9 @@ function CopyButton({ text }: { text: string }) {
title="Copy"
>
{copied ? (
<HugeiconsIcon icon={Tick01Icon} className="h-3 w-3 text-green-500" />
<Check size={12} className="text-green-500" />
) : (
<HugeiconsIcon icon={Copy01Icon} className="h-3 w-3" />
<Copy size={12} />
)}
</button>
);
@@ -340,11 +339,6 @@ export const MessageItem = memo(function MessageItem({ message }: MessageItemPro
return { files: [] as FileAttachment[], displayText: text };
}, [text, isUser]);
// Hide image-gen system notices — they exist in DB for Claude's context but shouldn't render
if (isUser && message.content.startsWith('[__IMAGE_GEN_NOTICE__')) {
return null;
}
useEffect(() => {
if (isUser && contentRef.current) {
setIsOverflowing(contentRef.current.scrollHeight > COLLAPSE_HEIGHT);
@@ -361,6 +355,11 @@ export const MessageItem = memo(function MessageItem({ message }: MessageItemPro
}
}, [message.token_usage]);
// Hide image-gen system notices — they exist in DB for Claude's context but shouldn't render
if (isUser && message.content.startsWith('[__IMAGE_GEN_NOTICE__')) {
return null;
}
const timestamp = parseDBDate(message.created_at).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
@@ -413,12 +412,12 @@ export const MessageItem = memo(function MessageItem({ message }: MessageItemPro
>
{isExpanded ? (
<>
<HugeiconsIcon icon={ArrowUp01Icon} className="h-3 w-3" />
<CaretUp size={12} />
<span>收起</span>
</>
) : (
<>
<HugeiconsIcon icon={ArrowDown01Icon} className="h-3 w-3" />
<CaretDown size={12} />
<span>展开</span>
</>
)}
+30 -31
View File
@@ -1,20 +1,19 @@
'use client';
import { useState } from 'react';
import { HugeiconsIcon } from "@hugeicons/react";
import type { IconSvgElement } from "@hugeicons/react";
import { useState, createElement } from 'react';
import type { Icon } from "@phosphor-icons/react";
import {
File01Icon,
FileEditIcon,
CommandLineIcon,
Search01Icon,
Wrench01Icon,
ArrowDown01Icon,
ArrowRight01Icon,
Loading02Icon,
CheckmarkCircle02Icon,
CancelCircleIcon,
} from "@hugeicons/core-free-icons";
File,
NotePencil,
Terminal,
MagnifyingGlass,
Wrench,
CaretDown,
CaretRight,
SpinnerGap,
CheckCircle,
XCircle,
} from "@phosphor-icons/react";
import { cn } from '@/lib/utils';
import { CodeBlock } from './CodeBlock';
@@ -44,13 +43,13 @@ function getToolCategory(name: string): 'read' | 'write' | 'bash' | 'search' | '
return 'other';
}
function getToolIcon(category: ReturnType<typeof getToolCategory>): IconSvgElement {
function getToolIcon(category: ReturnType<typeof getToolCategory>): Icon {
switch (category) {
case 'read': return File01Icon;
case 'write': return FileEditIcon;
case 'bash': return CommandLineIcon;
case 'search': return Search01Icon;
case 'other': return Wrench01Icon;
case 'read': return File;
case 'write': return NotePencil;
case 'bash': return Terminal;
case 'search': return MagnifyingGlass;
case 'other': return Wrench;
}
}
@@ -101,13 +100,13 @@ function StatusIndicator({ status }: { status: ToolStatus }) {
return (
<span className="relative flex h-3.5 w-3.5 items-center justify-center">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-30" />
<HugeiconsIcon icon={Loading02Icon} className="relative h-3.5 w-3.5 animate-spin text-primary" />
<SpinnerGap size={14} className="relative animate-spin text-primary" />
</span>
);
case 'success':
return <HugeiconsIcon icon={CheckmarkCircle02Icon} className="h-3.5 w-3.5 text-green-500" />;
return <CheckCircle size={14} className="text-green-500" />;
case 'error':
return <HugeiconsIcon icon={CancelCircleIcon} className="h-3.5 w-3.5 text-red-500" />;
return <XCircle size={14} className="text-red-500" />;
}
}
@@ -152,7 +151,7 @@ export function ToolCallBlock({
}: ToolCallBlockProps) {
const [expanded, setExpanded] = useState(false);
const category = getToolCategory(name);
const toolIconData = getToolIcon(category);
const toolIcon = getToolIcon(category);
const summary = getToolSummary(name, input, category);
const filePath = getFilePath(input);
@@ -173,7 +172,7 @@ export function ToolCallBlock({
)}
{!result && status === 'running' && (
<div className="flex items-center gap-2 py-2 text-xs text-muted-foreground">
<HugeiconsIcon icon={Loading02Icon} className="h-3 w-3 animate-spin" />
<SpinnerGap size={12} className="animate-spin" />
Reading file...
</div>
)}
@@ -224,7 +223,7 @@ export function ToolCallBlock({
)}
{!result && status === 'running' && (
<div className="flex items-center gap-2 py-2 text-xs text-muted-foreground">
<HugeiconsIcon icon={Loading02Icon} className="h-3 w-3 animate-spin" />
<SpinnerGap size={12} className="animate-spin" />
Executing...
</div>
)}
@@ -303,18 +302,18 @@ export function ToolCallBlock({
)}
>
{expanded ? (
<HugeiconsIcon icon={ArrowDown01Icon} className="h-3 w-3 shrink-0 text-muted-foreground" />
<CaretDown size={12} className="shrink-0 text-muted-foreground" />
) : (
<HugeiconsIcon icon={ArrowRight01Icon} className="h-3 w-3 shrink-0 text-muted-foreground" />
<CaretRight size={12} className="shrink-0 text-muted-foreground" />
)}
<HugeiconsIcon icon={toolIconData} className={cn(
"h-3.5 w-3.5 shrink-0",
{createElement(toolIcon, { size: 14, className: cn(
"shrink-0",
category === 'read' && "text-primary",
category === 'write' && "text-amber-500",
category === 'bash' && "text-green-500",
category === 'search' && "text-primary",
category === 'other' && "text-muted-foreground",
)} />
) })}
<span className="font-mono text-xs truncate flex-1 text-foreground/80">{summary}</span>
<div className="flex items-center gap-2 shrink-0 ml-2">
{duration !== undefined && (
+23 -23
View File
@@ -1,17 +1,16 @@
'use client';
import { useState, useCallback, useEffect } from 'react';
import { useState, useCallback, useRef } from 'react';
import { useRouter } from 'next/navigation';
import { HugeiconsIcon } from '@hugeicons/react';
import {
Download04Icon,
Delete02Icon,
PaintBrush01Icon,
Message02Icon,
ArrowLeft01Icon,
ArrowRight01Icon,
FavouriteIcon,
} from '@hugeicons/core-free-icons';
DownloadSimple,
Trash,
PaintBrush,
ChatCircle,
ArrowLeft,
ArrowRight,
Heart,
} from '@phosphor-icons/react';
import { cn, parseDBDate } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@@ -69,11 +68,13 @@ export function GalleryDetail({
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [confirmDelete, setConfirmDelete] = useState(false);
// Reset image index when item changes
useEffect(() => {
// Reset state when item changes (React-recommended ref pattern instead of useEffect+setState)
const prevItemId = useRef(item?.id);
if (item?.id !== prevItemId.current) {
prevItemId.current = item?.id;
setCurrentImageIndex(0);
setConfirmDelete(false);
}, [item?.id]);
}
const handleDownload = useCallback(async () => {
if (!item) return;
@@ -144,14 +145,14 @@ export function GalleryDetail({
onClick={() => setCurrentImageIndex((i) => (i > 0 ? i - 1 : item.images.length - 1))}
className="absolute left-2 top-1/2 -translate-y-1/2 rounded-full bg-black/50 p-1.5 text-white hover:bg-black/70 transition z-10"
>
<HugeiconsIcon icon={ArrowLeft01Icon} className="h-5 w-5" />
<ArrowLeft size={20} />
</button>
<button
type="button"
onClick={() => setCurrentImageIndex((i) => (i < item.images.length - 1 ? i + 1 : 0))}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-full bg-black/50 p-1.5 text-white hover:bg-black/70 transition z-10"
>
<HugeiconsIcon icon={ArrowRight01Icon} className="h-5 w-5" />
<ArrowRight size={20} />
</button>
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 rounded-full bg-black/50 px-2 py-0.5 text-xs text-white z-10">
{currentImageIndex + 1} / {item.images.length}
@@ -173,10 +174,9 @@ export function GalleryDetail({
: 'text-muted-foreground hover:text-red-500',
)}
>
<HugeiconsIcon
icon={FavouriteIcon}
className="h-5 w-5"
fill={item.favorited ? 'currentColor' : 'none'}
<Heart
size={20}
weight={item.favorited ? 'fill' : 'regular'}
/>
{item.favorited
? t('gallery.removeFromFavorites' as TranslationKey)
@@ -195,7 +195,7 @@ export function GalleryDetail({
<div className="flex items-center gap-1.5 flex-wrap">
{item.model && (
<Badge variant="secondary" className="text-[10px] gap-1">
<HugeiconsIcon icon={PaintBrush01Icon} className="h-3 w-3" />
<PaintBrush size={12} />
{item.model}
</Badge>
)}
@@ -250,12 +250,12 @@ export function GalleryDetail({
router.push(`/chat/${item.session_id}`);
}}
>
<HugeiconsIcon icon={Message02Icon} className="h-3.5 w-3.5" />
<ChatCircle size={14} />
{t('gallery.openChat' as TranslationKey)}
</Button>
)}
<Button variant="outline" size="sm" onClick={handleDownload}>
<HugeiconsIcon icon={Download04Icon} className="h-3.5 w-3.5" />
<DownloadSimple size={14} />
{t('gallery.download' as TranslationKey)}
</Button>
<div className="ml-auto">
@@ -264,7 +264,7 @@ export function GalleryDetail({
size="sm"
onClick={handleDelete}
>
<HugeiconsIcon icon={Delete02Icon} className="h-3.5 w-3.5" />
<Trash size={14} />
{confirmDelete
? t('gallery.confirmDelete' as TranslationKey)
: t('gallery.delete' as TranslationKey)}
+3 -4
View File
@@ -1,7 +1,6 @@
'use client';
import { HugeiconsIcon } from '@hugeicons/react';
import { PaintBrush01Icon, FavouriteIcon } from '@hugeicons/core-free-icons';
import { PaintBrush, Heart } from '@phosphor-icons/react';
export interface GalleryItem {
id: string;
@@ -64,7 +63,7 @@ export function GalleryGrid({ items, onSelect }: GalleryGridProps) {
/>
) : (
<div className="flex aspect-square items-center justify-center">
<HugeiconsIcon icon={PaintBrush01Icon} className="h-8 w-8 text-muted-foreground/30" />
<PaintBrush size={32} className="text-muted-foreground/30" />
</div>
)}
{item.images.length > 1 && (
@@ -74,7 +73,7 @@ export function GalleryGrid({ items, onSelect }: GalleryGridProps) {
)}
{item.favorited && (
<span className="absolute top-1.5 left-1.5">
<HugeiconsIcon icon={FavouriteIcon} className="h-4 w-4 text-red-500 drop-shadow" fill="currentColor" />
<Heart size={16} className="text-red-500 drop-shadow" weight="fill" />
</span>
)}
</div>
+3 -4
View File
@@ -1,8 +1,7 @@
'use client';
import { useState, useCallback, useEffect } from 'react';
import { HugeiconsIcon } from '@hugeicons/react';
import { PlusSignIcon, Cancel01Icon } from '@hugeicons/core-free-icons';
import { Plus, X } from '@phosphor-icons/react';
import { cn } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@@ -94,7 +93,7 @@ export function TagManager({
}}
className="ml-0.5 rounded-full p-0.5 opacity-0 group-hover:opacity-100 hover:bg-foreground/10 transition-opacity"
>
<HugeiconsIcon icon={Cancel01Icon} className="h-2.5 w-2.5" />
<X size={10} />
</button>
)}
</Badge>
@@ -109,7 +108,7 @@ export function TagManager({
onClick={() => setAdding(true)}
className="h-5 w-5"
>
<HugeiconsIcon icon={PlusSignIcon} className="h-3 w-3" />
<Plus size={12} />
</Button>
)}
</div>
+32 -34
View File
@@ -3,19 +3,18 @@
import { usePathname, useRouter } from "next/navigation";
import Link from "next/link";
import { useEffect, useState, useCallback, useMemo, useRef } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Delete02Icon,
Search01Icon,
Notification02Icon,
FileImportIcon,
Folder01Icon,
ArrowDown01Icon,
ArrowRight01Icon,
PlusSignIcon,
FolderOpenIcon,
AiUserIcon,
} from "@hugeicons/core-free-icons";
Trash,
MagnifyingGlass,
Bell,
FileArrowDown,
Folder,
CaretDown,
CaretRight,
Plus,
FolderOpen,
UserCircle,
} from "@phosphor-icons/react";
import { Columns2, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -392,7 +391,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
disabled={creatingChat}
onClick={handleNewChat}
>
<HugeiconsIcon icon={PlusSignIcon} className="h-3.5 w-3.5" />
<Plus size={14} />
{t('chatList.newConversation')}
</Button>
<Tooltip>
@@ -403,7 +402,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
className="h-8 w-8 shrink-0"
onClick={() => openFolderPicker()}
>
<HugeiconsIcon icon={FolderOpenIcon} className="h-3.5 w-3.5" />
<FolderOpen size={14} />
<span className="sr-only">{t('chatList.addProjectFolder')}</span>
</Button>
</TooltipTrigger>
@@ -414,9 +413,9 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
{/* Search */}
<div className="px-3 pb-2">
<div className="relative">
<HugeiconsIcon
icon={Search01Icon}
className="absolute left-2.5 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground"
<MagnifyingGlass
size={12}
className="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
/>
<Input
placeholder={t('chatList.searchSessions')}
@@ -435,7 +434,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
className="w-full justify-start gap-2 h-7 text-xs text-muted-foreground hover:text-foreground"
onClick={() => setImportDialogOpen(true)}
>
<HugeiconsIcon icon={FileImportIcon} className="h-3 w-3" />
<FileArrowDown size={12} />
{t('chatList.importFromCli')}
</Button>
</div>
@@ -487,7 +486,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
)}
{needsApproval && (
<span className="flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full bg-amber-500/10">
<HugeiconsIcon icon={Notification02Icon} className="h-2.5 w-2.5 text-amber-500" />
<Bell size={10} className="text-amber-500" />
</span>
)}
<div className="flex-1 min-w-0">
@@ -555,19 +554,21 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
}
onMouseLeave={() => setHoveredFolder(null)}
>
<HugeiconsIcon
icon={isCollapsed ? ArrowRight01Icon : ArrowDown01Icon}
className="h-3.5 w-3.5 shrink-0 text-muted-foreground"
/>
<HugeiconsIcon
icon={isCollapsed ? Folder01Icon : FolderOpenIcon}
className="h-4 w-4 shrink-0 text-muted-foreground"
/>
{isCollapsed ? (
<CaretRight size={14} className="shrink-0 text-muted-foreground" />
) : (
<CaretDown size={14} className="shrink-0 text-muted-foreground" />
)}
{isCollapsed ? (
<Folder size={16} className="shrink-0 text-muted-foreground" />
) : (
<FolderOpen size={16} className="shrink-0 text-muted-foreground" />
)}
<span className="flex-1 truncate text-[13px] font-medium text-sidebar-foreground">
{group.displayName}
</span>
{workspacePath && group.workingDirectory === workspacePath && (
<HugeiconsIcon icon={AiUserIcon} className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<UserCircle size={14} className="shrink-0 text-muted-foreground" />
)}
{/* New chat in project button (on hover) */}
{group.workingDirectory !== "" && (
@@ -588,10 +589,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
)
}
>
<HugeiconsIcon
icon={PlusSignIcon}
className="h-3.5 w-3.5"
/>
<Plus size={14} />
<span className="sr-only">
New chat in {group.displayName}
</span>
@@ -681,7 +679,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
"flex h-3.5 w-3.5 items-center justify-center rounded-full bg-amber-500/10 transition-opacity",
isHovered && canSplit ? "opacity-0" : "opacity-100"
)}>
<HugeiconsIcon icon={Notification02Icon} className="h-2.5 w-2.5 text-amber-500" />
<Bell size={10} className="text-amber-500" />
</span>
)}
</span>
@@ -706,7 +704,7 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
onClick={(e) => handleDeleteSession(e, session.id)}
disabled={isDeleting}
>
<HugeiconsIcon icon={Delete02Icon} className="h-3.5 w-3.5" />
<Trash size={14} />
</button>
</div>
</Link>
+5 -9
View File
@@ -2,8 +2,7 @@
import { useState, useEffect, useMemo } from "react";
import { useTheme } from "next-themes";
import { HugeiconsIcon } from "@hugeicons/react";
import { Cancel01Icon, Copy01Icon, Tick01Icon, Loading02Icon } from "@hugeicons/core-free-icons";
import { X, Copy, Check, SpinnerGap } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
import { useThemeFamily } from "@/lib/theme/context";
@@ -132,15 +131,15 @@ export function DocPreview({
<Button variant="ghost" size="icon-sm" onClick={handleCopyContent}>
{copied ? (
<HugeiconsIcon icon={Tick01Icon} className="h-3.5 w-3.5 text-green-500" />
<Check size={14} className="text-green-500" />
) : (
<HugeiconsIcon icon={Copy01Icon} className="h-3.5 w-3.5" />
<Copy size={14} />
)}
<span className="sr-only">Copy content</span>
</Button>
<Button variant="ghost" size="icon-sm" onClick={onClose}>
<HugeiconsIcon icon={Cancel01Icon} className="h-3.5 w-3.5" />
<X size={14} />
<span className="sr-only">Close preview</span>
</Button>
</div>
@@ -161,10 +160,7 @@ export function DocPreview({
<div className="flex-1 min-h-0 overflow-auto">
{loading ? (
<div className="flex items-center justify-center py-12">
<HugeiconsIcon
icon={Loading02Icon}
className="h-5 w-5 animate-spin text-muted-foreground"
/>
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
</div>
) : error ? (
<div className="px-4 py-8 text-center">
+3 -4
View File
@@ -2,8 +2,7 @@
import { useTheme } from "next-themes";
import { useCallback, useSyncExternalStore } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { Moon02Icon, Sun02Icon } from "@hugeicons/core-free-icons";
import { Moon, Sun } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
@@ -29,9 +28,9 @@ export function Header() {
className="h-7 w-7"
>
{theme === "dark" ? (
<HugeiconsIcon icon={Sun02Icon} className="h-4 w-4" />
<Sun size={16} />
) : (
<HugeiconsIcon icon={Moon02Icon} className="h-4 w-4" />
<Moon size={16} />
)}
<span className="sr-only">Toggle theme</span>
</Button>
+20 -48
View File
@@ -13,16 +13,15 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Search01Icon,
Loading02Icon,
FolderOpenIcon,
GitBranchIcon,
ClockIcon,
FileImportIcon,
MessageAddIcon,
} from "@hugeicons/core-free-icons";
MagnifyingGlass,
SpinnerGap,
FolderOpen,
GitBranch,
Clock,
FileArrowDown,
ChatCircleText,
} from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
import { cn, parseDBDate } from "@/lib/utils";
@@ -152,10 +151,7 @@ export function ImportSessionDialog({
<DialogContent className="sm:max-w-2xl max-h-[80vh] !flex flex-col overflow-hidden">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<HugeiconsIcon
icon={FileImportIcon}
className="h-5 w-5 text-primary"
/>
<FileArrowDown size={20} className="text-primary" />
{t('import.title')}
</DialogTitle>
<DialogDescription>
@@ -166,9 +162,9 @@ export function ImportSessionDialog({
{/* Search */}
<div className="relative">
<HugeiconsIcon
icon={Search01Icon}
className="absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground"
<MagnifyingGlass
size={14}
className="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
/>
<Input
placeholder={t('import.searchSessions')}
@@ -190,20 +186,14 @@ export function ImportSessionDialog({
<div className="flex flex-col gap-2 pb-2">
{loading ? (
<div className="flex items-center justify-center py-12">
<HugeiconsIcon
icon={Loading02Icon}
className="h-5 w-5 animate-spin text-muted-foreground"
/>
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
<span className="ml-2 text-sm text-muted-foreground">
Scanning CLI sessions...
</span>
</div>
) : filteredSessions.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
<HugeiconsIcon
icon={FolderOpenIcon}
className="h-8 w-8 mb-2 opacity-40"
/>
<FolderOpen size={32} className="mb-2 opacity-40" />
<p className="text-sm">
{searchQuery
? t('import.noSessions')
@@ -241,10 +231,7 @@ export function ImportSessionDialog({
variant="secondary"
className="text-[10px] px-1.5 py-0 h-4 shrink-0"
>
<HugeiconsIcon
icon={GitBranchIcon}
className="h-2.5 w-2.5 mr-0.5"
/>
<GitBranch size={10} className="mr-0.5" />
{session.gitBranch}
</Badge>
)}
@@ -262,18 +249,12 @@ export function ImportSessionDialog({
>
{isImporting ? (
<>
<HugeiconsIcon
icon={Loading02Icon}
className="h-3 w-3 mr-1 animate-spin"
/>
<SpinnerGap size={12} className="mr-1 animate-spin" />
{t('import.importing')}
</>
) : (
<>
<HugeiconsIcon
icon={FileImportIcon}
className="h-3 w-3 mr-1"
/>
<FileArrowDown size={12} className="mr-1" />
{t('import.import')}
</>
)}
@@ -286,24 +267,15 @@ export function ImportSessionDialog({
className="flex items-center gap-0.5 truncate"
title={session.cwd}
>
<HugeiconsIcon
icon={FolderOpenIcon}
className="h-2.5 w-2.5 shrink-0"
/>
<FolderOpen size={10} className="shrink-0" />
{session.cwd}
</span>
<span className="flex items-center gap-0.5 shrink-0">
<HugeiconsIcon
icon={MessageAddIcon}
className="h-2.5 w-2.5"
/>
<ChatCircleText size={10} />
{t(totalMessages !== 1 ? 'import.messagesPlural' : 'import.messages', { n: totalMessages })}
</span>
<span className="flex items-center gap-0.5 shrink-0">
<HugeiconsIcon
icon={ClockIcon}
className="h-2.5 w-2.5"
/>
<Clock size={10} />
{(() => {
const rel = formatRelativeTime(session.updatedAt);
return typeof rel === 'string' ? rel : t(rel.key, rel.params);
+19 -20
View File
@@ -11,16 +11,15 @@ import {
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Tick01Icon,
Cancel01Icon,
MinusSignIcon,
Loading02Icon,
RecordIcon,
Copy01Icon,
Download04Icon,
} from "@hugeicons/core-free-icons";
Check,
X,
Minus,
SpinnerGap,
Circle,
Copy,
DownloadSimple,
} from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
interface InstallProgress {
@@ -68,15 +67,15 @@ function getInstallAPI() {
function StepIcon({ status }: { status: string }) {
switch (status) {
case "success":
return <HugeiconsIcon icon={Tick01Icon} className="size-4 text-emerald-500" />;
return <Check size={16} className="text-emerald-500" />;
case "running":
return <HugeiconsIcon icon={Loading02Icon} className="size-4 text-primary animate-spin" />;
return <SpinnerGap size={16} className="text-primary animate-spin" />;
case "failed":
return <HugeiconsIcon icon={Cancel01Icon} className="size-4 text-red-500" />;
return <X size={16} className="text-red-500" />;
case "skipped":
return <HugeiconsIcon icon={MinusSignIcon} className="size-4 text-muted-foreground" />;
return <Minus size={16} className="text-muted-foreground" />;
default:
return <HugeiconsIcon icon={RecordIcon} className="size-3.5 text-muted-foreground/40" />;
return <Circle size={14} className="text-muted-foreground/40" />;
}
}
@@ -286,7 +285,7 @@ export function InstallWizard({
{/* Phase: checking */}
{phase === "checking" && steps.length === 0 && (
<div className="flex items-center gap-2.5 text-sm text-muted-foreground">
<HugeiconsIcon icon={Loading02Icon} className="size-4 animate-spin" />
<SpinnerGap size={16} className="animate-spin" />
<span>{t('install.checkingPrereqs')}</span>
</div>
)}
@@ -318,7 +317,7 @@ export function InstallWizard({
} catch { /* clipboard not available */ }
}}
>
<HugeiconsIcon icon={Copy01Icon} className="size-3.5" />
<Copy size={14} />
<span className="text-xs">{copiedBrew ? t('install.copied') : t('install.copy')}</span>
</Button>
</div>
@@ -360,7 +359,7 @@ export function InstallWizard({
{/* Phase: already-installed */}
{phase === "already-installed" && (
<div className="flex items-center gap-3 rounded-lg bg-emerald-500/10 px-4 py-3">
<HugeiconsIcon icon={Tick01Icon} className="size-5 text-emerald-500 shrink-0" />
<Check size={20} className="text-emerald-500 shrink-0" />
<div className="text-sm">
<p className="font-medium text-emerald-700 dark:text-emerald-400">
Already installed
@@ -375,7 +374,7 @@ export function InstallWizard({
{/* Phase: success */}
{phase === "success" && (
<div className="flex items-center gap-3 rounded-lg bg-emerald-500/10 px-4 py-3">
<HugeiconsIcon icon={Tick01Icon} className="size-5 text-emerald-500 shrink-0" />
<Check size={20} className="text-emerald-500 shrink-0" />
<div className="text-sm">
<p className="font-medium text-emerald-700 dark:text-emerald-400">
{t('install.complete')}
@@ -409,7 +408,7 @@ export function InstallWizard({
size="sm"
onClick={handleCopyLogs}
>
<HugeiconsIcon icon={Copy01Icon} />
<Copy size={16} />
{copied ? t('install.copied') : t('install.copyLogs')}
</Button>
)}
@@ -422,7 +421,7 @@ export function InstallWizard({
)}
{phase === "confirm" && !(prereqs && !prereqs.hasNode && !prereqs.hasHomebrew && prereqs.platform === "darwin") && (
<Button size="sm" onClick={handleConfirmInstall}>
<HugeiconsIcon icon={Download04Icon} />
<DownloadSimple size={16} />
{t('install.install')}
</Button>
)}
+15 -16
View File
@@ -2,15 +2,14 @@
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Message02Icon,
ZapIcon,
Plug01Icon,
Image01Icon,
Settings02Icon,
Wifi01Icon,
} from "@hugeicons/core-free-icons";
ChatCircle,
Lightning,
Plug,
Image,
Gear,
WifiHigh,
} from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
@@ -31,11 +30,11 @@ interface NavRailProps {
}
const navItems = [
{ href: "/chat", label: "Chats", icon: Message02Icon },
{ href: "/skills", label: "Skills", icon: ZapIcon },
{ href: "/mcp", label: "MCP", icon: Plug01Icon },
{ href: "/gallery", label: "Gallery", icon: Image01Icon },
{ href: "/bridge", label: "Bridge", icon: Wifi01Icon },
{ href: "/chat", label: "Chats", icon: ChatCircle },
{ href: "/skills", label: "Skills", icon: Lightning },
{ href: "/mcp", label: "MCP", icon: Plug },
{ href: "/gallery", label: "Gallery", icon: Image },
{ href: "/bridge", label: "Bridge", icon: WifiHigh },
] as const;
export function NavRail({ onToggleChatList, hasUpdate, readyToInstall, skipPermissionsActive }: NavRailProps) {
@@ -83,7 +82,7 @@ export function NavRail({ onToggleChatList, hasUpdate, readyToInstall, skipPermi
}
}}
>
<HugeiconsIcon icon={item.icon} className="h-4 w-4" />
<item.icon size={16} weight={isActive ? "fill" : "regular"} />
<span className="sr-only">{t(navLabelKeys[item.label] ?? item.label as TranslationKey)}</span>
</Button>
) : (
@@ -98,7 +97,7 @@ export function NavRail({ onToggleChatList, hasUpdate, readyToInstall, skipPermi
)}
>
<Link href={item.href}>
<HugeiconsIcon icon={item.icon} className="h-4 w-4" />
<item.icon size={16} weight={isActive ? "fill" : "regular"} />
<span className="sr-only">{t(navLabelKeys[item.label] ?? item.label as TranslationKey)}</span>
</Link>
</Button>
@@ -139,7 +138,7 @@ export function NavRail({ onToggleChatList, hasUpdate, readyToInstall, skipPermi
)}
>
<Link href="/settings">
<HugeiconsIcon icon={Settings02Icon} className="h-4 w-4" />
<Gear size={16} weight={isSettingsActive ? "fill" : "regular"} />
<span className="sr-only">{t('nav.settings')}</span>
</Link>
</Button>
+3 -4
View File
@@ -1,8 +1,7 @@
"use client";
import { useCallback } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { StructureFolderIcon, PanelRightCloseIcon } from "@hugeicons/core-free-icons";
import { TreeStructure, SidebarSimple } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
@@ -58,7 +57,7 @@ export function RightPanel({ width }: RightPanelProps) {
size="icon-sm"
onClick={() => setPanelOpen(true)}
>
<HugeiconsIcon icon={StructureFolderIcon} className="h-4 w-4" />
<TreeStructure size={16} />
<span className="sr-only">{t('panel.openPanel')}</span>
</Button>
</TooltipTrigger>
@@ -82,7 +81,7 @@ export function RightPanel({ width }: RightPanelProps) {
size="icon-sm"
onClick={() => setPanelOpen(false)}
>
<HugeiconsIcon icon={PanelRightCloseIcon} className="h-4 w-4" />
<SidebarSimple size={16} />
<span className="sr-only">{t('panel.closePanel')}</span>
</Button>
</TooltipTrigger>
+7 -8
View File
@@ -4,8 +4,7 @@ import { useState, useEffect, useCallback } from "react";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Badge } from "@/components/ui/badge";
import { HugeiconsIcon } from "@hugeicons/react";
import { PlusSignIcon, ListViewIcon, CodeIcon, Loading02Icon, RefreshIcon, Wifi01Icon } from "@hugeicons/core-free-icons";
import { Plus, List, Code, SpinnerGap, ArrowsClockwise, WifiHigh } from "@phosphor-icons/react";
import { McpServerList } from "@/components/plugins/McpServerList";
import { McpServerEditor } from "@/components/plugins/McpServerEditor";
import { ConfigEditor } from "@/components/plugins/ConfigEditor";
@@ -213,7 +212,7 @@ export function McpManager() {
</p>
</div>
<Button size="sm" className="gap-1" onClick={handleAdd}>
<HugeiconsIcon icon={PlusSignIcon} className="h-3.5 w-3.5" />
<Plus size={14} />
{t('mcp.addServer')}
</Button>
</div>
@@ -227,11 +226,11 @@ export function McpManager() {
<Tabs value={tab} onValueChange={(v) => setTab(v as "list" | "json")}>
<TabsList>
<TabsTrigger value="list" className="gap-1.5">
<HugeiconsIcon icon={ListViewIcon} className="h-3.5 w-3.5" />
<List size={14} />
{t('mcp.listTab')}
</TabsTrigger>
<TabsTrigger value="json" className="gap-1.5">
<HugeiconsIcon icon={CodeIcon} className="h-3.5 w-3.5" />
<Code size={14} />
{t('mcp.jsonTab')}
</TabsTrigger>
</TabsList>
@@ -239,7 +238,7 @@ export function McpManager() {
<TabsContent value="list" className="mt-4">
{loading ? (
<div className="flex items-center justify-center gap-2 py-12 text-muted-foreground">
<HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin" />
<SpinnerGap size={16} className="animate-spin" />
<p className="text-sm">{t('mcp.loadingServers')}</p>
</div>
) : (
@@ -283,7 +282,7 @@ export function McpManager() {
<div className="mt-6 border-t pt-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<HugeiconsIcon icon={Wifi01Icon} className="h-4 w-4 text-muted-foreground" />
<WifiHigh size={16} className="text-muted-foreground" />
<h4 className="text-sm font-medium">{t('mcp.runtimeStatus' as TranslationKey)}</h4>
</div>
<Button
@@ -293,7 +292,7 @@ export function McpManager() {
onClick={fetchRuntimeStatus}
disabled={runtimeLoading}
>
<HugeiconsIcon icon={runtimeLoading ? Loading02Icon : RefreshIcon} className={`h-3 w-3 ${runtimeLoading ? 'animate-spin' : ''}`} />
{runtimeLoading ? <SpinnerGap size={12} className="animate-spin" /> : <ArrowsClockwise size={12} />}
{t('mcp.refresh' as TranslationKey)}
</Button>
</div>
+5 -6
View File
@@ -13,8 +13,7 @@ import {
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
import { HugeiconsIcon } from "@hugeicons/react";
import { ServerStack01Icon, Wifi01Icon, GlobeIcon, CodeIcon } from "@hugeicons/core-free-icons";
import { HardDrives, WifiHigh, Globe, Code } from "@phosphor-icons/react";
import { useTranslation } from '@/hooks/useTranslation';
import type { MCPServer } from '@/types';
@@ -237,7 +236,7 @@ export function McpServerEditor({
setError(null);
}}
>
<HugeiconsIcon icon={CodeIcon} className="h-3.5 w-3.5" />
<Code size={14} />
{t('mcp.jsonEditTab')}
</Button>
</div>
@@ -268,15 +267,15 @@ export function McpServerEditor({
>
<TabsList className="w-full">
<TabsTrigger value="stdio" className="flex-1 gap-1.5">
<HugeiconsIcon icon={ServerStack01Icon} className="h-3.5 w-3.5" />
<HardDrives size={14} />
stdio
</TabsTrigger>
<TabsTrigger value="sse" className="flex-1 gap-1.5">
<HugeiconsIcon icon={Wifi01Icon} className="h-3.5 w-3.5" />
<WifiHigh size={14} />
SSE
</TabsTrigger>
<TabsTrigger value="http" className="flex-1 gap-1.5">
<HugeiconsIcon icon={GlobeIcon} className="h-3.5 w-3.5" />
<Globe size={14} />
HTTP
</TabsTrigger>
</TabsList>
+11 -12
View File
@@ -3,8 +3,7 @@
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { HugeiconsIcon } from "@hugeicons/react";
import { Delete02Icon, PencilIcon, ServerStack01Icon, Wifi01Icon, GlobeIcon, RefreshIcon, Loading02Icon } from "@hugeicons/core-free-icons";
import { Trash, PencilSimple, HardDrives, WifiHigh, Globe, ArrowsClockwise, SpinnerGap } from "@phosphor-icons/react";
import { useTranslation } from '@/hooks/useTranslation';
import type { TranslationKey } from '@/i18n';
import type { MCPServer } from '@/types';
@@ -28,11 +27,11 @@ function getServerTypeInfo(server: MCPServer) {
const type = server.type || 'stdio';
switch (type) {
case 'sse':
return { label: 'SSE', icon: Wifi01Icon, color: 'text-primary' };
return { label: 'SSE', icon: WifiHigh, color: 'text-primary' };
case 'http':
return { label: 'HTTP', icon: GlobeIcon, color: 'text-green-500' };
return { label: 'HTTP', icon: Globe, color: 'text-green-500' };
default:
return { label: 'stdio', icon: ServerStack01Icon, color: 'text-muted-foreground' };
return { label: 'stdio', icon: HardDrives, color: 'text-muted-foreground' };
}
}
@@ -102,7 +101,7 @@ export function McpServerList({ servers, onEdit, onDelete, runtimeStatus, active
if (entries.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
<HugeiconsIcon icon={ServerStack01Icon} className="h-10 w-10 mb-3 opacity-50" />
<HardDrives size={40} className="mb-3 opacity-50" />
<p className="text-sm">{t('mcp.noServers')}</p>
<p className="text-xs mt-1">
{t('mcp.noServersDesc')}
@@ -133,7 +132,7 @@ export function McpServerList({ servers, onEdit, onDelete, runtimeStatus, active
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-2">
<div className="flex-1 min-w-0 mr-3">
<div className="flex items-center gap-2 mb-1">
<HugeiconsIcon icon={typeInfo.icon} className={`h-4 w-4 shrink-0 ${typeInfo.color}`} />
<typeInfo.icon size={16} className={`shrink-0 ${typeInfo.color}`} />
<CardTitle className="text-sm font-medium">{name}</CardTitle>
<Badge variant="outline" className="text-xs shrink-0">
{typeInfo.label}
@@ -171,9 +170,9 @@ export function McpServerList({ servers, onEdit, onDelete, runtimeStatus, active
title={t('mcp.reconnect' as TranslationKey)}
>
{isReconnecting ? (
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin" />
<SpinnerGap size={14} className="animate-spin" />
) : (
<HugeiconsIcon icon={RefreshIcon} className="h-3.5 w-3.5" />
<ArrowsClockwise size={14} />
)}
</Button>
)}
@@ -187,7 +186,7 @@ export function McpServerList({ servers, onEdit, onDelete, runtimeStatus, active
onClick={() => handleToggle(name, true)}
>
{isToggling ? (
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
) : null}
{t('mcp.enable' as TranslationKey)}
</Button>
@@ -198,7 +197,7 @@ export function McpServerList({ servers, onEdit, onDelete, runtimeStatus, active
className="h-8 w-8"
onClick={() => onEdit(name, server)}
>
<HugeiconsIcon icon={PencilIcon} className="h-3.5 w-3.5" />
<PencilSimple size={14} />
</Button>
<Button
variant="ghost"
@@ -206,7 +205,7 @@ export function McpServerList({ servers, onEdit, onDelete, runtimeStatus, active
className="h-8 w-8 text-destructive hover:text-destructive"
onClick={() => onDelete(name)}
>
<HugeiconsIcon icon={Delete02Icon} className="h-3.5 w-3.5" />
<Trash size={14} />
</Button>
</div>
</CardHeader>
+4 -5
View File
@@ -4,8 +4,7 @@ import { useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
import { HugeiconsIcon } from "@hugeicons/react";
import { ArrowDown01Icon, ArrowUp01Icon, ZapIcon } from "@hugeicons/core-free-icons";
import { CaretDown, CaretUp, Lightning } from "@phosphor-icons/react";
export interface SkillInfo {
name: string;
@@ -37,7 +36,7 @@ export function PluginCard({ plugin, onSelect }: PluginCardProps) {
>
<div className="flex-1 min-w-0 mr-3">
<div className="flex items-center gap-2 mb-1">
<HugeiconsIcon icon={ZapIcon} className="h-4 w-4 text-muted-foreground shrink-0" />
<Lightning size={16} className="text-muted-foreground shrink-0" />
<CardTitle className="text-sm font-medium truncate">
/{displayName}
</CardTitle>
@@ -62,9 +61,9 @@ export function PluginCard({ plugin, onSelect }: PluginCardProps) {
}}
>
{expanded ? (
<HugeiconsIcon icon={ArrowUp01Icon} className="h-4 w-4" />
<CaretUp size={16} />
) : (
<HugeiconsIcon icon={ArrowDown01Icon} className="h-4 w-4" />
<CaretDown size={16} />
)}
</Button>
</CardHeader>
+3 -4
View File
@@ -3,8 +3,7 @@
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { HugeiconsIcon } from "@hugeicons/react";
import { Cancel01Icon, ZapIcon } from "@hugeicons/core-free-icons";
import { X, Lightning } from "@phosphor-icons/react";
import type { SkillInfo } from './PluginCard';
interface PluginDetailProps {
@@ -23,7 +22,7 @@ export function PluginDetail({ plugin, onClose }: PluginDetailProps) {
<div className="flex items-start justify-between mb-3">
<div>
<div className="flex items-center gap-2">
<HugeiconsIcon icon={ZapIcon} className="h-4 w-4 text-muted-foreground" />
<Lightning size={16} className="text-muted-foreground" />
<h3 className="text-lg font-semibold">/{displayName}</h3>
<Badge variant={isProject ? 'secondary' : 'outline'}>
{isProject ? 'Project' : 'Global'}
@@ -34,7 +33,7 @@ export function PluginDetail({ plugin, onClose }: PluginDetailProps) {
</p>
</div>
<Button variant="ghost" size="icon" onClick={onClose}>
<HugeiconsIcon icon={Cancel01Icon} className="h-4 w-4" />
<X size={16} />
</Button>
</div>
+5 -6
View File
@@ -4,8 +4,7 @@ import { useState } from 'react';
import { Input } from '@/components/ui/input';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { PluginCard, type SkillInfo } from './PluginCard';
import { HugeiconsIcon } from "@hugeicons/react";
import { Search01Icon, GlobeIcon, FolderOpenIcon, Plug01Icon } from "@hugeicons/core-free-icons";
import { MagnifyingGlass, Globe, FolderOpen, Plug } from "@phosphor-icons/react";
interface PluginListProps {
plugins: SkillInfo[];
@@ -31,7 +30,7 @@ export function PluginList({ plugins, onSelect }: PluginListProps) {
return (
<div className="space-y-4">
<div className="relative">
<HugeiconsIcon icon={Search01Icon} className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<MagnifyingGlass size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
@@ -49,16 +48,16 @@ export function PluginList({ plugins, onSelect }: PluginListProps) {
All ({plugins.length})
</TabsTrigger>
<TabsTrigger value="global" className="gap-1.5">
<HugeiconsIcon icon={GlobeIcon} className="h-3.5 w-3.5" />
<Globe size={14} />
Global ({globalCount})
</TabsTrigger>
<TabsTrigger value="project" className="gap-1.5">
<HugeiconsIcon icon={FolderOpenIcon} className="h-3.5 w-3.5" />
<FolderOpen size={14} />
Project ({projectCount})
</TabsTrigger>
{pluginCount > 0 && (
<TabsTrigger value="plugin" className="gap-1.5">
<HugeiconsIcon icon={Plug01Icon} className="h-3.5 w-3.5" />
<Plug size={14} />
Plugin ({pluginCount})
</TabsTrigger>
)}
+5 -6
View File
@@ -1,8 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { ArrowLeft01Icon, Copy01Icon, Tick01Icon, Loading02Icon } from "@hugeicons/core-free-icons";
import { ArrowLeft, Copy, Check, SpinnerGap } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
@@ -78,7 +77,7 @@ export function FilePreview({ filePath, onBack }: FilePreviewProps) {
{/* Header */}
<div className="flex items-center gap-2 pb-2">
<Button variant="ghost" size="icon-sm" onClick={onBack}>
<HugeiconsIcon icon={ArrowLeft01Icon} className="h-3.5 w-3.5" />
<ArrowLeft size={14} />
<span className="sr-only">{t('filePreview.backToTree')}</span>
</Button>
<div className="min-w-0 flex-1">
@@ -88,9 +87,9 @@ export function FilePreview({ filePath, onBack }: FilePreviewProps) {
</div>
<Button variant="ghost" size="icon-sm" onClick={handleCopyPath}>
{copied ? (
<HugeiconsIcon icon={Tick01Icon} className="h-3.5 w-3.5 text-green-500" />
<Check size={14} className="text-green-500" />
) : (
<HugeiconsIcon icon={Copy01Icon} className="h-3.5 w-3.5" />
<Copy size={14} />
)}
<span className="sr-only">{t('filePreview.copyPath')}</span>
</Button>
@@ -112,7 +111,7 @@ export function FilePreview({ filePath, onBack }: FilePreviewProps) {
<ScrollArea className="flex-1">
{loading ? (
<div className="flex items-center justify-center py-8">
<HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin text-muted-foreground" />
<SpinnerGap size={16} className="animate-spin text-muted-foreground" />
</div>
) : error ? (
<div className="py-4 text-center">
+8 -9
View File
@@ -1,8 +1,7 @@
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { RefreshIcon, Search01Icon, SourceCodeIcon, CodeIcon, File01Icon } from "@hugeicons/core-free-icons";
import { ArrowsClockwise, MagnifyingGlass, FileCode, Code, File } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
@@ -43,19 +42,19 @@ function getFileIcon(extension?: string): ReactNode {
case "lua":
case "php":
case "zig":
return <HugeiconsIcon icon={SourceCodeIcon} className="size-4 text-muted-foreground" />;
return <FileCode size={16} className="text-muted-foreground" />;
case "json":
case "yaml":
case "yml":
case "toml":
return <HugeiconsIcon icon={CodeIcon} className="size-4 text-muted-foreground" />;
return <Code size={16} className="text-muted-foreground" />;
case "md":
case "mdx":
case "txt":
case "csv":
return <HugeiconsIcon icon={File01Icon} className="size-4 text-muted-foreground" />;
return <File size={16} className="text-muted-foreground" />;
default:
return <HugeiconsIcon icon={File01Icon} className="size-4 text-muted-foreground" />;
return <File size={16} className="text-muted-foreground" />;
}
}
@@ -188,7 +187,7 @@ export function FileTree({ workingDirectory, onFileSelect, onFileAdd }: FileTree
{/* Search + Refresh */}
<div className="flex items-center gap-1.5 px-4 py-2 shrink-0">
<div className="relative flex-1 min-w-0">
<HugeiconsIcon icon={Search01Icon} className="absolute left-2.5 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground pointer-events-none" />
<MagnifyingGlass size={12} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none" />
<Input
placeholder={t('fileTree.filterFiles')}
value={searchQuery}
@@ -203,7 +202,7 @@ export function FileTree({ workingDirectory, onFileSelect, onFileAdd }: FileTree
disabled={loading}
className="h-7 w-7 shrink-0"
>
<HugeiconsIcon icon={RefreshIcon} className={cn("h-3 w-3", loading && "animate-spin")} />
<ArrowsClockwise size={12} className={cn(loading && "animate-spin")} />
<span className="sr-only">{t('fileTree.refresh')}</span>
</Button>
</div>
@@ -212,7 +211,7 @@ export function FileTree({ workingDirectory, onFileSelect, onFileAdd }: FileTree
<div className="flex-1 overflow-auto">
{loading && tree.length === 0 ? (
<div className="flex items-center justify-center py-8">
<HugeiconsIcon icon={RefreshIcon} className="h-4 w-4 animate-spin text-muted-foreground" />
<ArrowsClockwise size={16} className="animate-spin text-muted-foreground" />
</div>
) : tree.length === 0 ? (
<p className="py-4 text-center text-xs text-muted-foreground">
+2 -3
View File
@@ -1,8 +1,7 @@
"use client";
import { useState, useRef, useEffect } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { Delete02Icon } from "@hugeicons/core-free-icons";
import { Trash } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import { cn, parseDBDate } from "@/lib/utils";
import type { TaskItem, TaskStatus } from "@/types";
@@ -133,7 +132,7 @@ export function TaskCard({ task, onUpdate, onDelete }: TaskCardProps) {
onClick={() => onDelete(task.id)}
className="shrink-0 text-muted-foreground hover:text-destructive"
>
<HugeiconsIcon icon={Delete02Icon} className="h-3 w-3" />
<Trash size={12} />
<span className="sr-only">Delete task</span>
</Button>
)}
@@ -9,8 +9,7 @@ import {
resolveShikiThemes,
} from "@/lib/theme/code-themes";
import { useTranslation } from "@/hooks/useTranslation";
import { HugeiconsIcon } from "@hugeicons/react";
import { Sun02Icon, Moon02Icon, ComputerDesk01Icon } from "@hugeicons/core-free-icons";
import { Sun, Moon, Desktop } from "@phosphor-icons/react";
import {
Select,
SelectContent,
@@ -23,9 +22,9 @@ import { cn } from "@/lib/utils";
// ── Theme Mode Pill Selector ────────────────────────────────────────
const MODE_OPTIONS = [
{ value: "light", icon: Sun02Icon, labelKey: "settings.modeLight" as const },
{ value: "dark", icon: Moon02Icon, labelKey: "settings.modeDark" as const },
{ value: "system", icon: ComputerDesk01Icon, labelKey: "settings.modeSystem" as const },
{ value: "light", icon: Sun, labelKey: "settings.modeLight" as const },
{ value: "dark", icon: Moon, labelKey: "settings.modeDark" as const },
{ value: "system", icon: Desktop, labelKey: "settings.modeSystem" as const },
] as const;
function ThemeModePills({
@@ -54,7 +53,7 @@ function ThemeModePills({
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<HugeiconsIcon icon={opt.icon} className="h-3.5 w-3.5" />
<opt.icon size={14} />
{t(opt.labelKey)}
</button>
);
@@ -14,8 +14,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon, CheckmarkCircle02Icon, Cancel01Icon } from "@hugeicons/core-free-icons";
import { SpinnerGap, CheckCircle, X } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
import type { WorkspaceInspectResult } from "@/types";
@@ -426,7 +425,7 @@ export function AssistantWorkspaceSection() {
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-muted-foreground" />
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
</div>
);
}
@@ -445,11 +444,11 @@ export function AssistantWorkspaceSection() {
const renderValidationIcon = () => {
switch (pathValidation) {
case 'checking':
return <HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin text-muted-foreground" />;
return <SpinnerGap size={16} className="animate-spin text-muted-foreground" />;
case 'valid':
return <HugeiconsIcon icon={CheckmarkCircle02Icon} className="h-4 w-4 text-green-500" />;
return <CheckCircle size={16} className="text-green-500" />;
case 'invalid':
return <HugeiconsIcon icon={Cancel01Icon} className="h-4 w-4 text-red-500" />;
return <X size={16} className="text-red-500" />;
default:
return null;
}
@@ -489,7 +488,7 @@ export function AssistantWorkspaceSection() {
>
{inspecting ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.inspecting')}
</>
) : (
@@ -538,7 +537,7 @@ export function AssistantWorkspaceSection() {
disabled={creatingSession}
>
{creatingSession ? (
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin" />
<SpinnerGap size={14} className="animate-spin" />
) : workspace.state?.onboardingComplete
? t('assistant.redoOnboarding')
: t('assistant.startOnboarding')
@@ -575,7 +574,7 @@ export function AssistantWorkspaceSection() {
disabled={creatingSession}
>
{creatingSession ? (
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin" />
<SpinnerGap size={14} className="animate-spin" />
) : (
t('assistant.startCheckIn')
)}
@@ -639,7 +638,7 @@ export function AssistantWorkspaceSection() {
>
{refreshingDocs ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.refreshingDocs')}
</>
) : (
@@ -714,7 +713,7 @@ export function AssistantWorkspaceSection() {
>
{reindexing ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.indexReindexing')}
</>
) : (
@@ -738,7 +737,7 @@ export function AssistantWorkspaceSection() {
>
{archiving ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.organizeArchiving')}
</>
) : (
@@ -767,7 +766,7 @@ export function AssistantWorkspaceSection() {
<AlertDialogAction onClick={() => executeSave(true)} disabled={initializing}>
{initializing ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.initializing')}
</>
) : (
@@ -792,7 +791,7 @@ export function AssistantWorkspaceSection() {
<AlertDialogAction onClick={() => executeSave(true)} disabled={initializing}>
{initializing ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.initializing')}
</>
) : (
@@ -820,7 +819,7 @@ export function AssistantWorkspaceSection() {
<AlertDialogAction onClick={() => executeSave(true)} disabled={initializing}>
{initializing ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.initializing')}
</>
) : (
@@ -868,7 +867,7 @@ export function AssistantWorkspaceSection() {
<Button size="sm" variant="outline" onClick={() => executeSave(false, true, 'new')} disabled={initializing}>
{initializing ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.initializing')}
</>
) : (
@@ -894,7 +893,7 @@ export function AssistantWorkspaceSection() {
<AlertDialogAction onClick={() => executeSave(true)} disabled={initializing}>
{initializing ? (
<>
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin mr-1" />
<SpinnerGap size={14} className="animate-spin mr-1" />
{t('assistant.initializing')}
</>
) : (
+16 -17
View File
@@ -17,14 +17,13 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { HugeiconsIcon } from "@hugeicons/react";
import {
FloppyDiskIcon,
ReloadIcon,
CodeIcon,
SlidersHorizontalIcon,
Loading02Icon,
} from "@hugeicons/core-free-icons";
FloppyDisk,
ArrowClockwise,
Code,
SlidersHorizontal,
SpinnerGap,
} from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
import type { TranslationKey } from "@/i18n";
@@ -163,7 +162,7 @@ export function CliSettingsSection() {
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-muted-foreground" />
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
<span className="ml-2 text-sm text-muted-foreground">{t('cli.loadingSettings')}</span>
</div>
);
@@ -174,11 +173,11 @@ export function CliSettingsSection() {
<Tabs defaultValue="form">
<TabsList className="mb-4">
<TabsTrigger value="form" className="gap-2">
<HugeiconsIcon icon={SlidersHorizontalIcon} className="h-4 w-4" />
<SlidersHorizontal size={16} />
{t('cli.form')}
</TabsTrigger>
<TabsTrigger value="json" className="gap-2">
<HugeiconsIcon icon={CodeIcon} className="h-4 w-4" />
<Code size={16} />
{t('cli.json')}
</TabsTrigger>
</TabsList>
@@ -256,14 +255,14 @@ export function CliSettingsSection() {
<div className="flex items-center gap-3">
<Button onClick={() => confirmSave("form")} disabled={!hasChanges || saving} className="gap-2">
{saving ? (
<HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin" />
<SpinnerGap size={16} className="animate-spin" />
) : (
<HugeiconsIcon icon={FloppyDiskIcon} className="h-4 w-4" />
<FloppyDisk size={16} />
)}
{saving ? t('provider.saving') : t('cli.save')}
</Button>
<Button variant="outline" onClick={handleReset} disabled={!hasChanges} className="gap-2">
<HugeiconsIcon icon={ReloadIcon} className="h-4 w-4" />
<ArrowClockwise size={16} />
{t('cli.reset')}
</Button>
{saveSuccess && (
@@ -291,18 +290,18 @@ export function CliSettingsSection() {
<div className="flex items-center gap-3">
<Button onClick={() => confirmSave("json")} disabled={saving} className="gap-2">
{saving ? (
<HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin" />
<SpinnerGap size={16} className="animate-spin" />
) : (
<HugeiconsIcon icon={FloppyDiskIcon} className="h-4 w-4" />
<FloppyDisk size={16} />
)}
{saving ? t('provider.saving') : t('cli.save')}
</Button>
<Button variant="outline" onClick={handleFormatJson} className="gap-2">
<HugeiconsIcon icon={CodeIcon} className="h-4 w-4" />
<Code size={16} />
{t('cli.format')}
</Button>
<Button variant="outline" onClick={handleReset} className="gap-2">
<HugeiconsIcon icon={ReloadIcon} className="h-4 w-4" />
<ArrowClockwise size={16} />
{t('cli.reset')}
</Button>
{saveSuccess && (
+3 -4
View File
@@ -13,8 +13,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { HugeiconsIcon } from "@hugeicons/react";
import { ReloadIcon, Loading02Icon } from "@hugeicons/core-free-icons";
import { ArrowClockwise, SpinnerGap } from "@phosphor-icons/react";
import { useUpdate } from "@/hooks/useUpdate";
import { useTranslation } from "@/hooks/useTranslation";
import { SUPPORTED_LOCALES, type Locale } from "@/i18n";
@@ -62,9 +61,9 @@ function UpdateCard() {
className="gap-2"
>
{checking ? (
<HugeiconsIcon icon={Loading02Icon} className="h-3.5 w-3.5 animate-spin" />
<SpinnerGap size={14} className="animate-spin" />
) : (
<HugeiconsIcon icon={ReloadIcon} className="h-3.5 w-3.5" />
<ArrowClockwise size={14} />
)}
{checking ? t('settings.checking') : t('settings.checkForUpdates')}
</Button>
+3 -7
View File
@@ -20,8 +20,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon, ArrowDown01Icon, ArrowUp01Icon } from "@hugeicons/core-free-icons";
import { SpinnerGap, CaretDown, CaretUp } from "@phosphor-icons/react";
import type { ApiProvider } from "@/types";
import { useTranslation } from "@/hooks/useTranslation";
@@ -284,10 +283,7 @@ export function ProviderForm({
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
onClick={() => setShowAdvanced(!showAdvanced)}
>
<HugeiconsIcon
icon={showAdvanced ? ArrowUp01Icon : ArrowDown01Icon}
className="h-3 w-3"
/>
{showAdvanced ? <CaretUp size={12} /> : <CaretDown size={12} />}
{t('provider.advancedOptions')}
</button>
@@ -380,7 +376,7 @@ export function ProviderForm({
</Button>
<Button type="submit" disabled={saving} className="gap-2">
{saving && (
<HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin" />
<SpinnerGap size={16} className="animate-spin" />
)}
{saving ? t('provider.saving') : mode === "edit" ? t('provider.update') : t('provider.addProvider')}
</Button>
+14 -18
View File
@@ -31,15 +31,14 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Loading02Icon,
PencilEdit01Icon,
ArrowDown01Icon,
ArrowUp01Icon,
ServerStack01Icon,
Settings02Icon,
} from "@hugeicons/core-free-icons";
SpinnerGap,
PencilSimple,
CaretDown,
CaretUp,
HardDrives,
Gear,
} from "@phosphor-icons/react";
import { ProviderForm } from "./ProviderForm";
import type { ProviderFormData } from "./ProviderForm";
import type { ApiProvider } from "@/types";
@@ -81,7 +80,7 @@ function getProviderIcon(name: string, baseUrl: string): ReactNode {
if (lower.includes("aws")) return <Aws size={18} />;
if (lower.includes("anthropic") || url.includes("anthropic")) return <Anthropic size={18} />;
return <HugeiconsIcon icon={ServerStack01Icon} className="h-[18px] w-[18px] text-muted-foreground" />;
return <HardDrives size={18} className="text-muted-foreground" />;
}
// ---------------------------------------------------------------------------
@@ -113,7 +112,7 @@ const QUICK_PRESETS: QuickPreset[] = [
name: "Custom API (OpenAI-compatible)",
description: "OpenAI-compatible custom endpoint",
descriptionZh: "自定义 OpenAI 兼容 API 端点",
icon: <HugeiconsIcon icon={Settings02Icon} className="h-[18px] w-[18px] text-muted-foreground" />,
icon: <Gear size={18} className="text-muted-foreground" />,
provider_type: "custom",
protocol: "openai-compatible",
base_url: "",
@@ -284,7 +283,7 @@ const QUICK_PRESETS: QuickPreset[] = [
name: "LiteLLM",
description: "LiteLLM proxy — local or remote",
descriptionZh: "LiteLLM 代理 — 本地或远程",
icon: <HugeiconsIcon icon={ServerStack01Icon} className="h-[18px] w-[18px] text-muted-foreground" />,
icon: <HardDrives size={18} className="text-muted-foreground" />,
provider_type: "anthropic",
protocol: "anthropic",
base_url: "http://localhost:4000",
@@ -686,10 +685,7 @@ function PresetConnectDialog({
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
onClick={() => setShowAdvanced(!showAdvanced)}
>
<HugeiconsIcon
icon={showAdvanced ? ArrowUp01Icon : ArrowDown01Icon}
className="h-3 w-3"
/>
{showAdvanced ? <CaretUp size={12} /> : <CaretDown size={12} />}
{t('provider.advancedOptions')}
</button>
{showAdvanced && (
@@ -793,7 +789,7 @@ function PresetConnectDialog({
{t('common.cancel')}
</Button>
<Button type="submit" disabled={saving} className="gap-2">
{saving && <HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin" />}
{saving && <SpinnerGap size={16} className="animate-spin" />}
{saving ? t('provider.saving') : isEdit ? t('provider.update') : t('provider.connect')}
</Button>
</DialogFooter>
@@ -983,7 +979,7 @@ export function ProviderManager() {
{/* Loading */}
{loading && (
<div className="flex items-center justify-center gap-2 py-8 text-muted-foreground">
<HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin" />
<SpinnerGap size={16} className="animate-spin" />
<p className="text-sm">{t('common.loading')}</p>
</div>
)}
@@ -1046,7 +1042,7 @@ export function ProviderManager() {
title="Edit"
onClick={() => handleEdit(provider)}
>
<HugeiconsIcon icon={PencilEdit01Icon} className="h-3 w-3" />
<PencilSimple size={12} />
</Button>
<Button
variant="ghost"
+9 -15
View File
@@ -1,14 +1,8 @@
"use client";
import { useState, useCallback, useSyncExternalStore } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import type { IconSvgElement } from "@hugeicons/react";
import {
Settings02Icon,
CodeIcon,
AiUserIcon,
} from "@hugeicons/core-free-icons";
import { Plug01Icon, Analytics02Icon } from "@hugeicons/core-free-icons";
import type { Icon } from "@phosphor-icons/react";
import { Gear, Code, UserCircle, Plug, ChartBar } from "@phosphor-icons/react";
import { cn } from "@/lib/utils";
import { GeneralSection } from "./GeneralSection";
import { ProviderManager } from "./ProviderManager";
@@ -23,15 +17,15 @@ type Section = "general" | "providers" | "cli" | "usage" | "assistant";
interface SidebarItem {
id: Section;
label: string;
icon: IconSvgElement;
icon: Icon;
}
const sidebarItems: SidebarItem[] = [
{ id: "general", label: "General", icon: Settings02Icon },
{ id: "providers", label: "Providers", icon: Plug01Icon },
{ id: "cli", label: "Claude CLI", icon: CodeIcon },
{ id: "usage", label: "Usage", icon: Analytics02Icon },
{ id: "assistant", label: "Assistant", icon: AiUserIcon },
{ id: "general", label: "General", icon: Gear },
{ id: "providers", label: "Providers", icon: Plug },
{ id: "cli", label: "Claude CLI", icon: Code },
{ id: "usage", label: "Usage", icon: ChartBar },
{ id: "assistant", label: "Assistant", icon: UserCircle },
];
function getSectionFromHash(): Section {
@@ -97,7 +91,7 @@ export function SettingsLayout() {
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
)}
>
<HugeiconsIcon icon={item.icon} className="h-4 w-4 shrink-0" />
<item.icon size={16} className="shrink-0" />
{t(settingsLabelKeys[item.label])}
</button>
))}
+4 -5
View File
@@ -12,8 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon, GlobeIcon, FolderOpenIcon } from "@hugeicons/core-free-icons";
import { SpinnerGap, Globe, FolderOpen } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
import { cn } from "@/lib/utils";
@@ -138,7 +137,7 @@ export function CreateSkillDialog({
: "border-border hover:bg-accent"
)}
>
<HugeiconsIcon icon={FolderOpenIcon} className="h-4 w-4" />
<FolderOpen size={16} />
{t('skills.project')}
</button>
<button
@@ -151,7 +150,7 @@ export function CreateSkillDialog({
: "border-border hover:bg-accent"
)}
>
<HugeiconsIcon icon={GlobeIcon} className="h-4 w-4" />
<Globe size={16} />
{t('skills.global')}
</button>
</div>
@@ -196,7 +195,7 @@ export function CreateSkillDialog({
{t('common.cancel')}
</Button>
<Button onClick={handleCreate} disabled={creating} className="gap-2">
{creating && <HugeiconsIcon icon={Loading02Icon} className="h-4 w-4 animate-spin" />}
{creating && <SpinnerGap size={16} className="animate-spin" />}
{t('skills.createSkill')}
</Button>
</DialogFooter>
@@ -9,8 +9,7 @@ import {
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { HugeiconsIcon } from "@hugeicons/react";
import { Loading02Icon, CheckmarkCircle02Icon, Cancel01Icon } from "@hugeicons/core-free-icons";
import { SpinnerGap, CheckCircle, X } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
interface InstallProgressDialogProps {
@@ -140,13 +139,13 @@ export function InstallProgressDialog({
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
{phase === "running" && (
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-primary" />
<SpinnerGap size={20} className="animate-spin text-primary" />
)}
{phase === "success" && (
<HugeiconsIcon icon={CheckmarkCircle02Icon} className="h-5 w-5 text-green-500" />
<CheckCircle size={20} className="text-green-500" />
)}
{phase === "error" && (
<HugeiconsIcon icon={Cancel01Icon} className="h-5 w-5 text-red-500" />
<X size={20} className="text-red-500" />
)}
{phase === "running"
? t('skills.installing')
+7 -8
View File
@@ -2,8 +2,7 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { Input } from "@/components/ui/input";
import { HugeiconsIcon } from "@hugeicons/react";
import { Search01Icon, Loading02Icon, Store01Icon } from "@hugeicons/core-free-icons";
import { MagnifyingGlass, SpinnerGap, Storefront } from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
import { MarketplaceSkillCard } from "./MarketplaceSkillCard";
import { MarketplaceSkillDetail } from "./MarketplaceSkillDetail";
@@ -72,9 +71,9 @@ export function MarketplaceBrowser({ onInstalled }: MarketplaceBrowserProps) {
<div className="w-64 shrink-0 flex flex-col border border-border rounded-lg overflow-hidden">
<div className="p-2 border-b border-border">
<div className="relative">
<HugeiconsIcon
icon={Search01Icon}
className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground"
<MagnifyingGlass
size={14}
className="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground"
/>
<Input
placeholder={t('skills.marketplaceSearch')}
@@ -88,7 +87,7 @@ export function MarketplaceBrowser({ onInstalled }: MarketplaceBrowserProps) {
<div className="p-1">
{loading && results.length === 0 && (
<div className="flex items-center justify-center py-8">
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-muted-foreground" />
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
</div>
)}
{error && (
@@ -99,7 +98,7 @@ export function MarketplaceBrowser({ onInstalled }: MarketplaceBrowserProps) {
)}
{!loading && !error && results.length === 0 && (
<div className="flex flex-col items-center gap-2 py-8 text-muted-foreground">
<HugeiconsIcon icon={Store01Icon} className="h-8 w-8 opacity-40" />
<Storefront size={32} className="opacity-40" />
<p className="text-xs">{t('skills.searchNoResults')}</p>
</div>
)}
@@ -125,7 +124,7 @@ export function MarketplaceBrowser({ onInstalled }: MarketplaceBrowserProps) {
/>
) : (
<div className="flex flex-col items-center justify-center h-full text-muted-foreground gap-3">
<HugeiconsIcon icon={Store01Icon} className="h-12 w-12 opacity-30" />
<Storefront size={48} className="opacity-30" />
<div className="text-center">
<p className="text-sm font-medium">{t('skills.marketplaceHint')}</p>
<p className="text-xs">{t('skills.marketplaceHintDesc')}</p>
@@ -1,7 +1,6 @@
"use client";
import { HugeiconsIcon } from "@hugeicons/react";
import { ZapIcon, Download04Icon, CheckmarkCircle02Icon } from "@hugeicons/core-free-icons";
import { Lightning, DownloadSimple, CheckCircle } from "@phosphor-icons/react";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { useTranslation } from "@/hooks/useTranslation";
@@ -30,7 +29,7 @@ export function MarketplaceSkillCard({
)}
onClick={onSelect}
>
<HugeiconsIcon icon={ZapIcon} className="h-4 w-4 shrink-0 text-muted-foreground" />
<Lightning size={16} className="shrink-0 text-muted-foreground" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="text-sm font-medium truncate">{skill.name}</span>
@@ -39,7 +38,7 @@ export function MarketplaceSkillCard({
variant="outline"
className="text-[10px] px-1.5 py-0 border-green-500/40 text-green-600 dark:text-green-400"
>
<HugeiconsIcon icon={CheckmarkCircle02Icon} className="h-2.5 w-2.5 mr-0.5" />
<CheckCircle size={10} className="mr-0.5" />
{t('skills.installed')}
</Badge>
) : null}
@@ -48,7 +47,7 @@ export function MarketplaceSkillCard({
<span className="truncate">{skill.source}</span>
{skill.installs > 0 && (
<span className="flex items-center gap-0.5 shrink-0">
<HugeiconsIcon icon={Download04Icon} className="h-3 w-3" />
<DownloadSimple size={12} />
{skill.installs.toLocaleString()}
</span>
)}
@@ -3,15 +3,14 @@
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Download04Icon,
Delete02Icon,
CheckmarkCircle02Icon,
LinkSquare01Icon,
ZapIcon,
Loading02Icon,
} from "@hugeicons/core-free-icons";
DownloadSimple,
Trash,
CheckCircle,
ArrowSquareOut,
Lightning,
SpinnerGap,
} from "@phosphor-icons/react";
import { useTranslation } from "@/hooks/useTranslation";
import { InstallProgressDialog } from "./InstallProgressDialog";
import ReactMarkdown from "react-markdown";
@@ -85,7 +84,7 @@ export function MarketplaceSkillDetail({
<div className="border-b border-border px-6 py-4 shrink-0">
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-accent/50 shrink-0">
<HugeiconsIcon icon={ZapIcon} className="h-5 w-5 text-muted-foreground" />
<Lightning size={20} className="text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
@@ -95,7 +94,7 @@ export function MarketplaceSkillDetail({
variant="outline"
className="text-[10px] px-1.5 py-0 border-green-500/40 text-green-600 dark:text-green-400 shrink-0"
>
<HugeiconsIcon icon={CheckmarkCircle02Icon} className="h-2.5 w-2.5 mr-0.5" />
<CheckCircle size={10} className="mr-0.5" />
{t('skills.installed')}
</Badge>
)}
@@ -109,12 +108,12 @@ export function MarketplaceSkillDetail({
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors shrink-0"
>
<HugeiconsIcon icon={LinkSquare01Icon} className="h-3.5 w-3.5" />
<ArrowSquareOut size={14} />
</a>
)}
{skill.installs > 0 && (
<span className="flex items-center gap-0.5 text-xs text-muted-foreground shrink-0">
<HugeiconsIcon icon={Download04Icon} className="h-3 w-3" />
<DownloadSimple size={12} />
{skill.installs.toLocaleString()}
</span>
)}
@@ -128,12 +127,12 @@ export function MarketplaceSkillDetail({
className="gap-1.5"
onClick={handleUninstall}
>
<HugeiconsIcon icon={Delete02Icon} className="h-3.5 w-3.5" />
<Trash size={14} />
{t('skills.uninstall')}
</Button>
) : (
<Button size="sm" className="gap-1.5" onClick={handleInstall}>
<HugeiconsIcon icon={Download04Icon} className="h-3.5 w-3.5" />
<DownloadSimple size={14} />
{t('skills.install')}
</Button>
)}
@@ -145,7 +144,7 @@ export function MarketplaceSkillDetail({
<div className="flex-1 overflow-y-auto min-h-0">
{readmeLoading ? (
<div className="flex items-center justify-center py-12">
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-muted-foreground" />
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
</div>
) : displayContent ? (
<div className="prose prose-sm dark:prose-invert max-w-none px-6 py-4">
+18 -19
View File
@@ -4,17 +4,16 @@ import { useState, useCallback, useRef, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Textarea } from "@/components/ui/textarea";
import { HugeiconsIcon } from "@hugeicons/react";
import {
FloppyDiskIcon,
Delete02Icon,
EyeIcon,
Edit02Icon,
GlobeIcon,
FolderOpenIcon,
Loading02Icon,
LayoutTwoColumnIcon,
} from "@hugeicons/core-free-icons";
FloppyDisk,
Trash,
Eye,
PencilLine,
Globe,
FolderOpen,
SpinnerGap,
Columns,
} from "@phosphor-icons/react";
import {
Tooltip,
TooltipContent,
@@ -131,11 +130,11 @@ export function SkillEditor({ skill, onSave, onDelete }: SkillEditorProps) {
)}
>
{skill.source === "global" ? (
<HugeiconsIcon icon={GlobeIcon} className="h-2.5 w-2.5 mr-0.5" />
<Globe size={10} className="mr-0.5" />
) : skill.source === "installed" ? (
<HugeiconsIcon icon={FolderOpenIcon} className="h-2.5 w-2.5 mr-0.5" />
<FolderOpen size={10} className="mr-0.5" />
) : (
<HugeiconsIcon icon={FolderOpenIcon} className="h-2.5 w-2.5 mr-0.5" />
<FolderOpen size={10} className="mr-0.5" />
)}
{skill.source === "installed" && skill.installedSource
? `installed:${skill.installedSource}`
@@ -152,7 +151,7 @@ export function SkillEditor({ skill, onSave, onDelete }: SkillEditorProps) {
size="icon-xs"
onClick={() => setViewMode("edit")}
>
<HugeiconsIcon icon={Edit02Icon} className="h-3 w-3" />
<PencilLine size={12} />
</Button>
</TooltipTrigger>
<TooltipContent>{t('skills.edit')}</TooltipContent>
@@ -164,7 +163,7 @@ export function SkillEditor({ skill, onSave, onDelete }: SkillEditorProps) {
size="icon-xs"
onClick={() => setViewMode("preview")}
>
<HugeiconsIcon icon={EyeIcon} className="h-3 w-3" />
<Eye size={12} />
</Button>
</TooltipTrigger>
<TooltipContent>{t('skills.preview')}</TooltipContent>
@@ -176,7 +175,7 @@ export function SkillEditor({ skill, onSave, onDelete }: SkillEditorProps) {
size="icon-xs"
onClick={() => setViewMode("split")}
>
<HugeiconsIcon icon={LayoutTwoColumnIcon} className="h-3 w-3" />
<Columns size={12} />
</Button>
</TooltipTrigger>
<TooltipContent>{t('skills.splitView')}</TooltipContent>
@@ -192,9 +191,9 @@ export function SkillEditor({ skill, onSave, onDelete }: SkillEditorProps) {
className="gap-1"
>
{saving ? (
<HugeiconsIcon icon={Loading02Icon} className="h-3 w-3 animate-spin" />
<SpinnerGap size={12} className="animate-spin" />
) : (
<HugeiconsIcon icon={FloppyDiskIcon} className="h-3 w-3" />
<FloppyDisk size={12} />
)}
{saving ? "Saving" : saved ? t('skills.saved') : t('skills.save')}
</Button>
@@ -205,7 +204,7 @@ export function SkillEditor({ skill, onSave, onDelete }: SkillEditorProps) {
size="icon-xs"
onClick={handleDelete}
>
<HugeiconsIcon icon={Delete02Icon} className="h-3 w-3" />
<Trash size={12} />
</Button>
</div>
</div>
+3 -4
View File
@@ -1,7 +1,6 @@
"use client";
import { HugeiconsIcon } from "@hugeicons/react";
import { ZapIcon, Delete02Icon, GlobeIcon, Plug01Icon, Download04Icon } from "@hugeicons/core-free-icons";
import { Lightning, Trash } from "@phosphor-icons/react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@@ -66,7 +65,7 @@ export function SkillListItem({
setConfirmDelete(false);
}}
>
<HugeiconsIcon icon={ZapIcon} className="h-4 w-4 shrink-0 text-muted-foreground" />
<Lightning size={16} className="shrink-0 text-muted-foreground" />
<div className="flex-1 min-w-0">
<span className="text-sm font-medium truncate block">/{skill.name}</span>
<p className="text-xs text-muted-foreground truncate">
@@ -82,7 +81,7 @@ export function SkillListItem({
className="shrink-0"
onClick={handleDelete}
>
<HugeiconsIcon icon={Delete02Icon} className="h-3 w-3" />
<Trash size={12} />
</Button>
</TooltipTrigger>
<TooltipContent side="right">
+8 -9
View File
@@ -5,8 +5,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { usePanel } from "@/hooks/usePanel";
import { HugeiconsIcon } from "@hugeicons/react";
import { PlusSignIcon, Search01Icon, ZapIcon, Loading02Icon } from "@hugeicons/core-free-icons";
import { Plus, MagnifyingGlass, Lightning, SpinnerGap } from "@phosphor-icons/react";
import { SkillListItem } from "./SkillListItem";
import { SkillEditor } from "./SkillEditor";
import { CreateSkillDialog } from "./CreateSkillDialog";
@@ -143,7 +142,7 @@ export function SkillsManager() {
if (loading) {
return (
<div className="flex h-64 items-center justify-center">
<HugeiconsIcon icon={Loading02Icon} className="h-5 w-5 animate-spin text-muted-foreground" />
<SpinnerGap size={20} className="animate-spin text-muted-foreground" />
<span className="ml-2 text-sm text-muted-foreground">
{t('skills.loadingSkills')}
</span>
@@ -184,7 +183,7 @@ export function SkillsManager() {
<div className="flex-1" />
{viewTab === "local" && (
<Button size="sm" onClick={() => setShowCreate(true)} className="gap-1">
<HugeiconsIcon icon={PlusSignIcon} className="h-3.5 w-3.5" />
<Plus size={14} />
{t('skills.newSkill')}
</Button>
)}
@@ -199,7 +198,7 @@ export function SkillsManager() {
<div className="w-64 shrink-0 flex flex-col border border-border rounded-lg overflow-hidden">
<div className="p-2 border-b border-border">
<div className="relative">
<HugeiconsIcon icon={Search01Icon} className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<MagnifyingGlass size={14} className="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder={t('skills.searchSkills')}
value={search}
@@ -272,7 +271,7 @@ export function SkillsManager() {
)}
{filtered.length === 0 && (
<div className="flex flex-col items-center gap-2 py-8 text-muted-foreground">
<HugeiconsIcon icon={ZapIcon} className="h-8 w-8 opacity-40" />
<Lightning size={32} className="opacity-40" />
<p className="text-xs">
{search ? t('skills.noSkillsFound') : t('skills.noSkillsFound')}
</p>
@@ -283,7 +282,7 @@ export function SkillsManager() {
onClick={() => setShowCreate(true)}
className="gap-1"
>
<HugeiconsIcon icon={PlusSignIcon} className="h-3 w-3" />
<Plus size={12} />
Create one
</Button>
)}
@@ -304,7 +303,7 @@ export function SkillsManager() {
/>
) : (
<div className="flex flex-col items-center justify-center h-full text-muted-foreground gap-3">
<HugeiconsIcon icon={ZapIcon} className="h-12 w-12 opacity-30" />
<Lightning size={48} className="opacity-30" />
<div className="text-center">
<p className="text-sm font-medium">{t('skills.noSelected')}</p>
<p className="text-xs">
@@ -317,7 +316,7 @@ export function SkillsManager() {
onClick={() => setShowCreate(true)}
className="gap-1"
>
<HugeiconsIcon icon={PlusSignIcon} className="h-3.5 w-3.5" />
<Plus size={14} />
{t('skills.newSkill')}
</Button>
</div>