Add pro AI assistant to homepage
This commit is contained in:
@@ -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);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user