feat: 添加亮色/暗色主题切换功能

- 在 index.css 中新增 html.light 亮色主题 CSS 变量和过渡动画
- 在 App.tsx Header 添加 Sun/Moon 主题切换按钮
- 主题偏好通过 localStorage 持久化,默认暗色主题
- 将所有组件中的硬编码 Tailwind 颜色值转换为 CSS 变量引用
- 状态色(emerald/amber/red/violet)保持不变,两种主题均适用
This commit is contained in:
ooodc 2026-06-04 21:52:45 +08:00
parent d1d4998a26
commit bd13cffe14
9 changed files with 373 additions and 252 deletions

View File

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Zap, Cpu, MessageSquare, ArrowLeft, Bot, Clock } from 'lucide-react' import { Zap, Cpu, MessageSquare, ArrowLeft, Bot, Clock, Sun, Moon } from 'lucide-react'
import { ChatContainer } from './components/Chat/ChatContainer' import { ChatContainer } from './components/Chat/ChatContainer'
import { TopicList } from './components/Sidebar/TopicList' import { TopicList } from './components/Sidebar/TopicList'
import { SessionInfo } from './components/Sidebar/SessionInfo' import { SessionInfo } from './components/Sidebar/SessionInfo'
@ -59,6 +59,32 @@ function App() {
onMessage: handleServerMessage, onMessage: handleServerMessage,
}) })
// ---- 主题状态 ----
const [theme, setTheme] = useState<'dark' | 'light'>(() => {
const saved = localStorage.getItem('picobot-theme')
return saved === 'light' ? 'light' : 'dark'
})
useEffect(() => {
const root = document.documentElement
if (theme === 'light') {
root.classList.add('light')
} else {
root.classList.remove('light')
}
localStorage.setItem('picobot-theme', theme)
// 切换时启用平滑过渡
root.classList.add('theme-transitioning')
const timer = setTimeout(() => {
root.classList.remove('theme-transitioning')
}, 350)
return () => clearTimeout(timer)
}, [theme])
// ---- WebSocket 初始化 ----
// 连接建立后自动加载 Session // 连接建立后自动加载 Session
useEffect(() => { useEffect(() => {
if (isConnected && status === 'connected') { if (isConnected && status === 'connected') {
@ -261,23 +287,33 @@ function App() {
const toolMessages = messages const toolMessages = messages
return ( return (
<div className="flex h-screen flex-col bg-[#0a0a0f] text-white overflow-hidden"> <div className="flex h-screen flex-col bg-[var(--bg-primary)] text-[var(--text-primary)] overflow-hidden">
{/* Header */} {/* Header */}
<header className="flex items-center justify-between border-b border-white/8 bg-[#12121a]/80 backdrop-blur-md px-6 py-4"> <header className="flex items-center justify-between border-b border-[var(--border-color)] bg-[var(--bg-secondary)]/80 backdrop-blur-md px-6 py-4">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Zap className="h-6 w-6 text-[#00f0ff]" /> <Zap className="h-6 w-6 text-[var(--accent-cyan)]" />
<h1 className="text-xl font-bold tracking-tight"> <h1 className="text-xl font-bold tracking-tight">
<span className="text-white">Pico</span> <span className="text-[var(--text-primary)]">Pico</span>
<span className="text-[#00f0ff] glow-text">Bot</span> <span className="text-[var(--accent-cyan)] glow-text">Bot</span>
</h1> </h1>
</div> </div>
<div className="h-4 w-px bg-white/20" /> <div className="h-4 w-px bg-[var(--divider-color)]" />
<ConnectionStatus status={status} /> <ConnectionStatus status={status} />
{/* 主题切换按钮 */}
<div className="h-4 w-px bg-[var(--divider-color)]" />
<button
onClick={() => setTheme(prev => prev === 'dark' ? 'light' : 'dark')}
className="flex h-8 w-8 items-center justify-center rounded-lg text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:bg-[var(--overlay-hover)] transition-all"
title={theme === 'dark' ? '切换到亮色主题' : '切换到暗色主题'}
aria-label={theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
>
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</button>
</div> </div>
<div className="flex items-center gap-4 text-sm text-zinc-400"> <div className="flex items-center gap-4 text-sm text-[var(--text-secondary)]">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-[#00f0ff]" /> <Cpu className="h-4 w-4 text-[var(--accent-cyan)]" />
<span>AI Ready</span> <span>AI Ready</span>
</div> </div>
{session && ( {session && (
@ -294,21 +330,21 @@ function App() {
{/* Main Content */} {/* Main Content */}
<div className="flex flex-1 overflow-hidden"> <div className="flex flex-1 overflow-hidden">
{/* Left Sidebar */} {/* Left Sidebar */}
<div className={`w-72 shrink-0 border-r border-white/8 bg-[#12121a]/50 flex flex-col ${subAgentView || schedulerView ? 'opacity-50 pointer-events-none' : ''}`}> <div className={`w-72 shrink-0 border-r border-[var(--border-color)] bg-[var(--bg-secondary)]/50 flex flex-col ${subAgentView || schedulerView ? 'opacity-50 pointer-events-none' : ''}`}>
<SessionInfo <SessionInfo
session={session} session={session}
connectionId={connectionId} connectionId={connectionId}
/> />
<div className="border-b border-white/8" /> <div className="border-b border-[var(--border-color)]" />
{/* Tab 栏 */} {/* Tab 栏 */}
<div className="flex border-b border-white/8"> <div className="flex border-b border-[var(--border-color)]">
<button <button
onClick={() => setSidebarTab('topics')} onClick={() => setSidebarTab('topics')}
className={`flex-1 py-2.5 text-sm font-medium text-center transition-colors ${ className={`flex-1 py-2.5 text-sm font-medium text-center transition-colors ${
sidebarTab === 'topics' sidebarTab === 'topics'
? 'text-[#00f0ff] border-b-2 border-[#00f0ff]' ? 'text-[var(--accent-cyan)] border-b-2 border-[var(--accent-cyan)]'
: 'text-zinc-500 hover:text-zinc-300' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'
}`} }`}
> >
@ -317,8 +353,8 @@ function App() {
onClick={() => setSidebarTab('scheduler')} onClick={() => setSidebarTab('scheduler')}
className={`flex-1 py-2.5 text-sm font-medium text-center transition-colors ${ className={`flex-1 py-2.5 text-sm font-medium text-center transition-colors ${
sidebarTab === 'scheduler' sidebarTab === 'scheduler'
? 'text-[#00f0ff] border-b-2 border-[#00f0ff]' ? 'text-[var(--accent-cyan)] border-b-2 border-[var(--accent-cyan)]'
: 'text-zinc-500 hover:text-zinc-300' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'
}`} }`}
> >
@ -348,60 +384,60 @@ function App() {
</div> </div>
{/* Center - Chat */} {/* Center - Chat */}
<div className="flex-1 min-w-0 bg-[#0a0a0f] flex flex-col"> <div className="flex-1 min-w-0 bg-[var(--bg-primary)] flex flex-col">
{/* Scheduler job view back bar */} {/* Scheduler job view back bar */}
{schedulerView && ( {schedulerView && (
<div className="shrink-0 border-b border-white/8 bg-[#12121a]/80 px-4 py-2 flex items-center gap-4"> <div className="shrink-0 border-b border-[var(--border-color)] bg-[var(--bg-secondary)]/80 px-4 py-2 flex items-center gap-4">
<button <button
onClick={handleExitSchedulerJobView} onClick={handleExitSchedulerJobView}
className="flex items-center gap-1.5 text-sm text-[#00f0ff] hover:text-[#00f0ff]/80 transition-colors" className="flex items-center gap-1.5 text-sm text-[var(--accent-cyan)] hover:text-[var(--accent-cyan)]/80 transition-colors"
> >
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
<span></span> <span></span>
</button> </button>
<div className="h-4 w-px bg-white/20" /> <div className="h-4 w-px bg-[var(--divider-color)]" />
<div className="flex items-center gap-1.5 text-sm text-zinc-300"> <div className="flex items-center gap-1.5 text-sm text-[var(--text-secondary)]">
<Clock className="h-4 w-4 text-amber-400" /> <Clock className="h-4 w-4 text-amber-400" />
<span className="text-zinc-500">:</span> <span className="text-[var(--text-muted)]">:</span>
<span className="text-white font-medium font-mono text-xs truncate max-w-[250px]">{schedulerView.description}</span> <span className="text-[var(--text-primary)] font-medium font-mono text-xs truncate max-w-[250px]">{schedulerView.description}</span>
</div> </div>
<div className="h-4 w-px bg-white/20" /> <div className="h-4 w-px bg-[var(--divider-color)]" />
<div className="flex items-center gap-1.5 text-sm"> <div className="flex items-center gap-1.5 text-sm">
<span className="text-zinc-500">:</span> <span className="text-[var(--text-muted)]">:</span>
<span className="text-zinc-300">{schedulerView.channel}</span> <span className="text-[var(--text-secondary)]">{schedulerView.channel}</span>
</div> </div>
</div> </div>
)} )}
{/* Sub-agent back bar */} {/* Sub-agent back bar */}
{subAgentView && ( {subAgentView && (
<div className="shrink-0 border-b border-white/8 bg-[#12121a]/80 px-4 py-2 flex items-center gap-4"> <div className="shrink-0 border-b border-[var(--border-color)] bg-[var(--bg-secondary)]/80 px-4 py-2 flex items-center gap-4">
<button <button
onClick={handleExitSubAgentView} onClick={handleExitSubAgentView}
className="flex items-center gap-1.5 text-sm text-[#00f0ff] hover:text-[#00f0ff]/80 transition-colors" className="flex items-center gap-1.5 text-sm text-[var(--accent-cyan)] hover:text-[var(--accent-cyan)]/80 transition-colors"
> >
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
<span></span> <span></span>
</button> </button>
<div className="h-4 w-px bg-white/20" /> <div className="h-4 w-px bg-[var(--divider-color)]" />
<div className="flex items-center gap-1.5 text-sm text-zinc-300"> <div className="flex items-center gap-1.5 text-sm text-[var(--text-secondary)]">
<Bot className="h-4 w-4 text-violet-400" /> <Bot className="h-4 w-4 text-violet-400" />
<span className="text-zinc-500">:</span> <span className="text-[var(--text-muted)]">:</span>
<span className="text-white font-medium">{subAgentView.description}</span> <span className="text-[var(--text-primary)] font-medium">{subAgentView.description}</span>
</div> </div>
<div className="h-4 w-px bg-white/20" /> <div className="h-4 w-px bg-[var(--divider-color)]" />
<div className="flex items-center gap-1.5 text-sm"> <div className="flex items-center gap-1.5 text-sm">
<span className="text-zinc-500">:</span> <span className="text-[var(--text-muted)]">:</span>
<span className="text-zinc-300">{subAgentView.subagentType || '...'}</span> <span className="text-[var(--text-secondary)]">{subAgentView.subagentType || '...'}</span>
</div> </div>
<div className="h-4 w-px bg-white/20" /> <div className="h-4 w-px bg-[var(--divider-color)]" />
<div className="flex items-center gap-1.5 text-sm"> <div className="flex items-center gap-1.5 text-sm">
<span className="text-zinc-500">:</span> <span className="text-[var(--text-muted)]">:</span>
<span className={`font-medium ${ <span className={`font-medium ${
subAgentView.status === 'completed' ? 'text-emerald-400' : subAgentView.status === 'completed' ? 'text-emerald-400' :
subAgentView.status === 'failed' ? 'text-red-400' : subAgentView.status === 'failed' ? 'text-red-400' :
subAgentView.status === 'timeout' ? 'text-amber-400' : subAgentView.status === 'timeout' ? 'text-amber-400' :
subAgentView.status === 'running' ? 'text-amber-400' : subAgentView.status === 'running' ? 'text-amber-400' :
'text-zinc-400' 'text-[var(--text-secondary)]'
}`}> }`}>
{subAgentView.status === 'completed' ? '已完成' : {subAgentView.status === 'completed' ? '已完成' :
subAgentView.status === 'failed' ? '失败' : subAgentView.status === 'failed' ? '失败' :
@ -431,7 +467,7 @@ function App() {
</div> </div>
{/* Right Sidebar - Tool Panel */} {/* Right Sidebar - Tool Panel */}
<div className="w-80 shrink-0 border-l border-white/8 bg-[#12121a]/50"> <div className="w-80 shrink-0 border-l border-[var(--border-color)] bg-[var(--bg-secondary)]/50">
<ToolPanel messages={toolMessages} /> <ToolPanel messages={toolMessages} />
</div> </div>
</div> </div>

View File

@ -125,20 +125,20 @@ function AttachmentCard({ attachment }: { attachment: Attachment }) {
return ( return (
<div <div
onClick={canDownload ? handleDownload : undefined} onClick={canDownload ? handleDownload : undefined}
className={`flex items-center gap-2 rounded-lg bg-white/5 border border-white/10 px-3 py-2 text-xs transition-colors group ${ className={`flex items-center gap-2 rounded-lg bg-[var(--overlay-hover)] border border-[var(--border-color)] px-3 py-2 text-xs transition-colors group ${
canDownload ? 'hover:bg-white/10 hover:border-[#00f0ff]/30 cursor-pointer' : '' canDownload ? 'hover:bg-[var(--overlay-subtle)] hover:border-[var(--accent-cyan)]/30 cursor-pointer' : ''
}`} }`}
title={canDownload ? `下载 ${fileName}` : attachment.path} title={canDownload ? `下载 ${fileName}` : attachment.path}
> >
<span className={`text-zinc-400 transition-colors ${canDownload ? 'group-hover:text-[#00f0ff]' : ''}`}> <span className={`text-[var(--text-secondary)] transition-colors ${canDownload ? 'group-hover:text-[var(--accent-cyan)]' : ''}`}>
{getAttachmentIcon(attachment.media_type)} {getAttachmentIcon(attachment.media_type)}
</span> </span>
<span className={`text-zinc-300 truncate max-w-[200px] transition-colors ${canDownload ? 'group-hover:text-white' : ''}`} title={attachment.path}> <span className={`text-[var(--text-secondary)] truncate max-w-[200px] transition-colors ${canDownload ? 'group-hover:text-[var(--text-primary)]' : ''}`} title={attachment.path}>
{fileName} {fileName}
</span> </span>
<span className="text-zinc-600 ml-auto shrink-0">{attachment.media_type}</span> <span className="text-[var(--text-muted)] ml-auto shrink-0">{attachment.media_type}</span>
{canDownload && ( {canDownload && (
<Download className="h-3 w-3 text-zinc-600 group-hover:text-[#00f0ff] transition-colors" /> <Download className="h-3 w-3 text-[var(--text-muted)] group-hover:text-[var(--accent-cyan)] transition-colors" />
)} )}
</div> </div>
) )
@ -162,13 +162,13 @@ function CopyButton({ text, className = '' }: { text: string; className?: string
return ( return (
<button <button
onClick={handleCopy} onClick={handleCopy}
className={`opacity-0 group-hover:opacity-100 transition-opacity p-0.5 rounded hover:bg-white/10 flex-shrink-0 ${className}`} className={`opacity-0 group-hover:opacity-100 transition-opacity p-0.5 rounded hover:bg-[var(--overlay-subtle)] flex-shrink-0 ${className}`}
title="复制" title="复制"
> >
{copied ? ( {copied ? (
<Check className="h-3 w-3 text-emerald-400" /> <Check className="h-3 w-3 text-emerald-400" />
) : ( ) : (
<Copy className="h-3 w-3 text-zinc-500" /> <Copy className="h-3 w-3 text-[var(--text-muted)]" />
)} )}
</button> </button>
) )
@ -281,7 +281,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
</div> </div>
<div className="max-w-[80%] min-w-0"> <div className="max-w-[80%] min-w-0">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<span className="text-xs font-medium text-zinc-400">{message.toolName || 'Tool'}</span> <span className="text-xs font-medium text-[var(--text-secondary)]">{message.toolName || 'Tool'}</span>
{isTaskTool && ( {isTaskTool && (
<span className="text-xs px-1.5 py-0.5 rounded bg-violet-500/10 text-violet-400"> <span className="text-xs px-1.5 py-0.5 rounded bg-violet-500/10 text-violet-400">
·{subagentType} ·{subagentType}
@ -292,11 +292,11 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
</span> </span>
)} )}
<span className="text-xs text-zinc-600">{formatTime(message.timestamp)}</span> <span className="text-xs text-[var(--text-muted)]">{formatTime(message.timestamp)}</span>
</div> </div>
<div <div
onClick={() => setToolExpanded(!toolExpanded)} onClick={() => setToolExpanded(!toolExpanded)}
className={`cursor-pointer rounded-xl border bg-[#1a1a25]/60 w-full transition-all duration-500 hover:bg-[#1a1a25]/80 group ${ className={`cursor-pointer rounded-xl border bg-[var(--bg-tertiary)]/60 w-full transition-all duration-500 hover:bg-[var(--bg-tertiary)]/80 group ${
taskResult ? taskStatusConfig[taskResult.status].borderColor : statusConfig.fullBorder taskResult ? taskStatusConfig[taskResult.status].borderColor : statusConfig.fullBorder
}`} }`}
> >
@ -305,7 +305,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
<span className={`inline-block h-2 w-2 rounded-full flex-shrink-0 transition-colors duration-500 ${ <span className={`inline-block h-2 w-2 rounded-full flex-shrink-0 transition-colors duration-500 ${
taskResult ? taskStatusConfig[taskResult.status].dot : statusConfig.dot taskResult ? taskStatusConfig[taskResult.status].dot : statusConfig.dot
}`} /> }`} />
<span className="text-sm font-medium text-zinc-300 truncate"> <span className="text-sm font-medium text-[var(--text-secondary)] truncate">
{isTaskTool ? (taskDescription || '子智能体任务') : (message.toolName || 'Tool')} {isTaskTool ? (taskDescription || '子智能体任务') : (message.toolName || 'Tool')}
</span> </span>
<span className={`flex-shrink-0 transition-all duration-300 ${ <span className={`flex-shrink-0 transition-all duration-300 ${
@ -318,16 +318,16 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
)} )}
</span> </span>
{status === 'result' && message.durationMs != null && ( {status === 'result' && message.durationMs != null && (
<span className="text-xs text-zinc-600 flex-shrink-0 tabular-nums ml-1"> <span className="text-xs text-[var(--text-muted)] flex-shrink-0 tabular-nums ml-1">
{formatDuration(message.durationMs)} {formatDuration(message.durationMs)}
</span> </span>
)} )}
{hasResult && <CopyButton text={taskResult ? taskResult.output : displayContent} />} {hasResult && <CopyButton text={taskResult ? taskResult.output : displayContent} />}
<span className="ml-auto flex-shrink-0"> <span className="ml-auto flex-shrink-0">
{toolExpanded ? ( {toolExpanded ? (
<ChevronDown className="h-3.5 w-3.5 text-zinc-500" /> <ChevronDown className="h-3.5 w-3.5 text-[var(--text-muted)]" />
) : ( ) : (
<ChevronRight className="h-3.5 w-3.5 text-zinc-500" /> <ChevronRight className="h-3.5 w-3.5 text-[var(--text-muted)]" />
)} )}
</span> </span>
</div> </div>
@ -336,7 +336,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
{!toolExpanded && ( {!toolExpanded && (
<> <>
{hasArgs && argsPreview && !taskResult && ( {hasArgs && argsPreview && !taskResult && (
<div className="px-3 pb-1 text-xs text-zinc-500 font-mono line-clamp-3" <div className="px-3 pb-1 text-xs text-[var(--text-muted)] font-mono line-clamp-3"
style={{ style={{
display: '-webkit-box', display: '-webkit-box',
WebkitLineClamp: 3, WebkitLineClamp: 3,
@ -347,7 +347,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
</div> </div>
)} )}
{taskResult && taskResult.summary && ( {taskResult && taskResult.summary && (
<div className="px-3 pb-1 text-xs text-zinc-400 line-clamp-2"> <div className="px-3 pb-1 text-xs text-[var(--text-secondary)] line-clamp-2">
{taskResult.summary} {taskResult.summary}
</div> </div>
)} )}
@ -358,7 +358,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
e.stopPropagation() e.stopPropagation()
onNavigateToSubAgent?.(taskResult.task_id, taskDescription || '子智能体任务') onNavigateToSubAgent?.(taskResult.task_id, taskDescription || '子智能体任务')
}} }}
className="text-xs text-[#00f0ff] hover:text-[#00f0ff]/80 hover:underline transition-colors flex items-center gap-1" className="text-xs text-[var(--accent-cyan)] hover:text-[var(--accent-cyan)]/80 hover:underline transition-colors flex items-center gap-1"
> >
<span></span> <span></span>
<span></span> <span></span>
@ -372,7 +372,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
e.stopPropagation() e.stopPropagation()
onNavigateToSubAgent?.(message.subagentTaskId!, taskDescription || '子智能体任务') onNavigateToSubAgent?.(message.subagentTaskId!, taskDescription || '子智能体任务')
}} }}
className="text-xs text-[#00f0ff] hover:text-[#00f0ff]/80 hover:underline transition-colors flex items-center gap-1" className="text-xs text-[var(--accent-cyan)] hover:text-[var(--accent-cyan)]/80 hover:underline transition-colors flex items-center gap-1"
> >
<span></span> <span></span>
<span></span> <span></span>
@ -380,7 +380,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
</div> </div>
)} )}
{isTaskTool && !taskResult && !message.subagentTaskId && ( {isTaskTool && !taskResult && !message.subagentTaskId && (
<div className="px-3 pb-2 text-xs text-zinc-500"> <div className="px-3 pb-2 text-xs text-[var(--text-muted)]">
... ...
</div> </div>
)} )}
@ -389,13 +389,13 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
{/* Expanded */} {/* Expanded */}
{toolExpanded && ( {toolExpanded && (
<div className="border-t border-white/8 px-3 py-2 space-y-2"> <div className="border-t border-[var(--border-color)] px-3 py-2 space-y-2">
{taskResult ? ( {taskResult ? (
<> <>
{taskPrompt && ( {taskPrompt && (
<div> <div>
<div className="text-xs font-medium text-zinc-500 mb-1"></div> <div className="text-xs font-medium text-[var(--text-muted)] mb-1"></div>
<pre className="text-xs text-zinc-400 font-mono whitespace-pre-wrap bg-black/20 rounded-lg p-2 overflow-x-auto max-h-32 overflow-y-auto"> <pre className="text-xs text-[var(--text-secondary)] font-mono whitespace-pre-wrap bg-[var(--overlay-dim)] rounded-lg p-2 overflow-x-auto max-h-32 overflow-y-auto">
{taskPrompt} {taskPrompt}
</pre> </pre>
</div> </div>
@ -406,19 +406,19 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
taskResult.status === 'failed' ? 'bg-red-500/10 border border-red-500/30' : taskResult.status === 'failed' ? 'bg-red-500/10 border border-red-500/30' :
'bg-amber-500/10 border border-amber-500/30' 'bg-amber-500/10 border border-amber-500/30'
}`}> }`}>
<div className="text-xs font-medium text-zinc-500 mb-0.5"></div> <div className="text-xs font-medium text-[var(--text-muted)] mb-0.5"></div>
<div className="text-sm text-zinc-300">{taskResult.summary}</div> <div className="text-sm text-[var(--text-secondary)]">{taskResult.summary}</div>
</div> </div>
)} )}
<div> <div>
<div className="text-xs font-medium text-zinc-500 mb-1"></div> <div className="text-xs font-medium text-[var(--text-muted)] mb-1"></div>
<div className="markdown-content text-sm leading-relaxed bg-black/20 rounded-lg p-3 max-h-96 overflow-y-auto"> <div className="markdown-content text-sm leading-relaxed bg-[var(--overlay-dim)] rounded-lg p-3 max-h-96 overflow-y-auto">
<ReactMarkdown remarkPlugins={[remarkGfm]}> <ReactMarkdown remarkPlugins={[remarkGfm]}>
{taskResult.output} {taskResult.output}
</ReactMarkdown> </ReactMarkdown>
</div> </div>
</div> </div>
<div className="text-xs text-zinc-600 font-mono select-all"> <div className="text-xs text-[var(--text-muted)] font-mono select-all">
task_id: {taskResult.task_id} task_id: {taskResult.task_id}
</div> </div>
<button <button
@ -426,7 +426,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
e.stopPropagation() e.stopPropagation()
onNavigateToSubAgent?.(taskResult.task_id, taskDescription || '子智能体任务') onNavigateToSubAgent?.(taskResult.task_id, taskDescription || '子智能体任务')
}} }}
className="text-xs text-[#00f0ff] hover:text-[#00f0ff]/80 hover:underline transition-colors flex items-center gap-1" className="text-xs text-[var(--accent-cyan)] hover:text-[var(--accent-cyan)]/80 hover:underline transition-colors flex items-center gap-1"
> >
<span></span> <span></span>
<span></span> <span></span>
@ -436,16 +436,16 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
<> <>
{hasArgs && ( {hasArgs && (
<div> <div>
<div className="text-xs font-medium text-zinc-500 mb-1"></div> <div className="text-xs font-medium text-[var(--text-muted)] mb-1"></div>
<pre className="text-xs text-zinc-400 font-mono whitespace-pre-wrap bg-black/20 rounded-lg p-2 overflow-x-auto"> <pre className="text-xs text-[var(--text-secondary)] font-mono whitespace-pre-wrap bg-[var(--overlay-dim)] rounded-lg p-2 overflow-x-auto">
{JSON.stringify(message.arguments, null, 2)} {JSON.stringify(message.arguments, null, 2)}
</pre> </pre>
</div> </div>
)} )}
{hasResult && ( {hasResult && (
<div> <div>
<div className="text-xs font-medium text-zinc-500 mb-1"></div> <div className="text-xs font-medium text-[var(--text-muted)] mb-1"></div>
<pre className="text-xs text-zinc-400 font-mono whitespace-pre-wrap bg-black/20 rounded-lg p-2 overflow-x-auto max-h-48 overflow-y-auto"> <pre className="text-xs text-[var(--text-secondary)] font-mono whitespace-pre-wrap bg-[var(--overlay-dim)] rounded-lg p-2 overflow-x-auto max-h-48 overflow-y-auto">
{formatJSON(displayContent)} {formatJSON(displayContent)}
</pre> </pre>
</div> </div>
@ -456,7 +456,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
e.stopPropagation() e.stopPropagation()
onNavigateToSubAgent?.(message.subagentTaskId!, taskDescription || '子智能体任务') onNavigateToSubAgent?.(message.subagentTaskId!, taskDescription || '子智能体任务')
}} }}
className="text-xs text-[#00f0ff] hover:text-[#00f0ff]/80 hover:underline transition-colors flex items-center gap-1" className="text-xs text-[var(--accent-cyan)] hover:text-[var(--accent-cyan)]/80 hover:underline transition-colors flex items-center gap-1"
> >
<span></span> <span></span>
<span></span> <span></span>
@ -485,26 +485,26 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
const getContainerStyles = () => { const getContainerStyles = () => {
if (isUser) { if (isUser) {
return 'bg-gradient-to-br from-[#00f0ff]/20 to-[#3b82f6]/20 border-[#00f0ff]/30 text-white' return 'bg-gradient-to-br from-[var(--accent-cyan)]/20 to-[var(--accent-blue)]/20 border-[var(--accent-cyan)]/30 text-[var(--text-primary)]'
} }
if (isTool) { if (isTool) {
if (message.type === 'tool_call') return 'bg-amber-500/10 border-amber-500/30 text-amber-100' if (message.type === 'tool_call') return 'bg-amber-500/10 border-amber-500/30 text-amber-100'
if (message.type === 'tool_result') return 'bg-emerald-500/10 border-emerald-500/30 text-emerald-100' if (message.type === 'tool_result') return 'bg-emerald-500/10 border-emerald-500/30 text-emerald-100'
if (message.type === 'tool_pending') return 'bg-orange-500/10 border-orange-500/30 text-orange-100' if (message.type === 'tool_pending') return 'bg-orange-500/10 border-orange-500/30 text-orange-100'
return 'bg-zinc-800/50 border-zinc-700 text-zinc-300' return 'bg-[var(--bg-hover)] border-[var(--border-color)] text-[var(--text-secondary)]'
} }
return 'bg-[#1a1a25] border-white/10 text-white' return 'bg-[var(--bg-tertiary)] border-[var(--border-color)] text-[var(--text-primary)]'
} }
const getAvatarStyles = () => { const getAvatarStyles = () => {
if (isUser) return 'bg-gradient-to-br from-[#00f0ff] to-[#3b82f6]' if (isUser) return 'bg-gradient-to-br from-[var(--accent-cyan)] to-[var(--accent-blue)]'
if (isTool) { if (isTool) {
if (message.type === 'tool_call') return 'bg-amber-500' if (message.type === 'tool_call') return 'bg-amber-500'
if (message.type === 'tool_result') return 'bg-emerald-500' if (message.type === 'tool_result') return 'bg-emerald-500'
if (message.type === 'tool_pending') return 'bg-orange-500' if (message.type === 'tool_pending') return 'bg-orange-500'
return 'bg-zinc-700' return 'bg-zinc-700'
} }
return 'bg-gradient-to-br from-[#8b5cf6] to-[#ec4899]' return 'bg-gradient-to-br from-[var(--accent-purple)] to-[#ec4899]'
} }
return ( return (
@ -516,10 +516,10 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
</div> </div>
<div className={`max-w-[80%] ${isUser ? 'text-right' : 'text-left'}`}> <div className={`max-w-[80%] ${isUser ? 'text-right' : 'text-left'}`}>
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<span className={`text-xs font-medium ${isUser ? 'text-[#00f0ff]' : 'text-zinc-400'}`}> <span className={`text-xs font-medium ${isUser ? 'text-[var(--accent-cyan)]' : 'text-[var(--text-secondary)]'}`}>
{isUser ? 'You' : isTool ? message.toolName || 'Tool' : 'Assistant'} {isUser ? 'You' : isTool ? message.toolName || 'Tool' : 'Assistant'}
</span> </span>
<span className="text-xs text-zinc-600">{formatTime(message.timestamp)}</span> <span className="text-xs text-[var(--text-muted)]">{formatTime(message.timestamp)}</span>
<CopyButton text={message.content} /> <CopyButton text={message.content} />
</div> </div>
<div <div
@ -546,7 +546,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
if (isInline) { if (isInline) {
return ( return (
<code <code
className="bg-black/30 px-1.5 py-0.5 rounded text-[#00f0ff] font-mono text-xs" className="bg-[var(--overlay-code)] px-1.5 py-0.5 rounded text-[var(--accent-cyan)] font-mono text-xs"
{...props} {...props}
> >
{children} {children}
@ -554,7 +554,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
) )
} }
return ( return (
<pre className="bg-black/40 rounded-lg p-3 overflow-x-auto my-2"> <pre className="bg-[var(--overlay-dim-heavy)] rounded-lg p-3 overflow-x-auto my-2">
<code className={`${className} font-mono text-xs`} {...props}> <code className={`${className} font-mono text-xs`} {...props}>
{children} {children}
</code> </code>
@ -563,13 +563,13 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
}, },
// 标题样式 // 标题样式
h1: ({ children }) => ( h1: ({ children }) => (
<h1 className="text-xl font-bold text-white mb-2 mt-4">{children}</h1> <h1 className="text-xl font-bold text-[var(--text-primary)] mb-2 mt-4">{children}</h1>
), ),
h2: ({ children }) => ( h2: ({ children }) => (
<h2 className="text-lg font-bold text-white mb-2 mt-3">{children}</h2> <h2 className="text-lg font-bold text-[var(--text-primary)] mb-2 mt-3">{children}</h2>
), ),
h3: ({ children }) => ( h3: ({ children }) => (
<h3 className="text-base font-bold text-white mb-1 mt-2">{children}</h3> <h3 className="text-base font-bold text-[var(--text-primary)] mb-1 mt-2">{children}</h3>
), ),
// 段落 // 段落
p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>, p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>,
@ -582,7 +582,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
href={href} href={href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#00f0ff] hover:underline" className="text-[var(--accent-cyan)] hover:underline"
> >
{children} {children}
</a> </a>
@ -591,24 +591,24 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
table: ({ children }) => ( table: ({ children }) => (
<table className="w-full border-collapse mb-2 text-xs">{children}</table> <table className="w-full border-collapse mb-2 text-xs">{children}</table>
), ),
thead: ({ children }) => <thead className="bg-white/10">{children}</thead>, thead: ({ children }) => <thead className="bg-[var(--overlay-subtle)]">{children}</thead>,
th: ({ children }) => ( th: ({ children }) => (
<th className="border border-white/10 px-2 py-1 text-left font-semibold">{children}</th> <th className="border border-[var(--border-color)] px-2 py-1 text-left font-semibold">{children}</th>
), ),
td: ({ children }) => ( td: ({ children }) => (
<td className="border border-white/10 px-2 py-1">{children}</td> <td className="border border-[var(--border-color)] px-2 py-1">{children}</td>
), ),
// 引用块 // 引用块
blockquote: ({ children }) => ( blockquote: ({ children }) => (
<blockquote className="border-l-2 border-[#00f0ff]/50 pl-3 my-2 text-zinc-400"> <blockquote className="border-l-2 border-[var(--accent-cyan)]/50 pl-3 my-2 text-[var(--text-secondary)]">
{children} {children}
</blockquote> </blockquote>
), ),
// 分隔线 // 分隔线
hr: () => <hr className="border-white/10 my-3" />, hr: () => <hr className="border-[var(--border-color)] my-3" />,
// 加粗和斜体 // 加粗和斜体
strong: ({ children }) => <strong className="font-bold text-white">{children}</strong>, strong: ({ children }) => <strong className="font-bold text-[var(--text-primary)]">{children}</strong>,
em: ({ children }) => <em className="italic text-zinc-300">{children}</em>, em: ({ children }) => <em className="italic text-[var(--text-secondary)]">{children}</em>,
}} }}
> >
{message.content} {message.content}

View File

@ -251,22 +251,22 @@ export function MessageInput({
// 只读模式:显示提示占位符 // 只读模式:显示提示占位符
if (isReadOnly) { if (isReadOnly) {
return ( return (
<div className="border-t border-white/8 bg-[#12121a]/80 backdrop-blur-md p-4"> <div className="border-t border-[var(--border-color)] bg-[var(--bg-secondary)]/80 backdrop-blur-md p-4">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="rounded-xl border border-zinc-500/20 bg-[#1a1a25]/50 px-4 py-5 text-center"> <div className="rounded-xl border border-[var(--border-color)] bg-[var(--bg-tertiary)]/50 px-4 py-5 text-center">
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2 text-zinc-400"> <div className="flex items-center gap-2 text-[var(--text-secondary)]">
<Eye className="h-5 w-5" /> <Eye className="h-5 w-5" />
<span className="font-medium"></span> <span className="font-medium"></span>
</div> </div>
<p className="text-sm text-zinc-500"> <p className="text-sm text-[var(--text-muted)]">
{channelName ? ( {channelName ? (
<>{channelName} </> <>{channelName} </>
) : ( ) : (
'当前通道仅支持查看历史消息' '当前通道仅支持查看历史消息'
)} )}
</p> </p>
<p className="text-xs text-zinc-600"> <p className="text-xs text-[var(--text-muted)]">
WebSocket WebSocket
</p> </p>
</div> </div>
@ -277,7 +277,7 @@ export function MessageInput({
} }
return ( return (
<div className="border-t border-white/8 bg-[#12121a]/80 backdrop-blur-md p-4"> <div className="border-t border-[var(--border-color)] bg-[var(--bg-secondary)]/80 backdrop-blur-md p-4">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
{/* 错误提示 */} {/* 错误提示 */}
{error && ( {error && (
@ -292,7 +292,7 @@ export function MessageInput({
{attachments.map((att, index) => ( {attachments.map((att, index) => (
<div <div
key={index} key={index}
className="flex items-center gap-2 rounded-lg border border-white/10 bg-[#1a1a25] px-2 py-1.5 text-sm" className="flex items-center gap-2 rounded-lg border border-[var(--border-color)] bg-[var(--bg-tertiary)] px-2 py-1.5 text-sm"
> >
{att.preview ? ( {att.preview ? (
<img <img
@ -301,16 +301,16 @@ export function MessageInput({
className="h-8 w-8 rounded object-cover" className="h-8 w-8 rounded object-cover"
/> />
) : ( ) : (
<div className="h-8 w-8 rounded bg-zinc-700/50 flex items-center justify-center text-zinc-400"> <div className="h-8 w-8 rounded bg-[var(--bg-hover)] flex items-center justify-center text-[var(--text-secondary)]">
{getAttachmentIcon(att.attachment.media_type)} {getAttachmentIcon(att.attachment.media_type)}
</div> </div>
)} )}
<span className="text-zinc-300 max-w-[120px] truncate"> <span className="text-[var(--text-secondary)] max-w-[120px] truncate">
{att.attachment.file_name} {att.attachment.file_name}
</span> </span>
<button <button
onClick={() => handleRemoveAttachment(index)} onClick={() => handleRemoveAttachment(index)}
className="text-zinc-500 hover:text-red-400 transition-colors" className="text-[var(--text-muted)] hover:text-red-400 transition-colors"
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</button> </button>
@ -321,7 +321,7 @@ export function MessageInput({
{/* 输入区域 */} {/* 输入区域 */}
<div <div
className={`flex gap-3 items-center relative ${isDragging ? 'ring-2 ring-[#00f0ff]/50 rounded-xl' : ''}`} className={`flex gap-3 items-center relative ${isDragging ? 'ring-2 ring-[var(--accent-cyan)]/50 rounded-xl' : ''}`}
onDragEnter={handleDragEnter} onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
onDragOver={handleDragOver} onDragOver={handleDragOver}
@ -329,8 +329,8 @@ export function MessageInput({
> >
{/* 拖拽提示 */} {/* 拖拽提示 */}
{isDragging && ( {isDragging && (
<div className="absolute inset-0 rounded-xl bg-[#00f0ff]/10 border-2 border-[#00f0ff]/50 flex items-center justify-center z-10"> <div className="absolute inset-0 rounded-xl bg-[var(--accent-cyan)]/10 border-2 border-[var(--accent-cyan)]/50 flex items-center justify-center z-10">
<div className="text-[#00f0ff] text-sm font-medium"> <div className="text-[var(--accent-cyan)] text-sm font-medium">
</div> </div>
</div> </div>
@ -340,7 +340,7 @@ export function MessageInput({
<button <button
onClick={handleAttachClick} onClick={handleAttachClick}
disabled={disabled} disabled={disabled}
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-white/10 bg-[#1a1a25] text-zinc-400 hover:text-white hover:border-white/20 disabled:opacity-50 transition-all" className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-[var(--border-color)] bg-[var(--bg-tertiary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--border-color)] disabled:opacity-50 transition-all"
> >
<Paperclip className="h-5 w-5" /> <Paperclip className="h-5 w-5" />
</button> </button>
@ -363,9 +363,9 @@ export function MessageInput({
placeholder={placeholder} placeholder={placeholder}
disabled={disabled} disabled={disabled}
rows={1} rows={1}
className="w-full resize-none rounded-xl border border-white/10 bg-[#1a1a25] px-4 py-3 pr-12 text-sm text-white placeholder:text-zinc-500 focus:border-[#00f0ff]/50 focus:outline-none focus:ring-1 focus:ring-[#00f0ff]/20 disabled:opacity-50 transition-all self-center scrollbar-hide" className="w-full resize-none rounded-xl border border-[var(--border-color)] bg-[var(--bg-tertiary)] px-4 py-3 pr-12 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus:border-[var(--accent-cyan)]/50 focus:outline-none focus:ring-1 focus:ring-[var(--focus-ring)] disabled:opacity-50 transition-all self-center scrollbar-hide"
/> />
<Sparkles className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-600 pointer-events-none" /> <Sparkles className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--text-muted)] pointer-events-none" />
</div> </div>
{/* 发送/停止按钮 */} {/* 发送/停止按钮 */}
@ -381,7 +381,7 @@ export function MessageInput({
<button <button
onClick={handleSend} onClick={handleSend}
disabled={disabled || (!content.trim() && attachments.length === 0)} disabled={disabled || (!content.trim() && attachments.length === 0)}
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-r from-[#00f0ff] to-[#3b82f6] text-white shadow-lg shadow-[#00f0ff]/20 hover:shadow-xl hover:shadow-[#00f0ff]/30 hover:scale-105 disabled:opacity-50 disabled:hover:scale-100 disabled:cursor-not-allowed transition-all" className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-r from-[var(--accent-cyan)] to-[var(--accent-blue)] text-white shadow-lg shadow-[var(--accent-cyan)]/20 hover:shadow-xl hover:shadow-[var(--accent-cyan)]/30 hover:scale-105 disabled:opacity-50 disabled:hover:scale-100 disabled:cursor-not-allowed transition-all"
> >
{disabled ? ( {disabled ? (
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
@ -393,10 +393,10 @@ export function MessageInput({
</div> </div>
{/* 提示 */} {/* 提示 */}
<div className="mt-2 text-center text-xs text-zinc-500"> <div className="mt-2 text-center text-xs text-[var(--text-muted)]">
Enter Shift+Enter · / · 50MB Enter Shift+Enter · / · 50MB
</div> </div>
</div> </div>
</div> </div>
) )
} }

View File

@ -131,14 +131,14 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
return ( return (
<div className="flex h-full items-center justify-center"> <div className="flex h-full items-center justify-center">
<div className="text-center animate-fade-in"> <div className="text-center animate-fade-in">
<div className="mb-6 inline-flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-[#00f0ff]/20 to-[#3b82f6]/20 shadow-2xl shadow-[#00f0ff]/20"> <div className="mb-6 inline-flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-[var(--accent-cyan)]/20 to-[var(--accent-blue)]/20 shadow-2xl shadow-[var(--accent-cyan)]/20">
<Sparkles className="h-10 w-10 text-[#00f0ff]" /> <Sparkles className="h-10 w-10 text-[var(--accent-cyan)]" />
</div> </div>
<h2 className="mb-2 text-2xl font-bold text-white"></h2> <h2 className="mb-2 text-2xl font-bold text-[var(--text-primary)]"></h2>
<p className="text-zinc-500"> AI </p> <p className="text-[var(--text-muted)]"> AI </p>
<div className="mt-8 flex items-center justify-center gap-4 text-sm text-zinc-600"> <div className="mt-8 flex items-center justify-center gap-4 text-sm text-[var(--text-muted)]">
<span className="px-3 py-1 rounded-full bg-zinc-800/50 border border-zinc-700">/new </span> <span className="px-3 py-1 rounded-full bg-[var(--bg-hover)] border border-[var(--border-color)]">/new </span>
<span className="px-3 py-1 rounded-full bg-zinc-800/50 border border-zinc-700">/list </span> <span className="px-3 py-1 rounded-full bg-[var(--bg-hover)] border border-[var(--border-color)]">/list </span>
</div> </div>
</div> </div>
</div> </div>
@ -168,11 +168,11 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
onClick={scrollToTop} onClick={scrollToTop}
className="pointer-events-auto group relative flex items-center justify-center className="pointer-events-auto group relative flex items-center justify-center
w-9 h-9 rounded-full w-9 h-9 rounded-full
bg-[#1a1a25]/80 backdrop-blur-md bg-[var(--bg-tertiary)]/80 backdrop-blur-md
border border-white/[0.06] border border-[var(--border-color)]
text-zinc-500 hover:text-[#00f0ff] text-[var(--text-muted)] hover:text-[var(--accent-cyan)]
hover:border-[#00f0ff]/30 hover:border-[var(--accent-cyan)]/30
hover:shadow-[0_0_20px_rgba(0,240,255,0.12)] hover:shadow-[0_0_20px_var(--shadow-glow-sm)]
transition-all duration-300 ease-out transition-all duration-300 ease-out
animate-fade-in" animate-fade-in"
aria-label="回到顶部" aria-label="回到顶部"
@ -187,11 +187,11 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
onClick={() => scrollToBottom('smooth')} onClick={() => scrollToBottom('smooth')}
className="pointer-events-auto group relative flex items-center justify-center className="pointer-events-auto group relative flex items-center justify-center
w-9 h-9 rounded-full w-9 h-9 rounded-full
bg-[#1a1a25]/80 backdrop-blur-md bg-[var(--bg-tertiary)]/80 backdrop-blur-md
border border-white/[0.06] border border-[var(--border-color)]
text-zinc-500 hover:text-[#00f0ff] text-[var(--text-muted)] hover:text-[var(--accent-cyan)]
hover:border-[#00f0ff]/30 hover:border-[var(--accent-cyan)]/30
hover:shadow-[0_0_20px_rgba(0,240,255,0.12)] hover:shadow-[0_0_20px_var(--shadow-glow-sm)]
transition-all duration-300 ease-out transition-all duration-300 ease-out
animate-fade-in" animate-fade-in"
aria-label="回到底部" aria-label="回到底部"
@ -201,8 +201,8 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
{/* 未读消息指示点 */} {/* 未读消息指示点 */}
{hasNewMessage && ( {hasNewMessage && (
<span className="absolute -top-0.5 -right-0.5 flex h-2.5 w-2.5"> <span className="absolute -top-0.5 -right-0.5 flex h-2.5 w-2.5">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#00f0ff]/60"></span> <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--accent-cyan)]/60"></span>
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-[#00f0ff]"></span> <span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-[var(--accent-cyan)]"></span>
</span> </span>
)} )}
</button> </button>

View File

@ -128,11 +128,11 @@ export function ToolPanel({ messages }: ToolPanelProps) {
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<style>{animStyles}</style> <style>{animStyles}</style>
<div className="border-b border-white/8 p-4 font-semibold text-white flex items-center gap-2"> <div className="border-b border-[var(--border-color)] p-4 font-semibold text-[var(--text-primary)] flex items-center gap-2">
<Terminal className="h-4 w-4 text-[#00f0ff]" /> <Terminal className="h-4 w-4 text-[var(--accent-cyan)]" />
</div> </div>
<div className="flex flex-1 items-center justify-center p-4 text-sm text-zinc-500"> <div className="flex flex-1 items-center justify-center p-4 text-sm text-[var(--text-muted)]">
<div className="text-center"> <div className="text-center">
<Terminal className="h-10 w-10 mx-auto mb-3 opacity-30" /> <Terminal className="h-10 w-10 mx-auto mb-3 opacity-30" />
@ -145,10 +145,10 @@ export function ToolPanel({ messages }: ToolPanelProps) {
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<style>{animStyles}</style> <style>{animStyles}</style>
<div className="border-b border-white/8 p-4 font-semibold text-white flex items-center gap-2"> <div className="border-b border-[var(--border-color)] p-4 font-semibold text-[var(--text-primary)] flex items-center gap-2">
<Terminal className="h-4 w-4 text-[#00f0ff]" /> <Terminal className="h-4 w-4 text-[var(--accent-cyan)]" />
<span className="ml-auto text-xs px-2 py-0.5 rounded-full bg-[#00f0ff]/10 text-[#00f0ff]"> <span className="ml-auto text-xs px-2 py-0.5 rounded-full bg-[var(--accent-cyan)]/10 text-[var(--accent-cyan)]">
{toolCalls.length} {toolCalls.length}
</span> </span>
</div> </div>
@ -167,31 +167,31 @@ export function ToolPanel({ messages }: ToolPanelProps) {
return ( return (
<div <div
key={tool.toolCallId} key={tool.toolCallId}
className={`rounded-xl border bg-[#1a1a25]/50 text-sm overflow-hidden tool-card transition-colors duration-500 ${config.borderClass}`} className={`rounded-xl border bg-[var(--bg-tertiary)]/50 text-sm overflow-hidden tool-card transition-colors duration-500 ${config.borderClass}`}
> >
<button <button
onClick={() => toggleExpand(tool.toolCallId)} onClick={() => toggleExpand(tool.toolCallId)}
className="flex w-full items-center justify-between px-3 py-2.5 hover:bg-white/5 transition-colors" className="flex w-full items-center justify-between px-3 py-2.5 hover:bg-[var(--overlay-hover)] transition-colors"
> >
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<span className={`tool-status-icon ${tool.status === 'calling' && !hasResult ? 'animate-pulse' : ''}`}> <span className={`tool-status-icon ${tool.status === 'calling' && !hasResult ? 'animate-pulse' : ''}`}>
<StatusIcon className={`h-3.5 w-3.5 transition-colors duration-500 ${config.iconColor}`} /> <StatusIcon className={`h-3.5 w-3.5 transition-colors duration-500 ${config.iconColor}`} />
</span> </span>
<span className="font-medium text-zinc-300 truncate">{tool.toolName}</span> <span className="font-medium text-[var(--text-secondary)] truncate">{tool.toolName}</span>
<span className={`text-xs flex-shrink-0 transition-colors duration-500 ${config.labelClass}`}> <span className={`text-xs flex-shrink-0 transition-colors duration-500 ${config.labelClass}`}>
{config.label} {config.label}
</span> </span>
{tool.status === 'result' && tool.durationMs != null && ( {tool.status === 'result' && tool.durationMs != null && (
<span className="text-xs text-zinc-600 flex-shrink-0 tabular-nums ml-1"> <span className="text-xs text-[var(--text-muted)] flex-shrink-0 tabular-nums ml-1">
{formatDuration(tool.durationMs)} {formatDuration(tool.durationMs)}
</span> </span>
)} )}
</div> </div>
<span className="flex-shrink-0 ml-2"> <span className="flex-shrink-0 ml-2">
{isExpanded ? ( {isExpanded ? (
<ChevronDown className="h-4 w-4 text-zinc-500" /> <ChevronDown className="h-4 w-4 text-[var(--text-muted)]" />
) : ( ) : (
<ChevronRight className="h-4 w-4 text-zinc-500" /> <ChevronRight className="h-4 w-4 text-[var(--text-muted)]" />
)} )}
</span> </span>
</button> </button>
@ -200,7 +200,7 @@ export function ToolPanel({ messages }: ToolPanelProps) {
{hasResult && ( {hasResult && (
<div className="px-3 pb-2"> <div className="px-3 pb-2">
<div <div
className={`rounded-lg bg-black/30 px-2.5 py-2 text-xs text-zinc-400 font-mono cursor-pointer hover:bg-black/40 transition-colors ${ className={`rounded-lg bg-[var(--overlay-dim-strong)] px-2.5 py-2 text-xs text-[var(--text-secondary)] font-mono cursor-pointer hover:bg-[var(--overlay-dim-heavy)] transition-colors ${
isExpanded ? '' : 'line-clamp-2' isExpanded ? '' : 'line-clamp-2'
}`} }`}
onClick={() => toggleExpand(tool.toolCallId)} onClick={() => toggleExpand(tool.toolCallId)}
@ -212,7 +212,7 @@ export function ToolPanel({ messages }: ToolPanelProps) {
)} )}
</div> </div>
{!isExpanded && hasMore && ( {!isExpanded && hasMore && (
<div className="text-xs text-zinc-500 mt-1 px-1"> <div className="text-xs text-[var(--text-muted)] mt-1 px-1">
({displayContent.split('\n').length} ) ({displayContent.split('\n').length} )
</div> </div>
)} )}
@ -221,9 +221,9 @@ export function ToolPanel({ messages }: ToolPanelProps) {
{/* 展开区域:参数 */} {/* 展开区域:参数 */}
{isExpanded && tool.arguments ? ( {isExpanded && tool.arguments ? (
<div className="border-t border-white/8 px-3 py-2 bg-black/20"> <div className="border-t border-[var(--border-color)] px-3 py-2 bg-[var(--overlay-dim)]">
<div className="text-xs font-medium text-zinc-500 mb-1">:</div> <div className="text-xs font-medium text-[var(--text-muted)] mb-1">:</div>
<pre className="rounded-lg bg-black/40 p-2 text-xs overflow-x-auto text-zinc-400 font-mono whitespace-pre-wrap break-all"> <pre className="rounded-lg bg-[var(--overlay-dim-heavy)] p-2 text-xs overflow-x-auto text-[var(--text-secondary)] font-mono whitespace-pre-wrap break-all">
{JSON.stringify(tool.arguments, null, 2)} {JSON.stringify(tool.arguments, null, 2)}
</pre> </pre>
</div> </div>

View File

@ -68,7 +68,7 @@ function lastStatusIcon(lastStatus: string | undefined) {
case 'error': case 'error':
return <X className="h-3 w-3 text-red-400" /> return <X className="h-3 w-3 text-red-400" />
default: default:
return <Minus className="h-3 w-3 text-zinc-500" /> return <Minus className="h-3 w-3 text-[var(--text-muted)]" />
} }
} }
@ -76,17 +76,17 @@ export function SchedulerJobList({ jobs, onRefresh, onViewJob, sessionId }: Sche
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between border-b border-white/8 px-3 py-2.5"> <div className="flex items-center justify-between border-b border-[var(--border-color)] px-3 py-2.5">
<h2 className="font-semibold text-white flex items-center gap-2 text-sm"> <h2 className="font-semibold text-[var(--text-primary)] flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-[#00f0ff]" /> <Clock className="h-4 w-4 text-[var(--accent-cyan)]" />
{jobs.length > 0 && ( {jobs.length > 0 && (
<span className="text-xs text-zinc-500">({jobs.length})</span> <span className="text-xs text-[var(--text-muted)]">({jobs.length})</span>
)} )}
</h2> </h2>
<button <button
onClick={onRefresh} onClick={onRefresh}
className="flex items-center gap-1 rounded-lg px-2 py-1 text-xs text-zinc-400 hover:text-white hover:bg-white/10 transition-all" className="flex items-center gap-1 rounded-lg px-2 py-1 text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-subtle)] transition-all"
> >
<RefreshCw className="h-3.5 w-3.5" /> <RefreshCw className="h-3.5 w-3.5" />
@ -96,12 +96,12 @@ export function SchedulerJobList({ jobs, onRefresh, onViewJob, sessionId }: Sche
{/* Job List */} {/* Job List */}
<div className="flex-1 overflow-y-auto p-2"> <div className="flex-1 overflow-y-auto p-2">
{!sessionId ? ( {!sessionId ? (
<div className="p-4 text-center text-sm text-zinc-500"> <div className="p-4 text-center text-sm text-[var(--text-muted)]">
<Clock className="h-8 w-8 mx-auto mb-2 opacity-50" /> <Clock className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p>...</p> <p>...</p>
</div> </div>
) : jobs.length === 0 ? ( ) : jobs.length === 0 ? (
<div className="p-4 text-center text-sm text-zinc-500"> <div className="p-4 text-center text-sm text-[var(--text-muted)]">
<Clock className="h-8 w-8 mx-auto mb-2 opacity-50" /> <Clock className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p></p> <p></p>
</div> </div>
@ -121,23 +121,23 @@ export function SchedulerJobList({ jobs, onRefresh, onViewJob, sessionId }: Sche
disabled={!hasLookup} disabled={!hasLookup}
className={`w-full rounded-lg px-3 py-2.5 text-left transition-all border ${ className={`w-full rounded-lg px-3 py-2.5 text-left transition-all border ${
hasLookup hasLookup
? 'hover:bg-white/5 border-transparent hover:border-white/10 cursor-pointer' ? 'hover:bg-[var(--overlay-hover)] border-transparent hover:border-[var(--border-color)] cursor-pointer'
: 'border-transparent cursor-default opacity-70' : 'border-transparent cursor-default opacity-70'
}`} }`}
> >
{/* Row 1: ID + kind + enabled */} {/* Row 1: ID + kind + enabled */}
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-1.5 min-w-0 flex-1"> <div className="flex items-center gap-1.5 min-w-0 flex-1">
<span className="text-xs text-zinc-500 font-mono truncate max-w-[120px]"> <span className="text-xs text-[var(--text-muted)] font-mono truncate max-w-[120px]">
{job.id} {job.id}
</span> </span>
<span className="text-xs px-1.5 py-0.5 rounded bg-white/10 text-zinc-300 shrink-0"> <span className="text-xs px-1.5 py-0.5 rounded bg-[var(--overlay-subtle)] text-[var(--text-secondary)] shrink-0">
{kindLabel(job.kind)} {kindLabel(job.kind)}
</span> </span>
</div> </div>
<div className="flex items-center gap-1.5 shrink-0"> <div className="flex items-center gap-1.5 shrink-0">
<span className={`inline-block h-1.5 w-1.5 rounded-full ${job.enabled ? 'bg-emerald-400' : 'bg-zinc-600'}`} /> <span className={`inline-block h-1.5 w-1.5 rounded-full ${job.enabled ? 'bg-emerald-400' : 'bg-zinc-600'}`} />
{hasLookup && <ChevronRight className="h-3.5 w-3.5 text-zinc-600" />} {hasLookup && <ChevronRight className="h-3.5 w-3.5 text-[var(--text-muted)]" />}
</div> </div>
</div> </div>
@ -147,11 +147,11 @@ export function SchedulerJobList({ jobs, onRefresh, onViewJob, sessionId }: Sche
{badge.pulse && <span className="inline-block h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse" />} {badge.pulse && <span className="inline-block h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse" />}
{badge.label} {badge.label}
</span> </span>
<span className="inline-flex items-center gap-0.5 text-xs text-zinc-500"> <span className="inline-flex items-center gap-0.5 text-xs text-[var(--text-muted)]">
{lastStatusIcon(job.last_status)} {lastStatusIcon(job.last_status)}
<span className={ <span className={
job.last_status === 'error' ? 'text-red-400' : job.last_status === 'error' ? 'text-red-400' :
job.last_status === 'ok' ? 'text-emerald-400' : 'text-zinc-500' job.last_status === 'ok' ? 'text-emerald-400' : 'text-[var(--text-muted)]'
}> }>
{job.last_status === 'ok' ? '正常' : {job.last_status === 'ok' ? '正常' :
job.last_status === 'error' ? '异常' : job.last_status === 'error' ? '异常' :
@ -168,17 +168,17 @@ export function SchedulerJobList({ jobs, onRefresh, onViewJob, sessionId }: Sche
)} )}
{/* Row 4: Run count + schedule */} {/* Row 4: Run count + schedule */}
<div className="flex items-center justify-between text-xs text-zinc-500 mb-1"> <div className="flex items-center justify-between text-xs text-[var(--text-muted)] mb-1">
<span> <span>
: {job.run_count}{job.max_runs ? `/${job.max_runs}` : ''} : {job.run_count}{job.max_runs ? `/${job.max_runs}` : ''}
</span> </span>
<span className="truncate max-w-[120px] text-zinc-600"> <span className="truncate max-w-[120px] text-[var(--text-muted)]">
{scheduleDescription(job.schedule)} {scheduleDescription(job.schedule)}
</span> </span>
</div> </div>
{/* Row 5: Times */} {/* Row 5: Times */}
<div className="flex items-center justify-between text-xs text-zinc-600"> <div className="flex items-center justify-between text-xs text-[var(--text-muted)]">
<span>: {formatTime(job.last_fired_at)}</span> <span>: {formatTime(job.last_fired_at)}</span>
<span>: {formatTime(job.next_fire_at)}</span> <span>: {formatTime(job.next_fire_at)}</span>
</div> </div>

View File

@ -10,36 +10,36 @@ export function SessionInfo({ session, connectionId }: SessionInfoProps) {
return ( return (
<div className="p-4"> <div className="p-4">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<Wifi className="h-4 w-4 text-[#00f0ff]" /> <Wifi className="h-4 w-4 text-[var(--accent-cyan)]" />
<span className="text-sm font-medium text-white">WebSocket</span> <span className="text-sm font-medium text-[var(--text-primary)]">WebSocket</span>
<span className="text-xs px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-400"> <span className="text-xs px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-400">
线 线
</span> </span>
</div> </div>
<div className="rounded-xl border border-white/10 bg-[#1a1a25]/80 p-3"> <div className="rounded-xl border border-[var(--border-color)] bg-[var(--bg-tertiary)]/80 p-3">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<FolderOpen className="h-4 w-4 text-zinc-400" /> <FolderOpen className="h-4 w-4 text-[var(--text-secondary)]" />
<span className="text-xs text-zinc-500 uppercase tracking-wider"></span> <span className="text-xs text-[var(--text-muted)] uppercase tracking-wider"></span>
</div> </div>
{session ? ( {session ? (
<div className="space-y-1"> <div className="space-y-1">
<p className="text-sm font-medium text-white truncate"> <p className="text-sm font-medium text-[var(--text-primary)] truncate">
{session.title} {session.title}
</p> </p>
<div className="flex items-center gap-1 text-xs text-zinc-500"> <div className="flex items-center gap-1 text-xs text-[var(--text-muted)]">
<Hash className="h-3 w-3" /> <Hash className="h-3 w-3" />
<span className="font-mono">{session.id.slice(0, 8)}...</span> <span className="font-mono">{session.id.slice(0, 8)}...</span>
</div> </div>
</div> </div>
) : ( ) : (
<p className="text-sm text-zinc-500">...</p> <p className="text-sm text-[var(--text-muted)]">...</p>
)} )}
{connectionId && ( {connectionId && (
<div className="mt-2 pt-2 border-t border-white/10"> <div className="mt-2 pt-2 border-t border-[var(--border-color)]">
<p className="text-xs text-zinc-600 font-mono"> <p className="text-xs text-[var(--text-muted)] font-mono">
conn: {connectionId.slice(0, 8)}... conn: {connectionId.slice(0, 8)}...
</p> </p>
</div> </div>

View File

@ -39,12 +39,12 @@ export function TopicList({
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between border-b border-white/8 px-4 py-3"> <div className="flex items-center justify-between border-b border-[var(--border-color)] px-4 py-3">
<h2 className="font-semibold text-white flex items-center gap-2 text-sm"> <h2 className="font-semibold text-[var(--text-primary)] flex items-center gap-2 text-sm">
<Layers className="h-4 w-4 text-[#00f0ff]" /> <Layers className="h-4 w-4 text-[var(--accent-cyan)]" />
{topics.length > 0 && ( {topics.length > 0 && (
<span className="text-xs text-zinc-500">({topics.length})</span> <span className="text-xs text-[var(--text-muted)]">({topics.length})</span>
)} )}
</h2> </h2>
<button <button
@ -52,8 +52,8 @@ export function TopicList({
disabled={isReadOnly || !sessionId} disabled={isReadOnly || !sessionId}
className={`flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm transition-all ${ className={`flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm transition-all ${
isReadOnly || !sessionId isReadOnly || !sessionId
? 'bg-zinc-500/10 text-zinc-500 cursor-not-allowed' ? 'bg-[var(--overlay-subtle)] text-[var(--text-muted)] cursor-not-allowed'
: 'bg-[#00f0ff]/10 text-[#00f0ff] hover:bg-[#00f0ff]/20 border border-[#00f0ff]/30' : 'bg-[var(--accent-cyan)]/10 text-[var(--accent-cyan)] hover:bg-[var(--accent-cyan)]/20 border border-[var(--accent-cyan)]/30'
}`} }`}
> >
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
@ -63,21 +63,21 @@ export function TopicList({
{/* Session 标题 */} {/* Session 标题 */}
{sessionTitle && ( {sessionTitle && (
<div className="px-4 py-2 border-b border-white/8 bg-[#00f0ff]/5"> <div className="px-4 py-2 border-b border-[var(--border-color)] bg-[var(--accent-cyan)]/5">
<p className="text-xs text-zinc-500 mb-1"></p> <p className="text-xs text-[var(--text-muted)] mb-1"></p>
<p className="text-sm text-zinc-300 font-medium truncate">{sessionTitle}</p> <p className="text-sm text-[var(--text-secondary)] font-medium truncate">{sessionTitle}</p>
</div> </div>
)} )}
{/* Topics 列表 */} {/* Topics 列表 */}
<div className="flex-1 overflow-y-auto p-3"> <div className="flex-1 overflow-y-auto p-3">
{!sessionId ? ( {!sessionId ? (
<div className="p-4 text-center text-sm text-zinc-500"> <div className="p-4 text-center text-sm text-[var(--text-muted)]">
<MessageSquare className="h-8 w-8 mx-auto mb-2 opacity-50" /> <MessageSquare className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p>...</p> <p>...</p>
</div> </div>
) : topics.length === 0 ? ( ) : topics.length === 0 ? (
<div className="p-4 text-center text-sm text-zinc-500"> <div className="p-4 text-center text-sm text-[var(--text-muted)]">
<MessageSquare className="h-8 w-8 mx-auto mb-2 opacity-50" /> <MessageSquare className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p></p> <p></p>
<p className="text-xs mt-1">"新建"</p> <p className="text-xs mt-1">"新建"</p>
@ -90,33 +90,33 @@ export function TopicList({
onClick={() => onSwitchTopic(topic.id)} onClick={() => onSwitchTopic(topic.id)}
className={`w-full rounded-xl px-3 py-3 text-left text-sm transition-all ${ className={`w-full rounded-xl px-3 py-3 text-left text-sm transition-all ${
topic.id === currentTopicId topic.id === currentTopicId
? 'bg-gradient-to-r from-[#00f0ff]/20 to-transparent border border-[#00f0ff]/30' ? 'bg-gradient-to-r from-[var(--accent-cyan)]/20 to-transparent border border-[var(--accent-cyan)]/30'
: 'hover:bg-white/5 border border-transparent' : 'hover:bg-[var(--overlay-hover)] border border-transparent'
}`} }`}
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<span className="mt-0.5 text-xs text-zinc-500 font-mono w-4"> <span className="mt-0.5 text-xs text-[var(--text-muted)] font-mono w-4">
{index + 1} {index + 1}
</span> </span>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className={`truncate font-medium ${ <div className={`truncate font-medium ${
topic.id === currentTopicId ? 'text-[#00f0ff]' : 'text-zinc-300' topic.id === currentTopicId ? 'text-[var(--accent-cyan)]' : 'text-[var(--text-secondary)]'
}`}> }`}>
{topic.description || topic.title} {topic.description || topic.title}
</div> </div>
<div className="flex items-center gap-3 mt-1.5"> <div className="flex items-center gap-3 mt-1.5">
<span className="text-xs text-zinc-500 flex items-center gap-1"> <span className="text-xs text-[var(--text-muted)] flex items-center gap-1">
<Hash className="h-3 w-3" /> <Hash className="h-3 w-3" />
{topic.message_count} {topic.message_count}
</span> </span>
<span className="text-xs text-zinc-600 flex items-center gap-1"> <span className="text-xs text-[var(--text-muted)] flex items-center gap-1">
<Clock className="h-3 w-3" /> <Clock className="h-3 w-3" />
{formatTime(topic.updated_at)} {formatTime(topic.updated_at)}
</span> </span>
</div> </div>
</div> </div>
{topic.id === currentTopicId && ( {topic.id === currentTopicId && (
<span className="inline-block h-2 w-2 rounded-full bg-[#00f0ff] shadow-lg shadow-[#00f0ff]/50 mt-1.5" /> <span className="inline-block h-2 w-2 rounded-full bg-[var(--accent-cyan)] shadow-lg shadow-[var(--shadow-glow-soft)] mt-1.5" />
)} )}
</div> </div>
</button> </button>

View File

@ -1,6 +1,8 @@
@import "tailwindcss"; @import "tailwindcss";
/* PicoBot Theme - Tech Dark */ /* ============================================
PicoBot Theme Dark (default)
============================================ */
:root { :root {
/* Core colors */ /* Core colors */
--bg-primary: #0a0a0f; --bg-primary: #0a0a0f;
@ -24,9 +26,104 @@
/* Border */ /* Border */
--border-color: rgba(255, 255, 255, 0.08); --border-color: rgba(255, 255, 255, 0.08);
--border-accent: rgba(0, 240, 255, 0.3); --border-accent: rgba(0, 240, 255, 0.3);
/* Overlay — for hover / subtle surface effects */
--overlay-hover: rgba(255, 255, 255, 0.05);
--overlay-subtle: rgba(255, 255, 255, 0.10);
--overlay-medium: rgba(255, 255, 255, 0.20);
--overlay-dim: rgba(0, 0, 0, 0.20);
--overlay-dim-strong: rgba(0, 0, 0, 0.30);
--overlay-dim-heavy: rgba(0, 0, 0, 0.40);
--overlay-code: rgba(0, 0, 0, 0.30);
/* Shadows */
--shadow-glow-sm: rgba(0, 240, 255, 0.12);
--shadow-glow: rgba(0, 240, 255, 0.20);
--shadow-glow-strong: rgba(0, 240, 255, 0.30);
--shadow-glow-soft: rgba(0, 240, 255, 0.50);
/* Focus ring */
--focus-ring: rgba(0, 240, 255, 0.20);
/* Selection / code highlight */
--selection-bg: rgba(0, 240, 255, 0.30);
/* Divider */
--divider-color: rgba(255, 255, 255, 0.20);
color-scheme: dark;
} }
/* Custom scrollbar */ /* ============================================
PicoBot Theme Light
============================================ */
html.light {
/* Backgrounds */
--bg-primary: #f5f5f7;
--bg-secondary: #ffffff;
--bg-tertiary: #ebecf0;
--bg-hover: #dfe0e5;
/* Accents — slightly darker for readability on white */
--accent-cyan: #008899;
--accent-blue: #2563eb;
--accent-purple: #7c3aed;
--accent-green: #059669;
--accent-amber: #d97706;
/* Text */
--text-primary: #1a1a2e;
--text-secondary: #52525b;
--text-muted: #a1a1aa;
--text-accent: var(--accent-cyan);
/* Borders */
--border-color: rgba(0, 0, 0, 0.08);
--border-accent: rgba(0, 136, 153, 0.30);
/* Overlays */
--overlay-hover: rgba(0, 0, 0, 0.04);
--overlay-subtle: rgba(0, 0, 0, 0.06);
--overlay-medium: rgba(0, 0, 0, 0.10);
--overlay-dim: rgba(0, 0, 0, 0.04);
--overlay-dim-strong: rgba(0, 0, 0, 0.06);
--overlay-dim-heavy: rgba(0, 0, 0, 0.08);
--overlay-code: rgba(0, 0, 0, 0.06);
/* Shadows — softer */
--shadow-glow-sm: rgba(0, 136, 153, 0.10);
--shadow-glow: rgba(0, 136, 153, 0.15);
--shadow-glow-strong: rgba(0, 136, 153, 0.22);
--shadow-glow-soft: rgba(0, 136, 153, 0.35);
/* Focus ring */
--focus-ring: rgba(0, 136, 153, 0.20);
/* Selection / code highlight */
--selection-bg: rgba(0, 136, 153, 0.20);
/* Divider */
--divider-color: rgba(0, 0, 0, 0.10);
color-scheme: light;
}
/* ============================================
Smooth theme transition
============================================ */
html.theme-transitioning *,
html.theme-transitioning *::before,
html.theme-transitioning *::after {
transition: background-color 0.3s ease,
color 0.3s ease,
border-color 0.3s ease,
box-shadow 0.3s ease,
text-shadow 0.3s ease !important;
}
/* ============================================
Custom scrollbar
============================================ */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;
height: 6px; height: 6px;
@ -55,7 +152,9 @@
display: none; display: none;
} }
/* Base styles */ /* ============================================
Base styles
============================================ */
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@ -71,20 +170,26 @@ body {
line-height: 1.6; line-height: 1.6;
} }
/* Custom selection */ /* ============================================
Custom selection
============================================ */
::selection { ::selection {
background: rgba(0, 240, 255, 0.3); background: var(--selection-bg);
color: var(--text-primary); color: var(--text-primary);
} }
/* Glowing text effect */ /* ============================================
Glowing text effect
============================================ */
.glow-text { .glow-text {
text-shadow: 0 0 10px rgba(0, 240, 255, 0.5), text-shadow: 0 0 10px var(--shadow-glow-soft),
0 0 20px rgba(0, 240, 255, 0.3), 0 0 20px var(--shadow-glow-strong),
0 0 30px rgba(0, 240, 255, 0.1); 0 0 30px var(--shadow-glow-sm);
} }
/* Gradient border */ /* ============================================
Gradient border
============================================ */
.gradient-border { .gradient-border {
position: relative; position: relative;
} }
@ -106,7 +211,9 @@ body {
mask-composite: exclude; mask-composite: exclude;
} }
/* Animations */ /* ============================================
Animations
============================================ */
@keyframes pulse-glow { @keyframes pulse-glow {
0%, 100% { 0%, 100% {
box-shadow: 0 0 5px var(--accent-cyan), box-shadow: 0 0 5px var(--accent-cyan),
@ -132,63 +239,35 @@ body {
} }
@keyframes fade-in { @keyframes fade-in {
from { from { opacity: 0; }
opacity: 0; to { opacity: 1; }
}
to {
opacity: 1;
}
} }
@keyframes typing-dot { @keyframes typing-dot {
0%, 60%, 100% { 0%, 60%, 100% { transform: translateY(0); }
transform: translateY(0); 30% { transform: translateY(-4px); }
}
30% {
transform: translateY(-4px);
}
} }
@keyframes scale-in { @keyframes scale-in {
0% { 0% { opacity: 0; transform: scale(0.5); }
opacity: 0; 50% { transform: scale(1.1); }
transform: scale(0.5); 100% { opacity: 1; transform: scale(1); }
}
50% {
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1);
}
} }
.animate-slide-in { .animate-slide-in { animation: slide-in 0.3s ease-out; }
animation: slide-in 0.3s ease-out; .animate-fade-in { animation: fade-in 0.2s ease-out; }
} .animate-scale-in { animation: scale-in 0.3s ease-out; }
.animate-fade-in {
animation: fade-in 0.2s ease-out;
}
.animate-scale-in {
animation: scale-in 0.3s ease-out;
}
.typing-indicator span { .typing-indicator span {
animation: typing-dot 1.4s infinite; animation: typing-dot 1.4s infinite;
display: inline-block; display: inline-block;
} }
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
.typing-indicator span:nth-child(2) { /* ============================================
animation-delay: 0.2s; Code block styling
} ============================================ */
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
/* Code block styling */
pre { pre {
background: var(--bg-tertiary); background: var(--bg-tertiary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
@ -202,15 +281,19 @@ code {
font-size: 0.9em; font-size: 0.9em;
} }
/* Focus styles */ /* ============================================
Focus styles
============================================ */
input:focus, input:focus,
textarea:focus { textarea:focus {
outline: none; outline: none;
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
box-shadow: 0 0 0 2px rgba(0, 240, 255, 0.2); box-shadow: 0 0 0 2px var(--focus-ring);
} }
/* Button hover effects */ /* ============================================
Button hover effects
============================================ */
button { button {
transition: all 0.2s ease; transition: all 0.2s ease;
} }
@ -223,7 +306,9 @@ button:active:not(:disabled) {
transform: translateY(0); transform: translateY(0);
} }
/* Link styles */ /* ============================================
Link styles
============================================ */
a { a {
color: var(--accent-cyan); color: var(--accent-cyan);
text-decoration: none; text-decoration: none;