improve AI
This commit is contained in:
parent
daf830c5ed
commit
b3a7ab40c8
7 changed files with 118 additions and 17 deletions
|
|
@ -1,15 +1,27 @@
|
|||
import { memo, useState, useCallback } from 'react';
|
||||
import { SpinnerIcon } from '../ui/icons/SpinnerIcon';
|
||||
import { SparklesIcon } from '../ui/icons/SparklesIcon';
|
||||
import type { AiFilterErrorType } from '../../hooks/useAiFilters';
|
||||
|
||||
interface AiFilterInputProps {
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
errorType: AiFilterErrorType | null;
|
||||
notes: string | null;
|
||||
onSubmit: (query: string) => void;
|
||||
isLoggedIn: boolean;
|
||||
onLoginRequired: () => void;
|
||||
}
|
||||
|
||||
export default memo(function AiFilterInput({ loading, error, notes, onSubmit }: AiFilterInputProps) {
|
||||
export default memo(function AiFilterInput({
|
||||
loading,
|
||||
error,
|
||||
errorType,
|
||||
notes,
|
||||
onSubmit,
|
||||
isLoggedIn,
|
||||
onLoginRequired,
|
||||
}: AiFilterInputProps) {
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
|
|
@ -17,9 +29,13 @@ export default memo(function AiFilterInput({ loading, error, notes, onSubmit }:
|
|||
e.preventDefault();
|
||||
const trimmed = query.trim();
|
||||
if (!trimmed || loading) return;
|
||||
if (!isLoggedIn) {
|
||||
onLoginRequired();
|
||||
return;
|
||||
}
|
||||
onSubmit(trimmed);
|
||||
},
|
||||
[query, loading, onSubmit]
|
||||
[query, loading, isLoggedIn, onLoginRequired, onSubmit]
|
||||
);
|
||||
|
||||
const hasContent = query.trim().length > 0;
|
||||
|
|
@ -52,7 +68,17 @@ export default memo(function AiFilterInput({ loading, error, notes, onSubmit }:
|
|||
</button>
|
||||
)}
|
||||
</form>
|
||||
{error && (
|
||||
{error && errorType === 'verification' && (
|
||||
<p className="mt-1.5 text-xs text-amber-600 dark:text-amber-400">
|
||||
Please verify your email address to use AI-powered search. Check your inbox for a verification link.
|
||||
</p>
|
||||
)}
|
||||
{error && errorType === 'limit' && (
|
||||
<p className="mt-1.5 text-xs text-amber-600 dark:text-amber-400">
|
||||
You've reached the weekly AI usage limit. It will reset automatically next week.
|
||||
</p>
|
||||
)}
|
||||
{error && errorType === 'error' && (
|
||||
<p className="mt-1 text-xs text-red-600 dark:text-red-400 truncate" title={error}>
|
||||
{error}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { FeatureInfoPopup } from '../ui/FeatureInfoPopup';
|
|||
import { FeatureActions } from '../ui/FeatureIcons';
|
||||
import { FeatureLabel } from '../ui/FeatureLabel';
|
||||
import AiFilterInput from './AiFilterInput';
|
||||
import type { AiFilterErrorType } from '../../hooks/useAiFilters';
|
||||
import FeatureBrowser from './FeatureBrowser';
|
||||
import { TravelTimeCard } from './TravelTimeCard';
|
||||
import {
|
||||
|
|
@ -89,8 +90,11 @@ interface FiltersProps {
|
|||
onTravelTimeToggleBest: (index: number) => void;
|
||||
aiFilterLoading: boolean;
|
||||
aiFilterError: string | null;
|
||||
aiFilterErrorType: AiFilterErrorType | null;
|
||||
aiFilterNotes: string | null;
|
||||
onAiFilterSubmit: (query: string) => void;
|
||||
isLoggedIn: boolean;
|
||||
onLoginRequired: () => void;
|
||||
isLicensed: boolean;
|
||||
onUpgradeClick?: () => void;
|
||||
onResetTutorial?: () => void;
|
||||
|
|
@ -121,8 +125,11 @@ export default memo(function Filters({
|
|||
onTravelTimeToggleBest,
|
||||
aiFilterLoading,
|
||||
aiFilterError,
|
||||
aiFilterErrorType,
|
||||
aiFilterNotes,
|
||||
onAiFilterSubmit,
|
||||
isLoggedIn,
|
||||
onLoginRequired,
|
||||
isLicensed,
|
||||
onUpgradeClick,
|
||||
onResetTutorial,
|
||||
|
|
@ -278,7 +285,7 @@ export default memo(function Filters({
|
|||
</div>
|
||||
|
||||
<div ref={scrollRef} className="md:flex-1 md:overflow-y-auto">
|
||||
<AiFilterInput loading={aiFilterLoading} error={aiFilterError} notes={aiFilterNotes} onSubmit={onAiFilterSubmit} />
|
||||
<AiFilterInput loading={aiFilterLoading} error={aiFilterError} errorType={aiFilterErrorType} notes={aiFilterNotes} onSubmit={onAiFilterSubmit} isLoggedIn={isLoggedIn} onLoginRequired={onLoginRequired} />
|
||||
<div className="px-3 pb-2 space-y-2">
|
||||
<div className="flex rounded-lg bg-warm-100 dark:bg-warm-800 p-0.5">
|
||||
{(['historical', 'buy', 'rent'] as const).map((type) => {
|
||||
|
|
|
|||
|
|
@ -143,16 +143,29 @@ export default function MapPage({
|
|||
});
|
||||
|
||||
const aiFilters = useAiFilters();
|
||||
|
||||
const travelTime = useTravelTime(initialTravelTime);
|
||||
|
||||
const handleAiFilterSubmit = useCallback(
|
||||
async (query: string) => {
|
||||
const result = await aiFilters.fetchAiFilters(query);
|
||||
if (result) handleSetFilters(result.filters);
|
||||
if (!result) return;
|
||||
handleSetFilters(result.filters);
|
||||
// Apply travel time filters from AI
|
||||
if (result.travelTimeFilters.length > 0) {
|
||||
const newEntries = result.travelTimeFilters.map((tt) => ({
|
||||
mode: tt.mode,
|
||||
slug: tt.slug,
|
||||
label: tt.label,
|
||||
timeRange: [tt.min ?? 0, tt.max ?? 120] as [number, number],
|
||||
useBest: false,
|
||||
}));
|
||||
travelTime.handleSetEntries(newEntries);
|
||||
}
|
||||
},
|
||||
[aiFilters.fetchAiFilters, handleSetFilters]
|
||||
[aiFilters.fetchAiFilters, handleSetFilters, travelTime.handleSetEntries]
|
||||
);
|
||||
|
||||
const travelTime = useTravelTime(initialTravelTime);
|
||||
|
||||
const handleTravelTimeSetDestination = useCallback(
|
||||
(index: number, slug: string, label: string) => {
|
||||
travelTime.handleSetDestination(index, slug, label);
|
||||
|
|
@ -499,8 +512,11 @@ export default function MapPage({
|
|||
onTravelTimeToggleBest={travelTime.handleToggleBest}
|
||||
aiFilterLoading={aiFilters.loading}
|
||||
aiFilterError={aiFilters.error}
|
||||
aiFilterErrorType={aiFilters.errorType}
|
||||
aiFilterNotes={aiFilters.notes}
|
||||
onAiFilterSubmit={handleAiFilterSubmit}
|
||||
isLoggedIn={!!user}
|
||||
onLoginRequired={onRegisterClick ?? (() => {})}
|
||||
isLicensed={user?.subscription === 'licensed'}
|
||||
onUpgradeClick={() => onNavigateTo('pricing')}
|
||||
onResetTutorial={tutorial.resetTutorial}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue