diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
index c42b2af..8780ac9 100644
--- a/app/api/chat/route.ts
+++ b/app/api/chat/route.ts
@@ -32,16 +32,6 @@ function sanitizeUrl(url: string): string {
return url.replace(/\s+/g, '%20')
}
-// Helper function to geocode an address
-const geocodeAddress = async (address: string) => {
- const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
- const response = await fetch(
- `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(address)}.json?access_token=${mapboxToken}`
- );
- const data = await response.json();
- return data.features[0];
-};
-
export async function POST(req: Request) {
const { messages, model } = await req.json();
@@ -144,105 +134,89 @@ When asked a "What is" question, maintain the same format as the question and an
- 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.`,
tools: {
+ // Update the web_search tool parameters in app/api/chat/route.ts
web_search: tool({
- description:
- "Search the web for information with the given query, max results and search depth.",
+ description: "Search the web for information with multiple queries, max results and search depth.",
parameters: z.object({
- query: z.string().describe("The search query to look up on the web."),
- maxResults: z
+ queries: z.array(z.string().describe("Array of search queries to look up on the web.")),
+ maxResults: z.array(z
.number()
- .describe(
- "The maximum number of results to return. Default to be used is 10.",
- ),
- topic: z
+ .describe("Array of maximum number of results to return per query. Default is 10.")),
+ topic: z.array(z
.enum(["general", "news"])
- .describe("The topic type to search for. Default is general."),
- searchDepth: z
+ .describe("Array of topic types to search for. Default is general.")),
+ searchDepth: z.array(z
.enum(["basic", "advanced"])
- .describe(
- "The search depth to use for the search. Default is basic.",
- ),
+ .describe("Array of search depths to use. Default is basic.")),
exclude_domains: z
.array(z.string())
- .describe(
- "A list of domains to specifically exclude from the search results. Default is None, which doesn't exclude any domains.",
- ),
+ .describe("A list of domains to exclude from all search results. Default is None."),
}),
execute: async ({
- query,
+ queries,
maxResults,
topic,
searchDepth,
exclude_domains,
}: {
- query: string;
- maxResults: number;
- topic: "general" | "news";
- searchDepth: "basic" | "advanced";
+ queries: string[];
+ maxResults: number[];
+ topic: ("general" | "news")[];
+ searchDepth: ("basic" | "advanced")[];
exclude_domains?: string[];
}) => {
const apiKey = process.env.TAVILY_API_KEY;
const tvly = tavily({ apiKey });
- const includeImageDescriptions = true
+ const includeImageDescriptions = true;
-
- console.log("Query:", query);
+ console.log("Queries:", queries);
console.log("Max Results:", maxResults);
- console.log("Topic:", topic);
- console.log("Search Depth:", searchDepth);
+ console.log("Topics:", topic);
+ console.log("Search Depths:", searchDepth);
console.log("Exclude Domains:", exclude_domains);
- const data = await tvly.search(query, {
- topic: topic,
- days: topic === "news" ? 7 : undefined,
- maxResults: maxResults < 5 ? 5 : maxResults,
- searchDepth: searchDepth,
- includeAnswer: true,
- includeImages: true,
- includeImageDescriptions: includeImageDescriptions,
- excludeDomains: 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,
+ maxResults: maxResults[index] || maxResults[0] || 10,
+ searchDepth: searchDepth[index] || searchDepth[0] || "basic",
+ includeAnswer: true,
+ includeImages: true,
+ includeImageDescriptions: includeImageDescriptions,
+ excludeDomains: exclude_domains,
+ });
- let context = data.results.map(
- (obj: any, index: number) => {
- if (topic === "news") {
- return {
- url: obj.url,
- title: obj.title,
- content: obj.content,
- raw_content: obj.raw_content,
- published_date: obj.published_date,
- };
- }
- return {
+ return {
+ query,
+ results: data.results.map((obj: any) => ({
url: obj.url,
title: obj.title,
content: obj.content,
raw_content: obj.raw_content,
- };
- },
- );
+ published_date: topic[index] === "news" ? obj.published_date : undefined,
+ })),
+ images: includeImageDescriptions
+ ? data.images
+ .map(({ url, description }: { url: string; description?: string }) => ({
+ url: sanitizeUrl(url),
+ description: description ?? ''
+ }))
+ .filter(
+ (image: { url: string; description: string }): image is { url: string; description: string } =>
+ typeof image === 'object' &&
+ image.description !== undefined &&
+ image.description !== ''
+ )
+ : data.images.map(({ url }: { url: string }) => sanitizeUrl(url))
+ };
+ });
-
- const processedImages = includeImageDescriptions
- ? data.images
- .map(({ url, description }: { url: string; description?: string }) => ({
- url: sanitizeUrl(url),
- description: description ?? ''
- }))
- .filter(
- (
- image: { url: string; description: string }
- ): image is { url: string; description: string } =>
- typeof image === 'object' &&
- image.description !== undefined &&
- image.description !== ''
- )
- : data.images.map(({ url }: { url: string }) => sanitizeUrl(url))
+ const searchResults = await Promise.all(searchPromises);
return {
- results: context,
- images: processedImages
+ searches: searchResults,
};
},
}),
@@ -613,7 +587,7 @@ When asked a "What is" question, maintain the same format as the question and an
console.log(`Photo fetch failed for "${place.name}":`, error);
}
-
+
// Get timezone for the location
const tzResponse = await fetch(
diff --git a/app/search/page.tsx b/app/search/page.tsx
index cf4fa35..060dadf 100644
--- a/app/search/page.tsx
+++ b/app/search/page.tsx
@@ -106,6 +106,7 @@ 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';
export const maxDuration = 60;
@@ -116,147 +117,6 @@ interface Attachment {
size: number;
}
-interface SearchImage {
- url: string;
- description: string;
-}
-
-const ImageCarousel = ({ images, onClose }: { images: SearchImage[], onClose: () => void }) => {
- return (
-
-
-
-
- Close
-
-
-
- {images.map((image, index) => (
-
-
- {image.description}
-
- ))}
-
-
-
-
-
-
- );
-};
-
-
-const WebSearchResults = ({ result, args }: { result: any, args: any }) => {
- const [openDialog, setOpenDialog] = useState(false);
- const [selectedImageIndex, setSelectedImageIndex] = useState(0);
-
- const handleImageClick = (index: number) => {
- setSelectedImageIndex(index);
- setOpenDialog(true);
- };
-
- const handleCloseDialog = () => {
- setOpenDialog(false);
- };
-
- return (
-
-
-
-
-
-
-
-
Sources Found
-
- {result && (
-
{result.results.length} results
- )}
-
-
-
- {args?.query && (
-
-
- {args.query}
-
- )}
- {result && (
-
- {result.results.map((item: any, itemIndex: number) => (
-
- ))}
-
- )}
-
-
-
- {result && result.images && result.images.length > 0 && (
-
-
-
-
Images
-
-
- {result.images.slice(0, 4).map((image: SearchImage, itemIndex: number) => (
-
handleImageClick(itemIndex)}
- >
-
- {itemIndex === 3 && result.images.length > 4 && (
-
- )}
-
- ))}
-
-
- )}
- {openDialog && result.images && (
-
- )}
-
- );
-};
-
const HomeContent = () => {
const searchParams = useSearchParams();
const initialQuery = searchParams.get('query') || '';
@@ -357,31 +217,33 @@ const HomeContent = () => {
const changelogs: Changelog[] = [
{
id: "1",
- title: "Dark mode is here!",
+ title: "New Updates!",
images: [
- "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-dark-mode-promo.png",
- "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-new-input-bar-promo.png",
- "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-gpt-4o-back-Lwzx44RD4XofYLAmrEsLD3Fngnn33K.png"
+ "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-maps-beta.png",
+ "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-multi-run.png",
+ "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-multi-results.png",
+ "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-new-claude.png"
],
content:
- `## **Dark Mode**
+ `## **Nearby Map Search Beta**
-The most requested feature is finally here! You can now toggle between light and dark mode. Default is set to your system preference.
+The new Nearby Map Search tool is now available in beta! You can use it to find nearby places, restaurants, attractions, and more. Give it a try and let us know what you think!
-## **New Input Bar Design**
+## **Multi Search is here by default**
-The input bar has been redesigned to make it more focused, user-friendly and accessible. The model selection dropdown has been moved to the bottom left corner inside the input bar.
+The AI powered Multiple Query Search tool is now available by default. The LLM model will now automatically suggest multiple queries based on your input and run the searches in parallel.
-## **GPT-4o is back!**
+## **Claude 3.5 Sonnet(New) and 3.5 Haiku are here!**
-GPT-4o has been re-enabled! You can use it by selecting the model from the dropdown.`,
+The new Anthropic models: Claude 3.5 Sonnet and 3.5 Haiku models are now available on the platform.
+`
}
];
const ChangeLogs: React.FC<{ open: boolean; setOpen: (open: boolean) => void }> = ({ open, setOpen }) => {
return (
-
+
What's new
@@ -958,33 +820,8 @@ GPT-4o has been re-enabled! You can use it by selecting the model from the dropd
if (toolInvocation.toolName === 'web_search') {
return (
-
- {!result ? (
-
-
-
- Running a search...
-
-
- {[0, 1, 2].map((index) => (
-
- ))}
-
-
- ) : (
-
- )}
+
+
);
}
diff --git a/components/multi-search.tsx b/components/multi-search.tsx
new file mode 100644
index 0000000..4afa57c
--- /dev/null
+++ b/components/multi-search.tsx
@@ -0,0 +1,404 @@
+/* eslint-disable @next/next/no-img-element */
+import React, { useState } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Dialog, DialogContent } from "@/components/ui/dialog";
+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';
+
+type SearchImage = {
+ url: string;
+ description: string;
+};
+
+type SearchResult = {
+ url: string;
+ title: string;
+ content: string;
+ raw_content: string;
+ published_date?: string;
+};
+
+type SearchQueryResult = {
+ query: string;
+ results: SearchResult[];
+ images: SearchImage[];
+};
+
+type MultiSearchResponse = {
+ searches: SearchQueryResult[];
+};
+
+type MultiSearchArgs = {
+ queries: string[];
+ maxResults: number[];
+ topic: ("general" | "news")[];
+ searchDepth: ("basic" | "advanced")[];
+};
+
+interface ResultCardProps {
+ result: SearchResult;
+ index: number;
+}
+
+interface GalleryProps {
+ images: SearchImage[];
+ onClose: () => void;
+}
+
+interface SearchResultsProps {
+ searchData: SearchQueryResult;
+ topicType: string;
+ onImageClick: (index: number) => void;
+}
+
+const SearchQueryTab: React.FC<{ query: string; count: number; isActive: boolean }> = ({ query, count, isActive }) => (
+
+
+ {query}
+
+ {count}
+
+
+);
+
+const ResultCard: React.FC
= ({ result, index }) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+ {result.published_date && (
+
+
+
+ {new Date(result.published_date).toLocaleDateString()}
+
+
+ )}
+
+ );
+};
+
+const ImageGrid: React.FC<{ images: SearchImage[]; onImageClick: (index: number) => void }> = ({ images, onImageClick }) => (
+
+ {images.slice(0, 4).map((image, index) => (
+
onImageClick(index)}
+ whileHover={{ scale: 1.02 }}
+ >
+
+
+
+ {image.description}
+
+
+ {index === 3 && images.length > 4 && (
+
+ +{images.length - 4}
+
+ )}
+
+ ))}
+
+);
+
+const SearchResults: React.FC = ({ searchData, topicType, onImageClick }) => (
+
+
+
+
+
+
+
+
+
+
Results for “{searchData.query}“
+
+
+ {topicType}
+
+ {searchData.results.length} results
+
+
+
+
+
+
+
+
+ {searchData.results.map((result, index) => (
+
+ ))}
+
+
+
+
+
+ {searchData.images.length > 0 && (
+
+ )}
+
+);
+
+interface ContentDialogProps {
+ isOpen: boolean;
+ onClose: () => void;
+ result: SearchResult;
+}
+
+const ContentDialog: React.FC = ({ isOpen, onClose, result }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+ {result.published_date && (
+
+
+
+ Published on {new Date(result.published_date).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ })}
+
+
+ )}
+
+
+
+
+
+);
+
+
+const MultiSearch: React.FC<{ result: MultiSearchResponse | null; args: MultiSearchArgs }> = ({ result, args }) => {
+ const [activeTab, setActiveTab] = useState("0");
+ const [galleryOpen, setGalleryOpen] = useState(false);
+ const [selectedSearch, setSelectedSearch] = useState(0);
+ const [selectedImage, setSelectedImage] = useState(0);
+
+ // Replace the current loading state in MultiSearch component with this:
+ if (!result) {
+ return (
+
+
+
+
+
+
+
+ Running searches...
+
+
+ Processing {args.queries.length} queries
+
+
+
+
+
+
+ {[0, 1, 2].map((index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {result.searches.map((search, index) => (
+
+
+
+ ))}
+
+
+
+
+
+ {result.searches.map((search, index) => (
+
+ {
+ setSelectedSearch(index);
+ setSelectedImage(imageIndex);
+ setGalleryOpen(true);
+ }}
+ />
+
+ ))}
+
+
+
+ {galleryOpen && result.searches[selectedSearch].images && (
+
+
+
+
+
+ {selectedImage + 1} / {result.searches[selectedSearch].images.length}
+
+ setGalleryOpen(false)}
+ className="p-1.5 rounded-md dark:bg-neutral-800 bg-gray-100 dark:text-neutral-400 text-gray-500 hover:text-gray-700 dark:hover:text-neutral-200"
+ >
+
+
+
+
+
+
+ {result.searches[selectedSearch].images[selectedImage].description && (
+
+
+ {result.searches[selectedSearch].images[selectedImage].description}
+
+
+ )}
+
+
+
+ setSelectedImage(prev =>
+ prev === 0 ? result.searches[selectedSearch].images.length - 1 : prev - 1
+ )}
+ >
+
+
+
+
+
+ setSelectedImage(prev =>
+ prev === result.searches[selectedSearch].images.length - 1 ? 0 : prev + 1
+ )}
+ >
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default MultiSearch;
\ No newline at end of file
diff --git a/components/ui/form-component.tsx b/components/ui/form-component.tsx
index b5e21c0..6c4f757 100644
--- a/components/ui/form-component.tsx
+++ b/components/ui/form-component.tsx
@@ -1,4 +1,5 @@
/* eslint-disable @next/next/no-img-element */
+// /components/ui/form-component.tsx
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { motion } from 'framer-motion';
import { ChatRequestOptions, CreateMessage, Message } from 'ai';
@@ -431,7 +432,7 @@ const FormComponent: React.FC = ({
return (
0 || uploadQueue.length > 0
? "bg-gray-100/70 dark:bg-neutral-800 p-1"
: "bg-transparent"
diff --git a/package.json b/package.json
index a57f969..e5c11a8 100644
--- a/package.json
+++ b/package.json
@@ -11,11 +11,12 @@
"dependencies": {
"@ai-sdk/anthropic": "^0.0.55",
"@ai-sdk/azure": "^0.0.51",
- "@ai-sdk/cohere": "latest",
+ "@ai-sdk/cohere": "^1.0.3",
"@ai-sdk/google": "^0.0.55",
"@ai-sdk/groq": "^0.0.1",
"@ai-sdk/mistral": "^0.0.41",
"@ai-sdk/openai": "^0.0.58",
+ "@ai-sdk/xai": "^1.0.3",
"@e2b/code-interpreter": "^1.0.3",
"@foobar404/wave": "^2.0.5",
"@mendable/firecrawl-js": "^1.4.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 16a327d..4da7c7b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,8 +12,8 @@ dependencies:
specifier: ^0.0.51
version: 0.0.51(zod@3.23.8)
'@ai-sdk/cohere':
- specifier: latest
- version: 1.0.0(zod@3.23.8)
+ specifier: ^1.0.3
+ version: 1.0.3(zod@3.23.8)
'@ai-sdk/google':
specifier: ^0.0.55
version: 0.0.55(zod@3.23.8)
@@ -26,6 +26,9 @@ dependencies:
'@ai-sdk/openai':
specifier: ^0.0.58
version: 0.0.58(zod@3.23.8)
+ '@ai-sdk/xai':
+ specifier: ^1.0.3
+ version: 1.0.3(zod@3.23.8)
'@e2b/code-interpreter':
specifier: ^1.0.3
version: 1.0.3
@@ -279,14 +282,14 @@ packages:
zod: 3.23.8
dev: false
- /@ai-sdk/cohere@1.0.0(zod@3.23.8):
- resolution: {integrity: sha512-iN2Ww2VeRnprQBJ7dCp65DtdzCY/53+CA3UmM7Rhn8IZCTqRHkVoZebzv3ZOTb9pikO4CUWqV9yh1oUhQDgyow==}
+ /@ai-sdk/cohere@1.0.3(zod@3.23.8):
+ resolution: {integrity: sha512-SDjPinUcGzTNiSMN+9zs1fuAcP8rU1/+CmDWAGu7eMhwVGDurgiOqscC0Oqs/aLsodLt/sFeOvyqj86DAknpbg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
dependencies:
- '@ai-sdk/provider': 1.0.0
- '@ai-sdk/provider-utils': 2.0.0(zod@3.23.8)
+ '@ai-sdk/provider': 1.0.1
+ '@ai-sdk/provider-utils': 2.0.2(zod@3.23.8)
zod: 3.23.8
dev: false
@@ -425,8 +428,8 @@ packages:
zod: 3.23.8
dev: false
- /@ai-sdk/provider-utils@2.0.0(zod@3.23.8):
- resolution: {integrity: sha512-uITgVJByhtzuQU2ZW+2CidWRmQqTUTp6KADevy+4aRnmILZxY2LCt+UZ/ZtjJqq0MffwkuQPPY21ExmFAQ6kKA==}
+ /@ai-sdk/provider-utils@2.0.2(zod@3.23.8):
+ resolution: {integrity: sha512-IAvhKhdlXqiSmvx/D4uNlFYCl8dWT+M9K+IuEcSgnE2Aj27GWu8sDIpAf4r4Voc+wOUkOECVKQhFo8g9pozdjA==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
@@ -434,9 +437,9 @@ packages:
zod:
optional: true
dependencies:
- '@ai-sdk/provider': 1.0.0
+ '@ai-sdk/provider': 1.0.1
eventsource-parser: 3.0.0
- nanoid: 5.0.8
+ nanoid: 3.3.7
secure-json-parse: 2.7.0
zod: 3.23.8
dev: false
@@ -469,8 +472,8 @@ packages:
json-schema: 0.4.0
dev: false
- /@ai-sdk/provider@1.0.0:
- resolution: {integrity: sha512-Sj29AzooJ7SYvhPd+AAWt/E7j63E9+AzRnoMHUaJPRYzOd/WDrVNxxv85prF9gDcQ7XPVlSk9j6oAZV9/DXYpA==}
+ /@ai-sdk/provider@1.0.1:
+ resolution: {integrity: sha512-mV+3iNDkzUsZ0pR2jG0sVzU6xtQY5DtSCBy3JFycLp6PwjyLw/iodfL3MwdmMCRJWgs3dadcHejRnMvF9nGTBg==}
engines: {node: '>=18'}
dependencies:
json-schema: 0.4.0
@@ -562,6 +565,17 @@ packages:
- zod
dev: false
+ /@ai-sdk/xai@1.0.3(zod@3.23.8):
+ resolution: {integrity: sha512-Z3ovBU21Wp87EPwkLoP0K4SNkyIzwQk+YAFuBPnRLCSVtBESeMarcI5zDVvBJ0lmQalRX1ZBAs8U1FvQ4T9mqw==}
+ 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
+ dev: false
+
/@alloc/quick-lru@5.2.0:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -5251,12 +5265,6 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- /nanoid@5.0.8:
- resolution: {integrity: sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==}
- engines: {node: ^18 || >=20}
- hasBin: true
- dev: false
-
/natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
diff --git a/tailwind.config.ts b/tailwind.config.ts
index d694d9f..bf8c865 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -88,11 +88,6 @@ const config = {
},
},
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
- safelist: [
- {
- pattern: /katex-.*/,
- },
- ],
} satisfies Config
export default config
\ No newline at end of file