feat: added nearby_search tool to chat API and remove model switching
This commit is contained in:
parent
ba4f30a17f
commit
0c54998413
@ -21,9 +21,8 @@ A minimalistic AI-powered search engine that helps you find information on the i
|
|||||||
- [OpenWeather](https://openweathermap.org/)
|
- [OpenWeather](https://openweathermap.org/)
|
||||||
- [E2B](https://e2b.dev/)
|
- [E2B](https://e2b.dev/)
|
||||||
|
|
||||||
## LLMs used
|
## LLM used
|
||||||
- [OpenAI's GPT 4o mini](https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/)
|
- [OpenAI's GPT 4o mini](https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/)
|
||||||
- [Anthropic's Claude 3.5 Sonnet](https://www.anthropic.com/news/claude-3-5-sonnet)
|
|
||||||
|
|
||||||
### Deploy your own
|
### Deploy your own
|
||||||
|
|
||||||
|
|||||||
@ -9,19 +9,11 @@ import { geolocation } from "@vercel/functions";
|
|||||||
export const maxDuration = 60;
|
export const maxDuration = 60;
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const { messages, model } = await req.json();
|
const { messages } = await req.json();
|
||||||
const { latitude, longitude, city } = geolocation(req)
|
const { latitude, longitude, city } = geolocation(req)
|
||||||
|
|
||||||
let ansmodel;
|
|
||||||
|
|
||||||
if (model === "claude-3-5-sonnet-20240620") {
|
|
||||||
ansmodel = anthropic(model);
|
|
||||||
} else {
|
|
||||||
ansmodel = openai(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await streamText({
|
const result = await streamText({
|
||||||
model: ansmodel,
|
model: openai("gpt-4o-mini"),
|
||||||
messages: convertToCoreMessages(messages),
|
messages: convertToCoreMessages(messages),
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
maxTokens: 800,
|
maxTokens: 800,
|
||||||
@ -36,7 +28,7 @@ The user is located in ${city}(${latitude}, ${longitude}).
|
|||||||
|
|
||||||
Here are the tools available to you:
|
Here are the tools available to you:
|
||||||
<available_tools>
|
<available_tools>
|
||||||
web_search, retrieve, get_weather_data, programming
|
web_search, retrieve, get_weather_data, programming, nearby_search
|
||||||
</available_tools>
|
</available_tools>
|
||||||
|
|
||||||
Here is the general guideline per tool to follow when responding to user queries:
|
Here is the general guideline per tool to follow when responding to user queries:
|
||||||
@ -44,6 +36,8 @@ Here is the general guideline per tool to follow when responding to user queries
|
|||||||
- If you need to retrieve specific information from a webpage, use the retrieve tool. Then, compose your response based on the retrieved information.
|
- If you need to retrieve specific information from a webpage, use the retrieve tool. Then, compose your response based on the retrieved information.
|
||||||
- For weather-related queries, use the get_weather_data tool. Then, provide the weather information in your response.
|
- For weather-related queries, use the get_weather_data tool. Then, provide the weather information in your response.
|
||||||
- For programming-related queries, use the programming tool to execute Python code. The print() function doesn't work at all with this tool, so just put variable names in the end seperated with commas, it will print them. Then, compose your response based on the output of the code execution.
|
- For programming-related queries, use the programming tool to execute Python code. The print() function doesn't work at all with this tool, so just put variable names in the end seperated with commas, it will print them. Then, compose your response based on the output of the code execution.
|
||||||
|
- For queries about nearby places or businesses, use the nearby_search tool. Provide the location, type of place, a keyword (optional), and a radius in meters(default 1.5 Kilometers). Then, compose your response based on the search results.
|
||||||
|
- Do not use the retrieve tool for general web searches. It is only for retrieving specific information from a URL.- Do not use the retrieve tool for general web searches. It is only for retrieving specific information from a URL.
|
||||||
|
|
||||||
Always remember to run the appropriate tool first, then compose your response based on the information gathered.
|
Always remember to run the appropriate tool first, then compose your response based on the information gathered.
|
||||||
All tool should be called only once per response.
|
All tool should be called only once per response.
|
||||||
@ -239,6 +233,52 @@ Just run the tool and provide the answer.`,
|
|||||||
return "There was no output of the execution.";
|
return "There was no output of the execution.";
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
nearby_search: tool({
|
||||||
|
description: "Search for nearby places using Google Maps API.",
|
||||||
|
parameters: z.object({
|
||||||
|
location: z.string().describe("The location to search near (e.g., 'New York City' or '1600 Amphitheatre Parkway, Mountain View, CA')."),
|
||||||
|
type: z.string().describe("The type of place to search for (e.g., restaurant, cafe, park)."),
|
||||||
|
keyword: z.string().optional().describe("An optional keyword to refine the search."),
|
||||||
|
radius: z.number().default(3000).describe("The radius of the search area in meters (max 50000, default 3000)."),
|
||||||
|
}),
|
||||||
|
execute: async ({ location, type, keyword, radius }: { location: string; type: string; keyword?: string; radius: number }) => {
|
||||||
|
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
|
||||||
|
|
||||||
|
// First, use the Geocoding API to get the coordinates
|
||||||
|
const geocodeUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(location)}&key=${apiKey}`;
|
||||||
|
const geocodeResponse = await fetch(geocodeUrl);
|
||||||
|
const geocodeData = await geocodeResponse.json();
|
||||||
|
|
||||||
|
if (geocodeData.status !== "OK" || !geocodeData.results[0]) {
|
||||||
|
throw new Error("Failed to geocode the location");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { lat, lng } = geocodeData.results[0].geometry.location;
|
||||||
|
|
||||||
|
// perform the nearby search
|
||||||
|
let searchUrl = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&radius=${radius}&type=${type}&key=${apiKey}`;
|
||||||
|
|
||||||
|
if (keyword) {
|
||||||
|
searchUrl += `&keyword=${encodeURIComponent(keyword)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResponse = await fetch(searchUrl);
|
||||||
|
const searchData = await searchResponse.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: searchData.results.slice(0, 5).map((place: any) => ({
|
||||||
|
name: place.name,
|
||||||
|
vicinity: place.vicinity,
|
||||||
|
rating: place.rating,
|
||||||
|
user_ratings_total: place.user_ratings_total,
|
||||||
|
place_id: place.place_id,
|
||||||
|
location: place.geometry.location,
|
||||||
|
})),
|
||||||
|
center: { lat, lng },
|
||||||
|
formatted_address: geocodeData.results[0].formatted_address,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
toolChoice: "auto",
|
toolChoice: "auto",
|
||||||
});
|
});
|
||||||
|
|||||||
451
app/page.tsx
451
app/page.tsx
@ -23,8 +23,6 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|||||||
import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
import {
|
import {
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
ChevronDown,
|
|
||||||
FastForward,
|
|
||||||
Sparkles,
|
Sparkles,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Globe,
|
Globe,
|
||||||
@ -40,6 +38,8 @@ import {
|
|||||||
RefreshCw,
|
RefreshCw,
|
||||||
Heart,
|
Heart,
|
||||||
X,
|
X,
|
||||||
|
MapPin,
|
||||||
|
Star,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
HoverCard,
|
HoverCard,
|
||||||
@ -52,27 +52,12 @@ import {
|
|||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip"
|
} from "@/components/ui/tooltip"
|
||||||
|
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@ -92,9 +77,17 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
} from "@/components/ui/chart";
|
} from "@/components/ui/chart";
|
||||||
import { GitHubLogoIcon } from '@radix-ui/react-icons';
|
import { GitHubLogoIcon } from '@radix-ui/react-icons';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
|
||||||
export const maxDuration = 60;
|
export const maxDuration = 60;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
google: any;
|
||||||
|
initMap: () => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -103,18 +96,11 @@ export default function Home() {
|
|||||||
const [isAnimating, setIsAnimating] = useState(false);
|
const [isAnimating, setIsAnimating] = useState(false);
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([]);
|
const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([]);
|
||||||
const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false);
|
|
||||||
const [selectedModel, setSelectedModel] = useState('Speed');
|
|
||||||
const [showExamples, setShowExamples] = useState(false)
|
const [showExamples, setShowExamples] = useState(false)
|
||||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
|
||||||
const [newSelectedModel, setNewSelectedModel] = useState('');
|
|
||||||
const [isEditingQuery, setIsEditingQuery] = useState(false);
|
const [isEditingQuery, setIsEditingQuery] = useState(false);
|
||||||
|
|
||||||
const { isLoading, input, messages, setInput, append, reload, handleSubmit, setMessages } = useChat({
|
const { isLoading, input, messages, setInput, append, handleSubmit, setMessages } = useChat({
|
||||||
api: '/api/chat',
|
api: '/api/chat',
|
||||||
body: {
|
|
||||||
model: selectedModel === 'Speed' ? 'gpt-4o-mini' : 'claude-3-5-sonnet-20240620',
|
|
||||||
},
|
|
||||||
maxToolRoundtrips: 1,
|
maxToolRoundtrips: 1,
|
||||||
onFinish: async (message, { finishReason }) => {
|
onFinish: async (message, { finishReason }) => {
|
||||||
if (finishReason === 'stop') {
|
if (finishReason === 'stop') {
|
||||||
@ -163,101 +149,6 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const models = [
|
|
||||||
{ name: 'Speed', description: 'High speed, but lower quality.', details: '(OpenAI/GPT-4o-mini)', icon: FastForward },
|
|
||||||
{ name: 'Quality', description: 'High quality generation.', details: '(Anthropic/Claude-3.5-Sonnet)', icon: Sparkles },
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleModelChange = (value: string) => {
|
|
||||||
if (value !== selectedModel) {
|
|
||||||
if (hasSubmitted) {
|
|
||||||
setNewSelectedModel(value);
|
|
||||||
setShowConfirmModal(true);
|
|
||||||
} else {
|
|
||||||
setSelectedModel(value);
|
|
||||||
reload({
|
|
||||||
body: {
|
|
||||||
model: value === 'Speed' ? 'gpt-4o-mini' : 'claude-3-5-sonnet-20240620',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setIsModelSelectorOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirmModelChange = () => {
|
|
||||||
if (newSelectedModel !== selectedModel) {
|
|
||||||
setSelectedModel(newSelectedModel);
|
|
||||||
setShowConfirmModal(false);
|
|
||||||
setSuggestedQuestions([]);
|
|
||||||
reload({
|
|
||||||
body: {
|
|
||||||
model: newSelectedModel === 'Speed' ? 'gpt-4o-mini' : 'claude-3-5-sonnet-20240620',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setShowConfirmModal(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ModelSelectorProps {
|
|
||||||
selectedModel: string;
|
|
||||||
onModelSelect: (model: string) => void;
|
|
||||||
isDisabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ModelSelector({ selectedModel, onModelSelect, isDisabled }: ModelSelectorProps) {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleToggle = () => {
|
|
||||||
if (!isDisabled) {
|
|
||||||
setIsOpen(!isOpen);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className={`flex items-center p-0 px-2 rounded-full ${selectedModel.includes('Quality') ? 'bg-purple-500 hover:bg-purple-400 !disabled:bg-purple-600 disabled:!opacity-85' : 'bg-green-500 hover:bg-green-400 disabled:!bg-green-600 disabled:!opacity-85'
|
|
||||||
} text-white hover:text-white`}
|
|
||||||
disabled={isDisabled}
|
|
||||||
onClick={handleToggle}
|
|
||||||
>
|
|
||||||
{selectedModel === 'Speed' && <FastForward className="w-5 h-5 sm:mr-2" />}
|
|
||||||
{selectedModel.includes('Quality') && <Sparkles className="w-5 h-5 sm:mr-2" />}
|
|
||||||
<span className="hidden sm:inline">{selectedModel}</span>
|
|
||||||
<ChevronDown className={`w-5 h-5 ml-2 transform transition-transform ${isOpen ? 'rotate-180' : ''}`} />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-64 p-1 mr-2 sm:mr-0">
|
|
||||||
{models.map((model) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={model.name}
|
|
||||||
onSelect={() => onModelSelect(model.name)}
|
|
||||||
className={`flex items-start p-3 !font-sans rounded-md ${selectedModel === model.name ? 'bg-muted' : ''}`}
|
|
||||||
>
|
|
||||||
<model.icon className={`w-5 h-5 mr-1 mt-0.5 flex-shrink-0 ${model.name.includes('Quality') ? 'text-purple-500' : 'text-green-500'}`} />
|
|
||||||
<div className="flex-grow">
|
|
||||||
<div className="font-semibold flex items-center justify-between">
|
|
||||||
{model.name}
|
|
||||||
{selectedModel === model.name && (
|
|
||||||
<span className="text-xs text-white px-2 py-0.5 rounded-full bg-black">
|
|
||||||
Active
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500 mt-0.5">{model.description}</div>
|
|
||||||
<div className="text-xs text-gray-400 mt-0.5">{model.details}</div>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WeatherDataPoint {
|
interface WeatherDataPoint {
|
||||||
date: string;
|
date: string;
|
||||||
minTemp: number;
|
minTemp: number;
|
||||||
@ -366,10 +257,189 @@ export default function Home() {
|
|||||||
|
|
||||||
WeatherChart.displayName = 'WeatherChart';
|
WeatherChart.displayName = 'WeatherChart';
|
||||||
|
|
||||||
|
const isValidCoordinate = (coord: number) => {
|
||||||
|
return typeof coord === 'number' && !isNaN(coord) && isFinite(coord);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadGoogleMapsScript = (callback: () => void) => {
|
||||||
|
if (window.google && window.google.maps) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingScript = document.getElementById('googleMapsScript');
|
||||||
|
if (existingScript) {
|
||||||
|
existingScript.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.initMap = callback;
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.id = 'googleMapsScript';
|
||||||
|
script.src = `https://maps.googleapis.com/maps/api/js?key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}&libraries=places&callback=initMap`;
|
||||||
|
script.async = true;
|
||||||
|
script.defer = true;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MapComponent = React.memo(({ center, places }: { center: { lat: number; lng: number }, places: any[] }) => {
|
||||||
|
const mapRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [mapError, setMapError] = useState<string | null>(null);
|
||||||
|
const googleMapRef = useRef<google.maps.Map | null>(null);
|
||||||
|
const markersRef = useRef<google.maps.Marker[]>([]);
|
||||||
|
|
||||||
|
const memoizedCenter = useMemo(() => center, [center]);
|
||||||
|
const memoizedPlaces = useMemo(() => places, [places]);
|
||||||
|
|
||||||
|
const initializeMap = useCallback(() => {
|
||||||
|
if (mapRef.current && isValidCoordinate(memoizedCenter.lat) && isValidCoordinate(memoizedCenter.lng)) {
|
||||||
|
if (!googleMapRef.current) {
|
||||||
|
googleMapRef.current = new window.google.maps.Map(mapRef.current, {
|
||||||
|
center: memoizedCenter,
|
||||||
|
zoom: 14,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
googleMapRef.current.setCenter(memoizedCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing markers
|
||||||
|
markersRef.current.forEach(marker => marker.setMap(null));
|
||||||
|
markersRef.current = [];
|
||||||
|
|
||||||
|
memoizedPlaces.forEach((place) => {
|
||||||
|
if (isValidCoordinate(place.location.lat) && isValidCoordinate(place.location.lng)) {
|
||||||
|
const MarkerClass = window.google.maps.marker?.AdvancedMarkerElement || window.google.maps.Marker;
|
||||||
|
const marker = new MarkerClass({
|
||||||
|
position: place.location,
|
||||||
|
map: googleMapRef.current,
|
||||||
|
title: place.name,
|
||||||
|
});
|
||||||
|
markersRef.current.push(marker);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setMapError('Invalid coordinates provided');
|
||||||
|
}
|
||||||
|
}, [memoizedCenter, memoizedPlaces]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadGoogleMapsScript(() => {
|
||||||
|
try {
|
||||||
|
initializeMap();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing map:', error);
|
||||||
|
setMapError('Failed to initialize Google Maps');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Clean up markers when component unmounts
|
||||||
|
markersRef.current.forEach(marker => marker.setMap(null));
|
||||||
|
};
|
||||||
|
}, [initializeMap]);
|
||||||
|
|
||||||
|
if (mapError) {
|
||||||
|
return <div className="h-64 flex items-center justify-center bg-gray-100">{mapError}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div ref={mapRef} className="w-full h-64" />;
|
||||||
|
});
|
||||||
|
|
||||||
|
MapComponent.displayName = 'MapComponent';
|
||||||
|
|
||||||
|
const MapSkeleton = () => (
|
||||||
|
<Skeleton className="w-full h-64" />
|
||||||
|
);
|
||||||
|
|
||||||
|
const PlaceDetails = ({ place }: { place: any }) => (
|
||||||
|
<div className="flex justify-between items-start py-2">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold">{place.name}</h4>
|
||||||
|
<p className="text-sm text-muted-foreground max-w-[200px]" title={place.vicinity}>
|
||||||
|
{place.vicinity}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{place.rating && (
|
||||||
|
<Badge variant="secondary" className="flex items-center">
|
||||||
|
<Star className="h-3 w-3 mr-1 text-yellow-400" />
|
||||||
|
{place.rating} ({place.user_ratings_total})
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const renderToolInvocation = (toolInvocation: ToolInvocation, index: number) => {
|
const renderToolInvocation = (toolInvocation: ToolInvocation, index: number) => {
|
||||||
const args = JSON.parse(JSON.stringify(toolInvocation.args));
|
const args = JSON.parse(JSON.stringify(toolInvocation.args));
|
||||||
const result = 'result' in toolInvocation ? JSON.parse(JSON.stringify(toolInvocation.result)) : null;
|
const result = 'result' in toolInvocation ? JSON.parse(JSON.stringify(toolInvocation.result)) : null;
|
||||||
|
|
||||||
|
if (toolInvocation.toolName === 'nearby_search') {
|
||||||
|
if (!result) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<MapPin className="h-5 w-5 text-neutral-700 animate-pulse" />
|
||||||
|
<span className="text-neutral-700 text-lg">Searching nearby places...</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-1">
|
||||||
|
{[0, 1, 2].map((index) => (
|
||||||
|
<motion.div
|
||||||
|
key={index}
|
||||||
|
className="w-2 h-2 bg-muted-foreground rounded-full"
|
||||||
|
initial={{ opacity: 0.3 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration: 0.8,
|
||||||
|
delay: index * 0.2,
|
||||||
|
repeatType: "reverse",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Card className="w-full my-4 overflow-hidden">
|
||||||
|
<CardHeader>
|
||||||
|
<Skeleton className="h-6 w-3/4" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0 rounded-t-none rounded-b-xl">
|
||||||
|
<MapSkeleton />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="w-full my-4 overflow-hidden">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<MapPin className="h-5 w-5 text-primary" />
|
||||||
|
<span>Nearby {args.type ? args.type.charAt(0).toUpperCase() + args.type.slice(1) + 's' : 'Places'}</span>
|
||||||
|
{args.keyword && <Badge variant="secondary">{args.keyword}</Badge>}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<MapComponent center={result.center} places={result.results} />
|
||||||
|
<Accordion type="single" collapsible className="w-full">
|
||||||
|
<AccordionItem value="place-details">
|
||||||
|
<AccordionTrigger className="px-4">Place Details</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="px-4 space-y-4 max-h-64 overflow-y-auto">
|
||||||
|
{result.results.map((place: any, placeIndex: number) => (
|
||||||
|
<PlaceDetails key={placeIndex} place={place} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (toolInvocation.toolName === 'get_weather_data') {
|
if (toolInvocation.toolName === 'get_weather_data') {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return (
|
return (
|
||||||
@ -505,6 +575,74 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toolInvocation.toolName === 'nearby_search') {
|
||||||
|
if (!result) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<MapPin className="h-5 w-5 text-neutral-700 animate-pulse" />
|
||||||
|
<span className="text-neutral-700 text-lg">Searching nearby places...</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-1">
|
||||||
|
{[0, 1, 2].map((index) => (
|
||||||
|
<motion.div
|
||||||
|
key={index}
|
||||||
|
className="w-2 h-2 bg-muted-foreground rounded-full"
|
||||||
|
initial={{ opacity: 0.3 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration: 0.8,
|
||||||
|
delay: index * 0.2,
|
||||||
|
repeatType: "reverse",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapUrl = `https://www.google.com/maps/embed/v1/search?key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}&q=${encodeURIComponent(args.type)}¢er=${result.results[0].geometry.location.lat},${result.results[0].geometry.location.lng}&zoom=14`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="w-full my-4 overflow-hidden">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<MapPin className="h-5 w-5 text-primary" />
|
||||||
|
<span>Nearby {args.type.charAt(0).toUpperCase() + args.type.slice(1)}s</span>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<div className="aspect-video w-full">
|
||||||
|
<iframe
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{ border: 0 }}
|
||||||
|
loading="lazy"
|
||||||
|
allowFullScreen
|
||||||
|
referrerPolicy="no-referrer-when-downgrade"
|
||||||
|
src={mapUrl}
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 space-y-2">
|
||||||
|
{result.results.map((place: any, placeIndex: number) => (
|
||||||
|
<div key={placeIndex} className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold">{place.name}</h4>
|
||||||
|
<p className="text-sm text-muted-foreground">{place.vicinity}</p>
|
||||||
|
</div>
|
||||||
|
<Badge variant="secondary" className="flex items-center">
|
||||||
|
{place.rating} ★ ({place.user_ratings_total})
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{!result ? (
|
{!result ? (
|
||||||
@ -600,8 +738,7 @@ export default function Home() {
|
|||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CitationComponent: React.FC<CitationComponentProps> = React.memo(({ href, children, index }) => {
|
const CitationComponent: React.FC<CitationComponentProps> = React.memo(({ href, index }) => {
|
||||||
const citationText = Array.isArray(children) ? children[0] : children;
|
|
||||||
const faviconUrl = `https://www.google.com/s2/favicons?sz=128&domain=${new URL(href).hostname}`;
|
const faviconUrl = `https://www.google.com/s2/favicons?sz=128&domain=${new URL(href).hostname}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -789,17 +926,17 @@ export default function Home() {
|
|||||||
<div className="flex flex-col font-sans items-center min-h-screen p-2 sm:p-4 bg-background text-foreground transition-all duration-500">
|
<div className="flex flex-col font-sans items-center min-h-screen p-2 sm:p-4 bg-background text-foreground transition-all duration-500">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
<div className={`w-full max-w-[90%] sm:max-w-2xl space-y-4 sm:space-y-6 p-1 ${hasSubmitted ? 'mt-16 sm:mt-20' : 'mt-[15vh] sm:mt-[20vh]'}`}>
|
<div className={`w-full max-w-[90%] sm:max-w-2xl space-y-6 p-1 ${hasSubmitted ? 'mt-16 sm:mt-20' : 'mt-[16vh] sm:mt-[25vh]'}`}>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={hasSubmitted ? { scale: 1.2 } : { scale: 1 }}
|
animate={hasSubmitted ? { scale: 1.2 } : { scale: 1 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
className="text-center"
|
className="text-center"
|
||||||
>
|
>
|
||||||
<h1 className="text-3xl sm:text-4xl mb-4 sm:mb-8 text-primary font-serif">MiniPerplx</h1>
|
<h1 className="text-3xl sm:text-4xl mb-1 text-primary font-serif">MiniPerplx</h1>
|
||||||
{!hasSubmitted &&
|
{!hasSubmitted &&
|
||||||
<h2 className='text-xl sm:text-2xl font-serif text-balance text-center mb-2'>
|
<h2 className='text-xl sm:text-2xl font-serif text-balance text-center mb-6'>
|
||||||
A minimalistic AI-powered search engine that helps you find information on the internet.
|
In search for minimalism and simplicity
|
||||||
</h2>
|
</h2>
|
||||||
}
|
}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -811,48 +948,6 @@ export default function Home() {
|
|||||||
exit={{ opacity: 0, y: 20 }}
|
exit={{ opacity: 0, y: 20 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
<div className="relative px-2 mb-4">
|
|
||||||
<button
|
|
||||||
onClick={() => setIsModelSelectorOpen(!isModelSelectorOpen)}
|
|
||||||
className={`flex items-center font-semibold ${models.find((model) => model.name === selectedModel)?.name.includes('Quality') ? 'text-purple-500' : 'text-green-500'} focus:outline-none focus:ring-0 `}
|
|
||||||
>
|
|
||||||
{selectedModel === 'Speed' && <FastForward className="w-5 h-5 mr-2" />}
|
|
||||||
{(selectedModel === 'Quality') && <Sparkles className="w-5 h-5 mr-2" />}
|
|
||||||
{selectedModel}
|
|
||||||
<ChevronDown className={`w-5 h-5 ml-2 transform transition-transform ${isModelSelectorOpen ? 'rotate-180' : ''}`} />
|
|
||||||
</button>
|
|
||||||
{isModelSelectorOpen && (
|
|
||||||
<div className="absolute top-full left-0 mt-2 w-fit bg-white border border-gray-200 rounded-md shadow-lg z-10">
|
|
||||||
{models.map((model) => (
|
|
||||||
<button
|
|
||||||
key={model.name}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedModel(model.name);
|
|
||||||
setIsModelSelectorOpen(false);
|
|
||||||
}}
|
|
||||||
className={`w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center ${models.indexOf(model) === 0 ? 'rounded-t-md' : models.indexOf(model) === models.length - 1 ? 'rounded-b-md' : ''}`}
|
|
||||||
>
|
|
||||||
<model.icon className={`w-5 h-5 mr-3 ${model.name.includes('Quality') ? 'text-purple-500' : 'text-green-500'}`} />
|
|
||||||
<div>
|
|
||||||
<div className="font-semibold flex items-center">
|
|
||||||
{model.name}
|
|
||||||
{selectedModel === model.name && (
|
|
||||||
<span
|
|
||||||
className={`ml-2 text-xs text-white px-2 py-0.5 rounded-full ${model.name.includes('Quality') ? 'bg-purple-500' : 'bg-green-500'}`}
|
|
||||||
>
|
|
||||||
Selected
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500">{model.description}</div>
|
|
||||||
<div className="text-xs text-gray-400">{model.details}</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleFormSubmit} className="flex items-center space-x-2 px-2 mb-4 sm:mb-6">
|
<form onSubmit={handleFormSubmit} className="flex items-center space-x-2 px-2 mb-4 sm:mb-6">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<Input
|
<Input
|
||||||
@ -977,12 +1072,6 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
<Edit2 size={16} />
|
<Edit2 size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ModelSelector
|
|
||||||
selectedModel={selectedModel}
|
|
||||||
onModelSelect={handleModelChange}
|
|
||||||
isDisabled={isLoading || isEditingQuery}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -1078,20 +1167,6 @@ export default function Home() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<Dialog open={showConfirmModal} onOpenChange={setShowConfirmModal}>
|
|
||||||
<DialogContent className='!font-sans'>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Confirm Model Change</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to change the model? This will change the quality of the responses and cannot be undone.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={() => setShowConfirmModal(false)}>Cancel</Button>
|
|
||||||
<Button onClick={handleConfirmModelChange}>Confirm</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -21,6 +21,7 @@
|
|||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
"@tailwindcss/typography": "^0.5.13",
|
||||||
|
"@types/googlemaps": "^3.43.3",
|
||||||
"@vercel/analytics": "^1.3.1",
|
"@vercel/analytics": "^1.3.1",
|
||||||
"@vercel/functions": "^1.4.0",
|
"@vercel/functions": "^1.4.0",
|
||||||
"ai": "latest",
|
"ai": "latest",
|
||||||
|
|||||||
@ -41,6 +41,9 @@ dependencies:
|
|||||||
'@tailwindcss/typography':
|
'@tailwindcss/typography':
|
||||||
specifier: ^0.5.13
|
specifier: ^0.5.13
|
||||||
version: 0.5.13(tailwindcss@3.4.7)
|
version: 0.5.13(tailwindcss@3.4.7)
|
||||||
|
'@types/googlemaps':
|
||||||
|
specifier: ^3.43.3
|
||||||
|
version: 3.43.3
|
||||||
'@vercel/analytics':
|
'@vercel/analytics':
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
version: 1.3.1(next@14.2.5)(react@18.3.1)
|
version: 1.3.1(next@14.2.5)(react@18.3.1)
|
||||||
@ -49,7 +52,7 @@ dependencies:
|
|||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
ai:
|
ai:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 3.3.7(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
|
version: 3.3.9(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.0
|
version: 0.7.0
|
||||||
@ -198,8 +201,8 @@ packages:
|
|||||||
json-schema: 0.4.0
|
json-schema: 0.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/react@0.0.43(react@18.3.1)(zod@3.23.8):
|
/@ai-sdk/react@0.0.45(react@18.3.1)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-maWuV9529tIVVST9iXgnxBWUoM5Z8DW0lyrMYnsaLJAZ4kostt+PbqJjhy6eAQPzmXGcu4+OdgT1v1ZNCZR4+Q==}
|
resolution: {integrity: sha512-rPsjHtJ+qsWoRV88xzEpvPXhqRBF2wljGjWrsFcao4p48hKwZkuhW/zzpxZj3A4ZPy6jpDHRTYtqMuPHVpc9Eg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18 || ^19
|
react: ^18 || ^19
|
||||||
@ -210,15 +213,15 @@ packages:
|
|||||||
zod:
|
zod:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 1.0.11(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.13(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.31(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.33(zod@3.23.8)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
swr: 2.2.5(react@18.3.1)
|
swr: 2.2.5(react@18.3.1)
|
||||||
zod: 3.23.8
|
zod: 3.23.8
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/solid@0.0.34(zod@3.23.8):
|
/@ai-sdk/solid@0.0.36(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-puVv9rrskWXrtaikDbpoMkGeTboa4ZY6wTmC66Xw9rhZ0zK5yN15lLJBf/LeBIV6J1V9F9bBxjRX7UQXjE3sZg==}
|
resolution: {integrity: sha512-pm57goMQczSpPTNrUrwbab5BybZYofBRZ10UkTi2KgJP5i+S/sGHSh/xtgZz+xNpUt42pk8aYvOiNDN1ppjkDA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
solid-js: ^1.7.7
|
solid-js: ^1.7.7
|
||||||
@ -226,14 +229,14 @@ packages:
|
|||||||
solid-js:
|
solid-js:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 1.0.11(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.13(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.31(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.33(zod@3.23.8)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- zod
|
- zod
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/svelte@0.0.36(svelte@4.2.18)(zod@3.23.8):
|
/@ai-sdk/svelte@0.0.38(svelte@4.2.18)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-5pSaKt+UZK9+9AsbIYLs4REtAc/0HOLX4DK3nRtMcDqDLoWDoSJDKK/EjDMYVhYB1gqQmT0AeiSLo2WH0nf00w==}
|
resolution: {integrity: sha512-sTNkxzhS1B0TDdVWZR6yXG+3qQGYAxMwcEKzMVjm1VdpGlZits1PxF39aVvPldaWM8QB4MrVE+H5b5dTA43D0Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: ^3.0.0 || ^4.0.0
|
svelte: ^3.0.0 || ^4.0.0
|
||||||
@ -241,16 +244,16 @@ packages:
|
|||||||
svelte:
|
svelte:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 1.0.11(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.13(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.31(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.33(zod@3.23.8)
|
||||||
sswr: 2.1.0(svelte@4.2.18)
|
sswr: 2.1.0(svelte@4.2.18)
|
||||||
svelte: 4.2.18
|
svelte: 4.2.18
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- zod
|
- zod
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/ui-utils@0.0.31(zod@3.23.8):
|
/@ai-sdk/ui-utils@0.0.33(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-PA1mI+WC69Bc8JCTDOXwhLv9OAfocex/d+MRtQjfuWE6jTBjkBMa6davw+JjN7Vcp6zP0JLQG6gd7n6MnkFepQ==}
|
resolution: {integrity: sha512-2oZjZzZG3AGQihO1d3mWqgFuywTtjBtkUEeE7d8nicw3QQv9m1MwrbQqRhhKbbBetBke6V9o5FQ5wngmb/+3iw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
zod: ^3.0.0
|
zod: ^3.0.0
|
||||||
@ -258,16 +261,16 @@ packages:
|
|||||||
zod:
|
zod:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider': 0.0.19
|
'@ai-sdk/provider': 0.0.20
|
||||||
'@ai-sdk/provider-utils': 1.0.11(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.13(zod@3.23.8)
|
||||||
json-schema: 0.4.0
|
json-schema: 0.4.0
|
||||||
secure-json-parse: 2.7.0
|
secure-json-parse: 2.7.0
|
||||||
zod: 3.23.8
|
zod: 3.23.8
|
||||||
zod-to-json-schema: 3.22.5(zod@3.23.8)
|
zod-to-json-schema: 3.22.5(zod@3.23.8)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/vue@0.0.35(vue@3.4.35)(zod@3.23.8):
|
/@ai-sdk/vue@0.0.37(vue@3.4.35)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-7cPShsxiyxzoSB5orjCqwnFWvjpM/YX2W+a2K6lyV2Z2JAgHc+4PHhVnrKwc0c9Q7vwfpvW+3MoKM6U2xZaS+w==}
|
resolution: {integrity: sha512-m7dMi6qRoWPuru9TyWUm5jXAPGSDb1SHZp/Q+uYjhNY1dZwgsZxJvSeakogzR37uhiRCg3Kg8fCypQJe+dikPA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.3.4
|
vue: ^3.3.4
|
||||||
@ -275,8 +278,8 @@ packages:
|
|||||||
vue:
|
vue:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 1.0.11(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.13(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.31(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.33(zod@3.23.8)
|
||||||
swrv: 1.0.4(vue@3.4.35)
|
swrv: 1.0.4(vue@3.4.35)
|
||||||
vue: 3.4.35(typescript@5.5.4)
|
vue: 3.4.35(typescript@5.5.4)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -1321,6 +1324,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/googlemaps@3.43.3:
|
||||||
|
resolution: {integrity: sha512-ZWNoz/O8MPEpiajvj7QiqCY8tTLFNqNZ/a+s+zTV58wFVNAvvqV4bdGfnsjTb5Cs4V6wEsLrX8XRhmnyYJ2Tdg==}
|
||||||
|
deprecated: 'Types for the Google Maps browser API have moved to @types/google.maps. Note: these types are not for the googlemaps npm package, which is a Node API.'
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/hast@2.3.10:
|
/@types/hast@2.3.10:
|
||||||
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
|
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1561,8 +1569,8 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
/ai@3.3.7(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8):
|
/ai@3.3.9(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-xMfQdOL2s0aiGozdUO0ahOAfcwkGBUye3q4wC64PPNpmE3Qeing1Tv4JSsHk0zymhCMHBDiI1Tky8BNGdu+V6A==}
|
resolution: {integrity: sha512-PS6xHzYIH+iVLG3xd/jwsqZW7+X8GrSrsJwVB4ACMbxhZN8KRs4yUcnGEDPcvRkEL3PqHHJFUo9iVysdZwOUwg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
openai: ^4.42.0
|
openai: ^4.42.0
|
||||||
@ -1582,13 +1590,13 @@ packages:
|
|||||||
zod:
|
zod:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider': 0.0.19
|
'@ai-sdk/provider': 0.0.20
|
||||||
'@ai-sdk/provider-utils': 1.0.11(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.13(zod@3.23.8)
|
||||||
'@ai-sdk/react': 0.0.43(react@18.3.1)(zod@3.23.8)
|
'@ai-sdk/react': 0.0.45(react@18.3.1)(zod@3.23.8)
|
||||||
'@ai-sdk/solid': 0.0.34(zod@3.23.8)
|
'@ai-sdk/solid': 0.0.36(zod@3.23.8)
|
||||||
'@ai-sdk/svelte': 0.0.36(svelte@4.2.18)(zod@3.23.8)
|
'@ai-sdk/svelte': 0.0.38(svelte@4.2.18)(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.31(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.33(zod@3.23.8)
|
||||||
'@ai-sdk/vue': 0.0.35(vue@3.4.35)(zod@3.23.8)
|
'@ai-sdk/vue': 0.0.37(vue@3.4.35)(zod@3.23.8)
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
eventsource-parser: 1.1.2
|
eventsource-parser: 1.1.2
|
||||||
json-schema: 0.4.0
|
json-schema: 0.4.0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user