commit for day 1
This commit is contained in:
173
module-d/server.js
Normal file
173
module-d/server.js
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* 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}.
|
||||
Return a JSON object with exactly two keys:
|
||||
1. "city_description": A 2-sentence tourist introduction for the city.
|
||||
2. "country_info": One interesting or essential fact about the country ${country}.
|
||||
DO NOT output any explanations. JUST the JSON object.`,
|
||||
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}.
|
||||
Return a JSON object with:
|
||||
1. "description": A short 1-2 sentence description of the image.
|
||||
2. "keywords": An array of exactly 3 descriptive words.
|
||||
JUST the JSON object. DO NOT output any markdown, tags, or extra text.`,
|
||||
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`);
|
||||
});
|
||||
Reference in New Issue
Block a user