ideas-generator/server/routes/files.js
DJP ca4ed4976d Add dual-agent system with file upload support and compact UI
- Implement dual-agent architecture supporting both Chat Completions and Responses API
- Add comprehensive file upload functionality for responses agents with image and PDF support
- Integrate OpenAI Conversations API for persistent file context across messages
- Add compact UI design with reduced font sizes and tighter spacing for better chat focus
- Include vector store management, document upload system, and admin dashboard enhancements
- Fix conversation persistence ensuring uploaded files remain accessible in follow-up questions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-04 14:45:25 -04:00

169 lines
No EOL
4.8 KiB
JavaScript

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const OpenAI = require('openai');
const router = express.Router();
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
organization: process.env.OPENAI_ORG_ID,
});
// Configure multer for temporary file uploads
const tempStorage = multer.diskStorage({
destination: function (req, file, cb) {
const uploadDir = path.join(__dirname, '../temp-uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: function (req, file, cb) {
const uniqueName = uuidv4() + path.extname(file.originalname);
cb(null, uniqueName);
}
});
const tempUpload = multer({
storage: tempStorage,
limits: {
fileSize: 50 * 1024 * 1024, // 50MB limit for temporary files
},
fileFilter: (req, file, cb) => {
// Allow common document and image types for chat
const allowedTypes = [
'application/pdf',
'text/plain',
'text/markdown',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/csv',
'application/json',
'image/jpeg',
'image/png',
'image/gif',
'image/webp'
];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Unsupported file type for temporary upload.'));
}
}
});
// Upload file for temporary chat use
router.post('/temp-upload', tempUpload.single('file'), async (req, res, next) => {
try {
if (!req.file) {
return res.status(400).json({
error: 'Validation Error',
message: 'No file uploaded'
});
}
console.log(`Temporary file uploaded: ${req.file.originalname}`);
// For images, we can use them directly with vision models
if (req.file.mimetype.startsWith('image/')) {
// Convert to base64 for vision API
const imageBuffer = fs.readFileSync(req.file.path);
const base64Image = imageBuffer.toString('base64');
res.json({
id: req.file.filename,
name: req.file.originalname,
type: 'image',
mimeType: req.file.mimetype,
size: req.file.size,
data: `data:${req.file.mimetype};base64,${base64Image}`,
message: 'Image uploaded for chat analysis'
});
} else {
// For documents, upload to OpenAI for processing
try {
const openaiFile = await openai.files.create({
file: fs.createReadStream(req.file.path),
purpose: 'assistants'
});
console.log(`Document uploaded to OpenAI: ${openaiFile.id}`);
res.json({
id: req.file.filename,
name: req.file.originalname,
type: 'document',
mimeType: req.file.mimetype,
size: req.file.size,
openaiFileId: openaiFile.id,
message: 'Document uploaded for chat analysis'
});
} catch (openaiError) {
console.error('Error uploading to OpenAI:', openaiError);
res.json({
id: req.file.filename,
name: req.file.originalname,
type: 'document',
mimeType: req.file.mimetype,
size: req.file.size,
error: 'Failed to process with OpenAI, but file uploaded locally',
message: 'Document uploaded locally (OpenAI processing failed)'
});
}
}
// Schedule cleanup after 1 hour
setTimeout(() => {
if (fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
console.log(`Cleaned up temporary file: ${req.file.filename}`);
}
}, 60 * 60 * 1000); // 1 hour
} catch (error) {
console.error('Error with temporary file upload:', error);
// Clean up uploaded file if there was an error
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
next(error);
}
});
// Get temporary file info (for chat reference)
router.get('/temp/:fileId', async (req, res, next) => {
try {
const { fileId } = req.params;
const filePath = path.join(__dirname, '../temp-uploads', fileId);
if (!fs.existsSync(filePath)) {
return res.status(404).json({
error: 'Not Found',
message: 'Temporary file not found or expired'
});
}
const stats = fs.statSync(filePath);
const ext = path.extname(fileId);
res.json({
id: fileId,
exists: true,
size: stats.size,
uploadedAt: stats.birthtime,
extension: ext
});
} catch (error) {
console.error('Error getting temporary file info:', error);
next(error);
}
});
module.exports = router;