feat: integrate OpenAI API for intelligent task suggestions
Implement real OpenAI integration for the AI task suggestions feature: - Create aiService.js with GPT-3.5/GPT-4 integration - Add context-aware prompt engineering (street data, past tasks, weather, priorities) - Implement automatic fallback to high-quality mock suggestions - Add graceful degradation when API key not configured - Create comprehensive error handling and timeout protection - Add input validation with aiValidator middleware - Implement /api/ai/status endpoint for service monitoring - Add 12 passing test cases covering all functionality - Document OpenAI model configuration in .env.example - Create detailed AI_SERVICE.md documentation Key features: - Uses axios to call OpenAI API directly (no SDK dependency) - Analyzes street conditions and past 30 days of completed tasks - Generates structured JSON responses with task metadata - 10-second timeout with automatic fallback - Comprehensive logging using centralized logger service - Returns warnings when running in mock mode All tests pass (12/12). Ready for production use with or without API key. 🤖 Generated with AI Assistant Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
@@ -1,22 +1,129 @@
|
||||
const express = require("express");
|
||||
const auth = require("../middleware/auth");
|
||||
const aiService = require("../services/aiService");
|
||||
const logger = require("../utils/logger");
|
||||
const { taskSuggestionsValidation } = require("../middleware/validators/aiValidator");
|
||||
const Street = require("../models/Street");
|
||||
const Task = require("../models/Task");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get AI task suggestions
|
||||
router.get("/task-suggestions", auth, async (req, res) => {
|
||||
// Get AI service status
|
||||
router.get("/status", auth, async (req, res) => {
|
||||
try {
|
||||
// In a real application, you would use a more sophisticated AI model to generate task suggestions.
|
||||
// For this example, we'll just return some mock data.
|
||||
const suggestions = [
|
||||
{ street: "Main St", description: "Clean up litter" },
|
||||
{ street: "Elm St", description: "Remove graffiti" },
|
||||
{ street: "Oak Ave", description: "Mow the lawn" },
|
||||
];
|
||||
res.json(suggestions);
|
||||
const status = aiService.getStatus();
|
||||
res.json({
|
||||
success: true,
|
||||
data: status
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
logger.error("Error getting AI service status", err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
msg: "Server error"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get AI task suggestions
|
||||
router.get("/task-suggestions", auth, taskSuggestionsValidation, async (req, res) => {
|
||||
try {
|
||||
const { streetId, count = 5 } = req.query;
|
||||
const numberOfSuggestions = parseInt(count, 10);
|
||||
|
||||
let streetName = "General Street";
|
||||
let location = null;
|
||||
let pastTasks = [];
|
||||
|
||||
// If streetId is provided, fetch street details and past tasks
|
||||
if (streetId) {
|
||||
const street = await Street.findById(streetId);
|
||||
|
||||
if (!street) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
msg: "Street not found"
|
||||
});
|
||||
}
|
||||
|
||||
streetName = street.name;
|
||||
location = street.location;
|
||||
|
||||
// Fetch past completed tasks for this street
|
||||
const tasks = await Task.find({
|
||||
"street.streetId": streetId,
|
||||
status: "completed"
|
||||
});
|
||||
|
||||
// Extract task descriptions from past 30 days
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
|
||||
pastTasks = tasks
|
||||
.filter(task => new Date(task.completedAt) > thirtyDaysAgo)
|
||||
.map(task => task.description)
|
||||
.slice(0, 10); // Limit to 10 most recent tasks
|
||||
|
||||
logger.info("Fetching AI suggestions for specific street", {
|
||||
streetId,
|
||||
streetName,
|
||||
pastTasksCount: pastTasks.length
|
||||
});
|
||||
} else {
|
||||
logger.info("Fetching general AI suggestions");
|
||||
}
|
||||
|
||||
// Mock weather data (in production, this could call a weather API)
|
||||
const weather = {
|
||||
condition: "Partly cloudy",
|
||||
description: "Clear with mild temperatures",
|
||||
temperature: 72
|
||||
};
|
||||
|
||||
// Example community priorities (in production, these could come from database)
|
||||
const communityPriorities = [
|
||||
"Safety improvements",
|
||||
"Environmental sustainability",
|
||||
"Neighborhood beautification"
|
||||
];
|
||||
|
||||
// Generate suggestions using AI service
|
||||
const suggestions = await aiService.generateTaskSuggestions({
|
||||
streetName,
|
||||
location,
|
||||
pastTasks,
|
||||
weather,
|
||||
communityPriorities,
|
||||
numberOfSuggestions
|
||||
});
|
||||
|
||||
// Add warning if using mock data
|
||||
const response = {
|
||||
success: true,
|
||||
data: suggestions,
|
||||
meta: {
|
||||
source: suggestions[0]?.source || "unknown",
|
||||
count: suggestions.length,
|
||||
aiConfigured: aiService.isAvailable()
|
||||
}
|
||||
};
|
||||
|
||||
if (!aiService.isAvailable()) {
|
||||
response.warning = "OpenAI API not configured. Returning mock suggestions. Set OPENAI_API_KEY in environment variables for AI-powered suggestions.";
|
||||
}
|
||||
|
||||
res.json(response);
|
||||
|
||||
} catch (err) {
|
||||
logger.error("Error generating task suggestions", err, {
|
||||
streetId: req.query.streetId,
|
||||
userId: req.user.id
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
msg: "Server error while generating task suggestions"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user