Add pro AI assistant to homepage

This commit is contained in:
2569718930@qq.com
2026-04-23 08:44:10 +08:00
parent ecba404e88
commit b65bdd010d
6 changed files with 1459 additions and 22 deletions
+103
View File
@@ -8,6 +8,55 @@ import {
MarketScan,
} from "@/lib/dashboard-types";
export type AssistantOpportunityContext = {
city_name: string;
city_display_name: string;
airport?: string | null;
risk_level: string;
tradable: boolean;
local_time?: string | null;
current_temperature?: number | null;
deb_prediction?: number | null;
market_question?: string | null;
market_label?: string | null;
selected_date?: string | null;
best_side?: string | null;
yes_price?: number | null;
no_price?: number | null;
edge_percent?: number | null;
market_probability?: number | null;
model_probability?: number | null;
status?: string | null;
};
export type AssistantContextPayload = {
snapshot_id: string;
locale: string;
generated_at: string;
totals: {
cities: number;
tradable_markets: number;
high_risk: number;
medium_risk: number;
low_risk: number;
};
selected_city?: AssistantOpportunityContext | null;
opportunities: AssistantOpportunityContext[];
glossary: Array<{
term: string;
meaning: string;
}>;
};
export type AssistantChatResponse = {
answer: string;
cached?: boolean;
model?: string;
refused?: boolean;
snapshot_id?: string;
suggestions?: string[];
};
const CACHE_KEY = "polyWeather_v1";
const CACHE_TTL_MS = 5 * 60 * 1000;
const pendingCityDetailRequests = new Map<string, Promise<CityDetail>>();
@@ -21,6 +70,7 @@ const pendingCityMarketScanRequests = new Map<
selected_date?: string | null;
}>
>();
const pendingAssistantRequests = new Map<string, Promise<AssistantChatResponse>>();
const PRIORITY_WARM_SESSION_KEY = "polyWeather_priority_warm_v1";
type CityCacheMeta = {
@@ -331,6 +381,59 @@ export const dashboardClient = {
return request;
},
async askAssistant(payload: {
question: string;
locale: string;
snapshotId: string;
context: AssistantContextPayload;
}) {
const question = String(payload.question || "").trim();
const snapshotId = String(payload.snapshotId || "").trim();
if (!question) {
throw new Error("Question is required");
}
if (!snapshotId) {
throw new Error("Snapshot id is required");
}
const requestKey = [
snapshotId,
payload.locale,
question.toLowerCase(),
].join("::");
const existing = pendingAssistantRequests.get(requestKey);
if (existing) {
return existing;
}
const request = fetch("/api/assistant/chat", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
cache: "no-store",
body: JSON.stringify({
question,
locale: payload.locale,
snapshot_id: snapshotId,
context: payload.context,
}),
})
.then(async (response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json() as Promise<AssistantChatResponse>;
})
.finally(() => {
pendingAssistantRequests.delete(requestKey);
});
pendingAssistantRequests.set(requestKey, request);
return request;
},
isCityDetailFresh(meta?: CityCacheMeta | null) {
return isFresh(meta);
},