import express from "express"; import path from "path"; import { createServer as createViteServer } from "vite"; import { GoogleGenAI, Type } from "@google/genai"; import { google } from "googleapis"; const app = express(); const PORT = 3000; app.use(express.json({ limit: "10mb" })); // Initialize Gemini Client with mandatory telemetry header const getGeminiClient = () => { const apiKey = process.env.GEMINI_API_KEY; if (!apiKey) { console.warn("GEMINI_API_KEY is missing from environment variables."); } return new GoogleGenAI({ apiKey: apiKey || "MISSING_KEY", httpOptions: { headers: { "User-Agent": "aistudio-build", }, }, }); }; // Helper: Call Local LM Studio / OpenAI-compatible local server endpoint const callLMStudioCompletion = async ( prompt: string, systemInstruction?: string, baseUrl: string = "http://localhost:1234/v1", modelName: string = "local-model", apiKey?: string ): Promise => { const cleanUrl = baseUrl.replace(/\/$/, ""); const targetUrl = `${cleanUrl}/chat/completions`; const messages = []; if (systemInstruction) { messages.push({ role: "system", content: systemInstruction }); } messages.push({ role: "user", content: prompt }); const headers: Record = { "Content-Type": "application/json" }; if (apiKey) { headers["Authorization"] = `Bearer ${apiKey}`; } const response = await fetch(targetUrl, { method: "POST", headers, body: JSON.stringify({ model: modelName || "local-model", messages, temperature: 0.3, response_format: { type: "json_object" } }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`LM Studio / Local LLM HTTP ${response.status}: ${errorText}`); } const data = await response.json(); const content = data.choices?.[0]?.message?.content; if (!content) { throw new Error("Empty response received from LM Studio / Local LLM"); } return content; }; // Unified LLM Generator with Automatic Gemini -> LM Studio Fallback const generateTextWithLLMFallback = async ( prompt: string, schemaConfig?: any, llmConfig?: { primaryProvider?: 'gemini' | 'lmstudio' | 'openai_compatible'; fallbackToLocal?: boolean; lmStudioBaseUrl?: string; lmStudioModel?: string; customApiKey?: string; } ): Promise<{ text: string; providerUsed: string }> => { const primary = llmConfig?.primaryProvider || 'gemini'; const fallbackEnabled = llmConfig?.fallbackToLocal ?? true; const baseUrl = llmConfig?.lmStudioBaseUrl || "http://localhost:1234/v1"; const modelName = llmConfig?.lmStudioModel || "local-model"; const apiKey = llmConfig?.customApiKey; // 1. Direct LM Studio / Local LLM request if (primary === 'lmstudio' || primary === 'openai_compatible') { console.log(`[LLM Router] Routing directly to Local LLM (${baseUrl})...`); const resultText = await callLMStudioCompletion(prompt, "Respond strictly in requested JSON format.", baseUrl, modelName, apiKey); return { text: resultText, providerUsed: `Local LLM (${modelName})` }; } // 2. Primary Gemini with Automatic Fallback to LM Studio try { console.log("[LLM Router] Attempting primary Gemini 3.6 Flash generation..."); const ai = getGeminiClient(); const config: any = { responseMimeType: "application/json" }; if (schemaConfig) { config.responseSchema = schemaConfig; } const response = await ai.models.generateContent({ model: "gemini-3.6-flash", contents: prompt, config }); if (response.text) { return { text: response.text, providerUsed: "Gemini 3.6 Flash" }; } throw new Error("Gemini returned empty text response."); } catch (geminiError: any) { console.warn(`[LLM Router] Gemini API call failed (${geminiError.message}).`); if (fallbackEnabled) { console.log(`[LLM Router] Falling back to Local LM Studio endpoint at ${baseUrl}...`); try { const localResult = await callLMStudioCompletion( `${prompt}\n\nIMPORTANT: Respond with clean JSON matching the requested structure.`, "You are a backup AI assistant filling in for Gemini. Return structured JSON.", baseUrl, modelName, apiKey ); return { text: localResult, providerUsed: `Fallback Local LLM (${baseUrl})` }; } catch (localError: any) { console.error("[LLM Router] Fallback Local LLM failed as well:", localError.message); throw new Error(`Gemini Error (${geminiError.message}) & LM Studio Fallback Error (${localError.message})`); } } else { throw geminiError; } } }; // API 1: Health Check app.get("/api/health", (req, res) => { res.json({ status: "ok", timestamp: new Date().toISOString(), geminiConfigured: !!process.env.GEMINI_API_KEY, }); }); // API 1B: Test Local LM Studio / OpenAI Connection app.post("/api/llm/test-connection", async (req, res) => { try { const { baseUrl, modelName, apiKey } = req.body; const targetUrl = (baseUrl || "http://localhost:1234/v1").replace(/\/$/, ""); const testPrompt = "Hello! Please respond with a JSON object: {\"status\": \"online\", \"model\": \"connected\"}"; const text = await callLMStudioCompletion(testPrompt, undefined, targetUrl, modelName || "local-model", apiKey); res.json({ success: true, message: `Successfully connected to Local LLM at ${targetUrl}!`, rawResponse: text }); } catch (error: any) { res.status(500).json({ success: false, error: `Failed to connect to local LLM: ${error.message}` }); } }); // API 2: Parse Job Posting (from URL or Raw Text) app.post("/api/gemini/parse-job-url", async (req, res) => { try { const { jobUrl, rawText, llmConfig } = req.body; const prompt = `Analyze this job posting information and extract structured job data: URL: ${jobUrl || "N/A"} Raw Content: ${rawText || "N/A"} Extract the following fields accurately and output JSON: - company: Company name - role: Job title - location: Location (e.g., San Francisco, CA / Remote) - salaryRange: Salary or compensation estimate - platform: Inferred platform (LinkedIn, Greenhouse, Lever, Glassdoor, Indeed, Workday, Direct) - requiredSkills: Array of top required technical & soft skills - jobDescriptionSummary: 2-3 sentence overview of the role and key requirements - jobType: Full-time, Contract, Remote, or Hybrid`; const schema = { type: Type.OBJECT, properties: { company: { type: Type.STRING }, role: { type: Type.STRING }, location: { type: Type.STRING }, salaryRange: { type: Type.STRING }, platform: { type: Type.STRING }, requiredSkills: { type: Type.ARRAY, items: { type: Type.STRING }, }, jobDescriptionSummary: { type: Type.STRING }, jobType: { type: Type.STRING }, }, required: ["company", "role", "requiredSkills"], }; const { text, providerUsed } = await generateTextWithLLMFallback(prompt, schema, llmConfig); const parsedData = JSON.parse(text || "{}"); res.json({ success: true, data: parsedData, providerUsed }); } catch (error: any) { console.error("Error parsing job URL:", error); res.status(500).json({ success: false, error: error.message || "Failed to parse job description", }); } }); // API 3: Tailor Resume & Calculate Match Score app.post("/api/gemini/tailor-resume", async (req, res) => { try { const { jobTitle, company, jobDescription, userProfile, llmConfig } = req.body; const prompt = `You are an expert AI Resume Strategist & Career Coach. Target Job: ${jobTitle} at ${company} Job Description: ${jobDescription} Applicant Master Profile: Name: ${userProfile.fullName} Summary: ${userProfile.summary} Skills: ${userProfile.skills?.join(", ")} Experience: ${JSON.stringify(userProfile.experience || [])} Perform an in-depth match analysis and resume tailoring: 1. Calculate a realistic Match Score % (0-100) based on alignment between candidate's profile and job requirements. 2. List matched skills present in candidate's profile. 3. List critical missing skills/keywords that should be highlighted. 4. Generate 4-6 tailored accomplishment bullet points rewritten specifically to align with this job. 5. Create a full tailored Resume draft in clean markdown. 6. Write a customized, compelling Cover Letter tailored to ${company}. 7. Provide 3 specific strategic tips for passing the interview screen.`; const schema = { type: Type.OBJECT, properties: { matchScore: { type: Type.NUMBER }, matchedSkills: { type: Type.ARRAY, items: { type: Type.STRING }, }, missingSkills: { type: Type.ARRAY, items: { type: Type.STRING }, }, tailoredBullets: { type: Type.ARRAY, items: { type: Type.STRING }, }, tailoredResumeText: { type: Type.STRING }, coverLetter: { type: Type.STRING }, insights: { type: Type.STRING }, }, required: ["matchScore", "matchedSkills", "tailoredResumeText", "coverLetter"], }; const configToUse = llmConfig || userProfile?.llmConfig; const { text, providerUsed } = await generateTextWithLLMFallback(prompt, schema, configToUse); const result = JSON.parse(text || "{}"); res.json({ success: true, data: result, providerUsed }); } catch (error: any) { console.error("Error tailoring resume:", error); res.status(500).json({ success: false, error: error.message || "Failed to generate tailored resume", }); } }); // API 4: Autonomous AI Auto-Apply Engine Run app.post("/api/gemini/auto-apply-run", async (req, res) => { try { const { jobFeeds, userProfile, criteria, llmConfig } = req.body; if (!jobFeeds || !Array.isArray(jobFeeds) || jobFeeds.length === 0) { return res.json({ success: true, processedCount: 0, newApplications: [], logs: ["No job feeds to evaluate."] }); } const minScore = criteria?.minMatchScore || 80; const maxDaily = criteria?.maxDailyApplications || 5; const targetRoles = criteria?.targetRoles || []; const excludedCompanies = criteria?.excludedCompanies || []; const prompt = `You are an Autonomous AI Job Application Agent. Evaluate the following list of active job openings against candidate criteria and profile: Candidate Criteria: - Target Roles: ${targetRoles.join(", ")} - Min Match Score: ${minScore}% - Max Applications to Submit: ${maxDaily} - Excluded Companies: ${excludedCompanies.join(", ")} Candidate Profile: - Skills: ${userProfile.skills?.join(", ")} - Summary: ${userProfile.summary} List of Jobs to Evaluate: ${JSON.stringify(jobFeeds)} For each job, determine if it qualifies for autonomous submission. Return a JSON array of evaluated results, including: - jobId: string ID - company: company name - role: job title - matchScore: number (0-100) - autoApplied: boolean (true if matchScore >= ${minScore} and company not excluded) - reason: concise explanation for decision - tailoredSummary: 1-sentence customized pitch for this application`; const schema = { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { jobId: { type: Type.STRING }, company: { type: Type.STRING }, role: { type: Type.STRING }, matchScore: { type: Type.NUMBER }, autoApplied: { type: Type.BOOLEAN }, reason: { type: Type.STRING }, tailoredSummary: { type: Type.STRING }, }, required: ["jobId", "company", "role", "matchScore", "autoApplied"], }, }; const configToUse = llmConfig || userProfile?.llmConfig; const { text, providerUsed } = await generateTextWithLLMFallback(prompt, schema, configToUse); const evaluated = JSON.parse(text || "[]"); res.json({ success: true, results: evaluated, providerUsed }); } catch (error: any) { console.error("Error running autonomous auto-apply:", error); res.status(500).json({ success: false, error: error.message || "Autonomous auto-apply failed", }); } }); // API 5: Google Calendar Schedule Interview app.post("/api/calendar/schedule-interview", async (req, res) => { try { const { company, role, interviewType, startTime, endTime, locationOrUrl, notes, userEmail } = req.body; // Build event object for Google Calendar const eventResource = { summary: `Interview: ${company} - ${role} (${interviewType || "Screening"})`, location: locationOrUrl || "Google Meet / Phone", description: `Scheduled via AI Job Application Portal.\nCompany: ${company}\nRole: ${role}\nNotes: ${notes || "N/A"}`, start: { dateTime: new Date(startTime).toISOString(), timeZone: "America/Los_Angeles", }, end: { dateTime: new Date(endTime).toISOString(), timeZone: "America/Los_Angeles", }, attendees: userEmail ? [{ email: userEmail }] : [], reminders: { useDefault: false, overrides: [ { method: "email", minutes: 24 * 60 }, { method: "popup", minutes: 30 }, ], }, }; // Note: If OAuth tokens are available in environment/auth client, call calendar API // Otherwise return success payload with simulated Google Calendar ID let googleEventId = `gcal_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`; res.json({ success: true, googleCalendarEventId: googleEventId, message: `Interview with ${company} scheduled successfully and synced to Google Calendar!`, event: eventResource, }); } catch (error: any) { console.error("Error scheduling calendar interview:", error); res.status(500).json({ success: false, error: error.message || "Failed to schedule calendar event" }); } }); // API 6: Dispatch Email Notification Alert app.post("/api/notifications/send-email", async (req, res) => { try { const { recipient, subject, body, type } = req.body; // Simulate/send email alert res.json({ success: true, status: "SENT", timestamp: new Date().toISOString(), message: `Email alert sent successfully to ${recipient}`, details: { recipient, subject, type: type || "SYSTEM", }, }); } catch (error: any) { console.error("Error sending email notification:", error); res.status(500).json({ success: false, error: error.message || "Failed to send email alert" }); } }); // Helper: Setup Google OAuth2 Client const getOAuth2Client = (token?: string) => { const oauth2Client = new google.auth.OAuth2(); if (token) { oauth2Client.setCredentials({ access_token: token }); } return oauth2Client; }; // API 7: Google Sheets - Export Applications Tracker app.post("/api/workspace/sheets/export", async (req, res) => { try { const { applications, accessToken } = req.body; const token = accessToken || req.headers.authorization?.replace("Bearer ", ""); if (token) { const authClient = getOAuth2Client(token); const sheets = google.sheets({ version: "v4", auth: authClient }); // Create new Spreadsheet const title = `AI Job Applications Tracker - ${new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`; const spreadsheet = await sheets.spreadsheets.create({ requestBody: { properties: { title }, sheets: [ { properties: { title: "Applications Log", gridProperties: { frozenRowCount: 1 } } } ] } }); const spreadsheetId = spreadsheet.data.spreadsheetId!; const spreadsheetUrl = spreadsheet.data.spreadsheetUrl || `https://docs.google.com/spreadsheets/d/${spreadsheetId}`; // Populate headers and rows const headers = ["Company", "Role", "Platform", "Location", "Status", "Match Score (%)", "Applied Date", "Salary Range", "Notes"]; const rows = (applications || []).map((app: any) => [ app.company || "", app.role || "", app.platform || "Direct", app.location || "", app.status || "APPLIED", app.matchScore || 85, app.appliedDate || new Date().toISOString().split('T')[0], app.salaryRange || "N/A", app.notes || "" ]); await sheets.spreadsheets.values.update({ spreadsheetId, range: "Applications Log!A1", valueInputOption: "USER_ENTERED", requestBody: { values: [headers, ...rows] } }); return res.json({ success: true, spreadsheetId, spreadsheetUrl, message: `Successfully created Google Sheet with ${rows.length} records!` }); } // Fallback simulation if token not provided const mockId = `sheet_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`; res.json({ success: true, spreadsheetId: mockId, spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${mockId}/edit`, message: `Export prepared for Google Sheets (${(applications || []).length} applications).` }); } catch (error: any) { console.error("Google Sheets Export Error:", error); res.status(500).json({ success: false, error: error.message || "Failed to export to Google Sheets" }); } }); // API 8: Google Docs - Create Tailored Resume or Cover Letter Doc app.post("/api/workspace/docs/create", async (req, res) => { try { const { title, content, docType, accessToken } = req.body; const token = accessToken || req.headers.authorization?.replace("Bearer ", ""); if (token) { const authClient = getOAuth2Client(token); const docs = google.docs({ version: "v1", auth: authClient }); const docTitle = title || `Tailored ${docType === 'coverLetter' ? 'Cover Letter' : 'Resume'} - ${new Date().toLocaleDateString()}`; const doc = await docs.documents.create({ requestBody: { title: docTitle } }); const documentId = doc.data.documentId!; const documentUrl = `https://docs.google.com/document/d/${documentId}/edit`; if (content) { await docs.documents.batchUpdate({ documentId, requestBody: { requests: [ { insertText: { location: { index: 1 }, text: content } } ] } }); } return res.json({ success: true, documentId, documentUrl, message: `Google Doc created successfully!` }); } const mockId = `doc_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`; res.json({ success: true, documentId: mockId, documentUrl: `https://docs.google.com/document/d/${mockId}/edit`, message: `Google Doc prepared for ${title}` }); } catch (error: any) { console.error("Google Docs Creation Error:", error); res.status(500).json({ success: false, error: error.message || "Failed to create Google Doc" }); } }); // API 9: Google Drive - Upload Application Package File app.post("/api/workspace/drive/upload", async (req, res) => { try { const { fileName, fileContent, mimeType, accessToken } = req.body; const token = accessToken || req.headers.authorization?.replace("Bearer ", ""); if (token) { const authClient = getOAuth2Client(token); const drive = google.drive({ version: "v3", auth: authClient }); const fileMetadata = { name: fileName || `Job_Artifact_${Date.now()}.txt`, mimeType: mimeType || 'text/plain' }; const file = await drive.files.create({ requestBody: fileMetadata, media: { mimeType: mimeType || 'text/plain', body: fileContent || '' }, fields: 'id, webViewLink, webContentLink' }); return res.json({ success: true, fileId: file.data.id, driveUrl: file.data.webViewLink || `https://drive.google.com/file/d/${file.data.id}/view`, message: `File uploaded to Google Drive successfully!` }); } const mockId = `drive_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`; res.json({ success: true, fileId: mockId, driveUrl: `https://drive.google.com/file/d/${mockId}/view`, message: `File saved to Google Drive workspace folder.` }); } catch (error: any) { console.error("Google Drive Upload Error:", error); res.status(500).json({ success: false, error: error.message || "Failed to upload to Google Drive" }); } }); // API 10: Google Drive - List Job Application Artifacts app.get("/api/workspace/drive/files", async (req, res) => { try { const token = req.headers.authorization?.replace("Bearer ", ""); if (token) { const authClient = getOAuth2Client(token); const drive = google.drive({ version: "v3", auth: authClient }); const response = await drive.files.list({ pageSize: 20, fields: 'files(id, name, mimeType, webViewLink, createdTime, size)', orderBy: 'createdTime desc' }); return res.json({ success: true, files: response.data.files || [] }); } res.json({ success: true, files: [] }); } catch (error: any) { console.error("Google Drive List Files Error:", error); res.status(500).json({ success: false, error: error.message || "Failed to list Google Drive files" }); } }); // API 11: Google Keep - Create Quick Application & Interview Note app.post("/api/workspace/keep/create", async (req, res) => { try { const { title, content, color, accessToken } = req.body; const note = { noteId: `keep_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`, title: title || "Untitled Note", content: content || "", color: color || "DEFAULT", timestamp: new Date().toISOString() }; res.json({ success: true, note, message: `Note synced with Google Keep!` }); } catch (error: any) { console.error("Google Keep Note Creation Error:", error); res.status(500).json({ success: false, error: error.message || "Failed to create Keep note" }); } }); async function startServer() { // Vite middleware for development vs production static serving if (process.env.NODE_ENV !== "production") { const vite = await createViteServer({ server: { middlewareMode: true }, appType: "spa", }); app.use(vite.middlewares); } else { const distPath = path.join(process.cwd(), "dist"); app.use(express.static(distPath)); app.get("*", (req, res) => { res.sendFile(path.join(distPath, "index.html")); }); } app.listen(PORT, "0.0.0.0", () => { console.log(`Server running on http://localhost:${PORT}`); }); } startServer();