Merge branch 'dhk-2' into 'master'

样式修改

See merge request knowledge-base-bj/deerflow-server!13
This commit is contained in:
董宏凯 committed 2026-05-14 08:04:28 +00:00
commit f6d3d013fd
12 files changed
+408 -61

No files matched your search

+7 -1
View File
@@ -5,6 +5,8 @@ import { ThemeProvider as NextThemesProvider } from "next-themes";
import { QueryClientProvider } from "@/components/query-client-provider"; import { QueryClientProvider } from "@/components/query-client-provider";
import { I18nProvider } from "@/core/i18n/context"; import { I18nProvider } from "@/core/i18n/context";
import { detectLocale } from "@/core/i18n/locale"; import { detectLocale } from "@/core/i18n/locale";
import { ChatProvider } from "@/contexts/ChatContext";
import { ThemeProvider } from "@/contexts/ThemeContext";
import { HashRouter, useLocation } from "react-router-dom"; import { HashRouter, useLocation } from "react-router-dom";
@@ -26,7 +28,11 @@ function AppProviders({ children }: { children: React.ReactNode }) {
forcedTheme={forcedTheme} forcedTheme={forcedTheme}
> >
<I18nProvider initialLocale={locale}> <I18nProvider initialLocale={locale}>
<QueryClientProvider>{children}</QueryClientProvider> <ThemeProvider>
<ChatProvider>
<QueryClientProvider>{children}</QueryClientProvider>
</ChatProvider>
</ThemeProvider>
</I18nProvider> </I18nProvider>
</NextThemesProvider> </NextThemesProvider>
); );
+3 -3
View File
@@ -139,7 +139,7 @@ function SidebarProvider({
} as React.CSSProperties } as React.CSSProperties
} }
className={cn( className={cn(
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex h-full w-full",
className, className,
)} )}
{...props} {...props}
@@ -229,7 +229,7 @@ function Sidebar({
<div <div
data-slot="sidebar-container" data-slot="sidebar-container"
className={cn( className={cn(
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex", "fixed top-16 bottom-0 z-10 hidden w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
side === "left" side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
@@ -309,7 +309,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
<main <main
data-slot="sidebar-inset" data-slot="sidebar-inset"
className={cn( className={cn(
"bg-[#fffdfa] relative flex w-full flex-1 flex-col", "bg-background relative flex w-full flex-1 flex-col",
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2", "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className, className,
)} )}
@@ -449,7 +449,7 @@ export function InputBox({
<button <button
key={q.id} key={q.id}
type="button" type="button"
className="rounded-full border border-[#e4e4e7] bg-white px-4 py-2 text-sm text-[#18181b] shadow-none transition-colors hover:bg-[#f4f4f5] cursor-pointer" className="rounded-full border border-[#e4e4e7] bg-white px-4 py-2 text-sm text-[#18181b] shadow-none transition-colors hover:bg-[#f4f4f5] cursor-pointer dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700"
onClick={() => { onClick={() => {
textInput.setInput(q.content); textInput.setInput(q.content);
setFollowupsHidden(true); setFollowupsHidden(true);
@@ -495,7 +495,7 @@ export function InputBox({
)} )}
<PromptInput <PromptInput
className={cn( className={cn(
"rounded-[1.85rem] border border-[#eadfce] bg-white shadow-[0_24px_70px_-34px_rgba(120,83,42,0.32)] ring-1 ring-[#f6efe5] transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-[1.85rem]", "rounded-[1.85rem] border border-[#eadfce] bg-white shadow-[0_24px_70px_-34px_rgba(120,83,42,0.32)] ring-1 ring-[#f6efe5] transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-[1.85rem] dark:bg-slate-800 dark:border-slate-700 dark:ring-slate-700 dark:shadow-none",
className, className,
)} )}
disabled={disabled} disabled={disabled}
@@ -510,7 +510,7 @@ export function InputBox({
<PromptInputBody className="absolute top-0 right-0 left-0 z-3"> <PromptInputBody className="absolute top-0 right-0 left-0 z-3">
<PromptInputTextarea <PromptInputTextarea
className={cn( className={cn(
"size-full px-2 py-1 text-[15px] leading-7 placeholder:text-[#b7ab9a]", "size-full px-2 py-1 text-[15px] leading-7 placeholder:text-[#b7ab9a] dark:placeholder:text-slate-500",
)} )}
disabled={disabled} disabled={disabled}
placeholder={t.inputBox.placeholder} placeholder={t.inputBox.placeholder}
@@ -518,7 +518,7 @@ export function InputBox({
defaultValue={initialValue} defaultValue={initialValue}
/> />
</PromptInputBody> </PromptInputBody>
<PromptInputFooter className="flex items-center border-t border-[#f4ede4] px-2 pt-2.5 pb-1.5"> <PromptInputFooter className="flex items-center border-t border-[#f4ede4] dark:border-slate-700 px-2 pt-2.5 pb-1.5">
<PromptInputTools className="gap-1.5"> <PromptInputTools className="gap-1.5">
{/* TODO: Add more connectors here {/* TODO: Add more connectors here
<PromptInputActionMenu> <PromptInputActionMenu>
@@ -529,7 +529,7 @@ export function InputBox({
/> />
</PromptInputActionMenuContent> </PromptInputActionMenuContent>
</PromptInputActionMenu> */} </PromptInputActionMenu> */}
<AddAttachmentsButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-2.5! shadow-none hover:bg-white" /> <AddAttachmentsButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-2.5! shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700" />
<PromptInputActionMenu> <PromptInputActionMenu>
<ModeHoverGuide <ModeHoverGuide
mode={ mode={
@@ -541,7 +541,7 @@ export function InputBox({
: "flash" : "flash"
} }
> >
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white"> <PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<div> <div>
{context.mode === "flash" && <ZapIcon className="size-3" />} {context.mode === "flash" && <ZapIcon className="size-3" />}
{context.mode === "thinking" && ( {context.mode === "thinking" && (
@@ -703,7 +703,7 @@ export function InputBox({
</PromptInputActionMenu> </PromptInputActionMenu>
{supportReasoningEffort && context.mode !== "flash" && ( {supportReasoningEffort && context.mode !== "flash" && (
<PromptInputActionMenu> <PromptInputActionMenu>
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white"> <PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<div className="text-xs font-normal"> <div className="text-xs font-normal">
{t.inputBox.reasoningEffort}: {t.inputBox.reasoningEffort}:
{context.reasoning_effort === "minimal" && {context.reasoning_effort === "minimal" &&
@@ -824,7 +824,7 @@ export function InputBox({
onOpenChange={setModelDialogOpen} onOpenChange={setModelDialogOpen}
> >
<ModelSelectorTrigger asChild> <ModelSelectorTrigger asChild>
<PromptInputButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3 shadow-none hover:bg-white"> <PromptInputButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3 shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<div className="flex min-w-0 flex-col items-start text-left"> <div className="flex min-w-0 flex-col items-start text-left">
<ModelSelectorName className="text-xs font-normal"> <ModelSelectorName className="text-xs font-normal">
{selectedModel?.display_name} {selectedModel?.display_name}
@@ -858,7 +858,7 @@ export function InputBox({
</ModelSelectorContent> </ModelSelectorContent>
</ModelSelector> </ModelSelector>
<PromptInputSubmit <PromptInputSubmit
className="rounded-full border-[#e7d9c7] bg-white shadow-[0_10px_24px_-18px_rgba(120,83,42,0.34)]" className="rounded-full border-[#e7d9c7] bg-white shadow-[0_10px_24px_-18px_rgba(120,83,42,0.34)] dark:bg-blue-500 dark:border-blue-500 dark:text-white dark:hover:bg-blue-400 dark:shadow-none"
disabled={disabled} disabled={disabled}
variant="outline" variant="outline"
status={status} status={status}
@@ -866,7 +866,7 @@ export function InputBox({
</PromptInputTools> </PromptInputTools>
</PromptInputFooter> </PromptInputFooter>
{!isNewThread && ( {!isNewThread && (
<div className="absolute right-0 -bottom-[17px] left-0 z-0 h-4 bg-[#fffdfa]"></div> <div className="absolute right-0 -bottom-[17px] left-0 z-0 h-4 bg-[#fffdfa] dark:bg-background"></div>
)} )}
</PromptInput> </PromptInput>
@@ -454,6 +454,7 @@ function ClarificationCard({
const payload = getClarificationPayload(message); const payload = getClarificationPayload(message);
const [customAnswer, setCustomAnswer] = useState(""); const [customAnswer, setCustomAnswer] = useState("");
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [submitted, setSubmitted] = useState(false);
const options = useMemo( const options = useMemo(
() => () =>
@@ -473,16 +474,17 @@ function ClarificationCard({
); );
} }
const disabled = isLoading || submitting || !onSubmit; const disabled = false;
async function submitAnswer(answer: string) { async function submitAnswer(answer: string) {
const trimmed = answer.trim(); const trimmed = answer.trim();
if (!trimmed || disabled) { if (!trimmed) {
return; return;
} }
setSubmitting(true); setSubmitting(true);
try { try {
await onSubmit?.(trimmed); await onSubmit?.(trimmed);
setSubmitted(true);
} finally { } finally {
setSubmitting(false); setSubmitting(false);
} }
+81
View File
@@ -0,0 +1,81 @@
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
export interface ChatMessage {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: number;
type?: 'general' | 'agent' | 'compare';
agentName?: string;
}
interface ChatContextType {
messages: ChatMessage[];
addMessage: (role: 'user' | 'assistant', content: string, type?: 'general' | 'agent' | 'compare', agentName?: string) => void;
clearMessages: () => void;
deleteMessage: (id: string) => void;
}
const ChatContext = createContext<ChatContextType | undefined>(undefined);
export function ChatProvider({ children }: { children: ReactNode }) {
const [messages, setMessages] = useState<ChatMessage[]>(() => {
try {
const stored = localStorage.getItem('qa-chat-history');
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
});
const saveToStorage = useCallback((newMessages: ChatMessage[]) => {
try {
localStorage.setItem('qa-chat-history', JSON.stringify(newMessages));
} catch (error) {
console.error('Failed to save chat history:', error);
}
}, []);
const addMessage = useCallback((role: 'user' | 'assistant', content: string, type?: 'general' | 'agent' | 'compare', agentName?: string) => {
const newMessage: ChatMessage = {
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
role,
content,
timestamp: Date.now(),
type,
agentName,
};
setMessages((prev) => {
const updated = [...prev, newMessage];
saveToStorage(updated);
return updated;
});
}, [saveToStorage]);
const clearMessages = useCallback(() => {
setMessages([]);
localStorage.removeItem('qa-chat-history');
}, []);
const deleteMessage = useCallback((id: string) => {
setMessages((prev) => {
const updated = prev.filter((msg) => msg.id !== id);
saveToStorage(updated);
return updated;
});
}, [saveToStorage]);
return (
<ChatContext.Provider value={{ messages, addMessage, clearMessages, deleteMessage }}>
{children}
</ChatContext.Provider>
);
}
export function useChatContext() {
const context = useContext(ChatContext);
if (context === undefined) {
throw new Error('useChatContext must be used within a ChatProvider');
}
return context;
}
+145
View File
@@ -0,0 +1,145 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
// 主题类型
export type ThemeType = 'dark' | 'light';
// 主题配置
export interface ThemeConfig {
type: ThemeType;
name: string;
colors: {
background: string;
backgroundSecondary: string;
text: string;
textSecondary: string;
primary: string;
primaryHover: string;
border: string;
accent: string;
success: string;
warning: string;
error: string;
card: string;
cardHover: string;
input: string;
inputBorder: string;
overlay: string;
};
}
// 主题配置
const themes: Record<ThemeType, ThemeConfig> = {
dark: {
type: 'dark',
name: '深色主题',
colors: {
background: 'slate-900',
backgroundSecondary: 'slate-800',
text: 'white',
textSecondary: 'slate-400',
primary: 'blue-500',
primaryHover: 'blue-400',
border: 'slate-700',
accent: 'blue-600',
success: 'emerald-500',
warning: 'amber-500',
error: 'rose-500',
card: 'slate-800',
cardHover: 'slate-700',
input: 'slate-800',
inputBorder: 'slate-600',
overlay: 'slate-900/80',
},
},
light: {
type: 'light',
name: '红白主题',
colors: {
background: 'white',
backgroundSecondary: 'red-50',
text: 'gray-900',
textSecondary: 'gray-600',
primary: 'red-600',
primaryHover: 'red-500',
border: 'red-200',
accent: 'red-700',
success: 'green-600',
warning: 'amber-500',
error: 'red-600',
card: 'white',
cardHover: 'red-50',
input: 'white',
inputBorder: 'red-200',
overlay: 'black/30',
},
},
};
// 主题上下文
interface ThemeContextType {
theme: ThemeType;
themeConfig: ThemeConfig;
toggleTheme: () => void;
setTheme: (theme: ThemeType) => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// 主题提供者
interface ThemeProviderProps {
children: ReactNode;
}
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
const [theme, setThemeState] = useState<ThemeType>(() => {
// 从 localStorage 读取保存的主题
const saved = localStorage.getItem('strategy-theme');
return (saved as ThemeType) || 'dark';
});
// 切换主题
const toggleTheme = () => {
setThemeState((prev) => (prev === 'dark' ? 'light' : 'dark'));
};
// 设置特定主题
const setTheme = (newTheme: ThemeType) => {
setThemeState(newTheme);
};
// 保存到 localStorage
useEffect(() => {
localStorage.setItem('strategy-theme', theme);
// 更新文档类名
document.documentElement.classList.remove('dark', 'light');
document.documentElement.classList.add(theme);
}, [theme]);
const themeConfig = themes[theme];
return (
<ThemeContext.Provider value={{ theme, themeConfig, toggleTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
// 使用主题的 Hook
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
// 主题工具函数 - 生成动态类名
export const getThemeClasses = (colorKey: keyof ThemeConfig['colors']): string => {
const { themeConfig } = useTheme();
return themeConfig.colors[colorKey];
};
// 组合类名工具函数
export const cn = (...classes: (string | undefined | null | false)[]): string => {
return classes.filter(Boolean).join(' ');
};
+9
View File
@@ -98,6 +98,14 @@ export default function AgentChatPage() {
[sendMessage, threadId, agent_id], [sendMessage, threadId, agent_id],
); );
const handleClarificationSubmit = useCallback(
(answer: string) => {
setHasStartedConversation(true);
void sendMessage(threadId, { text: answer, files: [] }, { agent_id });
},
[sendMessage, threadId, agent_id],
);
const handleStop = useCallback(async () => { const handleStop = useCallback(async () => {
await thread.stop(); await thread.stop();
}, [thread]); }, [thread]);
@@ -160,6 +168,7 @@ export default function AgentChatPage() {
thread={thread} thread={thread}
paddingBottom={messageListPaddingBottom} paddingBottom={messageListPaddingBottom}
tokenUsageEnabled={tokenUsageEnabled} tokenUsageEnabled={tokenUsageEnabled}
onClarificationSubmit={handleClarificationSubmit}
/> />
</div> </div>
+9
View File
@@ -95,6 +95,14 @@ export default function ChatPage() {
}, },
[sendMessage, threadId], [sendMessage, threadId],
); );
const handleClarificationSubmit = useCallback(
(answer: string) => {
setHasStartedConversation(true);
void sendMessage(threadId, { text: answer, files: [] });
},
[sendMessage, threadId],
);
const handleStop = useCallback(async () => { const handleStop = useCallback(async () => {
await thread.stop(); await thread.stop();
}, [thread]); }, [thread]);
@@ -137,6 +145,7 @@ export default function ChatPage() {
thread={thread} thread={thread}
paddingBottom={messageListPaddingBottom} paddingBottom={messageListPaddingBottom}
tokenUsageEnabled={tokenUsageEnabled} tokenUsageEnabled={tokenUsageEnabled}
onClarificationSubmit={handleClarificationSubmit}
/> />
</div> </div>
<div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4"> <div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4">
+21 -17
View File
@@ -4,6 +4,7 @@ import { Navigate, Route, Routes } from "react-router-dom";
import { PageSidebar } from "@/components/page-sidebar"; import { PageSidebar } from "@/components/page-sidebar";
import WorkspaceRoutes from "./WorkspaceRoutes"; import WorkspaceRoutes from "./WorkspaceRoutes";
import Header from "@/strategy-components/Header";
// 设为 true 时隐藏侧边栏(开发中,暂未完成) // 设为 true 时隐藏侧边栏(开发中,暂未完成)
const SIDEBAR_ENABLED = false; const SIDEBAR_ENABLED = false;
@@ -12,31 +13,34 @@ function PageLayout() {
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
return ( return (
<div className="min-h-screen bg-muted/20"> <div className="h-screen flex bg-muted/20">
{SIDEBAR_ENABLED && ( {SIDEBAR_ENABLED && (
<PageSidebar <PageSidebar
collapsed={collapsed} collapsed={collapsed}
onToggleCollapsed={() => setCollapsed((prev) => !prev)} onToggleCollapsed={() => setCollapsed((prev) => !prev)}
/> />
)} )}
<main <div
className={`min-h-screen transition-[padding] duration-300 ${ className={`flex flex-col flex-1 overflow-hidden transition-[padding] duration-300 ${
!SIDEBAR_ENABLED ? "" : collapsed ? "pl-16" : "pl-16 md:pl-64" !SIDEBAR_ENABLED ? "" : collapsed ? "pl-16" : "pl-64"
}`} }`}
> >
<Routes> <Header />
<Route index element={<Navigate to="workspace" replace />} /> <main className="flex-1 overflow-hidden">
<Route <Routes>
path="workspace/*" <Route index element={<Navigate to="workspace" replace />} />
element={ <Route
<WorkspaceRoutes path="workspace/*"
pageSidebarCollapsed={collapsed} element={
pageSidebarHidden={!SIDEBAR_ENABLED} <WorkspaceRoutes
/> pageSidebarCollapsed={collapsed}
} pageSidebarHidden={!SIDEBAR_ENABLED}
/> />
</Routes> }
</main> />
</Routes>
</main>
</div>
</div> </div>
); );
} }
+1 -1
View File
@@ -37,7 +37,7 @@ function WorkspaceLayout({
return ( return (
<SidebarProvider <SidebarProvider
className="h-screen w-full overflow-x-hidden [&_[data-slot=sidebar-container]]:left-[var(--page-sidebar-width)]" className="h-full w-full overflow-x-hidden [&_[data-slot=sidebar-container]]:left-[var(--page-sidebar-width)]"
style={ style={
{ {
"--page-sidebar-width": pageSidebarWidth, "--page-sidebar-width": pageSidebarWidth,
@@ -0,0 +1,83 @@
import React from 'react';
import { Bell, Search, User, ChevronDown, Moon, Sun, Palette } from 'lucide-react';
import { useTheme } from '../contexts/ThemeContext';
const Header: React.FC = () => {
const { theme, toggleTheme } = useTheme();
const isDark = theme === 'dark';
return (
<header className={`h-16 flex items-center justify-between px-6 theme-transition ${
isDark
? 'bg-slate-900/50 backdrop-blur-xl border-b border-blue-800/30'
: 'bg-red-600 border-b border-red-700'
}`}>
{/* 左侧 - 搜索框 */}
<div className="flex items-center gap-4">
<div className="relative">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${isDark ? 'text-slate-500' : 'text-red-200'}`} />
<input
type="text"
placeholder="搜索场景、智能体、任务..."
className={`w-80 pl-10 pr-4 py-2 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-white/20 transition-all duration-200 theme-transition ${
isDark
? 'bg-slate-800/50 border border-blue-800/30 text-white placeholder-slate-500'
: 'bg-red-700 border border-red-800 text-white placeholder-red-200'
}`}
/>
</div>
</div>
{/* 右侧 - 用户信息和通知 */}
<div className="flex items-center gap-4">
{/* 主题切换按钮 */}
<button
onClick={toggleTheme}
className={`p-2 rounded-lg transition-all duration-200 ${
isDark
? 'hover:bg-slate-800/50 text-slate-400 hover:text-white'
: 'hover:bg-red-500 text-white'
}`}
title={isDark ? '切换到红白主题' : '切换到深色主题'}
>
{isDark ? (
<Palette className="w-5 h-5" />
) : (
<Moon className="w-5 h-5" />
)}
</button>
{/* 通知 */}
<button className={`relative p-2 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-800/50' : 'hover:bg-red-500'
}`}>
<Bell className={`w-5 h-5 ${isDark ? 'text-slate-400' : 'text-white'}`} />
<span className={`absolute top-1 right-1 w-2 h-2 rounded-full ${isDark ? 'bg-cyan-500' : 'bg-white'}`} />
</button>
{/* 分隔线 */}
<div className={`w-px h-8 ${isDark ? 'bg-blue-800/30' : 'bg-red-500'}`} />
{/* 用户信息 */}
<button className={`flex items-center gap-3 px-3 py-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-800/50' : 'hover:bg-red-500'
}`}>
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
isDark
? 'bg-gradient-to-br from-blue-500 to-cyan-400'
: 'bg-white'
}`}>
<User className={`w-4 h-4 ${isDark ? 'text-white' : 'text-red-600'}`} />
</div>
<div className="text-left">
<p className={`text-sm font-medium ${isDark ? 'text-white' : 'text-white'}`}>张策略</p>
<p className={`text-xs ${isDark ? 'text-blue-400/60' : 'text-red-200'}`}>策略分析师</p>
</div>
<ChevronDown className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-white'}`} />
</button>
</div>
</header>
);
};
export default Header;
+35 -27
View File
@@ -243,7 +243,7 @@
--workspace-brand: #d92121; --workspace-brand: #d92121;
--workspace-brand-foreground: #ffffff; --workspace-brand-foreground: #ffffff;
--radius: 0.75rem; --radius: 0.75rem;
--background: oklch(0.985 0.004 95); --background: oklch(1 0 0);
--foreground: oklch(0.22 0.02 25); --foreground: oklch(0.22 0.02 25);
--card: oklch(1 0.002 95); --card: oklch(1 0.002 95);
--card-foreground: oklch(0.22 0.02 25); --card-foreground: oklch(0.22 0.02 25);
@@ -277,39 +277,39 @@
} }
.dark { .dark {
--workspace-brand: #e11d1d; --workspace-brand: #3b82f6;
--workspace-brand-foreground: #ffffff; --workspace-brand-foreground: #ffffff;
--background: oklch(0.22 0.015 25); --background: oklch(0.208 0.042 264);
--foreground: oklch(0.96 0.01 95); --foreground: oklch(0.96 0.005 240);
--card: oklch(0.26 0.018 25); --card: oklch(0.265 0.04 260);
--card-foreground: oklch(0.96 0.01 95); --card-foreground: oklch(0.96 0.005 240);
--popover: oklch(0.24 0.018 25); --popover: oklch(0.245 0.04 262);
--popover-foreground: oklch(0.96 0.01 95); --popover-foreground: oklch(0.96 0.005 240);
--primary: oklch(0.62 0.17 27); --primary: oklch(0.623 0.214 259);
--primary-foreground: oklch(0.12 0.02 25); --primary-foreground: oklch(0.99 0 0);
--secondary: oklch(0.32 0.02 25); --secondary: oklch(0.32 0.038 260);
--secondary-foreground: oklch(0.96 0.01 95); --secondary-foreground: oklch(0.96 0.005 240);
--muted: oklch(0.3 0.02 25); --muted: oklch(0.30 0.035 260);
--muted-foreground: oklch(0.72 0.02 95); --muted-foreground: oklch(0.68 0.02 240);
--accent: oklch(0.34 0.06 25); --accent: oklch(0.35 0.06 260);
--accent-foreground: oklch(0.96 0.01 95); --accent-foreground: oklch(0.96 0.005 240);
--destructive: oklch(0.704 0.191 22.216); --destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 12%); --border: oklch(1 0 0 / 12%);
--input: oklch(1 0 0 / 14%); --input: oklch(1 0 0 / 14%);
--ring: oklch(0.62 0.17 27 / 0.45); --ring: oklch(0.623 0.214 259 / 0.45);
--chart-1: oklch(0.488 0.243 264.376); --chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48); --chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08); --chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9); --chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439); --chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.2 0.02 25); --sidebar: oklch(0.185 0.04 265);
--sidebar-foreground: oklch(0.94 0.01 95); --sidebar-foreground: oklch(0.94 0.005 240);
--sidebar-primary: oklch(0.62 0.17 27); --sidebar-primary: oklch(0.623 0.214 259);
--sidebar-primary-foreground: oklch(0.99 0 0); --sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.3 0.05 25); --sidebar-accent: oklch(0.30 0.055 260);
--sidebar-accent-foreground: oklch(0.96 0.01 95); --sidebar-accent-foreground: oklch(0.96 0.005 240);
--sidebar-border: oklch(1 0 0 / 10%); --sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.62 0.17 27 / 0.45); --sidebar-ring: oklch(0.623 0.214 259 / 0.45);
font-weight: 300; font-weight: 300;
} }
@@ -430,19 +430,27 @@
} }
.workspace-shell { .workspace-shell {
background: background: #ffffff;
radial-gradient(circle at top right, rgb(255 214 214 / 0.72), transparent 18%), }
linear-gradient(180deg, #fbf7f4 0%, #f6efea 100%);
.dark .workspace-shell {
background: var(--background);
} }
.workspace-panel { .workspace-panel {
border: 1px solid #f0d7d0; border: 1px solid #f0d7d0;
background: linear-gradient(180deg, rgb(255 253 251 / 0.98), rgb(255 247 244 / 0.98)); background: #ffffff;
box-shadow: box-shadow:
0 32px 72px -46px rgb(98 63 42 / 0.35), 0 32px 72px -46px rgb(98 63 42 / 0.35),
0 12px 28px -22px rgb(170 91 75 / 0.24); 0 12px 28px -22px rgb(170 91 75 / 0.24);
} }
.dark .workspace-panel {
border: 1px solid oklch(1 0 0 / 10%);
background: var(--card);
box-shadow: none;
}
.workspace-user-bubble { .workspace-user-bubble {
max-width: min(82%, 42rem); max-width: min(82%, 42rem);
border: 1px solid rgb(255 255 255 / 0.14); border: 1px solid rgb(255 255 255 / 0.14);