typescript fixin
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 13s
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 13s
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { AnalysisType, FlyerItem, Store } from '../types';
|
||||
import type { GroundingChunk } from '@google/genai';
|
||||
import { getQuickInsights, getDeepDiveAnalysis, searchWeb, planTripWithMaps, generateImageFromText } from '../services/geminiService';
|
||||
import { LoadingSpinner } from './LoadingSpinner';
|
||||
import { LightbulbIcon } from './icons/LightbulbIcon';
|
||||
@@ -14,6 +15,11 @@ interface AnalysisPanelProps {
|
||||
store?: Store;
|
||||
}
|
||||
|
||||
interface Source {
|
||||
uri: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface TabButtonProps {
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
@@ -38,7 +44,7 @@ const TabButton: React.FC<TabButtonProps> = ({ label, icon, isActive, onClick })
|
||||
export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({ flyerItems, store }) => {
|
||||
const [activeTab, setActiveTab] = useState<AnalysisType>(AnalysisType.QUICK_INSIGHTS);
|
||||
const [results, setResults] = useState<{ [key in AnalysisType]?: string }>({});
|
||||
const [sources, setSources] = useState<any[]>([]);
|
||||
const [sources, setSources] = useState<Source[]>([]);
|
||||
const [loadingStates, setLoadingStates] = useState<{ [key in AnalysisType]?: boolean }>({});
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -49,31 +55,47 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({ flyerItems, store
|
||||
const handleAnalysis = useCallback(async (type: AnalysisType) => {
|
||||
setLoadingStates(prev => ({ ...prev, [type]: true }));
|
||||
setError(null);
|
||||
setGeneratedImageUrl(null); // Clear generated image on new analysis
|
||||
// Clear sources if the new tab doesn't generate them
|
||||
if (type !== AnalysisType.WEB_SEARCH && type !== AnalysisType.PLAN_TRIP) {
|
||||
setSources([]);
|
||||
}
|
||||
try {
|
||||
let responseText = '';
|
||||
let newSources: Source[] = [];
|
||||
if (type === AnalysisType.QUICK_INSIGHTS) {
|
||||
responseText = await getQuickInsights(flyerItems);
|
||||
} else if (type === AnalysisType.DEEP_DIVE) {
|
||||
responseText = await getDeepDiveAnalysis(flyerItems);
|
||||
} else if (type === AnalysisType.WEB_SEARCH) {
|
||||
const { text, sources } = await searchWeb(flyerItems);
|
||||
const mappedSources: Source[] = sources.map((s: GroundingChunk) => ({
|
||||
uri: s.web?.uri || '',
|
||||
title: s.web?.title || 'Untitled Source'
|
||||
}));
|
||||
responseText = text;
|
||||
setSources(sources);
|
||||
newSources = mappedSources;
|
||||
} else if (type === AnalysisType.PLAN_TRIP) {
|
||||
const userLocation = await new Promise<GeolocationCoordinates>((resolve, reject) => {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => resolve(position.coords),
|
||||
(error) => reject(error)
|
||||
(err: GeolocationPositionError) => reject(err) // Type the error for better handling
|
||||
);
|
||||
});
|
||||
const { text, sources } = await planTripWithMaps(flyerItems, store, userLocation);
|
||||
responseText = text;
|
||||
setSources(sources);
|
||||
newSources = sources;
|
||||
}
|
||||
setResults(prev => ({ ...prev, [type]: responseText }));
|
||||
} catch (e: any) {
|
||||
setSources(newSources); // Update sources once after all logic
|
||||
} catch (e) { // Type 'e' is implicitly 'unknown'
|
||||
console.error(`Analysis failed for type ${type}:`, e);
|
||||
const userFriendlyMessage = e.code === 1 ? "Please allow location access to use this feature." : `Failed to get ${type.replace('_', ' ')}. Please try again.`;
|
||||
let userFriendlyMessage = `Failed to get ${type.replace('_', ' ')}. Please try again.`;
|
||||
if (e instanceof GeolocationPositionError && e.code === GeolocationPositionError.PERMISSION_DENIED) {
|
||||
userFriendlyMessage = "Please allow location access to use this feature.";
|
||||
} else if (e instanceof Error) {
|
||||
userFriendlyMessage = e.message; // Use specific error message if it's a standard Error
|
||||
}
|
||||
setError(userFriendlyMessage);
|
||||
} finally {
|
||||
setLoadingStates(prev => ({ ...prev, [type]: false }));
|
||||
@@ -88,8 +110,9 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({ flyerItems, store
|
||||
try {
|
||||
const base64Image = await generateImageFromText(mealPlanText);
|
||||
setGeneratedImageUrl(`data:image/png;base64,${base64Image}`);
|
||||
} catch (e: any) {
|
||||
setError(`Failed to generate image: ${e.message}`);
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'An unknown error occurred.';
|
||||
setError(`Failed to generate image: ${errorMessage}`);
|
||||
} finally {
|
||||
setIsGeneratingImage(false);
|
||||
}
|
||||
@@ -104,20 +127,18 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({ flyerItems, store
|
||||
if (resultText) {
|
||||
const isSearchType = activeTab === AnalysisType.WEB_SEARCH || activeTab === AnalysisType.PLAN_TRIP;
|
||||
return (
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap font-sans">
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap">
|
||||
{resultText}
|
||||
{isSearchType && sources.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h4 className="font-semibold">Sources:</h4>
|
||||
<ul className="list-disc pl-5">
|
||||
{sources.map((source, index) => {
|
||||
const uri = source.web?.uri || source.maps?.uri;
|
||||
const title = source.web?.title || source.maps?.title || 'Map Link';
|
||||
if (!uri) return null;
|
||||
{sources.map((source) => {
|
||||
if (!source.uri) return null;
|
||||
return (
|
||||
<li key={index}>
|
||||
<a href={uri} target="_blank" rel="noopener noreferrer" className="text-brand-primary hover:underline">
|
||||
{title}
|
||||
<li key={source.uri}>
|
||||
<a href={source.uri} target="_blank" rel="noopener noreferrer" className="text-brand-primary hover:underline">
|
||||
{source.title}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ export const BulkImporter: React.FC<BulkImporterProps> = ({ onProcess, isProcess
|
||||
onDragOver={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
className={`flex flex-col items-center justify-center w-full h-48 border-2 ${borderColor} border-dashed rounded-lg transition-colors duration-300 ${isProcessing ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700'}`}
|
||||
className={`flex flex-col items-center justify-center w-full h-48 border-2 ${borderColor} ${bgColor} border-dashed rounded-lg transition-colors duration-300 ${isProcessing ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700'}`}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center pt-5 pb-6 text-center">
|
||||
<UploadIcon className="w-10 h-10 mb-3 text-gray-400" />
|
||||
|
||||
@@ -120,7 +120,7 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
setPasswordMessage("Password updated successfully!");
|
||||
setPassword('');
|
||||
setConfirmPassword('');
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred.';
|
||||
logger.error('Failed to update user password.', { userId: session.user.id, error: errorMessage });
|
||||
setPasswordError(errorMessage);
|
||||
|
||||
Reference in New Issue
Block a user