From e3d164e9d2c87d5f35258187aa8a1befccd6bf3e Mon Sep 17 00:00:00 2001 From: iven Date: Sun, 15 Mar 2026 17:24:40 +0800 Subject: [PATCH] feat(ui): enhance UI with animations, dark mode support and and improved components - Add framer-motion page transitions and AnimatePresence support - Add dark mode support across all components - Create reusable UI components (Button, Badge, Card, EmptyState, Input, Toast, Skeleton) - Add CSS custom properties for consistent theming - Add animation variants and utility functions - Improve ChatArea, Sidebar, TriggersPanel with animations Co-Authored-By: Claude Opus 4.6 --- desktop/package.json | 4 + desktop/pnpm-lock.yaml | 69 +++++++++++++ desktop/src/App.tsx | 89 ++++++++-------- desktop/src/components/ChatArea.tsx | 124 +++++++++++++++-------- desktop/src/components/Sidebar.tsx | 62 ++++++++---- desktop/src/components/TriggersPanel.tsx | 2 - desktop/src/components/ui/Badge.tsx | 32 ++++++ desktop/src/components/ui/Button.tsx | 54 ++++++++++ desktop/src/components/ui/Card.tsx | 64 ++++++++++++ desktop/src/components/ui/EmptyState.tsx | 28 +++++ desktop/src/components/ui/Input.tsx | 45 ++++++++ desktop/src/components/ui/Skeleton.tsx | 42 ++++++++ desktop/src/components/ui/Toast.tsx | 89 ++++++++++++++++ desktop/src/components/ui/index.ts | 15 +++ desktop/src/index.css | 62 +++++++++++- desktop/src/lib/animations.ts | 62 ++++++++++++ desktop/src/lib/utils.ts | 6 ++ desktop/tailwind.config.js | 31 +++++- 18 files changed, 772 insertions(+), 108 deletions(-) create mode 100644 desktop/src/components/ui/Badge.tsx create mode 100644 desktop/src/components/ui/Button.tsx create mode 100644 desktop/src/components/ui/Card.tsx create mode 100644 desktop/src/components/ui/EmptyState.tsx create mode 100644 desktop/src/components/ui/Input.tsx create mode 100644 desktop/src/components/ui/Skeleton.tsx create mode 100644 desktop/src/components/ui/Toast.tsx create mode 100644 desktop/src/components/ui/index.ts create mode 100644 desktop/src/lib/animations.ts create mode 100644 desktop/src/lib/utils.ts diff --git a/desktop/package.json b/desktop/package.json index c2d21ac..b8cb662 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -22,9 +22,13 @@ "dependencies": { "@tauri-apps/api": "^2", "@tauri-apps/plugin-opener": "^2", + "clsx": "^2.1.1", + "framer-motion": "^12.36.0", "lucide-react": "^0.577.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "smol-toml": "^1.6.0", + "tailwind-merge": "^3.5.0", "tweetnacl": "^1.0.3", "zustand": "^5.0.11" }, diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml index 5f5d258..b7c1bbb 100644 --- a/desktop/pnpm-lock.yaml +++ b/desktop/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: '@tauri-apps/plugin-opener': specifier: ^2 version: 2.5.3 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + framer-motion: + specifier: ^12.36.0 + version: 12.36.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) lucide-react: specifier: ^0.577.0 version: 0.577.0(react@19.2.4) @@ -23,6 +29,12 @@ importers: react-dom: specifier: ^19.1.0 version: 19.2.4(react@19.2.4) + smol-toml: + specifier: ^1.6.0 + version: 1.6.0 + tailwind-merge: + specifier: ^3.5.0 + version: 3.5.0 tweetnacl: specifier: ^1.0.3 version: 1.0.3 @@ -684,6 +696,10 @@ packages: caniuse-lite@1.0.30001777: resolution: {integrity: sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -731,6 +747,20 @@ packages: fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + framer-motion@12.36.0: + resolution: {integrity: sha512-4PqYHAT7gev0ke0wos+PyrcFxI0HScjm3asgU8nSYa8YzJFuwgIvdj3/s3ZaxLq0bUSboIn19A2WS/MHwLCvfw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -845,6 +875,12 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + motion-dom@12.36.0: + resolution: {integrity: sha512-Ep1pq8P88rGJ75om8lTCA13zqd7ywPGwCqwuWwin6BKc0hMLkVfcS6qKlRqEo2+t0DwoUcgGJfXwaiFn4AOcQA==} + + motion-utils@12.36.0: + resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -895,10 +931,17 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + smol-toml@1.6.0: + resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} + engines: {node: '>= 18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwindcss@4.2.1: resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} @@ -910,6 +953,9 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tweetnacl@1.0.3: resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} @@ -1458,6 +1504,8 @@ snapshots: caniuse-lite@1.0.30001777: {} + clsx@2.1.1: {} + convert-source-map@2.0.0: {} csstype@3.2.3: {} @@ -1512,6 +1560,15 @@ snapshots: fraction.js@5.3.4: {} + framer-motion@12.36.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + motion-dom: 12.36.0 + motion-utils: 12.36.0 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + fsevents@2.3.3: optional: true @@ -1588,6 +1645,12 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + motion-dom@12.36.0: + dependencies: + motion-utils: 12.36.0 + + motion-utils@12.36.0: {} + ms@2.1.3: {} nanoid@3.3.11: {} @@ -1650,8 +1713,12 @@ snapshots: semver@6.3.1: {} + smol-toml@1.6.0: {} + source-map-js@1.2.1: {} + tailwind-merge@3.5.0: {} + tailwindcss@4.2.1: {} tapable@2.3.0: {} @@ -1661,6 +1728,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tslib@2.8.1: {} + tweetnacl@1.0.3: {} typescript@5.8.3: {} diff --git a/desktop/src/App.tsx b/desktop/src/App.tsx index a898493..193faf1 100644 --- a/desktop/src/App.tsx +++ b/desktop/src/App.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; import './index.css'; import { Sidebar, MainViewType } from './components/Sidebar'; import { ChatArea } from './components/ChatArea'; @@ -10,6 +11,9 @@ import { TeamCollaborationView } from './components/TeamCollaborationView'; import { useGatewayStore } from './store/gatewayStore'; import { useTeamStore } from './store/teamStore'; import { getStoredGatewayToken } from './lib/gateway-client'; +import { pageVariants, defaultTransition, fadeInVariants } from './lib/animations'; +import { Bot, Users } from 'lucide-react'; +import { EmptyState } from './components/ui'; type View = 'main' | 'settings'; @@ -66,48 +70,51 @@ function App() { /> {/* 中间区域 */} -
- {mainContentView === 'hands' && selectedHandId ? ( - setSelectedHandId(undefined)} - /> - ) : mainContentView === 'hands' ? ( -
-
-
- 🤖 -
-

选择一个 Hand

-

- 从左侧列表中选择一个自主能力包,查看其任务清单和执行结果。 -

-
-
- ) : mainContentView === 'workflow' ? ( -
- -
- ) : mainContentView === 'team' ? ( - activeTeam ? ( - + + + {mainContentView === 'hands' && selectedHandId ? ( + setSelectedHandId(undefined)} + /> + ) : mainContentView === 'hands' ? ( + } + title="Select a Hand" + description="Choose an autonomous capability package from the list on the left to view its task list and execution results." + /> + ) : mainContentView === 'workflow' ? ( + + + + ) : mainContentView === 'team' ? ( + activeTeam ? ( + + ) : ( + } + title="Select or Create a Team" + description="Choose a team from the list on the left, or click + to create a new multi-Agent collaboration team." + /> + ) ) : ( -
-
-
- 👥 -
-

选择或创建团队

-

- 从左侧列表选择一个团队,或点击 + 创建新的多 Agent 协作团队。 -

-
-
- ) - ) : ( - - )} -
+ + )} + + {/* 右侧边栏 */} diff --git a/desktop/src/components/ChatArea.tsx b/desktop/src/components/ChatArea.tsx index 20e7e0a..11a0aff 100644 --- a/desktop/src/components/ChatArea.tsx +++ b/desktop/src/components/ChatArea.tsx @@ -1,7 +1,10 @@ import { useState, useEffect, useRef, useCallback } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; import { useChatStore, Message } from '../store/chatStore'; import { useGatewayStore } from '../store/gatewayStore'; -import { Paperclip, ChevronDown, Terminal, SquarePen, ArrowUp } from 'lucide-react'; +import { Paperclip, ChevronDown, Terminal, SquarePen, ArrowUp, MessageSquare } from 'lucide-react'; +import { Button, EmptyState } from './ui'; +import { listItemVariants, defaultTransition, fadeInVariants } from '../lib/animations'; const MODELS = ['glm-5', 'qwen3.5-plus', 'kimi-k2.5', 'minimax-m2.5']; @@ -58,56 +61,84 @@ export function ChatArea() { return ( <> {/* Header */} -
+
-

{currentAgent?.name || 'ZCLAW'}

+

{currentAgent?.name || 'ZCLAW'}

{isStreaming ? ( - - + + 正在输入中 ) : ( - - + + {connected ? 'Gateway 已连接' : 'Gateway 未连接'} )}
{messages.length > 0 && ( - + )}
{/* Messages */} -
- {messages.length === 0 && ( -
-

欢迎使用 ZCLAW 🦞

-

{connected ? '发送消息开始对话' : '请先在设置中连接 Gateway'}

-
- )} +
+ + {messages.length === 0 && ( + + } + title="欢迎使用 ZCLAW" + description={connected ? '发送消息开始对话' : '请先在设置中连接 Gateway'} + /> + + )} - {messages.map((message) => ( - - ))} + {messages.map((message) => ( + + + + ))} +
{/* Input */} -
+
-
- +