Files
mini-test-projects-2-release/module-d/server.js
2026-04-12 18:58:03 +09:00

173 lines
7.6 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}.
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`);
});