Merge pull request #40 from simplr-sh/feat/safe-envs

Refactor environment variable handling and improve API key management
This commit is contained in:
Zaid Mukaddam 2025-01-06 19:43:14 +05:30 committed by GitHub
commit 690fd9fc79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 4971 additions and 3865 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-****
GROQ_API_KEY=gsk_****
OPENWEATHER_API_KEY=***
E2B_API_KEY=e2b_****
OPENWEATHER_API_KEY=
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,9 +1,10 @@
// app/actions.ts
'use server';
import { serverEnv } from '@/env/server';
import { xai } from '@ai-sdk/xai';
import { generateObject } from 'ai';
import { z } from 'zod';
import { xai } from '@ai-sdk/xai';
export async function suggestQuestions(history: any[]) {
'use server';
@ -18,7 +19,7 @@ export async function suggestQuestions(history: any[]) {
topK: 7,
system:
`You are a search engine query/questions generator. You 'have' to create only '3' questions for the search engine based on the message history which has been provided to you.
The questions should be open-ended and should encourage further discussion while maintaining the whole context. Limit it to 5-10 words per question.
The questions should be open-ended and should encourage further discussion while maintaining the whole context. Limit it to 5-10 words per question.
Always put the user input's context is some way so that the next search knows what to search for exactly.
Try to stick to the context of the conversation and avoid asking questions that are too general or too specific.
For weather based converations sent to you, always generate questions that are about news, sports, or other topics that are not related to the weather.
@ -37,7 +38,7 @@ Do not use pronouns like he, she, him, his, her, etc. in the questions as they b
};
}
const ELEVENLABS_API_KEY = process.env.ELEVENLABS_API_KEY;
const ELEVENLABS_API_KEY = serverEnv.ELEVENLABS_API_KEY;
export async function generateSpeech(text: string, voice: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer' = "alloy") {
@ -121,37 +122,37 @@ const groupTools = {
const groupPrompts = {
web: `
You are an expert AI web search engine called MiniPerplx, designed to help users find information on the internet with no unnecessary chatter.
You are an expert AI web search engine called MiniPerplx, designed to help users find information on the internet with no unnecessary chatter.
Always **run the tool first exactly once** before composing your response. **This is non-negotiable.**
Your goals:
- Stay concious and aware of the guidelines.
- Provide accurate, concise, and well-formatted responses.
- Avoid hallucinations or fabrications. Stick to verified facts and provide proper citations.
- Follow formatting guidelines strictly.
**Today's Date:** ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}
**Today's Date:** ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}
Comply with user requests to the best of your abilities using the appropriate tools. Maintain composure and follow the guidelines.
### Response Guidelines:
1. **Tools First:**
1. **Tools First:**
Plan the tools to run inside the 'thinking_canvas' tool.
Always run the appropriate tool before composing your response.
Do not run the same tool twice with identical parameters as it leads to redundancy and wasted resources. **This is non-negotiable.**
Once you get the content or results from the tools, start writing your response immediately.
2. **Content Rules:**
2. **Content Rules:**
- Responses must be informative, long and detailed, yet clear and concise like a textbook.
- Use structured answers with headings (no H1).
- Use structured answers with headings (no H1).
- Prefer bullet points over plain paragraphs but points can be long.
- Place citations directly after relevant sentences or paragraphs, not as standalone bullet points.
- Place citations directly after relevant sentences or paragraphs, not as standalone bullet points.
- Do not truncate sentences inside citations. Always finish the sentence before placing the citation.
3. **Latex and Currency Formatting:**
- Use '$' for inline equations and '$$' for block equations.
3. **Latex and Currency Formatting:**
- Use '$' for inline equations and '$$' for block equations.
- Avoid using '$' for currency. Use "USD" instead.
### Tool-Specific Guidelines:
#### Thinking Canvas:
@ -163,63 +164,63 @@ const groupPrompts = {
- Don't include the tool parameters in the 'thinking_canvas' tool except the queries of the tools.
#### Multi Query Web Search:
- Use this tool for multiple queries in one call.
- Use this tool for multiple queries in one call.
- Specify the year or "latest" in queries to fetch recent information.
#### Retrieve Tool:
- Use this for extracting information from specific URLs provided.
- Do not use this tool for general web searches.
#### Weather Data:
- Run the tool with the location and date parameters directly no need to plan in the thinking canvas.
- When you get the weather data, talk about the weather conditions and what to wear or do in that weather.
- Answer in paragraphs and no need of citations for this tool.
#### Programming Tool:
- Use this Python-only sandbox for calculations, data analysis, or visualizations.
- Include library installations (!pip install <library_name>) in the code where required.
- Use this Python-only sandbox for calculations, data analysis, or visualizations.
- Include library installations (!pip install <library_name>) in the code where required.
- Use 'plt.show()' for plots, and mention generated URLs for outputs.
#### Nearby Search:
- Use location and radius parameters. Adding the country name improves accuracy.
#### Translation:
- Only use the text_translate tool for user-requested translations.
#### Stock Charts:
- Assume stock names from user queries. Use the programming tool with Python code including 'yfinance'.
- Once the response is ready, talk about the stock's performance and trends, and then finish with the stock chart like this ![Stock Chart](URL).
#### Image Search:
- Analyze image details to determine tool parameters.
#### Movie/TV Show Queries:
- Use relevant tools for trending or specific movie/TV show information. Do not include images in responses.
- For this tool make the exception of just listing the top 5 movies or TV shows in your written response.
### Prohibited Actions:
- Never write your thoughts or preamble before running a tool.
- Avoid running the same tool twice with same parameters.
- Do not include images in responses unless explicitly allowed (e.g., plots from the programming tool).
- Never write your thoughts or preamble before running a tool.
- Avoid running the same tool twice with same parameters.
- Do not include images in responses unless explicitly allowed (e.g., plots from the programming tool).
- Avoid running GUI-based Python code in the programming tool.
- Do not run 'web_search' for stock queries.
### Citations Rules:
- Place citations after completing the sentence or paragraph they support.
- Format: [Source Title](URL).
- Place citations after completing the sentence or paragraph they support.
- Format: [Source Title](URL).
- Ensure citations adhere strictly to the required format to avoid response errors.`,
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" })}.
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 is unpresentable.
Provide summaries, key points, and references.
Latex should be wrapped with $ symbol for inline and $$ for block equations as they are supported in the response.
No matter what happens, always provide the 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.
No matter what happens, always provide the 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.
Citation format: [Author et al. (Year) Title](URL)
Always run the tools first and then write the response.`,
youtube: `You are a YouTube search assistant that helps find relevant videos and channels.
Just call the tool and run the search and then talk in long details in 2-6 paragraphs.
The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}.
The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}.
Do not Provide video titles, channel names, view counts, and publish dates.
Do not talk in bullet points or lists at all costs.
Provide complete explainations of the videos in paragraphs.
@ -227,7 +228,7 @@ const groupPrompts = {
Citation format: [Title](URL ending with parameter t=<no_of_seconds>)
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" })}.
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.

View File

@ -1,21 +1,21 @@
// /app/api/chat/route.ts
import { z } from "zod";
import { xai } from '@ai-sdk/xai'
import Exa from 'exa-js'
import {
convertToCoreMessages,
streamText,
tool,
smoothStream
} from "ai";
import { BlobRequestAbortedError, put } from '@vercel/blob';
import { getGroupConfig } from "@/app/actions";
import { serverEnv } from "@/env/server";
import { xai } from '@ai-sdk/xai';
import CodeInterpreter from "@e2b/code-interpreter";
import FirecrawlApp from '@mendable/firecrawl-js';
import { tavily } from '@tavily/core'
import { getGroupConfig } from "@/app/actions";
import { geolocation, ipAddress } from '@vercel/functions'
import { tavily } from '@tavily/core';
import { Ratelimit } from "@upstash/ratelimit"; // for deno: see above
import { Redis } from "@upstash/redis"; // see below for cloudflare and fastly adapters
import { BlobRequestAbortedError, put } from '@vercel/blob';
import {
convertToCoreMessages,
smoothStream,
streamText,
tool
} from "ai";
import Exa from 'exa-js';
import { z } from "zod";
// Allow streaming responses up to 60 seconds
export const maxDuration = 120;
@ -182,7 +182,7 @@ export async function POST(req: Request) {
searchDepth: ("basic" | "advanced")[];
exclude_domains?: string[];
}) => {
const apiKey = process.env.TAVILY_API_KEY;
const apiKey = serverEnv.TAVILY_API_KEY;
const tvly = tavily({ apiKey });
const includeImageDescriptions = true;
@ -258,7 +258,7 @@ export async function POST(req: Request) {
}),
execute: async ({ query }: { query: string }) => {
try {
const exa = new Exa(process.env.EXA_API_KEY as string);
const exa = new Exa(serverEnv.EXA_API_KEY as string);
const result = await exa.searchAndContents(
query,
@ -304,7 +304,7 @@ export async function POST(req: Request) {
query: z.string().describe("The search query for movies/TV shows"),
}),
execute: async ({ query }: { query: string }) => {
const TMDB_API_KEY = process.env.TMDB_API_KEY;
const TMDB_API_KEY = serverEnv.TMDB_API_KEY;
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
try {
@ -389,7 +389,7 @@ export async function POST(req: Request) {
description: "Get trending movies from TMDB",
parameters: z.object({}),
execute: async () => {
const TMDB_API_KEY = process.env.TMDB_API_KEY;
const TMDB_API_KEY = serverEnv.TMDB_API_KEY;
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
try {
@ -423,7 +423,7 @@ export async function POST(req: Request) {
description: "Get trending TV shows from TMDB",
parameters: z.object({}),
execute: async () => {
const TMDB_API_KEY = process.env.TMDB_API_KEY;
const TMDB_API_KEY = serverEnv.TMDB_API_KEY;
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
try {
@ -460,7 +460,7 @@ export async function POST(req: Request) {
}),
execute: async ({ query }: { query: string }) => {
try {
const exa = new Exa(process.env.EXA_API_KEY as string);
const exa = new Exa(serverEnv.EXA_API_KEY as string);
// Search academic papers with content summary
const result = await exa.searchAndContents(
@ -516,7 +516,7 @@ export async function POST(req: Request) {
}),
execute: async ({ query, no_of_results }: { query: string, no_of_results: number }) => {
try {
const exa = new Exa(process.env.EXA_API_KEY as string);
const exa = new Exa(serverEnv.EXA_API_KEY as string);
// Simple search to get YouTube URLs only
const searchResult = await exa.search(
@ -545,17 +545,17 @@ export async function POST(req: Request) {
try {
// Fetch detailed info from our endpoints
const [detailsResponse, captionsResponse, timestampsResponse] = await Promise.all([
fetch(`${process.env.YT_ENDPOINT}/video-data`, {
fetch(`${serverEnv.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`, {
fetch(`${serverEnv.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`, {
fetch(`${serverEnv.YT_ENDPOINT}/video-timestamps`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: result.url })
@ -595,7 +595,7 @@ export async function POST(req: Request) {
url: z.string().describe("The URL to retrieve the information from."),
}),
execute: async ({ url }: { url: string }) => {
const app = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY });
const app = new FirecrawlApp({ apiKey: serverEnv.FIRECRAWL_API_KEY });
try {
const content = await app.scrapeUrl(url);
if (!content.success || !content.metadata) {
@ -625,7 +625,7 @@ export async function POST(req: Request) {
lon: z.number().describe("The longitude of the location."),
}),
execute: async ({ lat, lon }: { lat: number; lon: number }) => {
const apiKey = process.env.OPENWEATHER_API_KEY;
const apiKey = serverEnv.OPENWEATHER_API_KEY;
const response = await fetch(
`https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${apiKey}`,
);
@ -645,7 +645,7 @@ export async function POST(req: Request) {
console.log("Title:", title);
console.log("Icon:", icon);
const sandbox = await CodeInterpreter.create(process.env.SANDBOX_TEMPLATE_ID!);
const sandbox = await CodeInterpreter.create(serverEnv.SANDBOX_TEMPLATE_ID!);
const execution = await sandbox.runCode(code);
let message = "";
let images = [];
@ -729,14 +729,14 @@ export async function POST(req: Request) {
execute: async ({ query, coordinates }: { query: string; coordinates: number[] }) => {
try {
// Forward geocoding with Google Maps API
const googleApiKey = process.env.GOOGLE_MAPS_API_KEY;
const googleApiKey = serverEnv.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();
// Reverse geocoding with Mapbox
const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
const mapboxToken = serverEnv.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}`
@ -805,7 +805,7 @@ export async function POST(req: Request) {
location?: string;
radius?: number;
}) => {
const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
const mapboxToken = serverEnv.MAPBOX_ACCESS_TOKEN;
let proximity = '';
if (location) {
@ -854,9 +854,9 @@ export async function POST(req: Request) {
from: z.string().describe("The source language (optional, will be auto-detected if not provided)."),
}),
execute: async ({ text, to, from }: { text: string; to: string; from?: string }) => {
const key = process.env.AZURE_TRANSLATOR_KEY;
const key = serverEnv.AZURE_TRANSLATOR_KEY;
const endpoint = "https://api.cognitive.microsofttranslator.com";
const location = process.env.AZURE_TRANSLATOR_LOCATION;
const location = serverEnv.AZURE_TRANSLATOR_LOCATION;
const url = `${endpoint}/translate?api-version=3.0&to=${to}${from ? `&from=${from}` : ''}`;
@ -893,14 +893,14 @@ export async function POST(req: Request) {
type: string;
radius: number;
}) => {
const apiKey = process.env.TRIPADVISOR_API_KEY;
const apiKey = serverEnv.TRIPADVISOR_API_KEY;
let finalLat = latitude;
let finalLng = longitude;
try {
// Try geocoding first
const geocodingData = await fetch(
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(location)}&key=${process.env.GOOGLE_MAPS_API_KEY}`
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(location)}&key=${serverEnv.GOOGLE_MAPS_API_KEY}`
);
const geocoding = await geocodingData.json();
@ -1007,7 +1007,7 @@ export async function POST(req: Request) {
// Get timezone for the location
const tzResponse = await fetch(
`https://maps.googleapis.com/maps/api/timezone/json?location=${details.latitude},${details.longitude}&timestamp=${Math.floor(Date.now() / 1000)}&key=${process.env.GOOGLE_MAPS_API_KEY}`
`https://maps.googleapis.com/maps/api/timezone/json?location=${details.latitude},${details.longitude}&timestamp=${Math.floor(Date.now() / 1000)}&key=${serverEnv.GOOGLE_MAPS_API_KEY}`
);
const tzData = await tzResponse.json();
const timezone = tzData.timeZoneId || 'UTC';
@ -1131,7 +1131,7 @@ export async function POST(req: Request) {
execute: async ({ flight_number }: { flight_number: string }) => {
try {
const response = await fetch(
`https://api.aviationstack.com/v1/flights?access_key=${process.env.AVIATION_STACK_API_KEY}&flight_iata=${flight_number}`
`https://api.aviationstack.com/v1/flights?access_key=${serverEnv.AVIATION_STACK_API_KEY}&flight_iata=${flight_number}`
);
return await response.json();
} catch (error) {

View File

@ -1,10 +1,10 @@
import { list, del, ListBlobResult } from '@vercel/blob';
import { del, list, ListBlobResult } from '@vercel/blob';
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'edge';
export async function GET(req: NextRequest) {
if (req.headers.get('Authorization') !== `Bearer ${process.env.CRON_SECRET}`) {
if (req.headers.get('Authorization') !== `Bearer ${serverEnv.CRON_SECRET}`) {
return new NextResponse('Unauthorized', { status: 401 });
}

View File

@ -1,13 +1,14 @@
"use client";
import { ThemeProvider } from "next-themes"
import { ReactNode } from "react"
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { clientEnv } from "@/env/client";
import { ThemeProvider } from "next-themes";
import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';
import { ReactNode } from "react";
if (typeof window !== 'undefined') {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
posthog.init(clientEnv.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: clientEnv.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'always',
})
}

View File

@ -1,7 +1,8 @@
import React, { useEffect, useRef, useCallback } from 'react';
import { clientEnv } from "@/env/client";
import { cn } from "@/lib/utils";
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import { cn } from "@/lib/utils";
import React, { useCallback, useEffect, useRef } from 'react';
interface Location {
lat: number;
@ -38,7 +39,7 @@ interface Place {
timezone?: string;
}
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || '';
mapboxgl.accessToken = clientEnv.NEXT_PUBLIC_MAPBOX_TOKEN || '';
interface InteractiveMapProps {
center: Location;

View File

@ -1,10 +1,11 @@
// /app/components/map-components.tsx
import React, { useEffect, useRef } from 'react';
import { Skeleton } from "@/components/ui/skeleton";
import { clientEnv } from "@/env/client";
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import { Skeleton } from "@/components/ui/skeleton";
import React, { useEffect, useRef } from 'react';
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || '';
mapboxgl.accessToken = clientEnv.NEXT_PUBLIC_MAPBOX_TOKEN || '';
interface Location {
lat: number;
@ -171,4 +172,4 @@ const MapContainer: React.FC<MapContainerProps> = ({
);
};
export { MapComponent, MapSkeleton, MapContainer };
export { MapComponent, MapContainer, MapSkeleton };

19
env/client.ts vendored Normal file
View File

@ -0,0 +1,19 @@
// https://env.t3.gg/docs/nextjs#create-your-schema
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'
export const clientEnv = createEnv({
client: {
NEXT_PUBLIC_MAPBOX_TOKEN: z.string().min(1),
NEXT_PUBLIC_POSTHOG_KEY: z.string().min(1),
NEXT_PUBLIC_POSTHOG_HOST: z.string().min(1).url(),
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: z.string().min(1).url(),
},
runtimeEnv: {
NEXT_PUBLIC_MAPBOX_TOKEN: process.env.NEXT_PUBLIC_MAPBOX_TOKEN,
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY
},
})

30
env/server.ts vendored Normal file
View File

@ -0,0 +1,30 @@
// https://env.t3.gg/docs/nextjs#create-your-schema
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'
export const serverEnv = createEnv({
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),
TAVILY_API_KEY: z.string().min(1),
EXA_API_KEY: z.string().min(1),
TMDB_API_KEY: z.string().min(1),
YT_ENDPOINT: z.string().min(1),
FIRECRAWL_API_KEY: z.string().min(1),
OPENWEATHER_API_KEY: z.string().min(1),
SANDBOX_TEMPLATE_ID: z.string().min(1),
GOOGLE_MAPS_API_KEY: z.string().min(1),
MAPBOX_ACCESS_TOKEN: z.string().min(1),
AZURE_TRANSLATOR_KEY: 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),
AVIATION_STACK_API_KEY: z.string().min(1),
CRON_SECRET: z.string().min(1),
BLOB_READ_WRITE_TOKEN: z.string().min(1),
},
experimental__runtimeEnv: process.env,
})

View File

@ -1,3 +1,12 @@
// https://env.t3.gg/docs/nextjs#validate-schema-on-build-(recommended)
import { createJiti } from 'jiti'
import { fileURLToPath } from 'node:url'
const jiti = createJiti(fileURLToPath(import.meta.url))
// Import env here to validate during build. Using jiti we can import .ts files :)
jiti.import('./env/server')
jiti.import('./env/client')
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ["geist"],

View File

@ -29,6 +29,7 @@
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@t3-oss/env-nextjs": "^0.11.1",
"@tailwindcss/typography": "^0.5.13",
"@tavily/core": "^0.0.2",
"@types/katex": "^0.16.7",
@ -54,6 +55,7 @@
"geist": "^1.3.1",
"google-auth-library": "^9.14.1",
"highlight.js": "^11.10.0",
"jiti": "^2.4.2",
"katex": "^0.16.11",
"lucide-react": "^0.424.0",
"luxon": "^3.5.0",

File diff suppressed because it is too large Load Diff