/** * ============================================================================ * WSC2026 Web Technologies - Test Project: AI Middleware Server * ============================================================================ * 🚨 [Competitor Notice] * You do NOT need to modify this file. This is a black-box AI microservice. * Your task is to run this server, test the APIs, and build your own * Front-end and Back-end (Database) application that communicates with it. * ============================================================================ */ const express = require('express'); const cors = require('cors'); const multer = require('multer'); const app = express(); app.use(cors()); app.use(express.json()); // Use memory storage (Converts image buffer directly to Base64) const upload = multer({ storage: multer.memoryStorage() }); const OLLAMA_API = 'http://localhost:11434/api/generate'; // ---------------------------------------------------------------------------- // API 1: Text AI (Generate City Description and Country Info) // ---------------------------------------------------------------------------- app.post('/api/generate-text', async (req, res) => { const { city_name, country } = req.body; if (!city_name || !country) { return res.status(400).json({ error: 'city_name and country are required.' }); } try { const response = await fetch(OLLAMA_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'phi3', // 🚀 Advanced Prompt: Request city description and country info in JSON format prompt: `Provide information about ${city_name} in ${country}. You MUST return ONLY a valid JSON object. Do not use nested objects, numbers, or bullet points for the values. The values MUST be plain strings. Use this EXACT JSON structure: { "city_description": "Write a 2-sentence tourist introduction for the city here.", "country_info": "Write one interesting fact about the country here." }`, format: 'json', stream: false }) }); const data = await response.json(); const rawText = data.response; console.log(`[Text API] AI Original Response for ${city_name}, ${country}:`, rawText); // --- 🛡️ Text Hallucination Defense Logic --- let cityDescription = "Failed to generate city description. Please enter manually."; let countryInfo = "Failed to generate country information. Please enter manually."; try { const parsed = JSON.parse(rawText); cityDescription = parsed.city_description || cityDescription; countryInfo = parsed.country_info || countryInfo; } catch (e) { // Fallback: Use regular expressions if JSON parsing fails console.warn("[Text API] JSON parsing failed. Attempting regex extraction."); const descMatch = rawText.match(/"city_description"\s*:\s*"([^"]+)"/i); if (descMatch) cityDescription = descMatch[1]; const infoMatch = rawText.match(/"country_info"\s*:\s*"([^"]+)"/i); if (infoMatch) countryInfo = infoMatch[1]; } res.json({ city_description: cityDescription, country_info: countryInfo }); } catch (error) { console.error("[Text API] Error:", error); res.status(500).json({ error: 'Text AI processing failed. Ensure Ollama is running.' }); } }); // ---------------------------------------------------------------------------- // API 2: Vision AI (Integrated Image Analysis - Description and Keywords) // ---------------------------------------------------------------------------- app.post('/api/analyze-image', upload.single('image'), async (req, res) => { const file = req.file; const { city_name, country } = req.body; if (!file || !city_name || !country) { return res.status(400).json({ error: 'image, city_name, and country are required.' }); } try { const base64Image = file.buffer.toString('base64'); const response = await fetch(OLLAMA_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llava', prompt: `Analyze this image of ${city_name}, ${country}. You MUST return ONLY a valid JSON object. Do not use nested objects. The keywords MUST be a simple array of 3 strings. Use this EXACT JSON structure: { "description": "Write a short 1-2 sentence description of the image here.", "keywords": ["keyword1", "keyword2", "keyword3"] }`, images: [base64Image], format: 'json', stream: false }) }); const data = await response.json(); const rawAiText = data.response; console.log(`[Vision API] AI Original Response for ${city_name}, ${country}:`, rawAiText); // --- 🛡️ Hallucination Defense & Data Sanitization Logic --- let finalDescription = "No description generated. Please enter manually."; let finalKeywords = []; try { // Attempt 1: Force extract inside curly braces {} in case AI adds markdown wrappers const jsonMatch = rawAiText.match(/\{[\s\S]*\}/); const targetText = jsonMatch ? jsonMatch[0] : rawAiText; const parsed = JSON.parse(targetText); if (parsed.description) finalDescription = parsed.description; if (parsed.keywords) { if (Array.isArray(parsed.keywords)) { finalKeywords = parsed.keywords; } else if (typeof parsed.keywords === 'string') { // Split into an array if keywords arrive as a comma-separated string finalKeywords = parsed.keywords.split(','); } } } catch (e) { console.warn("[Vision API] JSON parsing failed. Attempting regex fallback."); // Attempt fallback extraction for description const descMatch = rawAiText.match(/"description"\s*:\s*"([^"]+)"/i); if (descMatch) finalDescription = descMatch[1]; // Attempt fallback extraction for keywords array const arrMatch = rawAiText.match(/\[(.*?)\]/s); if (arrMatch) finalKeywords = arrMatch[1].replace(/["']/g, '').split(','); } // Sanitize the keywords array (trim, remove brackets/quotes, filter empty, limit to 3) finalKeywords = finalKeywords .map(w => w.trim().replace(/[{}"\[\]]/g, '')) .filter(w => w.length > 0) .slice(0, 3); if (finalKeywords.length === 0) { finalKeywords = ["analysis_failed", "manual_input_required", "error"]; } // Finally, return the description (Text) and keywords (Stringified Array) res.json({ description: finalDescription, keywords: JSON.stringify(finalKeywords) }); } catch (error) { console.error("[Vision API] Error:", error); res.status(500).json({ error: 'Vision AI processing failed. Ensure Ollama is running.' }); } }); const PORT = 3000; app.listen(PORT, () => { console.log(`🚀 AI Middleware Server running on http://localhost:${PORT}`); console.log(`- Text API: POST http://localhost:${PORT}/api/generate-text`); console.log(`- Vision API: POST http://localhost:${PORT}/api/analyze-image`); });