Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
*/build/
|
*/build/
|
||||||
config.php
|
|
||||||
107
api/defects.php
107
api/defects.php
@ -1,107 +0,0 @@
|
|||||||
<?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 to identify the vehicle\'s make, model, and color, and find all damages. Respond with a single JSON object with two top-level keys: "carInfo" and "defects". The "carInfo" key should contain an object with "make", "model", and "color". The "defects" key should contain an array of objects, where each object has two keys: "defect" (a string describing the damage) and "cost" (a number representing the estimated repair cost in EUR).'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'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']) || isset($defects_data['carInfo']))) {
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 242 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
@ -1,16 +0,0 @@
|
|||||||
<svg width="198" height="29" viewBox="0 0 198 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="20.8096" y="10.2266" width="8.32365" height="6.13598" rx="0.747827" fill="#02004E"/>
|
|
||||||
<rect width="29.1328" height="7.15864" rx="0.747827" fill="#5C7EF1"/>
|
|
||||||
<rect y="19.4307" width="7.28319" height="6.13598" rx="0.747827" fill="#8C9DFF"/>
|
|
||||||
<rect x="11.4453" y="19.4307" width="17.6878" height="6.13598" rx="0.747827" fill="#02004E"/>
|
|
||||||
<rect y="10.2266" width="16.6473" height="6.13598" rx="0.747827" fill="#ffb531"/>
|
|
||||||
<path d="M38.7715 2.78179H52.0493V6.45233H42.6837V11.1133H51.8715V14.7839H42.6837V23.1736H38.7715V2.78179Z" fill="#02004E"/>
|
|
||||||
<path d="M56.5337 19.6779H61.6907V6.27754H56.7115V2.78179H65.4251V19.6779H70.2265V23.1736H56.5337V19.6779Z" fill="#02004E"/>
|
|
||||||
<path d="M84.284 21.0762C83.8888 21.989 83.3356 22.6396 82.6242 23.028C81.9129 23.397 81.0831 23.5815 80.1346 23.5815C79.2455 23.5815 78.4058 23.4164 77.6154 23.0863C76.8448 22.7561 76.1631 22.2706 75.5704 21.6297C74.9776 20.9888 74.5034 20.212 74.1478 19.2992C73.8119 18.3864 73.6439 17.3474 73.6439 16.1822V15.7161C73.6439 14.5702 73.8119 13.5409 74.1478 12.6281C74.4837 11.7154 74.9381 10.9385 75.5111 10.2976C76.0841 9.65676 76.746 9.17124 77.4969 8.84109C78.2674 8.49151 79.0874 8.31672 79.9568 8.31672C81.004 8.31672 81.8438 8.49151 82.4761 8.84109C83.1281 9.19066 83.6418 9.73444 84.0172 10.4724H84.5507V8.72456H88.2851V18.804C88.2851 19.3866 88.5519 19.6779 89.0853 19.6779H89.6485V23.1736H87.0996C86.4278 23.1736 85.8745 22.9794 85.4399 22.591C85.0249 22.2026 84.8175 21.6977 84.8175 21.0762H84.284ZM80.9645 20.0857C82.0512 20.0857 82.9206 19.7362 83.5727 19.037C84.2247 18.3184 84.5507 17.3474 84.5507 16.1239V15.7743C84.5507 14.5508 84.2247 13.5895 83.5727 12.8903C82.9206 12.1718 82.0512 11.8125 80.9645 11.8125C79.8778 11.8125 79.0084 12.1718 78.3564 12.8903C77.7043 13.5895 77.3783 14.5508 77.3783 15.7743V16.1239C77.3783 17.3474 77.7043 18.3184 78.3564 19.037C79.0084 19.7362 79.8778 20.0857 80.9645 20.0857Z" fill="#02004E"/>
|
|
||||||
<path d="M92.5027 8.72456H96.8595V2.78179H100.594V8.72456H105.899V12.2203H100.594V18.804C100.594 19.3866 100.861 19.6779 101.394 19.6779H105.366V23.1736H99.1417C98.4699 23.1736 97.9166 22.96 97.4819 22.5328C97.067 22.1055 96.8595 21.5617 96.8595 20.9014V12.2203H92.5027V8.72456Z" fill="#02004E"/>
|
|
||||||
<path d="M110.976 19.6779H116.133V6.27754H111.154V2.78179H119.868V19.6779H124.669V23.1736H110.976V19.6779Z" fill="#02004E"/>
|
|
||||||
<path d="M143.617 16.1239C143.617 17.328 143.409 18.3961 142.994 19.3283C142.579 20.2411 142.016 21.0179 141.305 21.6588C140.594 22.2803 139.774 22.7561 138.845 23.0863C137.936 23.4164 136.978 23.5815 135.97 23.5815C134.963 23.5815 133.994 23.4164 133.066 23.0863C132.157 22.7561 131.347 22.2803 130.635 21.6588C129.924 21.0179 129.361 20.2411 128.946 19.3283C128.531 18.3961 128.324 17.328 128.324 16.1239V15.7743C128.324 14.5896 128.531 13.5409 128.946 12.6281C129.361 11.6959 129.924 10.9094 130.635 10.2685C131.347 9.62763 132.157 9.14211 133.066 8.81195C133.994 8.4818 134.963 8.31672 135.97 8.31672C136.978 8.31672 137.936 8.4818 138.845 8.81195C139.774 9.14211 140.594 9.62763 141.305 10.2685C142.016 10.9094 142.579 11.6959 142.994 12.6281C143.409 13.5409 143.617 14.5896 143.617 15.7743V16.1239ZM135.97 20.0857C136.504 20.0857 137.008 19.9983 137.482 19.8236C137.956 19.6488 138.371 19.3963 138.727 19.0661C139.082 18.736 139.359 18.3379 139.556 17.8718C139.774 17.3862 139.882 16.8425 139.882 16.2404V15.6578C139.882 15.0557 139.774 14.5217 139.556 14.0556C139.359 13.5701 139.082 13.1622 138.727 12.8321C138.371 12.5019 137.956 12.2494 137.482 12.0747C137.008 11.8999 136.504 11.8125 135.97 11.8125C135.437 11.8125 134.933 11.8999 134.459 12.0747C133.984 12.2494 133.57 12.5019 133.214 12.8321C132.858 13.1622 132.572 13.5701 132.354 14.0556C132.157 14.5217 132.058 15.0557 132.058 15.6578V16.2404C132.058 16.8425 132.157 17.3862 132.354 17.8718C132.572 18.3379 132.858 18.736 133.214 19.0661C133.57 19.3963 133.984 19.6488 134.459 19.8236C134.933 19.9983 135.437 20.0857 135.97 20.0857Z" fill="#02004E"/>
|
|
||||||
<path d="M157.852 21.0762H157.319C157.161 21.4064 156.963 21.7268 156.726 22.0375C156.509 22.3288 156.212 22.591 155.837 22.8241C155.481 23.0571 155.046 23.2416 154.533 23.3776C154.039 23.5135 153.436 23.5815 152.725 23.5815C151.816 23.5815 150.966 23.4261 150.176 23.1154C149.405 22.7852 148.724 22.3094 148.131 21.688C147.558 21.0471 147.103 20.2702 146.767 19.3575C146.451 18.4447 146.293 17.396 146.293 16.2113V15.6869C146.293 14.5217 146.461 13.4827 146.797 12.5699C147.153 11.6571 147.627 10.89 148.22 10.2685C148.832 9.62763 149.534 9.14211 150.324 8.81195C151.134 8.4818 151.984 8.31672 152.873 8.31672C154.058 8.31672 154.997 8.54006 155.689 8.98674C156.38 9.414 156.923 10.0258 157.319 10.822H157.852V8.72456H161.587V26.7277C161.587 27.388 161.369 27.9317 160.934 28.359C160.52 28.7863 159.976 28.9999 159.304 28.9999H149.405V25.5041H157.052C157.585 25.5041 157.852 25.2128 157.852 24.6302V21.0762ZM153.94 20.0857C155.106 20.0857 156.044 19.7265 156.756 19.0079C157.487 18.2699 157.852 17.3086 157.852 16.1239V15.7743C157.852 14.5896 157.487 13.638 156.756 12.9195C156.044 12.1815 155.106 11.8125 153.94 11.8125C152.774 11.8125 151.826 12.1718 151.095 12.8903C150.383 13.5895 150.028 14.5508 150.028 15.7743V16.1239C150.028 17.3474 150.383 18.3184 151.095 19.037C151.826 19.7362 152.774 20.0857 153.94 20.0857Z" fill="#02004E"/>
|
|
||||||
<path d="M165.775 19.6779H170.754V12.2203H166.13V8.72456H174.488V19.6779H178.756V23.1736H165.775V19.6779ZM175.407 4.44227C175.407 4.83069 175.328 5.19968 175.17 5.54926C175.031 5.87941 174.834 6.17072 174.577 6.42319C174.32 6.65624 174.014 6.85045 173.658 7.00582C173.322 7.14176 172.967 7.20974 172.591 7.20974C172.196 7.20974 171.831 7.14176 171.495 7.00582C171.159 6.85045 170.862 6.65624 170.606 6.42319C170.349 6.17072 170.141 5.87941 169.983 5.54926C169.845 5.19968 169.776 4.83069 169.776 4.44227C169.776 4.05385 169.845 3.69457 169.983 3.36442C170.141 3.01484 170.349 2.72353 170.606 2.49048C170.862 2.23801 171.159 2.0438 171.495 1.90785C171.831 1.75249 172.196 1.6748 172.591 1.6748C172.967 1.6748 173.322 1.75249 173.658 1.90785C174.014 2.0438 174.32 2.23801 174.577 2.49048C174.834 2.72353 175.031 3.01484 175.17 3.36442C175.328 3.69457 175.407 4.05385 175.407 4.44227Z" fill="#02004E"/>
|
|
||||||
<path d="M198 17.8718C197.664 19.5808 196.864 20.9597 195.599 22.0084C194.355 23.0571 192.685 23.5815 190.591 23.5815C189.504 23.5815 188.486 23.4164 187.538 23.0863C186.609 22.7561 185.799 22.2803 185.108 21.6588C184.416 21.0374 183.873 20.2702 183.477 19.3575C183.082 18.4447 182.885 17.4057 182.885 16.2404V15.8908C182.885 14.7256 183.082 13.6769 183.477 12.7447C183.873 11.8125 184.416 11.0162 185.108 10.3559C185.819 9.6956 186.639 9.19066 187.568 8.84109C188.516 8.49151 189.524 8.31672 190.591 8.31672C192.646 8.31672 194.305 8.84109 195.57 9.88981C196.854 10.9385 197.664 12.3174 198 14.0264L194.325 14.9586C194.206 14.1041 193.831 13.3661 193.199 12.7447C192.566 12.1232 191.677 11.8125 190.531 11.8125C189.998 11.8125 189.494 11.9096 189.02 12.1038C188.546 12.298 188.131 12.5796 187.775 12.9486C187.419 13.2982 187.133 13.7351 186.915 14.2595C186.718 14.7644 186.619 15.3373 186.619 15.9782V16.153C186.619 16.7939 186.718 17.3668 186.915 17.8718C187.133 18.3573 187.419 18.7651 187.775 19.0953C188.131 19.4254 188.546 19.6779 189.02 19.8527C189.494 20.0081 189.998 20.0857 190.531 20.0857C191.677 20.0857 192.547 19.8041 193.139 19.2409C193.752 18.6777 194.147 17.9203 194.325 16.9687L198 17.8718Z" fill="#02004E"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
538
index.php
538
index.php
@ -1,466 +1,150 @@
|
|||||||
<!DOCTYPE html>
|
<?php
|
||||||
<html lang="de">
|
declare(strict_types=1);
|
||||||
|
@ini_set('display_errors', '1');
|
||||||
|
@error_reporting(E_ALL);
|
||||||
|
@date_default_timezone_set('UTC');
|
||||||
|
|
||||||
|
$phpVersion = PHP_VERSION;
|
||||||
|
$now = date('Y-m-d H:i:s');
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Sentinel - Schadenserkennung</title>
|
<title>New Style</title>
|
||||||
|
<?php
|
||||||
|
// Read project preview data from environment
|
||||||
|
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||||
|
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||||
|
?>
|
||||||
|
<?php if ($projectDescription): ?>
|
||||||
|
<!-- Meta description -->
|
||||||
|
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||||
|
<!-- Open Graph meta tags -->
|
||||||
|
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||||
|
<!-- Twitter meta tags -->
|
||||||
|
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($projectImageUrl): ?>
|
||||||
|
<!-- Open Graph image -->
|
||||||
|
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||||
|
<!-- Twitter image -->
|
||||||
|
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||||
|
<?php endif; ?>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* Google Font: Inter */
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
||||||
|
|
||||||
/* Modern German-inspired Palette */
|
|
||||||
:root {
|
:root {
|
||||||
--background-main: #f8f9fa; /* Off-white */
|
--bg-color-start: #6a11cb;
|
||||||
--background-card: #ffffff;
|
--bg-color-end: #2575fc;
|
||||||
--text-primary: #212529; /* Near-black for high contrast */
|
--text-color: #ffffff;
|
||||||
--text-secondary: #6c757d; /* Muted grey for subtitles */
|
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||||
--accent-blue: #007bff; /* A strong, clear blue */
|
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||||
--accent-blue-darker: #0056b3;
|
|
||||||
--border-color: #dee2e6;
|
|
||||||
--border-dashed: #ced4da;
|
|
||||||
--error-red: #dc3545;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: var(--background-main);
|
font-family: 'Inter', sans-serif;
|
||||||
color: var(--text-primary);
|
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||||
-webkit-font-smoothing: antialiased;
|
color: var(--text-color);
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
background-color: var(--background-card);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
padding: 1.5rem 0;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
min-height: 100vh;
|
||||||
}
|
|
||||||
|
|
||||||
header h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 span {
|
|
||||||
color: var(--accent-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
main .grid-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 992px) {
|
|
||||||
.top-row {
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background-color: var(--background-card);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 2rem;
|
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.subtitle {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File Upload */
|
|
||||||
.file-input-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
border: 2px dashed var(--border-dashed);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 2rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: var(--background-main);
|
overflow: hidden;
|
||||||
transition: border-color 0.2s ease-in-out, background-color 0.2s ease-in-out;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
body::before {
|
||||||
.file-input-wrapper:hover {
|
content: '';
|
||||||
border-color: var(--accent-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input-label-icon {
|
|
||||||
color: var(--accent-blue);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
transition: transform 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input-wrapper:hover .file-input-label-icon {
|
|
||||||
transform: translateY(-3px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input-label span {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input-label span.highlight {
|
|
||||||
color: var(--accent-blue);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
#file-name {
|
|
||||||
margin-top: 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
min-height: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.875rem;
|
height: 100%;
|
||||||
margin-top: 1.5rem;
|
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||||
background-color: var(--accent-blue);
|
animation: bg-pan 20s linear infinite;
|
||||||
color: var(--background-card);
|
z-index: -1;
|
||||||
border: none;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease-in-out;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
@keyframes bg-pan {
|
||||||
.upload-button:hover:not(:disabled) {
|
0% { background-position: 0% 0%; }
|
||||||
background-color: var(--accent-blue-darker);
|
100% { background-position: 100% 100%; }
|
||||||
}
|
}
|
||||||
|
main {
|
||||||
.upload-button:disabled {
|
padding: 2rem;
|
||||||
background-color: #a1c9ff;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
}
|
||||||
|
.card {
|
||||||
.spinner {
|
background: var(--card-bg-color);
|
||||||
display: none;
|
border: 1px solid var(--card-border-color);
|
||||||
width: 1.2em;
|
border-radius: 16px;
|
||||||
height: 1.2em;
|
padding: 2rem;
|
||||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
backdrop-filter: blur(20px);
|
||||||
border-radius: 50%;
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.loader {
|
||||||
|
margin: 1.25rem auto 1.25rem;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||||
border-top-color: #fff;
|
border-top-color: #fff;
|
||||||
animation: spin 1s ease-in-out infinite;
|
border-radius: 50%;
|
||||||
margin-right: 0.5rem;
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
.hint {
|
||||||
/* Results Table */
|
opacity: 0.9;
|
||||||
.results-container {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
}
|
||||||
|
.sr-only {
|
||||||
table {
|
position: absolute;
|
||||||
width: 100%;
|
width: 1px; height: 1px;
|
||||||
border-collapse: collapse;
|
padding: 0; margin: -1px;
|
||||||
font-size: 0.95rem;
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap; border: 0;
|
||||||
}
|
}
|
||||||
|
h1 {
|
||||||
th, td {
|
font-size: 3rem;
|
||||||
padding: 1rem;
|
font-weight: 700;
|
||||||
text-align: left;
|
margin: 0 0 1rem;
|
||||||
border-bottom: 1px solid var(--border-color);
|
letter-spacing: -1px;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
p {
|
||||||
thead th {
|
margin: 0.5rem 0;
|
||||||
background-color: var(--background-main);
|
font-size: 1.1rem;
|
||||||
color: var(--text-secondary);
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
}
|
||||||
|
code {
|
||||||
tbody tr:last-child th,
|
background: rgba(0,0,0,0.2);
|
||||||
tbody tr:last-child td {
|
padding: 2px 6px;
|
||||||
border-bottom: none;
|
border-radius: 4px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr:hover {
|
|
||||||
background-color: #f1f3f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
padding: 3rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder.error {
|
|
||||||
color: var(--error-red);
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
padding: 2rem 0;
|
position: absolute;
|
||||||
font-size: 0.875rem;
|
bottom: 1rem;
|
||||||
color: var(--text-secondary);
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<main>
|
||||||
<div class="container">
|
<div class="card">
|
||||||
<h1>Sentinel<span>.</span></h1>
|
<h1>Analyzing your requirements and generating your website…</h1>
|
||||||
</div>
|
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||||
</header>
|
<span class="sr-only">Loading…</span>
|
||||||
|
|
||||||
<main class="container">
|
|
||||||
<div class="grid-container">
|
|
||||||
<div class="top-row">
|
|
||||||
<div class="upload-card card">
|
|
||||||
<h2>Fahrzeugbild hochladen</h2>
|
|
||||||
<p class="subtitle">Foto zur Analyse von Schäden hochladen.</p>
|
|
||||||
<form id="upload-form" method="post" enctype="multipart/form-data">
|
|
||||||
<div class="file-input-wrapper">
|
|
||||||
<input type="file" name="vehicleImage" id="vehicleImage" class="file-input" accept="image/*" required>
|
|
||||||
<label for="vehicleImage" class="file-input-label">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<span class="file-input-label">
|
|
||||||
<span class="highlight">Datei auswählen</span> oder hierher ziehen
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div id="file-name"></div>
|
|
||||||
<button type="submit" id="upload-button" class="upload-button">
|
|
||||||
<span class="spinner"></span>
|
|
||||||
<span class="button-text">Bild analysieren</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="image-preview-card card" id="image-preview-container" style="display: none;">
|
|
||||||
<h2>Vorschau</h2>
|
|
||||||
<p class="subtitle">Hochgeladenes Bild.</p>
|
|
||||||
<div class="image-wrapper">
|
|
||||||
<img id="image-preview" src="" alt="Vorschau des hochgeladenen Bildes" style="width: 100%; border-radius: 0.5rem;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="car-info-card card" id="car-info-container" style="display: none;">
|
|
||||||
<h2>Fahrzeuginformationen</h2>
|
|
||||||
<p class="subtitle">Grundlegende Fahrzeuginformationen.</p>
|
|
||||||
<div id="car-info-details"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="results-card card">
|
|
||||||
<h2>Analyseergebnisse</h2>
|
|
||||||
<p class="subtitle">Erkannte Schäden.</p>
|
|
||||||
<div class="results-container">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>Erkannter Schaden</th>
|
|
||||||
<th>Reparaturkosten</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="results-body">
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="placeholder">Laden Sie ein Bild hoch, um die Analyse zu sehen.</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||||
|
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||||
|
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container" style="display: flex; align-items: center; justify-content: center;">
|
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||||
<img src="https://flatlogic.com/assets/icons/footer_logo-102b5debccc6a5a944601a0bc451c7b8da6e282147b7906a42818dda605383ff.svg" alt="Flatlogic Logo" style="height: 24px; margin-right: 10px; vertical-align: middle;">
|
|
||||||
<p style="margin: 0;">built with Flatlogic Generator</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
|
||||||
const uploadForm = document.getElementById('upload-form');
|
|
||||||
const fileInput = document.getElementById('vehicleImage');
|
|
||||||
const fileNameDisplay = document.getElementById('file-name');
|
|
||||||
const uploadButton = document.getElementById('upload-button');
|
|
||||||
const buttonSpinner = uploadButton.querySelector('.spinner');
|
|
||||||
const buttonText = uploadButton.querySelector('.button-text');
|
|
||||||
const resultsBody = document.getElementById('results-body');
|
|
||||||
const imagePreviewContainer = document.getElementById('image-preview-container');
|
|
||||||
const imagePreview = document.getElementById('image-preview');
|
|
||||||
const carInfoContainer = document.getElementById('car-info-container');
|
|
||||||
const carInfoDetails = document.getElementById('car-info-details');
|
|
||||||
const placeholderText = 'Laden Sie ein Bild hoch, um die Analyse zu sehen.';
|
|
||||||
const errorPlaceholder = (message) => `Fehler: ${message}`;
|
|
||||||
|
|
||||||
fileInput.addEventListener('change', function() {
|
|
||||||
if (this.files.length > 0) {
|
|
||||||
fileNameDisplay.textContent = `Ausgewählt: ${this.files[0].name}`;
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
imagePreview.src = e.target.result;
|
|
||||||
imagePreviewContainer.style.display = 'block';
|
|
||||||
}
|
|
||||||
reader.readAsDataURL(this.files[0]);
|
|
||||||
} else {
|
|
||||||
fileNameDisplay.textContent = '';
|
|
||||||
imagePreview.src = '';
|
|
||||||
imagePreviewContainer.style.display = 'none';
|
|
||||||
carInfoContainer.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.carInfo) {
|
|
||||||
populateCarInfo(result.carInfo);
|
|
||||||
carInfoContainer.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
carInfoContainer.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
} 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="3" class="placeholder">Analysiere Bild...</td></tr>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTableError(message) {
|
|
||||||
resultsBody.innerHTML = `<tr><td colspan="3" class="placeholder error">${errorPlaceholder(message)}</td></tr>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTableEmpty() {
|
|
||||||
resultsBody.innerHTML = `<tr><td colspan="3" 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.defect;
|
|
||||||
|
|
||||||
const cellCost = document.createElement('td');
|
|
||||||
cellCost.textContent = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(defect.cost);
|
|
||||||
|
|
||||||
row.appendChild(cellIndex);
|
|
||||||
row.appendChild(cellDefect);
|
|
||||||
row.appendChild(cellCost);
|
|
||||||
|
|
||||||
resultsBody.appendChild(row);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateCarInfo(carInfo) {
|
|
||||||
carInfoDetails.innerHTML = `
|
|
||||||
<p><strong>Marke:</strong> ${carInfo.make}</p>
|
|
||||||
<p><strong>Modell:</strong> ${carInfo.model}</p>
|
|
||||||
<p><strong>Farbe:</strong> ${carInfo.color}</p>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user