Compare commits

...

10 Commits

Author SHA1 Message Date
d468ecca74 Add Docker Compose configuration for web, code-interpreter, and Redis services
- Introduced a new `docker-compose.yaml` file to define services for the application.
- Configured the `web` service with environment variables for production and port mapping.
- Added a `code-interpreter` service with its own environment variable and volume setup.
- Included a `redis` service using the Alpine image with persistent volume for data storage.
- Ensured proper dependency management between services.
2025-01-09 09:04:08 +03:00
Zaid Mukaddam
15d5ac5a61
Merge pull request #50 from simplr-sh/feat/nuqs
Add Nuqs integration and update package dependencies
2025-01-06 22:40:47 +05:30
simplr-sh
706f53f3ed Add Nuqs integration and update package dependencies 2025-01-06 22:15:09 +05:30
zaidmukaddam
8247f9b4ea Import server environment variables in clean_images API route 2025-01-06 19:50:22 +05:30
zaidmukaddam
527e46453d Refactor Google Trends fetching to use a default country code and simplify environment variable validation 2025-01-06 19:46:33 +05:30
Zaid Mukaddam
0289fc44df
Merge pull request #41 from simplr-sh/fix/fonts
Fix Font Handling: Enhance Tailwind CSS configuration and update global styles
2025-01-06 19:43:56 +05:30
Zaid Mukaddam
690fd9fc79
Merge pull request #40 from simplr-sh/feat/safe-envs
Refactor environment variable handling and improve API key management
2025-01-06 19:43:14 +05:30
simplr-sh
74d4d8d5a0 updated envs list 2025-01-06 19:34:28 +05:30
simplr-sh
fba1ccb0ef Enhance Tailwind CSS configuration and update global styles
- Updated Tailwind CSS configuration to include default font families for sans, serif, and mono.
- Removed unused font imports and CSS variables from globals.css.
- Improved layout component by organizing imports and ensuring proper font usage with fallback options.
- Added preload and display settings for the Instrument Serif font.
2025-01-06 12:06:46 +05:30
simplr-sh
f8ce4bd871 Refactor next.config.mjs for improved readability and consistency
- Reformatted the structure of the `next.config.mjs` file for better readability.
- Ensured consistent indentation and spacing throughout the configuration.
- No functional changes were made; the configuration remains the same.
2025-01-06 10:38:01 +05:30
13 changed files with 385 additions and 254 deletions

View File

@ -1,5 +1,28 @@
ANTHROPIC_API_KEY=sk-ant-api**** # Strictly Server side Env variables
XAI_API_KEY=
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
AVIATION_STACK_API_KEY=
SANDBOX_TEMPLATE_ID=
TMDB_API_KEY=
YT_ENDPOINT=
EXA_API_KEY=
TRIPADVISOR_API_KEY=
BLOB_READ_WRITE_TOKEN=
ELEVENLABS_API_KEY=
AZURE_TRANSLATOR_LOCATION=
AZURE_TRANSLATOR_KEY=
AZURE_RESOURCE_NAME=
AZURE_API_KEY=
MAPBOX_ACCESS_TOKEN=
FIRECRAWL_API_KEY=
TAVILY_API_KEY=tvly-**** TAVILY_API_KEY=tvly-****
GROQ_API_KEY=gsk_**** OPENWEATHER_API_KEY=
OPENWEATHER_API_KEY=*** E2B_API_KEY=e2b_****
E2B_API_KEY=e2b_**** GOOGLE_MAPS_API_KEY=
# Client side Env variables
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=
NEXT_PUBLIC_MAPBOX_TOKEN=
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=

View File

@ -1,3 +1,4 @@
import { serverEnv } from '@/env/server';
import { del, list, ListBlobResult } from '@vercel/blob'; import { del, list, ListBlobResult } from '@vercel/blob';
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';

View File

@ -1,7 +1,6 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { generateObject } from 'ai'; import { generateObject } from 'ai';
import { z } from 'zod'; import { z } from 'zod';
import { geolocation } from '@vercel/functions';
import { xai } from '@ai-sdk/xai'; import { xai } from '@ai-sdk/xai';
export interface TrendingQuery { export interface TrendingQuery {
@ -16,7 +15,7 @@ interface RedditPost {
}; };
} }
async function fetchGoogleTrends(countryCode: string = 'US'): Promise<TrendingQuery[]> { async function fetchGoogleTrends(): Promise<TrendingQuery[]> {
const fetchTrends = async (geo: string): Promise<TrendingQuery[]> => { const fetchTrends = async (geo: string): Promise<TrendingQuery[]> => {
try { try {
const response = await fetch(`https://trends.google.com/trends/trendingsearches/daily/rss?geo=${geo}`, { const response = await fetch(`https://trends.google.com/trends/trendingsearches/daily/rss?geo=${geo}`, {
@ -62,7 +61,7 @@ async function fetchGoogleTrends(countryCode: string = 'US'): Promise<TrendingQu
} }
}; };
const trends = await fetchTrends(countryCode); const trends = await fetchTrends("US");
return [ ...trends]; return [ ...trends];
} }
@ -95,11 +94,11 @@ async function fetchRedditQuestions(): Promise<TrendingQuery[]> {
} }
} }
async function fetchFromMultipleSources(countryCode: string) { async function fetchFromMultipleSources() {
const [googleTrends, const [googleTrends,
// redditQuestions // redditQuestions
] = await Promise.all([ ] = await Promise.all([
fetchGoogleTrends(countryCode), fetchGoogleTrends(),
// fetchRedditQuestions(), // fetchRedditQuestions(),
]); ]);
@ -112,11 +111,11 @@ async function fetchFromMultipleSources(countryCode: string) {
export async function GET(req: Request) { export async function GET(req: Request) {
try { try {
const countryCode = geolocation(req).countryRegion ?? 'US'; const trends = await fetchFromMultipleSources();
const trends = await fetchFromMultipleSources(countryCode);
if (trends.length === 0) { if (trends.length === 0) {
// Fallback queries if both sources fail // Fallback queries if both sources fail
console.error('Both sources failed to fetch trends, returning fallback queries');
return NextResponse.json([ return NextResponse.json([
{ {
icon: 'sparkles', icon: 'sparkles',

View File

@ -1,16 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Instrument+Serif:wght@400&display=swap');
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root {
--font-serif: "Instrument Serif", serif;
}
body {
font-family: var(--font-sans), sans-serif;
}
.homeBtn { .homeBtn {
box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px,
rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset,

View File

@ -1,12 +1,13 @@
import "./globals.css"; import { Analytics } from "@vercel/analytics/react";
import { GeistSans } from 'geist/font/sans';
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import 'mapbox-gl/dist/mapbox-gl.css'; import 'mapbox-gl/dist/mapbox-gl.css';
import { Metadata, Viewport } from "next"; import { Metadata, Viewport } from "next";
import { Toaster } from "sonner";
import { Instrument_Serif } from 'next/font/google'; import { Instrument_Serif } from 'next/font/google';
import { Analytics } from "@vercel/analytics/react"; import { NuqsAdapter } from 'nuqs/adapters/next/app';
import { Providers } from './providers' import { Toaster } from "sonner";
import { GeistSans } from 'geist/font/sans'; import "./globals.css";
import { Providers } from './providers';
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL("https://mplx.run"), metadataBase: new URL("https://mplx.run"),
@ -43,7 +44,11 @@ export const viewport: Viewport = {
const instrumentSerif = Instrument_Serif({ const instrumentSerif = Instrument_Serif({
weight: "400", weight: "400",
subsets: ["latin"], subsets: ["latin"],
variable: "--font-serif" style: ['normal', 'italic'],
variable: "--font-serif",
preload: true,
display: 'swap',
fallback: ['sans-serif'],
}) })
export default function RootLayout({ export default function RootLayout({
@ -53,11 +58,13 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body className={` ${GeistSans.className} ${instrumentSerif.className}`}> <body className={`${GeistSans.variable} ${instrumentSerif.variable} font-sans antialiased`}>
<Providers> <NuqsAdapter>
<Toaster position="top-center" richColors /> <Providers>
{children} <Toaster position="top-center" richColors />
</Providers> {children}
</Providers>
</NuqsAdapter>
<Analytics /> <Analytics />
</body> </body>
</html> </html>

View File

@ -2,137 +2,133 @@
"use client"; "use client";
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import
React,
{
useRef,
useCallback,
useState,
useEffect,
useMemo,
Suspense
} from 'react';
import ReactMarkdown from 'react-markdown';
import { useTheme } from 'next-themes';
import Marked, { ReactRenderer } from 'marked-react';
import Latex from 'react-latex-next';
import { useSearchParams } from 'next/navigation';
import { useChat } from 'ai/react';
import { ToolInvocation } from 'ai';
import { toast } from 'sonner';
import { motion, AnimatePresence } from 'framer-motion';
import Image from 'next/image';
import {
fetchMetadata,
generateSpeech,
suggestQuestions
} from './actions';
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 {
Sparkles,
ArrowRight,
Globe,
AlignLeft,
Copy,
Cloud,
Code,
Check,
Loader2,
User2,
Heart,
X,
MapPin,
Plus,
Download,
Flame,
Sun,
Pause,
Play,
TrendingUpIcon,
Calendar,
Calculator,
ChevronDown,
Edit2,
ChevronUp,
Moon,
Star,
YoutubeIcon,
LucideIcon,
FileText,
Book,
ExternalLink,
Building,
Users,
Brain,
TrendingUp,
Plane,
Film,
Tv,
ListTodo
} from 'lucide-react';
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer";
import { GitHubLogoIcon, TextIcon } from '@radix-ui/react-icons';
import Link from 'next/link';
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel";
import { cn, SearchGroupId } from '@/lib/utils';
import {
Table,
TableBody,
TableCell,
TableRow,
} from "@/components/ui/table";
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 { MapComponent, MapContainer } from '@/components/map-components';
import MultiSearch from '@/components/multi-search';
import { CurrencyDollar, Flag, RoadHorizon, SoccerBall, TennisBall, XLogo } from '@phosphor-icons/react';
import { BorderTrail } from '@/components/core/border-trail'; import { BorderTrail } from '@/components/core/border-trail';
import { TextShimmer } from '@/components/core/text-shimmer'; import { TextShimmer } from '@/components/core/text-shimmer';
import { Tweet } from 'react-tweet';
import NearbySearchMapView from '@/components/nearby-search-map-view';
import { Separator } from '@/components/ui/separator';
import { TrendingQuery } from './api/trending/route';
import { FlightTracker } from '@/components/flight-tracker'; import { FlightTracker } from '@/components/flight-tracker';
import { InstallPrompt } from '@/components/InstallPrompt'; import { InstallPrompt } from '@/components/InstallPrompt';
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import InteractiveChart from '@/components/interactive-charts';
import { vs } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import { MapComponent, MapContainer } from '@/components/map-components';
import { useMediaQuery } from '@/hooks/use-media-query';
import TMDBResult from '@/components/movie-info'; import TMDBResult from '@/components/movie-info';
import MultiSearch from '@/components/multi-search';
import NearbySearchMapView from '@/components/nearby-search-map-view';
import TrendingResults from '@/components/trending-tv-movies-results'; import TrendingResults from '@/components/trending-tv-movies-results';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer";
import FormComponent from '@/components/ui/form-component';
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
import {
Table,
TableBody,
TableCell,
TableRow,
} from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import WeatherChart from '@/components/weather-chart';
import { useMediaQuery } from '@/hooks/use-media-query';
import { cn, SearchGroupId } from '@/lib/utils';
import { Wave } from "@foobar404/wave";
import { CurrencyDollar, Flag, RoadHorizon, SoccerBall, TennisBall, XLogo } from '@phosphor-icons/react';
import { GitHubLogoIcon, TextIcon } from '@radix-ui/react-icons';
import { ToolInvocation } from 'ai';
import { useChat } from 'ai/react';
import Autoplay from 'embla-carousel-autoplay';
import { AnimatePresence, motion } from 'framer-motion';
import { GeistMono } from 'geist/font/mono'; import { GeistMono } from 'geist/font/mono';
import {
AlignLeft,
ArrowRight,
Book,
Brain,
Building,
Calculator,
Calendar,
Check,
ChevronDown,
ChevronUp,
Cloud,
Code,
Copy,
Download,
Edit2,
ExternalLink,
FileText,
Film,
Flame,
Globe,
Heart,
ListTodo,
Loader2,
LucideIcon,
MapPin,
Moon,
Pause,
Plane,
Play,
Plus,
Sparkles,
Sun,
TrendingUp,
TrendingUpIcon,
Tv,
User2,
Users,
X,
YoutubeIcon
} from 'lucide-react';
import Marked, { ReactRenderer } from 'marked-react';
import { useTheme } from 'next-themes';
import Image from 'next/image';
import Link from 'next/link';
import { parseAsString, useQueryState } from 'nuqs';
import React, {
Suspense,
useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react';
import Latex from 'react-latex-next';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark, vs } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { Tweet } from 'react-tweet';
import { toast } from 'sonner';
import {
fetchMetadata,
generateSpeech,
suggestQuestions
} from './actions';
import { TrendingQuery } from './api/trending/route';
export const maxDuration = 60; export const maxDuration = 60;
@ -563,12 +559,15 @@ const SponsorDialog = ({
}; };
const HomeContent = () => { const HomeContent = () => {
const searchParams = useSearchParams(); const [query] = useQueryState('query', parseAsString.withDefault(''))
const [q] = useQueryState('q', parseAsString.withDefault(''))
const [model] = useQueryState('model', parseAsString.withDefault('grok-2-1212'))
// Memoize initial values to prevent re-calculation // Memoize initial values to prevent re-calculation
const initialState = useMemo(() => ({ const initialState = useMemo(() => ({
query: searchParams.get('query') || searchParams.get('q') || '', query: query || q,
model: searchParams.get('model') || 'grok-2-1212' model: model
// eslint-disable-next-line react-hooks/exhaustive-deps
}), []); // Empty dependency array as we only want this on mount }), []); // Empty dependency array as we only want this on mount
const lastSubmittedQueryRef = useRef(initialState.query); const lastSubmittedQueryRef = useRef(initialState.query);
@ -699,6 +698,7 @@ const HomeContent = () => {
}; };
fetchTrending(); fetchTrending();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const ThemeToggle: React.FC = () => { const ThemeToggle: React.FC = () => {
@ -758,12 +758,12 @@ const HomeContent = () => {
id: "1", id: "1",
title: "The Unexpected Collab", title: "The Unexpected Collab",
images: [ images: [
"https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-collab.jpeg", "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-collab.jpeg",
], ],
content: ` content: `
## **MiniPerplx x Vercel x xAI Collab** ## **MiniPerplx x Vercel x xAI Collab**
Excited to annouce that MiniPerplx has partnered with Vercel and xAI to bring you the best of AI search experience. Excited to annouce that MiniPerplx has partnered with Vercel and xAI to bring you the best of AI search experience.
Grok 2 models are now available for you to try out. Grok 2 models are now available for you to try out.
` `
} }
@ -892,10 +892,11 @@ Grok 2 models are now available for you to try out.
const waveRef = useRef<Wave | null>(null); const waveRef = useRef<Wave | null>(null);
useEffect(() => { useEffect(() => {
const _audioRef = audioRef.current
return () => { return () => {
if (audioRef.current) { if (_audioRef) {
audioRef.current.pause(); _audioRef.pause();
audioRef.current.src = ''; _audioRef.src = '';
} }
}; };
}, []); }, []);
@ -1289,7 +1290,7 @@ Grok 2 models are now available for you to try out.
return <TrendingResults result={result} type="tv" />; return <TrendingResults result={result} type="tv" />;
} }
if (toolInvocation.toolName === 'x_search') { if (toolInvocation.toolName === 'x_search') {
if (!result) { if (!result) {
return <SearchLoadingState return <SearchLoadingState
@ -2117,10 +2118,10 @@ Grok 2 models are now available for you to try out.
<button <button
onClick={handleCopy} onClick={handleCopy}
className={` className={`
px-2 py-1.5 px-2 py-1.5
rounded-md text-xs rounded-md text-xs
transition-colors duration-200 transition-colors duration-200
${isCopied ? 'bg-green-500/10 text-green-500' : 'bg-neutral-100 dark:bg-neutral-800 text-neutral-500 dark:text-neutral-400'} ${isCopied ? 'bg-green-500/10 text-green-500' : 'bg-neutral-100 dark:bg-neutral-800 text-neutral-500 dark:text-neutral-400'}
opacity-0 group-hover:opacity-100 opacity-0 group-hover:opacity-100
hover:bg-neutral-200 dark:hover:bg-neutral-700 hover:bg-neutral-200 dark:hover:bg-neutral-700
flex items-center gap-1.5 flex items-center gap-1.5
@ -2541,8 +2542,8 @@ Grok 2 models are now available for you to try out.
<button <button
key={`${index}-${query.text}`} key={`${index}-${query.text}`}
onClick={() => handleExampleClick(query)} onClick={() => handleExampleClick(query)}
className="group flex-shrink-0 bg-neutral-50/50 dark:bg-neutral-800/50 className="group flex-shrink-0 bg-neutral-50/50 dark:bg-neutral-800/50
backdrop-blur-sm rounded-xl p-3.5 text-left backdrop-blur-sm rounded-xl p-3.5 text-left
hover:bg-neutral-100 dark:hover:bg-neutral-700/70 hover:bg-neutral-100 dark:hover:bg-neutral-700/70
transition-all duration-200 ease-out transition-all duration-200 ease-out
hover:scale-102 origin-center hover:scale-102 origin-center
@ -2588,6 +2589,7 @@ Grok 2 models are now available for you to try out.
<SuggestionCards <SuggestionCards
trendingQueries={trendingQueries} trendingQueries={trendingQueries}
/> />
// eslint-disable-next-line react-hooks/exhaustive-deps
), [trendingQueries]); ), [trendingQueries]);
return ( return (

60
docker-compose.yaml Normal file
View File

@ -0,0 +1,60 @@
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- XAI_API_KEY=${XAI_API_KEY}
- UPSTASH_REDIS_REST_URL=${UPSTASH_REDIS_REST_URL}
- UPSTASH_REDIS_REST_TOKEN=${UPSTASH_REDIS_REST_TOKEN}
- ELEVENLABS_API_KEY=${ELEVENLABS_API_KEY}
- TAVILY_API_KEY=${TAVILY_API_KEY}
- EXA_API_KEY=${EXA_API_KEY}
- TMDB_API_KEY=${TMDB_API_KEY}
- YT_ENDPOINT=${YT_ENDPOINT}
- FIRECRAWL_API_KEY=${FIRECRAWL_API_KEY}
- OPENWEATHER_API_KEY=${OPENWEATHER_API_KEY}
- SANDBOX_TEMPLATE_ID=${SANDBOX_TEMPLATE_ID}
- GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}
- MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN}
- AZURE_TRANSLATOR_KEY=${AZURE_TRANSLATOR_KEY}
- AZURE_TRANSLATOR_LOCATION=${AZURE_TRANSLATOR_LOCATION}
- AZURE_RESOURCE_NAME=${AZURE_RESOURCE_NAME}
- AZURE_API_KEY=${AZURE_API_KEY}
- TRIPADVISOR_API_KEY=${TRIPADVISOR_API_KEY}
- AVIATION_STACK_API_KEY=${AVIATION_STACK_API_KEY}
- CRON_SECRET=${CRON_SECRET}
- BLOB_READ_WRITE_TOKEN=${BLOB_READ_WRITE_TOKEN}
- NEXT_PUBLIC_MAPBOX_TOKEN=${NEXT_PUBLIC_MAPBOX_TOKEN}
- NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
- NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST}
- NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=${NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}
volumes:
- .:/app
- /app/node_modules
depends_on:
- code-interpreter
code-interpreter:
build:
context: .
dockerfile: e2b.Dockerfile
environment:
- E2B_API_KEY=${E2B_API_KEY}
volumes:
- ./certificates:/app/certificates
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
redis_data:

3
env/client.ts vendored
View File

@ -7,10 +7,13 @@ export const clientEnv = createEnv({
NEXT_PUBLIC_MAPBOX_TOKEN: z.string().min(1), NEXT_PUBLIC_MAPBOX_TOKEN: z.string().min(1),
NEXT_PUBLIC_POSTHOG_KEY: z.string().min(1), NEXT_PUBLIC_POSTHOG_KEY: z.string().min(1),
NEXT_PUBLIC_POSTHOG_HOST: z.string().min(1).url(), NEXT_PUBLIC_POSTHOG_HOST: z.string().min(1).url(),
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: z.string().min(1),
}, },
runtimeEnv: { runtimeEnv: {
NEXT_PUBLIC_MAPBOX_TOKEN: process.env.NEXT_PUBLIC_MAPBOX_TOKEN, NEXT_PUBLIC_MAPBOX_TOKEN: process.env.NEXT_PUBLIC_MAPBOX_TOKEN,
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST, NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY
}, },
}) })

6
env/server.ts vendored
View File

@ -4,6 +4,9 @@ import { z } from 'zod'
export const serverEnv = createEnv({ export const serverEnv = createEnv({
server: { server: {
XAI_API_KEY: z.string().min(1),
UPSTASH_REDIS_REST_URL: z.string().min(1).url(),
UPSTASH_REDIS_REST_TOKEN: z.string().min(1),
ELEVENLABS_API_KEY: z.string().min(1), ELEVENLABS_API_KEY: z.string().min(1),
TAVILY_API_KEY: z.string().min(1), TAVILY_API_KEY: z.string().min(1),
EXA_API_KEY: z.string().min(1), EXA_API_KEY: z.string().min(1),
@ -16,9 +19,12 @@ export const serverEnv = createEnv({
MAPBOX_ACCESS_TOKEN: z.string().min(1), MAPBOX_ACCESS_TOKEN: z.string().min(1),
AZURE_TRANSLATOR_KEY: z.string().min(1), AZURE_TRANSLATOR_KEY: z.string().min(1),
AZURE_TRANSLATOR_LOCATION: z.string().min(1), AZURE_TRANSLATOR_LOCATION: z.string().min(1),
AZURE_RESOURCE_NAME: z.string().min(1),
AZURE_API_KEY: z.string().min(1),
TRIPADVISOR_API_KEY: z.string().min(1), TRIPADVISOR_API_KEY: z.string().min(1),
AVIATION_STACK_API_KEY: z.string().min(1), AVIATION_STACK_API_KEY: z.string().min(1),
CRON_SECRET: z.string().min(1), CRON_SECRET: z.string().min(1),
BLOB_READ_WRITE_TOKEN: z.string().min(1),
}, },
experimental__runtimeEnv: process.env, experimental__runtimeEnv: process.env,
}) })

View File

@ -9,86 +9,86 @@ jiti.import('./env/client')
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
transpilePackages: ['geist'], transpilePackages: ["geist"],
async headers() { async headers() {
return [ return [
{ {
source: '/(.*)', source: '/(.*)',
headers: [ headers: [
{ {
key: 'X-Content-Type-Options', key: 'X-Content-Type-Options',
value: 'nosniff', value: 'nosniff',
}, },
{ {
key: 'X-Frame-Options', key: 'X-Frame-Options',
value: 'DENY', value: 'DENY',
}, },
{ {
key: 'Referrer-Policy', key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin', value: 'strict-origin-when-cross-origin',
}, },
], ],
}, },
] ]
}, },
images: { images: {
dangerouslyAllowSVG: true, dangerouslyAllowSVG: true,
remotePatterns: [ remotePatterns: [
{ {
protocol: 'https', protocol: 'https',
hostname: 'www.google.com', hostname: 'www.google.com',
port: '', port: '',
pathname: '/s2/favicons', pathname: '/s2/favicons',
}, },
{ {
protocol: 'https', protocol: 'https',
hostname: 'api.producthunt.com', hostname: 'api.producthunt.com',
port: '', port: '',
pathname: '/widgets/embed-image/v1/featured.svg', pathname: '/widgets/embed-image/v1/featured.svg',
}, },
{ {
protocol: 'https', protocol: 'https',
hostname: 'metwm7frkvew6tn1.public.blob.vercel-storage.com', hostname: 'metwm7frkvew6tn1.public.blob.vercel-storage.com',
port: '', port: '',
pathname: '**', pathname: "**"
}, },
// upload.wikimedia.org // upload.wikimedia.org
{ {
protocol: 'https', protocol: 'https',
hostname: 'upload.wikimedia.org', hostname: 'upload.wikimedia.org',
port: '', port: '',
pathname: '**', pathname: '**'
}, },
// media.theresanaiforthat.com // media.theresanaiforthat.com
{ {
protocol: 'https', protocol: 'https',
hostname: 'media.theresanaiforthat.com', hostname: 'media.theresanaiforthat.com',
port: '', port: '',
pathname: '**', pathname: '**'
}, },
// www.uneed.best // www.uneed.best
{ {
protocol: 'https', protocol: 'https',
hostname: 'www.uneed.best', hostname: 'www.uneed.best',
port: '', port: '',
pathname: '**', pathname: '**'
}, },
// image.tmdb.org // image.tmdb.org
{ {
protocol: 'https', protocol: 'https',
hostname: 'image.tmdb.org', hostname: 'image.tmdb.org',
port: '', port: '',
pathname: '/t/p/original/**', pathname: '/t/p/original/**'
}, },
// image.tmdb.org // image.tmdb.org
{ {
protocol: 'https', protocol: 'https',
hostname: 'image.tmdb.org', hostname: 'image.tmdb.org',
port: '', port: '',
pathname: '/**', pathname: '/**'
}, },
], ]
}, },
} };
export default nextConfig export default nextConfig;

View File

@ -64,6 +64,7 @@
"motion": "^11.13.5", "motion": "^11.13.5",
"next": "^14.2.21", "next": "^14.2.21",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"nuqs": "^2.3.0",
"openai": "^4.56.0", "openai": "^4.56.0",
"posthog-js": "^1.202.2", "posthog-js": "^1.202.2",
"react": "^18", "react": "^18",

View File

@ -173,6 +173,9 @@ importers:
next-themes: next-themes:
specifier: ^0.3.0 specifier: ^0.3.0
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nuqs:
specifier: ^2.3.0
version: 2.3.0(next@14.2.21(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
openai: openai:
specifier: ^4.56.0 specifier: ^4.56.0
version: 4.67.2(zod@3.24.1) version: 4.67.2(zod@3.24.1)
@ -2786,6 +2789,9 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
motion-dom@11.13.0: motion-dom@11.13.0:
resolution: {integrity: sha512-Oc1MLGJQ6nrvXccXA89lXtOqFyBmvHtaDcTRGT66o8Czl7nuA8BeHAd9MQV1pQKX0d2RHFBFaw5g3k23hQJt0w==} resolution: {integrity: sha512-Oc1MLGJQ6nrvXccXA89lXtOqFyBmvHtaDcTRGT66o8Czl7nuA8BeHAd9MQV1pQKX0d2RHFBFaw5g3k23hQJt0w==}
@ -2877,6 +2883,24 @@ packages:
nth-check@2.1.1: nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
nuqs@2.3.0:
resolution: {integrity: sha512-ChS56bJZdaTQzCJb6jPel6cIHYh8/V/GSIjZoIe5yAssGdcrVaBFBgzHfJW6IewbR6yc1Zch2CmGsdgztR+xmA==}
peerDependencies:
'@remix-run/react': '>=2'
next: '>=14.2.0'
react: '>=18.2.0 || ^19.0.0-0'
react-router: ^7
react-router-dom: ^6 || ^7
peerDependenciesMeta:
'@remix-run/react':
optional: true
next:
optional: true
react-router:
optional: true
react-router-dom:
optional: true
object-assign@4.1.1: object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -6735,6 +6759,8 @@ snapshots:
minipass@7.1.2: {} minipass@7.1.2: {}
mitt@3.0.1: {}
motion-dom@11.13.0: {} motion-dom@11.13.0: {}
motion-utils@11.13.0: {} motion-utils@11.13.0: {}
@ -6808,6 +6834,13 @@ snapshots:
dependencies: dependencies:
boolbase: 1.0.0 boolbase: 1.0.0
nuqs@2.3.0(next@14.2.21(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
dependencies:
mitt: 3.0.1
react: 18.3.1
optionalDependencies:
next: 14.2.21(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
object-assign@4.1.1: {} object-assign@4.1.1: {}
object-hash@3.0.0: {} object-hash@3.0.0: {}

View File

@ -1,4 +1,5 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
import { fontFamily } from 'tailwindcss/defaultTheme';
const config = { const config = {
darkMode: ["class"], darkMode: ["class"],
@ -23,9 +24,9 @@ const config = {
'screen-small': '100svh', 'screen-small': '100svh',
}, },
fontFamily: { fontFamily: {
sans: ['var(--font-geist-sans)'], sans: ['var(--font-geist-sans)', ...fontFamily.sans],
serif: ['var(--font-serif)'], serif: ['var(--font-serif)', ...fontFamily.serif],
mono: ['var(--font-geist-mono)'], mono: ['var(--font-geist-mono)', ...fontFamily.mono],
}, },
colors: { colors: {
border: "hsl(var(--border))", border: "hsl(var(--border))",
@ -87,7 +88,11 @@ const config = {
}, },
}, },
}, },
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography"),require("tailwind-scrollbar")], plugins: [
require("tailwindcss-animate"),
require("@tailwindcss/typography"),
require("tailwind-scrollbar")
],
} satisfies Config } satisfies Config
export default config export default config