Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 1x 1x 1x 1x 4x 4x 4x 3x 3x 2x 4x 3x 3x 1x 2x 2x 1x 1x 2x 2x 2x 3x 3x 1x 2x 1x 1x | // src/services/aiAnalysisService.ts
import { Flyer, FlyerItem, MasterGroceryItem, GroundedResponse, Source } from '../types';
import * as aiApiClient from './aiApiClient';
import { logger } from './logger.client';
interface RawSource {
web?: {
uri?: string;
title?: string;
};
}
/**
* A service class to encapsulate all AI analysis API calls and related business logic.
* This decouples the React components and hooks from the data fetching implementation.
*/
export class AiAnalysisService {
/**
* Fetches quick insights for a given set of flyer items.
* @param items - The flyer items to analyze.
* @returns The string result from the API.
*/
async getQuickInsights(items: FlyerItem[]): Promise<string> {
logger.info('[AiAnalysisService] getQuickInsights called.');
return aiApiClient.getQuickInsights(items).then((res) => res.json());
}
/**
* Fetches a deep dive analysis for a given set of flyer items.
* @param items - The flyer items to analyze.
* @returns The string result from the API.
*/
async getDeepDiveAnalysis(items: FlyerItem[]): Promise<string> {
logger.info('[AiAnalysisService] getDeepDiveAnalysis called.');
return aiApiClient.getDeepDiveAnalysis(items).then((res) => res.json());
}
/**
* Performs a web search based on the flyer items.
* @param items - The flyer items to use as context for the search.
* @returns A grounded response with text and sources.
*/
async searchWeb(items: FlyerItem[]): Promise<GroundedResponse> {
logger.info('[AiAnalysisService] searchWeb called.');
// Construct a query string from the item names.
const query = items.map((item) => item.item).join(', ');
// The API client returns a specific shape that we need to await the JSON from
const response: { text: string; sources: RawSource[] } = await aiApiClient
.searchWeb(query)
.then((res) => res.json());
// Normalize sources to a consistent format.
const mappedSources = (response.sources || []).map(
(s: RawSource) =>
(s.web ? { uri: s.web.uri || '', title: s.web.title || 'Untitled' } : { uri: '', title: 'Untitled' }) as Source,
);
return { ...response, sources: mappedSources };
}
/**
* Plans a shopping trip using maps and the user's location.
* @param items - The flyer items for the trip.
* @param store - The store associated with the flyer.
* @returns A grounded response with the trip plan and sources.
*/
async planTripWithMaps(items: FlyerItem[], store: Flyer['store']): Promise<GroundedResponse> {
logger.info('[AiAnalysisService] planTripWithMaps called.');
// Encapsulate geolocation logic within the service.
const userLocation = await this.getCurrentLocation();
return aiApiClient.planTripWithMaps(items, store, userLocation).then((res) => res.json());
}
/**
* Compares prices for a user's watched items.
* @param watchedItems - The list of master grocery items to compare.
* @returns A grounded response with the price comparison.
*/
async compareWatchedItemPrices(watchedItems: MasterGroceryItem[]): Promise<GroundedResponse> {
logger.info('[AiAnalysisService] compareWatchedItemPrices called.');
const response: { text: string; sources: RawSource[] } = await aiApiClient
.compareWatchedItemPrices(watchedItems)
.then((res) => res.json());
// Normalize sources to a consistent format.
const mappedSources = (response.sources || []).map(
(s: RawSource) =>
(s.web ? { uri: s.web.uri || '', title: s.web.title || 'Untitled' } : { uri: '', title: 'Untitled' }) as Source,
);
return { ...response, sources: mappedSources };
}
/**
* Generates an image based on a provided text prompt (e.g., a meal plan).
* @param prompt - The text to use for image generation.
* @returns A base64 encoded string of the generated image.
*/
async generateImageFromText(prompt: string): Promise<string> {
logger.info('[AiAnalysisService] generateImageFromText called.');
return aiApiClient.generateImageFromText(prompt).then((res) => res.json());
}
/**
* A helper to promisify the Geolocation API.
* @returns A promise that resolves with the user's coordinates.
* @private
*/
private getCurrentLocation(): Promise<GeolocationCoordinates> {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
return reject(new Error('Geolocation is not supported by your browser.'));
}
navigator.geolocation.getCurrentPosition(
(pos) => resolve(pos.coords),
(err) => reject(err),
);
});
}
}
|