From fea3cc741f15439851bcbbf783c403006f113d87 Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Thu, 14 May 2026 14:28:17 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/src/contexts/ChatContext.tsx | 81 ++++++++++++ frontend-web/src/contexts/ThemeContext.tsx | 145 +++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 frontend-web/src/contexts/ChatContext.tsx create mode 100644 frontend-web/src/contexts/ThemeContext.tsx diff --git a/frontend-web/src/contexts/ChatContext.tsx b/frontend-web/src/contexts/ChatContext.tsx new file mode 100644 index 00000000..67682896 --- /dev/null +++ b/frontend-web/src/contexts/ChatContext.tsx @@ -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(undefined); + +export function ChatProvider({ children }: { children: ReactNode }) { + const [messages, setMessages] = useState(() => { + 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 ( + + {children} + + ); +} + +export function useChatContext() { + const context = useContext(ChatContext); + if (context === undefined) { + throw new Error('useChatContext must be used within a ChatProvider'); + } + return context; +} \ No newline at end of file diff --git a/frontend-web/src/contexts/ThemeContext.tsx b/frontend-web/src/contexts/ThemeContext.tsx new file mode 100644 index 00000000..c6ce37b8 --- /dev/null +++ b/frontend-web/src/contexts/ThemeContext.tsx @@ -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 = { + dark: { + type: 'dark', + name: '深色主题', + colors: { + background: 'slate-900', + backgroundSecondary: 'slate-800', + text: 'white', + textSecondary: 'slate-400', + primary: 'blue-500', + primaryHover: 'blue-400', + border: 'blue-800', + accent: 'cyan-400', + success: 'emerald-500', + warning: 'amber-500', + error: 'rose-500', + card: 'slate-800', + cardHover: 'slate-700', + input: 'slate-800', + inputBorder: 'blue-800', + overlay: 'slate-900/50', + }, + }, + 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(undefined); + +// 主题提供者 +interface ThemeProviderProps { + children: ReactNode; +} + +export const ThemeProvider: React.FC = ({ children }) => { + const [theme, setThemeState] = useState(() => { + // 从 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 ( + + {children} + + ); +}; + +// 使用主题的 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(' '); +}; From 139dfb76e41ad066dd2f53ef0ae8a22e894dc954 Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Thu, 14 May 2026 14:59:12 +0800 Subject: [PATCH 2/3] =?UTF-8?q?bug=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/src/App.tsx | 8 +++++++- .../src/components/workspace/messages/message-list.tsx | 6 ++++-- frontend-web/src/pages/AgentChatPage.tsx | 9 +++++++++ frontend-web/src/pages/ChatPage.tsx | 9 +++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/frontend-web/src/App.tsx b/frontend-web/src/App.tsx index c1122c19..66fda5ba 100644 --- a/frontend-web/src/App.tsx +++ b/frontend-web/src/App.tsx @@ -5,6 +5,8 @@ import { ThemeProvider as NextThemesProvider } from "next-themes"; import { QueryClientProvider } from "@/components/query-client-provider"; import { I18nProvider } from "@/core/i18n/context"; import { detectLocale } from "@/core/i18n/locale"; +import { ChatProvider } from "@/contexts/ChatContext"; +import { ThemeProvider } from "@/contexts/ThemeContext"; import { HashRouter, useLocation } from "react-router-dom"; @@ -26,7 +28,11 @@ function AppProviders({ children }: { children: React.ReactNode }) { forcedTheme={forcedTheme} > - {children} + + + {children} + + ); diff --git a/frontend-web/src/components/workspace/messages/message-list.tsx b/frontend-web/src/components/workspace/messages/message-list.tsx index b9c8b90b..b27775be 100644 --- a/frontend-web/src/components/workspace/messages/message-list.tsx +++ b/frontend-web/src/components/workspace/messages/message-list.tsx @@ -454,6 +454,7 @@ function ClarificationCard({ const payload = getClarificationPayload(message); const [customAnswer, setCustomAnswer] = useState(""); const [submitting, setSubmitting] = useState(false); + const [submitted, setSubmitted] = useState(false); const options = useMemo( () => @@ -473,16 +474,17 @@ function ClarificationCard({ ); } - const disabled = isLoading || submitting || !onSubmit; + const disabled = false; async function submitAnswer(answer: string) { const trimmed = answer.trim(); - if (!trimmed || disabled) { + if (!trimmed) { return; } setSubmitting(true); try { await onSubmit?.(trimmed); + setSubmitted(true); } finally { setSubmitting(false); } diff --git a/frontend-web/src/pages/AgentChatPage.tsx b/frontend-web/src/pages/AgentChatPage.tsx index 19bd0918..871998b9 100644 --- a/frontend-web/src/pages/AgentChatPage.tsx +++ b/frontend-web/src/pages/AgentChatPage.tsx @@ -98,6 +98,14 @@ export default function AgentChatPage() { [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 () => { await thread.stop(); }, [thread]); @@ -160,6 +168,7 @@ export default function AgentChatPage() { thread={thread} paddingBottom={messageListPaddingBottom} tokenUsageEnabled={tokenUsageEnabled} + onClarificationSubmit={handleClarificationSubmit} /> diff --git a/frontend-web/src/pages/ChatPage.tsx b/frontend-web/src/pages/ChatPage.tsx index 0ac3427c..3ada83f5 100644 --- a/frontend-web/src/pages/ChatPage.tsx +++ b/frontend-web/src/pages/ChatPage.tsx @@ -95,6 +95,14 @@ export default function ChatPage() { }, [sendMessage, threadId], ); + + const handleClarificationSubmit = useCallback( + (answer: string) => { + setHasStartedConversation(true); + void sendMessage(threadId, { text: answer, files: [] }); + }, + [sendMessage, threadId], + ); const handleStop = useCallback(async () => { await thread.stop(); }, [thread]); @@ -137,6 +145,7 @@ export default function ChatPage() { thread={thread} paddingBottom={messageListPaddingBottom} tokenUsageEnabled={tokenUsageEnabled} + onClarificationSubmit={handleClarificationSubmit} />
From e6ad3c414f84b5be2c4d3fc79692e1b35bc85b5a Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Thu, 14 May 2026 16:01:25 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/src/components/ui/sidebar.tsx | 6 +- .../src/components/workspace/input-box.tsx | 20 ++--- frontend-web/src/contexts/ThemeContext.tsx | 8 +- frontend-web/src/pages/PageRoutes.tsx | 38 +++++---- frontend-web/src/pages/WorkspaceRoutes.tsx | 2 +- .../src/strategy-components/Header.tsx | 83 +++++++++++++++++++ frontend-web/src/styles/globals.css | 62 ++++++++------ 7 files changed, 157 insertions(+), 62 deletions(-) create mode 100644 frontend-web/src/strategy-components/Header.tsx diff --git a/frontend-web/src/components/ui/sidebar.tsx b/frontend-web/src/components/ui/sidebar.tsx index c62bf163..2e6d3ca1 100644 --- a/frontend-web/src/components/ui/sidebar.tsx +++ b/frontend-web/src/components/ui/sidebar.tsx @@ -139,7 +139,7 @@ function SidebarProvider({ } as React.CSSProperties } 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, )} {...props} @@ -229,7 +229,7 @@ function Sidebar({