mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-07 06:02:43 +08:00
feat: support attached images for all providers
This commit is contained in:
@@ -64,6 +64,7 @@ import pluginsRoutes from './routes/plugins.js';
|
||||
import providerRoutes from './modules/providers/provider.routes.js';
|
||||
import voiceRoutes from './voice-proxy.js';
|
||||
import browserUseRoutes from './modules/browser-use/browser-use.routes.js';
|
||||
import { assetsRoutes } from './modules/assets/index.js';
|
||||
import browserUseMcpRoutes from './modules/browser-use/browser-use-mcp.routes.js';
|
||||
import { browserUseService } from './modules/browser-use/browser-use.service.js';
|
||||
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
||||
@@ -185,6 +186,9 @@ app.use('/api/auth', authRoutes);
|
||||
// Projects API Routes (protected)
|
||||
app.use('/api/projects', authenticateToken, projectModuleRoutes);
|
||||
|
||||
// Chat image asset upload/serving (global ~/.cloudcli/assets store, protected)
|
||||
app.use('/api/assets', authenticateToken, assetsRoutes);
|
||||
|
||||
// Git API Routes (protected)
|
||||
app.use('/api/git', authenticateToken, gitRoutes);
|
||||
|
||||
@@ -1072,92 +1076,8 @@ const uploadFilesHandler = async (req, res) => {
|
||||
|
||||
app.post('/api/projects/:projectId/files/upload', authenticateToken, uploadFilesHandler);
|
||||
|
||||
// Image upload endpoint. Accepts the DB-assigned `projectId` (not a folder name)
|
||||
// but the current implementation doesn't need to touch the project directory,
|
||||
// so we just leave the param rename for consistency with the rest of the API.
|
||||
app.post('/api/projects/:projectId/upload-images', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const multer = (await import('multer')).default;
|
||||
const path = (await import('path')).default;
|
||||
const fs = (await import('fs')).promises;
|
||||
const os = (await import('os')).default;
|
||||
|
||||
// Configure multer for image uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: async (req, file, cb) => {
|
||||
const uploadDir = path.join(os.tmpdir(), 'claude-ui-uploads', String(req.user.id));
|
||||
await fs.mkdir(uploadDir, { recursive: true });
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
const sanitizedName = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
|
||||
cb(null, uniqueSuffix + '-' + sanitizedName);
|
||||
}
|
||||
});
|
||||
|
||||
const fileFilter = (req, file, cb) => {
|
||||
const allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
|
||||
if (allowedMimes.includes(file.mimetype)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Invalid file type. Only JPEG, PNG, GIF, WebP, and SVG are allowed.'));
|
||||
}
|
||||
};
|
||||
|
||||
const upload = multer({
|
||||
storage,
|
||||
fileFilter,
|
||||
limits: {
|
||||
fileSize: 5 * 1024 * 1024, // 5MB
|
||||
files: 5
|
||||
}
|
||||
});
|
||||
|
||||
// Handle multipart form data
|
||||
upload.array('images', 5)(req, res, async (err) => {
|
||||
if (err) {
|
||||
return res.status(400).json({ error: err.message });
|
||||
}
|
||||
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({ error: 'No image files provided' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Process uploaded images
|
||||
const processedImages = await Promise.all(
|
||||
req.files.map(async (file) => {
|
||||
// Read file and convert to base64
|
||||
const buffer = await fs.readFile(file.path);
|
||||
const base64 = buffer.toString('base64');
|
||||
const mimeType = file.mimetype;
|
||||
|
||||
// Clean up temp file immediately
|
||||
await fs.unlink(file.path);
|
||||
|
||||
return {
|
||||
name: file.originalname,
|
||||
data: `data:${mimeType};base64,${base64}`,
|
||||
size: file.size,
|
||||
mimeType: mimeType
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
res.json({ images: processedImages });
|
||||
} catch (error) {
|
||||
console.error('Error processing images:', error);
|
||||
// Clean up any remaining files
|
||||
await Promise.all(req.files.map(f => fs.unlink(f.path).catch(() => { })));
|
||||
res.status(500).json({ error: 'Failed to process images' });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in image upload endpoint:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
// Chat image uploads moved to POST /api/assets/images (server/modules/assets),
|
||||
// which stores them in the global ~/.cloudcli/assets folder.
|
||||
|
||||
// Get token usage for a specific session. `projectId` is the DB primary key;
|
||||
// the Claude branch below resolves it to an absolute path via the DB.
|
||||
|
||||
Reference in New Issue
Block a user