import express from 'express'; import multer from 'multer'; import { GoogleGenerativeAI } from "@google/generative-ai"; import { HarmCategory, HarmBlockThreshold } from '@google/generative-ai'; import { Mistral } from "@mistralai/mistralai"; import dotenv from "dotenv"; import sharp from 'sharp'; import rateLimit from 'express-rate-limit'; dotenv.config(); // Configure rate limiter const limiter = rateLimit({ windowMs: 60 * 1000, // 1 minute max: 10, // 1 request per window message: { status: 429, error: "Too many requests, please try again after 1 minute" }, standardHeaders: true, legacyHeaders: false }); const app = express(); const upload = multer({ storage: multer.memoryStorage() }); const port = 9081; // Add after imports let requestCounter = { analyze: 0, compareAnalyze: 0, total: 0 }; // Model type enum const ModelType = { GEMINI: 'GEMINI', MIXTRAL: 'MIXTRAL', GEMINI_THINKING: 'GEMINI_THINKING' }; class ImageAnalysisClient { constructor() { this.init(); } init() { // Initialize Gemini // Initialize Gemini models const geminiApiKey = process.env.API_KEY6; const geminiThinkingApiKey = process.env.API_KEY5; if (!geminiApiKey) throw new Error("Gemini API_KEY not found"); if (!geminiThinkingApiKey) throw new Error("Gemini Thinking API_KEY not found"); this.genAI = new GoogleGenerativeAI(geminiApiKey); this.genAIThinking = new GoogleGenerativeAI(geminiThinkingApiKey); // Initialize Mixtral const mixtralApiKey = process.env.API_KEY_MIXTRAL12; if (!mixtralApiKey) throw new Error("Mixtral API_KEY not found"); this.mistral = new Mistral({ apiKey: mixtralApiKey }); } async analyzeImage(imageBuffer, modelType) { const processedImageBuffer = await sharp(imageBuffer) .grayscale() .jpeg({ quality: 100, progressive: true }) .toBuffer(); const base64Image = processedImageBuffer.toString('base64'); const prompt = `Analyze the image for production date and expiration date. Return in JSON format. Rules: - Only extract dates that are explicitly labeled or clearly marked - If no clear production date or manufacturing date is found, set production_date to null - If no clear expiration date or 保质期 or 质期 is found, set expiration_date to null - Do not make assumptions or guess dates EXCEPT: * If only one date is found with no label: - If date is future (after ${new Date().toISOString().split('T')[0]}), set as expiration_date - If date is past, set as production_date - Date format must be YYYY.MM.DD when found - Production date and expiration date cannot be the same day Example responses: Case 1 - Labeled dates: { "production_date": "2024.08.20", "expiration_date": "2026.08.20", "production_id": null, "additional_info": null } Case 2 - Single unlabeled future date: { "production_date": null, "expiration_date": "2025.04.01", // Future date assumed as expiration "production_id": null, "additional_info": "Single unlabeled date found" } Case 3 - Single unlabeled past date: { "production_date": "2023.04.01", // Past date assumed as production "expiration_date": null, "production_id": null, "additional_info": "Single unlabeled date found" } Important: Return null for any field where the information is not explicitly visible in the image.`; try { if (modelType === ModelType.GEMINI) { return await this.analyzeWithGemini(base64Image, prompt); } else if (modelType === ModelType.GEMINI_THINKING) { return await this.analyzeWithGeminiThinking(base64Image, prompt); } else { return await this.analyzeWithMixtral(base64Image, prompt); } } catch (error) { console.error(`Error analyzing with ${modelType}:`, error); throw error; } } async analyzeWithGemini(base64Image, prompt) { const model = this.genAI.getGenerativeModel({ model: "gemini-2.0-flash-exp" }); const result = await model.generateContent([ { text: prompt }, { inlineData: { data: base64Image, mimeType: "image/jpeg" } } ]); const text = result.response.text(); const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/); if (jsonMatch) { return JSON.parse(jsonMatch[1]); } throw new Error("No JSON content found in Gemini response"); } async analyzeWithMixtral(base64Image, prompt) { try { const result = await this.mistral.chat.stream({ model: "pixtral-large-latest", messages: [ { role: "user", content: [ { type: "text", text: prompt }, { type: "image_url", imageUrl: `data:image/jpeg;base64,${base64Image}`, }, ] } ], max_tokens: 1024, temperature: 0.8, }); let response = ""; for await (const chunk of result) { response += chunk.data.choices[0].delta.content; } const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/); if (jsonMatch) { return JSON.parse(jsonMatch[1]); } return { production_date: null, expiration_date: null, production_id: null, additional_info: "Error: No valid JSON found in Mixtral response" }; } catch (error) { console.error("Mixtral API error:", error); return { production_date: null, expiration_date: null, production_id: null, additional_info: `Mixtral Error: ${error.message}` }; } } async analyzeWithGeminiThinking(base64Image, prompt) { const model = this.genAIThinking.getGenerativeModel({ model: "gemini-2.0-flash-thinking-exp-1219" }); const result = await model.generateContent([ { text: prompt }, { inlineData: { data: base64Image, mimeType: "image/jpeg" } } ]); const text = result.response.text(); const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/); if (jsonMatch) { return JSON.parse(jsonMatch[1]); } throw new Error("No JSON content found in Gemini Thinking response"); } async ask(prompt, modelType) { try { if (modelType === ModelType.GEMINI || modelType === ModelType.GEMINI_THINKING) { const genAI = modelType === ModelType.GEMINI ? this.genAI : this.genAIThinking; const modelName = modelType === ModelType.GEMINI ? "gemini-2.0-flash-exp" : "gemini-2.0-flash-thinking-exp-1219"; const model = genAI.getGenerativeModel({ model: modelName }); const chat = model.startChat({ generationConfig: { maxOutputTokens: 8192, temperature: 1, }, safetySettings: [ { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE }, { category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_NONE }, { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE }, { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_NONE }, ] }); let totalResponse = ""; const result = await chat.sendMessageStream(prompt); for await (const chunk of result.stream) { const chunkText = chunk.text(); totalResponse += chunkText; } return { response: totalResponse }; } else { // Mixtral handling const result = await this.mistral.chat.stream({ model: "mistral-large-latest", messages: [{ role: "user", content: prompt }], max_tokens: 1024*128, temperature: 0.8, }); let response = ""; for await (const chunk of result) { response += chunk.data.choices[0].delta.content; } return { response }; } } catch (error) { if (error.toString().includes("Too Many Requests") || error.toString().includes("Please try again later")) { throw new Error("Rate limit exceeded, please try again later"); } console.error(`Error in ${modelType} ask:`, error); throw error; } } } const client = new ImageAnalysisClient(); app.post('/analyze', limiter,upload.single('image'), async (req, res) => { requestCounter.analyze++; requestCounter.total++; try { if (!req.file) { return res.status(400).json({ status: 400, error: "No image file provided" }); } const modelType = req.body.model?.toUpperCase(); if (!ModelType[modelType]) { return res.status(400).json({ status: 400, error: "Invalid model type. Use GEMINI or MIXTRAL" }); } const result = await client.analyzeImage(req.file.buffer, modelType); res.json({ status: 200, data: result }); } catch (error) { console.error("Analysis error:", error); res.status(500).json({ status: 500, error: error.message }); } }); // Update HTML form app.get('/check', limiter, (req, res) => { res.send(`

AI API Service

API Endpoints:

Image Analysis Form:

Select image file:

Select model:

Ask AI Form:

Question:

Select model:


                
`); }); app.post('/compareAnalyze',limiter, upload.single('image'), async (req, res) => { requestCounter.compareAnalyze++; requestCounter.total++; try { if (!req.file) { return res.status(400).json({ status: 400, error: "No image file provided" }); } const [geminiResult, mixtralResult, geminiThinkingResult] = await Promise.all([ client.analyzeImage(req.file.buffer, ModelType.GEMINI) .catch(error => ({ production_date: null, expiration_date: null, production_id: null, additional_info: null })), client.analyzeImage(req.file.buffer, ModelType.MIXTRAL) .catch(error => ({ production_date: null, expiration_date: null, production_id: null, additional_info: null })), client.analyzeImage(req.file.buffer, ModelType.GEMINI_THINKING) .catch(error => ({ production_date: null, expiration_date: null, production_id: null, additional_info: null })) ]); res.json({ status: 200, datas: [geminiResult, mixtralResult, geminiThinkingResult] }); } catch (error) { console.error("Comparison analysis error:", error); res.status(500).json({ status: 500, error: 'Unknown error, please contact the administrator' }); } }); // Add status endpoint // Update status endpoint app.get('/status', (req, res) => { res.json({ status: "running", models: "model", version: "1.0.0", copyright: "sonygod", requests: { f1: requestCounter.analyze, f2: requestCounter.compareAnalyze, total: requestCounter.total } }); }); app.post('/ask', limiter, express.json(), async (req, res) => { requestCounter.total++; try { const { prompt, model } = req.body; if (!prompt) { return res.status(400).json({ status: 400, error: "No prompt provided" }); } const modelType = model?.toUpperCase(); if (!ModelType[modelType]) { return res.status(400).json({ status: 400, error: "Invalid model type. Use GEMINI, MIXTRAL, or GEMINI_THINKING" }); } const result = await client.ask(prompt, modelType); res.json({ status: 200, data: result }); } catch (error) { console.error("Ask error:", error); res.status(500).json({ status: 500, error: error.message }); } }); // Change server binding app.listen(port, '0.0.0.0', () => { console.log(`Server running on port ${port} (0.0.0.0)`); });