feat: 添加亮色/暗色主题切换功能
- 在 index.css 中新增 html.light 亮色主题 CSS 变量和过渡动画 - 在 App.tsx Header 添加 Sun/Moon 主题切换按钮 - 主题偏好通过 localStorage 持久化,默认暗色主题 - 将所有组件中的硬编码 Tailwind 颜色值转换为 CSS 变量引用 - 状态色(emerald/amber/red/violet)保持不变,两种主题均适用
This commit is contained in:
parent
d1d4998a26
commit
bd13cffe14
116
web/src/App.tsx
116
web/src/App.tsx
@ -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>
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user