feat: improved programming tool UI, complete google maps integrations with tools.

This commit is contained in:
zaidmukaddam 2024-08-20 10:36:18 +05:30
parent a158d5d1c5
commit ba1b77462e
6 changed files with 454 additions and 123 deletions

View File

@ -26,6 +26,8 @@ The questions should be open-ended and should encourage further discussion while
Always put the user input's context is some way so that the next search knows what to search for exactly.
Try to stick to the context of the conversation and avoid asking questions that are too general or too specific.
For weather based converations sent to you, always generate questions that are about news, sports, or other topics that are not related to the weather.
For programming based conversations, always generate questions that are about the algorithms, data structures, or other topics that are related to it or an improvement of the question.
For location based conversations, always generate questions that are about the culture, history, or other topics that are related to the location.
Never use pronouns in the questions as they blur the context.`,
messages: history,
schema: z.object({

View File

@ -27,7 +27,7 @@ The user is located in ${city}(${latitude}, ${longitude}).
Here are the tools available to you:
<available_tools>
web_search, retrieve, get_weather_data, programming, nearby_search
web_search, retrieve, get_weather_data, programming, nearby_search, find_place, text_search
</available_tools>
Here is the general guideline per tool to follow when responding to user queries:
@ -36,7 +36,12 @@ Here is the general guideline per tool to follow when responding to user queries
- For weather-related queries, use the get_weather_data tool. The weather results are 5 days weather forecast data with 3-hour step. 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 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.
- For queries about finding a specific place, use the find_place tool. Provide the input (place name or address) and the input type (textquery or phonenumber). Then, compose your response based on the search results.
- For text-based searches of places, use the text_search tool. Provide the query, location (optional), and radius (optional). 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.
- Show plots from the programming tool using plt.show() function. The tool will automatically capture the plot and display it in the response.
- If asked for multiple plots, make it happen in one run of the tool. The tool will automatically capture the plots and display them in the response.
- The location search tools return images in the response, please do not include them in the response at all costs.
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.
@ -226,8 +231,10 @@ Just run the tool and provide the answer.`,
execute: async ({ code }: { code: string }) => {
const sandbox = await CodeInterpreter.create();
const execution = await sandbox.notebook.execCell(code);
let message = "";
let images = [];
if (execution.results.length > 0) {
let message: string = "";
for (const result of execution.results) {
if (result.isMainResult) {
message += `${result.text}\n`;
@ -235,32 +242,31 @@ Just run the tool and provide the answer.`,
message += `${result.text}\n`;
}
if (result.formats().length > 0) {
message += `It has following formats: ${result.formats()}\n`;
const formats = result.formats();
for (let format of formats) {
if (format === "png") {
images.push({ format: "png", data: result.png });
} else if (format === "jpeg") {
images.push({ format: "jpeg", data: result.jpeg });
} else if (format === "svg") {
images.push({ format: "svg", data: result.svg });
}
}
}
}
sandbox.close();
return message;
}
if (
execution.logs.stdout.length > 0 ||
execution.logs.stderr.length > 0
) {
let message = "";
if (execution.logs.stdout.length > 0 || execution.logs.stderr.length > 0) {
if (execution.logs.stdout.length > 0) {
message += `${execution.logs.stdout.join("\n")}\n`;
}
if (execution.logs.stderr.length > 0) {
message += `${execution.logs.stderr.join("\n")}\n`;
}
sandbox.close();
return message;
}
sandbox.close();
return "There was no output of the execution.";
return { message: message.trim(), images };
},
}),
nearby_search: tool({
@ -309,6 +315,46 @@ Just run the tool and provide the answer.`,
};
},
}),
find_place: tool({
description: "Find a specific place using Google Maps API.",
parameters: z.object({
input: z.string().describe("The place to search for (e.g., 'Museum of Contemporary Art Australia')."),
inputtype: z.enum(["textquery", "phonenumber"]).describe("The type of input (textquery or phonenumber)."),
}),
execute: async ({ input, inputtype }: { input: string; inputtype: "textquery" | "phonenumber" }) => {
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
const url = `https://maps.googleapis.com/maps/api/place/findplacefromtext/json?fields=formatted_address,name,rating,opening_hours,geometry&input=${encodeURIComponent(input)}&inputtype=${inputtype}&key=${apiKey}`;
const response = await fetch(url);
const data = await response.json();
return data;
},
}),
text_search: tool({
description: "Perform a text-based search for places using Google Maps API.",
parameters: z.object({
query: z.string().describe("The search query (e.g., '123 main street')."),
location: z.string().optional().describe("The location to center the search (e.g., '42.3675294,-71.186966')."),
radius: z.number().optional().describe("The radius of the search area in meters (max 50000)."),
}),
execute: async ({ query, location, radius }: { query: string; location?: string; radius?: number }) => {
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
let url = `https://maps.googleapis.com/maps/api/place/textsearch/json?query=${encodeURIComponent(query)}&key=${apiKey}`;
if (location) {
url += `&location=${encodeURIComponent(location)}`;
}
if (radius) {
url += `&radius=${radius}`;
}
const response = await fetch(url);
const data = await response.json();
return data;
},
}),
},
toolChoice: "auto",
});

View File

@ -8,7 +8,8 @@ React,
useCallback,
useState,
useEffect,
useMemo
useMemo,
memo
} from 'react';
import ReactMarkdown, { Components } from 'react-markdown';
import { useRouter } from 'next/navigation';
@ -35,17 +36,22 @@ import {
Loader2,
User2,
Edit2,
RefreshCw,
Heart,
X,
MapPin,
Star,
Plus,
Terminal,
ImageIcon,
Download,
} from 'lucide-react';
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Accordion,
AccordionContent,
@ -78,6 +84,8 @@ import {
} from "@/components/ui/chart";
import { GitHubLogoIcon } from '@radix-ui/react-icons';
import { Skeleton } from '@/components/ui/skeleton';
import Link from 'next/link';
import { cn } from '@/lib/utils';
export const maxDuration = 60;
@ -149,6 +157,8 @@ export default function Home() {
);
};
// Weather chart components
interface WeatherDataPoint {
date: string;
minTemp: number;
@ -257,6 +267,9 @@ export default function Home() {
WeatherChart.displayName = 'WeatherChart';
// Google Maps components
const isValidCoordinate = (coord: number) => {
return typeof coord === 'number' && !isNaN(coord) && isFinite(coord);
};
@ -346,7 +359,7 @@ export default function Home() {
return <div ref={mapRef} className="w-full h-64" />;
});
MapComponent.displayName = 'MapComponent';
const MapSkeleton = () => (
@ -370,6 +383,111 @@ export default function Home() {
</div>
);
const MapEmbed = memo(({ location, zoom = 15 }: { location: string, zoom?: number }) => {
const apiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;
const mapUrl = `https://www.google.com/maps/embed/v1/place?key=${apiKey}&q=${encodeURIComponent(location)}&zoom=${zoom}`;
return (
<div className="aspect-video w-full">
<iframe
width="100%"
height="100%"
style={{ border: 0 }}
loading="lazy"
allowFullScreen
referrerPolicy="no-referrer-when-downgrade"
src={mapUrl}
className='rounded-xl'
></iframe>
</div>
);
});
MapEmbed.displayName = 'MapEmbed';
const FindPlaceResult = memo(({ result }: { result: any }) => {
const place = result.candidates[0];
const location = `${place.geometry.location.lat},${place.geometry.location.lng}`;
return (
<Card className="w-full my-4 overflow-hidden shadow-none">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MapPin className="h-5 w-5 text-primary" />
<span>{place.name}</span>
</CardTitle>
</CardHeader>
<CardContent>
<MapEmbed location={location} />
<div className="mt-4 space-y-2">
<p><strong>Address:</strong> {place.formatted_address}</p>
{place.rating && (
<div className="flex items-center">
<strong className="mr-2">Rating:</strong>
<Badge variant="secondary" className="flex items-center">
<Star className="h-3 w-3 mr-1 text-yellow-400" />
{place.rating}
</Badge>
</div>
)}
{place.opening_hours && (
<p><strong>Open now:</strong> {place.opening_hours.open_now ? 'Yes' : 'No'}</p>
)}
</div>
</CardContent>
</Card>
);
});
FindPlaceResult.displayName = 'FindPlaceResult';
const TextSearchResult = memo(({ result }: { result: any }) => {
const centerLocation = result.results[0]?.geometry?.location;
const mapLocation = centerLocation ? `${centerLocation.lat},${centerLocation.lng}` : '';
return (
<Card className="w-full my-4 overflow-hidden shadow-none">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MapPin className="h-5 w-5 text-primary" />
<span>Text Search Results</span>
</CardTitle>
</CardHeader>
<CardContent>
{mapLocation && <MapEmbed location={mapLocation} zoom={13} />}
<Accordion type="single" collapsible className="w-full mt-4">
<AccordionItem value="place-details">
<AccordionTrigger>Place Details</AccordionTrigger>
<AccordionContent>
<div className="space-y-4 max-h-64 overflow-y-auto">
{result.results.map((place: any, index: number) => (
<div key={index} className="flex justify-between items-start py-2 border-b last:border-b-0">
<div>
<h4 className="font-semibold">{place.name}</h4>
<p className="text-sm text-muted-foreground max-w-[200px]" title={place.formatted_address}>
{place.formatted_address}
</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>
))}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</CardContent>
</Card>
);
});
TextSearchResult.displayName = 'TextSearchResult';
const renderToolInvocation = (toolInvocation: ToolInvocation, index: number) => {
const args = JSON.parse(JSON.stringify(toolInvocation.args));
const result = 'result' in toolInvocation ? JSON.parse(JSON.stringify(toolInvocation.result)) : null;
@ -442,6 +560,68 @@ export default function Home() {
</Card>
);
}
if (toolInvocation.toolName === 'find_place') {
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">Finding place...</span>
</div>
<motion.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",
}}
/>
))}
</motion.div>
</div>
);
}
return <FindPlaceResult result={result} />;
}
if (toolInvocation.toolName === 'text_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 places...</span>
</div>
<motion.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",
}}
/>
))}
</motion.div>
</div>
);
}
return <TextSearchResult result={result} />;
}
if (toolInvocation.toolName === 'get_weather_data') {
if (!result) {
@ -489,92 +669,110 @@ export default function Home() {
if (toolInvocation.toolName === 'programming') {
return (
<Accordion type="single" collapsible className="w-full my-4">
<AccordionItem value="programming" className="border-none">
<AccordionTrigger className="hover:no-underline">
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2 text-left">
<Code className="h-5 w-5 text-primary" />
<span className="font-semibold">Programming</span>
</div>
{result ? (
<Badge variant="secondary" className="ml-auto mr-2 rounded-full">
<Check className="h-3 w-3 mr-1" />
Run Complete
</Badge>
) : (
<Badge variant="secondary" className="ml-auto mr-2 rounded-full">
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
Running
</Badge>
)}
</div>
</AccordionTrigger>
<AccordionContent className="pt-4 pb-2 space-y-4">
{args?.code && (
<div>
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium">Code</h3>
<CopyButton text={args.code} />
</div>
<div className="relative">
<SyntaxHighlighter
language="python"
style={oneLight}
customStyle={{
margin: 0,
padding: '1rem',
borderRadius: '0.5rem',
fontSize: '0.875rem',
}}
>
{args.code}
</SyntaxHighlighter>
<div className="absolute top-2 right-2">
<Badge variant="outline" className="text-xs">
Python
</Badge>
</div>
</div>
</div>
<div className="w-full my-2 border border-gray-200 overflow-hidden rounded-md">
<div className="bg-gray-100 p-2 flex items-center">
<Code className="h-5 w-5 text-gray-500 mr-2" />
<span className="text-sm font-medium">Programming</span>
</div>
<Tabs defaultValue="code" className="w-full">
<TabsList className="bg-gray-50 p-0 h-auto shadow-sm">
<TabsTrigger
value="code"
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
>
Code
</TabsTrigger>
<TabsTrigger
value="output"
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
>
Output
</TabsTrigger>
{result?.images && result.images.length > 0 && (
<TabsTrigger
value="images"
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
>
Images
</TabsTrigger>
)}
<div>
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium">Result</h3>
{result && <CopyButton text={result} />}
</TabsList>
<TabsContent value="code" className="p-0 m-0 rounded-none">
<div className="relative">
<SyntaxHighlighter
language="python"
style={oneLight}
customStyle={{
margin: 0,
padding: '1rem',
fontSize: '0.875rem',
borderRadius: 0,
}}
>
{args.code}
</SyntaxHighlighter>
<div className="absolute top-2 right-2">
<CopyButton text={args.code} />
</div>
</div>
</TabsContent>
<TabsContent value="output" className="p-0 m-0 rounded-none">
<div className="relative bg-white p-4">
{result ? (
<pre className="bg-neutral-50 p-3 rounded-md overflow-x-auto text-sm">
<code>{result}</code>
</pre>
) : (
<div className="flex items-center justify-between w-full !bg-neutral-100 p-3 rounded-md">
<div className="flex items-center gap-2">
<Loader2 className="h-5 w-5 text-muted-foreground animate-spin" />
<span className="text-muted-foreground text-sm">Executing code...</span>
<>
<pre className="text-sm">
<code>{result.message}</code>
</pre>
<div className="absolute top-2 right-2">
<CopyButton text={result.message} />
</div>
<div className="flex space-x-1">
{[0, 1, 2].map((index) => (
<motion.div
key={index}
className="w-1.5 h-1.5 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 className="flex items-center justify-center h-20">
<div className="flex items-center gap-2">
<Loader2 className="h-5 w-5 text-gray-400 animate-spin" />
<span className="text-gray-500 text-sm">Executing code...</span>
</div>
</div>
)}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</TabsContent>
{result?.images && result.images.length > 0 && (
<TabsContent value="images" className="p-0 m-0 bg-white">
<div className="space-y-4 p-4">
{result.images.map((img: { format: 'png' | 'jpeg' | 'svg', data: string }, imgIndex: number) => (
<div key={imgIndex} className="space-y-2">
<div className="flex justify-between items-center">
<h4 className="text-sm font-medium">Image {imgIndex + 1}</h4>
<Button
variant="ghost"
size="sm"
className="p-0 h-8 w-8"
onClick={() => {
const link = document.createElement('a');
link.href = `data:image/${img.format === 'svg' ? 'svg+xml' : img.format};base64,${img.data}`;
link.download = `generated-image-${imgIndex + 1}.${img.format}`;
link.click();
}}
>
<Download className="h-4 w-4" />
</Button>
</div>
<div className="relative w-full" style={{ aspectRatio: '16/9' }}>
<Image
src={`data:image/${img.format === 'svg' ? 'svg+xml' : img.format};base64,${img.data}`}
alt={`Generated image ${imgIndex + 1}`}
layout="fill"
objectFit="contain"
/>
</div>
</div>
))}
</div>
</TabsContent>
)}
</Tabs>
</div>
);
}
@ -883,15 +1081,18 @@ export default function Home() {
const Navbar = () => (
<div className="fixed top-0 left-0 right-0 z-50 flex justify-between items-center p-4 bg-background">
<Button
variant="outline"
size="sm"
onClick={() => router.push('/new')}
className="flex items-center space-x-2"
>
<RefreshCw className="h-4 w-4" />
<span>New</span>
</Button>
<Link href="/new">
<Button
type="button"
variant={'secondary'}
className="rounded-full bg-secondary/80 group transition-all hover:scale-105 pointer-events-auto"
>
<Plus size={18} className="group-hover:rotate-90 transition-all" />
<span className="text-sm ml-2 group-hover:block hidden animate-in fade-in duration-300">
New
</span>
</Button>
</Link>
<div
className='flex items-center space-x-2'
>
@ -930,20 +1131,16 @@ export default function Home() {
<Navbar />
<div className={`w-full max-w-[90%] sm:max-w-2xl space-y-6 p-1 ${hasSubmitted ? 'mt-16 sm:mt-20' : 'mt-[26vh] sm:mt-[30vh]'}`}>
<motion.div
initial={false}
animate={hasSubmitted ? { scale: 0.7 } : { scale: 1 }}
transition={{ duration: 0.5 }}
className="text-center"
>
<h1 className="text-4xl sm:text-6xl mb-1 text-primary font-serif">MiniPerplx</h1>
{!hasSubmitted &&
{!hasSubmitted &&
<div
className="text-center"
>
<h1 className="text-4xl sm:text-6xl mb-1 text-primary font-serif">MiniPerplx</h1>
<h2 className='text-xl sm:text-2xl font-serif text-balance text-center mb-6'>
In search for minimalism and simplicity
</h2>
}
</motion.div>
</div>
}
<AnimatePresence>
{!hasSubmitted && (
<motion.div

55
components/ui/tabs.tsx Normal file
View File

@ -0,0 +1,55 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@ -19,6 +19,7 @@
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@tailwindcss/typography": "^0.5.13",
"@types/googlemaps": "^3.43.3",

View File

@ -35,6 +35,9 @@ dependencies:
'@radix-ui/react-slot':
specifier: ^1.1.0
version: 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-tabs':
specifier: ^1.1.0
version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-tooltip':
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
@ -52,7 +55,7 @@ dependencies:
version: 1.4.0
ai:
specifier: latest
version: 3.3.10(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
version: 3.3.11(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@ -204,8 +207,8 @@ packages:
json-schema: 0.4.0
dev: false
/@ai-sdk/react@0.0.45(react@18.3.1)(zod@3.23.8):
resolution: {integrity: sha512-rPsjHtJ+qsWoRV88xzEpvPXhqRBF2wljGjWrsFcao4p48hKwZkuhW/zzpxZj3A4ZPy6jpDHRTYtqMuPHVpc9Eg==}
/@ai-sdk/react@0.0.46(react@18.3.1)(zod@3.23.8):
resolution: {integrity: sha512-MQHC6AxEUi10++8LsnR3NUK+VkxB2sEWVlFxYrj6PKF5krxWnLgoCmpvbAdM6oc5b9byySBJaj6ZxRxutRpWtQ==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19
@ -1101,6 +1104,33 @@ packages:
react: 18.3.1
dev: false
/@radix-ui/react-tabs@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@radix-ui/react-tooltip@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==}
peerDependencies:
@ -1576,8 +1606,8 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
/ai@3.3.10(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8):
resolution: {integrity: sha512-NuUUymuQb5KWZii8xMARfhloXmzlRNJiibv7Ci5N6WsMs2DmiVUlybUEL+DOYfIdDRzqCC2mxE0agpdgCbA2kA==}
/ai@3.3.11(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8):
resolution: {integrity: sha512-KPfikv3KDqFbZXRoB3jaM6bRJGU2ivmk12ahmo8LynEq1zqJJPc/2JEqlXNG9MoAQo3tQuqYiQfDzPIEO4aObA==}
engines: {node: '>=18'}
peerDependencies:
openai: ^4.42.0
@ -1599,7 +1629,7 @@ packages:
dependencies:
'@ai-sdk/provider': 0.0.20
'@ai-sdk/provider-utils': 1.0.13(zod@3.23.8)
'@ai-sdk/react': 0.0.45(react@18.3.1)(zod@3.23.8)
'@ai-sdk/react': 0.0.46(react@18.3.1)(zod@3.23.8)
'@ai-sdk/solid': 0.0.36(zod@3.23.8)
'@ai-sdk/svelte': 0.0.38(svelte@4.2.18)(zod@3.23.8)
'@ai-sdk/ui-utils': 0.0.33(zod@3.23.8)