Files
mini-test-projects-2-release/module-d/server-v2.js
2026-04-14 15:47:41 +09:00

179 lines
7.8 KiB
JavaScript

/**
* ============================================================================
* 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`);
});