diff --git a/module-d/server-v2.js b/module-d/server-v2.js new file mode 100644 index 0000000..7c2da23 --- /dev/null +++ b/module-d/server-v2.js @@ -0,0 +1,179 @@ +/** + * ============================================================================ + * 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`); +}); \ No newline at end of file