This commit is contained in:
parent
26ff5ce220
commit
d58e80a962
|
|
@ -21,7 +21,7 @@ export default function AnalysisPage() {
|
||||||
const { setAnalysisResult, setTaskId, setMarketType, marketType } = useAnalysisContext();
|
const { setAnalysisResult, setTaskId, setMarketType, marketType } = useAnalysisContext();
|
||||||
const { runAnalysis, loading, error, result, taskId } = useAnalysis();
|
const { runAnalysis, loading, error, result, taskId } = useAnalysis();
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
const { t } = useLanguage();
|
const { t, locale } = useLanguage();
|
||||||
|
|
||||||
// Ref to track if we've already saved (to prevent duplicate saves)
|
// Ref to track if we've already saved (to prevent duplicate saves)
|
||||||
const hasSavedRef = useRef(false);
|
const hasSavedRef = useRef(false);
|
||||||
|
|
@ -47,7 +47,8 @@ export default function AnalysisPage() {
|
||||||
marketType,
|
marketType,
|
||||||
result.analysis_date,
|
result.analysis_date,
|
||||||
result,
|
result,
|
||||||
taskId || undefined
|
taskId || undefined,
|
||||||
|
locale as "en" | "zh-TW" // Pass current language for filtering
|
||||||
);
|
);
|
||||||
console.log("📁 Auto-saved report to local storage");
|
console.log("📁 Auto-saved report to local storage");
|
||||||
|
|
||||||
|
|
@ -58,6 +59,7 @@ export default function AnalysisPage() {
|
||||||
market_type: marketType,
|
market_type: marketType,
|
||||||
analysis_date: result.analysis_date,
|
analysis_date: result.analysis_date,
|
||||||
result: result,
|
result: result,
|
||||||
|
language: locale as "en" | "zh-TW",
|
||||||
});
|
});
|
||||||
if (cloudId) {
|
if (cloudId) {
|
||||||
console.log("☁️ Auto-saved report to cloud");
|
console.log("☁️ Auto-saved report to cloud");
|
||||||
|
|
@ -68,7 +70,7 @@ export default function AnalysisPage() {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Auto-save failed:", 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.)
|
// Auto-save when page unloads (closing tab, navigating away, etc.)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,8 @@ export default function AnalysisResultsPage() {
|
||||||
marketType,
|
marketType,
|
||||||
analysisResult.analysis_date,
|
analysisResult.analysis_date,
|
||||||
analysisResult,
|
analysisResult,
|
||||||
taskId || undefined
|
taskId || undefined,
|
||||||
|
locale as "en" | "zh-TW" // Pass current language for filtering
|
||||||
);
|
);
|
||||||
|
|
||||||
// If authenticated, also save to cloud
|
// If authenticated, also save to cloud
|
||||||
|
|
@ -107,6 +108,7 @@ export default function AnalysisResultsPage() {
|
||||||
market_type: marketType,
|
market_type: marketType,
|
||||||
analysis_date: analysisResult.analysis_date,
|
analysis_date: analysisResult.analysis_date,
|
||||||
result: analysisResult,
|
result: analysisResult,
|
||||||
|
language: locale as "en" | "zh-TW",
|
||||||
});
|
});
|
||||||
if (cloudId) {
|
if (cloudId) {
|
||||||
setSavedToCloud(true);
|
setSavedToCloud(true);
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,41 @@ const extractDecisionFromReport = (report: SavedReport): { action: string; color
|
||||||
return { action: "N/A", color: "text-gray-500" };
|
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() {
|
export default function HistoryPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { setAnalysisResult, setTaskId, setMarketType } = useAnalysisContext();
|
const { setAnalysisResult, setTaskId, setMarketType } = useAnalysisContext();
|
||||||
|
|
@ -232,10 +267,10 @@ export default function HistoryPage() {
|
||||||
// Auto-sync tracking ref
|
// Auto-sync tracking ref
|
||||||
const hasAutoSyncedRef = useRef(false);
|
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(() => {
|
useEffect(() => {
|
||||||
loadReports();
|
loadReports();
|
||||||
}, [activeTab, isAuthenticated]);
|
}, [activeTab, isAuthenticated, locale]);
|
||||||
|
|
||||||
// Load counts on mount or auth change
|
// Load counts on mount or auth change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -332,6 +367,7 @@ export default function HistoryPage() {
|
||||||
analysis_date: r.analysis_date,
|
analysis_date: r.analysis_date,
|
||||||
saved_at: new Date(r.created_at),
|
saved_at: new Date(r.created_at),
|
||||||
result: r.result,
|
result: r.result,
|
||||||
|
language: r.language, // Include language from cloud
|
||||||
})) as (SavedReport & { cloudId?: string })[];
|
})) as (SavedReport & { cloudId?: string })[];
|
||||||
|
|
||||||
if (cloudFiltered.length > 0) {
|
if (cloudFiltered.length > 0) {
|
||||||
|
|
@ -354,20 +390,43 @@ export default function HistoryPage() {
|
||||||
new Date(b.saved_at).getTime() - new Date(a.saved_at).getTime()
|
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);
|
setIsCloudData(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no cloud data or not authenticated, use local only
|
// 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);
|
setIsCloudData(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load reports:", error);
|
console.error("Failed to load reports:", error);
|
||||||
// Fall back to local on error
|
// Fall back to local on error
|
||||||
const data = await getReportsByMarketType(activeTab);
|
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);
|
setIsCloudData(false);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@ import { getPendingTask, clearPendingTask, isPendingTaskValid, type PendingTask
|
||||||
import { saveReport, checkDuplicateReport } from "@/lib/reports-db";
|
import { saveReport, checkDuplicateReport } from "@/lib/reports-db";
|
||||||
import { saveCloudReport, isCloudSyncEnabled } from "@/lib/user-api";
|
import { saveCloudReport, isCloudSyncEnabled } from "@/lib/user-api";
|
||||||
import { useAuth } from "@/contexts/auth-context";
|
import { useAuth } from "@/contexts/auth-context";
|
||||||
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
|
|
||||||
export function PendingTaskRecovery() {
|
export function PendingTaskRecovery() {
|
||||||
const [pendingTask, setPendingTask] = useState<PendingTask | null>(null);
|
const [pendingTask, setPendingTask] = useState<PendingTask | null>(null);
|
||||||
const [status, setStatus] = useState<'checking' | 'found' | 'recovering' | 'success' | 'failed' | 'not_found'>('checking');
|
const [status, setStatus] = useState<'checking' | 'found' | 'recovering' | 'success' | 'failed' | 'not_found'>('checking');
|
||||||
const [message, setMessage] = useState<string>("");
|
const [message, setMessage] = useState<string>("");
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
|
const { locale } = useLanguage();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check for pending tasks on mount
|
// Check for pending tasks on mount
|
||||||
|
|
@ -72,7 +74,8 @@ export function PendingTaskRecovery() {
|
||||||
pendingTask.marketType,
|
pendingTask.marketType,
|
||||||
taskStatus.result.analysis_date,
|
taskStatus.result.analysis_date,
|
||||||
taskStatus.result,
|
taskStatus.result,
|
||||||
pendingTask.taskId
|
pendingTask.taskId,
|
||||||
|
locale as "en" | "zh-TW" // Pass current language for filtering
|
||||||
);
|
);
|
||||||
|
|
||||||
// If authenticated, also save to cloud
|
// If authenticated, also save to cloud
|
||||||
|
|
@ -82,6 +85,7 @@ export function PendingTaskRecovery() {
|
||||||
market_type: pendingTask.marketType,
|
market_type: pendingTask.marketType,
|
||||||
analysis_date: taskStatus.result.analysis_date,
|
analysis_date: taskStatus.result.analysis_date,
|
||||||
result: taskStatus.result,
|
result: taskStatus.result,
|
||||||
|
language: locale as "en" | "zh-TW",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +125,7 @@ export function PendingTaskRecovery() {
|
||||||
setStatus('failed');
|
setStatus('failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [pendingTask, isAuthenticated]);
|
}, [pendingTask, isAuthenticated, locale]);
|
||||||
|
|
||||||
const handleDismiss = () => {
|
const handleDismiss = () => {
|
||||||
clearPendingTask();
|
clearPendingTask();
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
report.market_type,
|
report.market_type,
|
||||||
report.analysis_date,
|
report.analysis_date,
|
||||||
report.result,
|
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`);
|
console.log(`Restored ${cloudReports.length} reports from cloud`);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export interface SavedReport {
|
||||||
saved_at: Date; // Save timestamp
|
saved_at: Date; // Save timestamp
|
||||||
task_id?: string; // Original task ID
|
task_id?: string; // Original task ID
|
||||||
result: AnalysisResponse; // Full analysis result
|
result: AnalysisResponse; // Full analysis result
|
||||||
|
language?: "en" | "zh-TW"; // Language of the report (for filtering)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database class extending Dexie
|
// Database class extending Dexie
|
||||||
|
|
@ -23,10 +24,14 @@ class ReportsDatabase extends Dexie {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super("TradingAgentsReports");
|
super("TradingAgentsReports");
|
||||||
|
// Version 1: Original schema
|
||||||
this.version(1).stores({
|
this.version(1).stores({
|
||||||
// Define indexes: ++id = auto-increment, others are indexed fields
|
|
||||||
reports: "++id, ticker, market_type, analysis_date, saved_at",
|
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",
|
market_type: "us" | "twse" | "tpex",
|
||||||
analysis_date: string,
|
analysis_date: string,
|
||||||
result: AnalysisResponse,
|
result: AnalysisResponse,
|
||||||
task_id?: string
|
task_id?: string,
|
||||||
|
language?: "en" | "zh-TW",
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const report: SavedReport = {
|
const report: SavedReport = {
|
||||||
ticker,
|
ticker,
|
||||||
|
|
@ -50,6 +56,7 @@ export async function saveReport(
|
||||||
saved_at: new Date(),
|
saved_at: new Date(),
|
||||||
task_id,
|
task_id,
|
||||||
result,
|
result,
|
||||||
|
language,
|
||||||
};
|
};
|
||||||
|
|
||||||
return await db.reports.add(report);
|
return await db.reports.add(report);
|
||||||
|
|
@ -59,7 +66,7 @@ export async function saveReport(
|
||||||
* Get all reports by market type
|
* Get all reports by market type
|
||||||
*/
|
*/
|
||||||
export async function getReportsByMarketType(
|
export async function getReportsByMarketType(
|
||||||
market_type: "us" | "twse" | "tpex"
|
market_type: "us" | "twse" | "tpex",
|
||||||
): Promise<SavedReport[]> {
|
): Promise<SavedReport[]> {
|
||||||
return await db.reports
|
return await db.reports
|
||||||
.where("market_type")
|
.where("market_type")
|
||||||
|
|
@ -79,7 +86,7 @@ export async function getAllReports(): Promise<SavedReport[]> {
|
||||||
* Get a single report by ID
|
* Get a single report by ID
|
||||||
*/
|
*/
|
||||||
export async function getReportById(
|
export async function getReportById(
|
||||||
id: number
|
id: number,
|
||||||
): Promise<SavedReport | undefined> {
|
): Promise<SavedReport | undefined> {
|
||||||
return await db.reports.get(id);
|
return await db.reports.get(id);
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +127,7 @@ export async function getReportCountByMarketType(): Promise<{
|
||||||
*/
|
*/
|
||||||
export async function checkDuplicateReport(
|
export async function checkDuplicateReport(
|
||||||
ticker: string,
|
ticker: string,
|
||||||
analysis_date: string
|
analysis_date: string,
|
||||||
): Promise<SavedReport | undefined> {
|
): Promise<SavedReport | undefined> {
|
||||||
return await db.reports
|
return await db.reports
|
||||||
.where("ticker")
|
.where("ticker")
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ interface CloudReport {
|
||||||
analysis_date: string;
|
analysis_date: string;
|
||||||
result: any;
|
result: any;
|
||||||
created_at: string;
|
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";
|
market_type: "us" | "twse" | "tpex";
|
||||||
analysis_date: string;
|
analysis_date: string;
|
||||||
result: any;
|
result: any;
|
||||||
|
language?: "en" | "zh-TW";
|
||||||
}): Promise<string | null> {
|
}): Promise<string | null> {
|
||||||
if (!isCloudSyncEnabled()) {
|
if (!isCloudSyncEnabled()) {
|
||||||
console.warn("☁️ Cloud sync not enabled (no auth token)");
|
console.warn("☁️ Cloud sync not enabled (no auth token)");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue