canvas-website/src/lib/clientConfig.ts

331 lines
13 KiB
TypeScript

/**
* Client-side configuration utility
* Handles environment variables in browser environment
*/
export interface ClientConfig {
githubToken?: string
quartzRepo?: string
quartzBranch?: string
cloudflareApiKey?: string
cloudflareAccountId?: string
quartzApiUrl?: string
quartzApiKey?: string
webhookUrl?: string
webhookSecret?: string
openaiApiKey?: string
runpodApiKey?: string
runpodEndpointId?: string
runpodImageEndpointId?: string
runpodVideoEndpointId?: string
runpodTextEndpointId?: string
runpodWhisperEndpointId?: string
ollamaUrl?: string
}
/**
* Get client-side configuration
* This works in both browser and server environments
*/
export function getClientConfig(): ClientConfig {
// In Vite, environment variables are available via import.meta.env
// In Next.js, NEXT_PUBLIC_ variables are available at build time
if (typeof window !== 'undefined') {
// Browser environment - check for Vite first, then Next.js
if (typeof import.meta !== 'undefined' && import.meta.env) {
// Vite environment
return {
githubToken: import.meta.env.VITE_GITHUB_TOKEN || import.meta.env.NEXT_PUBLIC_GITHUB_TOKEN,
quartzRepo: import.meta.env.VITE_QUARTZ_REPO || import.meta.env.NEXT_PUBLIC_QUARTZ_REPO,
quartzBranch: import.meta.env.VITE_QUARTZ_BRANCH || import.meta.env.NEXT_PUBLIC_QUARTZ_BRANCH,
cloudflareApiKey: import.meta.env.VITE_CLOUDFLARE_API_KEY || import.meta.env.NEXT_PUBLIC_CLOUDFLARE_API_KEY,
cloudflareAccountId: import.meta.env.VITE_CLOUDFLARE_ACCOUNT_ID || import.meta.env.NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID,
quartzApiUrl: import.meta.env.VITE_QUARTZ_API_URL || import.meta.env.NEXT_PUBLIC_QUARTZ_API_URL,
quartzApiKey: import.meta.env.VITE_QUARTZ_API_KEY || import.meta.env.NEXT_PUBLIC_QUARTZ_API_KEY,
webhookUrl: import.meta.env.VITE_QUARTZ_WEBHOOK_URL || import.meta.env.NEXT_PUBLIC_QUARTZ_WEBHOOK_URL,
webhookSecret: import.meta.env.VITE_QUARTZ_WEBHOOK_SECRET || import.meta.env.NEXT_PUBLIC_QUARTZ_WEBHOOK_SECRET,
openaiApiKey: import.meta.env.VITE_OPENAI_API_KEY || import.meta.env.NEXT_PUBLIC_OPENAI_API_KEY,
runpodApiKey: import.meta.env.VITE_RUNPOD_API_KEY || import.meta.env.NEXT_PUBLIC_RUNPOD_API_KEY,
runpodEndpointId: import.meta.env.VITE_RUNPOD_ENDPOINT_ID || import.meta.env.VITE_RUNPOD_IMAGE_ENDPOINT_ID || import.meta.env.NEXT_PUBLIC_RUNPOD_ENDPOINT_ID,
runpodImageEndpointId: import.meta.env.VITE_RUNPOD_IMAGE_ENDPOINT_ID || import.meta.env.NEXT_PUBLIC_RUNPOD_IMAGE_ENDPOINT_ID,
runpodVideoEndpointId: import.meta.env.VITE_RUNPOD_VIDEO_ENDPOINT_ID || import.meta.env.NEXT_PUBLIC_RUNPOD_VIDEO_ENDPOINT_ID,
runpodTextEndpointId: import.meta.env.VITE_RUNPOD_TEXT_ENDPOINT_ID || import.meta.env.NEXT_PUBLIC_RUNPOD_TEXT_ENDPOINT_ID,
runpodWhisperEndpointId: import.meta.env.VITE_RUNPOD_WHISPER_ENDPOINT_ID || import.meta.env.NEXT_PUBLIC_RUNPOD_WHISPER_ENDPOINT_ID,
ollamaUrl: import.meta.env.VITE_OLLAMA_URL || import.meta.env.NEXT_PUBLIC_OLLAMA_URL,
}
} else {
// Next.js environment
return {
githubToken: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_GITHUB_TOKEN,
quartzRepo: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_QUARTZ_REPO,
quartzBranch: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_QUARTZ_BRANCH,
cloudflareApiKey: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_CLOUDFLARE_API_KEY,
cloudflareAccountId: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID,
quartzApiUrl: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_QUARTZ_API_URL,
quartzApiKey: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_QUARTZ_API_KEY,
webhookUrl: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_QUARTZ_WEBHOOK_URL,
webhookSecret: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_QUARTZ_WEBHOOK_SECRET,
openaiApiKey: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_OPENAI_API_KEY,
runpodApiKey: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_RUNPOD_API_KEY,
runpodEndpointId: (window as any).__NEXT_DATA__?.env?.NEXT_PUBLIC_RUNPOD_ENDPOINT_ID,
}
}
} else {
// Server environment
return {
githubToken: process.env.VITE_GITHUB_TOKEN || process.env.NEXT_PUBLIC_GITHUB_TOKEN,
quartzRepo: process.env.VITE_QUARTZ_REPO || process.env.NEXT_PUBLIC_QUARTZ_REPO,
quartzBranch: process.env.VITE_QUARTZ_BRANCH || process.env.NEXT_PUBLIC_QUARTZ_BRANCH,
cloudflareApiKey: process.env.VITE_CLOUDFLARE_API_KEY || process.env.NEXT_PUBLIC_CLOUDFLARE_API_KEY,
cloudflareAccountId: process.env.VITE_CLOUDFLARE_ACCOUNT_ID || process.env.NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID,
quartzApiUrl: process.env.VITE_QUARTZ_API_URL || process.env.NEXT_PUBLIC_QUARTZ_API_URL,
quartzApiKey: process.env.VITE_QUARTZ_API_KEY || process.env.NEXT_PUBLIC_QUARTZ_API_KEY,
webhookUrl: process.env.VITE_QUARTZ_WEBHOOK_URL || process.env.NEXT_PUBLIC_QUARTZ_WEBHOOK_URL,
webhookSecret: process.env.VITE_QUARTZ_WEBHOOK_SECRET || process.env.NEXT_PUBLIC_QUARTZ_WEBHOOK_SECRET,
runpodApiKey: process.env.VITE_RUNPOD_API_KEY || process.env.NEXT_PUBLIC_RUNPOD_API_KEY,
runpodEndpointId: process.env.VITE_RUNPOD_ENDPOINT_ID || process.env.VITE_RUNPOD_IMAGE_ENDPOINT_ID || process.env.NEXT_PUBLIC_RUNPOD_ENDPOINT_ID,
runpodImageEndpointId: process.env.VITE_RUNPOD_IMAGE_ENDPOINT_ID || process.env.NEXT_PUBLIC_RUNPOD_IMAGE_ENDPOINT_ID,
runpodVideoEndpointId: process.env.VITE_RUNPOD_VIDEO_ENDPOINT_ID || process.env.NEXT_PUBLIC_RUNPOD_VIDEO_ENDPOINT_ID,
runpodTextEndpointId: process.env.VITE_RUNPOD_TEXT_ENDPOINT_ID || process.env.NEXT_PUBLIC_RUNPOD_TEXT_ENDPOINT_ID,
runpodWhisperEndpointId: process.env.VITE_RUNPOD_WHISPER_ENDPOINT_ID || process.env.NEXT_PUBLIC_RUNPOD_WHISPER_ENDPOINT_ID,
ollamaUrl: process.env.VITE_OLLAMA_URL || process.env.NEXT_PUBLIC_OLLAMA_URL,
}
}
}
// Default RunPod API key - shared across all endpoints
// This allows all users to access AI features without their own API keys
const DEFAULT_RUNPOD_API_KEY = 'rpa_YYOARL5MEBTTKKWGABRKTW2CVHQYRBTOBZNSGIL3lwwfdz'
// Default RunPod endpoint IDs (from CLAUDE.md)
const DEFAULT_RUNPOD_IMAGE_ENDPOINT_ID = 'tzf1j3sc3zufsy' // Automatic1111 for image generation
const DEFAULT_RUNPOD_VIDEO_ENDPOINT_ID = '4jql4l7l0yw0f3' // Wan2.2 for video generation
const DEFAULT_RUNPOD_TEXT_ENDPOINT_ID = '03g5hz3hlo8gr2' // vLLM for text generation
const DEFAULT_RUNPOD_WHISPER_ENDPOINT_ID = 'lrtisuv8ixbtub' // Whisper for transcription
/**
* Get RunPod configuration for API calls (defaults to image endpoint)
* Falls back to pre-configured endpoints if not set via environment
*/
export function getRunPodConfig(): { apiKey: string; endpointId: string } | null {
const config = getClientConfig()
const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY
const endpointId = config.runpodEndpointId || config.runpodImageEndpointId || DEFAULT_RUNPOD_IMAGE_ENDPOINT_ID
return {
apiKey: apiKey,
endpointId: endpointId
}
}
/**
* Get RunPod configuration for image generation
* Falls back to pre-configured Automatic1111 endpoint
*/
export function getRunPodImageConfig(): { apiKey: string; endpointId: string } | null {
const config = getClientConfig()
const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY
const endpointId = config.runpodImageEndpointId || config.runpodEndpointId || DEFAULT_RUNPOD_IMAGE_ENDPOINT_ID
return {
apiKey: apiKey,
endpointId: endpointId
}
}
/**
* Get RunPod configuration for video generation
* Falls back to pre-configured Wan2.2 endpoint
*/
export function getRunPodVideoConfig(): { apiKey: string; endpointId: string } | null {
const config = getClientConfig()
const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY
const endpointId = config.runpodVideoEndpointId || DEFAULT_RUNPOD_VIDEO_ENDPOINT_ID
return {
apiKey: apiKey,
endpointId: endpointId
}
}
/**
* Get RunPod configuration for text generation (vLLM)
* Falls back to pre-configured vLLM endpoint
*/
export function getRunPodTextConfig(): { apiKey: string; endpointId: string } | null {
const config = getClientConfig()
const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY
const endpointId = config.runpodTextEndpointId || DEFAULT_RUNPOD_TEXT_ENDPOINT_ID
return {
apiKey: apiKey,
endpointId: endpointId
}
}
/**
* Get RunPod configuration for Whisper transcription
* Falls back to pre-configured Whisper endpoint
*/
export function getRunPodWhisperConfig(): { apiKey: string; endpointId: string } | null {
const config = getClientConfig()
const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY
const endpointId = config.runpodWhisperEndpointId || DEFAULT_RUNPOD_WHISPER_ENDPOINT_ID
return {
apiKey: apiKey,
endpointId: endpointId
}
}
/**
* Get Ollama configuration for local LLM
* Falls back to the default Netcup AI Orchestrator if not configured
*/
export function getOllamaConfig(): { url: string } | null {
const config = getClientConfig()
// Default to Netcup AI Orchestrator (Ollama) if not configured
// This ensures all users have free AI access without needing their own API keys
const ollamaUrl = config.ollamaUrl || 'https://ai.jeffemmett.com'
return {
url: ollamaUrl
}
}
/**
* Check if RunPod integration is configured
*/
export function isRunPodConfigured(): boolean {
const config = getClientConfig()
return !!(config.runpodApiKey && config.runpodEndpointId)
}
/**
* Check if GitHub integration is configured
*/
export function isGitHubConfigured(): boolean {
const config = getClientConfig()
return !!(config.githubToken && config.quartzRepo)
}
/**
* Get GitHub configuration for API calls
*/
export function getGitHubConfig(): { token: string; repo: string; branch: string } | null {
const config = getClientConfig()
if (!config.githubToken || !config.quartzRepo) {
return null
}
const [owner, repo] = config.quartzRepo.split('/')
if (!owner || !repo) {
return null
}
return {
token: config.githubToken,
repo: config.quartzRepo,
branch: config.quartzBranch || 'main'
}
}
/**
* Check if OpenAI integration is configured
* Reads from user profile settings (localStorage) instead of environment variables
*/
export function isOpenAIConfigured(): boolean {
try {
// First try to get user-specific API keys if available
const session = JSON.parse(localStorage.getItem('session') || '{}')
if (session.authed && session.username) {
const userApiKeys = localStorage.getItem(`${session.username}_api_keys`)
if (userApiKeys) {
try {
const parsed = JSON.parse(userApiKeys)
if (parsed.keys && parsed.keys.openai && parsed.keys.openai.trim() !== '') {
return true
}
} catch (e) {
// Continue to fallback
}
}
}
// Fallback to global API keys
const settings = localStorage.getItem("openai_api_key")
if (settings) {
try {
const parsed = JSON.parse(settings)
if (parsed.keys && parsed.keys.openai && parsed.keys.openai.trim() !== '') {
return true
}
} catch (e) {
// If it's not JSON, it might be the old format (just a string)
if (settings.startsWith('sk-') && settings.trim() !== '') {
return true
}
}
}
return false
} catch (e) {
return false
}
}
/**
* Get OpenAI API key for API calls
* Reads from user profile settings (localStorage) instead of environment variables
*/
export function getOpenAIConfig(): { apiKey: string } | null {
try {
// First try to get user-specific API keys if available
const session = JSON.parse(localStorage.getItem('session') || '{}')
if (session.authed && session.username) {
const userApiKeys = localStorage.getItem(`${session.username}_api_keys`)
if (userApiKeys) {
try {
const parsed = JSON.parse(userApiKeys)
if (parsed.keys && parsed.keys.openai && parsed.keys.openai.trim() !== '') {
console.log('🔑 Found user-specific OpenAI API key')
return { apiKey: parsed.keys.openai }
}
} catch (e) {
console.log('🔑 Error parsing user-specific API keys:', e)
}
}
}
// Fallback to global API keys
const settings = localStorage.getItem("openai_api_key")
if (settings) {
try {
const parsed = JSON.parse(settings)
if (parsed.keys && parsed.keys.openai && parsed.keys.openai.trim() !== '') {
console.log('🔑 Found global OpenAI API key')
return { apiKey: parsed.keys.openai }
}
} catch (e) {
// If it's not JSON, it might be the old format (just a string)
if (settings.startsWith('sk-') && settings.trim() !== '') {
console.log('🔑 Found old format OpenAI API key')
return { apiKey: settings }
}
}
}
console.log('🔑 No OpenAI API key found')
return null
} catch (e) {
console.log('🔑 Error getting OpenAI config:', e)
return null
}
}