This commit is contained in:
parent
26ff5ce220
commit
d58e80a962
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)");
|
||||
|
|
|
|||
Loading…
Reference in New Issue