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:
1 parent
5adeb3548e
commit
e5a48ca6af
61 files changed
+606
-752
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
Generated
+15
-25
@@ -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
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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"
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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,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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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')}
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user