This commit is contained in:
MarkLo127 2026-01-27 13:22:52 +08:00
parent 26ff5ce220
commit d58e80a962
7 changed files with 94 additions and 17 deletions

View File

@ -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(() => {

View File

@ -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);

View File

@ -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);

View File

@ -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<PendingTask | null>(null);
const [status, setStatus] = useState<'checking' | 'found' | 'recovering' | 'success' | 'failed' | 'not_found'>('checking');
const [message, setMessage] = useState<string>("");
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();

View File

@ -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`);

View File

@ -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<number> {
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<SavedReport[]> {
return await db.reports
.where("market_type")
@ -79,7 +86,7 @@ export async function getAllReports(): Promise<SavedReport[]> {
* Get a single report by ID
*/
export async function getReportById(
id: number
id: number,
): Promise<SavedReport | undefined> {
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<SavedReport | undefined> {
return await db.reports
.where("ticker")

View File

@ -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<string | null> {
if (!isCloudSyncEnabled()) {
console.warn("☁️ Cloud sync not enabled (no auth token)");