feat(ui): add light theme with system auto-switch + Cyrene avatar
- Miuix day/night dual theme CSS variables - themeStore: light default, follows prefers-color-scheme - Header: sun/moon toggle button - All components migrated to CSS variables - Cyrene avatar image replaces 'C' logo everywhere - Favicon added
This commit is contained in:
1 parent
78c5f0f1b0
commit
5faa955bce
24 files changed
+337
-218
No files matched your search
@@ -18,9 +18,9 @@ export default function AdminLayout() {
|
||||
return (
|
||||
<div className="flex h-full">
|
||||
{/* Sidebar */}
|
||||
<nav className="w-52 bg-[#000] border-r border-[#393939] shrink-0 flex flex-col">
|
||||
<div className="p-4 border-b border-[#393939]">
|
||||
<h2 className="text-sm font-semibold text-white/80">管理面板</h2>
|
||||
<nav className="w-52 bg-[var(--mx-surface)] border-r border-[var(--mx-divider)] shrink-0 flex flex-col">
|
||||
<div className="p-4 border-b border-[var(--mx-divider)]">
|
||||
<h2 className="text-sm font-semibold text-[var(--mx-text-secondary)]">管理面板</h2>
|
||||
</div>
|
||||
<div className="flex-1 p-2 space-y-0.5">
|
||||
{navItems.map(item => (
|
||||
@@ -30,8 +30,8 @@ export default function AdminLayout() {
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 px-3 py-2.5 rounded-[14px] text-sm transition-colors ${
|
||||
isActive
|
||||
? 'bg-[#277AF7]/15 text-[#4788FF] font-medium'
|
||||
: 'text-white/50 hover:text-white/80 hover:bg-white/5'
|
||||
? 'bg-[var(--mx-primary)]/15 text-[var(--mx-tertiary-text)] font-medium'
|
||||
: 'text-[var(--mx-text-dim)] hover:text-[var(--mx-text-secondary)] hover:bg-[color-mix(in srgb, var(--mx-primary) 5%, transparent)]'
|
||||
}`
|
||||
}
|
||||
>
|
||||
@@ -43,7 +43,7 @@ export default function AdminLayout() {
|
||||
</nav>
|
||||
|
||||
{/* Content area */}
|
||||
<div className="flex-1 overflow-auto bg-[#242424]">
|
||||
<div className="flex-1 overflow-auto bg-[var(--mx-bg)]">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,10 +40,10 @@ export default function ChatContainer() {
|
||||
<div className="max-w-2xl mx-auto px-4 py-6">
|
||||
{messages.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center space-y-2 pt-32">
|
||||
<div className="w-16 h-16 rounded-[20px] bg-[#277AF7] flex items-center justify-center shadow-lg shadow-[#277AF7]/20 mb-2">
|
||||
<span className="text-2xl font-bold text-white">C</span>
|
||||
<div className="w-16 h-16 rounded-[20px] bg-[var(--mx-primary)] flex items-center justify-center shadow-lg shadow-[var(--mx-primary)]/20 mb-2">
|
||||
<img src="/images/cyrene-avatar.png" alt="Cyrene" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<p className="text-white/50 text-sm">开始新对话吧~</p>
|
||||
<p className="text-[var(--mx-text-dim)] text-sm">开始新对话吧~</p>
|
||||
</div>
|
||||
) : (
|
||||
<MessageList messages={messages} />
|
||||
@@ -53,7 +53,7 @@ export default function ChatContainer() {
|
||||
</div>
|
||||
|
||||
{/* Input bar */}
|
||||
<div className="shrink-0 border-t border-[#393939] bg-[#000]">
|
||||
<div className="shrink-0 border-t border-[var(--mx-divider)] bg-[var(--mx-surface)]">
|
||||
<div className="max-w-2xl mx-auto px-4 py-3">
|
||||
<ChatInput value={inputText} onChange={setInput} onSend={handleSend} disabled={isStreaming} />
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function ChatInput({ value, onChange, onSend, disabled }: ChatInp
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-end gap-2 bg-[#000] rounded-[20px] border border-[#404040] focus-within:border-[#277AF7] transition-colors p-1.5">
|
||||
<div className="flex items-end gap-2 bg-[var(--mx-surface)] rounded-[20px] border border-[var(--mx-outline)] focus-within:border-[var(--mx-primary)] transition-colors p-1.5">
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
@@ -22,12 +22,12 @@ export default function ChatInput({ value, onChange, onSend, disabled }: ChatInp
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={disabled}
|
||||
placeholder="输入消息..."
|
||||
className="flex-1 bg-transparent px-3 py-1.5 text-sm text-[#F2F2F2] placeholder:text-white/30 outline-none disabled:opacity-40"
|
||||
className="flex-1 bg-transparent px-3 py-1.5 text-sm text-[var(--mx-text)] placeholder:text-[var(--mx-text-dim)] outline-none disabled:opacity-40"
|
||||
/>
|
||||
<button
|
||||
onClick={onSend}
|
||||
disabled={disabled || !value.trim()}
|
||||
className="w-9 h-9 bg-[#277AF7] hover:bg-[#3482FF] active:scale-90 disabled:bg-[#253E64] text-white rounded-full flex items-center justify-center shrink-0 transition-all duration-200"
|
||||
className="w-9 h-9 bg-[var(--mx-primary)] hover:bg-[var(--mx-primary-hover)] active:scale-90 disabled:bg-[#253E64] text-white rounded-full flex items-center justify-center shrink-0 transition-all duration-200"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M2 8L14 2L8 14L6.5 9.5L2 8Z" fill="currentColor" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
|
||||
@@ -19,7 +19,7 @@ function MessageBubble({ message }: { message: Message }) {
|
||||
if (isAction) {
|
||||
return (
|
||||
<div className="flex justify-center py-1">
|
||||
<span className="text-xs text-[#4788FF] bg-[#2B3B54]/80 px-3 py-1 rounded-full">
|
||||
<span className="text-xs text-[var(--mx-tertiary-text)] bg-[var(--mx-badge-blue)]/80 px-3 py-1 rounded-full">
|
||||
{message.content}
|
||||
</span>
|
||||
</div>
|
||||
@@ -29,7 +29,7 @@ function MessageBubble({ message }: { message: Message }) {
|
||||
if (isSystem) {
|
||||
return (
|
||||
<div className="flex justify-center py-1">
|
||||
<span className="text-xs text-[#ffdad6] bg-[#2E0603]/80 px-3 py-1 rounded-full">{message.content}</span>
|
||||
<span className="text-xs text-[var(--mx-error-text)] bg-[var(--mx-error-container)]/80 px-3 py-1 rounded-full">{message.content}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -38,17 +38,17 @@ function MessageBubble({ message }: { message: Message }) {
|
||||
<div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>
|
||||
{/* Avatar for AI */}
|
||||
{!isUser && (
|
||||
<div className="w-8 h-8 rounded-xl bg-[#277AF7] flex items-center justify-center shrink-0 mr-2 mt-1">
|
||||
<span className="text-xs font-bold text-white">C</span>
|
||||
<div className="w-8 h-8 rounded-xl bg-[var(--mx-primary)] flex items-center justify-center shrink-0 mr-2 mt-1">
|
||||
<img src="/images/cyrene-avatar.png" alt="Cyrene" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`max-w-[75%] px-4 py-2.5 text-sm leading-relaxed ${
|
||||
isUser
|
||||
? 'bg-[#277AF7] text-white rounded-[18px] rounded-br-[6px]'
|
||||
? 'bg-[var(--mx-primary)] text-white rounded-[18px] rounded-br-[6px]'
|
||||
: isEmpty
|
||||
? 'bg-transparent text-transparent'
|
||||
: 'bg-[#000] text-[#F2F2F2] rounded-[18px] rounded-bl-[6px] border border-[#404040]'
|
||||
: 'bg-[var(--mx-surface)] text-[var(--mx-text)] rounded-[18px] rounded-bl-[6px] border border-[var(--mx-outline)]'
|
||||
}`}>
|
||||
{isEmpty ? (
|
||||
<span className="flex gap-1.5 py-1">
|
||||
|
||||
@@ -1,41 +1,51 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useAuthStore } from '../../store/authStore'
|
||||
import { useThemeStore } from '../../store/themeStore'
|
||||
|
||||
export default function AppLayout({ children }: { children: ReactNode }) {
|
||||
const { username, logout } = useAuthStore()
|
||||
const { theme, toggle } = useThemeStore()
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-[#242424] text-[#F2F2F2]">
|
||||
<div className="h-screen flex flex-col text-[var(--mx-text)]" style={{ background: 'var(--mx-bg)' }}>
|
||||
{/* Header */}
|
||||
<header className="h-14 shrink-0 flex items-center justify-between px-4 border-b border-[#393939] bg-[#000]">
|
||||
<header className="h-14 shrink-0 flex items-center justify-between px-4 border-b mx-glass" style={{ borderColor: 'var(--mx-divider)', background: 'var(--mx-surface)' }}>
|
||||
<Link to="/chat" className="flex items-center gap-2">
|
||||
<div className="w-7 h-7 rounded-lg bg-[#277AF7] flex items-center justify-center">
|
||||
<span className="text-sm font-bold text-white">C</span>
|
||||
<div className="w-7 h-7 rounded-lg overflow-hidden flex items-center justify-center" style={{ background: 'var(--mx-primary)' }}>
|
||||
<img src="/images/cyrene-avatar.png" alt="Cyrene" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<span className="font-semibold text-base">Cyrene</span>
|
||||
</Link>
|
||||
|
||||
<nav className="flex items-center gap-1">
|
||||
<Link to="/chat"
|
||||
className={`px-3 py-1.5 rounded-full text-sm transition-colors ${
|
||||
location.pathname.startsWith('/chat')
|
||||
? 'bg-[#277AF7]/15 text-[#4788FF]'
|
||||
: 'text-white/50 hover:text-white/80'
|
||||
}`}
|
||||
className="px-3 py-1.5 rounded-full text-sm transition-colors"
|
||||
style={{
|
||||
background: location.pathname.startsWith('/chat') ? 'color-mix(in srgb, var(--mx-primary) 15%, transparent)' : 'transparent',
|
||||
color: location.pathname.startsWith('/chat') ? 'var(--mx-tertiary-text)' : 'var(--mx-text-dim)',
|
||||
}}
|
||||
>聊天</Link>
|
||||
<Link to="/admin/dashboard"
|
||||
className={`px-3 py-1.5 rounded-full text-sm transition-colors ${
|
||||
location.pathname.startsWith('/admin')
|
||||
? 'bg-[#277AF7]/15 text-[#4788FF]'
|
||||
: 'text-white/50 hover:text-white/80'
|
||||
}`}
|
||||
className="px-3 py-1.5 rounded-full text-sm transition-colors"
|
||||
style={{
|
||||
background: location.pathname.startsWith('/admin') ? 'color-mix(in srgb, var(--mx-primary) 15%, transparent)' : 'transparent',
|
||||
color: location.pathname.startsWith('/admin') ? 'var(--mx-tertiary-text)' : 'var(--mx-text-dim)',
|
||||
}}
|
||||
>管理</Link>
|
||||
<div className="w-px h-4 bg-[#393939] mx-1" />
|
||||
<span className="text-xs text-white/40">{username}</span>
|
||||
<div className="w-px h-4 mx-1" style={{ background: 'var(--mx-divider)' }} />
|
||||
{/* Theme toggle */}
|
||||
<button onClick={toggle}
|
||||
className="w-8 h-8 rounded-full flex items-center justify-center text-lg transition-colors hover:opacity-80"
|
||||
title={theme === 'dark' ? '切换日间模式' : '切换夜间模式'}
|
||||
>
|
||||
{theme === 'dark' ? '☀️' : '🌙'}
|
||||
</button>
|
||||
<span className="text-xs" style={{ color: 'var(--mx-text-dim)' }}>{username}</span>
|
||||
<button onClick={logout}
|
||||
className="px-3 py-1.5 rounded-full text-sm text-white/40 hover:text-[#F12522] transition-colors"
|
||||
className="px-3 py-1.5 rounded-full text-sm transition-colors"
|
||||
style={{ color: 'var(--mx-text-dim)' }}
|
||||
>退出</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -16,10 +16,10 @@ export default function Sidebar() {
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="w-60 bg-[#000] border-r border-[#393939] shrink-0 flex flex-col">
|
||||
<aside className="w-60 bg-[var(--mx-surface)] border-r border-[var(--mx-divider)] shrink-0 flex flex-col">
|
||||
<div className="p-3">
|
||||
<button onClick={handleNewChat}
|
||||
className="w-full py-2.5 bg-[#277AF7] hover:bg-[#3482FF] active:scale-[0.98] text-white font-medium text-sm rounded-full transition-all duration-200"
|
||||
className="w-full py-2.5 bg-[var(--mx-primary)] hover:bg-[var(--mx-primary-hover)] active:scale-[0.98] text-white font-medium text-sm rounded-full transition-all duration-200"
|
||||
>
|
||||
+ 新对话
|
||||
</button>
|
||||
@@ -27,11 +27,11 @@ export default function Sidebar() {
|
||||
|
||||
<div className="flex-1 overflow-auto px-2 pb-2">
|
||||
{currentSessionId ? (
|
||||
<div className="px-3 py-2 bg-[#242424] rounded-[14px]">
|
||||
<p className="text-xs text-white/40 truncate">{currentSessionId.slice(0, 12)}...</p>
|
||||
<div className="px-3 py-2 bg-[var(--mx-bg)] rounded-[14px]">
|
||||
<p className="text-xs text-[var(--mx-text-dim)] truncate">{currentSessionId.slice(0, 12)}...</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="px-3 py-2 text-xs text-white/30 text-center">暂无对话</p>
|
||||
<p className="px-3 py-2 text-xs text-[var(--mx-text-dim)] text-center">暂无对话</p>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Reference in new issue
Block a user