This commit is contained in:
Flatlogic Bot 2025-10-29 13:52:10 +00:00
parent 85fce52d84
commit c010161bd8
3 changed files with 238 additions and 13 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules/ node_modules/
*/node_modules/ */node_modules/
*/build/ */build/
config.php

107
api/defects.php Normal file
View File

@ -0,0 +1,107 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../config.php';
if (!defined('OPENAI_API_KEY')) {
http_response_code(500);
echo json_encode(['error' => 'API key not configured.']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_FILES['vehicleImage'])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid request.']);
exit;
}
$file = $_FILES['vehicleImage'];
if ($file['error'] !== UPLOAD_ERR_OK) {
http_response_code(500);
echo json_encode(['error' => 'Error uploading file.']);
exit;
}
// Check file type
$mime_type = mime_content_type($file['tmp_name']);
if (strpos($mime_type, 'image') !== 0) {
http_response_code(400);
echo json_encode(['error' => 'Invalid file type. Please upload an image.']);
exit;
}
$imageData = file_get_contents($file['tmp_name']);
$base64Image = base64_encode($imageData);
$payload = [
'model' => 'gpt-4o',
'messages' => [
[
'role' => 'system',
'content' => 'You are an expert in vehicle damage assessment. Analyze the provided image and identify all damages. Respond with a JSON object containing a single key "defects". The value of "defects" should be an array of strings, where each string is a description of a single detected defect.'
],
[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => 'Please analyze the attached image for vehicle defects and list them.'
],
[
'type' => 'image_url',
'image_url' => [
'url' => "data:{$mime_type};base64,{$base64Image}"
]
]
]
]
],
'response_format' => ['type' => 'json_object'],
'max_tokens' => 1000
];
$ch = curl_init('https://api.openai.com/v1/chat/completions');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . OPENAI_API_KEY
]);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpcode >= 400) {
http_response_code($httpcode);
// Forward the error from OpenAI if possible
$error_response = json_decode($response, true);
if (isset($error_response['error']['message'])) {
echo json_encode(['error' => 'OpenAI API Error: ' . $error_response['error']['message']]);
} else {
echo json_encode(['error' => 'An error occurred with the OpenAI API.']);
}
exit;
}
$result = json_decode($response, true);
if (isset($result['choices'][0]['message']['content'])) {
// The response from OpenAI is a JSON string, so we need to decode it again
$defects_json = $result['choices'][0]['message']['content'];
$defects_data = json_decode($defects_json, true);
if (json_last_error() === JSON_ERROR_NONE && isset($defects_data['defects'])) {
echo json_encode($defects_data);
} else {
// Handle cases where the response is not the expected JSON
http_response_code(500);
echo json_encode(['error' => 'Failed to parse defects from API response.', 'raw_response' => $defects_json]);
}
} else {
http_response_code(500);
echo json_encode(['error' => 'No content received from API.', 'raw_response' => $response]);
}
?>

141
index.php
View File

@ -18,6 +18,7 @@
--accent-blue-darker: #0056b3; --accent-blue-darker: #0056b3;
--border-color: #dee2e6; --border-color: #dee2e6;
--border-dashed: #ced4da; --border-dashed: #ced4da;
--error-red: #dc3545;
} }
body { body {
@ -148,7 +149,9 @@
} }
.upload-button { .upload-button {
display: block; display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
padding: 0.875rem; padding: 0.875rem;
margin-top: 1.5rem; margin-top: 1.5rem;
@ -163,10 +166,30 @@
text-align: center; text-align: center;
} }
.upload-button:hover { .upload-button:hover:not(:disabled) {
background-color: var(--accent-blue-darker); background-color: var(--accent-blue-darker);
} }
.upload-button:disabled {
background-color: #a1c9ff;
cursor: not-allowed;
}
.spinner {
display: none;
width: 1.2em;
height: 1.2em;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
margin-right: 0.5rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Results Table */ /* Results Table */
.results-container { .results-container {
overflow-x: auto; overflow-x: auto;
@ -207,7 +230,12 @@
text-align: center; text-align: center;
color: var(--text-secondary); color: var(--text-secondary);
padding: 3rem 1rem; padding: 3rem 1rem;
font-style: italic; }
.placeholder.error {
color: var(--error-red);
font-style: normal;
font-weight: 500;
} }
footer { footer {
@ -230,9 +258,9 @@
<div class="upload-card card"> <div class="upload-card card">
<h2>Fahrzeugbild hochladen</h2> <h2>Fahrzeugbild hochladen</h2>
<p class="subtitle">Foto zur Analyse von Schäden hochladen.</p> <p class="subtitle">Foto zur Analyse von Schäden hochladen.</p>
<form action="" method="post" enctype="multipart/form-data"> <form id="upload-form" method="post" enctype="multipart/form-data">
<div class="file-input-wrapper"> <div class="file-input-wrapper">
<input type="file" name="vehicleImage" id="vehicleImage" class="file-input" accept="image/*"> <input type="file" name="vehicleImage" id="vehicleImage" class="file-input" accept="image/*" required>
<label for="vehicleImage" class="file-input-label"> <label for="vehicleImage" class="file-input-label">
<div class="file-input-label-icon"> <div class="file-input-label-icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M21.2 15c.7-1.2 1-2.5.7-3.9-.6-2.4-2.4-4.2-4.8-4.8-1.4-.3-2.7 0-3.9.7L4 4 3 3"/><path d="M19 19l-1.5-1.5"/><path d="M22 22l-2-2"/><path d="M7 12a5 5 0 0 1 5-5"/><path d="M12 17a5 5 0 0 1-5-5"/></svg> <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M21.2 15c.7-1.2 1-2.5.7-3.9-.6-2.4-2.4-4.2-4.8-4.8-1.4-.3-2.7 0-3.9.7L4 4 3 3"/><path d="M19 19l-1.5-1.5"/><path d="M22 22l-2-2"/><path d="M7 12a5 5 0 0 1 5-5"/><path d="M12 17a5 5 0 0 1-5-5"/></svg>
@ -243,25 +271,27 @@
</label> </label>
</div> </div>
<div id="file-name"></div> <div id="file-name"></div>
<button type="submit" class="upload-button">Bild analysieren</button> <button type="submit" id="upload-button" class="upload-button">
<span class="spinner"></span>
<span class="button-text">Bild analysieren</span>
</button>
</form> </form>
</div> </div>
<div class="results-card card"> <div class="results-card card">
<h2>Analyseergebnisse</h2> <h2>Analyseergebnisse</h2>
<p class="subtitle">Erkannte Schäden und geschätzte Kosten.</p> <p class="subtitle">Erkannte Schäden.</p>
<div class="results-container"> <div class="results-container">
<table> <table>
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>Erkannter Schaden</th> <th>Erkannter Schaden</th>
<th>Geschätzte Kosten</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="results-body">
<tr> <tr>
<td colspan="3" class="placeholder">Laden Sie ein Bild hoch, um die Analyse zu sehen.</td> <td colspan="2" class="placeholder">Laden Sie ein Bild hoch, um die Analyse zu sehen.</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -277,10 +307,15 @@
</footer> </footer>
<script> <script>
const uploadForm = document.getElementById('upload-form');
const fileInput = document.getElementById('vehicleImage'); const fileInput = document.getElementById('vehicleImage');
const fileNameDisplay = document.getElementById('file-name'); const fileNameDisplay = document.getElementById('file-name');
const fileLabel = document.querySelector('.file-input-label'); const uploadButton = document.getElementById('upload-button');
const originalLabel = fileLabel.innerHTML; const buttonSpinner = uploadButton.querySelector('.spinner');
const buttonText = uploadButton.querySelector('.button-text');
const resultsBody = document.getElementById('results-body');
const placeholderText = 'Laden Sie ein Bild hoch, um die Analyse zu sehen.';
const errorPlaceholder = (message) => `Fehler: ${message}`;
fileInput.addEventListener('change', function() { fileInput.addEventListener('change', function() {
if (this.files.length > 0) { if (this.files.length > 0) {
@ -289,6 +324,88 @@
fileNameDisplay.textContent = ''; fileNameDisplay.textContent = '';
} }
}); });
uploadForm.addEventListener('submit', async function(e) {
e.preventDefault();
if (fileInput.files.length === 0) {
setTableError('Bitte wählen Sie eine Bilddatei aus.');
return;
}
const formData = new FormData();
formData.append('vehicleImage', fileInput.files[0]);
setLoading(true);
setTableLoading();
try {
const response = await fetch('api/defects.php', {
method: 'POST',
body: formData
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || 'Ein unbekannter Fehler ist aufgetreten.');
}
if (result.defects && result.defects.length > 0) {
populateTable(result.defects);
} else {
setTableEmpty();
}
} catch (error) {
console.error('Error during analysis:', error);
setTableError(error.message);
} finally {
setLoading(false);
}
});
function setLoading(isLoading) {
uploadButton.disabled = isLoading;
if (isLoading) {
buttonSpinner.style.display = 'block';
buttonText.textContent = 'Analysiere...';
} else {
buttonSpinner.style.display = 'none';
buttonText.textContent = 'Bild analysieren';
}
}
function setTableLoading() {
resultsBody.innerHTML = `<tr><td colspan="2" class="placeholder">Analysiere Bild...</td></tr>`;
}
function setTableError(message) {
resultsBody.innerHTML = `<tr><td colspan="2" class="placeholder error">${errorPlaceholder(message)}</td></tr>`;
}
function setTableEmpty() {
resultsBody.innerHTML = `<tr><td colspan="2" class="placeholder">Keine Schäden im Bild erkannt.</td></tr>`;
}
function populateTable(defects) {
resultsBody.innerHTML = ''; // Clear existing rows
defects.forEach((defect, index) => {
const row = document.createElement('tr');
const cellIndex = document.createElement('td');
cellIndex.textContent = index + 1;
const cellDefect = document.createElement('td');
cellDefect.textContent = defect;
row.appendChild(cellIndex);
row.appendChild(cellDefect);
resultsBody.appendChild(row);
});
}
</script> </script>
</body> </body>
</html> </html>