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`}>
<NuqsAdapter>
<Providers> <Providers>
<Toaster position="top-center" richColors /> <Toaster position="top-center" richColors />
{children} {children}
</Providers> </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 import { BorderTrail } from '@/components/core/border-trail';
React, import { TextShimmer } from '@/components/core/text-shimmer';
{ import { FlightTracker } from '@/components/flight-tracker';
useRef, import { InstallPrompt } from '@/components/InstallPrompt';
useCallback, import InteractiveChart from '@/components/interactive-charts';
useState, import { MapComponent, MapContainer } from '@/components/map-components';
useEffect, import TMDBResult from '@/components/movie-info';
useMemo, import MultiSearch from '@/components/multi-search';
Suspense import NearbySearchMapView from '@/components/nearby-search-map-view';
} from 'react'; import TrendingResults from '@/components/trending-tv-movies-results';
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 { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
} from "@/components/ui/accordion"; } 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 { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { import {
Card, Card,
CardContent, CardContent,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } 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 { Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel";
import { cn, SearchGroupId } from '@/lib/utils'; 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 { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import Autoplay from 'embla-carousel-autoplay'; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import FormComponent from '@/components/ui/form-component'; import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import WeatherChart from '@/components/weather-chart'; 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 { 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 { InstallPrompt } from '@/components/InstallPrompt';
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import { vs } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import { useMediaQuery } from '@/hooks/use-media-query'; import { useMediaQuery } from '@/hooks/use-media-query';
import TMDBResult from '@/components/movie-info'; import { cn, SearchGroupId } from '@/lib/utils';
import TrendingResults from '@/components/trending-tv-movies-results'; 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 = () => {
@ -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 = '';
} }
}; };
}, []); }, []);
@ -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,7 +9,7 @@ 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 [
{ {
@ -50,45 +50,45 @@ const nextConfig = {
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