diff --git a/frontend/app/analysis/page.tsx b/frontend/app/analysis/page.tsx index f1b262bc..353206e3 100644 --- a/frontend/app/analysis/page.tsx +++ b/frontend/app/analysis/page.tsx @@ -21,7 +21,7 @@ export default function AnalysisPage() { const { setAnalysisResult, setTaskId, setMarketType, marketType } = useAnalysisContext(); const { runAnalysis, loading, error, result, taskId } = useAnalysis(); const { isAuthenticated } = useAuth(); - const { t } = useLanguage(); + const { t, locale } = useLanguage(); // Ref to track if we've already saved (to prevent duplicate saves) const hasSavedRef = useRef(false); @@ -47,7 +47,8 @@ export default function AnalysisPage() { marketType, result.analysis_date, result, - taskId || undefined + taskId || undefined, + locale as "en" | "zh-TW" // Pass current language for filtering ); console.log("πŸ“ Auto-saved report to local storage"); @@ -58,6 +59,7 @@ export default function AnalysisPage() { market_type: marketType, analysis_date: result.analysis_date, result: result, + language: locale as "en" | "zh-TW", }); if (cloudId) { console.log("☁️ Auto-saved report to cloud"); @@ -68,7 +70,7 @@ export default function AnalysisPage() { } catch (error) { console.error("Auto-save failed:", error); } - }, [result, marketType, taskId, isAuthenticated]); + }, [result, marketType, taskId, isAuthenticated, locale]); // Auto-save when page unloads (closing tab, navigating away, etc.) useEffect(() => { diff --git a/frontend/app/analysis/results/page.tsx b/frontend/app/analysis/results/page.tsx index 07396223..67a64971 100644 --- a/frontend/app/analysis/results/page.tsx +++ b/frontend/app/analysis/results/page.tsx @@ -97,7 +97,8 @@ export default function AnalysisResultsPage() { marketType, analysisResult.analysis_date, analysisResult, - taskId || undefined + taskId || undefined, + locale as "en" | "zh-TW" // Pass current language for filtering ); // If authenticated, also save to cloud @@ -107,6 +108,7 @@ export default function AnalysisResultsPage() { market_type: marketType, analysis_date: analysisResult.analysis_date, result: analysisResult, + language: locale as "en" | "zh-TW", }); if (cloudId) { setSavedToCloud(true); diff --git a/frontend/app/history/page.tsx b/frontend/app/history/page.tsx index 2c7c2421..cdca3d7c 100644 --- a/frontend/app/history/page.tsx +++ b/frontend/app/history/page.tsx @@ -207,6 +207,41 @@ const extractDecisionFromReport = (report: SavedReport): { action: string; color return { action: "N/A", color: "text-gray-500" }; }; +/** + * Detect report language from content (for backward compatibility with old reports) + * Checks trader_investment_plan for Chinese/English keywords + */ +const detectReportLanguage = (reports: any): "en" | "zh-TW" => { + const traderPlan = reports?.trader_investment_plan; + if (!traderPlan || typeof traderPlan !== 'string') { + // If no trader plan, check other reports for Chinese characters + const allText = JSON.stringify(reports || {}); + const chineseRegex = /[\u4e00-\u9fa5]/; + return chineseRegex.test(allText) ? 'zh-TW' : 'en'; + } + + // Check for Chinese decision keywords + const chineseKeywords = ['θ²·ε…₯', 'θ³£ε‡Ί', 'ζŒζœ‰', 'ζœ€η΅‚δΊ€ζ˜“ζζ‘ˆ']; + for (const keyword of chineseKeywords) { + if (traderPlan.includes(keyword)) { + return 'zh-TW'; + } + } + + // Check for English decision keywords + const englishKeywords = ['buy', 'sell', 'hold', 'Final Trading Proposal']; + const lowerPlan = traderPlan.toLowerCase(); + for (const keyword of englishKeywords) { + if (lowerPlan.includes(keyword.toLowerCase())) { + return 'en'; + } + } + + // Fallback: check for Chinese characters in the content + const chineseRegex = /[\u4e00-\u9fa5]/; + return chineseRegex.test(traderPlan) ? 'zh-TW' : 'en'; +}; + export default function HistoryPage() { const router = useRouter(); const { setAnalysisResult, setTaskId, setMarketType } = useAnalysisContext(); @@ -232,10 +267,10 @@ export default function HistoryPage() { // Auto-sync tracking ref const hasAutoSyncedRef = useRef(false); - // Load reports when tab changes or auth state changes + // Load reports when tab changes, auth state changes, or language changes useEffect(() => { loadReports(); - }, [activeTab, isAuthenticated]); + }, [activeTab, isAuthenticated, locale]); // Load counts on mount or auth change useEffect(() => { @@ -332,6 +367,7 @@ export default function HistoryPage() { analysis_date: r.analysis_date, saved_at: new Date(r.created_at), result: r.result, + language: r.language, // Include language from cloud })) as (SavedReport & { cloudId?: string })[]; if (cloudFiltered.length > 0) { @@ -354,20 +390,43 @@ export default function HistoryPage() { new Date(b.saved_at).getTime() - new Date(a.saved_at).getTime() ); - setReports(merged); + // Filter by current language + const languageFiltered = merged.filter(report => { + // Use stored language if available + if (report.language) { + return report.language === locale; + } + // Fallback: detect from content for old reports without language field + return detectReportLanguage(report.result?.reports) === locale; + }); + + setReports(languageFiltered); setIsCloudData(true); return; } } // If no cloud data or not authenticated, use local only - setReports(localData); + // Filter by current language + const languageFiltered = localData.filter(report => { + if (report.language) { + return report.language === locale; + } + return detectReportLanguage(report.result?.reports) === locale; + }); + setReports(languageFiltered); setIsCloudData(false); } catch (error) { console.error("Failed to load reports:", error); // Fall back to local on error const data = await getReportsByMarketType(activeTab); - setReports(data); + const languageFiltered = data.filter(report => { + if (report.language) { + return report.language === locale; + } + return detectReportLanguage(report.result?.reports) === locale; + }); + setReports(languageFiltered); setIsCloudData(false); } finally { setLoading(false); diff --git a/frontend/components/PendingTaskRecovery.tsx b/frontend/components/PendingTaskRecovery.tsx index e4ee8e4c..b8ec59f6 100644 --- a/frontend/components/PendingTaskRecovery.tsx +++ b/frontend/components/PendingTaskRecovery.tsx @@ -13,12 +13,14 @@ import { getPendingTask, clearPendingTask, isPendingTaskValid, type PendingTask import { saveReport, checkDuplicateReport } from "@/lib/reports-db"; import { saveCloudReport, isCloudSyncEnabled } from "@/lib/user-api"; import { useAuth } from "@/contexts/auth-context"; +import { useLanguage } from "@/contexts/LanguageContext"; export function PendingTaskRecovery() { const [pendingTask, setPendingTask] = useState(null); const [status, setStatus] = useState<'checking' | 'found' | 'recovering' | 'success' | 'failed' | 'not_found'>('checking'); const [message, setMessage] = useState(""); const { isAuthenticated } = useAuth(); + const { locale } = useLanguage(); useEffect(() => { // Check for pending tasks on mount @@ -72,7 +74,8 @@ export function PendingTaskRecovery() { pendingTask.marketType, taskStatus.result.analysis_date, taskStatus.result, - pendingTask.taskId + pendingTask.taskId, + locale as "en" | "zh-TW" // Pass current language for filtering ); // If authenticated, also save to cloud @@ -82,6 +85,7 @@ export function PendingTaskRecovery() { market_type: pendingTask.marketType, analysis_date: taskStatus.result.analysis_date, result: taskStatus.result, + language: locale as "en" | "zh-TW", }); } @@ -121,7 +125,7 @@ export function PendingTaskRecovery() { setStatus('failed'); } } - }, [pendingTask, isAuthenticated]); + }, [pendingTask, isAuthenticated, locale]); const handleDismiss = () => { clearPendingTask(); diff --git a/frontend/contexts/auth-context.tsx b/frontend/contexts/auth-context.tsx index da16acaf..064753d0 100644 --- a/frontend/contexts/auth-context.tsx +++ b/frontend/contexts/auth-context.tsx @@ -219,7 +219,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { report.market_type, report.analysis_date, report.result, - (report as any).task_id + (report as any).task_id, + report.language // Preserve language from cloud ); } console.log(`Restored ${cloudReports.length} reports from cloud`); diff --git a/frontend/lib/reports-db.ts b/frontend/lib/reports-db.ts index 7e3cee67..89f9485c 100644 --- a/frontend/lib/reports-db.ts +++ b/frontend/lib/reports-db.ts @@ -15,6 +15,7 @@ export interface SavedReport { saved_at: Date; // Save timestamp task_id?: string; // Original task ID result: AnalysisResponse; // Full analysis result + language?: "en" | "zh-TW"; // Language of the report (for filtering) } // Database class extending Dexie @@ -23,10 +24,14 @@ class ReportsDatabase extends Dexie { constructor() { super("TradingAgentsReports"); + // Version 1: Original schema this.version(1).stores({ - // Define indexes: ++id = auto-increment, others are indexed fields reports: "++id, ticker, market_type, analysis_date, saved_at", }); + // Version 2: Added language field for filtering by UI language + this.version(2).stores({ + reports: "++id, ticker, market_type, analysis_date, saved_at, language", + }); } } @@ -41,7 +46,8 @@ export async function saveReport( market_type: "us" | "twse" | "tpex", analysis_date: string, result: AnalysisResponse, - task_id?: string + task_id?: string, + language?: "en" | "zh-TW", ): Promise { const report: SavedReport = { ticker, @@ -50,6 +56,7 @@ export async function saveReport( saved_at: new Date(), task_id, result, + language, }; return await db.reports.add(report); @@ -59,7 +66,7 @@ export async function saveReport( * Get all reports by market type */ export async function getReportsByMarketType( - market_type: "us" | "twse" | "tpex" + market_type: "us" | "twse" | "tpex", ): Promise { return await db.reports .where("market_type") @@ -79,7 +86,7 @@ export async function getAllReports(): Promise { * Get a single report by ID */ export async function getReportById( - id: number + id: number, ): Promise { return await db.reports.get(id); } @@ -120,7 +127,7 @@ export async function getReportCountByMarketType(): Promise<{ */ export async function checkDuplicateReport( ticker: string, - analysis_date: string + analysis_date: string, ): Promise { return await db.reports .where("ticker") diff --git a/frontend/lib/user-api.ts b/frontend/lib/user-api.ts index 2755b5c6..03f0b620 100644 --- a/frontend/lib/user-api.ts +++ b/frontend/lib/user-api.ts @@ -15,6 +15,7 @@ interface CloudReport { analysis_date: string; result: any; created_at: string; + language?: "en" | "zh-TW"; // Language of the report } /** @@ -102,6 +103,7 @@ export async function saveCloudReport(report: { market_type: "us" | "twse" | "tpex"; analysis_date: string; result: any; + language?: "en" | "zh-TW"; }): Promise { if (!isCloudSyncEnabled()) { console.warn("☁️ Cloud sync not enabled (no auth token)");