This repository was archived by the owner on Mar 3, 2026. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathserver.js
More file actions
262 lines (225 loc) · 9.31 KB
/
Copy pathserver.js
File metadata and controls
262 lines (225 loc) · 9.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const multer = require('multer');
const Anthropic = require('@anthropic-ai/sdk');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: function (req, file, cb) {
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: function (req, file, cb) {
cb(null, Date.now() + '-' + file.originalname);
}
});
// Set file size limits and accepted mime types
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB limit per file
fileFilter: (req, file, cb) => {
// Accept only image files
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed'));
}
}
});
// Initialize Anthropic client
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
// Load JSON structure template
const jsonStructure = JSON.parse(fs.readFileSync(path.join(__dirname, 'jsonStructure.json'), 'utf8'));
// Middleware
app.use(cors());
app.use(bodyParser.json({ limit: '50mb' }));
app.use(express.static(path.join(__dirname, '.')));
// Serve the main application
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
// API endpoint for image comparison
app.post('/api/compare-images', upload.array('images', 2), async (req, res) => {
try {
if (!req.files || req.files.length !== 2) {
return res.status(400).json({ error: 'Please upload exactly two images' });
}
console.log("⏳ Starting image comparison analysis...");
const imagePaths = req.files.map(file => file.path);
// Convert images to base64
const imageBase64 = imagePaths.map(path => {
const buffer = fs.readFileSync(path);
return buffer.toString('base64');
});
console.log("🔄 Preparing images for analysis with Claude 3.7 Sonnet");
// Send to Claude for analysis
const response = await anthropic.messages.create({
model: "claude-3-7-sonnet-20250219",
max_tokens: 4000,
temperature: 0.2, // Low temperature for more deterministic/factual responses
system: "You are an expert photography analyst and image comparison specialist. Your task is to analyze two images and provide a detailed comparison using a specific JSON structure. Your output MUST be valid JSON only, no explanations or surrounding text.",
messages: [
{
role: "user",
content: [
{
type: "text",
text: `Please analyze these two images and provide a detailed comparison according to the following JSON structure template: ${JSON.stringify(jsonStructure.imageComparisonTemplate)}. Focus on comparing the visual attributes like color grading, brightness, composition, perspective, depth of field, objects present, scene type, theme, lighting techniques, and emotional tone. For each attribute, identify the primary and secondary characteristics, then formulate key differences between the images. Output only PURE valid JSON without any markdown or explanations.`
},
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: imageBase64[0]
}
},
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: imageBase64[1]
}
}
]
}
]
});
console.log("✅ Claude 3.7 Sonnet analysis completed successfully");
// Clean up uploaded files
imagePaths.forEach(path => fs.unlinkSync(path));
// Parse the result and send it back
try {
// Extract only the JSON part from Claude's response
const contentText = response.content[0].text;
// Find JSON content (between curly braces)
const jsonMatch = contentText.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error("Could not extract valid JSON from Claude's response");
}
const jsonString = jsonMatch[0];
const analysisResult = JSON.parse(jsonString);
res.json(analysisResult);
} catch (jsonError) {
console.error(`❌ Error parsing JSON from Claude: ${jsonError.message}`);
console.error('Response content:', response.content[0].text);
res.status(500).json({
error: 'Failed to parse analysis result',
rawResponse: response.content[0].text
});
}
} catch (error) {
console.error(`❌ Error in image comparison: ${error.message}`);
console.error('Error details:', error);
res.status(500).json({ error: 'Analysis failed: ' + error.message });
}
});
// For direct base64 image upload
app.post('/api/compare-base64', async (req, res) => {
try {
const { image1, image2 } = req.body;
if (!image1 || !image2) {
return res.status(400).json({ error: 'Please provide both images in base64 format' });
}
// Check file size (approximate check for base64)
const maxSizeBytes = 5 * 1024 * 1024; // 5MB
if (image1.length > maxSizeBytes * 1.37 || image2.length > maxSizeBytes * 1.37) { // base64 is ~1.37x larger
return res.status(400).json({ error: 'Image file size exceeds 5MB limit' });
}
console.log("⏳ Starting base64 image comparison analysis with Claude 3.7 Sonnet...");
// Extract the base64 data (remove data URL prefix if present)
const extractBase64 = (dataUrl) => {
if (dataUrl.includes(',')) {
return dataUrl.split(',')[1];
}
return dataUrl;
};
const image1Base64 = extractBase64(image1);
const image2Base64 = extractBase64(image2);
// Determine image types
const getImageType = (dataUrl) => {
if (dataUrl.startsWith('data:')) {
const match = dataUrl.match(/data:([^;]+);/);
return match ? match[1] : 'image/jpeg';
}
return 'image/jpeg'; // Default assumption
};
const image1Type = getImageType(image1);
const image2Type = getImageType(image2);
console.log("🔄 Preparing base64 images for Claude 3.7 Sonnet analysis");
// Send to Claude for analysis
const response = await anthropic.messages.create({
model: "claude-3-7-sonnet-20250219",
max_tokens: 4000,
temperature: 0.2, // Low temperature for more deterministic/factual responses
system: "You are an expert photography analyst and image comparison specialist. Your task is to analyze two images and provide a detailed comparison using a specific JSON structure. Your output MUST be valid JSON only, no explanations or surrounding text.",
messages: [
{
role: "user",
content: [
{
type: "text",
text: `Please analyze these two images and provide a detailed comparison according to the following JSON structure template: ${JSON.stringify(jsonStructure.imageComparisonTemplate)}. Focus on comparing the visual attributes like color grading, brightness, composition, perspective, depth of field, objects present, scene type, theme, lighting techniques, and emotional tone. For each attribute, identify the primary and secondary characteristics, then formulate key differences between the images. Output only PURE valid JSON without any markdown or explanations.`
},
{
type: "image",
source: {
type: "base64",
media_type: image1Type,
data: image1Base64
}
},
{
type: "image",
source: {
type: "base64",
media_type: image2Type,
data: image2Base64
}
}
]
}
]
});
console.log("✅ Claude 3.7 Sonnet base64 analysis completed successfully");
// Parse the result and send it back
try {
// Extract only the JSON part from Claude's response
const contentText = response.content[0].text;
// Find JSON content (between curly braces)
const jsonMatch = contentText.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error("Could not extract valid JSON from Claude's response");
}
const jsonString = jsonMatch[0];
const analysisResult = JSON.parse(jsonString);
res.json(analysisResult);
} catch (jsonError) {
console.error(`❌ Error parsing JSON from Claude: ${jsonError.message}`);
console.error('Response content:', response.content[0].text);
res.status(500).json({
error: 'Failed to parse analysis result',
rawResponse: response.content[0].text
});
}
} catch (error) {
console.error(`❌ Error in base64 image comparison: ${error.message}`);
console.error('Error details:', error);
res.status(500).json({ error: 'Analysis failed: ' + error.message });
}
});
// Start the server
app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
});