From e5fa002d12d8fca0c43254689ac8c7edd33927fc Mon Sep 17 00:00:00 2001 From: kcar Date: Thu, 28 May 2026 13:29:47 +0100 Subject: [PATCH] fix: remove hardcoded prod/local browser URLs from app dev bundle - Create centralized src/config/apiConfig.ts for API_BASE, TLSYNC_URL, WHISPERLIVE_URL, and SEARCH_URL resolution - Fix multiplayerUser.tsx to use VITE_TLSYNC_URL instead of deriving from VITE_FRONTEND_SITE_URL (was pointing to prod TLSync) - Replace all 127.0.0.1:8080 fallbacks with /api (relative path) - Replace all localhost:8000 fallbacks with VITE_API_BASE || /api - Remove hardcoded https://api.classroomcopilot.ai production fallbacks from transcription store and panel - Fix axiosConfig.ts to use centralized API config - Update .env.dev: set VITE_TLSYNC_URL to dev frontend URL - Update .env.development: fix VITE_SEARCH_URL from localhost to prod - Add missing env var type declarations to vite-env.d.ts Fixes: t_73d78fb1 --- .env.development | 2 +- .env.example | 62 +++++++++++++------ src/axiosConfig.ts | 7 ++- src/config/apiConfig.ts | 44 +++++++++++++ src/pages/Header.tsx | 2 +- src/pages/auth/PlatformAdminPage.tsx | 2 +- src/pages/dev/SimpleUploadTest.tsx | 2 +- src/pages/timetable/ClassDetailPage.tsx | 2 +- src/pages/timetable/LessonPlanDetailPage.tsx | 2 +- src/pages/timetable/LessonPlansPage.tsx | 2 +- src/pages/timetable/SchoolSettingsPage.tsx | 2 +- src/pages/timetable/StaffManagerPage.tsx | 2 +- src/pages/timetable/StudentLessonsPage.tsx | 2 +- src/pages/timetable/StudentManagerPage.tsx | 2 +- src/pages/timetable/TaughtLessonsPage.tsx | 2 +- .../CCDocumentIntelligence/CCBundleViewer.tsx | 4 +- .../CCDoclingViewer.tsx | 6 +- .../CCDocumentIntelligence.tsx | 6 +- .../CCEnhancedFilePanel.tsx | 2 +- .../CCFileDetailPanel.tsx | 2 +- src/pages/tldraw/multiplayerUser.tsx | 5 +- src/services/timetableService.ts | 2 +- src/stores/transcriptionStore.ts | 10 +-- .../components/shared/CCCabinetsPanel.tsx | 2 +- .../components/shared/CCFilesPanel.tsx | 2 +- .../shared/CCFilesPanelEnhanced.tsx | 2 +- .../shared/CCTranscriptionPanel.tsx | 2 +- src/vite-env.d.ts | 27 ++++++++ 28 files changed, 152 insertions(+), 57 deletions(-) create mode 100644 src/config/apiConfig.ts diff --git a/.env.development b/.env.development index 16607d6..6339405 100644 --- a/.env.development +++ b/.env.development @@ -37,4 +37,4 @@ VITE_MICROSOFT_CLIENT_SECRET_ID=dummy_secret_id VITE_MICROSOFT_CLIENT_SECRET=dummy_secret VITE_MICROSOFT_TENANT_ID=common -VITE_SEARCH_URL=http://localhost:8888 +VITE_SEARCH_URL=https://search.kevlarai.com diff --git a/.env.example b/.env.example index 775b7af..a53ab87 100644 --- a/.env.example +++ b/.env.example @@ -1,33 +1,34 @@ # Classroom Copilot App - Environment Variables Template -# Copy this file to .env and fill in the values +# Copy this file to .env.dev or .env.development and fill in the values # ============================================================================= # Frontend Configuration # ============================================================================= VITE_APP_NAME=Classroom Copilot -VITE_DEV=false -VITE_FRONTEND_SITE_URL=https://app.classroomcopilot.ai -VITE_PORT_FRONTEND=3000 -VITE_PORT_FRONTEND_HMR=24678 -VITE_APP_HMR_URL=http://localhost:24678 +VITE_DEV=true +VITE_DEV=true +VITE_FRONTEND_SITE_URL=http://192.168.0.251:13000 +VITE_PORT_FRONTEND=13000 +VITE_PORT_FRONTEND_HMR=5173 +VITE_APP_HMR_URL=http://192.168.0.251:13000 # ============================================================================= -# API Configuration +# API Configuration (primary: VITE_API_BASE, legacy alias: VITE_API_URL) # ============================================================================= -VITE_API_URL=https://api.classroomcopilot.ai -VITE_API_BASE=https://api.classroomcopilot.ai +VITE_API_URL=http://192.168.0.64:18000 +VITE_API_BASE=http://192.168.0.64:18000 # ============================================================================= # Supabase Configuration # ============================================================================= -VITE_SUPABASE_URL=https://your-project.supabase.co +VITE_SUPABASE_URL=http://192.168.0.94:8000 VITE_SUPABASE_ANON_KEY=your-supabase-anon-key # ============================================================================= -# TLSync (TLDraw Sync) Configuration +# TLSync (TLDraw Sync Worker) Configuration # ============================================================================= -VITE_TLSYNC_URL=https://app.classroomcopilot.ai/tldraw -VITE_TLSYNC_SECRET=your-tlsync-secret +VITE_TLSYNC_URL=http://192.168.0.251:13000/tldraw +VITE_TLSYNC_SECRET=your-tlsync-shared-secret # ============================================================================= # WhisperLive (Transcription) Configuration @@ -35,19 +36,42 @@ VITE_TLSYNC_SECRET=your-tlsync-secret VITE_WHISPERLIVE_URL=wss://whisperlive.classroomcopilot.ai/ws # ============================================================================= -# Search Configuration +# Search Configuration (searxng proxy) # ============================================================================= VITE_SEARCH_URL=https://search.kevlarai.com # ============================================================================= -# Super Admin Configuration +# Neo4j Configuration # ============================================================================= -VITE_SUPER_ADMIN_EMAIL=admin@example.com +VITE_NEO4J_URL=bolt://192.168.0.209:7687 +VITE_NEO4J_USER=neo4j +VITE_NEO4J_PASSWORD=password # ============================================================================= -# Port Configuration (for docker-compose) +# LLM / Ollama Configuration # ============================================================================= -PORT_FRONTEND=3000 -PORT_FRONTEND_HMR=24678 +VITE_LLM_PROVIDER=ollama +VITE_OLLAMA_API_URL=https://ollama.kevlarai.com +VITE_OLLAMA_BASE_URL=https://ollama.kevlarai.com + +# ============================================================================= +# Microsoft Entra ID (SSO) +# ============================================================================= +VITE_MICROSOFT_CLIENT_ID=dummy_client_id +VITE_MICROSOFT_CLIENT_SECRET_DESC="Local dev OAuth client secret" +VITE_MICROSOFT_CLIENT_SECRET_ID=secret-id +VITE_MICROSOFT_CLIENT_SECRET=secret-value +VITE_MICROSOFT_TENANT_ID=common + +# ============================================================================= +# Super Admin Configuration +# ============================================================================= +VITE_SUPER_ADMIN_EMAIL=admin@classroomcopilot.ai + +# ============================================================================= +# Port Configuration (for docker-compose, non-VITE_ prefix) +# ============================================================================= +PORT_FRONTEND=13000 +PORT_FRONTEND_HMR=3002 PORT_API=8000 PORT_SUPABASE=8000 diff --git a/src/axiosConfig.ts b/src/axiosConfig.ts index 68aedc6..59afa5c 100644 --- a/src/axiosConfig.ts +++ b/src/axiosConfig.ts @@ -1,11 +1,12 @@ import axios from 'axios'; +import { API_BASE as CONFIG_API_BASE } from './config/apiConfig'; import { logger } from './debugConfig'; // Use development backend URL if no custom URL is provided -const baseURL = import.meta.env.VITE_API_BASE || 'http://localhost:8080'; +const baseURL = CONFIG_API_BASE; -if (!import.meta.env.VITE_API_BASE) { - logger.warn('axios', '⚠️ VITE_API_BASE not set, defaulting to http://localhost:8080'); +if (!import.meta.env.VITE_API_BASE && !import.meta.env.VITE_API_URL) { + logger.warn('axios', '⚠️ VITE_API_BASE not set, using relative /api fallback'); } const instance = axios.create({ diff --git a/src/config/apiConfig.ts b/src/config/apiConfig.ts new file mode 100644 index 0000000..4916252 --- /dev/null +++ b/src/config/apiConfig.ts @@ -0,0 +1,44 @@ +/** + * Centralized API configuration for Classroom Copilot. + * + * Resolves the API base URL from environment variables with proper fallbacks. + * - In dev: VITE_API_BASE should always be set (e.g., http://192.168.0.64:18000) + * - In production: VITE_API_BASE should be set to the production API URL + * - Fallback: "/api" (relative path, works when API is served from same origin) + * + * IMPORTANT: Never hardcode localhost or 127.0.0.1 as fallbacks - they break + * remote dev access and do not work in containerized environments. + */ + +/** + * Primary API base URL. Use this for all API calls. + * Resolved from VITE_API_BASE (preferred) or VITE_API_URL (legacy alias). + */ +export const API_BASE: string = + import.meta.env.VITE_API_BASE || + import.meta.env.VITE_API_URL || + "/api"; + +/** + * TLSync worker URL for tldraw multiplayer. + * Resolved from VITE_TLSYNC_URL (preferred) or derived from VITE_FRONTEND_SITE_URL. + */ +export const TLSYNC_URL: string = + import.meta.env.VITE_TLSYNC_URL || + (import.meta.env.VITE_FRONTEND_SITE_URL?.startsWith("http") + ? `${import.meta.env.VITE_FRONTEND_SITE_URL}/tldraw` + : `https://${import.meta.env.VITE_FRONTEND_SITE_URL}/tldraw`); + +/** + * WhisperLive WebSocket URL for live transcription. + * Resolved from VITE_WHISPERLIVE_URL. Must be set in env. + */ +export const WHISPERLIVE_URL: string | undefined = + import.meta.env.VITE_WHISPERLIVE_URL; + +/** + * Search proxy URL (searxng). + * Resolved from VITE_SEARCH_URL. + */ +export const SEARCH_URL: string = + import.meta.env.VITE_SEARCH_URL || "/searxng-api/"; diff --git a/src/pages/Header.tsx b/src/pages/Header.tsx index c0a7e5f..788a17a 100644 --- a/src/pages/Header.tsx +++ b/src/pages/Header.tsx @@ -38,7 +38,7 @@ import { HEADER_HEIGHT } from './Layout'; import { logger } from '../debugConfig'; import { GraphNavigator } from '../components/navigation/GraphNavigator'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; const Header: React.FC = () => { const theme = useTheme(); diff --git a/src/pages/auth/PlatformAdminPage.tsx b/src/pages/auth/PlatformAdminPage.tsx index e973b00..89c5240 100644 --- a/src/pages/auth/PlatformAdminPage.tsx +++ b/src/pages/auth/PlatformAdminPage.tsx @@ -8,7 +8,7 @@ import { Refresh, School, People, EventNote, HourglassEmpty } from '@mui/icons-m import { useNavigate } from 'react-router'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; interface SchoolEntry { id: string; diff --git a/src/pages/dev/SimpleUploadTest.tsx b/src/pages/dev/SimpleUploadTest.tsx index 9729a34..034d1ad 100644 --- a/src/pages/dev/SimpleUploadTest.tsx +++ b/src/pages/dev/SimpleUploadTest.tsx @@ -123,7 +123,7 @@ const SimpleUploadTest: React.FC = () => { const fileInputRef = useRef(null); const dirInputRef = useRef(null); - const API_BASE = import.meta.env.VITE_API_BASE || 'http://127.0.0.1:8080'; + const API_BASE = import.meta.env.VITE_API_BASE || '/api'; const apiFetch = useCallback(async (url: string, init?: { method?: string; body?: FormData | string; headers?: Record }) => { const session = await supabase.auth.getSession(); diff --git a/src/pages/timetable/ClassDetailPage.tsx b/src/pages/timetable/ClassDetailPage.tsx index b7a4300..8b7f3b1 100644 --- a/src/pages/timetable/ClassDetailPage.tsx +++ b/src/pages/timetable/ClassDetailPage.tsx @@ -10,7 +10,7 @@ import { } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/LessonPlanDetailPage.tsx b/src/pages/timetable/LessonPlanDetailPage.tsx index 6f8fd3d..27da778 100644 --- a/src/pages/timetable/LessonPlanDetailPage.tsx +++ b/src/pages/timetable/LessonPlanDetailPage.tsx @@ -12,7 +12,7 @@ import { } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/LessonPlansPage.tsx b/src/pages/timetable/LessonPlansPage.tsx index 5855eea..e0e3a74 100644 --- a/src/pages/timetable/LessonPlansPage.tsx +++ b/src/pages/timetable/LessonPlansPage.tsx @@ -9,7 +9,7 @@ import { import { Add, Search, LibraryBooks, Edit, Delete, FilterList } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/SchoolSettingsPage.tsx b/src/pages/timetable/SchoolSettingsPage.tsx index a62585d..03bc93f 100644 --- a/src/pages/timetable/SchoolSettingsPage.tsx +++ b/src/pages/timetable/SchoolSettingsPage.tsx @@ -13,7 +13,7 @@ import { import { useNavigate } from 'react-router'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; const DAY_TYPE_OPTIONS = ['Academic', 'Holiday', 'Staff', 'OffTimetable']; const DAY_TYPE_COLORS: Record = { diff --git a/src/pages/timetable/StaffManagerPage.tsx b/src/pages/timetable/StaffManagerPage.tsx index e87888b..24467e6 100644 --- a/src/pages/timetable/StaffManagerPage.tsx +++ b/src/pages/timetable/StaffManagerPage.tsx @@ -10,7 +10,7 @@ import { } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/StudentLessonsPage.tsx b/src/pages/timetable/StudentLessonsPage.tsx index c21e3e7..789f706 100644 --- a/src/pages/timetable/StudentLessonsPage.tsx +++ b/src/pages/timetable/StudentLessonsPage.tsx @@ -5,7 +5,7 @@ import { import { ChevronLeft, ChevronRight, Today } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/StudentManagerPage.tsx b/src/pages/timetable/StudentManagerPage.tsx index 202e5c5..e1031ad 100644 --- a/src/pages/timetable/StudentManagerPage.tsx +++ b/src/pages/timetable/StudentManagerPage.tsx @@ -9,7 +9,7 @@ import { } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; interface Student { profile_id: string; diff --git a/src/pages/timetable/TaughtLessonsPage.tsx b/src/pages/timetable/TaughtLessonsPage.tsx index ffccb49..f4ee3c7 100644 --- a/src/pages/timetable/TaughtLessonsPage.tsx +++ b/src/pages/timetable/TaughtLessonsPage.tsx @@ -12,7 +12,7 @@ import { } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/tldraw/CCDocumentIntelligence/CCBundleViewer.tsx b/src/pages/tldraw/CCDocumentIntelligence/CCBundleViewer.tsx index b851d94..a098955 100644 --- a/src/pages/tldraw/CCDocumentIntelligence/CCBundleViewer.tsx +++ b/src/pages/tldraw/CCDocumentIntelligence/CCBundleViewer.tsx @@ -50,8 +50,8 @@ export const CCBundleViewer: React.FC<{ const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'), []); - const API_BASE_FALLBACK = 'http://127.0.0.1:8080'; + const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || '/api', []); + const API_BASE_FALLBACK = '/api'; const proxyUrl = useCallback(async (bucket: string, relPath: string) => { const token = accessToken || ''; diff --git a/src/pages/tldraw/CCDocumentIntelligence/CCDoclingViewer.tsx b/src/pages/tldraw/CCDocumentIntelligence/CCDoclingViewer.tsx index fa03301..fec71ba 100644 --- a/src/pages/tldraw/CCDocumentIntelligence/CCDoclingViewer.tsx +++ b/src/pages/tldraw/CCDocumentIntelligence/CCDoclingViewer.tsx @@ -182,7 +182,7 @@ export const CCDoclingViewer: React.FC<{ setError(null); try { // Try page-images manifest first - const API_BASE = import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'); + const API_BASE = import.meta.env.VITE_API_BASE || '/api'; try { const mRes = await fetch(`${API_BASE}/database/files/${encodeURIComponent(fileId)}/page-images/manifest`, { headers: { 'Authorization': `Bearer ${accessToken || ''}` } @@ -199,7 +199,7 @@ export const CCDoclingViewer: React.FC<{ } // Legacy: Load artefacts for file to find docling JSON artefacts - const artefactsRes = await fetch(`${import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api')}/database/files/${encodeURIComponent(fileId)}/artefacts`, { + const artefactsRes = await fetch(`${import.meta.env.VITE_API_BASE || '/api'}/database/files/${encodeURIComponent(fileId)}/artefacts`, { headers: { 'Authorization': `Bearer ${accessToken || ''}` } }); if (!artefactsRes.ok) throw new Error(await artefactsRes.text()); @@ -239,7 +239,7 @@ export const CCDoclingViewer: React.FC<{ run(); }, [fileId]); /* eslint-disable-line react-hooks/exhaustive-deps */ - const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'), []); + const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || '/api', []); const pageProxyUrl = useMemo(() => { if (!manifest) return undefined; diff --git a/src/pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence.tsx b/src/pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence.tsx index fd0f1f0..0ecbe6e 100644 --- a/src/pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence.tsx +++ b/src/pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence.tsx @@ -148,7 +148,7 @@ export const CCDocumentIntelligence: React.FC = () => { useEffect(() => { const loadBundles = async () => { if (!validFileId) return; - const API_BASE = import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'); + const API_BASE = import.meta.env.VITE_API_BASE || '/api'; const token = accessToken || ''; const res = await fetch(`${API_BASE}/database/files/${encodeURIComponent(validFileId)}/artefacts`, { headers: { Authorization: `Bearer ${token}` } }); if (!res.ok) return; @@ -223,7 +223,7 @@ export const CCDocumentIntelligence: React.FC = () => { const run = async () => { if (!validFileId) return; setOutlineOptions([]); - const API_BASE = import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'); + const API_BASE = import.meta.env.VITE_API_BASE || '/api'; try { const artsRes = await fetch(`${API_BASE}/database/files/${encodeURIComponent(validFileId)}/artefacts`, { headers: { 'Authorization': `Bearer ${accessToken || ''}` } @@ -486,7 +486,7 @@ export const CCDocumentIntelligence: React.FC = () => {