mirror of
https://github.com/run-llama/auto_rfp.git
synced 2026-06-30 21:57:56 -04:00
Refactor environment variable management with centralized validation (#45)
* Refactor environment variable management with centralized validation - Replace plain object with Map-based env structure - Add all environment variables (not just LlamaCloud ones) - Use sentinel value 'mandatory-env-not-set' for required vars - Add helper functions: isDevelopment(), isProduction(), logEnv() - Create instrumentation.ts for one-time startup validation - Validate environment once at server startup using Next.js hook - Exit with error code 1 if validation fails This ensures the app doesn't start with missing required env vars and provides better error messages during startup. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Update services to use centralized env.get() pattern Replace direct process.env access with env.get() in: - OpenAI services (openai-question-extractor, multi-step-response) - Supabase utilities (4 files) - Database client (db.ts) - Login actions and API routes Also use helper functions: - isDevelopment() and isProduction() instead of NODE_ENV checks - Keep Vercel platform vars (VERCEL_URL, VERCEL_ENV) as process.env Remove constructor-level env validation checks since validation now happens once at startup. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Remove redundant validateEnv() calls from services Remove 9 validateEnv() calls across: - lib/llama-index-service.ts - lib/llamaparse-service.ts - lib/services/llamacloud-documents-service.ts - lib/services/llamacloud-connection-service.ts - app/api/llamacloud/projects/route.ts - app/api/organizations/route.ts Environment validation now happens once at startup via instrumentation.ts, so these per-request checks are no longer needed. Also updated env access to use env.get() pattern consistently. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Simplify next.config.ts by removing explicit env var exposure Remove explicit env var configuration from next.config.ts. Next.js automatically makes NEXT_PUBLIC_* variables available to the client, so explicit exposure is unnecessary. Keeps only essential config: - reactStrictMode - output: 'standalone' (for Docker deployment) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Fix remaining env.PROPERTY to env.get() conversions Updated remaining files that were still using env.PROPERTY pattern: - lib/llama-index-service.ts - lib/llamaparse-service.ts - lib/services/llamacloud-client.ts - lib/services/llamacloud-documents-service.ts - app/api/llamacloud/projects/route.ts - app/api/organizations/route.ts - app/api/projects/[projectId]/indexes/route.ts All files now consistently use env.get('VARIABLE_NAME')! pattern. TypeScript compilation now passes successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Refactor LlamaIndexService import paths and consolidate service files * Fix ESLint warnings in React components Fix React Hook dependency warnings: - ProjectDocuments: Add fetchProjectDocuments to useEffect deps - ProjectIndexSelector: Wrap hasChanges and handleSave in useCallback and add all dependencies to the debounced auto-save useEffect All ESLint checks now pass with no warnings or errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add comprehensive environment variable documentation Add detailed documentation explaining build-time vs runtime variables, especially for NEXT_PUBLIC_* variables and Docker deployments. Key additions: - New "Environment Variables" section in README with comprehensive guide - Explanation of build-time (NEXT_PUBLIC_*) vs runtime variables - Docker deployment workflow (correct vs incorrect approaches) - Why NEXT_PUBLIC_* variables require Docker rebuild - Best practices for multi-environment deployments - Code examples showing proper env.get() usage - Startup validation explanation This documentation is critical for users deploying with Docker, as changing NEXT_PUBLIC_* variables without rebuilding will not work. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Implement auth for all api routes * NODE_ENV has a default * Make the env lookup type-safe. * Restrict API health check path in session update logic * Improve UI consistency --------- Co-authored-by: Roland Tritsch <roland@tritsch.email> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
86e0a09873
commit
a9431b3ba0
@@ -93,7 +93,9 @@ LLAMACLOUD_API_KEY=<your-llamacloud-api-key>
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
```
|
||||
|
||||
**Note**: To use the env file for the app AND for docker the env var values cannot be in quotes.
|
||||
**Important Notes:**
|
||||
- Environment variable values should NOT be wrapped in quotes for Docker compatibility
|
||||
- See the [Environment Variables](#-environment-variables) section below for critical information about `NEXT_PUBLIC_*` variables
|
||||
|
||||
### 4. Database Setup
|
||||
|
||||
@@ -260,7 +262,28 @@ pnpm docker-run
|
||||
|
||||
**Note:** The Docker container uses Next.js standalone output mode for optimized production deployment. Make sure your `.env.local` includes a database connection string that's accessible from within the Docker container.
|
||||
|
||||
**IMPORTANT NOTE**: When you build/run a docker container all of the `NEXT_*` env vars are resolved at `build-time` (when the container is built; because the vars also need to be available in the frontend and are generated into the frontend code). The other vars are resolved at `run-time` (when the container is started). Means, if you (for instance) need to set `NEXT_PUBLIC_APP_URL` to your own site (e.g. `https://rfp.mydomain.com`) then you need to make this change to the env file, before you run docker-build.
|
||||
**CRITICAL: Build-Time vs Runtime Variables**
|
||||
|
||||
Environment variables with the `NEXT_PUBLIC_` prefix are resolved at **build-time**, while others are resolved at **runtime**:
|
||||
|
||||
- **Build-Time (`NEXT_PUBLIC_*`)**: Embedded into the generated JavaScript during build
|
||||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
- `NEXT_PUBLIC_APP_URL`
|
||||
- **Require Docker rebuild** when changed
|
||||
|
||||
- **Runtime (others)**: Read from environment when server starts
|
||||
- `DATABASE_URL`
|
||||
- `OPENAI_API_KEY`
|
||||
- `LLAMACLOUD_API_KEY`
|
||||
- Can be changed by restarting the container
|
||||
|
||||
**Example**: To change `NEXT_PUBLIC_APP_URL` from `http://localhost:3000` to `https://rfp.mydomain.com`:
|
||||
1. Update the `.env` file
|
||||
2. Run `pnpm docker-build` (rebuild required!)
|
||||
3. Run `pnpm docker-run`
|
||||
|
||||
Simply changing the environment variable and restarting will NOT work for `NEXT_PUBLIC_*` variables. See the [Environment Variables](#-environment-variables) section for more details.
|
||||
|
||||
## 🔌 API Endpoints
|
||||
|
||||
@@ -286,6 +309,147 @@ Try the platform with our sample RFP document:
|
||||
- **Sample File**: [RFP - Launch Services for Medium-Lift Payloads][rfp-sample-file]
|
||||
- **Use Case**: Download and upload to test question extraction and response generation
|
||||
|
||||
## 🔐 Environment Variables
|
||||
|
||||
### Understanding Build-Time vs Runtime Variables
|
||||
|
||||
AutoRFP uses a centralized environment variable management system through `lib/env.ts`. Understanding how different variables are resolved is critical for proper deployment, especially with Docker.
|
||||
|
||||
### Variable Types
|
||||
|
||||
#### Runtime Variables (Server-Side Only)
|
||||
|
||||
Variables **without** the `NEXT_PUBLIC_` prefix are resolved at **runtime**:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://...
|
||||
DIRECT_URL=postgresql://...
|
||||
OPENAI_API_KEY=sk-...
|
||||
LLAMACLOUD_API_KEY=llx-...
|
||||
LLAMACLOUD_API_KEY_INTERNAL=llx-...
|
||||
LLAMACLOUD_API_URL=https://api.cloud.llamaindex.ai
|
||||
INTERNAL_EMAIL_DOMAIN=@runllama.ai
|
||||
NODE_ENV=production
|
||||
```
|
||||
|
||||
**Characteristics:**
|
||||
- ✅ Only available on the server (API routes, server components)
|
||||
- ✅ Read from environment when server starts
|
||||
- ✅ Can be changed without rebuilding
|
||||
- ✅ NOT bundled into client JavaScript
|
||||
- ✅ Safe for secrets and credentials
|
||||
|
||||
**Docker behavior:**
|
||||
- Can be changed by updating `.env` and restarting the container
|
||||
- No rebuild required
|
||||
|
||||
#### Build-Time Variables (Public/Client-Side)
|
||||
|
||||
Variables **with** the `NEXT_PUBLIC_` prefix are resolved at **build-time**:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
|
||||
NEXT_PUBLIC_APP_URL=https://rfp.mydomain.com
|
||||
```
|
||||
|
||||
**Characteristics:**
|
||||
- ⚠️ Available on both server AND client
|
||||
- ⚠️ Embedded into generated JavaScript during build
|
||||
- ⚠️ Require rebuild to reflect changes
|
||||
- ⚠️ Visible in browser DevTools (never use for secrets!)
|
||||
|
||||
**Docker behavior:**
|
||||
- **MUST rebuild Docker image** when changed
|
||||
- Restarting the container alone will NOT pick up changes
|
||||
- The built JavaScript contains hardcoded values
|
||||
|
||||
### Why This Matters for Docker
|
||||
|
||||
When Next.js builds your application, it performs a step called "static optimization" where it replaces all `process.env.NEXT_PUBLIC_*` references with their literal string values. This means:
|
||||
|
||||
```javascript
|
||||
// Your code:
|
||||
const apiUrl = process.env.NEXT_PUBLIC_APP_URL;
|
||||
|
||||
// After build (in the generated JavaScript):
|
||||
const apiUrl = "http://localhost:3000";
|
||||
```
|
||||
|
||||
The Docker image contains these pre-built files with hardcoded values. Changing environment variables at runtime won't affect code that was already compiled.
|
||||
|
||||
### Docker Deployment Workflow
|
||||
|
||||
**Wrong approach** (will not work):
|
||||
```bash
|
||||
# Build with localhost
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
pnpm docker-build
|
||||
|
||||
# Try to change to production (THIS WON'T WORK!)
|
||||
NEXT_PUBLIC_APP_URL=https://rfp.mydomain.com
|
||||
pnpm docker-run
|
||||
# ❌ App will still use http://localhost:3000
|
||||
```
|
||||
|
||||
**Correct approach**:
|
||||
```bash
|
||||
# Set production values BEFORE building
|
||||
NEXT_PUBLIC_APP_URL=https://rfp.mydomain.com
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
|
||||
# Build with production values
|
||||
pnpm docker-build
|
||||
|
||||
# Run the container
|
||||
pnpm docker-run
|
||||
# ✅ App will use https://rfp.mydomain.com
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
**For Development:**
|
||||
- Use `.env.local` for local development variables
|
||||
- Keep `NEXT_PUBLIC_APP_URL=http://localhost:3000`
|
||||
|
||||
**For Docker/Production:**
|
||||
- Create environment-specific `.env` files (`.env.production`, `.env.staging`)
|
||||
- Set all `NEXT_PUBLIC_*` variables correctly before building
|
||||
- Build separate Docker images for each environment
|
||||
- Never expose secrets in `NEXT_PUBLIC_*` variables
|
||||
|
||||
**For Multi-Environment Deployments:**
|
||||
```bash
|
||||
# Development build
|
||||
cp .env.development .env
|
||||
pnpm docker-build -t auto_rfp:dev
|
||||
|
||||
# Production build
|
||||
cp .env.production .env
|
||||
pnpm docker-build -t auto_rfp:prod
|
||||
```
|
||||
|
||||
### Accessing Variables in Code
|
||||
|
||||
All environment variable access goes through the centralized `lib/env.ts` module:
|
||||
|
||||
```typescript
|
||||
// ✅ Correct
|
||||
import { env } from '@/lib/env';
|
||||
const apiKey = env.get('OPENAI_API_KEY')!;
|
||||
const appUrl = env.get('NEXT_PUBLIC_APP_URL')!;
|
||||
|
||||
// ❌ Incorrect
|
||||
const apiKey = process.env.OPENAI_API_KEY;
|
||||
```
|
||||
|
||||
### Startup Validation
|
||||
|
||||
Environment variables are validated once at server startup via `instrumentation.ts`:
|
||||
- Application will not start if required variables are missing
|
||||
- Validation errors are logged clearly
|
||||
- Eliminates per-request validation overhead
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
@@ -2,13 +2,19 @@ import { NextRequest } from 'next/server';
|
||||
import { apiHandler } from '@/lib/middleware/api-handler';
|
||||
import { ExtractQuestionsRequestSchema } from '@/lib/validators/extract-questions';
|
||||
import { questionExtractionService } from '@/lib/services/question-extraction-service';
|
||||
import { ValidationError } from '@/lib/errors/api-errors';
|
||||
import { AuthorizationError } from '@/lib/errors/api-errors';
|
||||
import { organizationService } from '@/lib/organization-service';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return apiHandler(async () => {
|
||||
// Parse and validate request body
|
||||
// SECURITY: Verify authentication first
|
||||
const currentUser = await organizationService.getCurrentUser();
|
||||
if (!currentUser) {
|
||||
throw new AuthorizationError('Authentication required');
|
||||
}
|
||||
|
||||
console.log("request", request);
|
||||
// Parse and validate request body
|
||||
console.log("request", request);
|
||||
const body = await request.json();
|
||||
const validatedRequest = ExtractQuestionsRequestSchema.parse(body);
|
||||
|
||||
|
||||
@@ -4,19 +4,13 @@ import { z } from 'zod';
|
||||
import { NextRequest } from 'next/server';
|
||||
import { organizationService } from '@/lib/organization-service';
|
||||
import { db } from '@/lib/db';
|
||||
import { LlamaIndexService } from '@/lib/llama-index-service';
|
||||
import { LlamaIndexService } from '@/lib/llamaindex-service';
|
||||
import { getLlamaCloudApiKey } from '@/lib/env';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
console.log('🎯 Multi-step API route called');
|
||||
|
||||
|
||||
try {
|
||||
// Check OpenAI API key
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
console.log('❌ OPENAI_API_KEY not configured');
|
||||
return new Response('OpenAI API key not configured', { status: 500 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
console.log('📝 Request body:', JSON.stringify(body, null, 2));
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function GET(request: NextRequest) {
|
||||
status: "healthy",
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
environment: process.env.NODE_ENV,
|
||||
environment: env.get('NODE_ENV')!,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { NextRequest } from 'next/server';
|
||||
import { apiHandler } from '@/lib/middleware/api-handler';
|
||||
import { env, validateEnv, getLlamaCloudApiKey } from '@/lib/env';
|
||||
import { env, getLlamaCloudApiKey } from '@/lib/env';
|
||||
import { organizationService } from '@/lib/organization-service';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return apiHandler(async () => {
|
||||
// Validate environment variables
|
||||
if (!validateEnv()) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'LlamaCloud API key not configured in environment variables' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Get current user to determine which API key to use
|
||||
@@ -20,14 +13,14 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Fetch projects and organizations from LlamaCloud
|
||||
const [projectsResponse, organizationsResponse] = await Promise.all([
|
||||
fetch(`${env.LLAMACLOUD_API_URL}/api/v1/projects`, {
|
||||
fetch(`${env.get('LLAMACLOUD_API_URL')!}/api/v1/projects`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}),
|
||||
fetch(`${env.LLAMACLOUD_API_URL}/api/v1/organizations`, {
|
||||
fetch(`${env.get('LLAMACLOUD_API_URL')!}/api/v1/organizations`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
|
||||
@@ -3,13 +3,21 @@ import { apiHandler } from '@/lib/middleware/api-handler';
|
||||
import { LlamaParseRequestSchema } from '@/lib/validators/llamaparse';
|
||||
import { llamaParseProcessingService } from '@/lib/services/llamaparse-processing-service';
|
||||
import { parseFormDataToLlamaParseRequest } from '@/lib/utils/form-data-parser';
|
||||
import { AuthorizationError } from '@/lib/errors/api-errors';
|
||||
import { organizationService } from '@/lib/organization-service';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return apiHandler(async () => {
|
||||
// SECURITY: Verify authentication first
|
||||
const currentUser = await organizationService.getCurrentUser();
|
||||
if (!currentUser) {
|
||||
throw new AuthorizationError('Authentication required');
|
||||
}
|
||||
|
||||
// Parse FormData into structured request
|
||||
const formData = await request.formData();
|
||||
const parsedRequest = parseFormDataToLlamaParseRequest(formData);
|
||||
|
||||
|
||||
// Validate the parsed request
|
||||
const validatedRequest = LlamaParseRequestSchema.parse(parsedRequest);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { organizationService } from '@/lib/organization-service';
|
||||
import { llamaCloudConnectionService } from '@/lib/services/llamacloud-connection-service';
|
||||
import { env, validateEnv, getLlamaCloudApiKey } from '@/lib/env';
|
||||
import { env, getLlamaCloudApiKey } from '@/lib/env';
|
||||
|
||||
export async function GET() {
|
||||
|
||||
@@ -78,22 +78,19 @@ export async function GET() {
|
||||
// Helper function to fetch available LlamaCloud projects
|
||||
async function fetchLlamaCloudProjects(userEmail?: string) {
|
||||
try {
|
||||
if (!validateEnv()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the appropriate API key based on user's email
|
||||
const apiKey = getLlamaCloudApiKey(userEmail);
|
||||
|
||||
const [projectsResponse, organizationsResponse] = await Promise.all([
|
||||
fetch(`${env.LLAMACLOUD_API_URL}/api/v1/projects`, {
|
||||
fetch(`${env.get('LLAMACLOUD_API_URL')!}/api/v1/projects`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}),
|
||||
fetch(`${env.LLAMACLOUD_API_URL}/api/v1/organizations`, {
|
||||
fetch(`${env.get('LLAMACLOUD_API_URL')!}/api/v1/organizations`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
|
||||
@@ -74,7 +74,7 @@ export async function GET(
|
||||
// Get the appropriate API key based on user's email
|
||||
const apiKey = getLlamaCloudApiKey(currentUser.email);
|
||||
|
||||
const pipelinesResponse = await fetch(`${env.LLAMACLOUD_API_URL}/api/v1/pipelines`, {
|
||||
const pipelinesResponse = await fetch(`${env.get('LLAMACLOUD_API_URL')!}/api/v1/pipelines`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
@@ -245,7 +245,7 @@ export async function POST(
|
||||
// Get the appropriate API key based on user's email
|
||||
const apiKey = getLlamaCloudApiKey(currentUser.email);
|
||||
|
||||
const pipelinesResponse = await fetch(`${env.LLAMACLOUD_API_URL}/api/v1/pipelines`, {
|
||||
const pipelinesResponse = await fetch(`${env.get('LLAMACLOUD_API_URL')!}/api/v1/pipelines`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { revalidatePath } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
import { createClient } from '@/lib/utils/supabase/server'
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
export async function signInWithMagicLink(formData: FormData) {
|
||||
const supabase = await createClient()
|
||||
@@ -27,7 +28,7 @@ export async function signInWithMagicLink(formData: FormData) {
|
||||
return `${protocol}://${process.env.VERCEL_URL}`
|
||||
}
|
||||
// For local development or custom deployments
|
||||
return process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
||||
return env.get('NEXT_PUBLIC_APP_URL')!
|
||||
}
|
||||
|
||||
const origin = getOrigin()
|
||||
|
||||
@@ -103,7 +103,7 @@ export default function OrganizationPage({ params }: OrganizationPageProps) {
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={() => setIsCreateProjectOpen(true)}>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
New Project
|
||||
Create Project
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -230,7 +230,7 @@ export function ProjectDocuments({ projectId, refreshKey }: ProjectDocumentsProp
|
||||
|
||||
useEffect(() => {
|
||||
fetchProjectDocuments();
|
||||
}, [projectId, refreshKey]);
|
||||
}, [projectId, refreshKey, fetchProjectDocuments]);
|
||||
|
||||
const handleRefresh = () => {
|
||||
fetchProjectDocuments();
|
||||
|
||||
@@ -85,7 +85,12 @@ export function ProjectIndexSelector({ projectId, onSaveSuccess }: ProjectIndexS
|
||||
setSelectedIndexId(prev => prev === indexId ? null : indexId);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const hasChanges = useCallback(() => {
|
||||
const currentId = currentIndexes.length > 0 ? currentIndexes[0].id : null;
|
||||
return selectedIndexId !== currentId;
|
||||
}, [currentIndexes, selectedIndexId]);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
try {
|
||||
setIsSaving(true);
|
||||
|
||||
@@ -121,12 +126,7 @@ export function ProjectIndexSelector({ projectId, onSaveSuccess }: ProjectIndexS
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const hasChanges = () => {
|
||||
const currentId = currentIndexes.length > 0 ? currentIndexes[0].id : null;
|
||||
return selectedIndexId !== currentId;
|
||||
};
|
||||
}, [projectId, selectedIndexId, onSaveSuccess, toast]);
|
||||
|
||||
// Debounced auto-save effect
|
||||
useEffect(() => {
|
||||
@@ -141,7 +141,7 @@ export function ProjectIndexSelector({ projectId, onSaveSuccess }: ProjectIndexS
|
||||
}, 800);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [selectedIndexId]);
|
||||
}, [selectedIndexId, isInitialized, isLoading, hasChanges, handleSave]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
||||
@@ -62,6 +62,14 @@ export function OrganizationProvider({ children }: OrganizationProviderProps) {
|
||||
const fetchOrganizations = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/organizations");
|
||||
|
||||
// Check if response is JSON before parsing
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!contentType || !contentType.includes("application/json")) {
|
||||
console.error("Expected JSON response but got:", contentType);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setOrganizations(data.data);
|
||||
@@ -75,8 +83,16 @@ export function OrganizationProvider({ children }: OrganizationProviderProps) {
|
||||
try {
|
||||
// Track which organization we're fetching for
|
||||
fetchingProjectsForOrgRef.current = organizationId;
|
||||
|
||||
|
||||
const response = await fetch(`/api/projects?organizationId=${organizationId}`);
|
||||
|
||||
// Check if response is JSON before parsing
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!contentType || !contentType.includes("application/json")) {
|
||||
console.error("Expected JSON response but got:", contentType);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
// Only set projects if we're still fetching for the same organization
|
||||
@@ -92,6 +108,14 @@ export function OrganizationProvider({ children }: OrganizationProviderProps) {
|
||||
const fetchProjectById = async (projectId: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${projectId}`);
|
||||
|
||||
// Check if response is JSON before parsing
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!contentType || !contentType.includes("application/json")) {
|
||||
console.error("Expected JSON response but got:", contentType);
|
||||
throw new Error(`Expected JSON response but got: ${contentType}`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch project: ${response.statusText}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { validateEnv } from './lib/env';
|
||||
|
||||
/**
|
||||
* Next.js instrumentation hook - runs once at server startup
|
||||
* This is the ideal place to validate environment configuration
|
||||
*/
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
console.log('Validating environment configuration at startup...');
|
||||
|
||||
if (!validateEnv()) {
|
||||
console.error('Environment validation failed. Server will not start.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Environment validation passed. Server starting...');
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { isDevelopment, isProduction } from '@/lib/env';
|
||||
|
||||
// PrismaClient is attached to the `global` object in development to prevent
|
||||
// exhausting your database connection limit.
|
||||
@@ -7,7 +8,7 @@ const globalForPrisma = global as unknown as { prisma: PrismaClient };
|
||||
export const db =
|
||||
globalForPrisma.prisma ||
|
||||
new PrismaClient({
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
log: isDevelopment() ? ['query', 'error', 'warn'] : ['error'],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db;
|
||||
if (!isProduction()) globalForPrisma.prisma = db;
|
||||
+171
-28
@@ -1,39 +1,182 @@
|
||||
// Environment variables configuration
|
||||
export const env = {
|
||||
LLAMACLOUD_API_KEY: process.env.LLAMACLOUD_API_KEY || '',
|
||||
LLAMACLOUD_API_KEY_INTERNAL: process.env.LLAMACLOUD_API_KEY_INTERNAL || '',
|
||||
LLAMACLOUD_API_URL: process.env.LLAMACLOUD_API_URL || 'https://api.cloud.llamaindex.ai',
|
||||
INTERNAL_EMAIL_DOMAIN: process.env.INTERNAL_EMAIL_DOMAIN || '@runllama.ai',
|
||||
};
|
||||
/**
|
||||
* Centralized environment variables configuration.
|
||||
* All environment variable access should go through this module.
|
||||
*
|
||||
* Env vars can be:
|
||||
*
|
||||
* - Mandatory: Must be set (use without test)
|
||||
* - Optional (with default): Can be set (use without test)
|
||||
* - Optional (without default): Can be set (use with test)
|
||||
*/
|
||||
|
||||
// Function to validate required environment variables
|
||||
export function validateEnv() {
|
||||
const requiredVars = [
|
||||
{ key: 'LLAMACLOUD_API_KEY', value: env.LLAMACLOUD_API_KEY }
|
||||
/**
|
||||
* Define all valid environment variable keys
|
||||
* This provides compile-time type safety for env.get() calls
|
||||
*/
|
||||
const ENV_KEYS = {
|
||||
// Database
|
||||
DATABASE_URL: 'DATABASE_URL',
|
||||
DIRECT_URL: 'DIRECT_URL',
|
||||
|
||||
// Supabase
|
||||
NEXT_PUBLIC_SUPABASE_URL: 'NEXT_PUBLIC_SUPABASE_URL',
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
||||
|
||||
// OpenAI
|
||||
OPENAI_API_KEY: 'OPENAI_API_KEY',
|
||||
|
||||
// LlamaCloud
|
||||
LLAMACLOUD_API_KEY: 'LLAMACLOUD_API_KEY',
|
||||
LLAMACLOUD_API_KEY_INTERNAL: 'LLAMACLOUD_API_KEY_INTERNAL',
|
||||
LLAMACLOUD_API_URL: 'LLAMACLOUD_API_URL',
|
||||
INTERNAL_EMAIL_DOMAIN: 'INTERNAL_EMAIL_DOMAIN',
|
||||
|
||||
// App configuration
|
||||
NEXT_PUBLIC_APP_URL: 'NEXT_PUBLIC_APP_URL',
|
||||
NODE_ENV: 'NODE_ENV',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Type representing valid environment variable keys
|
||||
* Derived from ENV_KEYS to ensure single source of truth
|
||||
*/
|
||||
type EnvKey = keyof typeof ENV_KEYS;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper around Map for environment variables
|
||||
* Prevents typos by only accepting valid EnvKey values
|
||||
*/
|
||||
class EnvStore {
|
||||
private store: Map<string, string | undefined>;
|
||||
|
||||
constructor(initialValues: Map<string, string | undefined>) {
|
||||
this.store = initialValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment variable value by key
|
||||
* Only accepts valid EnvKey types - typos will cause compile-time errors
|
||||
*/
|
||||
get(key: EnvKey): string | undefined {
|
||||
return this.store.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all environment variables
|
||||
* Used by validation logic
|
||||
*/
|
||||
forEach(callback: (value: string | undefined, key: string) => void): void {
|
||||
this.store.forEach(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get iterator over environment variable entries
|
||||
* Used by validation logic
|
||||
*/
|
||||
entries(): IterableIterator<[string, string | undefined]> {
|
||||
return this.store.entries();
|
||||
}
|
||||
}
|
||||
|
||||
const envMap = new Map<string, string | undefined>([
|
||||
// Database
|
||||
['DATABASE_URL', process.env.DATABASE_URL || 'mandatory-env-not-set'],
|
||||
['DIRECT_URL', process.env.DIRECT_URL || 'mandatory-env-not-set'],
|
||||
|
||||
// Supabase
|
||||
['NEXT_PUBLIC_SUPABASE_URL', process.env.NEXT_PUBLIC_SUPABASE_URL || 'mandatory-env-not-set'],
|
||||
['NEXT_PUBLIC_SUPABASE_ANON_KEY', process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'mandatory-env-not-set'],
|
||||
|
||||
// OpenAI
|
||||
['OPENAI_API_KEY', process.env.OPENAI_API_KEY || 'mandatory-env-not-set'],
|
||||
|
||||
// LlamaCloud
|
||||
['LLAMACLOUD_API_KEY', process.env.LLAMACLOUD_API_KEY || 'mandatory-env-not-set'],
|
||||
['LLAMACLOUD_API_KEY_INTERNAL', process.env.LLAMACLOUD_API_KEY_INTERNAL],
|
||||
['LLAMACLOUD_API_URL', process.env.LLAMACLOUD_API_URL || 'https://api.cloud.llamaindex.ai'],
|
||||
['INTERNAL_EMAIL_DOMAIN', process.env.INTERNAL_EMAIL_DOMAIN || '@runllama.ai'],
|
||||
|
||||
// App configuration
|
||||
['NEXT_PUBLIC_APP_URL', process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'],
|
||||
['NODE_ENV', process.env.NODE_ENV || 'development'],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Type-safe environment variable store
|
||||
* Use env.get(key) to access variables - only valid keys are accepted
|
||||
*/
|
||||
export const env = new EnvStore(envMap);
|
||||
|
||||
function logEnv() {
|
||||
const sensitiveVars = [
|
||||
'DATABASE_URL',
|
||||
'DIRECT_URL',
|
||||
'NEXT_PUBLIC_SUPABASE_URL',
|
||||
'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
||||
'OPENAI_API_KEY',
|
||||
'LLAMACLOUD_API_KEY',
|
||||
'LLAMACLOUD_API_KEY_INTERNAL',
|
||||
];
|
||||
|
||||
const missingVars = requiredVars.filter(v => !v.value);
|
||||
|
||||
console.log('=== Environment Variables ===');
|
||||
env.forEach((value, key) => {
|
||||
const displayValue = sensitiveVars.includes(key) && value !== undefined && value !== 'mandatory-env-not-set'
|
||||
? `${value.substring(0, 8)}...`
|
||||
: value;
|
||||
console.log(`${key}: ${displayValue}`);
|
||||
});
|
||||
console.log('=============================');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate required environment variables.
|
||||
*
|
||||
* Returns true if all required vars are set, false otherwise
|
||||
*/
|
||||
export function validateEnv(): boolean {
|
||||
logEnv();
|
||||
|
||||
const missingVars: string[] = [];
|
||||
|
||||
for (const [key, value] of env.entries()) {
|
||||
if (value === 'mandatory-env-not-set') {
|
||||
missingVars.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingVars.length > 0) {
|
||||
console.error(`
|
||||
Missing required environment variables:
|
||||
${missingVars.map(v => `- ${v.key}`).join('\n ')}
|
||||
|
||||
Please set these in your .env file
|
||||
`);
|
||||
console.error(`Missing required environment variables: ${missingVars.join(', ')}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper function to get the appropriate LlamaCloud API key based on user email
|
||||
/**
|
||||
* Get the appropriate LlamaCloud API key based on user email
|
||||
* Returns internal key for internal users, regular key otherwise
|
||||
*/
|
||||
export function getLlamaCloudApiKey(userEmail?: string | null): string {
|
||||
const internalDomain = env.get('INTERNAL_EMAIL_DOMAIN')!;
|
||||
const internalKey = env.get('LLAMACLOUD_API_KEY_INTERNAL');
|
||||
const regularKey = env.get('LLAMACLOUD_API_KEY')!;
|
||||
|
||||
// If user has internal email domain and internal key is configured, use internal key
|
||||
if (userEmail?.endsWith(env.INTERNAL_EMAIL_DOMAIN) && env.LLAMACLOUD_API_KEY_INTERNAL) {
|
||||
return env.LLAMACLOUD_API_KEY_INTERNAL;
|
||||
}
|
||||
|
||||
// Otherwise, use the regular API key
|
||||
return env.LLAMACLOUD_API_KEY;
|
||||
}
|
||||
const key = userEmail?.endsWith(internalDomain) && internalKey ? internalKey : regularKey;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running in production environment
|
||||
*/
|
||||
export function isProduction(): boolean {
|
||||
return env.get('NODE_ENV') === 'production';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running in development environment
|
||||
*/
|
||||
export function isDevelopment(): boolean {
|
||||
return env.get('NODE_ENV') === 'development';
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { env, validateEnv } from "./env";
|
||||
import { env } from "./env";
|
||||
import { LlamaCloudIndex, ContextChatEngine } from "llamaindex";
|
||||
import {
|
||||
ILlamaIndexService,
|
||||
@@ -31,14 +31,11 @@ export class LlamaIndexService implements ILlamaIndexService {
|
||||
};
|
||||
} else {
|
||||
// Fallback to environment variables (for default responses)
|
||||
if (!validateEnv()) {
|
||||
throw new Error('Required environment variables are missing');
|
||||
}
|
||||
this.config = {
|
||||
apiKey: env.LLAMACLOUD_API_KEY,
|
||||
apiKey: env.get('LLAMACLOUD_API_KEY')!,
|
||||
projectName: 'Default',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.initializeIndexes();
|
||||
}
|
||||
@@ -48,7 +45,7 @@ export class LlamaIndexService implements ILlamaIndexService {
|
||||
try {
|
||||
// Extract hostname from LLAMACLOUD_API_URL for LlamaCloudIndex
|
||||
// The SDK expects just the hostname (e.g., 'api.cloud.eu.llamaindex.ai')
|
||||
const baseUrlHostname = new URL(env.LLAMACLOUD_API_URL).hostname;
|
||||
const baseUrlHostname = new URL(env.get('LLAMACLOUD_API_URL')!).hostname;
|
||||
|
||||
console.log('Initializing LlamaCloud indexes with config:', this.config);
|
||||
if (this.config.indexNames && this.config.indexNames.length > 0) {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { env, validateEnv } from "./env";
|
||||
import { env } from "./env";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
@@ -18,11 +18,7 @@ export class LlamaParseService {
|
||||
private apiKey: string;
|
||||
|
||||
constructor() {
|
||||
if (!validateEnv()) {
|
||||
throw new Error('Required environment variables are missing');
|
||||
}
|
||||
|
||||
this.apiKey = env.LLAMACLOUD_API_KEY;
|
||||
this.apiKey = env.get('LLAMACLOUD_API_KEY')!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,12 +54,12 @@ export class LlamaParseService {
|
||||
const useAgentic = options.agenticMode !== false;
|
||||
|
||||
// LlamaParseReader uses protocol + hostname format (no /api/v1)
|
||||
// env.LLAMACLOUD_API_URL is already in this format
|
||||
// env.get('LLAMACLOUD_API_URL') is already in this format
|
||||
let readerOptions: Record<string, any> = {
|
||||
apiKey: this.apiKey,
|
||||
resultType: "markdown",
|
||||
useAgenticParse: useAgentic,
|
||||
baseUrl: env.LLAMACLOUD_API_URL,
|
||||
baseUrl: env.get('LLAMACLOUD_API_URL')!,
|
||||
};
|
||||
|
||||
// Add mode-specific options
|
||||
|
||||
@@ -19,7 +19,7 @@ export class LlamaCloudClient implements ILlamaCloudClient {
|
||||
|
||||
constructor(config: Partial<LlamaCloudClientConfig> = {}) {
|
||||
this.config = {
|
||||
baseUrl: `${env.LLAMACLOUD_API_URL}/api/v1`,
|
||||
baseUrl: `${env.get('LLAMACLOUD_API_URL')!}/api/v1`,
|
||||
timeout: 30000,
|
||||
retryAttempts: 3,
|
||||
...config,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { llamaCloudClient } from './llamacloud-client';
|
||||
import { organizationAuth } from './organization-auth';
|
||||
import { db } from '@/lib/db';
|
||||
import { DatabaseError, LlamaCloudConnectionError } from '@/lib/errors/api-errors';
|
||||
import { env, validateEnv, getLlamaCloudApiKey } from '@/lib/env';
|
||||
import { env, getLlamaCloudApiKey } from '@/lib/env';
|
||||
|
||||
/**
|
||||
* Main LlamaCloud connection management service
|
||||
@@ -23,12 +23,7 @@ export class LlamaCloudConnectionService implements ILlamaCloudConnectionService
|
||||
// Step 1: Verify user has admin access
|
||||
await organizationAuth.requireAdminAccess(userId, request.organizationId);
|
||||
|
||||
// Step 2: Validate environment variables
|
||||
if (!validateEnv()) {
|
||||
throw new LlamaCloudConnectionError('LlamaCloud API key not configured in environment variables');
|
||||
}
|
||||
|
||||
// Get user's email to determine which API key to use
|
||||
// Step 2: Get user's email to determine which API key to use
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { email: true }
|
||||
@@ -167,8 +162,8 @@ export class LlamaCloudConnectionService implements ILlamaCloudConnectionService
|
||||
},
|
||||
});
|
||||
|
||||
// Connection is considered active if there's a project ID and the environment API key is available
|
||||
const isConnected = !!(organization?.llamaCloudProjectId && validateEnv());
|
||||
// Connection is considered active if there's a project ID
|
||||
const isConnected = !!organization?.llamaCloudProjectId;
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { llamaCloudClient } from './llamacloud-client';
|
||||
import { organizationAuth } from './organization-auth';
|
||||
import { db } from '@/lib/db';
|
||||
import { DatabaseError, LlamaCloudConnectionError, NotFoundError } from '@/lib/errors/api-errors';
|
||||
import { env, validateEnv, getLlamaCloudApiKey } from '@/lib/env';
|
||||
import { env, getLlamaCloudApiKey } from '@/lib/env';
|
||||
|
||||
/**
|
||||
* LlamaCloud documents management service
|
||||
@@ -22,12 +22,7 @@ export class LlamaCloudDocumentsService implements ILlamaCloudDocumentsService {
|
||||
// Step 1: Verify user has organization access
|
||||
await organizationAuth.requireMembership(userId, request.organizationId);
|
||||
|
||||
// Step 2: Validate environment variables
|
||||
if (!validateEnv()) {
|
||||
throw new LlamaCloudConnectionError('LlamaCloud API key not configured in environment variables');
|
||||
}
|
||||
|
||||
// Get user's email to determine which API key to use
|
||||
// Step 2: Get user's email to determine which API key to use
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { email: true }
|
||||
@@ -125,16 +120,11 @@ export class LlamaCloudDocumentsService implements ILlamaCloudDocumentsService {
|
||||
*/
|
||||
async fetchDocumentsForAllPipelines(organizationId: string): Promise<LlamaCloudFile[]> {
|
||||
try {
|
||||
// Validate environment variables
|
||||
if (!validateEnv()) {
|
||||
throw new LlamaCloudConnectionError('LlamaCloud API key not configured in environment variables');
|
||||
}
|
||||
|
||||
const organization = await this.getConnectedOrganization(organizationId);
|
||||
|
||||
// Get all pipelines for the project
|
||||
const pipelines = await llamaCloudClient.fetchPipelinesForProject(
|
||||
env.LLAMACLOUD_API_KEY,
|
||||
env.get('LLAMACLOUD_API_KEY')!,
|
||||
organization.llamaCloudProjectId!
|
||||
);
|
||||
|
||||
@@ -143,7 +133,7 @@ export class LlamaCloudDocumentsService implements ILlamaCloudDocumentsService {
|
||||
for (const pipeline of pipelines) {
|
||||
try {
|
||||
const documents = await llamaCloudClient.fetchFilesForPipeline(
|
||||
env.LLAMACLOUD_API_KEY,
|
||||
env.get('LLAMACLOUD_API_KEY')!,
|
||||
pipeline.id
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { IMultiStepResponseService } from '@/lib/interfaces/multi-step-response';
|
||||
import {
|
||||
MultiStepGenerateRequest,
|
||||
MultiStepResponse,
|
||||
StepUpdate,
|
||||
import {
|
||||
MultiStepGenerateRequest,
|
||||
MultiStepResponse,
|
||||
StepUpdate,
|
||||
StepResult,
|
||||
QuestionAnalysis,
|
||||
DocumentSearchResult,
|
||||
@@ -10,11 +10,12 @@ import {
|
||||
ResponseSynthesis,
|
||||
MultiStepConfig
|
||||
} from '@/lib/validators/multi-step-response';
|
||||
import { LlamaIndexService } from '@/lib/llama-index-service';
|
||||
import { LlamaIndexService } from '@/lib/llamaindex-service';
|
||||
import { generateId } from 'ai';
|
||||
import { db } from '@/lib/db';
|
||||
import { organizationService } from '@/lib/organization-service';
|
||||
import OpenAI from 'openai';
|
||||
import { env } from '@/lib/env';
|
||||
|
||||
/**
|
||||
* Multi-step response generation service implementation with AI-powered reasoning
|
||||
@@ -39,7 +40,7 @@ export class MultiStepResponseService implements IMultiStepResponseService {
|
||||
|
||||
// Initialize OpenAI for AI-powered reasoning
|
||||
this.openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
apiKey: env.get('OPENAI_API_KEY')!,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ export class MultiStepResponseService implements IMultiStepResponseService {
|
||||
console.log(`DEBUG Multi-step: selected index names:`, selectedIndexNames);
|
||||
|
||||
this.llamaIndexService = new LlamaIndexService({
|
||||
apiKey: process.env.LLAMACLOUD_API_KEY!,
|
||||
apiKey: env.get('LLAMACLOUD_API_KEY')!,
|
||||
projectName: projectConfig.organization.llamaCloudProjectName || 'Default',
|
||||
indexNames: selectedIndexNames.length > 0 ? selectedIndexNames : undefined,
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IAIQuestionExtractor, AIServiceConfig } from '@/lib/interfaces/ai-servi
|
||||
import { ExtractedQuestions, ExtractedQuestionsSchema } from '@/lib/validators/extract-questions';
|
||||
import { DEFAULT_LANGUAGE_MODEL } from '@/lib/constants';
|
||||
import { AIServiceError } from '@/lib/errors/api-errors';
|
||||
import { env } from '@/lib/env';
|
||||
|
||||
/**
|
||||
* OpenAI-powered question extraction service
|
||||
@@ -13,7 +14,7 @@ export class OpenAIQuestionExtractor implements IAIQuestionExtractor {
|
||||
|
||||
constructor(config: Partial<AIServiceConfig> = {}) {
|
||||
this.client = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
apiKey: env.get('OPENAI_API_KEY')!,
|
||||
});
|
||||
|
||||
this.config = {
|
||||
@@ -23,10 +24,6 @@ export class OpenAIQuestionExtractor implements IAIQuestionExtractor {
|
||||
timeout: 60000,
|
||||
...config,
|
||||
};
|
||||
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
throw new AIServiceError('OpenAI API key is not configured');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { db } from '@/lib/db';
|
||||
import { organizationService } from '@/lib/organization-service';
|
||||
import { LlamaIndexService } from '@/lib/llama-index-service';
|
||||
import { LlamaIndexService } from '@/lib/llamaindex-service';
|
||||
import { getLlamaCloudApiKey } from '@/lib/env';
|
||||
import {
|
||||
GenerateResponseRequest,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createBrowserClient } from '@supabase/ssr'
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
export function createClient() {
|
||||
return createBrowserClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
env.get('NEXT_PUBLIC_SUPABASE_URL')!,
|
||||
env.get('NEXT_PUBLIC_SUPABASE_ANON_KEY')!
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createServerClient } from '@supabase/ssr'
|
||||
import { NextResponse, type NextRequest } from 'next/server'
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
export async function updateSession(request: NextRequest) {
|
||||
let supabaseResponse = NextResponse.next({
|
||||
@@ -7,8 +8,8 @@ export async function updateSession(request: NextRequest) {
|
||||
})
|
||||
|
||||
const supabase = createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
env.get('NEXT_PUBLIC_SUPABASE_URL')!,
|
||||
env.get('NEXT_PUBLIC_SUPABASE_ANON_KEY')!,
|
||||
{
|
||||
cookies: {
|
||||
getAll() {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { createServerClient } from '@supabase/ssr'
|
||||
import { cookies } from 'next/headers'
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
export async function createClient() {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
return createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
env.get('NEXT_PUBLIC_SUPABASE_URL')!,
|
||||
env.get('NEXT_PUBLIC_SUPABASE_ANON_KEY')!,
|
||||
{
|
||||
cookies: {
|
||||
getAll() {
|
||||
|
||||
+2
-14
@@ -1,20 +1,8 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
// Configure environment variables that will be available on both server and client side
|
||||
env: {
|
||||
// You will need to set LLAMACLOUD_API_KEY in your deployment environment
|
||||
// or in a .env.local file that's not committed to version control
|
||||
LLAMACLOUD_API_KEY: process.env.LLAMACLOUD_API_KEY,
|
||||
// Internal API key for internal users
|
||||
LLAMACLOUD_API_KEY_INTERNAL: process.env.LLAMACLOUD_API_KEY_INTERNAL,
|
||||
// Internal email domain (defaults to @runllama.ai)
|
||||
INTERNAL_EMAIL_DOMAIN: process.env.INTERNAL_EMAIL_DOMAIN,
|
||||
},
|
||||
// Other Next.js config options
|
||||
reactStrictMode: true,
|
||||
// Enable standalone output for Docker deployment
|
||||
output: 'standalone',
|
||||
reactStrictMode: true,
|
||||
output: 'standalone',
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createBrowserClient } from '@supabase/ssr'
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
export function createClient() {
|
||||
return createBrowserClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
env.get('NEXT_PUBLIC_SUPABASE_URL')!,
|
||||
env.get('NEXT_PUBLIC_SUPABASE_ANON_KEY')!
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user