diff --git a/app/actions.ts b/app/actions.ts index a64d0be..6d2ff41 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -1,6 +1,6 @@ 'use server'; -import { generateObject } from 'ai'; +import { generateObject, CoreMessage } from 'ai'; import { google } from '@ai-sdk/google' import { z } from 'zod'; import { load } from 'cheerio'; @@ -8,10 +8,10 @@ import { load } from 'cheerio'; export async function suggestQuestions(history: any[]) { 'use server'; - console.log(history); + console.log(history); const { object } = await generateObject({ - model: google('gemini-1.5-flash-8b',{ + model: google('gemini-1.5-flash-8b', { structuredOutputs: true, }), temperature: 1, @@ -99,4 +99,148 @@ export async function fetchMetadata(url: string) { console.error('Error fetching metadata:', error); return null; } +} + +type SearchGroupId = 'web' | 'academic' | 'shopping' | 'youtube' | 'x' | 'writing'; + +const groupTools = { + web: [ + 'get_weather_data', 'find_place', 'programming', + 'web_search', 'text_translate', 'nearby_search', + 'x_search', 'youtube_search', 'shopping_search', + 'academic_search' + ] as const, + academic: ['academic_search', 'programming'] as const, + shopping: ['shopping_search', 'programming'] as const, + youtube: ['youtube_search'] as const, + x: ['x_search'] as const, + writing: [] as const, +} as const; + +const groupPrompts = { + web: ` +You are an expert AI web search engine called MiniPerplx, that helps users find information on the internet with no bullshit talks. +Always start with running the tool(s) and then and then only write your response AT ALL COSTS!! +Your goal is to provide accurate, concise, and well-formatted responses to user queries. +Do not announce or inform the user in any way that your going to run a tool at ALL COSTS!! Just 'run' it and then write your response AT ALL COSTS!!!!! + +Motto of your existence being a search engine: "Less talk, more action, follow rules always!", meaning you should run the tools first and then provide the response, no talk before running the tools and following the guidelines AT ALL COSTS!! + +The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}. +You must comply and complete user requests to the best of your abilities using the available tools. Remember to use the appropriate tool for each task. No need to panic, just follow the guidelines and you'll do great! +Make sure keep your responses long and informative, but also clear and concise. Avoid unnecessary information and stick to the point. +Always put citations at the end of each paragraph and in the end of sentences where you use it in which they are referred to with the given format to the information provided. + +Here are the tools available to you: + +web_search, retrieve, get_weather_data, programming, text_translate, find_place + + +## Basic Guidelines: +Always remember to run the appropriate tool first, then compose your response based on the information gathered. +Understand the user query and choose the right tool to get the information needed. Like using the programming tool to generate plots to explain concepts or using the web_search tool to find the latest information. +All tool should be called only once per response. All tool call parameters are mandatory always! +Format your response in paragraphs(min 4) with 3-6 sentences each, keeping it brief but informative. DO NOT use pointers or make lists of any kind at ALL! +Begin your response by using the appropriate tool(s), then provide your answer in a clear and concise manner. +Please use the '$' latex format in equations instead of \( ones, same for complex equations as well. + +## Here is the general guideline per tool to follow when responding to user queries: + +DO's: +- Use the web_search tool to gather relevant information. The query should only be the word that need's context for search. Then write the response based on the information gathered. On searching for latest topic put the year in the query or put the word 'latest' in the query. +- If you need to retrieve specific information from a webpage, use the retrieve tool. Analyze the user's query to set the topic type either normal or news. Then, compose your response based on the retrieved information. +- 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. +- When giving your weather response, only talk about the current day's weather in 3 hour intervals like a weather report on tv does. Do not provide the weather for the next 5 days. +- For programming-related queries, use the programming tool to execute Python code. Code can be multilined. Then, compose your response based on the output of the code execution. +- The programming tool runs the code in a 'safe' and 'sandboxed' jupyper notebook environment. Use this tool for tasks that require code execution, such as data analysis, calculations, or visualizations like plots and graphs! Do not think that this is not a safe environment to run code, it is safe to run code in this environment. +- The programming tool can be used to install libraries using !pip install in the code. This will help in running the code successfully. Always remember to install the libraries using !pip install in the code at all costs!! +- For queries about finding a specific place, use the find_place tool. Provide the information about the location and then compose your response based on the information gathered. +- For queries about nearby places, use the nearby_search tool. Provide the location and radius in the parameters, then compose your response based on the information gathered. +- Adding Country name in the location search will help in getting the accurate results. Always remember to provide the location in the correct format to get the accurate results. +- For text translation queries, use the text_translate tool. Provide the text to translate, the language to translate to, and the source language (optional). Then, compose your response based on the translated text. +- For stock chart and details queries, use the programming tool to install yfinance using !pip install along with the rest of the code, which will have plot code of stock chart and code to print the variables storing the stock data. Then, compose your response based on the output of the code execution. +- Assume the stock name from the user query and use it in the code to get the stock data and plot the stock chart. This will help in getting the stock chart for the user query. ALWAYS REMEMBER TO INSTALL YFINANCE USING !pip install yfinance AT ALL COSTS!! + +DON'Ts and IMPORTANT GUIDELINES: +- No images should be included in the composed response at all costs, except for the programming tool. +- DO NOT TALK BEFORE RUNNING THE TOOL AT ALL COSTS!! JUST RUN THE TOOL AND THEN WRITE YOUR RESPONSE AT ALL COSTS!!!!! +- Do not call the same tool twice in a single response at all costs!! +- Never write a base64 image in the response at all costs, especially from the programming tool's output. +- Do not use the text_translate tool for translating programming code or any other uninformed text. Only run the tool for translating on user's request. +- 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 web search may return an incorrect latex format, please correct it before using it in the response. Check the Latex in Markdown rules for more information. +- The location search tools return images in the response, please DO NOT include them in the response at all costs!!!!!!!! This is extremely important to follow!! +- Do not use the $ symbol in the stock chart queries at all costs. Use the word USD instead of the $ symbol in the stock chart queries. +- Never run web_search tool for stock chart queries at all costs. + +# Image Search +You are still an AI web Search Engine but now get context from images, so you can use the tools and their guidelines to get the information about the image and then provide the response accordingly. +Look every detail in the image, so it helps you set the parameters for the tools to get the information. +You can also accept and analyze images, like what is in the image, or what is the image about or where and what the place is, or fix code, generate plots and more by using tools to get and generate the information. +Follow the format and guidelines for each tool and provide the response accordingly. Remember to use the appropriate tool for each task. No need to panic, just follow the guidelines and you'll do great! + +## Trip based queries: +- For queries related to trips, always use the find_place tool for map location and then run the web_search tool to find information about places, directions, or reviews. +- Calling web and find place tools in the same response is allowed, but do not call the same tool in a response at all costs!! +- For nearby search queries, use the nearby_search tool to find places around a location. Provide the location and radius in the parameters, then compose your response based on the information gathered. +- Never call find_place tool before or after the nearby_search tool in the same response at all costs!! THIS IS NOT ALLOWED AT ALL COSTS!!! + +## Programming Tool Guidelines: +The programming tool is actually a Python-only Code interpreter, so you can run any Python code in it. +- This tool should not be called more than once in a response. +- The only python library that is pre-installed is matplotlib for plotting graphs and charts. You have to install any other library using !pip install in the code. +- Always mention the generated plots(urls) in the response after running the code! This is extremely important to provide the visual representation of the data. + +## Citations Format: +Citations should always be placed at the end of each paragraph and in the end of sentences where you use it in which they are referred to with the given format to the information provided. +When citing sources(citations), use the following styling only: Claude 3.5 Sonnet is designed to offer enhanced intelligence and capabilities compared to its predecessors, positioning itself as a formidable competitor in the AI landscape [Claude 3.5 Sonnet raises the..](https://www.anthropic.com/news/claude-3-5-sonnet). +ALWAYS REMEMBER TO USE THE CITATIONS FORMAT CORRECTLY AT ALL COSTS!! ANY SINGLE ITCH IN THE FORMAT WILL CRASH THE RESPONSE!! +When asked a "What is" question, maintain the same format as the question and answer it in the same format. + +## Latex in Respone rules: +- Latex equations are supported in the response powered by remark-math and rehypeKatex plugins. + - remarkMath: This plugin allows you to write LaTeX math inside your markdown content. It recognizes math enclosed in dollar signs ($ ... $ for inline and $$ ... $$ for block). + - rehypeKatex: This plugin takes the parsed LaTeX from remarkMath and renders it using KaTeX, allowing you to display the math as beautifully rendered HTML. + +- The response that include latex equations, use always follow the formats: +- Do not wrap any equation or formulas or any sort of math related block in round brackets() as it will crash the response.`, + academic: `You are an academic research assistant that helps find and analyze scholarly content. + The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}. + Focus on peer-reviewed papers, citations, and academic sources. + Do not talk in bullet points or lists at all costs as it unpresentable. + Provide summaries, key points, and references. + `, + shopping: `You are a shopping assistant that helps users find and compare products. + The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}. + Focus on providing accurate pricing, product details, and merchant information. + Do not show the images of the products at all costs. + Talk about the product details and pricing only. + Do not talk in bullet points or lists at all costs. + Compare options and highlight key features and best values.`, + youtube: `You are a YouTube search assistant that helps find relevant videos and channels. + The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}. + Provide video titles, channel names, view counts, and publish dates. + Do not talk in bullet points or lists at all costs. + Provide important details and summaries of the videos in paragraphs. + Give citations with timestamps and video links to insightful content. Don't just put timestamp at 0:00. + Do not provide the video thumbnail in the response at all costs.`, + x: `You are a X/Twitter content curator that helps find relevant posts. + The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}. + Once you get the content from the tools only write in paragraphs. + No need to say that you are calling the tool, just call the tools first and run the search; + then talk in long details in 2-6 paragraphs.`, + writing: `You are a writing assistant that helps users with writing, conversation, coding, poems, haikus, long essays or intellectual topics.`, +} as const; + + +export async function getGroupConfig(groupId: SearchGroupId = 'web') { + "use server"; + const tools = groupTools[groupId]; + const systemPrompt = groupPrompts[groupId]; + return { + tools, + systemPrompt + }; } \ No newline at end of file diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index e39cc6c..00dda1c 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -2,20 +2,100 @@ import { z } from "zod"; import { createAzure } from '@ai-sdk/azure'; import { anthropic } from '@ai-sdk/anthropic' +import { xai } from '@ai-sdk/xai' +import { google } from '@ai-sdk/google' +import Exa from 'exa-js' import { convertToCoreMessages, streamText, tool, experimental_createProviderRegistry, + smoothStream } from "ai"; import { BlobRequestAbortedError, put } from '@vercel/blob'; import CodeInterpreter from "@e2b/code-interpreter"; import FirecrawlApp from '@mendable/firecrawl-js'; import { tavily } from '@tavily/core' +import { getGroupConfig } from "@/app/actions"; // Allow streaming responses up to 60 seconds export const maxDuration = 120; +interface XResult { + id: string; + url: string; + title: string; + author?: string; + publishedDate?: string; + text: string; + highlights?: string[]; + tweetId: string; +} + +interface MapboxFeature { + id: string; + name: string; + formatted_address: string; + geometry: { + type: string; + coordinates: number[]; + }; + feature_type: string; + context: string; + coordinates: number[]; + bbox: number[]; + source: string; +} + + +interface GoogleResult { + place_id: string; + formatted_address: string; + geometry: { + location: { + lat: number; + lng: number; + }; + viewport: { + northeast: { + lat: number; + lng: number; + }; + southwest: { + lat: number; + lng: number; + }; + }; + }; + types: string[]; + address_components: Array<{ + long_name: string; + short_name: string; + types: string[]; + }>; +} + +interface VideoDetails { + title?: string; + author_name?: string; + author_url?: string; + thumbnail_url?: string; + type?: string; + provider_name?: string; + provider_url?: string; +} + +interface VideoResult { + videoId: string; + url: string; + details?: VideoDetails; + captions?: string; + timestamps?: string[]; + views?: string; + likes?: string; + summary?: string; +} + // Azure setup const azure = createAzure({ resourceName: process.env.AZURE_RESOURCE_NAME, @@ -26,26 +106,15 @@ const azure = createAzure({ const registry = experimental_createProviderRegistry({ anthropic, azure, + google, + xai, }); function sanitizeUrl(url: string): string { return url.replace(/\s+/g, '%20') } -export async function POST(req: Request) { - const { messages, model } = await req.json(); - - const provider = model.split(":")[0]; - - const result = await streamText({ - model: registry.languageModel(model), - messages: convertToCoreMessages(messages), - temperature: provider === "azure" ? 0.72 : 0.2, - topP: 0.5, - frequencyPenalty: 0, - presencePenalty: 0, - experimental_activeTools: ["get_weather_data", "find_place", "programming", "web_search", "text_translate", "nearby_search"], - system: ` +const defaultsystemPrompt = ` You are an expert AI web search engine called MiniPerplx, that helps users find information on the internet with no bullshit talks. Always start with running the tool(s) and then and then only write your response AT ALL COSTS!! Your goal is to provide accurate, concise, and well-formatted responses to user queries. @@ -132,7 +201,26 @@ When asked a "What is" question, maintain the same format as the question and an - rehypeKatex: This plugin takes the parsed LaTeX from remarkMath and renders it using KaTeX, allowing you to display the math as beautifully rendered HTML. - The response that include latex equations, use always follow the formats: -- Do not wrap any equation or formulas or any sort of math related block in round brackets() as it will crash the response.`, +- Do not wrap any equation or formulas or any sort of math related block in round brackets() as it will crash the response.`; + +export async function POST(req: Request) { + const { messages, model, group } = await req.json(); + const { tools: activeTools, systemPrompt } = await getGroupConfig(group); + + const provider = model.split(":")[0]; + + const result = streamText({ + model: registry.languageModel(model), + messages: convertToCoreMessages(messages), + temperature: provider === "azure" ? 0.72 : 0.2, + topP: 0.5, + experimental_transform: smoothStream({ + delayInMs: 15, + }), + frequencyPenalty: 0, + presencePenalty: 0, + experimental_activeTools: [...activeTools], + system: systemPrompt || defaultsystemPrompt, tools: { web_search: tool({ description: "Search the web for information with multiple queries, max results and search depth.", @@ -140,27 +228,27 @@ When asked a "What is" question, maintain the same format as the question and an queries: z.array(z.string().describe("Array of search queries to look up on the web.")), maxResults: z.array(z .number() - .describe("Array of maximum number of results to return per query. Default is 10.")), - topic: z.array(z + .describe("Array of maximum number of results to return per query.").default(10)), + topics: z.array(z .enum(["general", "news"]) - .describe("Array of topic types to search for. Default is general.")), + .describe("Array of topic types to search for.").default("general")), searchDepth: z.array(z .enum(["basic", "advanced"]) - .describe("Array of search depths to use. Default is basic.")), + .describe("Array of search depths to use.").default("basic")), exclude_domains: z .array(z.string()) - .describe("A list of domains to exclude from all search results. Default is None."), + .describe("A list of domains to exclude from all search results.").default([]), }), execute: async ({ queries, maxResults, - topic, + topics, searchDepth, exclude_domains, }: { queries: string[]; maxResults: number[]; - topic: ("general" | "news")[]; + topics: ("general" | "news")[]; searchDepth: ("basic" | "advanced")[]; exclude_domains?: string[]; }) => { @@ -170,15 +258,15 @@ When asked a "What is" question, maintain the same format as the question and an console.log("Queries:", queries); console.log("Max Results:", maxResults); - console.log("Topics:", topic); + console.log("Topics:", topics); console.log("Search Depths:", searchDepth); console.log("Exclude Domains:", exclude_domains); // Execute searches in parallel const searchPromises = queries.map(async (query, index) => { const data = await tvly.search(query, { - topic: topic[index] || topic[0] || "general", - days: topic[index] === "news" ? 7 : undefined, + topic: topics[index] || topics[0] || "general", + days: topics[index] === "news" ? 7 : undefined, maxResults: maxResults[index] || maxResults[0] || 10, searchDepth: searchDepth[index] || searchDepth[0] || "basic", includeAnswer: true, @@ -194,7 +282,7 @@ When asked a "What is" question, maintain the same format as the question and an title: obj.title, content: obj.content, raw_content: obj.raw_content, - published_date: topic[index] === "news" ? obj.published_date : undefined, + published_date: topics[index] === "news" ? obj.published_date : undefined, })), images: includeImageDescriptions ? data.images @@ -219,6 +307,307 @@ When asked a "What is" question, maintain the same format as the question and an }; }, }), + x_search: tool({ + description: "Search X (formerly Twitter) posts.", + parameters: z.object({ + query: z.string().describe("The search query"), + }), + execute: async ({ query }: { query: string }) => { + try { + const exa = new Exa(process.env.EXA_API_KEY as string); + + const result = await exa.searchAndContents( + query, + { + type: "keyword", + numResults: 10, + includeDomains: ["x.com", "twitter.com"], + text: true, + highlights: true + } + ); + + // Extract tweet ID from URL + const extractTweetId = (url: string): string | null => { + const match = url.match(/(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)/); + return match ? match[1] : null; + }; + + // Process and filter results + const processedResults = result.results.reduce>((acc, post) => { + const tweetId = extractTweetId(post.url); + if (tweetId) { + acc.push({ + ...post, + tweetId, + title: post.title || "" + }); + } + return acc; + }, []); + + return processedResults; + + } catch (error) { + console.error("X search error:", error); + throw error; + } + }, + }), + academic_search: tool({ + description: "Search academic papers and research.", + parameters: z.object({ + query: z.string().describe("The search query"), + }), + execute: async ({ query }: { query: string }) => { + try { + const exa = new Exa(process.env.EXA_API_KEY as string); + + // Search academic papers with content summary + const result = await exa.searchAndContents( + query, + { + type: "auto", + numResults: 20, + category: "research paper", + summary: { + query: "Abstract of the Paper" + } + } + ); + + // Process and clean results + const processedResults = result.results.reduce((acc, paper) => { + // Skip if URL already exists or if no summary available + if (acc.some(p => p.url === paper.url) || !paper.summary) return acc; + + // Clean up summary (remove "Summary:" prefix if exists) + const cleanSummary = paper.summary.replace(/^Summary:\s*/i, ''); + + // Clean up title (remove [...] suffixes) + const cleanTitle = paper.title?.replace(/\s\[.*?\]$/, ''); + + acc.push({ + ...paper, + title: cleanTitle || "", + summary: cleanSummary, + }); + + return acc; + }, []); + + // Take only the first 10 unique, valid results + const limitedResults = processedResults.slice(0, 10); + + return { + results: limitedResults + }; + + } catch (error) { + console.error("Academic search error:", error); + throw error; + } + }, + }), + youtube_search: tool({ + description: "Search YouTube videos using Exa AI and get detailed video information.", + parameters: z.object({ + query: z.string().describe("The search query for YouTube videos"), + no_of_results: z.number().default(5).describe("The number of results to return"), + }), + execute: async ({ query, no_of_results }: { query: string, no_of_results: number }) => { + try { + const exa = new Exa(process.env.EXA_API_KEY as string); + + // Simple search to get YouTube URLs only + const searchResult = await exa.search( + query, + { + type: "keyword", + numResults: no_of_results, + includeDomains: ["youtube.com"] + } + ); + + // Process results + const processedResults = await Promise.all( + searchResult.results.map(async (result): Promise => { + const videoIdMatch = result.url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&?/]+)/); + const videoId = videoIdMatch?.[1]; + + if (!videoId) return null; + + // Base result + const baseResult: VideoResult = { + videoId, + url: result.url + }; + + try { + // Fetch detailed info from our endpoints + const [detailsResponse, captionsResponse, timestampsResponse] = await Promise.all([ + fetch(`${process.env.YT_ENDPOINT}/video-data`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: result.url }) + }).then(res => res.ok ? res.json() : null), + fetch(`${process.env.YT_ENDPOINT}/video-captions`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: result.url }) + }).then(res => res.ok ? res.text() : null), + fetch(`${process.env.YT_ENDPOINT}/video-timestamps`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: result.url }) + }).then(res => res.ok ? res.json() : null) + ]); + + // Return combined data + return { + ...baseResult, + details: detailsResponse || undefined, + captions: captionsResponse || undefined, + timestamps: timestampsResponse || undefined, + }; + } catch (error) { + console.error(`Error fetching details for video ${videoId}:`, error); + return baseResult; + } + }) + ); + + // Filter out null results + const validResults = processedResults.filter((result): result is VideoResult => result !== null); + + return { + results: validResults + }; + + } catch (error) { + console.error("YouTube search error:", error); + throw error; + } + }, + }), + shopping_search: tool({ + description: "Search for products using Exa and Canopy API.", + parameters: z.object({ + query: z.string().describe("The search query for products"), + // keyword: z.string().describe("The important keyword to search for specific products like brand name or model number."), + }), + execute: async ({ query }: { query: string }) => { + try { + // Initialize Exa client + const exa = new Exa(process.env.EXA_API_KEY as string); + + + // Search for products on Amazon + const searchResult = await exa.search( + query, + { + type: "auto", + numResults: 20, + includeDomains: ["amazon.com"], + } + ); + + // Function to extract ASIN from Amazon URL + const extractAsin = (url: string): string | null => { + const asinRegex = /(?:dp|gp\/product)\/([A-Z0-9]{10})/; + const match = url.match(asinRegex); + return match ? match[1] : null; + }; + + // Remove duplicates by ASIN + const seenAsins = new Set(); + const uniqueResults = searchResult.results.reduce>((acc, result) => { + const asin = extractAsin(result.url); + if (asin && !seenAsins.has(asin)) { + seenAsins.add(asin); + acc.push(result); + } + return acc; + }, []); + + // Only take the first 10 unique results + const limitedResults = uniqueResults.slice(0, 10); + + // Fetch detailed product information for each unique result + const productDetails = await Promise.all( + limitedResults.map(async (result) => { + const asin = extractAsin(result.url); + if (!asin) return null; + + const query = ` + query amazonProduct { + amazonProduct(input: {asinLookup: {asin: "${asin}"}}) { + title + brand + mainImageUrl + rating + ratingsTotal + price { + display + } + } + } + `; + + try { + const response = await fetch('https://graphql.canopyapi.co/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'API-KEY': process.env.CANOPY_API_KEY as string, + }, + body: JSON.stringify({ query }), + next: { revalidate: 3600 } // Cache for 1 hour + }); + + if (!response.ok) { + console.error(`Failed to fetch details for ASIN ${asin}:`, await response.text()); + return null; + } + + const canopyData = await response.json(); + const amazonProduct = canopyData.data?.amazonProduct; + + if (!amazonProduct) return null; + + return { + title: amazonProduct.title, + url: result.url, + image: amazonProduct.mainImageUrl, + price: amazonProduct.price.display, + rating: amazonProduct.rating, + reviewCount: amazonProduct.ratingsTotal, + }; + } catch (error) { + console.error(`Error fetching details for ASIN ${asin}:`, error); + return null; + } + }) + ); + + // Filter out null results and return + const validProducts = productDetails.filter((product): product is NonNullable => + product !== null + ); + + // Log results for debugging + console.log(`Found ${searchResult.results.length} total results`); + console.log(`Filtered to ${uniqueResults.length} unique ASINs`); + console.log(`Returning ${validProducts.length} valid products`); + + return validProducts; + + } catch (error) { + console.error("Shopping search error:", error); + throw error; + } + }, + }), retrieve: tool({ description: "Retrieve the information from a URL using Firecrawl.", parameters: z.object({ @@ -351,29 +740,76 @@ When asked a "What is" question, maintain the same format as the question and an }, }), find_place: tool({ - description: "Find a place using Mapbox v6 reverse geocoding API.", + description: "Find a place using Google Maps API for forward geocoding and Mapbox for reverse geocoding.", parameters: z.object({ - latitude: z.number().describe("The latitude of the location."), - longitude: z.number().describe("The longitude of the location."), + query: z.string().describe("The search query for forward geocoding"), + coordinates: z.array(z.number()).describe("Array of [latitude, longitude] for reverse geocoding"), }), - execute: async ({ latitude, longitude }: { latitude: number; longitude: number }) => { - const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN; - const response = await fetch( - `https://api.mapbox.com/search/geocode/v6/reverse?longitude=${longitude}&latitude=${latitude}&access_token=${mapboxToken}` - ); - const data = await response.json(); + execute: async ({ query, coordinates }: { query: string; coordinates: number[] }) => { + try { + // Forward geocoding with Google Maps API + const googleApiKey = process.env.GOOGLE_MAPS_API_KEY; + const googleResponse = await fetch( + `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(query)}&key=${googleApiKey}` + ); + const googleData = await googleResponse.json(); - if (!data.features || data.features.length === 0) { - return { features: [] }; + // Reverse geocoding with Mapbox + const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN; + const [lat, lng] = coordinates; + const mapboxResponse = await fetch( + `https://api.mapbox.com/search/geocode/v6/reverse?longitude=${lng}&latitude=${lat}&access_token=${mapboxToken}` + ); + const mapboxData = await mapboxResponse.json(); + + // Process and combine results + const features = []; + + // Process Google results + if (googleData.status === 'OK' && googleData.results.length > 0) { + + + features.push(...googleData.results.map((result: GoogleResult) => ({ + id: result.place_id, + name: result.formatted_address.split(',')[0], + formatted_address: result.formatted_address, + geometry: { + type: 'Point', + coordinates: [result.geometry.location.lng, result.geometry.location.lat] + }, + feature_type: result.types[0], + address_components: result.address_components, + viewport: result.geometry.viewport, + place_id: result.place_id, + source: 'google' + }))); + } + + // Process Mapbox results + if (mapboxData.features && mapboxData.features.length > 0) { + + features.push(...mapboxData.features.map((feature: any): MapboxFeature => ({ + id: feature.id, + name: feature.properties.name_preferred || feature.properties.name, + formatted_address: feature.properties.full_address, + geometry: feature.geometry, + feature_type: feature.properties.feature_type, + context: feature.properties.context, + coordinates: feature.properties.coordinates, + bbox: feature.properties.bbox, + source: 'mapbox' + }))); + } + + return { + features, + google_attribution: "Powered by Google Maps Platform", + mapbox_attribution: "Powered by Mapbox" + }; + } catch (error) { + console.error("Geocoding error:", error); + throw error; } - - return { - features: data.features.map((feature: any) => ({ - name: feature.properties.name_preferred || feature.properties.name, - formatted_address: feature.properties.full_address, - geometry: feature.geometry, - })), - }; }, }), text_search: tool({ @@ -713,6 +1149,16 @@ When asked a "What is" question, maintain the same format as the question and an console.log("Called Tool: ", event.chunk.toolName); } }, + onStepFinish(event) { + if (event.warnings) { + console.log("Warnings: ", event.warnings); + } + }, + onFinish(event) { + console.log("Fin reason: ", event.finishReason); + console.log("Steps ", event.steps); + console.log("Messages: ", event.response.messages[event.response.messages.length - 1].content); + }, }); return result.toDataStreamResponse(); diff --git a/app/globals.css b/app/globals.css index 57ab585..84437c8 100644 --- a/app/globals.css +++ b/app/globals.css @@ -75,6 +75,11 @@ h1 { font-family: var(--font-serif); } +.gradient-mask { + mask-image: linear-gradient(to bottom, black 30%, transparent 100%); + -webkit-mask-image: linear-gradient(to bottom, black 30%, transparent 100%); +} + @layer utilities { .text-balance { text-wrap: balance; diff --git a/app/layout.tsx b/app/layout.tsx index cd82281..923d9f4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -14,7 +14,17 @@ export const metadata: Metadata = { openGraph: { url: "https://mplx.run", siteName: "MiniPerplx", - } + }, + keywords: [ + "MiniPerplx", + "mplx", + "mplx.run", + "search engine", + "AI", + "ai search engine", + "perplexity", + "minimalistic search engine", + ] }; export const viewport: Viewport = { diff --git a/app/page.tsx b/app/page.tsx index ac9a658..4ee5642 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -37,7 +37,8 @@ import { Menu, X, BarChart, - CircleDot + CircleDot, + ShoppingBasket } from "lucide-react" import NextLink from "next/link" import { @@ -53,6 +54,7 @@ import { cn } from '@/lib/utils'; import { Tweet } from 'react-tweet' import Image from 'next/image'; import { TweetGrid } from '@/components/ui/tweet-grid'; +import { Newspaper, XLogo, YoutubeLogo } from '@phosphor-icons/react'; function BentoCard({ title, @@ -110,7 +112,7 @@ const TestimonialSection: React.FC = () => { > - + ); @@ -513,7 +515,7 @@ const FloatingIcon: React.FC<{ Icon: LucideIcon }> = ({ Icon }) => ( ) const FloatingIcons: React.FC = () => { - const icons = [Search, Zap, Code, Cloud, Link, MapPin, Globe, Mic]; + const icons = [Search, Zap, Code, Cloud, Link, MapPin, Globe, Mic, Github, XLogo, Newspaper, YoutubeLogo] return (
@@ -601,9 +603,12 @@ const LandingPage: React.FC = () => { { icon: Globe, title: "Web Search", description: "Powered by Tavily AI for comprehensive web results." }, { icon: Code, title: "Code Interpreter", description: "Utilize e2b.dev for advanced code interpretation and execution." }, { icon: Cloud, title: "Weather Forecast", description: "Get accurate weather information via OpenWeatherMap." }, - { icon: Link, title: "URL Summary", description: "Summarize web content quickly with FireCrawl's Scrape API." }, - { icon: MapPin, title: "Location Search", description: "Find places and nearby locations using Google Maps API." }, - { icon: Mic, title: "Translation & TTS", description: "Translate text and convert to speech with OpenAI TTS." }, + { icon: YoutubeLogo, title: "Youtube Search", description: "Summarize web content quickly with FireCrawl's Scrape API." }, + { icon: XLogo, title: "Search X Posts", description: "Search for posts on X.com" }, + { icon: Newspaper, title: "Research Paper Search", description: "Search for research papers on arXiv and more" }, + { icon: MapPin, title: "Location Search", description: "Find places and nearby locations using Google Maps API, Mapbox and TripAdvisior API." }, + { icon: Mic, title: "Translation & TTS", description: "Translate text and convert to speech with Elevenlabs TTS and Microsoft's Translation API." }, + { icon: ShoppingBasket, title: "Product Search", description: "Search for products on Amazon." }, ] const containerVariants = { @@ -765,6 +770,15 @@ const LandingPage: React.FC = () => { className="h-12 w-auto" /> + + Uneed Embed Badge +
diff --git a/app/search/page.tsx b/app/search/page.tsx index a508a09..08569cd 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -16,9 +16,9 @@ import ReactMarkdown from 'react-markdown'; import { useTheme } from 'next-themes'; import Marked, { ReactRenderer } from 'marked-react'; import { track } from '@vercel/analytics'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { useSearchParams } from 'next/navigation'; import { useChat } from 'ai/react'; -import { Message, ToolInvocation } from 'ai'; +import { ToolInvocation } from 'ai'; import { toast } from 'sonner'; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; @@ -31,12 +31,10 @@ import { Wave } from "@foobar404/wave"; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneLight, oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { - SearchIcon, Sparkles, ArrowRight, Globe, AlignLeft, - Newspaper, Copy, Cloud, Code, @@ -56,11 +54,21 @@ import { TrendingUpIcon, Calendar, Calculator, - ImageIcon, ChevronDown, Edit2, ChevronUp, - Moon + Moon, + ShoppingBasket, + Star, + Truck, + YoutubeIcon, + LucideIcon, + PlayCircle, + FileText, + Book, + Eye, + ExternalLink, + Building } from 'lucide-react'; import { HoverCard, @@ -89,11 +97,13 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { GitHubLogoIcon, PlusCircledIcon } from '@radix-ui/react-icons'; +import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; +import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer"; +import { GitHubLogoIcon } from '@radix-ui/react-icons'; import Link from 'next/link'; import { Dialog, DialogContent } from "@/components/ui/dialog"; -import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"; -import { cn } from '@/lib/utils'; +import { Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel"; +import { cn, SearchGroupId } from '@/lib/utils'; import { Table, TableBody, @@ -104,9 +114,16 @@ import Autoplay from 'embla-carousel-autoplay'; import FormComponent from '@/components/ui/form-component'; import WeatherChart from '@/components/weather-chart'; import InteractiveChart from '@/components/interactive-charts'; -import NearbySearchMapView from '@/components/nearby-search-map-view'; import { MapComponent, MapContainer, MapSkeleton } from '@/components/map-components'; import MultiSearch from '@/components/multi-search'; +import { RedditLogo, RoadHorizon, XLogo } from '@phosphor-icons/react'; +import { BorderTrail } from '@/components/core/border-trail'; +import { TextShimmer } from '@/components/core/text-shimmer'; +import { Tweet } from 'react-tweet'; +import NearbySearchMapView from '@/components/nearby-search-map-view'; +import { Place } from '../../components/map-components'; +import { Separator } from '@/components/ui/separator'; +import { ChartTypes } from '@e2b/code-interpreter'; export const maxDuration = 60; @@ -117,11 +134,433 @@ interface Attachment { size: number; } +interface ShoppingProduct { + title: string; + url: string; + image: string; + price: string; + rating?: number; + reviewCount?: number; +} + +interface RedditResult { + title: string; + url: string; + subreddit?: string; + score?: number; +} + +interface XResult { + id: string; + url: string; + title: string; + author?: string; + publishedDate?: string; + text: string; + highlights?: string[]; + tweetId: string; +} + +interface YouTubeVideo { + title: string; + link: string; + snippet?: string; + imageUrl?: string; + duration?: string; + source?: string; + channel?: string; + date?: string; +} + +interface AcademicResult { + title: string; + url: string; + author?: string | null; + publishedDate?: string; + summary: string; +} + +interface YouTubeVideo { + videoId: string; + url: string; + title: string; + description?: string; + author?: string; + publishedDate?: string; + views?: string; + likes?: string; + subscribers?: string; + summary?: string; + thumbnail?: string; +} + + +/* +Mapbox API interfaces +*/ + +interface MapboxCoordinates { + longitude: number; + latitude: number; +} + +interface MapboxContext { + street?: { + mapbox_id: string; + name: string; + }; + postcode?: { + mapbox_id: string; + name: string; + }; + locality?: { + mapbox_id: string; + name: string; + wikidata_id?: string; + }; + place?: { + mapbox_id: string; + name: string; + wikidata_id?: string; + }; + district?: { + mapbox_id: string; + name: string; + wikidata_id?: string; + }; + region?: { + mapbox_id: string; + name: string; + wikidata_id?: string; + region_code?: string; + region_code_full?: string; + }; + country?: { + mapbox_id: string; + name: string; + wikidata_id?: string; + country_code: string; + country_code_alpha_3: string; + }; +} + +interface MapboxFeature { + id: string; + type: string; + geometry: { + type: string; + coordinates: [number, number]; // [longitude, latitude] + }; + properties: { + mapbox_id: string; + feature_type: 'street' | 'locality' | 'address' | string; + name: string; + name_preferred?: string; + full_address?: string; + coordinates: MapboxCoordinates; + place_formatted?: string; + bbox?: [number, number, number, number]; + context?: MapboxContext; + }; +} + +// Simplified feature interface for the UI +interface SimplifiedFeature { + id: string; + name: string; + formatted_address?: string; + geometry: { + type: string; + coordinates: [number, number]; + }; + context?: MapboxContext; + place_formatted?: string; + feature_type: string; + coordinates: MapboxCoordinates; + bbox?: [number, number, number, number]; +} + +/* +Mapbox API interfaces end +*/ + +// Updated SearchLoadingState with new colors and states +const SearchLoadingState = ({ + icon: Icon, + text, + color +}: { + icon: LucideIcon, + text: string, + color: "red" | "green" | "orange" | "violet" | "gray" | "blue" +}) => { + // Map of color variants + const colorVariants = { + red: { + background: "bg-red-50 dark:bg-red-950", + border: "from-red-200 via-red-500 to-red-200 dark:from-red-400 dark:via-red-500 dark:to-red-700", + text: "text-red-500", + icon: "text-red-500" + }, + green: { + background: "bg-green-50 dark:bg-green-950", + border: "from-green-200 via-green-500 to-green-200 dark:from-green-400 dark:via-green-500 dark:to-green-700", + text: "text-green-500", + icon: "text-green-500" + }, + orange: { + background: "bg-orange-50 dark:bg-orange-950", + border: "from-orange-200 via-orange-500 to-orange-200 dark:from-orange-400 dark:via-orange-500 dark:to-orange-700", + text: "text-orange-500", + icon: "text-orange-500" + }, + violet: { + background: "bg-violet-50 dark:bg-violet-950", + border: "from-violet-200 via-violet-500 to-violet-200 dark:from-violet-400 dark:via-violet-500 dark:to-violet-700", + text: "text-violet-500", + icon: "text-violet-500" + }, + gray: { + background: "bg-neutral-50 dark:bg-neutral-950", + border: "from-neutral-200 via-neutral-500 to-neutral-200 dark:from-neutral-400 dark:via-neutral-500 dark:to-neutral-700", + text: "text-neutral-500", + icon: "text-neutral-500" + }, + blue: { + background: "bg-blue-50 dark:bg-blue-950", + border: "from-blue-200 via-blue-500 to-blue-200 dark:from-blue-400 dark:via-blue-500 dark:to-blue-700", + text: "text-blue-500", + icon: "text-blue-500" + } + }; + + const variant = colorVariants[color]; + + return ( + + + +
+
+
+ + +
+
+ + {text} + +
+ {[...Array(3)].map((_, i) => ( +
+ ))} +
+
+
+
+ + + ); +}; + +// Base YouTube Types +interface VideoDetails { + title?: string; + author_name?: string; + author_url?: string; + thumbnail_url?: string; + type?: string; + provider_name?: string; + provider_url?: string; + height?: number; + width?: number; +} + +interface VideoResult { + videoId: string; + url: string; + details?: VideoDetails; + captions?: string; + timestamps?: string[]; + views?: string; + likes?: string; + summary?: string; +} + +interface YouTubeSearchResponse { + results: VideoResult[]; +} + +// UI Component Types +interface YouTubeCardProps { + video: VideoResult; + index: number; +} + + +const YouTubeCard: React.FC = ({ video, index }) => { + const [timestampsExpanded, setTimestampsExpanded] = useState(false); + const [transcriptExpanded, setTranscriptExpanded] = useState(false); + + if (!video) return null; + + return ( + + {/* Thumbnail */} + + {video.details?.thumbnail_url ? ( + {video.details?.title + ) : ( +
+ +
+ )} +
+ +
+ + +
+ {/* Title and Channel */} +
+ + {video.details?.title || 'YouTube Video'} + + + {video.details?.author_name && ( + +
+ +
+ + {video.details.author_name} + + + )} +
+ + {/* Interactive Sections */} + {(video.timestamps && video.timestamps?.length > 0 || video.captions) && ( +
+ + + {/* Timestamps */} + {video.timestamps && video.timestamps.length > 0 && ( +
+
+

Key Moments

+ +
+
+ {video.timestamps + .slice(0, timestampsExpanded ? undefined : 3) + .map((timestamp, i) => ( +
+ {timestamp} +
+ ))} +
+
+ )} + + {/* Transcript */} + {video.captions && ( + <> + {video.timestamps && video.timestamps!.length > 0 && } +
+
+

Transcript

+ +
+ {transcriptExpanded && ( +
+

+ {video.captions} +

+
+ )} +
+ + )} +
+ )} +
+
+ ); +}; + const HomeContent = () => { - const router = useRouter(); const searchParams = useSearchParams(); - const initialQuery = searchParams.get('query') || ''; - const initialModel = searchParams.get('model') || 'azure:gpt4o-mini'; // Memoize initial values to prevent re-calculation const initialState = useMemo(() => ({ @@ -140,15 +579,17 @@ const HomeContent = () => { const fileInputRef = useRef(null); const inputRef = useRef(null); const initializedRef = useRef(false); + const [selectedGroup, setSelectedGroup] = useState('web'); const { theme } = useTheme(); const [openChangelog, setOpenChangelog] = useState(false); - const { isLoading, input, messages, setInput, handleInputChange, append, handleSubmit, setMessages, reload, stop } = useChat({ + const { isLoading, input, messages, setInput, append, handleSubmit, setMessages, reload, stop } = useChat({ maxSteps: 10, body: { model: selectedModel, + group: selectedGroup, }, onFinish: async (message, { finishReason }) => { console.log("[finish reason]:", finishReason); @@ -521,40 +962,487 @@ The new Anthropic models: Claude 3.5 Sonnet and 3.5 Haiku models are now availab const args = JSON.parse(JSON.stringify(toolInvocation.args)); const result = 'result' in toolInvocation ? JSON.parse(JSON.stringify(toolInvocation.result)) : null; + // Find place results if (toolInvocation.toolName === 'find_place') { if (!result) { - return ( -
- -

Loading place information...

-
- ); + return ; } - const place = result.features[0]; - if (!place) return null; + const { features } = result; + if (!features || features.length === 0) return null; return ( -
- + {/* Map Container */} +
+
+ + {features.length} Locations Found + +
+ + ({ + name: feature.name, location: { - lat: place.geometry.coordinates[1], - lng: place.geometry.coordinates[0], + lat: feature.geometry.coordinates[1], + lng: feature.geometry.coordinates[0], }, - vicinity: place.formatted_address, - }, - ]} - zoom={15} - /> + vicinity: feature.formatted_address, + }))} + zoom={features.length > 1 ? 12 : 15} + /> +
+ + {/* Place Details Footer */} +
+ {features.map((place: any, index: any) => { + const isGoogleResult = place.source === 'google'; + + return ( +
+
+
+ {place.feature_type === 'street_address' || place.feature_type === 'street' ? ( + + ) : place.feature_type === 'locality' ? ( + + ) : ( + + )} +
+ +
+

+ {place.name} +

+ {place.formatted_address && ( +

+ {place.formatted_address} +

+ )} + + {place.feature_type.replace(/_/g, ' ')} + +
+ +
+ + + + + + Copy Coordinates + + + + + + + + + View in Maps + + +
+
+
+ ); + })} +
+ + ); + } + + // Shopping search results + if (toolInvocation.toolName === 'shopping_search') { + if (!result) { + return ; + } + + return ( + + +
+
+ +
+
+ Shopping Results +

Scroll to see more products

+
+
+
+ +
+ {result.map((product: ShoppingProduct) => ( + + +
+ {product.title} + {product.rating && ( +
+ + {product.rating} +
+ )} +
+ + + {product.title} + +

+ {product.price} +

+ +
+
+
+ ))} +
+
+
+ ); + } + + if (toolInvocation.toolName === 'x_search') { + if (!result) { + return ; + } + + const PREVIEW_COUNT = 3; + + // Shared content component + const FullTweetList = () => ( +
+ {result.map((post: XResult, index: number) => ( + + + + ))}
); + + return ( + + +
+
+ +
+
+ Latest from X +

+ {result.length} tweets found +

+
+
+
+
+
+
+ {result.slice(0, PREVIEW_COUNT).map((post: XResult, index: number) => ( + + + + ))} +
+
+ + {/* Gradient overlay */} +
+ + {/* Show More Buttons - Desktop Sheet */} +
+ {/* Desktop Sheet */} +
+ + + + + + + All Tweets + + + + +
+ + {/* Mobile Drawer */} +
+ + + + + + + All Tweets + +
+ +
+
+
+
+
+
+ + ); + } + + if (toolInvocation.toolName === 'youtube_search') { + if (!result) { + return ; + } + + const youtubeResult = result as YouTubeSearchResponse; + + return ( + + + +
+
+ +
+
+

+ YouTube Results +

+
+ + {youtubeResult.results.length} videos + +
+
+
+
+ + +
+ {youtubeResult.results.map((video, index) => ( + + ))} +
+
+
+
+ ); + } + + // Academic search results continued... + if (toolInvocation.toolName === 'academic_search') { + if (!result) { + return ; + } + + return ( + + +
+
+ +
+
+ Academic Papers +

Found {result.results.length} papers

+
+
+
+
+
+ {result.results.map((paper: AcademicResult, index: number) => ( + +
+ {/* Background with gradient border */} +
+ + {/* Main content container */} +
+ {/* Title */} +

+ {paper.title} +

+ + {/* Authors with better overflow handling */} + {paper.author && ( +
+
+ + + {paper.author.split(';') + .slice(0, 2) // Take first two authors + .join(', ') + + (paper.author.split(';').length > 2 ? ' et al.' : '') + } + +
+
+ )} + + {/* Date if available */} + {paper.publishedDate && ( +
+
+ + {new Date(paper.publishedDate).toLocaleDateString()} +
+
+ )} + + {/* Summary with gradient border */} +
+
+

+ {paper.summary} +

+
+ + {/* Actions */} +
+ + + {paper.url.includes('arxiv.org') && ( + + )} +
+
+
+ + ))} +
+
+ + ); } if (toolInvocation.toolName === 'nearby_search') { @@ -824,13 +1712,15 @@ The new Anthropic models: Claude 3.5 Sonnet and 3.5 Haiku models are now availab {result?.chart && ( )} @@ -1337,6 +2227,8 @@ The new Anthropic models: Claude 3.5 Sonnet and 3.5 Haiku models are now availab setSelectedModel={handleModelChange} resetSuggestedQuestions={resetSuggestedQuestions} lastSubmittedQueryRef={lastSubmittedQueryRef} + selectedGroup={selectedGroup} + setSelectedGroup={setSelectedGroup} />
@@ -1418,7 +2310,7 @@ The new Anthropic models: Claude 3.5 Sonnet and 3.5 Haiku models are now availab )} )} - {message.role === 'assistant' && message.content && ( + {message.role === 'assistant' && message.content !== null && !message.toolInvocations && (
@@ -1499,6 +2391,8 @@ The new Anthropic models: Claude 3.5 Sonnet and 3.5 Haiku models are now availab setSelectedModel={handleModelChange} resetSuggestedQuestions={resetSuggestedQuestions} lastSubmittedQueryRef={lastSubmittedQueryRef} + selectedGroup={selectedGroup} + setSelectedGroup={setSelectedGroup} /> )} diff --git a/components/core/border-trail.tsx b/components/core/border-trail.tsx new file mode 100644 index 0000000..c977ec5 --- /dev/null +++ b/components/core/border-trail.tsx @@ -0,0 +1,48 @@ +'use client'; +import { cn } from '@/lib/utils'; +import { motion, Transition } from 'motion/react'; + +type BorderTrailProps = { + className?: string; + size?: number; + transition?: Transition; + delay?: number; + onAnimationComplete?: () => void; + style?: React.CSSProperties; +}; + +export function BorderTrail({ + className, + size = 60, + transition, + delay, + onAnimationComplete, + style, +}: BorderTrailProps) { + const BASE_TRANSITION = { + repeat: Infinity, + duration: 5, + ease: 'linear', + }; + + return ( +
+ +
+ ); +} diff --git a/components/core/text-shimmer.tsx b/components/core/text-shimmer.tsx new file mode 100644 index 0000000..a729c16 --- /dev/null +++ b/components/core/text-shimmer.tsx @@ -0,0 +1,55 @@ +'use client'; +import React, { useMemo, type JSX } from 'react'; +import { motion } from 'motion/react'; +import { cn } from '@/lib/utils'; + +interface TextShimmerProps { + children: string; + as?: React.ElementType; + className?: string; + duration?: number; + spread?: number; +} + +export function TextShimmer({ + children, + as: Component = 'p', + className, + duration = 2, + spread = 2, +}: TextShimmerProps) { + const MotionComponent = motion.create( + Component as keyof JSX.IntrinsicElements + ); + + const dynamicSpread = useMemo(() => { + return children.length * spread; + }, [children, spread]); + + return ( + + {children} + + ); +} diff --git a/components/interactive-charts.tsx b/components/interactive-charts.tsx index 9c17f36..af52832 100644 --- a/components/interactive-charts.tsx +++ b/components/interactive-charts.tsx @@ -1,219 +1,141 @@ -// components/interactive-charts.tsx import React from 'react'; -import ReactECharts from 'echarts-for-react'; -import * as echarts from 'echarts'; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import ReactECharts, { EChartsOption } from 'echarts-for-react'; +import { Card, CardContent } from "@/components/ui/card"; import { useTheme } from 'next-themes'; -interface BaseChartProps { +interface BaseChart { + type: string; title: string; - xLabel?: string; - yLabel?: string; - xUnit?: string | null; - yUnit?: string | null; + x_label?: string; + y_label?: string; + x_unit?: string; + y_unit?: string; x_ticks?: (number | string)[]; x_tick_labels?: string[]; x_scale?: string; y_ticks?: (number | string)[]; y_tick_labels?: string[]; y_scale?: string; + elements: any[]; } -type BarChartElement = { - label: string; - group: string; - value: number; -}; - -type PointData = { - label: string; - points: [number | string, number | string][]; -}; - -interface ChartProps extends BaseChartProps { - type: 'bar' | 'line'; - elements: BarChartElement[] | PointData[]; -} - -const InteractiveChart: React.FC = ({ - type, - title, - xLabel = '', - yLabel = '', - xUnit = '', - yUnit = '', - elements, - x_ticks, - x_tick_labels, - x_scale = 'value', - y_ticks, - y_tick_labels, - y_scale = 'value', -}) => { +export function InteractiveChart({ chart }: { chart: BaseChart }) { const { theme } = useTheme(); + const textColor = theme === 'dark' ? '#e5e5e5' : '#171717'; + const gridColor = theme === 'dark' ? '#404040' : '#e5e5e5'; - const getChartOptions = () => { - if (!elements || elements.length === 0) { - return {}; - } - - const textColor = theme === 'dark' ? '#e5e5e5' : '#171717'; - const axisLineColor = theme === 'dark' ? '#404040' : '#e5e5e5'; - - if (type === 'line') { - const lineElements = elements as PointData[]; - - const xAxisType = x_scale === 'datetime' ? 'time' : x_scale; - - const series = lineElements.map(el => ({ - name: el.label, - type: 'line', - data: el.points.map(point => { - const xValue = - xAxisType === 'time' - ? new Date(point[0] as string).getTime() - : point[0]; - return [xValue, point[1]]; - }), - smooth: true, - })); - - return { - title: { - text: title || '', - textStyle: { color: textColor }, - }, - tooltip: { - trigger: 'axis', - axisPointer: { - type: 'cross', - }, - }, - legend: { - data: lineElements.map(el => el.label), - textStyle: { color: textColor }, - }, - xAxis: { - type: xAxisType, - name: xLabel, - nameTextStyle: { color: textColor }, - axisLabel: { - color: textColor, - formatter: (value: number) => { - if (xAxisType === 'time') { - return echarts.format.formatTime('yyyy-MM', value); - } - return xUnit ? `${value} ${xUnit}` : `${value}`; - }, - }, - axisLine: { lineStyle: { color: axisLineColor } }, - }, - yAxis: { - type: y_scale, - name: yLabel, - nameTextStyle: { color: textColor }, - axisLabel: { - color: textColor, - formatter: (value: number) => - yUnit ? `${value} ${yUnit}` : `${value}`, - }, - axisLine: { lineStyle: { color: axisLineColor } }, - ...(y_ticks && { - min: Math.min(...(y_ticks as number[])), - max: Math.max(...(y_ticks as number[])), - }), - }, - series, - }; - } else if (type === 'bar') { - const barElements = elements as BarChartElement[]; - - const groups = Array.from(new Set(barElements.map(el => el.group))).filter(Boolean); - const labels = Array.from(new Set(barElements.map(el => el.label))).filter(Boolean); - - const series = groups.map(group => ({ - name: group, - type: 'bar', - data: labels.map(label => { - const el = barElements.find(e => e.group === group && e.label === label); - return el ? el.value : 0; - }), - })); - - return { - title: { - text: title || '', - textStyle: { color: textColor }, - }, - tooltip: { trigger: 'axis' }, - legend: { - data: groups, - textStyle: { color: textColor }, - }, - xAxis: { - type: 'category', - data: labels, - name: xLabel, - nameTextStyle: { color: textColor }, - axisLabel: { color: textColor }, - axisLine: { lineStyle: { color: axisLineColor } }, - }, - yAxis: { - type: 'value', - name: yLabel, - nameTextStyle: { color: textColor }, - axisLabel: { - color: textColor, - formatter: (value: number) => - yUnit ? `${value} ${yUnit}` : value.toString(), - }, - axisLine: { lineStyle: { color: axisLineColor } }, - ...(y_ticks && { - min: Math.min(...(y_ticks as number[])), - max: Math.max(...(y_ticks as number[])), - }), - }, - series, - }; - } - - return {}; + const sharedOptions: EChartsOption = { + grid: { top: 30, right: 8, bottom: 28, left: 28 }, + legend: {} }; - const options = getChartOptions(); + if (chart.type === 'line') { + const series = chart.elements.map((e) => { + return { + name: e.label, + type: 'line', + data: e.points.map((p: [number, number]) => [p[0], p[1]]) + }; + }); + + const options: EChartsOption = { + ...sharedOptions, + xAxis: { + type: chart.x_scale === 'datetime' ? 'time' : 'category', + name: chart.x_label, + nameLocation: 'middle', + axisLabel: { + color: textColor, + formatter: (value: number) => { + if (chart.x_scale === 'datetime') { + return new Date(value).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric' + }); + } + return chart.x_unit ? `${value}${chart.x_unit}` : `${value}`; + } + }, + axisLine: { lineStyle: { color: gridColor } } + }, + yAxis: { + name: chart.y_label, + nameLocation: 'middle', + axisLabel: { + color: textColor, + formatter: (value: number) => + chart.y_unit ? `${chart.y_unit}${value}` : value.toString() + }, + axisLine: { lineStyle: { color: gridColor } } + }, + series, + tooltip: { + trigger: 'axis' + } + }; - if (!options || Object.keys(options).length === 0) { return ( - - - - {title || 'Chart'} - - - -
- No data available to display the chart. -
+ + + ); } - return ( - - - - {title || 'Chart'} - - - - - - - ); -}; + if (chart.type === 'bar') { + const data = Object.groupBy(chart.elements, ({ group }) => group); + + const series = Object.entries(data).map(([group, elements]) => ({ + name: group, + type: 'bar', + stack: 'total', + data: elements?.map((e) => [e.label, e.value]) + })); + + const options: EChartsOption = { + ...sharedOptions, + xAxis: { + type: 'category', + name: chart.x_label, + nameLocation: 'middle', + axisLabel: { color: textColor }, + axisLine: { lineStyle: { color: gridColor } } + }, + yAxis: { + name: chart.y_label, + nameLocation: 'middle', + axisLabel: { + color: textColor, + formatter: (value: number) => + chart.y_unit ? `${chart.y_unit}${value}` : value.toString() + }, + axisLine: { lineStyle: { color: gridColor } } + }, + series, + tooltip: { + trigger: 'axis' + } + }; + + return ( + + + + + + ); + } + + return null; +} export default InteractiveChart; \ No newline at end of file diff --git a/components/map-components.tsx b/components/map-components.tsx index 995de5b..c059fa2 100644 --- a/components/map-components.tsx +++ b/components/map-components.tsx @@ -21,6 +21,7 @@ interface MapProps { center: Location; places?: Place[]; zoom?: number; + onMarkerClick?: (place: Place) => void; } const MapComponent = ({ center, places = [], zoom = 14, onMarkerClick }: MapProps & { onMarkerClick?: (place: Place) => void }) => { @@ -28,10 +29,8 @@ const MapComponent = ({ center, places = [], zoom = 14, onMarkerClick }: MapProp const mapInstance = useRef(null); const markersRef = useRef([]); - // Initialize the map only once useEffect(() => { if (!mapRef.current || mapInstance.current) return; - if (!mapboxgl.accessToken) { console.error('Mapbox access token is not set'); return; @@ -39,44 +38,70 @@ const MapComponent = ({ center, places = [], zoom = 14, onMarkerClick }: MapProp mapInstance.current = new mapboxgl.Map({ container: mapRef.current, - style: 'mapbox://styles/mapbox/standard', + style: 'mapbox://styles/mapbox/dark-v11', center: [center.lng, center.lat], zoom, + attributionControl: false, }); + // Add zoom and rotation controls + mapInstance.current.addControl(new mapboxgl.NavigationControl(), 'top-right'); + + // Add attribution control in bottom-left + mapInstance.current.addControl( + new mapboxgl.AttributionControl({ compact: true }), + 'bottom-left' + ); + + // Add fullscreen control + mapInstance.current.addControl(new mapboxgl.FullscreenControl(), 'top-right'); + return () => { mapInstance.current?.remove(); mapInstance.current = null; }; }, [center.lat, center.lng, zoom]); - // Update map center when 'center' prop changes useEffect(() => { if (mapInstance.current) { mapInstance.current.flyTo({ center: [center.lng, center.lat], zoom, essential: true, + duration: 1000, + padding: { top: 50, bottom: 50, left: 50, right: 50 } }); } }, [center, zoom]); - // Update markers when 'places' prop changes useEffect(() => { if (!mapInstance.current) return; - // Remove existing markers markersRef.current.forEach((marker) => marker.remove()); markersRef.current = []; - // Add new markers places.forEach((place) => { - const marker = new mapboxgl.Marker() + // Create custom marker element + const el = document.createElement('div'); + el.className = 'custom-marker'; + el.innerHTML = ` +
+ + + +
+ `; + + const marker = new mapboxgl.Marker(el) .setLngLat([place.location.lng, place.location.lat]) .setPopup( - new mapboxgl.Popup({ offset: 25 }).setText( - `${place.name}${place.vicinity ? `\n${place.vicinity}` : ''}` - ) + new mapboxgl.Popup({ offset: 25, closeButton: false }) + .setHTML(` +
+

${place.name}

+ ${place.vicinity ? `

${place.vicinity}

` : ''} +
+ `) ) .addTo(mapInstance.current!); @@ -91,7 +116,7 @@ const MapComponent = ({ center, places = [], zoom = 14, onMarkerClick }: MapProp }, [places, onMarkerClick]); return ( -
+
); diff --git a/components/multi-search.tsx b/components/multi-search.tsx index ffcf5ec..055f00d 100644 --- a/components/multi-search.tsx +++ b/components/multi-search.tsx @@ -3,10 +3,16 @@ import React, { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { ArrowUpRight, Calendar, ChevronLeft, ChevronRight, Clock, Globe, ImageIcon, Newspaper, Search, X } from 'lucide-react'; -import { ScrollArea } from '@/components/ui/scroll-area'; +import { cn } from '@/lib/utils'; type SearchImage = { url: string; @@ -34,7 +40,7 @@ type MultiSearchResponse = { type MultiSearchArgs = { queries: string[]; maxResults: number[]; - topic: ("general" | "news")[]; + topics: ("general" | "news")[]; searchDepth: ("basic" | "advanced")[]; }; @@ -147,36 +153,46 @@ const ImageGrid: React.FC<{ images: SearchImage[]; onImageClick: (index: number) const SearchResults: React.FC = ({ searchData, topicType, onImageClick }) => (
-
-
-
+ + +
- +
-

Results for “{searchData.query}“

+

+ Results for {searchData.query} +

{topicType} - {searchData.results.length} results + + {searchData.results.length} results +
-
-
- - -
- {searchData.results.map((result, index) => ( - - ))} -
-
-
+ + +
+ {searchData.results.map((result, index) => ( + + ))} +
+
+ + + {/* Original Image Results Section */} {searchData.images.length > 0 && (
@@ -191,66 +207,6 @@ const SearchResults: React.FC = ({ searchData, topicType, on
); -interface ContentDialogProps { - isOpen: boolean; - onClose: () => void; - result: SearchResult; -} - -const ContentDialog: React.FC = ({ isOpen, onClose, result }) => ( - - -
-
-
- -
- -
- -
-
-

- {result.content} -

-
- {result.published_date && ( -
- - -
- )} -
-
-
-
-
-); - - const MultiSearch: React.FC<{ result: MultiSearchResponse | null; args: MultiSearchArgs }> = ({ result, args }) => { const [activeTab, setActiveTab] = useState("0"); const [galleryOpen, setGalleryOpen] = useState(false); @@ -325,7 +281,7 @@ const MultiSearch: React.FC<{ result: MultiSearchResponse | null; args: MultiSea { setSelectedSearch(index); setSelectedImage(imageIndex); diff --git a/components/search-groups.tsx b/components/search-groups.tsx new file mode 100644 index 0000000..6521833 --- /dev/null +++ b/components/search-groups.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import { Button } from "@/components/ui/button" +import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { searchGroups, type SearchGroup } from '@/lib/utils' +import { cn } from '@/lib/utils' + +interface SearchGroupsProps { + selectedGroup: string + onGroupSelect: (group: SearchGroup) => void +} + +export function SearchGroups({ selectedGroup, onGroupSelect }: SearchGroupsProps) { + return ( +
+ {searchGroups.map((group) => { + const Icon = group.icon + const isSelected = selectedGroup === group.id + + return ( + onGroupSelect(group)} + > + +
+
+ +
+
+ {group.name} + + {group.description} + +
+
+
+
+ ) + })} +
+ ) +} \ No newline at end of file diff --git a/components/shopping-cards.tsx b/components/shopping-cards.tsx new file mode 100644 index 0000000..9d01ab2 --- /dev/null +++ b/components/shopping-cards.tsx @@ -0,0 +1,223 @@ +"use client"; +/* eslint-disable @next/next/no-img-element */ +import { motion, PanInfo, useMotionValue, useTransform } from "framer-motion"; +import { Badge } from "./ui/badge"; +import { Heart, Star, X } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { Button } from "./ui/button"; +import { Card, CardContent } from "./ui/card"; + + +interface ShoppingProduct { + id: string | number; + title: string; + price: string; + originalPrice?: string; + currency: string; + image: string; + link: string; + source: string; + rating?: string | null; + reviewCount?: string | null; + delivery: string; +} + +interface CardRotateProps { + children: React.ReactNode; + onSendToBack: () => void; + onSwipe?: (direction: 'left' | 'right') => void; +} + +function CardRotate({ children, onSendToBack, onSwipe }: CardRotateProps) { + const x = useMotionValue(0); + const y = useMotionValue(0); + // Reduced rotation values for more subtle effect + const rotateX = useTransform(y, [-100, 100], [15, -15]); + const rotateY = useTransform(x, [-100, 100], [-15, 15]); + + function handleDragEnd(_: any, info: PanInfo) { + const threshold = 100; + if (Math.abs(info.offset.x) > threshold) { + onSendToBack(); + if (onSwipe) { + onSwipe(info.offset.x > 0 ? 'right' : 'left'); + } + } else { + x.set(0); + y.set(0); + } + } + + return ( + + {children} + + ); +} + +const ProductCard = ({ product }: { product: ShoppingProduct }) => { + const formattedPrice = parseFloat(product.price).toFixed(2); + const formattedOriginalPrice = product.originalPrice ? parseFloat(product.originalPrice).toFixed(2) : null; + const discount = formattedOriginalPrice ? + Math.round(((parseFloat(formattedOriginalPrice) - parseFloat(formattedPrice)) / parseFloat(formattedOriginalPrice)) * 100) : null; + + return ( +
+
+ {product.title} + {discount && discount > 0 && ( + + {discount}% OFF + + )} +
+
+
+

+ {product.title} +

+
+ + ${formattedPrice} + + {formattedOriginalPrice && ( + + ${formattedOriginalPrice} + + )} +
+
+
+
+ {product.rating && ( + <> + + + {product.rating} {product.reviewCount && `(${product.reviewCount})`} + + + )} +
+ + {product.source} + +
+
+
+ ); +}; + +export const SwipeableProductStack = ({ products }: { products: ShoppingProduct[] }) => { + const [cards, setCards] = useState(products); + const [savedProducts, setSavedProducts] = useState([]); + + const sendToBack = (id: string | number, direction?: 'left' | 'right') => { + setCards((prev) => { + const newCards = [...prev]; + const index = newCards.findIndex((card) => card.id === id); + const [card] = newCards.splice(index, 1); + + if (direction === 'right') { + setSavedProducts(prev => [...prev, card]); + toast.success('Product saved!'); + } + + newCards.unshift(card); + return newCards; + }); + }; + + return ( +
+
+ {cards.map((product, index) => ( + sendToBack(product.id)} + onSwipe={(direction) => sendToBack(product.id, direction)} + > + + + + + ))} +
+ +
+ + +
+ + {savedProducts.length > 0 && ( +
+

+ Saved Products ({savedProducts.length}) +

+
+ {savedProducts.map((product) => ( + + + {product.title} +
{product.title}
+
+ ${parseFloat(product.price).toFixed(2)} +
+ +
+
+ ))} +
+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx new file mode 100644 index 0000000..6a0ef53 --- /dev/null +++ b/components/ui/drawer.tsx @@ -0,0 +1,118 @@ +"use client" + +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) => ( + +) +Drawer.displayName = "Drawer" + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerPortal = DrawerPrimitive.Portal + +const DrawerClose = DrawerPrimitive.Close + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = "DrawerContent" + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerHeader.displayName = "DrawerHeader" + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerFooter.displayName = "DrawerFooter" + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerTitle.displayName = DrawerPrimitive.Title.displayName + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/components/ui/form-component.tsx b/components/ui/form-component.tsx index bf7877e..9e6f1dd 100644 --- a/components/ui/form-component.tsx +++ b/components/ui/form-component.tsx @@ -7,15 +7,18 @@ import { track } from '@vercel/analytics'; import { toast } from 'sonner'; import { Button } from '../ui/button'; import { Textarea } from '../ui/textarea'; +import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'; import useWindowSize from '@/hooks/use-window-size'; -import { Sparkles, X, Zap, Cpu } from 'lucide-react'; +import { Sparkles, X, Zap, Cpu, Search, ChevronDown, Check, Atom } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { cn } from '@/lib/utils'; +import { cn, SearchGroup, SearchGroupId, searchGroups } from '@/lib/utils'; +import { useMediaQuery } from '@/hooks/use-media-query'; +import { XLogo } from '@phosphor-icons/react'; interface ModelSwitcherProps { selectedModel: string; @@ -26,6 +29,7 @@ interface ModelSwitcherProps { const models = [ { value: "azure:gpt4o-mini", label: "GPT-4o Mini", icon: Zap, description: "God speed, good quality", color: "emerald", vision: true }, { value: "anthropic:claude-3-5-haiku-20241022", label: "Claude 3.5 Haiku", icon: Sparkles, description: "Good quality, high speed", color: "orange", vision: false }, + { value: "xai:grok-2-vision-1212", label: "Grok 2.0 Vision", icon: XLogo, description: "Good quality, normal speed", color: "glossyblack", vision: true }, { value: "anthropic:claude-3-5-sonnet-latest", label: "Claude 3.5 Sonnet (New)", icon: Sparkles, description: "High quality, good speed", color: "indigo", vision: true }, { value: "azure:gpt-4o", label: "GPT-4o", icon: Cpu, description: "Higher quality, normal speed", color: "blue", vision: true }, ]; @@ -52,6 +56,10 @@ const getColorClasses = (color: string, isSelected: boolean = false) => { return isSelected ? `${baseClasses} ${selectedClasses} !bg-orange-500 dark:!bg-orange-600 !text-white hover:!bg-orange-600 dark:hover:!bg-orange-700` : `${baseClasses} !text-orange-700 dark:!text-orange-300 hover:!bg-orange-200 dark:hover:!bg-orange-800/70`; + case 'glossyblack': + return isSelected + ? `${baseClasses} ${selectedClasses} bg-gradient-to-br from-black to-neutral-800 !text-white shadow-inner` + : `${baseClasses} !text-black dark:!text-white hover:!bg-black/10 dark:hover:!bg-black/40`; default: return isSelected ? `${baseClasses} ${selectedClasses} !bg-neutral-500 dark:!bg-neutral-600 !text-white hover:!bg-neutral-600 dark:hover:!bg-neutral-700` @@ -175,34 +183,6 @@ const hasVisionSupport = (modelValue: string): boolean => { return selectedModel?.vision === true }; -interface FormComponentProps { - input: string; - setInput: (input: string) => void; - attachments: Array; - setAttachments: React.Dispatch>>; - hasSubmitted: boolean; - setHasSubmitted: (value: boolean) => void; - isLoading: boolean; - handleSubmit: ( - event?: { - preventDefault?: () => void; - }, - chatRequestOptions?: ChatRequestOptions, - ) => void; - fileInputRef: React.RefObject; - inputRef: React.RefObject; - stop: () => void; - messages: Array; - append: ( - message: Message | CreateMessage, - chatRequestOptions?: ChatRequestOptions, - ) => Promise; - selectedModel: string; - setSelectedModel: (value: string) => void; - resetSuggestedQuestions: () => void; - lastSubmittedQueryRef: React.MutableRefObject; -} - const AttachmentPreview: React.FC<{ attachment: Attachment | UploadingAttachment, onRemove: () => void, isUploading: boolean }> = ({ attachment, onRemove, isUploading }) => { const formatFileSize = (bytes: number): string => { if (bytes < 1024) return bytes + ' bytes'; @@ -295,6 +275,339 @@ interface UploadingAttachment { progress: number; } +interface FormComponentProps { + input: string; + setInput: (input: string) => void; + attachments: Array; + setAttachments: React.Dispatch>>; + hasSubmitted: boolean; + setHasSubmitted: (value: boolean) => void; + isLoading: boolean; + handleSubmit: ( + event?: { + preventDefault?: () => void; + }, + chatRequestOptions?: ChatRequestOptions, + ) => void; + fileInputRef: React.RefObject; + inputRef: React.RefObject; + stop: () => void; + messages: Array; + append: ( + message: Message | CreateMessage, + chatRequestOptions?: ChatRequestOptions, + ) => Promise; + selectedModel: string; + setSelectedModel: (value: string) => void; + resetSuggestedQuestions: () => void; + lastSubmittedQueryRef: React.MutableRefObject; + selectedGroup: SearchGroupId; + setSelectedGroup: React.Dispatch>; +} + +// Add this component either in a new file or in your form component +interface GroupSelectorProps { + selectedGroup: SearchGroupId; + onGroupSelect: (group: SearchGroup) => void; +} + +const themeColors: Record = { + web: { + bg: '!bg-white hover:!bg-cyan-50 dark:!bg-neutral-900/40 dark:hover:!bg-cyan-950/40', + bgHover: 'hover:!border-cyan-200 dark:hover:!border-cyan-500/30', + bgSelected: '!bg-cyan-50 dark:!bg-cyan-950/40 !border-cyan-500 dark:!border-cyan-400', + text: '!text-cyan-600 dark:!text-cyan-400', + description: '!text-neutral-600 dark:!text-neutral-500', + focus: 'focus:!ring-cyan-500 dark:focus:!ring-cyan-400' + }, + academic: { + bg: '!bg-white hover:!bg-violet-50 dark:!bg-neutral-900/40 dark:hover:!bg-violet-950/40', + bgHover: 'hover:!border-violet-200 dark:hover:!border-violet-500/30', + bgSelected: '!bg-violet-50 dark:!bg-violet-950/40 !border-violet-500 dark:!border-violet-400', + text: '!text-violet-600 dark:!text-violet-400', + description: '!text-neutral-600 dark:!text-neutral-500', + focus: 'focus:!ring-violet-500 dark:focus:!ring-violet-400' + }, + youtube: { + bg: '!bg-white hover:!bg-red-50 dark:!bg-neutral-900/40 dark:hover:!bg-red-950/40', + bgHover: 'hover:!border-red-200 dark:hover:!border-red-500/30', + bgSelected: '!bg-red-50 dark:!bg-red-950/40 !border-red-500 dark:!border-red-400', + text: '!text-red-600 dark:!text-red-400', + description: '!text-neutral-600 dark:!text-neutral-500', + focus: 'focus:!ring-red-500 dark:focus:!ring-red-400' + }, + x: { + bg: '!bg-white hover:!bg-neutral-50 dark:!bg-neutral-900/40 dark:hover:!bg-neutral-800/40', + bgHover: 'hover:!border-neutral-300 dark:hover:!border-neutral-600/30', + bgSelected: '!bg-neutral-50 dark:!bg-neutral-800/40 !border-neutral-500 dark:!border-neutral-400', + text: '!text-neutral-900 dark:!text-neutral-100', + description: '!text-neutral-600 dark:!text-neutral-500', + focus: 'focus:!ring-neutral-500 dark:focus:!ring-neutral-400' + }, + shopping: { + bg: '!bg-white hover:!bg-green-50 dark:!bg-neutral-900/40 dark:hover:!bg-green-950/40', + bgHover: 'hover:!border-green-200 dark:hover:!border-green-500/30', + bgSelected: '!bg-green-50 dark:!bg-green-950/40 !border-green-500 dark:!border-green-400', + text: '!text-green-600 dark:!text-green-400', + description: '!text-neutral-600 dark:!text-neutral-500', + focus: 'focus:!ring-green-500 dark:focus:!ring-green-400' + }, + writing: { + bg: '!bg-white hover:!bg-blue-50 dark:!bg-neutral-900/40 dark:hover:!bg-blue-950/40', + bgHover: 'hover:!border-blue-200 dark:hover:!border-blue-500/30', + bgSelected: '!bg-blue-50 dark:!bg-blue-950/40 !border-blue-500 dark:!border-blue-400', + text: '!text-blue-600 dark:!text-blue-400', + description: '!text-neutral-600 dark:!text-neutral-500', + focus: 'focus:!ring-blue-500 dark:focus:!ring-blue-400' + } +}; + +const DrawerSelectionContent = ({ + selectedGroup, + onGroupSelect +}: { + selectedGroup: SearchGroupId, + onGroupSelect: (group: SearchGroup) => void +}) => ( +
+ {searchGroups.map((group) => { + const Icon = group.icon; + const isSelected = selectedGroup === group.id; + const groupColors = themeColors[group.id]; + + return ( +
+ +
+ ); + })} +
+); + +const DropdownSelectionContent = ({ + selectedGroup, + onGroupSelect +}: { + selectedGroup: SearchGroupId, + onGroupSelect: (group: SearchGroup) => void +}) => ( +
+ {searchGroups.map((group) => { + const Icon = group.icon; + const isSelected = selectedGroup === group.id; + const groupColors = themeColors[group.id]; + + return ( + onGroupSelect(group)} + className={cn( + "flex flex-col gap-2 p-4 rounded-lg cursor-pointer font-sans group/item", + "transition-all duration-200 relative overflow-hidden", + !isSelected && "border dark:border-neutral-800 border-neutral-200", + groupColors.bg, + groupColors.bgHover, + isSelected && cn( + "ring-1 dark:ring-white/20 ring-black/10", + "shadow-lg", + groupColors.bgSelected + ) + )} + > +
+ + + {group.name} + +
+

+ {group.description} +

+
+ + ); + })} +
+); + +const TriggerContent = ({ + selectedGroup, + isOpen +}: { + selectedGroup: SearchGroupId, + isOpen: boolean +}) => { + const selectedGroupDetails = searchGroups.find(g => g.id === selectedGroup); + const Icon = selectedGroupDetails?.icon; + const colors = themeColors[selectedGroup]; + + return ( +
+ {Icon && ( + + )} + + {selectedGroupDetails?.name} + + + +
+ ); +}; + +const GroupSelector = ({ selectedGroup, onGroupSelect }: GroupSelectorProps) => { + const [isOpen, setIsOpen] = useState(false); + const isDesktop = useMediaQuery("(min-width: 768px)"); + + const handleGroupSelection = (group: SearchGroup) => { + onGroupSelect(group); + setIsOpen(false); + }; + + if (!isDesktop) { + return ( + + + + + + + + Select Search Type + + +
+ +
+
+
+ ); + } + + return ( + + + + + + + + + + ); +}; + const FormComponent: React.FC = ({ input, setInput, @@ -313,6 +626,8 @@ const FormComponent: React.FC = ({ setSelectedModel, resetSuggestedQuestions, lastSubmittedQueryRef, + selectedGroup, + setSelectedGroup, }) => { const [uploadQueue, setUploadQueue] = useState>([]); const { width } = useWindowSize(); @@ -331,6 +646,14 @@ const FormComponent: React.FC = ({ setIsFocused(false); }; + const handleGroupSelect = useCallback((group: SearchGroup) => { + setSelectedGroup(group.id); + setInput(''); + resetSuggestedQuestions(); + inputRef.current?.focus(); + }, [setSelectedGroup, setInput, resetSuggestedQuestions, inputRef]); + + // Keep existing file upload and form submission logic... const uploadFile = async (file: File): Promise => { const formData = new FormData(); formData.append('file', file); @@ -392,7 +715,7 @@ const FormComponent: React.FC = ({ if (input.trim() || attachments.length > 0) { setHasSubmitted(true); lastSubmittedQueryRef.current = input.trim(); - track("search input", { query: input.trim() }) + track("search input", { query: input.trim() }); handleSubmit(event, { experimental_attachments: attachments, @@ -429,19 +752,20 @@ const FormComponent: React.FC = ({ } }, [attachments.length, hasSubmitted, fileInputRef]); - return ( +
0 || uploadQueue.length > 0 ? "bg-gray-100/70 dark:bg-neutral-800 p-1" : "bg-transparent" )}> - - + + {(attachments.length > 0 || uploadQueue.length > 0) && (
+ {/* Existing attachment previews */} {attachments.map((attachment, index) => ( = ({ "overflow-y-auto overflow-x-hidden", "text-base leading-relaxed", "bg-neutral-100 dark:bg-neutral-900", - "boder border-neutral-200 dark:border-neutral-700", + "border border-neutral-200 dark:border-neutral-700", "focus:border-neutral-300 dark:focus:border-neutral-600", "text-neutral-900 dark:text-neutral-100", - "focus:ring-2 focus:ring-neutral-300 dark:focus:ring-neutral-600", - "px-4 pt-3 pb-5" // Increased bottom padding to prevent overlap + "focus:!ring-1 focus:!ring-neutral-300 dark:focus:!ring-neutral-600", + "px-4 pt-3 pb-5" )} rows={3} onKeyDown={(event) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); if (isLoading) { - toast.error("Please wait for the model to finish its response!"); + toast.error("Please wait for the response to complete!"); } else { submitForm(); } @@ -499,15 +823,26 @@ const FormComponent: React.FC = ({ }} /> -
- +
+ {!hasSubmitted ? + + : null + } + +
{hasVisionSupport(selectedModel) && ( diff --git a/components/ui/separator.tsx b/components/ui/separator.tsx new file mode 100644 index 0000000..12d81c4 --- /dev/null +++ b/components/ui/separator.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx new file mode 100644 index 0000000..92506a4 --- /dev/null +++ b/components/ui/sheet.tsx @@ -0,0 +1,139 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" +import { Cross2Icon } from "@radix-ui/react-icons" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + + + Close + + {children} + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/hooks/use-media-query.tsx b/hooks/use-media-query.tsx new file mode 100644 index 0000000..ddcbdd7 --- /dev/null +++ b/hooks/use-media-query.tsx @@ -0,0 +1,18 @@ +// hooks/use-media-query.ts +import { useEffect, useState } from 'react' + +export function useMediaQuery(query: string) { + const [matches, setMatches] = useState(false) + + useEffect(() => { + const media = window.matchMedia(query) + if (media.matches !== matches) { + setMatches(media.matches) + } + const listener = () => setMatches(media.matches) + media.addEventListener('change', listener) + return () => media.removeEventListener('change', listener) + }, [matches, query]) + + return matches +} \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts index 1a860ee..a1136c7 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,80 @@ +// /lib/utils.ts import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" +import { Globe, Book, ShoppingBasket, YoutubeIcon, Pen } from 'lucide-react' +import { RedditLogo, XLogo } from '@phosphor-icons/react' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) -} \ No newline at end of file +} + +export type SearchGroupId = 'web' | 'academic' | 'shopping' | 'youtube' | 'x' | 'writing'; + +export const searchGroups = [ + { + id: 'web' as const, + name: 'Web', + description: 'Search across the entire internet', + icon: Globe, + }, + { + id: 'academic' as const, + name: 'Academic', + description: 'Search academic papers and research', + icon: Book, + }, + { + id: 'shopping' as const, + name: 'Shopping', + description: 'Find products and compare prices', + icon: ShoppingBasket, + }, + { + id: 'youtube' as const, + name: 'YouTube', + description: 'Search YouTube videos in real-time', + icon: YoutubeIcon, + }, + { + id: 'x' as const, + name: 'X', + description: 'Search X(Twitter) posts and content', + icon: XLogo, + }, + { + id: 'writing' as const, + name: 'Writing', + description: 'Chat or Talk without web search.', + icon: Pen, + } +] as const; + +export const groupTools = { + web: ['web_search', 'retrieve', 'programming'] as const, + academic: ['academic_search', 'retrieve', 'programming'] as const, + shopping: ['shopping_search', 'programming'] as const, + youtube: ['youtube_search'] as const, + x: ['x_search'] as const, + writing: [] as const, +} as const; + +export const groupPrompts = { + web: `You are an expert AI web search engine, that helps users find information on the internet. + Always start with running the search tool and then provide accurate, concise responses. + Format your response in clear paragraphs with citations.`, + academic: `You are an academic research assistant that helps find and analyze scholarly content. + Focus on peer-reviewed papers, citations, and academic sources. + Always include proper citations and summarize key findings.`, + shopping: `You are a shopping assistant that helps users find and compare products. + Focus on providing accurate pricing, product details, and merchant information. + Compare options and highlight key features and best values.`, + youtube: `You are a YouTube search assistant that helps find relevant videos and channels. + Provide video titles, channel names, view counts, and publish dates. + Summarize video content and highlight key moments.`, + reddit: `You are a Reddit content curator that helps find relevant posts and discussions. + Search across subreddits and provide post titles, vote counts, and comment highlights. + Summarize key discussions and community consensus.`, + writing: `You are a writing assistant that helps users with writing, conversation, or intellectual topics.`, +} as const; + +export type SearchGroup = typeof searchGroups[number]; diff --git a/next.config.mjs b/next.config.mjs index 348d7bc..6cbb0ca 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -56,6 +56,13 @@ const nextConfig = { port: '', pathname: '**' }, + // www.uneed.best + { + protocol: 'https', + hostname: 'www.uneed.best', + port: '', + pathname: '**' + }, ] }, }; diff --git a/package.json b/package.json index e5c11a8..dd1d5db 100644 --- a/package.json +++ b/package.json @@ -9,26 +9,28 @@ "lint": "next lint" }, "dependencies": { - "@ai-sdk/anthropic": "^0.0.55", - "@ai-sdk/azure": "^0.0.51", + "@ai-sdk/anthropic": "^1.0.5", + "@ai-sdk/azure": "^1.0.10", "@ai-sdk/cohere": "^1.0.3", - "@ai-sdk/google": "^0.0.55", + "@ai-sdk/google": "^1.0.7", "@ai-sdk/groq": "^0.0.1", "@ai-sdk/mistral": "^0.0.41", "@ai-sdk/openai": "^0.0.58", - "@ai-sdk/xai": "^1.0.3", + "@ai-sdk/xai": "^1.0.6", "@e2b/code-interpreter": "^1.0.3", "@foobar404/wave": "^2.0.5", "@mendable/firecrawl-js": "^1.4.3", + "@phosphor-icons/react": "^2.1.7", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-collapsible": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-scroll-area": "^1.1.0", + "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.0", @@ -43,7 +45,7 @@ "@vercel/analytics": "^1.3.1", "@vercel/blob": "^0.23.4", "@vercel/functions": "^1.4.0", - "ai": "^3.4.33", + "ai": "^4.0.17", "anthropic-vertex-ai": "^1.0.0", "cheerio": "^1.0.0", "class-variance-authority": "^0.7.0", @@ -53,6 +55,7 @@ "echarts-for-react": "^3.0.2", "embla-carousel-autoplay": "^8.3.0", "embla-carousel-react": "^8.3.0", + "exa-js": "^1.3.3", "framer-motion": "^11.3.19", "google-auth-library": "^9.14.1", "highlight.js": "^11.10.0", @@ -61,7 +64,8 @@ "luxon": "^3.5.0", "mapbox-gl": "^3.7.0", "marked-react": "^2.0.0", - "next": "^14.2.17", + "motion": "^11.13.5", + "next": "^14.2.20", "next-themes": "^0.3.0", "openai": "^4.56.0", "react": "^18", @@ -79,7 +83,8 @@ "tailwindcss-animate": "^1.0.7", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", - "zod": "^3.23.8" + "vaul": "^1.1.1", + "zod": "^3.24.1" }, "devDependencies": { "@types/google.maps": "^3.55.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4da7c7b..020cc01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,29 +6,29 @@ settings: dependencies: '@ai-sdk/anthropic': - specifier: ^0.0.55 - version: 0.0.55(zod@3.23.8) + specifier: ^1.0.5 + version: 1.0.5(zod@3.24.1) '@ai-sdk/azure': - specifier: ^0.0.51 - version: 0.0.51(zod@3.23.8) + specifier: ^1.0.10 + version: 1.0.10(zod@3.24.1) '@ai-sdk/cohere': specifier: ^1.0.3 - version: 1.0.3(zod@3.23.8) + version: 1.0.3(zod@3.24.1) '@ai-sdk/google': - specifier: ^0.0.55 - version: 0.0.55(zod@3.23.8) + specifier: ^1.0.7 + version: 1.0.7(zod@3.24.1) '@ai-sdk/groq': specifier: ^0.0.1 - version: 0.0.1(zod@3.23.8) + version: 0.0.1(zod@3.24.1) '@ai-sdk/mistral': specifier: ^0.0.41 - version: 0.0.41(zod@3.23.8) + version: 0.0.41(zod@3.24.1) '@ai-sdk/openai': specifier: ^0.0.58 - version: 0.0.58(zod@3.23.8) + version: 0.0.58(zod@3.24.1) '@ai-sdk/xai': - specifier: ^1.0.3 - version: 1.0.3(zod@3.23.8) + specifier: ^1.0.6 + version: 1.0.6(zod@3.24.1) '@e2b/code-interpreter': specifier: ^1.0.3 version: 1.0.3 @@ -38,6 +38,9 @@ dependencies: '@mendable/firecrawl-js': specifier: ^1.4.3 version: 1.5.3(ws@8.18.0) + '@phosphor-icons/react': + specifier: ^2.1.7 + version: 2.1.7(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-accordion': specifier: ^1.2.0 version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) @@ -45,7 +48,7 @@ dependencies: specifier: ^1.1.1 version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-dialog': - specifier: ^1.1.1 + specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-dropdown-menu': specifier: ^2.1.1 @@ -65,6 +68,9 @@ dependencies: '@radix-ui/react-scroll-area': specifier: ^1.1.0 version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-separator': + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.1.0(@types/react@18.3.11)(react@18.3.1) @@ -100,7 +106,7 @@ dependencies: version: 1.34.2 '@vercel/analytics': specifier: ^1.3.1 - version: 1.3.1(next@14.2.17)(react@18.3.1) + version: 1.3.1(next@14.2.20)(react@18.3.1) '@vercel/blob': specifier: ^0.23.4 version: 0.23.4 @@ -108,11 +114,11 @@ dependencies: specifier: ^1.4.0 version: 1.4.2 ai: - specifier: ^3.4.33 - version: 3.4.33(openai@4.67.2)(react@18.3.1)(svelte@4.2.19)(vue@3.5.11)(zod@3.23.8) + specifier: ^4.0.17 + version: 4.0.17(react@18.3.1)(zod@3.24.1) anthropic-vertex-ai: specifier: ^1.0.0 - version: 1.0.0(zod@3.23.8) + version: 1.0.0(zod@3.24.1) cheerio: specifier: ^1.0.0 version: 1.0.0 @@ -137,6 +143,9 @@ dependencies: embla-carousel-react: specifier: ^8.3.0 version: 8.3.0(react@18.3.1) + exa-js: + specifier: ^1.3.3 + version: 1.3.3 framer-motion: specifier: ^11.3.19 version: 11.11.2(react-dom@18.3.1)(react@18.3.1) @@ -161,15 +170,18 @@ dependencies: marked-react: specifier: ^2.0.0 version: 2.0.0(react@18.3.1) + motion: + specifier: ^11.13.5 + version: 11.13.5(react-dom@18.3.1)(react@18.3.1) next: - specifier: ^14.2.17 - version: 14.2.17(react-dom@18.3.1)(react@18.3.1) + specifier: ^14.2.20 + version: 14.2.20(react-dom@18.3.1)(react@18.3.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1)(react@18.3.1) openai: specifier: ^4.56.0 - version: 4.67.2(zod@3.23.8) + version: 4.67.2(zod@3.24.1) react: specifier: ^18 version: 18.3.1 @@ -215,9 +227,12 @@ dependencies: unist-util-visit: specifier: ^5.0.0 version: 5.0.0 + vaul: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) zod: - specifier: ^3.23.8 - version: 3.23.8 + specifier: ^3.24.1 + version: 3.24.1 devDependencies: '@types/google.maps': @@ -259,96 +274,96 @@ devDependencies: packages: - /@ai-sdk/anthropic@0.0.55(zod@3.23.8): - resolution: {integrity: sha512-SIPGu8on4PKl+PIdbjOniT5/AiE82Yw8HOE/W0GEb2bNGq2KhfkjVt5MBhppg+ZRme5w2iexB4bl66eRa2o89g==} + /@ai-sdk/anthropic@1.0.5(zod@3.24.1): + resolution: {integrity: sha512-qNEB7AYz6W0HTHbhJk/brhGZtjivcRdberD1fn3aCdvzlQ321q1EOTc2k7TvfE+PmNCZbp/uutBbWPGHHODKpw==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider': 1.0.2 + '@ai-sdk/provider-utils': 2.0.4(zod@3.24.1) + zod: 3.24.1 dev: false - /@ai-sdk/azure@0.0.51(zod@3.23.8): - resolution: {integrity: sha512-DMPuWURdgoweDlSLTj6o3Yx6+hnfr9idvHaTBwgPY+mayvEwlLZp3l08juMn/d/gKgHheQpUEgCQNhCzk6ZJ5w==} + /@ai-sdk/azure@1.0.10(zod@3.24.1): + resolution: {integrity: sha512-drbmzYS0iPRU/I3xnzphxNsYvSMjYhoq8gK34zShjeJGpPbewZPsXbPrncX6gdrkD4JR021yCahPc9E6RpXr5Q==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: - '@ai-sdk/openai': 0.0.71(zod@3.23.8) - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/openai': 1.0.8(zod@3.24.1) + '@ai-sdk/provider': 1.0.2 + '@ai-sdk/provider-utils': 2.0.4(zod@3.24.1) + zod: 3.24.1 dev: false - /@ai-sdk/cohere@1.0.3(zod@3.23.8): + /@ai-sdk/cohere@1.0.3(zod@3.24.1): resolution: {integrity: sha512-SDjPinUcGzTNiSMN+9zs1fuAcP8rU1/+CmDWAGu7eMhwVGDurgiOqscC0Oqs/aLsodLt/sFeOvyqj86DAknpbg==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 1.0.1 - '@ai-sdk/provider-utils': 2.0.2(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider-utils': 2.0.2(zod@3.24.1) + zod: 3.24.1 dev: false - /@ai-sdk/google@0.0.55(zod@3.23.8): - resolution: {integrity: sha512-dvEMS8Ex2H0OeuFBiT4Q1Kfrxi1ckjooy/PazNLjRQ3w9o9VQq4O24eMQGCuW1Z47qgMdXjhDzsH6qD0HOX6Cw==} + /@ai-sdk/google@1.0.7(zod@3.24.1): + resolution: {integrity: sha512-D2R/VFA0zpcWYnAoqYGaZn7XqHb8ASt1hZJ86u7BOVoBnQTRPRUYHb4lSXjrMcj/QYMXIJkKojfY4isenkku5Q==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider': 1.0.2 + '@ai-sdk/provider-utils': 2.0.4(zod@3.24.1) + zod: 3.24.1 dev: false - /@ai-sdk/groq@0.0.1(zod@3.23.8): + /@ai-sdk/groq@0.0.1(zod@3.24.1): resolution: {integrity: sha512-M8XHUovs2UqOx6xlhABXXCGlzbgeErSyIwvH1LQeDl3Z2CbSSgvttc0k6irm4J7ViuULE5XcIDQurXijIePWqQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 0.0.24 - '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider-utils': 1.0.20(zod@3.24.1) + zod: 3.24.1 dev: false - /@ai-sdk/mistral@0.0.41(zod@3.23.8): + /@ai-sdk/mistral@0.0.41(zod@3.24.1): resolution: {integrity: sha512-UTVtdC61AF4KQWnM3VAoo6/gi7G1frL3qVlKyW5toiRAUjCdeqLJUF2ho2iO8yqf+qIT6j57jWT3o6pqREy3Wg==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 0.0.23 - '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider-utils': 1.0.19(zod@3.24.1) + zod: 3.24.1 dev: false - /@ai-sdk/openai@0.0.58(zod@3.23.8): + /@ai-sdk/openai@0.0.58(zod@3.24.1): resolution: {integrity: sha512-Eao1L0vzfXdymgvc5FDHwV2g2A7BCWml1cShNA+wliY1RL7NNREGcuQvBDNoggB9PM24fawzZyk0ZJ5jlo9Q0w==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 0.0.23 - '@ai-sdk/provider-utils': 1.0.18(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider-utils': 1.0.18(zod@3.24.1) + zod: 3.24.1 dev: false - /@ai-sdk/openai@0.0.71(zod@3.23.8): - resolution: {integrity: sha512-ds7u3sWEnKyHxM3lAL9xTs72228HEKcPZCAEFaxmgrexKPJe2tyLBtvS/Kg39SPKPtY9EeaKqi/nbx1AmnXK6A==} + /@ai-sdk/openai@1.0.8(zod@3.24.1): + resolution: {integrity: sha512-wcTHM9qgRWGYVO3WxPSTN/RwnZ9R5/17xyo61iUCCSCZaAuJyh6fKddO0/oamwDp3BG7g+4wbfAyuTo32H+fHw==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider': 1.0.2 + '@ai-sdk/provider-utils': 2.0.4(zod@3.24.1) + zod: 3.24.1 dev: false - /@ai-sdk/provider-utils@1.0.17(zod@3.23.8): + /@ai-sdk/provider-utils@1.0.17(zod@3.24.1): resolution: {integrity: sha512-2VyeTH5DQ6AxqvwdyytKIeiZyYTyJffpufWjE67zM2sXMIHgYl7fivo8m5wVl6Cbf1dFPSGKq//C9s+lz+NHrQ==} engines: {node: '>=18'} peerDependencies: @@ -361,10 +376,10 @@ packages: eventsource-parser: 1.1.2 nanoid: 3.3.6 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.24.1 dev: false - /@ai-sdk/provider-utils@1.0.18(zod@3.23.8): + /@ai-sdk/provider-utils@1.0.18(zod@3.24.1): resolution: {integrity: sha512-9u/XE/dB1gsIGcxiC5JfGOLzUz+EKRXt66T8KYWwDg4x8d02P+fI/EPOgkf+T4oLBrcQgvs4GPXPKoXGPJxBbg==} engines: {node: '>=18'} peerDependencies: @@ -377,10 +392,10 @@ packages: eventsource-parser: 1.1.2 nanoid: 3.3.6 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.24.1 dev: false - /@ai-sdk/provider-utils@1.0.19(zod@3.23.8): + /@ai-sdk/provider-utils@1.0.19(zod@3.24.1): resolution: {integrity: sha512-p02Fq5Mnc8T6nwRBN1Iaou8YXvN1sDS6hbmJaD5UaRbXjizbh+8rpFS/o7jqAHTwf3uHCDitP3pnODyHdc/CDQ==} engines: {node: '>=18'} peerDependencies: @@ -393,10 +408,10 @@ packages: eventsource-parser: 1.1.2 nanoid: 3.3.6 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.24.1 dev: false - /@ai-sdk/provider-utils@1.0.20(zod@3.23.8): + /@ai-sdk/provider-utils@1.0.20(zod@3.24.1): resolution: {integrity: sha512-ngg/RGpnA00eNOWEtXHenpX1MsM2QshQh4QJFjUfwcqHpM5kTfG7je7Rc3HcEDP+OkRVv2GF+X4fC1Vfcnl8Ow==} engines: {node: '>=18'} peerDependencies: @@ -409,26 +424,10 @@ packages: eventsource-parser: 1.1.2 nanoid: 3.3.6 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.24.1 dev: false - /@ai-sdk/provider-utils@1.0.22(zod@3.23.8): - resolution: {integrity: sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true - dependencies: - '@ai-sdk/provider': 0.0.26 - eventsource-parser: 1.1.2 - nanoid: 3.3.7 - secure-json-parse: 2.7.0 - zod: 3.23.8 - dev: false - - /@ai-sdk/provider-utils@2.0.2(zod@3.23.8): + /@ai-sdk/provider-utils@2.0.2(zod@3.24.1): resolution: {integrity: sha512-IAvhKhdlXqiSmvx/D4uNlFYCl8dWT+M9K+IuEcSgnE2Aj27GWu8sDIpAf4r4Voc+wOUkOECVKQhFo8g9pozdjA==} engines: {node: '>=18'} peerDependencies: @@ -439,9 +438,25 @@ packages: dependencies: '@ai-sdk/provider': 1.0.1 eventsource-parser: 3.0.0 - nanoid: 3.3.7 + nanoid: 3.3.8 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.24.1 + dev: false + + /@ai-sdk/provider-utils@2.0.4(zod@3.24.1): + resolution: {integrity: sha512-GMhcQCZbwM6RoZCri0MWeEWXRt/T+uCxsmHEsTwNvEH3GDjNzchfX25C8ftry2MeEOOn6KfqCLSKomcgK6RoOg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + dependencies: + '@ai-sdk/provider': 1.0.2 + eventsource-parser: 3.0.0 + nanoid: 3.3.8 + secure-json-parse: 2.7.0 + zod: 3.24.1 dev: false /@ai-sdk/provider@0.0.22: @@ -465,13 +480,6 @@ packages: json-schema: 0.4.0 dev: false - /@ai-sdk/provider@0.0.26: - resolution: {integrity: sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==} - engines: {node: '>=18'} - dependencies: - json-schema: 0.4.0 - dev: false - /@ai-sdk/provider@1.0.1: resolution: {integrity: sha512-mV+3iNDkzUsZ0pR2jG0sVzU6xtQY5DtSCBy3JFycLp6PwjyLw/iodfL3MwdmMCRJWgs3dadcHejRnMvF9nGTBg==} engines: {node: '>=18'} @@ -479,8 +487,15 @@ packages: json-schema: 0.4.0 dev: false - /@ai-sdk/react@0.0.70(react@18.3.1)(zod@3.23.8): - resolution: {integrity: sha512-GnwbtjW4/4z7MleLiW+TOZC2M29eCg1tOUpuEiYFMmFNZK8mkrqM0PFZMo6UsYeUYMWqEOOcPOU9OQVJMJh7IQ==} + /@ai-sdk/provider@1.0.2: + resolution: {integrity: sha512-YYtP6xWQyaAf5LiWLJ+ycGTOeBLWrED7LUrvc+SQIWhGaneylqbaGsyQL7VouQUeQ4JZ1qKYZuhmi3W56HADPA==} + engines: {node: '>=18'} + dependencies: + json-schema: 0.4.0 + dev: false + + /@ai-sdk/react@1.0.6(react@18.3.1)(zod@3.24.1): + resolution: {integrity: sha512-8Hkserq0Ge6AEi7N4hlv2FkfglAGbkoAXEZ8YSp255c3PbnZz6+/5fppw+aROmZMOfNwallSRuy1i/iPa2rBpQ==} engines: {node: '>=18'} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc @@ -491,48 +506,16 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) + '@ai-sdk/provider-utils': 2.0.4(zod@3.24.1) + '@ai-sdk/ui-utils': 1.0.5(zod@3.24.1) react: 18.3.1 swr: 2.2.5(react@18.3.1) throttleit: 2.1.0 - zod: 3.23.8 + zod: 3.24.1 dev: false - /@ai-sdk/solid@0.0.54(zod@3.23.8): - resolution: {integrity: sha512-96KWTVK+opdFeRubqrgaJXoNiDP89gNxFRWUp0PJOotZW816AbhUf4EnDjBjXTLjXL1n0h8tGSE9sZsRkj9wQQ==} - engines: {node: '>=18'} - peerDependencies: - solid-js: ^1.7.7 - peerDependenciesMeta: - solid-js: - optional: true - dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) - transitivePeerDependencies: - - zod - dev: false - - /@ai-sdk/svelte@0.0.57(svelte@4.2.19)(zod@3.23.8): - resolution: {integrity: sha512-SyF9ItIR9ALP9yDNAD+2/5Vl1IT6kchgyDH8xkmhysfJI6WrvJbtO1wdQ0nylvPLcsPoYu+cAlz1krU4lFHcYw==} - engines: {node: '>=18'} - peerDependencies: - svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - svelte: - optional: true - dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) - sswr: 2.1.0(svelte@4.2.19) - svelte: 4.2.19 - transitivePeerDependencies: - - zod - dev: false - - /@ai-sdk/ui-utils@0.0.50(zod@3.23.8): - resolution: {integrity: sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==} + /@ai-sdk/ui-utils@1.0.5(zod@3.24.1): + resolution: {integrity: sha512-DGJSbDf+vJyWmFNexSPUsS1AAy7gtsmFmoSyNbNbJjwl9hRIf2dknfA1V0ahx6pg3NNklNYFm53L8Nphjovfvg==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 @@ -540,72 +523,27 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - json-schema: 0.4.0 - secure-json-parse: 2.7.0 - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) + '@ai-sdk/provider': 1.0.2 + '@ai-sdk/provider-utils': 2.0.4(zod@3.24.1) + zod: 3.24.1 + zod-to-json-schema: 3.24.1(zod@3.24.1) dev: false - /@ai-sdk/vue@0.0.59(vue@3.5.11)(zod@3.23.8): - resolution: {integrity: sha512-+ofYlnqdc8c4F6tM0IKF0+7NagZRAiqBJpGDJ+6EYhDW8FHLUP/JFBgu32SjxSxC6IKFZxEnl68ZoP/Z38EMlw==} - engines: {node: '>=18'} - peerDependencies: - vue: ^3.3.4 - peerDependenciesMeta: - vue: - optional: true - dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) - swrv: 1.0.4(vue@3.5.11) - vue: 3.5.11(typescript@5.6.2) - transitivePeerDependencies: - - zod - dev: false - - /@ai-sdk/xai@1.0.3(zod@3.23.8): - resolution: {integrity: sha512-Z3ovBU21Wp87EPwkLoP0K4SNkyIzwQk+YAFuBPnRLCSVtBESeMarcI5zDVvBJ0lmQalRX1ZBAs8U1FvQ4T9mqw==} + /@ai-sdk/xai@1.0.6(zod@3.24.1): + resolution: {integrity: sha512-bNEAJMSyjMNJIUx9bEr0o3PvP+s4tTU+GzuzG9OVhNc8Zx28kHGXogl4SjDkOgCKHOLixE9RIKuVojZFOACdww==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: - '@ai-sdk/provider': 1.0.1 - '@ai-sdk/provider-utils': 2.0.2(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider': 1.0.2 + '@ai-sdk/provider-utils': 2.0.4(zod@3.24.1) + zod: 3.24.1 dev: false /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - /@ampproject/remapping@2.3.0: - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - dev: false - - /@babel/helper-string-parser@7.25.7: - resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-validator-identifier@7.25.7: - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/parser@7.25.7: - resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.25.7 - dev: false - /@babel/runtime@7.25.7: resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} engines: {node: '>=6.9.0'} @@ -613,15 +551,6 @@ packages: regenerator-runtime: 0.14.1 dev: false - /@babel/types@7.25.7: - resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - to-fast-properties: 2.0.0 - dev: false - /@bufbuild/protobuf@1.10.0: resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} dev: false @@ -821,15 +750,15 @@ packages: axios: 1.7.7 isows: 1.0.6(ws@8.18.0) typescript-event-target: 1.1.1 - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) + zod: 3.24.1 + zod-to-json-schema: 3.23.3(zod@3.24.1) transitivePeerDependencies: - debug - ws dev: false - /@next/env@14.2.17: - resolution: {integrity: sha512-MCgO7VHxXo8sYR/0z+sk9fGyJJU636JyRmkjc7ZJY8Hurl8df35qG5hoAh5KMs75FLjhlEo9bb2LGe89Y/scDA==} + /@next/env@14.2.20: + resolution: {integrity: sha512-JfDpuOCB0UBKlEgEy/H6qcBSzHimn/YWjUHzKl1jMeUO+QVRdzmTTl8gFJaNO87c8DXmVKhFCtwxQ9acqB3+Pw==} dev: false /@next/eslint-plugin-next@14.2.5: @@ -838,8 +767,8 @@ packages: glob: 10.3.10 dev: true - /@next/swc-darwin-arm64@14.2.17: - resolution: {integrity: sha512-WiOf5nElPknrhRMTipXYTJcUz7+8IAjOYw3vXzj3BYRcVY0hRHKWgTgQ5439EvzQyHEko77XK+yN9x9OJ0oOog==} + /@next/swc-darwin-arm64@14.2.20: + resolution: {integrity: sha512-WDfq7bmROa5cIlk6ZNonNdVhKmbCv38XteVFYsxea1vDJt3SnYGgxLGMTXQNfs5OkFvAhmfKKrwe7Y0Hs+rWOg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -847,8 +776,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@14.2.17: - resolution: {integrity: sha512-29y425wYnL17cvtxrDQWC3CkXe/oRrdt8ie61S03VrpwpPRI0XsnTvtKO06XCisK4alaMnZlf8riwZIbJTaSHQ==} + /@next/swc-darwin-x64@14.2.20: + resolution: {integrity: sha512-XIQlC+NAmJPfa2hruLvr1H1QJJeqOTDV+v7tl/jIdoFvqhoihvSNykLU/G6NMgoeo+e/H7p/VeWSOvMUHKtTIg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -856,8 +785,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@14.2.17: - resolution: {integrity: sha512-SSHLZls3ZwNEHsc+d0ynKS+7Af0Nr8+KTUBAy9pm6xz9SHkJ/TeuEg6W3cbbcMSh6j4ITvrjv3Oi8n27VR+IPw==} + /@next/swc-linux-arm64-gnu@14.2.20: + resolution: {integrity: sha512-pnzBrHTPXIMm5QX3QC8XeMkpVuoAYOmyfsO4VlPn+0NrHraNuWjdhe+3xLq01xR++iCvX+uoeZmJDKcOxI201Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -865,8 +794,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@14.2.17: - resolution: {integrity: sha512-VFge37us5LNPatB4F7iYeuGs9Dprqe4ZkW7lOEJM91r+Wf8EIdViWHLpIwfdDXinvCdLl6b4VyLpEBwpkctJHA==} + /@next/swc-linux-arm64-musl@14.2.20: + resolution: {integrity: sha512-WhJJAFpi6yqmUx1momewSdcm/iRXFQS0HU2qlUGlGE/+98eu7JWLD5AAaP/tkK1mudS/rH2f9E3WCEF2iYDydQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -874,8 +803,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@14.2.17: - resolution: {integrity: sha512-aaQlpxUVb9RZ41adlTYVQ3xvYEfBPUC8+6rDgmQ/0l7SvK8S1YNJzPmDPX6a4t0jLtIoNk7j+nroS/pB4nx7vQ==} + /@next/swc-linux-x64-gnu@14.2.20: + resolution: {integrity: sha512-ao5HCbw9+iG1Kxm8XsGa3X174Ahn17mSYBQlY6VGsdsYDAbz/ZP13wSLfvlYoIDn1Ger6uYA+yt/3Y9KTIupRg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -883,8 +812,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@14.2.17: - resolution: {integrity: sha512-HSyEiFaEY3ay5iATDqEup5WAfrhMATNJm8dYx3ZxL+e9eKv10XKZCwtZByDoLST7CyBmyDz+OFJL1wigyXeaoA==} + /@next/swc-linux-x64-musl@14.2.20: + resolution: {integrity: sha512-CXm/kpnltKTT7945np6Td3w7shj/92TMRPyI/VvveFe8+YE+/YOJ5hyAWK5rpx711XO1jBCgXl211TWaxOtkaA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -892,8 +821,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@14.2.17: - resolution: {integrity: sha512-h5qM9Btqv87eYH8ArrnLoAHLyi79oPTP2vlGNSg4CDvUiXgi7l0+5KuEGp5pJoMhjuv9ChRdm7mRlUUACeBt4w==} + /@next/swc-win32-arm64-msvc@14.2.20: + resolution: {integrity: sha512-upJn2HGQgKNDbXVfIgmqT2BN8f3z/mX8ddoyi1I565FHbfowVK5pnMEwauvLvaJf4iijvuKq3kw/b6E9oIVRWA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -901,8 +830,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@14.2.17: - resolution: {integrity: sha512-BD/G++GKSLexQjdyoEUgyo5nClU7er5rK0sE+HlEqnldJSm96CIr/+YOTT063LVTT/dUOeQsNgp5DXr86/K7/A==} + /@next/swc-win32-ia32-msvc@14.2.20: + resolution: {integrity: sha512-igQW/JWciTGJwj3G1ipalD2V20Xfx3ywQy17IV0ciOUBbFhNfyU1DILWsTi32c8KmqgIDviUEulW/yPb2FF90w==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -910,8 +839,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@14.2.17: - resolution: {integrity: sha512-vkQfN1+4V4KqDibkW2q0sJ6CxQuXq5l2ma3z0BRcfIqkAMZiiW67T9yCpwqJKP68QghBtPEFjPAlaqe38O6frw==} + /@next/swc-win32-x64-msvc@14.2.20: + resolution: {integrity: sha512-AFmqeLW6LtxeFTuoB+MXFeM5fm5052i3MU6xD0WzJDOwku6SkZaxb1bxjBaRC8uNqTRTSPl0yMFtjNowIVI67w==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -947,6 +876,17 @@ packages: engines: {node: '>=8.0.0'} dev: false + /@phosphor-icons/react@2.1.7(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-g2e2eVAn1XG2a+LI09QU3IORLhnFNAFkNbo2iwbX6NOKSLOwvEMmTa7CgOzEbgNWR47z8i8kwjdvYZ5fkGx1mQ==} + engines: {node: '>=10'} + peerDependencies: + react: '>= 16.8' + react-dom: '>= 16.8' + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1072,6 +1012,19 @@ packages: react: 18.3.1 dev: false + /@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.11 + react: 18.3.1 + dev: false + /@radix-ui/react-context@1.1.0(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -1474,6 +1427,26 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false + /@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + 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/react-slot': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} peerDependencies: @@ -1530,6 +1503,26 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false + /@radix-ui/react-separator@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==} + 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/react-primitive': 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-slot@1.1.0(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} peerDependencies: @@ -1544,6 +1537,20 @@ packages: react: 18.3.1 dev: false + /@radix-ui/react-slot@1.1.1(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + react: 18.3.1 + dev: false + /@radix-ui/react-switch@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==} peerDependencies: @@ -2063,7 +2070,7 @@ packages: crypto-js: 4.2.0 dev: false - /@vercel/analytics@1.3.1(next@14.2.17)(react@18.3.1): + /@vercel/analytics@1.3.1(next@14.2.20)(react@18.3.1): resolution: {integrity: sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==} peerDependencies: next: '>= 13' @@ -2074,7 +2081,7 @@ packages: react: optional: true dependencies: - next: 14.2.17(react-dom@18.3.1)(react@18.3.1) + next: 14.2.20(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 server-only: 0.0.1 dev: false @@ -2100,80 +2107,6 @@ packages: optional: true dev: false - /@vue/compiler-core@3.5.11: - resolution: {integrity: sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg==} - dependencies: - '@babel/parser': 7.25.7 - '@vue/shared': 3.5.11 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - dev: false - - /@vue/compiler-dom@3.5.11: - resolution: {integrity: sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==} - dependencies: - '@vue/compiler-core': 3.5.11 - '@vue/shared': 3.5.11 - dev: false - - /@vue/compiler-sfc@3.5.11: - resolution: {integrity: sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw==} - dependencies: - '@babel/parser': 7.25.7 - '@vue/compiler-core': 3.5.11 - '@vue/compiler-dom': 3.5.11 - '@vue/compiler-ssr': 3.5.11 - '@vue/shared': 3.5.11 - estree-walker: 2.0.2 - magic-string: 0.30.11 - postcss: 8.4.47 - source-map-js: 1.2.1 - dev: false - - /@vue/compiler-ssr@3.5.11: - resolution: {integrity: sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA==} - dependencies: - '@vue/compiler-dom': 3.5.11 - '@vue/shared': 3.5.11 - dev: false - - /@vue/reactivity@3.5.11: - resolution: {integrity: sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w==} - dependencies: - '@vue/shared': 3.5.11 - dev: false - - /@vue/runtime-core@3.5.11: - resolution: {integrity: sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA==} - dependencies: - '@vue/reactivity': 3.5.11 - '@vue/shared': 3.5.11 - dev: false - - /@vue/runtime-dom@3.5.11: - resolution: {integrity: sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ==} - dependencies: - '@vue/reactivity': 3.5.11 - '@vue/runtime-core': 3.5.11 - '@vue/shared': 3.5.11 - csstype: 3.1.3 - dev: false - - /@vue/server-renderer@3.5.11(vue@3.5.11): - resolution: {integrity: sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA==} - peerDependencies: - vue: 3.5.11 - dependencies: - '@vue/compiler-ssr': 3.5.11 - '@vue/shared': 3.5.11 - vue: 3.5.11(typescript@5.6.2) - dev: false - - /@vue/shared@3.5.11: - resolution: {integrity: sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==} - dev: false - /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -2193,6 +2126,7 @@ packages: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} hasBin: true + dev: true /agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} @@ -2210,47 +2144,27 @@ packages: humanize-ms: 1.2.1 dev: false - /ai@3.4.33(openai@4.67.2)(react@18.3.1)(svelte@4.2.19)(vue@3.5.11)(zod@3.23.8): - resolution: {integrity: sha512-plBlrVZKwPoRTmM8+D1sJac9Bq8eaa2jiZlHLZIWekKWI1yMWYZvCCEezY9ASPwRhULYDJB2VhKOBUUeg3S5JQ==} + /ai@4.0.17(react@18.3.1)(zod@3.24.1): + resolution: {integrity: sha512-MfI5FBDveV91e71CffZNbutXCMinHqXL1yLHjpN9Pf4vRtC2hXyMmlcCSyX865TDGIWmgVfi9yN6XlYn/ZsfUQ==} engines: {node: '>=18'} peerDependencies: - openai: ^4.42.0 react: ^18 || ^19 || ^19.0.0-rc - sswr: ^2.1.0 - svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 zod: ^3.0.0 peerDependenciesMeta: - openai: - optional: true react: optional: true - sswr: - optional: true - svelte: - optional: true zod: optional: true dependencies: - '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/react': 0.0.70(react@18.3.1)(zod@3.23.8) - '@ai-sdk/solid': 0.0.54(zod@3.23.8) - '@ai-sdk/svelte': 0.0.57(svelte@4.2.19)(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) - '@ai-sdk/vue': 0.0.59(vue@3.5.11)(zod@3.23.8) + '@ai-sdk/provider': 1.0.2 + '@ai-sdk/provider-utils': 2.0.4(zod@3.24.1) + '@ai-sdk/react': 1.0.6(react@18.3.1)(zod@3.24.1) + '@ai-sdk/ui-utils': 1.0.5(zod@3.24.1) '@opentelemetry/api': 1.9.0 - eventsource-parser: 1.1.2 - json-schema: 0.4.0 jsondiffpatch: 0.6.0 - openai: 4.67.2(zod@3.23.8) react: 18.3.1 - secure-json-parse: 2.7.0 - svelte: 4.2.19 - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) - transitivePeerDependencies: - - solid-js - - vue + zod: 3.24.1 + zod-to-json-schema: 3.24.1(zod@3.24.1) dev: false /ajv@6.12.6: @@ -2280,16 +2194,16 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - /anthropic-vertex-ai@1.0.0(zod@3.23.8): + /anthropic-vertex-ai@1.0.0(zod@3.24.1): resolution: {integrity: sha512-ME1e8kCNLVvVWrR6vB3zFlREEp1kRLmNZUC+oih+tziPkb/li5ESRvzb1eDV+zyhw7tZDDLy7numRllJwdkCEw==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 0.0.22 - '@ai-sdk/provider-utils': 1.0.17(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.17(zod@3.24.1) google-auth-library: 9.14.1 - zod: 3.23.8 + zod: 3.24.1 transitivePeerDependencies: - encoding - supports-color @@ -2325,11 +2239,6 @@ packages: deep-equal: 2.2.3 dev: true - /aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - dev: false - /array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -2463,6 +2372,7 @@ packages: /axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + dev: true /bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -2656,16 +2566,6 @@ packages: engines: {node: '>=6'} dev: false - /code-red@1.0.4: - resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - acorn: 8.12.1 - estree-walker: 3.0.3 - periscopic: 3.1.0 - dev: false - /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2707,6 +2607,14 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2729,14 +2637,6 @@ packages: nth-check: 2.1.1 dev: false - /css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.2.1 - dev: false - /css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -3549,16 +3449,6 @@ packages: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} dev: false - /estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: false - - /estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - dependencies: - '@types/estree': 1.0.6 - dev: false - /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -3583,6 +3473,14 @@ packages: engines: {node: '>=18.0.0'} dev: false + /exa-js@1.3.3: + resolution: {integrity: sha512-GynRF6cQt2FrrKUOzeJzBqBfURd7r80TFILUK+Na+XxkhGUjKWxpv7BRTraCTvgfJvGy+TYtp3b/SW6rteFaog==} + dependencies: + cross-fetch: 4.0.0 + transitivePeerDependencies: + - encoding + dev: false + /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: false @@ -3726,6 +3624,27 @@ packages: tslib: 2.7.0 dev: false + /framer-motion@11.13.5(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-rArI0zPU9VkpS3Wt0J7dmRxAFUWtzPWoSofNQAP0UO276CmJ+Xlf5xN19GMw3w2QsdrS2sU+0+Q2vtuz4IEZaw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + motion-dom: 11.13.0 + motion-utils: 11.13.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.7.0 + dev: false + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true @@ -4370,12 +4289,6 @@ packages: engines: {node: '>=0.10.0'} dev: false - /is-reference@3.0.2: - resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} - dependencies: - '@types/estree': 1.0.6 - dev: false - /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -4614,10 +4527,6 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - /locate-character@3.0.0: - resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} - dev: false - /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -4673,12 +4582,6 @@ packages: engines: {node: '>=12'} dev: false - /magic-string@0.30.11: - resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - dev: false - /mapbox-gl@3.7.0: resolution: {integrity: sha512-dCbVyH1uGobwv6f4QKRv2Z2wuVT/RmspsudK3sTxGRFxZi6Pd2P9axdbVyZpmGddCAREy44pHhvzvO0qgpdKAg==} dependencies: @@ -4927,10 +4830,6 @@ packages: '@types/mdast': 4.0.4 dev: false - /mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - dev: false - /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -5240,6 +5139,34 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + /motion-dom@11.13.0: + resolution: {integrity: sha512-Oc1MLGJQ6nrvXccXA89lXtOqFyBmvHtaDcTRGT66o8Czl7nuA8BeHAd9MQV1pQKX0d2RHFBFaw5g3k23hQJt0w==} + dev: false + + /motion-utils@11.13.0: + resolution: {integrity: sha512-lq6TzXkH5c/ysJQBxgLXgM01qwBH1b4goTPh57VvZWJbVJZF/0SB31UWEn4EIqbVPf3au88n2rvK17SpDTja1A==} + dev: false + + /motion@11.13.5(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-zmX/dz60w1ZtQB5NP9xYkLcCKwX9gc+pnHp4/mFhD9YW8wUe2ZmT8OPOtrTtq26/huxElSDu3hB7BMTSJa5iIQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + framer-motion: 11.13.5(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.7.0 + dev: false + /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5265,6 +5192,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -5279,8 +5212,8 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false - /next@14.2.17(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-hNo/Zy701DDO3nzKkPmsLRlDfNCtb1OJxFUvjGEl04u7SFa3zwC6hqsOUzMajcaEOEV8ey1GjvByvrg0Qr5AiQ==} + /next@14.2.20(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-yPvIiWsiyVYqJlSQxwmzMIReXn5HxFNq4+tlVQ812N1FbvhmE+fDpIAD7bcS2mGYQwPJ5vAsQouyme2eKsxaug==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -5297,7 +5230,7 @@ packages: sass: optional: true dependencies: - '@next/env': 14.2.17 + '@next/env': 14.2.20 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001667 @@ -5307,15 +5240,15 @@ packages: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.17 - '@next/swc-darwin-x64': 14.2.17 - '@next/swc-linux-arm64-gnu': 14.2.17 - '@next/swc-linux-arm64-musl': 14.2.17 - '@next/swc-linux-x64-gnu': 14.2.17 - '@next/swc-linux-x64-musl': 14.2.17 - '@next/swc-win32-arm64-msvc': 14.2.17 - '@next/swc-win32-ia32-msvc': 14.2.17 - '@next/swc-win32-x64-msvc': 14.2.17 + '@next/swc-darwin-arm64': 14.2.20 + '@next/swc-darwin-x64': 14.2.20 + '@next/swc-linux-arm64-gnu': 14.2.20 + '@next/swc-linux-arm64-musl': 14.2.20 + '@next/swc-linux-x64-gnu': 14.2.20 + '@next/swc-linux-x64-musl': 14.2.20 + '@next/swc-win32-arm64-msvc': 14.2.20 + '@next/swc-win32-ia32-msvc': 14.2.20 + '@next/swc-win32-x64-msvc': 14.2.20 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -5427,7 +5360,7 @@ packages: wrappy: 1.0.2 dev: true - /openai@4.67.2(zod@3.23.8): + /openai@4.67.2(zod@3.24.1): resolution: {integrity: sha512-u4FJFGXgqEHrCYcD5jAD4nHj6JCiicH+/dskQY7qka9R6hOw29R0kOz7GwcA9k2JKcLf86lzAWPtPagPbO8KnQ==} hasBin: true peerDependencies: @@ -5443,7 +5376,7 @@ packages: form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.7.0 - zod: 3.23.8 + zod: 3.24.1 transitivePeerDependencies: - encoding dev: false @@ -5574,14 +5507,6 @@ packages: resolve-protobuf-schema: 2.1.0 dev: false - /periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} - dependencies: - '@types/estree': 1.0.6 - estree-walker: 3.0.3 - is-reference: 3.0.2 - dev: false - /picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} @@ -5673,7 +5598,7 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.0 source-map-js: 1.2.1 dev: false @@ -6225,15 +6150,6 @@ packages: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} dev: false - /sswr@2.1.0(svelte@4.2.19): - resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==} - peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.0 - dependencies: - svelte: 4.2.19 - swrev: 4.0.0 - dev: false - /stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} @@ -6403,26 +6319,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /svelte@4.2.19: - resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} - engines: {node: '>=16'} - dependencies: - '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - '@types/estree': 1.0.6 - acorn: 8.12.1 - aria-query: 5.3.2 - axobject-query: 4.1.0 - code-red: 1.0.4 - css-tree: 2.3.1 - estree-walker: 3.0.3 - is-reference: 3.0.2 - locate-character: 3.0.0 - magic-string: 0.30.11 - periscopic: 3.1.0 - dev: false - /swr@2.2.5(react@18.3.1): resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} peerDependencies: @@ -6433,18 +6329,6 @@ packages: use-sync-external-store: 1.2.2(react@18.3.1) dev: false - /swrev@4.0.0: - resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==} - dev: false - - /swrv@1.0.4(vue@3.5.11): - resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==} - peerDependencies: - vue: '>=3.2.26 < 4' - dependencies: - vue: 3.5.11(typescript@5.6.2) - dev: false - /tailwind-merge@2.5.3: resolution: {integrity: sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw==} dev: false @@ -6520,11 +6404,6 @@ packages: resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} dev: false - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - dev: false - /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -6636,6 +6515,7 @@ packages: resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} engines: {node: '>=14.17'} hasBin: true + dev: true /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -6777,6 +6657,20 @@ packages: hasBin: true dev: false + /vaul@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-+ejzF6ffQKPcfgS7uOrGn017g39F8SO4yLPXbBhpC7a0H+oPqPna8f1BUfXaz8eU4+pxbQcmjxW+jWBSbxjaFg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 + dependencies: + '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + dev: false + /vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} dependencies: @@ -6825,22 +6719,6 @@ packages: pbf: 3.3.0 dev: false - /vue@3.5.11(typescript@5.6.2): - resolution: {integrity: sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@vue/compiler-dom': 3.5.11 - '@vue/compiler-sfc': 3.5.11 - '@vue/runtime-dom': 3.5.11 - '@vue/server-renderer': 3.5.11(vue@3.5.11) - '@vue/shared': 3.5.11 - typescript: 5.6.2 - dev: false - /web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} dev: false @@ -6982,16 +6860,24 @@ packages: engines: {node: '>=10'} dev: true - /zod-to-json-schema@3.23.3(zod@3.23.8): + /zod-to-json-schema@3.23.3(zod@3.24.1): resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==} peerDependencies: zod: ^3.23.3 dependencies: - zod: 3.23.8 + zod: 3.24.1 dev: false - /zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + /zod-to-json-schema@3.24.1(zod@3.24.1): + resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==} + peerDependencies: + zod: ^3.24.1 + dependencies: + zod: 3.24.1 + dev: false + + /zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} dev: false /zrender@5.6.0: