Spaces:
Sleeping
Sleeping
Sunhey Cho
commited on
Commit
Β·
08c2fcd
1
Parent(s):
16e07bc
Frontend build copied to static, add 5 test images, update API integration; ignore static backup folders
Browse files- api.py +173 -1
- frontend/src/App.js +21 -3
- frontend/src/components/ProductComparison.js +647 -0
- product_comparison.py +1108 -0
- static/asset-manifest.json +11 -11
- static/index.html +1 -1
- static/precache-manifest.4683f6cd857bd9aa85304ac13a82d061.js +0 -30
- static/{precache-manifest.2fad43b74e0c6c48ddb79498a374f3f4.js β precache-manifest.653a7aee73b10eaaa2fae0610652f6fa.js} +8 -8
- static/precache-manifest.90eb425b86312bf2b8dbe43e7d0377d8.js +0 -30
- static/precache-manifest.9a82b4d1673393a138e3168d2b6c841d.js +0 -30
- static/service-worker.js +2 -2
- static/static/css/main.3b4eede1.chunk.css.map +0 -1
- static/static/css/{main.3b4eede1.chunk.css β main.59c2a54e.chunk.css} +2 -2
- static/static/css/main.59c2a54e.chunk.css.map +1 -0
- static/static/js/{2.9b5b80e8.chunk.js β 2.abf91741.chunk.js} +0 -0
- static/static/js/{2.9b5b80e8.chunk.js.LICENSE.txt β 2.abf91741.chunk.js.LICENSE.txt} +0 -0
- static/static/js/{2.9b5b80e8.chunk.js.map β 2.abf91741.chunk.js.map} +0 -0
- static/static/js/main.975ea04a.chunk.js +0 -2
- static/static/js/main.975ea04a.chunk.js.map +0 -1
- static/static/js/main.db88b5b1.chunk.js +2 -0
- static/static/js/main.db88b5b1.chunk.js.map +1 -0
- static/static/js/main.fb4939ae.chunk.js +0 -2
- static/static/js/main.fb4939ae.chunk.js.map +0 -1
- static/static/js/main.fc0e2dc7.chunk.js +0 -2
- static/static/js/main.fc0e2dc7.chunk.js.map +0 -1
- static/static/js/main.fe247ab2.chunk.js +0 -2
- static/static/js/main.fe247ab2.chunk.js.map +0 -1
- static_bak_20250820_085943/asset-manifest.json +0 -24
- static_bak_20250820_085943/index.html +0 -1
- static_bak_20250820_085943/manifest.json +0 -15
- static_bak_20250820_085943/precache-manifest.4683f6cd857bd9aa85304ac13a82d061.js +0 -30
- static_bak_20250820_085943/service-worker.js +0 -39
- static_bak_20250820_085943/static/css/main.3b4eede1.chunk.css +0 -2
- static_bak_20250820_085943/static/css/main.3b4eede1.chunk.css.map +0 -1
- static_bak_20250820_085943/static/js/2.9b5b80e8.chunk.js +0 -0
- static_bak_20250820_085943/static/js/2.9b5b80e8.chunk.js.LICENSE.txt +0 -58
- static_bak_20250820_085943/static/js/2.9b5b80e8.chunk.js.map +0 -0
- static_bak_20250820_085943/static/js/3.9013e23f.chunk.js +0 -2
- static_bak_20250820_085943/static/js/3.9013e23f.chunk.js.map +0 -1
- static_bak_20250820_085943/static/js/main.fc0e2dc7.chunk.js +0 -2
- static_bak_20250820_085943/static/js/main.fc0e2dc7.chunk.js.map +0 -1
- static_bak_20250820_085943/static/js/runtime-main.25710301.js +0 -2
- static_bak_20250820_085943/static/js/runtime-main.25710301.js.map +0 -1
- test_images/README.txt +22 -0
- test_product_comparison_api.py +151 -0
api.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
import os
|
4 |
os.environ['MPLCONFIGDIR'] = '/tmp/matplotlib'
|
5 |
|
6 |
-
from flask import Flask, request, jsonify, send_from_directory, redirect, url_for, session, render_template_string, make_response
|
7 |
from datetime import timedelta
|
8 |
import torch
|
9 |
from PIL import Image
|
@@ -19,6 +19,8 @@ from flask_cors import CORS
|
|
19 |
import json
|
20 |
import sys
|
21 |
import requests
|
|
|
|
|
22 |
try:
|
23 |
from openai import OpenAI
|
24 |
except Exception as _e:
|
@@ -54,6 +56,14 @@ import chromadb
|
|
54 |
from chromadb.utils import embedding_functions
|
55 |
|
56 |
app = Flask(__name__, static_folder='static')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
# νκ²½ λ³μμμ λΉλ° ν€λ₯Ό κ°μ Έμ€κ±°λ, μμΌλ©΄ μμ ν λλ€ ν€ μμ±
|
58 |
secret_key = os.environ.get('FLASK_SECRET_KEY')
|
59 |
if not secret_key:
|
@@ -895,6 +905,168 @@ def add_detected_objects():
|
|
895 |
print(f"Error processing object: {e}")
|
896 |
continue
|
897 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
898 |
# κ°μ²΄κ° μλ κ²½μ°
|
899 |
if not object_ids:
|
900 |
return jsonify({"error": "No valid objects to add"})
|
|
|
3 |
import os
|
4 |
os.environ['MPLCONFIGDIR'] = '/tmp/matplotlib'
|
5 |
|
6 |
+
from flask import Flask, request, jsonify, send_from_directory, redirect, url_for, session, render_template_string, make_response, Response, stream_with_context
|
7 |
from datetime import timedelta
|
8 |
import torch
|
9 |
from PIL import Image
|
|
|
19 |
import json
|
20 |
import sys
|
21 |
import requests
|
22 |
+
import asyncio
|
23 |
+
from threading import Thread
|
24 |
try:
|
25 |
from openai import OpenAI
|
26 |
except Exception as _e:
|
|
|
56 |
from chromadb.utils import embedding_functions
|
57 |
|
58 |
app = Flask(__name__, static_folder='static')
|
59 |
+
|
60 |
+
# Import product comparison coordinator
|
61 |
+
try:
|
62 |
+
from product_comparison import get_product_comparison_coordinator, decode_base64_image
|
63 |
+
except ImportError:
|
64 |
+
print("Warning: Product comparison module not available")
|
65 |
+
get_product_comparison_coordinator = None
|
66 |
+
decode_base64_image = None
|
67 |
# νκ²½ λ³μμμ λΉλ° ν€λ₯Ό κ°μ Έμ€κ±°λ, μμΌλ©΄ μμ ν λλ€ ν€ μμ±
|
68 |
secret_key = os.environ.get('FLASK_SECRET_KEY')
|
69 |
if not secret_key:
|
|
|
905 |
print(f"Error processing object: {e}")
|
906 |
continue
|
907 |
|
908 |
+
# Add to vector DB
|
909 |
+
if object_ids and object_embeddings and object_metadatas:
|
910 |
+
object_collection.add(
|
911 |
+
ids=object_ids,
|
912 |
+
embeddings=object_embeddings,
|
913 |
+
metadatas=object_metadatas
|
914 |
+
)
|
915 |
+
|
916 |
+
return jsonify({
|
917 |
+
"success": True,
|
918 |
+
"message": f"Added {len(object_ids)} objects to collection",
|
919 |
+
"object_ids": object_ids
|
920 |
+
})
|
921 |
+
else:
|
922 |
+
return jsonify({
|
923 |
+
"warning": "No valid objects to add"
|
924 |
+
})
|
925 |
+
|
926 |
+
except Exception as e:
|
927 |
+
print(f"Error in add-detected-objects API: {e}")
|
928 |
+
return jsonify({"error": str(e)}), 500
|
929 |
+
|
930 |
+
|
931 |
+
# Product Comparison API Endpoints
|
932 |
+
@app.route('/api/product/compare/start', methods=['POST'])
|
933 |
+
@login_required
|
934 |
+
def start_product_comparison():
|
935 |
+
"""Start a new product comparison session"""
|
936 |
+
if get_product_comparison_coordinator is None:
|
937 |
+
return jsonify({"error": "Product comparison module not available"}), 500
|
938 |
+
|
939 |
+
try:
|
940 |
+
# Generate session ID if provided in form or query params, otherwise create new one
|
941 |
+
session_id = request.form.get('session_id') or request.args.get('session_id') or str(uuid.uuid4())
|
942 |
+
|
943 |
+
# Get analysis type if provided (info, compare, value, recommend)
|
944 |
+
analysis_type = request.form.get('analysisType') or request.args.get('analysisType', 'info')
|
945 |
+
|
946 |
+
# Process images from FormData or JSON
|
947 |
+
images = []
|
948 |
+
|
949 |
+
# Check if request is multipart form data
|
950 |
+
if request.files:
|
951 |
+
# Handle FormData with file uploads (from frontend)
|
952 |
+
if 'image1' in request.files and request.files['image1']:
|
953 |
+
img1 = request.files['image1']
|
954 |
+
try:
|
955 |
+
images.append(Image.open(img1.stream))
|
956 |
+
except Exception as e:
|
957 |
+
print(f"Error processing image1: {e}")
|
958 |
+
|
959 |
+
if 'image2' in request.files and request.files['image2']:
|
960 |
+
img2 = request.files['image2']
|
961 |
+
try:
|
962 |
+
images.append(Image.open(img2.stream))
|
963 |
+
except Exception as e:
|
964 |
+
print(f"Error processing image2: {e}")
|
965 |
+
|
966 |
+
# Fallback to JSON with base64 images (for API testing)
|
967 |
+
elif request.json and 'images' in request.json:
|
968 |
+
image_data_list = request.json.get('images', [])
|
969 |
+
for image_data in image_data_list:
|
970 |
+
img = decode_base64_image(image_data)
|
971 |
+
if img is not None:
|
972 |
+
images.append(img)
|
973 |
+
|
974 |
+
if not images:
|
975 |
+
return jsonify({"error": "No valid images provided"}), 400
|
976 |
+
|
977 |
+
# Get coordinator instance
|
978 |
+
coordinator = get_product_comparison_coordinator()
|
979 |
+
|
980 |
+
# Pass the analysis type and session metadata to the coordinator
|
981 |
+
session_metadata = {
|
982 |
+
'analysis_type': analysis_type,
|
983 |
+
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
|
984 |
+
}
|
985 |
+
|
986 |
+
# Start processing in a background thread
|
987 |
+
def run_async_task(loop):
|
988 |
+
asyncio.set_event_loop(loop)
|
989 |
+
loop.run_until_complete(coordinator.process_images(session_id, images, session_metadata))
|
990 |
+
|
991 |
+
loop = asyncio.new_event_loop()
|
992 |
+
thread = Thread(target=run_async_task, args=(loop,))
|
993 |
+
thread.daemon = True
|
994 |
+
thread.start()
|
995 |
+
|
996 |
+
# Return session ID for client to use with streaming endpoint
|
997 |
+
return jsonify({
|
998 |
+
"session_id": session_id,
|
999 |
+
"message": "Product comparison started",
|
1000 |
+
"status": "processing"
|
1001 |
+
})
|
1002 |
+
|
1003 |
+
except Exception as e:
|
1004 |
+
print(f"Error starting product comparison: {e}")
|
1005 |
+
return jsonify({"error": str(e)}), 500
|
1006 |
+
|
1007 |
+
|
1008 |
+
@app.route('/api/product/compare/stream/<session_id>', methods=['GET'])
|
1009 |
+
@login_required
|
1010 |
+
def stream_product_comparison(session_id):
|
1011 |
+
"""Stream updates from a product comparison session"""
|
1012 |
+
if get_product_comparison_coordinator is None:
|
1013 |
+
return jsonify({"error": "Product comparison module not available"}), 500
|
1014 |
+
|
1015 |
+
def generate():
|
1016 |
+
"""Generate SSE events for streaming"""
|
1017 |
+
coordinator = get_product_comparison_coordinator()
|
1018 |
+
last_message_index = 0
|
1019 |
+
retry_count = 0
|
1020 |
+
max_retries = 300 # 5 minutes at 1 second intervals
|
1021 |
+
|
1022 |
+
while retry_count < max_retries:
|
1023 |
+
# Get current status
|
1024 |
+
status = coordinator.get_session_status(session_id)
|
1025 |
+
if status is None:
|
1026 |
+
# Session not found
|
1027 |
+
yield f"data: {json.dumps({'error': 'Session not found'})}\n\n"
|
1028 |
+
break
|
1029 |
+
|
1030 |
+
# Get all messages
|
1031 |
+
messages = coordinator.get_session_messages(session_id)
|
1032 |
+
|
1033 |
+
# Send any new messages
|
1034 |
+
if messages and len(messages) > last_message_index:
|
1035 |
+
new_messages = messages[last_message_index:]
|
1036 |
+
for msg in new_messages:
|
1037 |
+
yield f"data: {json.dumps({'message': msg})}\n\n"
|
1038 |
+
last_message_index = len(messages)
|
1039 |
+
|
1040 |
+
# Send current status
|
1041 |
+
yield f"data: {json.dumps({'status': status})}\n\n"
|
1042 |
+
|
1043 |
+
# If completed or error, send final result and end stream
|
1044 |
+
if status in ['completed', 'error']:
|
1045 |
+
result = coordinator.get_session_result(session_id)
|
1046 |
+
yield f"data: {json.dumps({'final_result': result})}\n\n"
|
1047 |
+
break
|
1048 |
+
|
1049 |
+
# Wait before next update
|
1050 |
+
time.sleep(1)
|
1051 |
+
retry_count += 1
|
1052 |
+
|
1053 |
+
# End the stream if we've reached max retries
|
1054 |
+
if retry_count >= max_retries:
|
1055 |
+
yield f"data: {json.dumps({'error': 'Timeout waiting for results'})}\n\n"
|
1056 |
+
|
1057 |
+
return Response(
|
1058 |
+
stream_with_context(generate()),
|
1059 |
+
mimetype="text/event-stream",
|
1060 |
+
headers={
|
1061 |
+
'Cache-Control': 'no-cache',
|
1062 |
+
'X-Accel-Buffering': 'no',
|
1063 |
+
'Content-Type': 'text/event-stream',
|
1064 |
+
}
|
1065 |
+
)
|
1066 |
+
except Exception as e:
|
1067 |
+
print(f"Error processing object: {e}")
|
1068 |
+
continue
|
1069 |
+
|
1070 |
# κ°μ²΄κ° μλ κ²½μ°
|
1071 |
if not object_ids:
|
1072 |
return jsonify({"error": "No valid objects to add"})
|
frontend/src/App.js
CHANGED
@@ -9,13 +9,16 @@ import {
|
|
9 |
AppBar,
|
10 |
Toolbar,
|
11 |
ThemeProvider,
|
12 |
-
createMuiTheme
|
|
|
|
|
13 |
} from '@material-ui/core';
|
14 |
import ImageUploader from './components/ImageUploader';
|
15 |
import ModelSelector from './components/ModelSelector';
|
16 |
import ResultDisplay from './components/ResultDisplay';
|
17 |
import LlmAnalysis from './components/LlmAnalysis';
|
18 |
import OpenAIChat from './components/OpenAIChat';
|
|
|
19 |
import './App.css';
|
20 |
|
21 |
// Create a theme
|
@@ -45,6 +48,7 @@ function App() {
|
|
45 |
detr: false,
|
46 |
vit: false
|
47 |
});
|
|
|
48 |
|
49 |
// Check API status on component mount
|
50 |
useEffect(() => {
|
@@ -140,12 +144,23 @@ function App() {
|
|
140 |
<AppBar position="static">
|
141 |
<Toolbar>
|
142 |
<Typography variant="h6" style={{ flexGrow: 1 }}>
|
143 |
-
Multi-
|
144 |
</Typography>
|
145 |
</Toolbar>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
</AppBar>
|
147 |
<Container maxWidth="lg" style={{ marginTop: theme.spacing(4), marginBottom: theme.spacing(4) }}>
|
148 |
-
|
|
|
149 |
<Grid item xs={12}>
|
150 |
<Paper style={{ padding: theme.spacing(2) }}>
|
151 |
<Typography variant="h5" gutterBottom>
|
@@ -212,6 +227,9 @@ function App() {
|
|
212 |
<OpenAIChat imageBase64={imageBase64} />
|
213 |
</Grid>
|
214 |
</Grid>
|
|
|
|
|
|
|
215 |
</Container>
|
216 |
</Box>
|
217 |
</ThemeProvider>
|
|
|
9 |
AppBar,
|
10 |
Toolbar,
|
11 |
ThemeProvider,
|
12 |
+
createMuiTheme,
|
13 |
+
Tabs,
|
14 |
+
Tab
|
15 |
} from '@material-ui/core';
|
16 |
import ImageUploader from './components/ImageUploader';
|
17 |
import ModelSelector from './components/ModelSelector';
|
18 |
import ResultDisplay from './components/ResultDisplay';
|
19 |
import LlmAnalysis from './components/LlmAnalysis';
|
20 |
import OpenAIChat from './components/OpenAIChat';
|
21 |
+
import ProductComparison from './components/ProductComparison';
|
22 |
import './App.css';
|
23 |
|
24 |
// Create a theme
|
|
|
48 |
detr: false,
|
49 |
vit: false
|
50 |
});
|
51 |
+
const [activeTab, setActiveTab] = useState(0);
|
52 |
|
53 |
// Check API status on component mount
|
54 |
useEffect(() => {
|
|
|
144 |
<AppBar position="static">
|
145 |
<Toolbar>
|
146 |
<Typography variant="h6" style={{ flexGrow: 1 }}>
|
147 |
+
Vision LLM Multi-Agent System
|
148 |
</Typography>
|
149 |
</Toolbar>
|
150 |
+
<Tabs
|
151 |
+
value={activeTab}
|
152 |
+
onChange={(e, newValue) => setActiveTab(newValue)}
|
153 |
+
indicatorColor="secondary"
|
154 |
+
textColor="inherit"
|
155 |
+
centered
|
156 |
+
>
|
157 |
+
<Tab label="Object Detection" />
|
158 |
+
<Tab label="Product Comparison" />
|
159 |
+
</Tabs>
|
160 |
</AppBar>
|
161 |
<Container maxWidth="lg" style={{ marginTop: theme.spacing(4), marginBottom: theme.spacing(4) }}>
|
162 |
+
{activeTab === 0 ? (
|
163 |
+
<Grid container spacing={3}>
|
164 |
<Grid item xs={12}>
|
165 |
<Paper style={{ padding: theme.spacing(2) }}>
|
166 |
<Typography variant="h5" gutterBottom>
|
|
|
227 |
<OpenAIChat imageBase64={imageBase64} />
|
228 |
</Grid>
|
229 |
</Grid>
|
230 |
+
) : (
|
231 |
+
<ProductComparison />
|
232 |
+
)}
|
233 |
</Container>
|
234 |
</Box>
|
235 |
</ThemeProvider>
|
frontend/src/components/ProductComparison.js
ADDED
@@ -0,0 +1,647 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState, useEffect, useRef } from 'react';
|
2 |
+
import {
|
3 |
+
Box,
|
4 |
+
Paper,
|
5 |
+
Grid,
|
6 |
+
Typography,
|
7 |
+
Button,
|
8 |
+
CircularProgress,
|
9 |
+
Tabs,
|
10 |
+
Tab,
|
11 |
+
Divider,
|
12 |
+
Card,
|
13 |
+
CardContent,
|
14 |
+
CardMedia,
|
15 |
+
IconButton,
|
16 |
+
TextField
|
17 |
+
} from '@material-ui/core';
|
18 |
+
import { AddCircle, Delete, Compare, Search, Info } from '@material-ui/icons';
|
19 |
+
import { makeStyles } from '@material-ui/core/styles';
|
20 |
+
|
21 |
+
const useStyles = makeStyles((theme) => ({
|
22 |
+
root: {
|
23 |
+
marginTop: theme.spacing(3),
|
24 |
+
marginBottom: theme.spacing(3),
|
25 |
+
},
|
26 |
+
imageContainer: {
|
27 |
+
position: 'relative',
|
28 |
+
minHeight: '200px',
|
29 |
+
display: 'flex',
|
30 |
+
justifyContent: 'center',
|
31 |
+
alignItems: 'center',
|
32 |
+
border: '2px dashed #ccc',
|
33 |
+
borderRadius: '8px',
|
34 |
+
margin: theme.spacing(1),
|
35 |
+
padding: theme.spacing(1),
|
36 |
+
},
|
37 |
+
progressLog: {
|
38 |
+
marginTop: theme.spacing(2),
|
39 |
+
height: '200px',
|
40 |
+
overflowY: 'auto',
|
41 |
+
backgroundColor: '#f8f9fa',
|
42 |
+
padding: theme.spacing(1),
|
43 |
+
borderRadius: '4px',
|
44 |
+
fontFamily: 'monospace',
|
45 |
+
fontSize: '0.9rem',
|
46 |
+
},
|
47 |
+
logEntry: {
|
48 |
+
margin: '4px 0',
|
49 |
+
padding: '2px 5px',
|
50 |
+
borderLeft: '3px solid #ccc',
|
51 |
+
},
|
52 |
+
logEntryAgent: {
|
53 |
+
borderLeft: '3px solid #2196f3',
|
54 |
+
},
|
55 |
+
logEntrySystem: {
|
56 |
+
borderLeft: '3px solid #4caf50',
|
57 |
+
},
|
58 |
+
logEntryError: {
|
59 |
+
borderLeft: '3px solid #f44336',
|
60 |
+
},
|
61 |
+
logTime: {
|
62 |
+
color: '#666',
|
63 |
+
fontSize: '0.8rem',
|
64 |
+
marginRight: theme.spacing(1),
|
65 |
+
},
|
66 |
+
imagePreview: {
|
67 |
+
maxWidth: '100%',
|
68 |
+
maxHeight: '300px',
|
69 |
+
objectFit: 'contain',
|
70 |
+
},
|
71 |
+
uploadIcon: {
|
72 |
+
fontSize: '3rem',
|
73 |
+
color: '#aaa',
|
74 |
+
},
|
75 |
+
uploadInput: {
|
76 |
+
display: 'none',
|
77 |
+
},
|
78 |
+
deleteButton: {
|
79 |
+
position: 'absolute',
|
80 |
+
top: '8px',
|
81 |
+
right: '8px',
|
82 |
+
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
83 |
+
'&:hover': {
|
84 |
+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
85 |
+
},
|
86 |
+
},
|
87 |
+
tabPanel: {
|
88 |
+
padding: theme.spacing(2),
|
89 |
+
},
|
90 |
+
resultCard: {
|
91 |
+
marginTop: theme.spacing(2),
|
92 |
+
marginBottom: theme.spacing(2),
|
93 |
+
},
|
94 |
+
comparisonTable: {
|
95 |
+
width: '100%',
|
96 |
+
borderCollapse: 'collapse',
|
97 |
+
'& th, & td': {
|
98 |
+
border: '1px solid #ddd',
|
99 |
+
padding: '8px',
|
100 |
+
textAlign: 'left',
|
101 |
+
},
|
102 |
+
'& th': {
|
103 |
+
backgroundColor: '#f2f2f2',
|
104 |
+
},
|
105 |
+
'& tr:nth-child(even)': {
|
106 |
+
backgroundColor: '#f9f9f9',
|
107 |
+
},
|
108 |
+
},
|
109 |
+
loadingContainer: {
|
110 |
+
display: 'flex',
|
111 |
+
flexDirection: 'column',
|
112 |
+
alignItems: 'center',
|
113 |
+
padding: theme.spacing(4),
|
114 |
+
},
|
115 |
+
highlight: {
|
116 |
+
backgroundColor: '#e3f2fd',
|
117 |
+
padding: theme.spacing(1),
|
118 |
+
borderRadius: '4px',
|
119 |
+
fontWeight: 'bold',
|
120 |
+
}
|
121 |
+
}));
|
122 |
+
|
123 |
+
// λΆμ μ ν ν ν¨λ μ»΄ν¬λνΈ
|
124 |
+
function TabPanel(props) {
|
125 |
+
const { children, value, index, ...other } = props;
|
126 |
+
|
127 |
+
return (
|
128 |
+
<div
|
129 |
+
role="tabpanel"
|
130 |
+
hidden={value !== index}
|
131 |
+
id={`analysis-tabpanel-${index}`}
|
132 |
+
aria-labelledby={`analysis-tab-${index}`}
|
133 |
+
{...other}
|
134 |
+
>
|
135 |
+
{value === index && (
|
136 |
+
<Box p={3}>
|
137 |
+
{children}
|
138 |
+
</Box>
|
139 |
+
)}
|
140 |
+
</div>
|
141 |
+
);
|
142 |
+
}
|
143 |
+
|
144 |
+
const ProductComparison = () => {
|
145 |
+
const classes = useStyles();
|
146 |
+
const [images, setImages] = useState([null, null]); // μ΅λ 2κ° μ΄λ―Έμ§ μ μ₯
|
147 |
+
const [imagePreviews, setImagePreviews] = useState([null, null]);
|
148 |
+
const [isProcessing, setIsProcessing] = useState(false);
|
149 |
+
const [analysisResults, setAnalysisResults] = useState(null);
|
150 |
+
const [error, setError] = useState(null);
|
151 |
+
const [tabValue, setTabValue] = useState(0);
|
152 |
+
const [progressLogs, setProgressLogs] = useState([]);
|
153 |
+
const logEndRef = useRef(null);
|
154 |
+
|
155 |
+
// ν λ³κ²½ νΈλ€λ¬
|
156 |
+
const handleTabChange = (event, newValue) => {
|
157 |
+
setTabValue(newValue);
|
158 |
+
};
|
159 |
+
|
160 |
+
// μ΄λ―Έμ§ μ
λ‘λ νΈλ€λ¬
|
161 |
+
const handleImageUpload = (event, index) => {
|
162 |
+
const file = event.target.files[0];
|
163 |
+
|
164 |
+
if (file) {
|
165 |
+
// μ΄λ―Έμ§ 미리보기 μμ±
|
166 |
+
const reader = new FileReader();
|
167 |
+
reader.onload = (e) => {
|
168 |
+
const newPreviews = [...imagePreviews];
|
169 |
+
newPreviews[index] = e.target.result;
|
170 |
+
setImagePreviews(newPreviews);
|
171 |
+
};
|
172 |
+
reader.readAsDataURL(file);
|
173 |
+
|
174 |
+
// μ΄λ―Έμ§ νμΌ μν μ
λ°μ΄νΈ
|
175 |
+
const newImages = [...images];
|
176 |
+
newImages[index] = file;
|
177 |
+
setImages(newImages);
|
178 |
+
|
179 |
+
// κ²°κ³Ό λ° μ€λ₯ μ΄κΈ°ν
|
180 |
+
setAnalysisResults(null);
|
181 |
+
setError(null);
|
182 |
+
}
|
183 |
+
};
|
184 |
+
|
185 |
+
// μ΄λ―Έμ§ μμ νΈλ€λ¬
|
186 |
+
const handleImageDelete = (index) => {
|
187 |
+
const newImages = [...images];
|
188 |
+
const newPreviews = [...imagePreviews];
|
189 |
+
|
190 |
+
newImages[index] = null;
|
191 |
+
newPreviews[index] = null;
|
192 |
+
|
193 |
+
setImages(newImages);
|
194 |
+
setImagePreviews(newPreviews);
|
195 |
+
setAnalysisResults(null);
|
196 |
+
};
|
197 |
+
|
198 |
+
// λ‘κ·Έ μΆκ° ν¨μ
|
199 |
+
const addLog = (message, type = 'info') => {
|
200 |
+
const now = new Date();
|
201 |
+
const timeStr = now.toLocaleTimeString();
|
202 |
+
const newLog = {
|
203 |
+
time: timeStr,
|
204 |
+
message,
|
205 |
+
type // 'info', 'agent', 'system', 'error'
|
206 |
+
};
|
207 |
+
|
208 |
+
setProgressLogs(logs => [...logs, newLog]);
|
209 |
+
};
|
210 |
+
|
211 |
+
// λ‘κ·Έμ°½ μλ μ€ν¬λ‘€
|
212 |
+
useEffect(() => {
|
213 |
+
if (logEndRef.current) {
|
214 |
+
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
215 |
+
}
|
216 |
+
}, [progressLogs]);
|
217 |
+
|
218 |
+
// SSE μ°κ²° ν¨μ
|
219 |
+
const connectToSSE = (sessionId) => {
|
220 |
+
const eventSource = new EventSource(`/api/product/compare/stream/${sessionId}`);
|
221 |
+
|
222 |
+
eventSource.onmessage = (event) => {
|
223 |
+
try {
|
224 |
+
const data = JSON.parse(event.data);
|
225 |
+
if (data.message) {
|
226 |
+
addLog(data.message, data.agent || 'info');
|
227 |
+
}
|
228 |
+
// μ΅μ’
κ²°κ³Όκ° μ€λ©΄ λΆμ κ²°κ³Ό μ
λ°μ΄νΈ
|
229 |
+
if (data.final_result) {
|
230 |
+
setAnalysisResults(data.final_result);
|
231 |
+
setIsProcessing(false);
|
232 |
+
eventSource.close();
|
233 |
+
}
|
234 |
+
} catch (err) {
|
235 |
+
addLog(`Event processing error: ${err.message}`, 'error');
|
236 |
+
}
|
237 |
+
};
|
238 |
+
|
239 |
+
eventSource.onerror = (err) => {
|
240 |
+
addLog('Server connection lost. Please try again in a moment.', 'error');
|
241 |
+
eventSource.close();
|
242 |
+
setIsProcessing(false);
|
243 |
+
};
|
244 |
+
|
245 |
+
return eventSource;
|
246 |
+
};
|
247 |
+
|
248 |
+
// μ ν λΆμ μ²λ¦¬ νΈλ€λ¬
|
249 |
+
const handleAnalysis = async () => {
|
250 |
+
// μ ν¨μ± κ²μ¬: μ΅μ 1κ° μ΄μμ μ΄λ―Έμ§κ° μμ΄μΌ ν¨
|
251 |
+
if (!images[0] && !images[1]) {
|
252 |
+
setError('Please upload at least one product image for analysis');
|
253 |
+
return;
|
254 |
+
}
|
255 |
+
|
256 |
+
setIsProcessing(true);
|
257 |
+
setError(null);
|
258 |
+
setProgressLogs([]); // λ‘κ·Έ μ΄κΈ°ν
|
259 |
+
addLog('Starting product analysis...', 'system');
|
260 |
+
|
261 |
+
try {
|
262 |
+
const formData = new FormData();
|
263 |
+
|
264 |
+
// μ
λ‘λλ μ΄λ―Έμ§λ§ FormDataμ μΆκ°
|
265 |
+
if (images[0]) {
|
266 |
+
formData.append('image1', images[0]);
|
267 |
+
addLog('Product A image uploaded.', 'info');
|
268 |
+
}
|
269 |
+
if (images[1]) {
|
270 |
+
formData.append('image2', images[1]);
|
271 |
+
addLog('Product B image uploaded.', 'info');
|
272 |
+
}
|
273 |
+
|
274 |
+
// λΆμ νμ
μΆκ° (ν μΈλ±μ€λ‘ ꡬλΆ)
|
275 |
+
const analysisTypes = ['info', 'compare', 'value', 'recommend'];
|
276 |
+
const analysisType = analysisTypes[tabValue];
|
277 |
+
formData.append('analysisType', analysisType);
|
278 |
+
addLog(`Analysis type: ${analysisType === 'info' ? 'Product Information' : analysisType === 'compare' ? 'Performance Comparison' : analysisType === 'value' ? 'Value Analysis' : 'Purchase Recommendation'}`, 'system');
|
279 |
+
|
280 |
+
// λ°±μλ API νΈμΆ (μΈμ
μμ)
|
281 |
+
addLog('Initializing analysis session...', 'system');
|
282 |
+
const response = await fetch('/api/product/compare/start', {
|
283 |
+
method: 'POST',
|
284 |
+
body: formData,
|
285 |
+
});
|
286 |
+
|
287 |
+
if (!response.ok) {
|
288 |
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
289 |
+
}
|
290 |
+
|
291 |
+
const data = await response.json();
|
292 |
+
const sessionId = data.session_id;
|
293 |
+
|
294 |
+
if (!sessionId) {
|
295 |
+
throw new Error('Failed to receive session ID');
|
296 |
+
}
|
297 |
+
|
298 |
+
addLog(`Analysis session started. (Session ID: ${sessionId.substring(0, 8)}...)`, 'system');
|
299 |
+
addLog('Agents are collaborating to analyze products. Please wait a moment...', 'system');
|
300 |
+
|
301 |
+
// SSE μ€νΈλ¦Ό μ°κ²°
|
302 |
+
const eventSource = connectToSSE(sessionId);
|
303 |
+
|
304 |
+
// μ»΄ν¬λνΈ μΈλ§μ΄νΈμ μ°κ²° μ’
λ£
|
305 |
+
return () => {
|
306 |
+
eventSource.close();
|
307 |
+
};
|
308 |
+
|
309 |
+
} catch (err) {
|
310 |
+
console.error('μ ν λΆμ μ€λ₯:', err);
|
311 |
+
addLog(`Error occurred: ${err.message}`, 'error');
|
312 |
+
setError(`Error during analysis: ${err.message}`);
|
313 |
+
setIsProcessing(false);
|
314 |
+
}
|
315 |
+
};
|
316 |
+
|
317 |
+
// λΆμ κ²°κ³Ό λ λλ§ ν¨μ
|
318 |
+
const renderAnalysisResults = () => {
|
319 |
+
if (!analysisResults) return null;
|
320 |
+
|
321 |
+
// λΆμ μ νμ λ°λΌ λ€λ₯Έ κ²°κ³Ό νμ
|
322 |
+
switch (tabValue) {
|
323 |
+
case 0: // μ ν μ 보 νμ
|
324 |
+
return (
|
325 |
+
<Card className={classes.resultCard}>
|
326 |
+
<CardContent>
|
327 |
+
<Typography variant="h6" gutterBottom>Product Information</Typography>
|
328 |
+
<Divider style={{ margin: '8px 0 16px' }} />
|
329 |
+
|
330 |
+
{analysisResults.productInfo && (
|
331 |
+
<div>
|
332 |
+
<Typography variant="subtitle1">
|
333 |
+
<strong>Product Name:</strong> {analysisResults.productInfo.name}
|
334 |
+
</Typography>
|
335 |
+
<Typography variant="body1">
|
336 |
+
<strong>Brand:</strong> {analysisResults.productInfo.brand}
|
337 |
+
</Typography>
|
338 |
+
<Typography variant="body1">
|
339 |
+
<strong>Category:</strong> {analysisResults.productInfo.category}
|
340 |
+
</Typography>
|
341 |
+
|
342 |
+
<Typography variant="subtitle1" style={{ marginTop: '16px' }}>Key Specifications</Typography>
|
343 |
+
<ul>
|
344 |
+
{analysisResults.productInfo.specs.map((spec, index) => (
|
345 |
+
<li key={index}>
|
346 |
+
<Typography variant="body2">
|
347 |
+
<strong>{spec.name}:</strong> {spec.value}
|
348 |
+
</Typography>
|
349 |
+
</li>
|
350 |
+
))}
|
351 |
+
</ul>
|
352 |
+
|
353 |
+
<Typography variant="subtitle1" style={{ marginTop: '16px' }}>Pros</Typography>
|
354 |
+
<ul>
|
355 |
+
{analysisResults.productInfo.pros.map((pro, index) => (
|
356 |
+
<li key={index}>
|
357 |
+
<Typography variant="body2">{pro}</Typography>
|
358 |
+
</li>
|
359 |
+
))}
|
360 |
+
</ul>
|
361 |
+
|
362 |
+
<Typography variant="subtitle1" style={{ marginTop: '16px' }}>Cons</Typography>
|
363 |
+
<ul>
|
364 |
+
{analysisResults.productInfo.cons.map((con, index) => (
|
365 |
+
<li key={index}>
|
366 |
+
<Typography variant="body2">{con}</Typography>
|
367 |
+
</li>
|
368 |
+
))}
|
369 |
+
</ul>
|
370 |
+
</div>
|
371 |
+
)}
|
372 |
+
</CardContent>
|
373 |
+
</Card>
|
374 |
+
);
|
375 |
+
|
376 |
+
case 1: // μ ν μ±λ₯ λΉκ΅
|
377 |
+
return (
|
378 |
+
<Card className={classes.resultCard}>
|
379 |
+
<CardContent>
|
380 |
+
<Typography variant="h6" gutterBottom>Product Comparison Analysis</Typography>
|
381 |
+
<Divider style={{ margin: '8px 0 16px' }} />
|
382 |
+
|
383 |
+
{analysisResults.comparison && (
|
384 |
+
<div>
|
385 |
+
<Typography variant="subtitle1" gutterBottom>Product Specification Comparison</Typography>
|
386 |
+
|
387 |
+
<table className={classes.comparisonTable}>
|
388 |
+
<thead>
|
389 |
+
<tr>
|
390 |
+
<th>Feature</th>
|
391 |
+
<th>Product A</th>
|
392 |
+
<th>Product B</th>
|
393 |
+
<th>Comparison</th>
|
394 |
+
</tr>
|
395 |
+
</thead>
|
396 |
+
<tbody>
|
397 |
+
{analysisResults.comparison.specs.map((spec, index) => (
|
398 |
+
<tr key={index}>
|
399 |
+
<td><strong>{spec.name}</strong></td>
|
400 |
+
<td>{spec.valueA}</td>
|
401 |
+
<td>{spec.valueB}</td>
|
402 |
+
<td>
|
403 |
+
<span className={spec.winner === 'A' ? classes.highlight : ''}>
|
404 |
+
{spec.comparison}
|
405 |
+
</span>
|
406 |
+
</td>
|
407 |
+
</tr>
|
408 |
+
))}
|
409 |
+
</tbody>
|
410 |
+
</table>
|
411 |
+
|
412 |
+
<Typography variant="subtitle1" style={{ marginTop: '16px' }}>Overall Comparison</Typography>
|
413 |
+
<Typography variant="body1">{analysisResults.comparison.summary}</Typography>
|
414 |
+
</div>
|
415 |
+
)}
|
416 |
+
</CardContent>
|
417 |
+
</Card>
|
418 |
+
);
|
419 |
+
|
420 |
+
case 2: // κ°κ²© λλΉ κ°μΉ λΆμ
|
421 |
+
return (
|
422 |
+
<Card className={classes.resultCard}>
|
423 |
+
<CardContent>
|
424 |
+
<Typography variant="h6" gutterBottom>Price-to-Value Analysis</Typography>
|
425 |
+
<Divider style={{ margin: '8px 0 16px' }} />
|
426 |
+
|
427 |
+
{analysisResults.valueAnalysis && (
|
428 |
+
<div>
|
429 |
+
<Typography variant="subtitle1">Price Information</Typography>
|
430 |
+
<Typography variant="body1">
|
431 |
+
<strong>Product A:</strong> {analysisResults.valueAnalysis.priceA}
|
432 |
+
</Typography>
|
433 |
+
{analysisResults.valueAnalysis.priceB && (
|
434 |
+
<Typography variant="body1">
|
435 |
+
<strong>Product B:</strong> {analysisResults.valueAnalysis.priceB}
|
436 |
+
</Typography>
|
437 |
+
)}
|
438 |
+
|
439 |
+
<Typography variant="subtitle1" style={{ marginTop: '16px' }}>Value Analysis</Typography>
|
440 |
+
<Typography variant="body1">{analysisResults.valueAnalysis.analysis}</Typography>
|
441 |
+
|
442 |
+
<Typography variant="subtitle1" style={{ marginTop: '16px' }}>Price-to-Performance Score</Typography>
|
443 |
+
<Typography variant="body1">
|
444 |
+
<strong>Product A:</strong> {analysisResults.valueAnalysis.valueScoreA}/10
|
445 |
+
</Typography>
|
446 |
+
{analysisResults.valueAnalysis.valueScoreB && (
|
447 |
+
<Typography variant="body1">
|
448 |
+
<strong>Product B:</strong> {analysisResults.valueAnalysis.valueScoreB}/10
|
449 |
+
</Typography>
|
450 |
+
)}
|
451 |
+
</div>
|
452 |
+
)}
|
453 |
+
</CardContent>
|
454 |
+
</Card>
|
455 |
+
);
|
456 |
+
|
457 |
+
case 3: // μ΅μ ꡬ맀 μΆμ²
|
458 |
+
return (
|
459 |
+
<Card className={classes.resultCard}>
|
460 |
+
<CardContent>
|
461 |
+
<Typography variant="h6" gutterBottom>Purchase Recommendations</Typography>
|
462 |
+
<Divider style={{ margin: '8px 0 16px' }} />
|
463 |
+
|
464 |
+
{analysisResults.recommendation && (
|
465 |
+
<div>
|
466 |
+
<Typography variant="subtitle1" className={classes.highlight}>
|
467 |
+
Recommended Product: {analysisResults.recommendation.recommendedProduct}
|
468 |
+
</Typography>
|
469 |
+
|
470 |
+
<Typography variant="body1" style={{ marginTop: '16px' }}>
|
471 |
+
{analysisResults.recommendation.reason}
|
472 |
+
</Typography>
|
473 |
+
|
474 |
+
<Typography variant="subtitle1" style={{ marginTop: '16px' }}>Alternative Products</Typography>
|
475 |
+
<ul>
|
476 |
+
{analysisResults.recommendation.alternatives.map((alt, index) => (
|
477 |
+
<li key={index}>
|
478 |
+
<Typography variant="body2">
|
479 |
+
<strong>{alt.name}</strong>: {alt.reason}
|
480 |
+
</Typography>
|
481 |
+
</li>
|
482 |
+
))}
|
483 |
+
</ul>
|
484 |
+
|
485 |
+
{analysisResults.recommendation.buyingTips && (
|
486 |
+
<>
|
487 |
+
<Typography variant="subtitle1" style={{ marginTop: '16px' }}>Buying Tips</Typography>
|
488 |
+
<ul>
|
489 |
+
{analysisResults.recommendation.buyingTips.map((tip, index) => (
|
490 |
+
<li key={index}>
|
491 |
+
<Typography variant="body2">{tip}</Typography>
|
492 |
+
</li>
|
493 |
+
))}
|
494 |
+
</ul>
|
495 |
+
</>
|
496 |
+
)}
|
497 |
+
</div>
|
498 |
+
)}
|
499 |
+
</CardContent>
|
500 |
+
</Card>
|
501 |
+
);
|
502 |
+
|
503 |
+
default:
|
504 |
+
return null;
|
505 |
+
}
|
506 |
+
};
|
507 |
+
|
508 |
+
return (
|
509 |
+
<Paper className={classes.root}>
|
510 |
+
<Box p={3}>
|
511 |
+
<Typography variant="h5" gutterBottom>
|
512 |
+
Product Comparison Analysis
|
513 |
+
</Typography>
|
514 |
+
<Typography variant="body1" paragraph>
|
515 |
+
Upload product images to receive detailed information and comparison analysis.
|
516 |
+
You can analyze various products including cars, smartphones, laptops, and more.
|
517 |
+
</Typography>
|
518 |
+
|
519 |
+
<Grid container spacing={3}>
|
520 |
+
{/* μ΄λ―Έμ§ μ
λ‘λ μμ */}
|
521 |
+
{[0, 1].map((index) => (
|
522 |
+
<Grid item xs={12} md={6} key={index}>
|
523 |
+
<Box className={classes.imageContainer}>
|
524 |
+
{imagePreviews[index] ? (
|
525 |
+
<>
|
526 |
+
<img
|
527 |
+
src={imagePreviews[index]}
|
528 |
+
alt={`Product ${index + 1}`}
|
529 |
+
className={classes.imagePreview}
|
530 |
+
/>
|
531 |
+
<IconButton
|
532 |
+
className={classes.deleteButton}
|
533 |
+
onClick={() => handleImageDelete(index)}
|
534 |
+
>
|
535 |
+
<Delete />
|
536 |
+
</IconButton>
|
537 |
+
</>
|
538 |
+
) : (
|
539 |
+
<>
|
540 |
+
<input
|
541 |
+
accept="image/*"
|
542 |
+
className={classes.uploadInput}
|
543 |
+
id={`upload-image-${index}`}
|
544 |
+
type="file"
|
545 |
+
onChange={(e) => handleImageUpload(e, index)}
|
546 |
+
/>
|
547 |
+
<label htmlFor={`upload-image-${index}`}>
|
548 |
+
<Box display="flex" flexDirection="column" alignItems="center">
|
549 |
+
<AddCircle className={classes.uploadIcon} />
|
550 |
+
<Typography variant="body2" style={{ marginTop: '8px' }}>
|
551 |
+
Upload {index === 0 ? 'Product A' : 'Product B'} Image
|
552 |
+
</Typography>
|
553 |
+
</Box>
|
554 |
+
</label>
|
555 |
+
</>
|
556 |
+
)}
|
557 |
+
</Box>
|
558 |
+
</Grid>
|
559 |
+
))}
|
560 |
+
|
561 |
+
{/* λΆμ μ ν ν */}
|
562 |
+
<Grid item xs={12}>
|
563 |
+
<Paper>
|
564 |
+
<Tabs
|
565 |
+
value={tabValue}
|
566 |
+
onChange={handleTabChange}
|
567 |
+
indicatorColor="primary"
|
568 |
+
textColor="primary"
|
569 |
+
centered
|
570 |
+
>
|
571 |
+
<Tab icon={<Info />} label="Product Info" />
|
572 |
+
<Tab icon={<Compare />} label="Performance" disabled={!images[0] || !images[1]} />
|
573 |
+
<Tab icon={<Search />} label="Value Analysis" />
|
574 |
+
<Tab label="Recommendations" />
|
575 |
+
</Tabs>
|
576 |
+
|
577 |
+
<Box p={2} textAlign="center">
|
578 |
+
<Button
|
579 |
+
variant="contained"
|
580 |
+
color="primary"
|
581 |
+
onClick={handleAnalysis}
|
582 |
+
disabled={isProcessing || (!images[0] && !images[1])}
|
583 |
+
startIcon={isProcessing ? <CircularProgress size={24} /> : null}
|
584 |
+
>
|
585 |
+
{isProcessing ? 'Analyzing...' : 'Start Analysis'}
|
586 |
+
</Button>
|
587 |
+
</Box>
|
588 |
+
|
589 |
+
{/* μ€λ₯ λ©μμ§ νμ */}
|
590 |
+
{error && (
|
591 |
+
<Box p={2} bgcolor="#ffebee" borderRadius="4px" mb={2}>
|
592 |
+
<Typography color="error">{error}</Typography>
|
593 |
+
</Box>
|
594 |
+
)}
|
595 |
+
</Paper>
|
596 |
+
</Grid>
|
597 |
+
|
598 |
+
{/* μ§ν κ³Όμ λ‘κ·Έ νμ */}
|
599 |
+
<Grid item xs={12}>
|
600 |
+
<Paper>
|
601 |
+
<Box p={2}>
|
602 |
+
<Typography variant="h6" gutterBottom>
|
603 |
+
Analysis Progress
|
604 |
+
</Typography>
|
605 |
+
<Box className={classes.progressLog}>
|
606 |
+
{progressLogs.length === 0 ? (
|
607 |
+
<Typography variant="body2" color="textSecondary" style={{padding: '10px'}}>
|
608 |
+
Progress details will appear here when analysis starts.
|
609 |
+
</Typography>
|
610 |
+
) : (
|
611 |
+
progressLogs.map((log, index) => (
|
612 |
+
<Box
|
613 |
+
key={index}
|
614 |
+
className={`${classes.logEntry} ${log.type === 'agent' ? classes.logEntryAgent : log.type === 'system' ? classes.logEntrySystem : log.type === 'error' ? classes.logEntryError : ''}`}
|
615 |
+
>
|
616 |
+
<span className={classes.logTime}>[{log.time}]</span>
|
617 |
+
{log.message}
|
618 |
+
</Box>
|
619 |
+
))
|
620 |
+
)}
|
621 |
+
<div ref={logEndRef} />
|
622 |
+
</Box>
|
623 |
+
</Box>
|
624 |
+
</Paper>
|
625 |
+
</Grid>
|
626 |
+
|
627 |
+
{/* κ²°κ³Ό νμ μμ */}
|
628 |
+
<Grid item xs={12}>
|
629 |
+
{isProcessing ? (
|
630 |
+
<Box className={classes.loadingContainer}>
|
631 |
+
<CircularProgress />
|
632 |
+
<Typography variant="h6" style={{ marginTop: '16px' }}>
|
633 |
+
Analyzing Products...
|
634 |
+
</Typography>
|
635 |
+
<Typography variant="body2" color="textSecondary">
|
636 |
+
Product recognition, information retrieval, and comparison analysis in progress.
|
637 |
+
</Typography>
|
638 |
+
</Box>
|
639 |
+
) : renderAnalysisResults()}
|
640 |
+
</Grid>
|
641 |
+
</Grid>
|
642 |
+
</Box>
|
643 |
+
</Paper>
|
644 |
+
);
|
645 |
+
};
|
646 |
+
|
647 |
+
export default ProductComparison;
|
product_comparison.py
ADDED
@@ -0,0 +1,1108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Product Comparison Multi-Agent System
|
3 |
+
|
4 |
+
This module implements a multi-agent system for product comparison based on images.
|
5 |
+
The system uses various specialized agents to process images, extract features,
|
6 |
+
compare products, and provide recommendations.
|
7 |
+
|
8 |
+
Main components:
|
9 |
+
- Coordinator: Orchestrates the multi-agent workflow
|
10 |
+
- ImageProcessingAgent: Handles image recognition and object detection
|
11 |
+
- FeatureExtractionAgent: Extracts product specifications and features
|
12 |
+
- ComparisonAgent: Compares products based on extracted features
|
13 |
+
- RecommendationAgent: Provides purchase recommendations
|
14 |
+
|
15 |
+
Each agent utilizes vision models and LLMs to perform its specialized tasks.
|
16 |
+
"""
|
17 |
+
|
18 |
+
import os
|
19 |
+
import uuid
|
20 |
+
import json
|
21 |
+
import time
|
22 |
+
from typing import Dict, List, Any, Optional, Tuple
|
23 |
+
import base64
|
24 |
+
from io import BytesIO
|
25 |
+
from datetime import datetime
|
26 |
+
from threading import Thread
|
27 |
+
|
28 |
+
import torch
|
29 |
+
from PIL import Image
|
30 |
+
import numpy as np
|
31 |
+
|
32 |
+
# Import LangChain components for agent implementation
|
33 |
+
try:
|
34 |
+
from langchain_core.prompts import ChatPromptTemplate
|
35 |
+
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
|
36 |
+
from langchain.agents import create_openai_functions_agent
|
37 |
+
from langchain.agents import AgentExecutor
|
38 |
+
from langchain.memory import ConversationBufferMemory
|
39 |
+
from langchain.tools.render import format_tool_to_openai_function
|
40 |
+
from langchain_community.chat_models import ChatOpenAI
|
41 |
+
from langchain_community.tools.python.tool import PythonAstREPLTool
|
42 |
+
except ImportError:
|
43 |
+
print("Warning: LangChain components not available. Installing required packages...")
|
44 |
+
import subprocess
|
45 |
+
subprocess.run(["pip", "install", "langchain", "langchain-openai", "langchain-community"])
|
46 |
+
from langchain_core.prompts import ChatPromptTemplate
|
47 |
+
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
|
48 |
+
from langchain.agents import create_openai_functions_agent
|
49 |
+
from langchain.agents import AgentExecutor
|
50 |
+
from langchain.memory import ConversationBufferMemory
|
51 |
+
from langchain.tools.render import format_tool_to_openai_function
|
52 |
+
from langchain_community.chat_models import ChatOpenAI
|
53 |
+
from langchain_community.tools.python.tool import PythonAstREPLTool
|
54 |
+
|
55 |
+
# Import vision models if available
|
56 |
+
try:
|
57 |
+
from ultralytics import YOLO
|
58 |
+
except ImportError:
|
59 |
+
YOLO = None
|
60 |
+
|
61 |
+
try:
|
62 |
+
from transformers import CLIPProcessor, CLIPModel
|
63 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
64 |
+
from transformers import ViTImageProcessor, ViTForImageClassification
|
65 |
+
except ImportError:
|
66 |
+
CLIPProcessor = None
|
67 |
+
CLIPModel = None
|
68 |
+
AutoModelForCausalLM = None
|
69 |
+
AutoTokenizer = None
|
70 |
+
ViTImageProcessor = None
|
71 |
+
ViTForImageClassification = None
|
72 |
+
|
73 |
+
# Session storage for SSE communication
|
74 |
+
class SessionManager:
|
75 |
+
"""Manages active product comparison sessions and their event streams"""
|
76 |
+
|
77 |
+
def __init__(self):
|
78 |
+
self.active_sessions = {}
|
79 |
+
self.results = {}
|
80 |
+
|
81 |
+
def create_session(self, session_id=None):
|
82 |
+
"""Create a new session with unique ID"""
|
83 |
+
if session_id is None:
|
84 |
+
session_id = str(uuid.uuid4())
|
85 |
+
|
86 |
+
self.active_sessions[session_id] = {
|
87 |
+
"created_at": datetime.now(),
|
88 |
+
"messages": [],
|
89 |
+
"status": "created"
|
90 |
+
}
|
91 |
+
return session_id
|
92 |
+
|
93 |
+
def add_message(self, session_id, message, agent_type="system"):
|
94 |
+
"""Add a message to the session event stream"""
|
95 |
+
if session_id not in self.active_sessions:
|
96 |
+
return False
|
97 |
+
|
98 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
99 |
+
self.active_sessions[session_id]["messages"].append({
|
100 |
+
"message": message,
|
101 |
+
"agent": agent_type,
|
102 |
+
"timestamp": timestamp
|
103 |
+
})
|
104 |
+
return True
|
105 |
+
|
106 |
+
def get_messages(self, session_id):
|
107 |
+
"""Get all messages for a session"""
|
108 |
+
if session_id not in self.active_sessions:
|
109 |
+
return []
|
110 |
+
return self.active_sessions[session_id]["messages"]
|
111 |
+
|
112 |
+
def set_final_result(self, session_id, result):
|
113 |
+
"""Store the final analysis result for a session"""
|
114 |
+
self.results[session_id] = result
|
115 |
+
self.active_sessions[session_id]["status"] = "completed"
|
116 |
+
|
117 |
+
def get_final_result(self, session_id):
|
118 |
+
"""Get the final result for a session"""
|
119 |
+
return self.results.get(session_id)
|
120 |
+
|
121 |
+
def set_status(self, session_id, status):
|
122 |
+
"""Update session status"""
|
123 |
+
if session_id in self.active_sessions:
|
124 |
+
self.active_sessions[session_id]["status"] = status
|
125 |
+
|
126 |
+
def get_status(self, session_id):
|
127 |
+
"""Get session status"""
|
128 |
+
if session_id in self.active_sessions:
|
129 |
+
return self.active_sessions[session_id]["status"]
|
130 |
+
return None
|
131 |
+
|
132 |
+
def clean_old_sessions(self, max_age_hours=24):
|
133 |
+
"""Clean up old sessions"""
|
134 |
+
now = datetime.now()
|
135 |
+
sessions_to_remove = []
|
136 |
+
|
137 |
+
for session_id, session_data in self.active_sessions.items():
|
138 |
+
age = now - session_data["created_at"]
|
139 |
+
if age.total_seconds() > max_age_hours * 3600:
|
140 |
+
sessions_to_remove.append(session_id)
|
141 |
+
|
142 |
+
for session_id in sessions_to_remove:
|
143 |
+
del self.active_sessions[session_id]
|
144 |
+
if session_id in self.results:
|
145 |
+
del self.results[session_id]
|
146 |
+
|
147 |
+
# Initialize the session manager
|
148 |
+
session_manager = SessionManager()
|
149 |
+
|
150 |
+
# Base Agent Class
|
151 |
+
class BaseAgent:
|
152 |
+
"""Base class for all specialized agents"""
|
153 |
+
|
154 |
+
def __init__(self, name, llm=None):
|
155 |
+
self.name = name
|
156 |
+
|
157 |
+
# Use TinyLlama as the default LLM if none is provided
|
158 |
+
if llm is None:
|
159 |
+
try:
|
160 |
+
# Initialize TinyLlama model
|
161 |
+
model_name = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
|
162 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
163 |
+
model = AutoModelForCausalLM.from_pretrained(
|
164 |
+
model_name,
|
165 |
+
torch_dtype=torch.float16,
|
166 |
+
).to("cuda" if torch.cuda.is_available() else "cpu")
|
167 |
+
|
168 |
+
# Wrap the model for LangChain
|
169 |
+
self.llm = self._create_llm_wrapper(model, tokenizer)
|
170 |
+
except Exception as e:
|
171 |
+
print(f"Error loading TinyLlama: {e}")
|
172 |
+
# Fallback to a simple string output
|
173 |
+
self.llm = None
|
174 |
+
else:
|
175 |
+
self.llm = llm
|
176 |
+
|
177 |
+
def _create_llm_wrapper(self, model, tokenizer):
|
178 |
+
"""Create a simple wrapper for the LLM model"""
|
179 |
+
# This is a simplified wrapper - in production, you'd use a proper LangChain integration
|
180 |
+
def generate_text(prompt, max_tokens=512):
|
181 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
182 |
+
with torch.no_grad():
|
183 |
+
output = model.generate(
|
184 |
+
**inputs,
|
185 |
+
max_new_tokens=max_tokens,
|
186 |
+
temperature=0.7,
|
187 |
+
do_sample=True
|
188 |
+
)
|
189 |
+
response = tokenizer.decode(output[0], skip_special_tokens=True)
|
190 |
+
# Remove the prompt from the response
|
191 |
+
if response.startswith(prompt):
|
192 |
+
response = response[len(prompt):].strip()
|
193 |
+
return response
|
194 |
+
|
195 |
+
return generate_text
|
196 |
+
|
197 |
+
def log(self, session_id, message):
|
198 |
+
"""Log a message to the session"""
|
199 |
+
return session_manager.add_message(session_id, message, agent_type=self.name)
|
200 |
+
|
201 |
+
async def process(self, session_id, data):
|
202 |
+
"""Process data with this agent - to be implemented by subclasses"""
|
203 |
+
raise NotImplementedError("Subclasses must implement this method")
|
204 |
+
|
205 |
+
|
206 |
+
class ImageProcessingAgent(BaseAgent):
|
207 |
+
"""Agent responsible for image processing and object recognition
|
208 |
+
|
209 |
+
This agent uses computer vision models to detect product type, brand, model,
|
210 |
+
and other visual characteristics from product images.
|
211 |
+
"""
|
212 |
+
|
213 |
+
def __init__(self, name="ImageProcessingAgent"):
|
214 |
+
super().__init__(name)
|
215 |
+
# Initialize vision models
|
216 |
+
self.models = self._load_vision_models()
|
217 |
+
|
218 |
+
def _load_vision_models(self):
|
219 |
+
"""Load vision models for product recognition"""
|
220 |
+
models = {}
|
221 |
+
|
222 |
+
# Try to load YOLO for object detection
|
223 |
+
try:
|
224 |
+
if YOLO is not None:
|
225 |
+
models["yolo"] = YOLO("yolov8n.pt")
|
226 |
+
except Exception as e:
|
227 |
+
print(f"Error loading YOLO: {e}")
|
228 |
+
|
229 |
+
# Try to load ViT for image classification
|
230 |
+
try:
|
231 |
+
if ViTImageProcessor is not None and ViTForImageClassification is not None:
|
232 |
+
models["vit_processor"] = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224")
|
233 |
+
models["vit_model"] = ViTForImageClassification.from_pretrained("google/vit-base-patch16-224")
|
234 |
+
except Exception as e:
|
235 |
+
print(f"Error loading ViT: {e}")
|
236 |
+
|
237 |
+
# Try to load CLIP for visual embedding
|
238 |
+
try:
|
239 |
+
if CLIPProcessor is not None and CLIPModel is not None:
|
240 |
+
models["clip_processor"] = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
241 |
+
models["clip_model"] = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
|
242 |
+
except Exception as e:
|
243 |
+
print(f"Error loading CLIP: {e}")
|
244 |
+
|
245 |
+
return models
|
246 |
+
|
247 |
+
def _process_with_yolo(self, image):
|
248 |
+
"""Process image with YOLO for object detection"""
|
249 |
+
if "yolo" not in self.models:
|
250 |
+
return {"error": "YOLO model not available"}
|
251 |
+
|
252 |
+
# Convert image to numpy array if it's a PIL image
|
253 |
+
if isinstance(image, Image.Image):
|
254 |
+
image_np = np.array(image)
|
255 |
+
else:
|
256 |
+
image_np = image
|
257 |
+
|
258 |
+
# Run inference
|
259 |
+
results = self.models["yolo"](image_np)
|
260 |
+
|
261 |
+
# Extract detection results
|
262 |
+
detections = []
|
263 |
+
for result in results:
|
264 |
+
boxes = result.boxes
|
265 |
+
for box in boxes:
|
266 |
+
class_id = int(box.cls[0].item())
|
267 |
+
class_name = result.names[class_id]
|
268 |
+
confidence = round(box.conf[0].item(), 2)
|
269 |
+
bbox = box.xyxy[0].tolist()
|
270 |
+
bbox = [round(x) for x in bbox]
|
271 |
+
|
272 |
+
detections.append({
|
273 |
+
"class": class_name,
|
274 |
+
"confidence": confidence,
|
275 |
+
"bbox": bbox
|
276 |
+
})
|
277 |
+
|
278 |
+
return {"detections": detections}
|
279 |
+
|
280 |
+
def _process_with_vit(self, image):
|
281 |
+
"""Process image with ViT for classification"""
|
282 |
+
if "vit_model" not in self.models or "vit_processor" not in self.models:
|
283 |
+
return {"error": "ViT model not available"}
|
284 |
+
|
285 |
+
# Prepare image for the model
|
286 |
+
inputs = self.models["vit_processor"](images=image, return_tensors="pt")
|
287 |
+
|
288 |
+
# Run inference
|
289 |
+
with torch.no_grad():
|
290 |
+
outputs = self.models["vit_model"](**inputs)
|
291 |
+
logits = outputs.logits
|
292 |
+
|
293 |
+
# Get top 5 predictions
|
294 |
+
probs = torch.nn.functional.softmax(logits, dim=-1)[0]
|
295 |
+
top5_prob, top5_indices = torch.topk(probs, 5)
|
296 |
+
|
297 |
+
results = []
|
298 |
+
for i, (prob, idx) in enumerate(zip(top5_prob, top5_indices)):
|
299 |
+
class_name = self.models["vit_model"].config.id2label[idx.item()]
|
300 |
+
results.append({
|
301 |
+
"rank": i+1,
|
302 |
+
"class": class_name,
|
303 |
+
"probability": round(prob.item(), 3)
|
304 |
+
})
|
305 |
+
|
306 |
+
return {"classifications": results}
|
307 |
+
|
308 |
+
def _extract_product_info_from_vision(self, image, results):
|
309 |
+
"""Extract product information using LLM and vision results"""
|
310 |
+
if self.llm is None:
|
311 |
+
# Provide a basic extraction based on detection results only
|
312 |
+
if "detections" in results and results["detections"]:
|
313 |
+
detections = results["detections"]
|
314 |
+
# Get the most confident detection
|
315 |
+
top_detection = max(detections, key=lambda x: x["confidence"])
|
316 |
+
return {
|
317 |
+
"product_type": top_detection["class"],
|
318 |
+
"confidence": top_detection["confidence"]
|
319 |
+
}
|
320 |
+
return {"product_type": "unknown"}
|
321 |
+
|
322 |
+
# If we have an LLM, we can do more sophisticated extraction
|
323 |
+
prompt = f"""Analyze this product image detection results and extract detailed product information.
|
324 |
+
Detection results: {json.dumps(results)}
|
325 |
+
|
326 |
+
Extract the following information in JSON format:
|
327 |
+
- product_type: The category of the product (car, smartphone, laptop, etc.)
|
328 |
+
- brand: The most likely brand of the product
|
329 |
+
- model: Any model information that can be determined
|
330 |
+
- color: The main color of the product
|
331 |
+
- key_features: List of notable visual features
|
332 |
+
"""
|
333 |
+
|
334 |
+
try:
|
335 |
+
response = self.llm(prompt)
|
336 |
+
# Try to parse as JSON
|
337 |
+
try:
|
338 |
+
extracted = json.loads(response)
|
339 |
+
return extracted
|
340 |
+
except json.JSONDecodeError:
|
341 |
+
# If LLM output is not valid JSON, extract key information using simple parsing
|
342 |
+
lines = response.split('\n')
|
343 |
+
extracted = {}
|
344 |
+
for line in lines:
|
345 |
+
if ':' in line:
|
346 |
+
key, value = line.split(':', 1)
|
347 |
+
key = key.strip().lower().replace(' ', '_')
|
348 |
+
value = value.strip().strip('"').strip("'")
|
349 |
+
if key and value:
|
350 |
+
extracted[key] = value
|
351 |
+
return extracted
|
352 |
+
except Exception as e:
|
353 |
+
print(f"Error extracting product info: {e}")
|
354 |
+
return {"product_type": "unknown", "error": str(e)}
|
355 |
+
|
356 |
+
async def process(self, session_id, data):
|
357 |
+
"""Process product images to identify products and extract features"""
|
358 |
+
self.log(session_id, "Starting image analysis to identify products...")
|
359 |
+
|
360 |
+
results = {}
|
361 |
+
product_info = {}
|
362 |
+
|
363 |
+
# Process each image if available
|
364 |
+
images = data.get("images", [])
|
365 |
+
for i, img in enumerate(images):
|
366 |
+
if img is None:
|
367 |
+
continue
|
368 |
+
|
369 |
+
image_key = f"image{i+1}"
|
370 |
+
self.log(session_id, f"Processing {image_key}...")
|
371 |
+
|
372 |
+
# Run object detection
|
373 |
+
yolo_results = self._process_with_yolo(img)
|
374 |
+
|
375 |
+
# Run classification
|
376 |
+
vit_results = self._process_with_vit(img)
|
377 |
+
|
378 |
+
# Combine results
|
379 |
+
vision_results = {
|
380 |
+
**yolo_results,
|
381 |
+
**vit_results
|
382 |
+
}
|
383 |
+
|
384 |
+
# Extract product information from vision results
|
385 |
+
info = self._extract_product_info_from_vision(img, vision_results)
|
386 |
+
|
387 |
+
self.log(session_id, f"Identified product in {image_key}: {info.get('product_type', 'unknown')}")
|
388 |
+
if "brand" in info:
|
389 |
+
self.log(session_id, f"Detected brand: {info['brand']}")
|
390 |
+
|
391 |
+
results[image_key] = {
|
392 |
+
"vision_results": vision_results,
|
393 |
+
"product_info": info
|
394 |
+
}
|
395 |
+
product_info[image_key] = info
|
396 |
+
|
397 |
+
self.log(session_id, "Image processing completed")
|
398 |
+
return {
|
399 |
+
"vision_results": results,
|
400 |
+
"product_info": product_info
|
401 |
+
}
|
402 |
+
|
403 |
+
|
404 |
+
class FeatureExtractionAgent(BaseAgent):
|
405 |
+
"""Agent responsible for extracting detailed product features and specifications
|
406 |
+
|
407 |
+
This agent analyzes image processing results and uses LLMs to extract detailed
|
408 |
+
product specifications, features, and technical details.
|
409 |
+
"""
|
410 |
+
|
411 |
+
def __init__(self, name="FeatureExtractionAgent"):
|
412 |
+
super().__init__(name)
|
413 |
+
|
414 |
+
def _extract_specifications(self, product_info):
|
415 |
+
"""Extract detailed specifications from product information"""
|
416 |
+
if self.llm is None:
|
417 |
+
# If no LLM is available, return basic specs from product info
|
418 |
+
return product_info
|
419 |
+
|
420 |
+
# Prepare prompt for specification extraction
|
421 |
+
product_type = product_info.get("product_type", "unknown")
|
422 |
+
prompt = f"""Based on this product information, generate a detailed list of specifications.
|
423 |
+
Product information: {json.dumps(product_info)}
|
424 |
+
|
425 |
+
For a {product_type}, extract or infer the following specifications in JSON format:
|
426 |
+
"""
|
427 |
+
|
428 |
+
# Add product-specific specification types based on product type
|
429 |
+
if "car" in product_type.lower() or "vehicle" in product_type.lower():
|
430 |
+
prompt += """
|
431 |
+
- make: The manufacturer of the car
|
432 |
+
- model: The model name
|
433 |
+
- year: Estimated year of manufacture
|
434 |
+
- body_type: The body style (sedan, SUV, etc.)
|
435 |
+
- fuel_type: The fuel type if identifiable
|
436 |
+
- engine: Engine specifications if identifiable
|
437 |
+
- color: Exterior color
|
438 |
+
- features: List of visible features
|
439 |
+
"""
|
440 |
+
elif "phone" in product_type.lower() or "smartphone" in product_type.lower():
|
441 |
+
prompt += """
|
442 |
+
- brand: The manufacturer
|
443 |
+
- model: The phone model
|
444 |
+
- screen_size: Estimated screen size
|
445 |
+
- camera: Visible camera specifications
|
446 |
+
- color: Device color
|
447 |
+
- generation: Device generation if identifiable
|
448 |
+
- features: List of visible features
|
449 |
+
"""
|
450 |
+
elif "laptop" in product_type.lower() or "computer" in product_type.lower():
|
451 |
+
prompt += """
|
452 |
+
- brand: The manufacturer
|
453 |
+
- model: The computer model
|
454 |
+
- screen_size: Estimated screen size
|
455 |
+
- form_factor: Laptop, desktop, all-in-one, etc.
|
456 |
+
- color: Device color
|
457 |
+
- features: List of visible features
|
458 |
+
"""
|
459 |
+
else:
|
460 |
+
# Generic product specifications
|
461 |
+
prompt += """
|
462 |
+
- brand: The manufacturer if identifiable
|
463 |
+
- model: The product model if identifiable
|
464 |
+
- color: Product color
|
465 |
+
- dimensions: Estimated dimensions
|
466 |
+
- features: List of visible features
|
467 |
+
- materials: Visible materials used
|
468 |
+
"""
|
469 |
+
|
470 |
+
try:
|
471 |
+
response = self.llm(prompt)
|
472 |
+
# Try to parse as JSON
|
473 |
+
try:
|
474 |
+
specs = json.loads(response)
|
475 |
+
return specs
|
476 |
+
except json.JSONDecodeError:
|
477 |
+
# If LLM output is not valid JSON, extract key information using simple parsing
|
478 |
+
lines = response.split('\n')
|
479 |
+
specs = {}
|
480 |
+
for line in lines:
|
481 |
+
if ':' in line:
|
482 |
+
key, value = line.split(':', 1)
|
483 |
+
key = key.strip().lower().replace(' ', '_').strip('-').strip('_')
|
484 |
+
value = value.strip().strip('"').strip("'")
|
485 |
+
if key and value:
|
486 |
+
specs[key] = value
|
487 |
+
return specs
|
488 |
+
except Exception as e:
|
489 |
+
print(f"Error extracting specifications: {e}")
|
490 |
+
return {"error": str(e)}
|
491 |
+
|
492 |
+
def _get_feature_highlights(self, specs):
|
493 |
+
"""Extract key feature highlights from specifications"""
|
494 |
+
if not specs or not isinstance(specs, dict):
|
495 |
+
return []
|
496 |
+
|
497 |
+
highlights = []
|
498 |
+
|
499 |
+
# Extract key features based on product type
|
500 |
+
product_type = specs.get("product_type", "").lower()
|
501 |
+
|
502 |
+
if "car" in product_type or "vehicle" in product_type:
|
503 |
+
# Highlight car features
|
504 |
+
if "make" in specs and "model" in specs:
|
505 |
+
highlights.append(f"{specs['make']} {specs['model']}")
|
506 |
+
if "year" in specs:
|
507 |
+
highlights.append(f"{specs['year']} model")
|
508 |
+
if "engine" in specs:
|
509 |
+
highlights.append(f"Engine: {specs['engine']}")
|
510 |
+
if "body_type" in specs:
|
511 |
+
highlights.append(f"{specs['body_type']} body style")
|
512 |
+
|
513 |
+
elif "phone" in product_type or "smartphone" in product_type:
|
514 |
+
# Highlight phone features
|
515 |
+
if "brand" in specs and "model" in specs:
|
516 |
+
highlights.append(f"{specs['brand']} {specs['model']}")
|
517 |
+
if "screen_size" in specs:
|
518 |
+
highlights.append(f"{specs['screen_size']} display")
|
519 |
+
if "camera" in specs:
|
520 |
+
highlights.append(f"Camera: {specs['camera']}")
|
521 |
+
|
522 |
+
elif "laptop" in product_type or "computer" in product_type:
|
523 |
+
# Highlight laptop features
|
524 |
+
if "brand" in specs and "model" in specs:
|
525 |
+
highlights.append(f"{specs['brand']} {specs['model']}")
|
526 |
+
if "screen_size" in specs:
|
527 |
+
highlights.append(f"{specs['screen_size']} display")
|
528 |
+
|
529 |
+
# Generic highlights for any product
|
530 |
+
if "features" in specs:
|
531 |
+
if isinstance(specs["features"], list):
|
532 |
+
highlights.extend(specs["features"][:3]) # Top 3 features
|
533 |
+
elif isinstance(specs["features"], str):
|
534 |
+
features = specs["features"].split(",")
|
535 |
+
highlights.extend([f.strip() for f in features[:3]]) # Top 3 features
|
536 |
+
|
537 |
+
# Add color as a feature if available
|
538 |
+
if "color" in specs:
|
539 |
+
highlights.append(f"{specs['color']} color")
|
540 |
+
|
541 |
+
return highlights
|
542 |
+
|
543 |
+
async def process(self, session_id, data):
|
544 |
+
"""Process product information to extract detailed specifications"""
|
545 |
+
self.log(session_id, "Extracting detailed product specifications...")
|
546 |
+
|
547 |
+
results = {}
|
548 |
+
product_info = data.get("product_info", {})
|
549 |
+
|
550 |
+
if not product_info:
|
551 |
+
self.log(session_id, "No product information available for specification extraction")
|
552 |
+
return {"specifications": {}}
|
553 |
+
|
554 |
+
# Process each product
|
555 |
+
for key, info in product_info.items():
|
556 |
+
self.log(session_id, f"Extracting specifications for {key}...")
|
557 |
+
|
558 |
+
# Extract detailed specifications
|
559 |
+
specs = self._extract_specifications(info)
|
560 |
+
|
561 |
+
# Get feature highlights
|
562 |
+
highlights = self._get_feature_highlights(specs)
|
563 |
+
|
564 |
+
# Log results
|
565 |
+
if highlights:
|
566 |
+
self.log(session_id, f"Key features for {key}: {', '.join(highlights[:3])}")
|
567 |
+
|
568 |
+
results[key] = {
|
569 |
+
"specifications": specs,
|
570 |
+
"highlights": highlights
|
571 |
+
}
|
572 |
+
|
573 |
+
self.log(session_id, "Feature extraction completed")
|
574 |
+
return {"specifications": results}
|
575 |
+
|
576 |
+
|
577 |
+
class ComparisonAgent(BaseAgent):
|
578 |
+
"""Agent responsible for comparing products based on their specifications
|
579 |
+
|
580 |
+
This agent analyzes the specifications of multiple products and identifies
|
581 |
+
the key differences, advantages, and disadvantages between them.
|
582 |
+
"""
|
583 |
+
|
584 |
+
def __init__(self, name="ComparisonAgent"):
|
585 |
+
super().__init__(name)
|
586 |
+
|
587 |
+
def _compare_specifications(self, specs1, specs2):
|
588 |
+
"""Compare two sets of product specifications"""
|
589 |
+
if not specs1 or not specs2:
|
590 |
+
return {"error": "Insufficient specification data for comparison"}
|
591 |
+
|
592 |
+
# If we have an LLM, use it for more sophisticated comparison
|
593 |
+
if self.llm is not None:
|
594 |
+
prompt = f"""Compare these two products based on their specifications.
|
595 |
+
Product 1: {json.dumps(specs1)}
|
596 |
+
Product 2: {json.dumps(specs2)}
|
597 |
+
|
598 |
+
Provide a detailed comparison in JSON format with the following structure:
|
599 |
+
{{"differences": [...], "product1_advantages": [...], "product2_advantages": [...], "summary": "..."}}
|
600 |
+
|
601 |
+
- differences: List key differences between the products
|
602 |
+
- product1_advantages: List advantages of Product 1 over Product 2
|
603 |
+
- product2_advantages: List advantages of Product 2 over Product 1
|
604 |
+
- summary: A concise summary of the comparison
|
605 |
+
"""
|
606 |
+
|
607 |
+
try:
|
608 |
+
response = self.llm(prompt)
|
609 |
+
# Try to parse as JSON
|
610 |
+
try:
|
611 |
+
comparison = json.loads(response)
|
612 |
+
return comparison
|
613 |
+
except json.JSONDecodeError:
|
614 |
+
# If LLM output is not valid JSON, extract key sections using simple parsing
|
615 |
+
sections = {}
|
616 |
+
current_section = None
|
617 |
+
section_content = []
|
618 |
+
|
619 |
+
lines = response.split('\n')
|
620 |
+
for line in lines:
|
621 |
+
line = line.strip()
|
622 |
+
if not line:
|
623 |
+
continue
|
624 |
+
|
625 |
+
if line.endswith(":") and not line.startswith("-"):
|
626 |
+
# This is a section header
|
627 |
+
if current_section and section_content:
|
628 |
+
sections[current_section] = section_content
|
629 |
+
section_content = []
|
630 |
+
|
631 |
+
current_section = line.strip(":").lower().replace(" ", "_")
|
632 |
+
elif line.startswith("-") and current_section:
|
633 |
+
# This is a list item
|
634 |
+
section_content.append(line.strip("- "))
|
635 |
+
elif current_section:
|
636 |
+
# This is content for the current section
|
637 |
+
section_content.append(line)
|
638 |
+
|
639 |
+
# Add the last section
|
640 |
+
if current_section and section_content:
|
641 |
+
sections[current_section] = section_content
|
642 |
+
|
643 |
+
return sections
|
644 |
+
except Exception as e:
|
645 |
+
print(f"Error in LLM comparison: {e}")
|
646 |
+
# Fall back to simple comparison
|
647 |
+
pass
|
648 |
+
|
649 |
+
# Simple comparison logic as fallback
|
650 |
+
differences = []
|
651 |
+
product1_advantages = []
|
652 |
+
product2_advantages = []
|
653 |
+
|
654 |
+
# Identify common keys to compare
|
655 |
+
all_keys = set(list(specs1.keys()) + list(specs2.keys()))
|
656 |
+
|
657 |
+
# Exclude utility keys like 'error'
|
658 |
+
exclude_keys = {'error', 'product_type', 'confidence'}
|
659 |
+
compare_keys = all_keys - exclude_keys
|
660 |
+
|
661 |
+
for key in compare_keys:
|
662 |
+
val1 = specs1.get(key)
|
663 |
+
val2 = specs2.get(key)
|
664 |
+
|
665 |
+
if val1 is None and val2 is not None:
|
666 |
+
differences.append(f"Product 2 has {key}: {val2}, but Product 1 doesn't")
|
667 |
+
product2_advantages.append(f"Has {key}: {val2}")
|
668 |
+
elif val1 is not None and val2 is None:
|
669 |
+
differences.append(f"Product 1 has {key}: {val1}, but Product 2 doesn't")
|
670 |
+
product1_advantages.append(f"Has {key}: {val1}")
|
671 |
+
elif val1 != val2:
|
672 |
+
differences.append(f"Different {key}: Product 1 has {val1}, Product 2 has {val2}")
|
673 |
+
|
674 |
+
# Try to determine advantages based on common metrics
|
675 |
+
if key in ['price', 'weight']:
|
676 |
+
# Lower is generally better
|
677 |
+
try:
|
678 |
+
num1 = float(str(val1).split()[0])
|
679 |
+
num2 = float(str(val2).split()[0])
|
680 |
+
if num1 < num2:
|
681 |
+
product1_advantages.append(f"Lower {key}: {val1}")
|
682 |
+
else:
|
683 |
+
product2_advantages.append(f"Lower {key}: {val2}")
|
684 |
+
except (ValueError, IndexError):
|
685 |
+
pass
|
686 |
+
elif key in ['screen_size', 'storage', 'memory', 'ram', 'battery', 'capacity']:
|
687 |
+
# Higher is generally better
|
688 |
+
try:
|
689 |
+
num1 = float(str(val1).split()[0])
|
690 |
+
num2 = float(str(val2).split()[0])
|
691 |
+
if num1 > num2:
|
692 |
+
product1_advantages.append(f"Higher {key}: {val1}")
|
693 |
+
else:
|
694 |
+
product2_advantages.append(f"Higher {key}: {val2}")
|
695 |
+
except (ValueError, IndexError):
|
696 |
+
pass
|
697 |
+
|
698 |
+
# Create a simple summary
|
699 |
+
product1_type = specs1.get('product_type', 'Product 1')
|
700 |
+
product2_type = specs2.get('product_type', 'Product 2')
|
701 |
+
|
702 |
+
summary = f"Comparison between {product1_type} and {product2_type} reveals {len(differences)} key differences."
|
703 |
+
|
704 |
+
if len(product1_advantages) > len(product2_advantages):
|
705 |
+
summary += f" {product1_type} appears to have more advantages."
|
706 |
+
elif len(product2_advantages) > len(product1_advantages):
|
707 |
+
summary += f" {product2_type} appears to have more advantages."
|
708 |
+
else:
|
709 |
+
summary += " Both products have similar number of advantages."
|
710 |
+
|
711 |
+
return {
|
712 |
+
"differences": differences,
|
713 |
+
"product1_advantages": product1_advantages,
|
714 |
+
"product2_advantages": product2_advantages,
|
715 |
+
"summary": summary
|
716 |
+
}
|
717 |
+
|
718 |
+
async def process(self, session_id, data):
|
719 |
+
"""Compare products based on their specifications"""
|
720 |
+
self.log(session_id, "Starting product comparison analysis...")
|
721 |
+
|
722 |
+
specifications = data.get("specifications", {})
|
723 |
+
if len(specifications) < 2:
|
724 |
+
self.log(session_id, "Not enough products to compare")
|
725 |
+
return {"comparison": {"error": "Need at least two products to compare"}}
|
726 |
+
|
727 |
+
# Get the product keys (image1, image2, etc.)
|
728 |
+
product_keys = list(specifications.keys())
|
729 |
+
|
730 |
+
if len(product_keys) > 2:
|
731 |
+
self.log(session_id, f"Found {len(product_keys)} products, comparing the first two only")
|
732 |
+
product_keys = product_keys[:2]
|
733 |
+
|
734 |
+
# Get the specifications for each product
|
735 |
+
product1_specs = specifications.get(product_keys[0], {}).get("specifications", {})
|
736 |
+
product2_specs = specifications.get(product_keys[1], {}).get("specifications", {})
|
737 |
+
|
738 |
+
# Perform comparison
|
739 |
+
comparison = self._compare_specifications(product1_specs, product2_specs)
|
740 |
+
|
741 |
+
# Log comparison results
|
742 |
+
if "differences" in comparison:
|
743 |
+
num_diff = len(comparison["differences"])
|
744 |
+
self.log(session_id, f"Found {num_diff} key differences between the products")
|
745 |
+
|
746 |
+
# Log a few example differences
|
747 |
+
if num_diff > 0:
|
748 |
+
for i, diff in enumerate(comparison["differences"][:3]):
|
749 |
+
self.log(session_id, f"Difference {i+1}: {diff}")
|
750 |
+
|
751 |
+
# Log summary if available
|
752 |
+
if "summary" in comparison:
|
753 |
+
self.log(session_id, f"Comparison summary: {comparison['summary']}")
|
754 |
+
|
755 |
+
self.log(session_id, "Comparison analysis completed")
|
756 |
+
return {
|
757 |
+
"comparison": comparison,
|
758 |
+
"product_keys": product_keys
|
759 |
+
}
|
760 |
+
|
761 |
+
|
762 |
+
class RecommendationAgent(BaseAgent):
|
763 |
+
"""Agent responsible for providing purchase recommendations
|
764 |
+
|
765 |
+
This agent analyzes product comparisons and provides personalized recommendations
|
766 |
+
based on the user's needs and preferences.
|
767 |
+
"""
|
768 |
+
|
769 |
+
def __init__(self, name="RecommendationAgent"):
|
770 |
+
super().__init__(name)
|
771 |
+
|
772 |
+
def _generate_recommendation(self, comparison, product_keys, specifications):
|
773 |
+
"""Generate a purchase recommendation based on comparison results"""
|
774 |
+
if not comparison or "error" in comparison:
|
775 |
+
return {
|
776 |
+
"recommendation": "Insufficient data to make a recommendation",
|
777 |
+
"reason": "Could not compare the products",
|
778 |
+
"confidence": 0.0
|
779 |
+
}
|
780 |
+
|
781 |
+
# Get product information
|
782 |
+
product1_key = product_keys[0]
|
783 |
+
product2_key = product_keys[1]
|
784 |
+
product1_specs = specifications.get(product1_key, {}).get("specifications", {})
|
785 |
+
product2_specs = specifications.get(product2_key, {}).get("specifications", {})
|
786 |
+
|
787 |
+
product1_type = product1_specs.get("product_type", product1_key)
|
788 |
+
product2_type = product2_specs.get("product_type", product2_key)
|
789 |
+
|
790 |
+
# If we have an LLM, use it for more sophisticated recommendation
|
791 |
+
if self.llm is not None:
|
792 |
+
prompt = f"""Based on this product comparison, provide a purchase recommendation.
|
793 |
+
Product 1: {json.dumps(product1_specs)}
|
794 |
+
Product 2: {json.dumps(product2_specs)}
|
795 |
+
Comparison: {json.dumps(comparison)}
|
796 |
+
|
797 |
+
Provide a recommendation in JSON format with the following structure:
|
798 |
+
{{"recommended_product": "1 or 2", "recommendation": "...", "reason": "...", "confidence": 0.0-1.0, "use_cases": [...]}}
|
799 |
+
|
800 |
+
- recommended_product: Either "1" or "2" indicating which product is recommended
|
801 |
+
- recommendation: A concise recommendation statement
|
802 |
+
- reason: The main reason for the recommendation
|
803 |
+
- confidence: A confidence score between 0 and 1
|
804 |
+
- use_cases: List of ideal use cases for the recommended product
|
805 |
+
"""
|
806 |
+
|
807 |
+
try:
|
808 |
+
response = self.llm(prompt)
|
809 |
+
# Try to parse as JSON
|
810 |
+
try:
|
811 |
+
recommendation = json.loads(response)
|
812 |
+
return recommendation
|
813 |
+
except json.JSONDecodeError:
|
814 |
+
# If LLM output is not valid JSON, extract key information using simple parsing
|
815 |
+
lines = response.split('\n')
|
816 |
+
recommendation = {}
|
817 |
+
|
818 |
+
# Look for recommendation indicator
|
819 |
+
for line in lines:
|
820 |
+
if "recommended product" in line.lower() and ("1" in line or "2" in line):
|
821 |
+
recommendation["recommended_product"] = "1" if "1" in line else "2"
|
822 |
+
elif "recommendation" in line.lower() and ":" in line:
|
823 |
+
recommendation["recommendation"] = line.split(":", 1)[1].strip().strip('"')
|
824 |
+
elif "reason" in line.lower() and ":" in line:
|
825 |
+
recommendation["reason"] = line.split(":", 1)[1].strip().strip('"')
|
826 |
+
elif "confidence" in line.lower() and ":" in line:
|
827 |
+
try:
|
828 |
+
conf_str = line.split(":", 1)[1].strip()
|
829 |
+
conf = float(conf_str)
|
830 |
+
recommendation["confidence"] = conf
|
831 |
+
except ValueError:
|
832 |
+
recommendation["confidence"] = 0.5
|
833 |
+
elif "use cases" in line.lower() and ":" in line:
|
834 |
+
use_cases = line.split(":", 1)[1].strip().strip('"')
|
835 |
+
recommendation["use_cases"] = [uc.strip() for uc in use_cases.split(",")]
|
836 |
+
|
837 |
+
return recommendation
|
838 |
+
except Exception as e:
|
839 |
+
print(f"Error in LLM recommendation: {e}")
|
840 |
+
# Fall back to simple recommendation
|
841 |
+
pass
|
842 |
+
|
843 |
+
# Simple recommendation logic as fallback
|
844 |
+
product1_advantages = comparison.get("product1_advantages", [])
|
845 |
+
product2_advantages = comparison.get("product2_advantages", [])
|
846 |
+
|
847 |
+
# Count advantages
|
848 |
+
p1_count = len(product1_advantages)
|
849 |
+
p2_count = len(product2_advantages)
|
850 |
+
|
851 |
+
# Simple confidence calculation
|
852 |
+
total_advantages = p1_count + p2_count
|
853 |
+
if total_advantages == 0:
|
854 |
+
confidence = 0.5 # Can't determine
|
855 |
+
else:
|
856 |
+
# Maximum confidence is 0.95, minimum is 0.55
|
857 |
+
max_confidence = 0.95
|
858 |
+
min_confidence = 0.55
|
859 |
+
base_confidence = max(p1_count, p2_count) / total_advantages if total_advantages > 0 else 0.5
|
860 |
+
confidence = min_confidence + base_confidence * (max_confidence - min_confidence)
|
861 |
+
|
862 |
+
# Make recommendation
|
863 |
+
if p1_count > p2_count:
|
864 |
+
recommended = "1"
|
865 |
+
recommendation = f"{product1_type} is recommended over {product2_type}"
|
866 |
+
reason = f"It has {p1_count} advantages compared to {p2_count} for the alternative"
|
867 |
+
elif p2_count > p1_count:
|
868 |
+
recommended = "2"
|
869 |
+
recommendation = f"{product2_type} is recommended over {product1_type}"
|
870 |
+
reason = f"It has {p2_count} advantages compared to {p1_count} for the alternative"
|
871 |
+
else:
|
872 |
+
# Equal advantages, slight preference for first product
|
873 |
+
recommended = "1"
|
874 |
+
recommendation = f"Both {product1_type} and {product2_type} appear equally matched"
|
875 |
+
reason = "Consider your specific needs as both have similar advantages"
|
876 |
+
confidence = 0.5 # Equal confidence
|
877 |
+
|
878 |
+
# Determine use cases based on advantages
|
879 |
+
use_cases = []
|
880 |
+
|
881 |
+
if recommended == "1" and product1_advantages:
|
882 |
+
# Extract use cases from product 1's advantages
|
883 |
+
use_cases = [f"When {adv.lower()}" for adv in product1_advantages[:3]]
|
884 |
+
elif recommended == "2" and product2_advantages:
|
885 |
+
# Extract use cases from product 2's advantages
|
886 |
+
use_cases = [f"When {adv.lower()}" for adv in product2_advantages[:3]]
|
887 |
+
|
888 |
+
# Add generic use case if none found
|
889 |
+
if not use_cases:
|
890 |
+
use_cases = [f"General {product1_type if recommended == '1' else product2_type} usage"]
|
891 |
+
|
892 |
+
return {
|
893 |
+
"recommended_product": recommended,
|
894 |
+
"recommendation": recommendation,
|
895 |
+
"reason": reason,
|
896 |
+
"confidence": round(confidence, 2),
|
897 |
+
"use_cases": use_cases
|
898 |
+
}
|
899 |
+
|
900 |
+
async def process(self, session_id, data):
|
901 |
+
"""Generate purchase recommendations based on product comparison"""
|
902 |
+
self.log(session_id, "Generating product recommendations...")
|
903 |
+
|
904 |
+
# Get comparison data
|
905 |
+
comparison = data.get("comparison", {})
|
906 |
+
product_keys = data.get("product_keys", [])
|
907 |
+
specifications = data.get("specifications", {})
|
908 |
+
|
909 |
+
if not comparison or not product_keys or len(product_keys) < 2:
|
910 |
+
self.log(session_id, "Insufficient data for recommendation")
|
911 |
+
return {"recommendation": {"error": "Not enough data to generate recommendation"}}
|
912 |
+
|
913 |
+
# Generate recommendation
|
914 |
+
recommendation = self._generate_recommendation(comparison, product_keys, specifications)
|
915 |
+
|
916 |
+
# Log recommendation
|
917 |
+
self.log(session_id, f"Recommendation: {recommendation.get('recommendation', 'No recommendation')}")
|
918 |
+
self.log(session_id, f"Reason: {recommendation.get('reason', 'No reason provided')}")
|
919 |
+
|
920 |
+
# Log confidence
|
921 |
+
confidence = recommendation.get('confidence', 0)
|
922 |
+
confidence_percent = f"{int(confidence * 100)}%"
|
923 |
+
self.log(session_id, f"Confidence in recommendation: {confidence_percent}")
|
924 |
+
|
925 |
+
self.log(session_id, "Recommendation generation completed")
|
926 |
+
return {"recommendation": recommendation}
|
927 |
+
|
928 |
+
|
929 |
+
class ProductComparisonCoordinator:
|
930 |
+
"""Main coordinator for the product comparison multi-agent system
|
931 |
+
|
932 |
+
This class orchestrates the entire product comparison workflow by managing
|
933 |
+
all the specialized agents and their interactions.
|
934 |
+
"""
|
935 |
+
|
936 |
+
def __init__(self):
|
937 |
+
# Initialize all agents
|
938 |
+
self.image_processor = ImageProcessingAgent()
|
939 |
+
self.feature_extractor = FeatureExtractionAgent()
|
940 |
+
self.comparison_agent = ComparisonAgent()
|
941 |
+
self.recommendation_agent = RecommendationAgent()
|
942 |
+
|
943 |
+
async def process_images(self, session_id, images, session_metadata=None):
|
944 |
+
"""Process images through the entire multi-agent workflow
|
945 |
+
|
946 |
+
Args:
|
947 |
+
session_id: Unique session identifier
|
948 |
+
images: List of image data (PIL Images or numpy arrays)
|
949 |
+
session_metadata: Optional dictionary with additional session information
|
950 |
+
|
951 |
+
Returns:
|
952 |
+
Dictionary containing the final analysis results
|
953 |
+
"""
|
954 |
+
# Initialize session if it doesn't exist
|
955 |
+
if session_manager.get_status(session_id) is None:
|
956 |
+
session_manager.create_session(session_id)
|
957 |
+
|
958 |
+
# Set default metadata if not provided
|
959 |
+
if session_metadata is None:
|
960 |
+
session_metadata = {}
|
961 |
+
|
962 |
+
# Get analysis type from metadata
|
963 |
+
analysis_type = session_metadata.get('analysis_type', 'info')
|
964 |
+
|
965 |
+
session_manager.set_status(session_id, "processing")
|
966 |
+
session_manager.add_message(session_id, f"Starting product {analysis_type} analysis")
|
967 |
+
|
968 |
+
try:
|
969 |
+
# Step 1: Process images with Image Processing Agent
|
970 |
+
session_manager.add_message(session_id, "Step 1: Analyzing product images...")
|
971 |
+
image_results = await self.image_processor.process(session_id, {"images": images})
|
972 |
+
|
973 |
+
# Check if we have enough products to compare
|
974 |
+
product_info = image_results.get("product_info", {})
|
975 |
+
if len(product_info) < 1:
|
976 |
+
session_manager.add_message(session_id, "Error: No products detected in images")
|
977 |
+
session_manager.set_status(session_id, "error")
|
978 |
+
return {"error": "No products detected in images"}
|
979 |
+
|
980 |
+
# Step 2: Extract features with Feature Extraction Agent
|
981 |
+
session_manager.add_message(session_id, "Step 2: Extracting product specifications...")
|
982 |
+
feature_results = await self.feature_extractor.process(session_id, image_results)
|
983 |
+
|
984 |
+
# Step 3: Compare products with Comparison Agent if we have multiple products
|
985 |
+
comparison_results = {}
|
986 |
+
if len(product_info) >= 2:
|
987 |
+
session_manager.add_message(session_id, "Step 3: Comparing products...")
|
988 |
+
comparison_results = await self.comparison_agent.process(
|
989 |
+
session_id,
|
990 |
+
{**feature_results, "specifications": feature_results.get("specifications", {})}
|
991 |
+
)
|
992 |
+
|
993 |
+
# Step 4: Generate recommendation with Recommendation Agent
|
994 |
+
session_manager.add_message(session_id, "Step 4: Generating purchase recommendation...")
|
995 |
+
recommendation_results = await self.recommendation_agent.process(
|
996 |
+
session_id,
|
997 |
+
{**comparison_results, "specifications": feature_results.get("specifications", {})}
|
998 |
+
)
|
999 |
+
else:
|
1000 |
+
session_manager.add_message(session_id, "Skipping comparison: Only one product detected")
|
1001 |
+
comparison_results = {"comparison": {"error": "Need at least two products to compare"}}
|
1002 |
+
recommendation_results = {"recommendation": {"error": "Need at least two products to compare"}}
|
1003 |
+
|
1004 |
+
# Tailor results based on analysis type
|
1005 |
+
final_results = {
|
1006 |
+
"status": "completed"
|
1007 |
+
}
|
1008 |
+
|
1009 |
+
# Include results based on analysis type
|
1010 |
+
if analysis_type == 'info':
|
1011 |
+
final_results["productInfo"] = image_results.get("product_info", {})
|
1012 |
+
final_results["specifications"] = feature_results.get("specifications", {})
|
1013 |
+
|
1014 |
+
elif analysis_type == 'compare':
|
1015 |
+
final_results["comparison"] = comparison_results.get("comparison", {})
|
1016 |
+
final_results["productInfo"] = image_results.get("product_info", {})
|
1017 |
+
|
1018 |
+
elif analysis_type == 'value':
|
1019 |
+
# Value analysis combines specs and comparison data
|
1020 |
+
final_results["valueAnalysis"] = {
|
1021 |
+
"priceA": "$" + str(1000 + int(hash(str(session_id)) % 500)), # Mock price for demo
|
1022 |
+
"valueScoreA": 7 + (int(hash(str(session_id)) % 3)), # Mock score between 7-9
|
1023 |
+
"analysis": "Based on the specifications and market positioning, this product offers good value for money."
|
1024 |
+
}
|
1025 |
+
if len(product_info) >= 2:
|
1026 |
+
final_results["valueAnalysis"]["priceB"] = "$" + str(1200 + int(hash(str(session_id + "B")) % 500))
|
1027 |
+
final_results["valueAnalysis"]["valueScoreB"] = 6 + (int(hash(str(session_id + "B")) % 4))
|
1028 |
+
|
1029 |
+
elif analysis_type == 'recommend':
|
1030 |
+
if recommendation_results.get("recommendation", {}).get("error"):
|
1031 |
+
# Create a mock recommendation if only one product
|
1032 |
+
if len(product_info) == 1:
|
1033 |
+
product_name = next(iter(product_info.values())).get("name", "Product")
|
1034 |
+
final_results["recommendation"] = {
|
1035 |
+
"recommendedProduct": product_name,
|
1036 |
+
"reason": "This is the only product analyzed and appears to meet standard quality benchmarks.",
|
1037 |
+
"confidence": 0.85,
|
1038 |
+
"alternatives": [
|
1039 |
+
{"name": "Similar model with higher storage", "reason": "If you need more storage capacity"},
|
1040 |
+
{"name": "Budget alternative", "reason": "If price is your primary concern"}
|
1041 |
+
],
|
1042 |
+
"buyingTips": [
|
1043 |
+
"Wait for seasonal sales for the best price",
|
1044 |
+
"Check for warranty terms before purchasing"
|
1045 |
+
]
|
1046 |
+
}
|
1047 |
+
else:
|
1048 |
+
final_results["recommendation"] = recommendation_results.get("recommendation", {})
|
1049 |
+
else:
|
1050 |
+
final_results["recommendation"] = recommendation_results.get("recommendation", {})
|
1051 |
+
|
1052 |
+
# Always include basic product info and vision results for context
|
1053 |
+
final_results["vision_results"] = image_results.get("vision_results", {})
|
1054 |
+
if "productInfo" not in final_results: # Don't duplicate if already added
|
1055 |
+
final_results["productInfo"] = image_results.get("product_info", {})
|
1056 |
+
|
1057 |
+
# Set final results in session manager
|
1058 |
+
session_manager.set_final_result(session_id, final_results)
|
1059 |
+
session_manager.set_status(session_id, "completed")
|
1060 |
+
session_manager.add_message(session_id, "Product comparison analysis completed successfully")
|
1061 |
+
|
1062 |
+
return final_results
|
1063 |
+
|
1064 |
+
except Exception as e:
|
1065 |
+
error_msg = f"Error during product comparison: {str(e)}"
|
1066 |
+
session_manager.add_message(session_id, error_msg)
|
1067 |
+
session_manager.set_status(session_id, "error")
|
1068 |
+
return {"error": error_msg}
|
1069 |
+
|
1070 |
+
def get_session_messages(self, session_id):
|
1071 |
+
"""Get all messages for a session"""
|
1072 |
+
return session_manager.get_messages(session_id)
|
1073 |
+
|
1074 |
+
def get_session_result(self, session_id):
|
1075 |
+
"""Get the final result for a session"""
|
1076 |
+
return session_manager.get_final_result(session_id)
|
1077 |
+
|
1078 |
+
def get_session_status(self, session_id):
|
1079 |
+
"""Get the status of a session"""
|
1080 |
+
return session_manager.get_status(session_id)
|
1081 |
+
|
1082 |
+
|
1083 |
+
# Helper function to create a coordinator instance
|
1084 |
+
def get_product_comparison_coordinator():
|
1085 |
+
"""Get a singleton instance of the ProductComparisonCoordinator"""
|
1086 |
+
if not hasattr(get_product_comparison_coordinator, "_instance"):
|
1087 |
+
get_product_comparison_coordinator._instance = ProductComparisonCoordinator()
|
1088 |
+
return get_product_comparison_coordinator._instance
|
1089 |
+
|
1090 |
+
|
1091 |
+
# Helper function to convert base64 image data to PIL Image
|
1092 |
+
def decode_base64_image(base64_data):
|
1093 |
+
"""Convert base64 image data to PIL Image"""
|
1094 |
+
try:
|
1095 |
+
# Check if the base64 data includes a data URL prefix
|
1096 |
+
if base64_data.startswith('data:image'):
|
1097 |
+
# Extract the actual base64 data after the comma
|
1098 |
+
base64_data = base64_data.split(',', 1)[1]
|
1099 |
+
|
1100 |
+
# Decode base64 data
|
1101 |
+
image_data = base64.b64decode(base64_data)
|
1102 |
+
|
1103 |
+
# Convert to PIL Image
|
1104 |
+
image = Image.open(BytesIO(image_data))
|
1105 |
+
return image
|
1106 |
+
except Exception as e:
|
1107 |
+
print(f"Error decoding base64 image: {e}")
|
1108 |
+
return None
|
static/asset-manifest.json
CHANGED
@@ -1,24 +1,24 @@
|
|
1 |
{
|
2 |
"files": {
|
3 |
-
"main.css": "/static/css/main.
|
4 |
-
"main.js": "/static/js/main.
|
5 |
-
"main.js.map": "/static/js/main.
|
6 |
"runtime-main.js": "/static/js/runtime-main.25710301.js",
|
7 |
"runtime-main.js.map": "/static/js/runtime-main.25710301.js.map",
|
8 |
-
"static/js/2.
|
9 |
-
"static/js/2.
|
10 |
"static/js/3.9013e23f.chunk.js": "/static/js/3.9013e23f.chunk.js",
|
11 |
"static/js/3.9013e23f.chunk.js.map": "/static/js/3.9013e23f.chunk.js.map",
|
12 |
"index.html": "/index.html",
|
13 |
-
"precache-manifest.
|
14 |
"service-worker.js": "/service-worker.js",
|
15 |
-
"static/css/main.
|
16 |
-
"static/js/2.
|
17 |
},
|
18 |
"entrypoints": [
|
19 |
"static/js/runtime-main.25710301.js",
|
20 |
-
"static/js/2.
|
21 |
-
"static/css/main.
|
22 |
-
"static/js/main.
|
23 |
]
|
24 |
}
|
|
|
1 |
{
|
2 |
"files": {
|
3 |
+
"main.css": "/static/css/main.59c2a54e.chunk.css",
|
4 |
+
"main.js": "/static/js/main.db88b5b1.chunk.js",
|
5 |
+
"main.js.map": "/static/js/main.db88b5b1.chunk.js.map",
|
6 |
"runtime-main.js": "/static/js/runtime-main.25710301.js",
|
7 |
"runtime-main.js.map": "/static/js/runtime-main.25710301.js.map",
|
8 |
+
"static/js/2.abf91741.chunk.js": "/static/js/2.abf91741.chunk.js",
|
9 |
+
"static/js/2.abf91741.chunk.js.map": "/static/js/2.abf91741.chunk.js.map",
|
10 |
"static/js/3.9013e23f.chunk.js": "/static/js/3.9013e23f.chunk.js",
|
11 |
"static/js/3.9013e23f.chunk.js.map": "/static/js/3.9013e23f.chunk.js.map",
|
12 |
"index.html": "/index.html",
|
13 |
+
"precache-manifest.653a7aee73b10eaaa2fae0610652f6fa.js": "/precache-manifest.653a7aee73b10eaaa2fae0610652f6fa.js",
|
14 |
"service-worker.js": "/service-worker.js",
|
15 |
+
"static/css/main.59c2a54e.chunk.css.map": "/static/css/main.59c2a54e.chunk.css.map",
|
16 |
+
"static/js/2.abf91741.chunk.js.LICENSE.txt": "/static/js/2.abf91741.chunk.js.LICENSE.txt"
|
17 |
},
|
18 |
"entrypoints": [
|
19 |
"static/js/runtime-main.25710301.js",
|
20 |
+
"static/js/2.abf91741.chunk.js",
|
21 |
+
"static/css/main.59c2a54e.chunk.css",
|
22 |
+
"static/js/main.db88b5b1.chunk.js"
|
23 |
]
|
24 |
}
|
static/index.html
CHANGED
@@ -1 +1 @@
|
|
1 |
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Multi-Model Object Detection Demo"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Vision Web App</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link href="/static/css/main.
|
|
|
1 |
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Multi-Model Object Detection Demo"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Vision Web App</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link href="/static/css/main.59c2a54e.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"9013e23f"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="/",i.oe=function(e){throw console.error(e),e};var a=this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([])</script><script src="/static/js/2.abf91741.chunk.js"></script><script src="/static/js/main.db88b5b1.chunk.js"></script></body></html>
|
static/precache-manifest.4683f6cd857bd9aa85304ac13a82d061.js
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
2 |
-
{
|
3 |
-
"revision": "d54f7d4646b42d5859a3c60dc17c2cbc",
|
4 |
-
"url": "/index.html"
|
5 |
-
},
|
6 |
-
{
|
7 |
-
"revision": "bb5a62e98165b2cca977",
|
8 |
-
"url": "/static/css/main.3b4eede1.chunk.css"
|
9 |
-
},
|
10 |
-
{
|
11 |
-
"revision": "ca6d8d363680165da358",
|
12 |
-
"url": "/static/js/2.9b5b80e8.chunk.js"
|
13 |
-
},
|
14 |
-
{
|
15 |
-
"revision": "89a1b2dcd30c03705b2bceeb141b76b6",
|
16 |
-
"url": "/static/js/2.9b5b80e8.chunk.js.LICENSE.txt"
|
17 |
-
},
|
18 |
-
{
|
19 |
-
"revision": "70d68b45d511e1e11f23",
|
20 |
-
"url": "/static/js/3.9013e23f.chunk.js"
|
21 |
-
},
|
22 |
-
{
|
23 |
-
"revision": "bb5a62e98165b2cca977",
|
24 |
-
"url": "/static/js/main.fc0e2dc7.chunk.js"
|
25 |
-
},
|
26 |
-
{
|
27 |
-
"revision": "d8c310b0ac7ffa6d8151",
|
28 |
-
"url": "/static/js/runtime-main.25710301.js"
|
29 |
-
}
|
30 |
-
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/{precache-manifest.2fad43b74e0c6c48ddb79498a374f3f4.js β precache-manifest.653a7aee73b10eaaa2fae0610652f6fa.js}
RENAMED
@@ -1,27 +1,27 @@
|
|
1 |
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
2 |
{
|
3 |
-
"revision": "
|
4 |
"url": "/index.html"
|
5 |
},
|
6 |
{
|
7 |
-
"revision": "
|
8 |
-
"url": "/static/css/main.
|
9 |
},
|
10 |
{
|
11 |
-
"revision": "
|
12 |
-
"url": "/static/js/2.
|
13 |
},
|
14 |
{
|
15 |
"revision": "89a1b2dcd30c03705b2bceeb141b76b6",
|
16 |
-
"url": "/static/js/2.
|
17 |
},
|
18 |
{
|
19 |
"revision": "70d68b45d511e1e11f23",
|
20 |
"url": "/static/js/3.9013e23f.chunk.js"
|
21 |
},
|
22 |
{
|
23 |
-
"revision": "
|
24 |
-
"url": "/static/js/main.
|
25 |
},
|
26 |
{
|
27 |
"revision": "d8c310b0ac7ffa6d8151",
|
|
|
1 |
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
2 |
{
|
3 |
+
"revision": "63e79b8c5a7091459c975e9ced1d4412",
|
4 |
"url": "/index.html"
|
5 |
},
|
6 |
{
|
7 |
+
"revision": "d435db4fd542a4ef6aa8",
|
8 |
+
"url": "/static/css/main.59c2a54e.chunk.css"
|
9 |
},
|
10 |
{
|
11 |
+
"revision": "a3aa56e81892b1b7fdee",
|
12 |
+
"url": "/static/js/2.abf91741.chunk.js"
|
13 |
},
|
14 |
{
|
15 |
"revision": "89a1b2dcd30c03705b2bceeb141b76b6",
|
16 |
+
"url": "/static/js/2.abf91741.chunk.js.LICENSE.txt"
|
17 |
},
|
18 |
{
|
19 |
"revision": "70d68b45d511e1e11f23",
|
20 |
"url": "/static/js/3.9013e23f.chunk.js"
|
21 |
},
|
22 |
{
|
23 |
+
"revision": "d435db4fd542a4ef6aa8",
|
24 |
+
"url": "/static/js/main.db88b5b1.chunk.js"
|
25 |
},
|
26 |
{
|
27 |
"revision": "d8c310b0ac7ffa6d8151",
|
static/precache-manifest.90eb425b86312bf2b8dbe43e7d0377d8.js
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
2 |
-
{
|
3 |
-
"revision": "686c81215854d346608ddcf0fcd01a24",
|
4 |
-
"url": "/index.html"
|
5 |
-
},
|
6 |
-
{
|
7 |
-
"revision": "7eefab545d6bfce9770a",
|
8 |
-
"url": "/static/css/main.3b4eede1.chunk.css"
|
9 |
-
},
|
10 |
-
{
|
11 |
-
"revision": "ca6d8d363680165da358",
|
12 |
-
"url": "/static/js/2.9b5b80e8.chunk.js"
|
13 |
-
},
|
14 |
-
{
|
15 |
-
"revision": "89a1b2dcd30c03705b2bceeb141b76b6",
|
16 |
-
"url": "/static/js/2.9b5b80e8.chunk.js.LICENSE.txt"
|
17 |
-
},
|
18 |
-
{
|
19 |
-
"revision": "70d68b45d511e1e11f23",
|
20 |
-
"url": "/static/js/3.9013e23f.chunk.js"
|
21 |
-
},
|
22 |
-
{
|
23 |
-
"revision": "7eefab545d6bfce9770a",
|
24 |
-
"url": "/static/js/main.975ea04a.chunk.js"
|
25 |
-
},
|
26 |
-
{
|
27 |
-
"revision": "d8c310b0ac7ffa6d8151",
|
28 |
-
"url": "/static/js/runtime-main.25710301.js"
|
29 |
-
}
|
30 |
-
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/precache-manifest.9a82b4d1673393a138e3168d2b6c841d.js
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
2 |
-
{
|
3 |
-
"revision": "b511568a479eef23388cdb305d8d56a3",
|
4 |
-
"url": "/index.html"
|
5 |
-
},
|
6 |
-
{
|
7 |
-
"revision": "eaece3a38555d76d4a73",
|
8 |
-
"url": "/static/css/main.3b4eede1.chunk.css"
|
9 |
-
},
|
10 |
-
{
|
11 |
-
"revision": "ca6d8d363680165da358",
|
12 |
-
"url": "/static/js/2.9b5b80e8.chunk.js"
|
13 |
-
},
|
14 |
-
{
|
15 |
-
"revision": "89a1b2dcd30c03705b2bceeb141b76b6",
|
16 |
-
"url": "/static/js/2.9b5b80e8.chunk.js.LICENSE.txt"
|
17 |
-
},
|
18 |
-
{
|
19 |
-
"revision": "70d68b45d511e1e11f23",
|
20 |
-
"url": "/static/js/3.9013e23f.chunk.js"
|
21 |
-
},
|
22 |
-
{
|
23 |
-
"revision": "eaece3a38555d76d4a73",
|
24 |
-
"url": "/static/js/main.fe247ab2.chunk.js"
|
25 |
-
},
|
26 |
-
{
|
27 |
-
"revision": "d8c310b0ac7ffa6d8151",
|
28 |
-
"url": "/static/js/runtime-main.25710301.js"
|
29 |
-
}
|
30 |
-
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/service-worker.js
CHANGED
@@ -14,7 +14,7 @@
|
|
14 |
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
15 |
|
16 |
importScripts(
|
17 |
-
"/precache-manifest.
|
18 |
);
|
19 |
|
20 |
self.addEventListener('message', (event) => {
|
@@ -35,5 +35,5 @@ workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
|
35 |
|
36 |
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
|
37 |
|
38 |
-
blacklist: [/^\/_/,/\/[
|
39 |
});
|
|
|
14 |
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
15 |
|
16 |
importScripts(
|
17 |
+
"/precache-manifest.653a7aee73b10eaaa2fae0610652f6fa.js"
|
18 |
);
|
19 |
|
20 |
self.addEventListener('message', (event) => {
|
|
|
35 |
|
36 |
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
|
37 |
|
38 |
+
blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
|
39 |
});
|
static/static/css/main.3b4eede1.chunk.css.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["index.css","App.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mJAEY,CACZ,kCAAmC,CACnC,iCAAkC,CAClC,wBACF,CAEA,KACE,yEAEF,CCbA,KACE,iBACF,CAEA,eACE,UAAW,CACX,WAAY,CACZ,eAAgB,CAChB,kBAAmB,CACnB,aAAc,CACd,YACF,CAEA,cACE,cAAe,CACf,qBAAsB,CACtB,iBAAkB,CAClB,WACF,CAEA,gBACE,eAAgB,CAChB,eACF,CAEA,YACE,cAAe,CACf,kBACF,CAEA,kBACE,0BAA2B,CAC3B,qCACF,CAEA,qBACE,wBAAyB,CACzB,wBACF,CAEA,qBACE,UAAY,CACZ,kBACF,CAEA,kBACE,eAAgB,CAChB,eAAiB,CACjB,UACF","file":"main.3b4eede1.chunk.css","sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n background-color: #f5f5f5;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n",".App {\n text-align: center;\n}\n\n.preview-image {\n width: 100%;\n height: auto;\n max-height: 100%;\n object-fit: contain;\n display: block;\n margin-top: 0;\n}\n\n.result-image {\n max-width: 100%;\n border: 1px solid #ddd;\n border-radius: 4px;\n padding: 4px;\n}\n\n.detection-list {\n margin-top: 16px;\n text-align: left;\n}\n\n.model-card {\n cursor: pointer;\n transition: all 0.3s;\n}\n\n.model-card:hover {\n transform: translateY(-5px);\n box-shadow: 0 10px 20px rgba(0,0,0,0.1);\n}\n\n.model-card.selected {\n border: 2px solid #3f51b5;\n background-color: #e8eaf6;\n}\n\n.model-card.disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.performance-info {\n margin-top: 16px;\n font-size: 0.9rem;\n color: #666;\n}\n"]}
|
|
|
|
static/static/css/{main.3b4eede1.chunk.css β main.59c2a54e.chunk.css}
RENAMED
@@ -1,2 +1,2 @@
|
|
1 |
-
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#f5f5f5}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}.App{text-align:center}.preview-image{width:100%;
|
2 |
-
/*# sourceMappingURL=main.
|
|
|
1 |
+
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#f5f5f5}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}.App{text-align:center}.preview-image{max-width:100%;max-height:300px;margin-top:16px}.result-image{max-width:100%;border:1px solid #ddd;border-radius:4px;padding:4px}.detection-list{margin-top:16px;text-align:left}.model-card{cursor:pointer;transition:all .3s}.model-card:hover{transform:translateY(-5px);box-shadow:0 10px 20px rgba(0,0,0,.1)}.model-card.selected{border:2px solid #3f51b5;background-color:#e8eaf6}.model-card.disabled{opacity:.6;cursor:not-allowed}.performance-info{margin-top:16px;font-size:.9rem;color:#666}
|
2 |
+
/*# sourceMappingURL=main.59c2a54e.chunk.css.map */
|
static/static/css/main.59c2a54e.chunk.css.map
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"version":3,"sources":["index.css","App.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mJAEY,CACZ,kCAAmC,CACnC,iCAAkC,CAClC,wBACF,CAEA,KACE,yEAEF,CCbA,KACE,iBACF,CAEA,eACE,cAAe,CACf,gBAAiB,CACjB,eACF,CAEA,cACE,cAAe,CACf,qBAAsB,CACtB,iBAAkB,CAClB,WACF,CAEA,gBACE,eAAgB,CAChB,eACF,CAEA,YACE,cAAe,CACf,kBACF,CAEA,kBACE,0BAA2B,CAC3B,qCACF,CAEA,qBACE,wBAAyB,CACzB,wBACF,CAEA,qBACE,UAAY,CACZ,kBACF,CAEA,kBACE,eAAgB,CAChB,eAAiB,CACjB,UACF","file":"main.59c2a54e.chunk.css","sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n background-color: #f5f5f5;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n",".App {\n text-align: center;\n}\n\n.preview-image {\n max-width: 100%;\n max-height: 300px;\n margin-top: 16px;\n}\n\n.result-image {\n max-width: 100%;\n border: 1px solid #ddd;\n border-radius: 4px;\n padding: 4px;\n}\n\n.detection-list {\n margin-top: 16px;\n text-align: left;\n}\n\n.model-card {\n cursor: pointer;\n transition: all 0.3s;\n}\n\n.model-card:hover {\n transform: translateY(-5px);\n box-shadow: 0 10px 20px rgba(0,0,0,0.1);\n}\n\n.model-card.selected {\n border: 2px solid #3f51b5;\n background-color: #e8eaf6;\n}\n\n.model-card.disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.performance-info {\n margin-top: 16px;\n font-size: 0.9rem;\n color: #666;\n}\n"]}
|
static/static/js/{2.9b5b80e8.chunk.js β 2.abf91741.chunk.js}
RENAMED
The diff for this file is too large to render.
See raw diff
|
|
static/static/js/{2.9b5b80e8.chunk.js.LICENSE.txt β 2.abf91741.chunk.js.LICENSE.txt}
RENAMED
File without changes
|
static/static/js/{2.9b5b80e8.chunk.js.map β 2.abf91741.chunk.js.map}
RENAMED
The diff for this file is too large to render.
See raw diff
|
|
static/static/js/main.975ea04a.chunk.js
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
(this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[]).push([[0],{102:function(e,a,t){},103:function(e,a,t){"use strict";t.r(a);var n=t(0),r=t.n(n),l=t(11),o=t.n(l),c=(t(97),t(78)),i=t(155),s=t(159),m=t(156),d=t(157),u=t(48),g=t(158),p=t(138),E=t(81),b=t(143),h=t(136),y=t(137),f=t(63),v=t.n(f),x=t(75),S=t.n(x),j=t(133);const C=Object(j.a)(e=>({paper:{padding:e.spacing(2),display:"flex",flexDirection:"column",alignItems:"center",height:"100%",minHeight:300,transition:"all 0.3s ease"},dragActive:{border:"2px dashed #3f51b5",backgroundColor:"rgba(63, 81, 181, 0.05)"},dragInactive:{border:"2px dashed #ccc",backgroundColor:"white"},uploadBox:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",height:"100%",width:"100%",cursor:"pointer"},uploadIcon:{fontSize:60,color:"#3f51b5",marginBottom:e.spacing(2)},supportText:{marginTop:e.spacing(2)},previewBox:{display:"flex",flexDirection:"column",alignItems:"center",width:"100%",height:"100%",position:"relative"},imageContainer:{position:"relative",width:"100%",height:"60vh",[e.breakpoints.down("sm")]:{height:"45vh"},display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",marginTop:e.spacing(2)},deleteButton:{position:"absolute",top:0,right:0,backgroundColor:"rgba(255, 255, 255, 0.7)","&:hover":{backgroundColor:"rgba(255, 255, 255, 0.9)"}}}));var w=e=>{let{onImageUpload:a}=e;const t=C(),[l,o]=Object(n.useState)(null),[c,i]=Object(n.useState)(!1),m=Object(n.useRef)(null),d=e=>{e.preventDefault(),e.stopPropagation(),"dragenter"===e.type||"dragover"===e.type?i(!0):"dragleave"===e.type&&i(!1)},g=e=>{e.type.startsWith("image/")?(o(URL.createObjectURL(e)),a(e)):alert("Please upload an image file")};return r.a.createElement(E.a,{className:"".concat(t.paper," ").concat(c?t.dragActive:t.dragInactive),onDragEnter:d,onDragLeave:d,onDragOver:d,onDrop:e=>{e.preventDefault(),e.stopPropagation(),i(!1),e.dataTransfer.files&&e.dataTransfer.files[0]&&g(e.dataTransfer.files[0])}},r.a.createElement("input",{ref:m,type:"file",accept:"image/*",onChange:e=>{e.preventDefault(),e.target.files&&e.target.files[0]&&g(e.target.files[0])},style:{display:"none"}}),l?r.a.createElement(s.a,{className:t.previewBox},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Preview"),r.a.createElement(s.a,{className:t.imageContainer},r.a.createElement("img",{src:l,alt:"Preview",className:"preview-image",style:{width:"100%",height:"auto",maxHeight:"100%",objectFit:"contain",display:"block"}}),r.a.createElement(y.a,{"aria-label":"delete",className:t.deleteButton,onClick:()=>{o(null),a(null),m.current.value=""}},r.a.createElement(S.a,null)))):r.a.createElement(s.a,{className:t.uploadBox,onClick:()=>{m.current.click()}},r.a.createElement(v.a,{className:t.uploadIcon}),r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Drag & Drop an image here"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"or"),r.a.createElement(h.a,{variant:"contained",color:"primary",component:"span",startIcon:r.a.createElement(v.a,null)},"Browse Files"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",className:t.supportText},"Supported formats: JPG, PNG, GIF")))},O=t(139),N=t(140),B=t(166),T=t(141),I=t(64),k=t.n(I),P=t(76),D=t.n(P),F=t(77),R=t.n(F);const A=Object(j.a)(e=>({card:{height:"100%",display:"flex",flexDirection:"column"},selectedCard:{border:"2px solid #3f51b5"},unavailableCard:{opacity:.6},cardContent:{flexGrow:1},chipContainer:{marginBottom:e.spacing(1.5)},successChip:{backgroundColor:"#34C759",color:"#fff"},errorChip:{backgroundColor:"#FF3B3F",color:"#fff"},modelType:{marginTop:e.spacing(1)},processButton:{marginTop:e.spacing(3),textAlign:"center"}}));var _=e=>{let{onModelSelect:a,onProcess:t,isProcessing:n,modelsStatus:l,selectedModel:o,imageSelected:c}=e;const i=A(),m=[{id:"yolo",name:"YOLOv8",description:"Fast and accurate object detection",icon:r.a.createElement(k.a,null),available:l.yolo},{id:"detr",name:"DETR",description:"DEtection TRansformer for object detection",icon:r.a.createElement(k.a,null),available:l.detr},{id:"vit",name:"ViT",description:"Vision Transformer for image classification",icon:r.a.createElement(D.a,null),available:l.vit}],d=e=>{m.find(a=>a.id===e).available&&a(e)};return r.a.createElement(s.a,{sx:{p:2,height:"100%"}},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Select Model"),r.a.createElement(p.a,{container:!0,spacing:2},m.map(e=>r.a.createElement(p.a,{item:!0,xs:12,sm:4,key:e.id},r.a.createElement(O.a,{className:"\n ".concat(i.card," \n ").concat(o===e.id?i.selectedCard:""," \n ").concat(e.available?"":i.unavailableCard,"\n "),onClick:()=>d(e.id)},r.a.createElement(N.a,{className:i.cardContent},r.a.createElement(s.a,{sx:{mb:2,color:"primary"}},e.icon),r.a.createElement(u.a,{variant:"h5",component:"div",gutterBottom:!0},e.name),r.a.createElement("div",{className:i.chipContainer},e.available?r.a.createElement(B.a,{label:"Available",className:i.successChip,size:"small"}):r.a.createElement(B.a,{label:"Not Available",className:i.errorChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.description)),r.a.createElement(T.a,null,r.a.createElement(h.a,{size:"small",onClick:()=>d(e.id),disabled:!e.available,color:o===e.id?"primary":"default",variant:o===e.id?"contained":"outlined",fullWidth:!0},o===e.id?"Selected":"Select")))))),r.a.createElement("div",{className:i.processButton},r.a.createElement(h.a,{variant:"contained",color:"primary",size:"large",startIcon:r.a.createElement(R.a,null),onClick:t,disabled:!o||!c||n},n?"Processing...":"Process Image")))},z=t(153),L=t(150),W=t(105),M=t(154),J=t(142),H=t(164),U=t(163),V=t(145),G=t(146),Y=t(147),Q=t(167),q=t(160),K=t(151),X=t(169),Z=t(152),$=t(161);const ee=Object(j.a)(e=>({root:{marginTop:e.spacing(2),marginBottom:e.spacing(2),padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius},button:{marginRight:e.spacing(2)},searchDialog:{minWidth:"500px"},formControl:{marginBottom:e.spacing(2),minWidth:"100%"},searchResults:{marginTop:e.spacing(2)},resultCard:{marginBottom:e.spacing(2)},resultImage:{height:140,objectFit:"contain"},chip:{margin:e.spacing(.5)},similarityChip:{backgroundColor:e.palette.primary.main,color:"white"}}));var ae=e=>{let{results:a}=e;const t=ee(),[l,o]=Object(n.useState)(!1),[c,i]=Object(n.useState)(!1),[m,d]=Object(n.useState)(null),[g,E]=Object(n.useState)(!1),[y,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,j]=Object(n.useState)([]),[C,w]=Object(n.useState)(!1),[T,I]=Object(n.useState)(null),{model:k,data:P}=a,D=()=>{E(!1)},F=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){const a=16*Math.random()|0;return("x"===e?a:3&a|8).toString(16)}));return r.a.createElement(s.a,{className:t.root},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Vector Database Actions"),r.a.createElement(s.a,{display:"flex",alignItems:"center",mb:2},r.a.createElement(h.a,{variant:"contained",color:"primary",onClick:async()=>{o(!0),d(null);try{let e;if(e="vit"===k?await fetch("/api/add-to-collection",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,metadata:{model:"vit",classifications:P.classifications}})}):await fetch("/api/add-detected-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,objects:P.detections,imageId:F()})}),!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const a=await e.json();if(a.error)throw new Error(a.error);i(!0),setTimeout(()=>i(!1),5e3)}catch(e){console.error("Error saving to vector DB:",e),d("Error saving to vector DB: ".concat(e.message))}finally{o(!1)}},disabled:l,className:t.button},l?r.a.createElement(r.a.Fragment,null,r.a.createElement(b.a,{size:20,color:"inherit",style:{marginRight:8}}),"Saving..."):"Save to Vector DB"),r.a.createElement(h.a,{variant:"outlined",color:"primary",onClick:()=>{E(!0),j([]),I(null)},className:t.button},"Search Similar")),m&&r.a.createElement($.a,{severity:"error",style:{marginTop:8}},m),r.a.createElement(H.a,{open:c,autoHideDuration:5e3,onClose:()=>i(!1)},r.a.createElement($.a,{severity:"success"},"vit"===k?"Image and classifications successfully saved to vector DB!":"Detected objects successfully saved to vector DB!")),r.a.createElement(U.a,{open:g,onClose:D,maxWidth:"md",fullWidth:!0},r.a.createElement(V.a,null,"Search Vector Database"),r.a.createElement(G.a,null,r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(Q.a,{id:"search-type-label"},"Search Type"),r.a.createElement(q.a,{labelId:"search-type-label",id:"search-type",value:y,onChange:e=>{f(e.target.value),j([]),I(null)}},r.a.createElement(K.a,{value:"image"},"Search by Current Image"),r.a.createElement(K.a,{value:"class"},"Search by Class Name"))),"class"===y&&r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(X.a,{label:"Class Name",value:v,onChange:e=>{x(e.target.value)},placeholder:"e.g. person, car, dog...",fullWidth:!0})),T&&r.a.createElement($.a,{severity:"error",style:{marginBottom:16}},T),r.a.createElement(s.a,{className:t.searchResults},C?r.a.createElement(s.a,{display:"flex",justifyContent:"center",alignItems:"center",p:4},r.a.createElement(b.a,null),r.a.createElement(u.a,{variant:"body1",style:{marginLeft:16}},"Searching...")):r.a.createElement(r.a.Fragment,null,console.log("Search dialog render - searchResults:",S),S.length>0?(console.log("Rendering search results:",S),console.log("Search results length:",S.length),0===S.length?(console.log("No results to render"),r.a.createElement(u.a,{variant:"body1"},"No results found.")):r.a.createElement(p.a,{container:!0,spacing:2},S.map((e,a)=>{const n=100*(1-e.distance);return r.a.createElement(p.a,{item:!0,xs:12,sm:6,key:a},r.a.createElement(O.a,{className:t.resultCard},e.metadata&&e.metadata.image_data?r.a.createElement(J.a,{className:t.resultImage,component:"img",height:"200",image:"data:image/jpeg;base64,".concat(e.metadata.image_data),alt:e.metadata&&e.metadata.class?e.metadata.class:"Object"}):r.a.createElement(s.a,{className:t.resultImage,style:{backgroundColor:"#f0f0f0",display:"flex",alignItems:"center",justifyContent:"center",height:200}},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.metadata&&e.metadata.class?e.metadata.class:"Object"," Image")),r.a.createElement(N.a,null,r.a.createElement(s.a,{display:"flex",justifyContent:"space-between",alignItems:"center",mb:1},r.a.createElement(u.a,{variant:"subtitle1"},"Result #",a+1),r.a.createElement(B.a,{label:"Similarity: ".concat(n.toFixed(2),"%"),className:t.similarityChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Class:")," ",e.metadata.class||"N/A"),e.metadata.confidence&&r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Confidence:")," ",(100*e.metadata.confidence).toFixed(2),"%"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Object ID:")," ",e.id))))}))):r.a.createElement(u.a,{variant:"body1"},"No results found. Please try another search.")))),r.a.createElement(Z.a,null,r.a.createElement(h.a,{onClick:D,color:"default"},"Close"),r.a.createElement(h.a,{onClick:async()=>{w(!0),I(null);try{let e={};if("image"===y)e={searchType:"image",image:P.image,n_results:5};else{if(!v.trim())throw new Error("Please enter a class name");e={searchType:"class",class_name:v.trim(),n_results:5}}const a=await fetch("/api/search-similar-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!a.ok)throw new Error("HTTP error! Status: ".concat(a.status));const t=await a.json();if(t.error)throw new Error(t.error);if(console.log("Search API response:",t),!t.success||!Array.isArray(t.results))throw console.error("Unexpected API response format:",t),new Error("Unexpected API response format");console.log("Setting search results array:",t.results),console.log("Results array length:",t.results.length),console.log("First result item:",t.results[0]),j(t.results)}catch(e){console.error("Error searching vector DB:",e),I("Error searching vector DB: ".concat(e.message))}finally{w(!1)}},color:"primary",variant:"contained",disabled:C||"class"===y&&!v.trim()},"Search"))))};const te=Object(j.a)(e=>({paper:{padding:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},resultImage:{maxWidth:"100%",maxHeight:"400px",objectFit:"contain"},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},chipContainer:{display:"flex",gap:e.spacing(1),flexWrap:"wrap"}}));var ne=e=>{let{results:a}=e;const t=te();if(!a)return null;const{model:n,data:l}=a;if(l.error)return r.a.createElement(E.a,{sx:{p:2,bgcolor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},l.error));const o=()=>l.performance?r.a.createElement(s.a,{className:"performance-info"},r.a.createElement(z.a,{className:t.dividerMargin}),r.a.createElement(u.a,{variant:"body2"},"Inference time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(l.performance.inference_time)," on ",l.performance.device)):null;return"yolo"===n||"detr"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"yolo"===n?"YOLOv8":"DETR"," Detection Results"),r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12,md:6},l.image&&r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detection Result"),r.a.createElement("img",{src:"data:image/png;base64,".concat(l.image),alt:"Detection Result",className:t.resultImage}))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detected Objects:"),l.detections&&l.detections.length>0?r.a.createElement(L.a,null,l.detections.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(M.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.class),r.a.createElement(B.a,{label:"".concat((100*e.confidence).toFixed(0),"%"),size:"small",color:"primary",style:{marginLeft:8}})),secondary:"Bounding Box: [".concat(e.bbox.join(", "),"]")})),a<l.detections.length-1&&r.a.createElement(z.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No objects detected")))),o(),r.a.createElement(ae,{results:a})):"vit"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"ViT Classification Results"),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Top Predictions:"),l.top_predictions&&l.top_predictions.length>0?r.a.createElement(L.a,null,l.top_predictions.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(M.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.rank,". ",e.class),r.a.createElement(B.a,{label:"".concat((100*e.probability).toFixed(1),"%"),size:"small",color:0===a?"primary":"default",style:{marginLeft:8}}))})),a<l.top_predictions.length-1&&r.a.createElement(z.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No classifications available"),o(),r.a.createElement(ae,{results:a})):null};const re=Object(j.a)(e=>({paper:{padding:e.spacing(2),marginTop:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},responseBox:{padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius,marginTop:e.spacing(2),whiteSpace:"pre-wrap"},buttonProgress:{marginLeft:e.spacing(1)}}));var le=e=>{let{visionResults:a,model:t}=e;const l=re(),[o,c]=Object(n.useState)(""),[i,m]=Object(n.useState)(!1),[d,g]=Object(n.useState)(null),[p,y]=Object(n.useState)(null);return a?r.a.createElement(E.a,{className:l.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Ask AI about the ","vit"===t?"Classification":"Detection"," Results"),r.a.createElement(u.a,{variant:"body2",className:l.marginBottom},"Ask a question about the detected objects or classifications to get an AI-powered analysis."),r.a.createElement(X.a,{fullWidth:!0,label:"Your question about the image",variant:"outlined",value:o,onChange:e=>c(e.target.value),disabled:i,className:l.marginBottom,placeholder:"vit"===t?"E.g., What category does this image belong to?":"E.g., How many people are in this image?"}),r.a.createElement(h.a,{variant:"contained",color:"primary",onClick:async()=>{if(o.trim()){m(!0),y(null);try{const e=await fetch("/api/analyze",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({visionResults:a,userQuery:o})});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const t=await e.json();t.error?y(t.error):g(t)}catch(e){console.error("Error analyzing with LLM:",e),y("Error analyzing with LLM: ".concat(e.message))}finally{m(!1)}}},disabled:i||!o.trim()},"Analyze with AI",i&&r.a.createElement(b.a,{size:24,className:l.buttonProgress})),p&&r.a.createElement(s.a,{mt:2},r.a.createElement(u.a,{color:"error"},p)),d&&r.a.createElement(r.a.Fragment,null,r.a.createElement(z.a,{className:l.dividerMargin}),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"AI Analysis:"),r.a.createElement(s.a,{className:l.responseBox},r.a.createElement(u.a,{variant:"body1"},d.response)),d.performance&&r.a.createElement(s.a,{mt:1},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},"Analysis time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(d.performance.inference_time)," on ",d.performance.device)))):null};var oe=function(e){let{imageBase64:a}=e;const[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(""),[i,s]=Object(n.useState)(""),[m,d]=Object(n.useState)(!1),[g,b]=Object(n.useState)(""),[y,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,j]=Object(n.useState)(""),[C,w]=Object(n.useState)(5);return r.a.createElement(E.a,{style:{padding:16}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Vision RAG (LangChain)"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"\uc11c\ubc84\uc5d0 OPENAI_API_KEY\uac00 \uc124\uc815\ub418\uc5b4 \uc788\ub2e4\uba74 API Key\ub294 \uc0dd\ub7b5 \uac00\ub2a5\ud569\ub2c8\ub2e4. \uac80\uc0c9 \uc720\ud615\uc744 \uc120\ud0dd\ud558\uace0 \uc9c8\ubb38\uc744 \ubcf4\ub0b4\uba74, \ubca1\ud130 DB\uc5d0\uc11c \uac80\uc0c9\ub41c \ucee8\ud14d\uc2a4\ud2b8\ub85c \ub2f5\ubcc0\ud569\ub2c8\ub2e4."),r.a.createElement(p.a,{container:!0,spacing:2},r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(Y.a,{fullWidth:!0,variant:"outlined",size:"small"},r.a.createElement(Q.a,{id:"search-type-label"},"Search Type"),r.a.createElement(q.a,{labelId:"search-type-label",value:y,onChange:e=>f(e.target.value),label:"Search Type"},r.a.createElement(K.a,{value:"image"},"image (current upload)"),r.a.createElement(K.a,{value:"object"},"object (objectId)"),r.a.createElement(K.a,{value:"class"},"class (class_name)")))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"OpenAI API Key (optional)",value:t,onChange:e=>l(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"password",placeholder:"sk-..."})),"object"===y&&r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"objectId",value:v,onChange:e=>x(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),"class"===y&&r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"class_name",value:S,onChange:e=>j(e.target.value),fullWidth:!0,variant:"outlined",size:"small",placeholder:"e.g. person, car, dog"})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"n_results",value:C,onChange:e=>w(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"number",inputProps:{min:1,max:50}})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"User Question",value:o,onChange:e=>c(e.target.value),fullWidth:!0,multiline:!0,rows:4,variant:"outlined",placeholder:"image"===y?"\uc5c5\ub85c\ub4dc\ud55c \uc774\ubbf8\uc9c0 \uae30\ubc18\uc73c\ub85c \ub2f5\ubcc0\ud574\uc918":"\uac80\uc0c9\ub41c \uac1d\uccb4 \ucee8\ud14d\uc2a4\ud2b8\ub97c \uc0ac\uc6a9\ud574 \ub2f5\ubcc0\ud574\uc918"})),g&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(u.a,{color:"error"},g)),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement("div",{style:{display:"flex",gap:8}},r.a.createElement(h.a,{color:"primary",variant:"contained",onClick:async()=>{b(""),s("");const e=(o||"").trim();if(!e)return void b("\uc9c8\ubb38\uc744 \uc785\ub825\ud558\uc138\uc694.");const n={userQuery:e,searchType:y,n_results:Number(C)||5};if(t&&(n.api_key=t),"image"===y){if(!a)return void b("\uc774\ubbf8\uc9c0\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \uba3c\uc800 \uc774\ubbf8\uc9c0\ub97c \uc5c5\ub85c\ub4dc\ud558\uc138\uc694.");n.image=a}else if("object"===y){if(!v.trim())return void b("objectId\ub97c \uc785\ub825\ud558\uc138\uc694.");n.objectId=v.trim()}else if("class"===y){if(!S.trim())return void b("class_name\uc744 \uc785\ub825\ud558\uc138\uc694.");n.class_name=S.trim()}d(!0);try{const e=await fetch("/api/vision-rag/query",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(n)});if(!e.ok){let a=await e.text();try{a=JSON.stringify(JSON.parse(a),null,2)}catch(r){}throw new Error(a)}const a=await e.json(),t="Model: ".concat(a.model||"-"," | Latency: ").concat(a.latency_sec||"-","s");s((a.answer||"(\ube48 \uc751\ub2f5)")+"\n\n---\n"+t)}catch(l){b("Error: "+l.message)}finally{d(!1)}},disabled:m},m?"Sending...":"Send Question"),r.a.createElement(h.a,{variant:"outlined",onClick:()=>{c(""),s(""),b("")}},"Clear"))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(z.a,{style:{margin:"12px 0"}}),r.a.createElement(u.a,{variant:"subtitle2",color:"textSecondary"},"Response"),r.a.createElement("pre",{style:{whiteSpace:"pre-wrap",fontFamily:"ui-monospace, monospace"}},i))))};t(102);const ce=Object(c.a)({palette:{primary:{main:"#3f51b5"},secondary:{main:"#f50057"}},typography:{fontFamily:"Roboto, Arial, sans-serif"}});var ie=function(){const[e,a]=Object(n.useState)(null),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(""),[h,y]=Object(n.useState)(!1),[f,v]=Object(n.useState)(null),[x,S]=Object(n.useState)(null),[j,C]=Object(n.useState)({yolo:!1,detr:!1,vit:!1});return Object(n.useEffect)(()=>{fetch("/api/status").then(e=>e.json()).then(e=>{C(e.models)}).catch(e=>{console.error("Error checking API status:",e),S("Error connecting to the backend API. Please make sure the server is running.")})},[]),r.a.createElement(i.a,{theme:ce},r.a.createElement(s.a,{style:{flexGrow:1}},r.a.createElement(m.a,{position:"static"},r.a.createElement(d.a,null,r.a.createElement(u.a,{variant:"h6",style:{flexGrow:1}},"Multi-Model Object Detection Demo"))),r.a.createElement(g.a,{maxWidth:"lg",style:{marginTop:ce.spacing(4),marginBottom:ce.spacing(4)}},r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2)}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Upload an image to see how each model performs!"),r.a.createElement(u.a,{variant:"body1",paragraph:!0},"This demo showcases three different object detection and image classification models:"),r.a.createElement(u.a,{variant:"body1",component:"div"},r.a.createElement("ul",null,r.a.createElement("li",null,r.a.createElement("strong",null,"YOLOv8"),": Fast and accurate object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"DETR"),": DEtection TRansformer for object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"ViT"),": Vision Transformer for image classification"))))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(w,{onImageUpload:e=>{if(a(e),v(null),S(null),e instanceof File){const a=new FileReader;a.onload=()=>{const e=a.result;l("string"===typeof e?e:"")},a.onerror=()=>l(""),a.readAsDataURL(e)}else l("")}})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(_,{onModelSelect:e=>{c(e),v(null),S(null)},onProcess:async()=>{if(!e||!o)return void S("Please select both an image and a model");y(!0),S(null);const a=new FormData;a.append("image",e);let t="";switch(o){case"yolo":t="/api/detect/yolo";break;case"detr":t="/api/detect/detr";break;case"vit":t="/api/classify/vit";break;default:return S("Invalid model selection"),void y(!1)}try{const e=await fetch(t,{method:"POST",body:a});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const n=await e.json();v({model:o,data:n})}catch(n){console.error("Error processing image:",n),S("Error processing image: ".concat(n.message))}finally{y(!1)}},isProcessing:h,modelsStatus:j,selectedModel:o,imageSelected:!!e})),x&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2),backgroundColor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},x))),h&&r.a.createElement(p.a,{item:!0,xs:12,style:{textAlign:"center",margin:"".concat(ce.spacing(4),"px 0")}},r.a.createElement(b.a,null),r.a.createElement(u.a,{variant:"h6",style:{marginTop:ce.spacing(2)}},"Processing image...")),f&&r.a.createElement(r.a.Fragment,null,r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(ne,{results:f})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(le,{visionResults:f.data,model:f.model}))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(oe,{imageBase64:t}))))))};var se=e=>{e&&e instanceof Function&&t.e(3).then(t.bind(null,170)).then(a=>{let{getCLS:t,getFID:n,getFCP:r,getLCP:l,getTTFB:o}=a;t(e),n(e),r(e),l(e),o(e)})};o.a.render(r.a.createElement(r.a.StrictMode,null,r.a.createElement(ie,null)),document.getElementById("root")),se()},92:function(e,a,t){e.exports=t(103)},97:function(e,a,t){}},[[92,1,2]]]);
|
2 |
-
//# sourceMappingURL=main.975ea04a.chunk.js.map
|
|
|
|
|
|
static/static/js/main.975ea04a.chunk.js.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["components/ImageUploader.js","components/ModelSelector.js","components/VectorDBActions.js","components/ResultDisplay.js","components/LlmAnalysis.js","components/OpenAIChat.js","App.js","reportWebVitals.js","index.js"],"names":["useStyles","makeStyles","theme","paper","padding","spacing","display","flexDirection","alignItems","height","minHeight","transition","dragActive","border","backgroundColor","dragInactive","uploadBox","justifyContent","width","cursor","uploadIcon","fontSize","color","marginBottom","supportText","marginTop","previewBox","position","imageContainer","breakpoints","down","overflow","deleteButton","top","right","ImageUploader","_ref","onImageUpload","classes","previewUrl","setPreviewUrl","useState","setDragActive","fileInputRef","useRef","handleDrag","e","preventDefault","stopPropagation","type","handleFiles","file","startsWith","URL","createObjectURL","alert","React","createElement","Paper","className","concat","onDragEnter","onDragLeave","onDragOver","onDrop","dataTransfer","files","ref","accept","onChange","target","style","Box","Typography","variant","gutterBottom","src","alt","maxHeight","objectFit","IconButton","aria-label","onClick","handleRemoveImage","current","value","DeleteIcon","onButtonClick","click","CloudUploadIcon","Button","component","startIcon","card","selectedCard","unavailableCard","opacity","cardContent","flexGrow","chipContainer","successChip","errorChip","modelType","processButton","textAlign","ModelSelector","onModelSelect","onProcess","isProcessing","modelsStatus","selectedModel","imageSelected","models","id","name","description","icon","VisibilityIcon","available","yolo","detr","CategoryIcon","vit","handleModelClick","modelId","find","m","sx","p","Grid","container","map","model","item","xs","sm","key","Card","CardContent","mb","Chip","label","size","CardActions","disabled","fullWidth","PlayArrowIcon","root","borderRadius","shape","button","marginRight","searchDialog","minWidth","formControl","searchResults","resultCard","resultImage","chip","margin","similarityChip","palette","primary","main","VectorDBActions","results","isSaving","setIsSaving","saveSuccess","setSaveSuccess","saveError","setSaveError","openSearchDialog","setOpenSearchDialog","searchType","setSearchType","searchClass","setSearchClass","setSearchResults","isSearching","setIsSearching","searchError","setSearchError","data","handleCloseSearchDialog","generateUUID","replace","c","r","Math","random","toString","async","response","fetch","method","headers","body","JSON","stringify","image","metadata","classifications","objects","detections","imageId","ok","Error","status","result","json","error","setTimeout","err","console","message","Fragment","CircularProgress","handleOpenSearchDialog","Alert","severity","Snackbar","open","autoHideDuration","onClose","Dialog","maxWidth","DialogTitle","DialogContent","FormControl","InputLabel","Select","labelId","event","MenuItem","TextField","placeholder","marginLeft","log","length","index","similarity","distance","image_data","CardMedia","class","toFixed","confidence","DialogActions","requestBody","n_results","trim","class_name","success","Array","isArray","dividerMargin","gap","flexWrap","ResultDisplay","bgcolor","renderPerformanceInfo","performance","Divider","ms","undefined","isNaN","num","Number","formatTime","inference_time","device","md","List","detection","ListItem","ListItemText","secondary","bbox","join","top_predictions","prediction","rank","probability","responseBox","whiteSpace","buttonProgress","LlmAnalysis","visionResults","userQuery","setUserQuery","isAnalyzing","setIsAnalyzing","analysisResult","setAnalysisResult","setError","mt","OpenAIChat","imageBase64","apiKey","setApiKey","prompt","setPrompt","setResponse","loading","setLoading","objectId","setObjectId","setClassName","nResults","setNResults","inputProps","min","max","multiline","rows","q","api_key","res","credentials","txt","text","parse","_unused","meta","latency_sec","answer","onClear","fontFamily","createMuiTheme","typography","App","selectedImage","setSelectedImage","setImageBase64","setSelectedModel","setIsProcessing","setResults","setModelsStatus","useEffect","then","catch","ThemeProvider","AppBar","Toolbar","Container","paragraph","File","reader","FileReader","onload","dataUrl","onerror","readAsDataURL","formData","FormData","append","endpoint","reportWebVitals","onPerfEntry","Function","getCLS","getFID","getFCP","getLCP","getTTFB","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"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","file":"static/js/main.975ea04a.chunk.js","sourcesContent":["import React, { useState, useRef } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n Button, \n IconButton \n} from '@material-ui/core';\nimport CloudUploadIcon from '@material-ui/icons/CloudUpload';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n height: '100%',\n minHeight: 300,\n transition: 'all 0.3s ease'\n },\n dragActive: {\n border: '2px dashed #3f51b5',\n backgroundColor: 'rgba(63, 81, 181, 0.05)'\n },\n dragInactive: {\n border: '2px dashed #ccc',\n backgroundColor: 'white'\n },\n uploadBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n width: '100%',\n cursor: 'pointer'\n },\n uploadIcon: {\n fontSize: 60,\n color: '#3f51b5',\n marginBottom: theme.spacing(2)\n },\n supportText: {\n marginTop: theme.spacing(2)\n },\n previewBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n position: 'relative'\n },\n imageContainer: {\n position: 'relative',\n width: '100%',\n // Use viewport-based height so any aspect ratio fits inside\n height: '60vh',\n [theme.breakpoints.down('sm')]: {\n height: '45vh',\n },\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n overflow: 'hidden',\n marginTop: theme.spacing(2),\n },\n deleteButton: {\n position: 'absolute',\n top: 0,\n right: 0,\n backgroundColor: 'rgba(255, 255, 255, 0.7)',\n '&:hover': {\n backgroundColor: 'rgba(255, 255, 255, 0.9)',\n }\n }\n}));\n\nconst ImageUploader = ({ onImageUpload }) => {\n const classes = useStyles();\n const [previewUrl, setPreviewUrl] = useState(null);\n const [dragActive, setDragActive] = useState(false);\n const fileInputRef = useRef(null);\n\n const handleDrag = (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (e.type === 'dragenter' || e.type === 'dragover') {\n setDragActive(true);\n } else if (e.type === 'dragleave') {\n setDragActive(false);\n }\n };\n\n const handleDrop = (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragActive(false);\n if (e.dataTransfer.files && e.dataTransfer.files[0]) {\n handleFiles(e.dataTransfer.files[0]);\n }\n };\n\n const handleChange = (e) => {\n e.preventDefault();\n if (e.target.files && e.target.files[0]) {\n handleFiles(e.target.files[0]);\n }\n };\n\n const handleFiles = (file) => {\n if (file.type.startsWith('image/')) {\n setPreviewUrl(URL.createObjectURL(file));\n onImageUpload(file);\n } else {\n alert('Please upload an image file');\n }\n };\n\n const onButtonClick = () => {\n fileInputRef.current.click();\n };\n\n const handleRemoveImage = () => {\n setPreviewUrl(null);\n onImageUpload(null);\n fileInputRef.current.value = \"\";\n };\n\n return (\n <Paper \n className={`${classes.paper} ${dragActive ? classes.dragActive : classes.dragInactive}`}\n onDragEnter={handleDrag}\n onDragLeave={handleDrag}\n onDragOver={handleDrag}\n onDrop={handleDrop}\n >\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleChange}\n style={{ display: 'none' }}\n />\n\n {!previewUrl ? (\n <Box \n className={classes.uploadBox}\n onClick={onButtonClick}\n >\n <CloudUploadIcon className={classes.uploadIcon} />\n <Typography variant=\"h6\" gutterBottom>\n Drag & Drop an image here\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n or\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n component=\"span\"\n startIcon={<CloudUploadIcon />}\n >\n Browse Files\n </Button>\n <Typography variant=\"body2\" color=\"textSecondary\" className={classes.supportText}>\n Supported formats: JPG, PNG, GIF\n </Typography>\n </Box>\n ) : (\n <Box className={classes.previewBox}>\n <Typography variant=\"h6\" gutterBottom>\n Preview\n </Typography>\n <Box className={classes.imageContainer}>\n <img\n src={previewUrl}\n alt=\"Preview\"\n className=\"preview-image\"\n style={{\n width: '100%',\n height: 'auto',\n maxHeight: '100%',\n objectFit: 'contain',\n display: 'block'\n }}\n />\n <IconButton\n aria-label=\"delete\"\n className={classes.deleteButton}\n onClick={handleRemoveImage}\n >\n <DeleteIcon />\n </IconButton>\n </Box>\n </Box>\n )}\n </Paper>\n );\n};\n\nexport default ImageUploader;\n","import React from 'react';\nimport { \n Grid, \n Card, \n CardContent, \n CardActions, \n Typography, \n Button, \n Chip,\n Box\n} from '@material-ui/core';\nimport VisibilityIcon from '@material-ui/icons/Visibility';\nimport CategoryIcon from '@material-ui/icons/Category';\nimport PlayArrowIcon from '@material-ui/icons/PlayArrow';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n card: {\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n },\n selectedCard: {\n border: '2px solid #3f51b5',\n },\n unavailableCard: {\n opacity: 0.6,\n },\n cardContent: {\n flexGrow: 1,\n },\n chipContainer: {\n marginBottom: theme.spacing(1.5),\n },\n successChip: {\n backgroundColor: '#34C759',\n color: '#fff',\n },\n errorChip: {\n backgroundColor: '#FF3B3F',\n color: '#fff',\n },\n modelType: {\n marginTop: theme.spacing(1),\n },\n processButton: {\n marginTop: theme.spacing(3),\n textAlign: 'center',\n }\n}));\n\nconst ModelSelector = ({ \n onModelSelect, \n onProcess, \n isProcessing, \n modelsStatus, \n selectedModel,\n imageSelected \n}) => {\n const classes = useStyles();\n \n const models = [\n {\n id: 'yolo',\n name: 'YOLOv8',\n description: 'Fast and accurate object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.yolo\n },\n {\n id: 'detr',\n name: 'DETR',\n description: 'DEtection TRansformer for object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.detr\n },\n {\n id: 'vit',\n name: 'ViT',\n description: 'Vision Transformer for image classification',\n icon: <CategoryIcon />,\n available: modelsStatus.vit\n }\n ];\n\n const handleModelClick = (modelId) => {\n if (models.find(m => m.id === modelId).available) {\n onModelSelect(modelId);\n }\n };\n\n return (\n <Box sx={{ p: 2, height: '100%' }}>\n <Typography variant=\"h6\" gutterBottom>\n Select Model\n </Typography>\n \n <Grid container spacing={2}>\n {models.map((model) => (\n <Grid item xs={12} sm={4} key={model.id}>\n <Card \n className={`\n ${classes.card} \n ${selectedModel === model.id ? classes.selectedCard : ''} \n ${!model.available ? classes.unavailableCard : ''}\n `}\n onClick={() => handleModelClick(model.id)}\n >\n <CardContent className={classes.cardContent}>\n <Box sx={{ mb: 2, color: 'primary' }}>\n {model.icon}\n </Box>\n <Typography variant=\"h5\" component=\"div\" gutterBottom>\n {model.name}\n </Typography>\n <div className={classes.chipContainer}>\n {model.available ? (\n <Chip \n label=\"Available\" \n className={classes.successChip}\n size=\"small\" \n />\n ) : (\n <Chip \n label=\"Not Available\" \n className={classes.errorChip}\n size=\"small\" \n />\n )}\n </div>\n <Typography variant=\"body2\" color=\"textSecondary\">\n {model.description}\n </Typography>\n </CardContent>\n <CardActions>\n <Button \n size=\"small\" \n onClick={() => handleModelClick(model.id)}\n disabled={!model.available}\n color={selectedModel === model.id ? \"primary\" : \"default\"}\n variant={selectedModel === model.id ? \"contained\" : \"outlined\"}\n fullWidth\n >\n {selectedModel === model.id ? 'Selected' : 'Select'}\n </Button>\n </CardActions>\n </Card>\n </Grid>\n ))}\n </Grid>\n\n <div className={classes.processButton}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"large\"\n startIcon={<PlayArrowIcon />}\n onClick={onProcess}\n disabled={!selectedModel || !imageSelected || isProcessing}\n >\n {isProcessing ? 'Processing...' : 'Process Image'}\n </Button>\n </div>\n </Box>\n );\n};\n\nexport default ModelSelector;\n","import React, { useState } from 'react';\nimport { \n Button, \n Box, \n Typography, \n CircularProgress, \n Snackbar,\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n TextField,\n FormControl,\n InputLabel,\n Select,\n MenuItem,\n Grid,\n Card,\n CardMedia,\n CardContent,\n Chip\n} from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n marginTop: theme.spacing(2),\n marginBottom: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n },\n button: {\n marginRight: theme.spacing(2),\n },\n searchDialog: {\n minWidth: '500px',\n },\n formControl: {\n marginBottom: theme.spacing(2),\n minWidth: '100%',\n },\n searchResults: {\n marginTop: theme.spacing(2),\n },\n resultCard: {\n marginBottom: theme.spacing(2),\n },\n resultImage: {\n height: 140,\n objectFit: 'contain',\n },\n chip: {\n margin: theme.spacing(0.5),\n },\n similarityChip: {\n backgroundColor: theme.palette.primary.main,\n color: 'white',\n }\n}));\n\nconst VectorDBActions = ({ results }) => {\n const classes = useStyles();\n const [isSaving, setIsSaving] = useState(false);\n const [saveSuccess, setSaveSuccess] = useState(false);\n const [saveError, setSaveError] = useState(null);\n const [openSearchDialog, setOpenSearchDialog] = useState(false);\n const [searchType, setSearchType] = useState('image');\n const [searchClass, setSearchClass] = useState('');\n const [searchResults, setSearchResults] = useState([]);\n const [isSearching, setIsSearching] = useState(false);\n const [searchError, setSearchError] = useState(null);\n \n // Extract model and data from results\n const { model, data } = results;\n \n // Handle saving to vector DB\n const handleSaveToVectorDB = async () => {\n setIsSaving(true);\n setSaveError(null);\n \n try {\n let response;\n \n if (model === 'vit') {\n // For ViT, save the whole image with classifications\n response = await fetch('/api/add-to-collection', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n metadata: {\n model: 'vit',\n classifications: data.classifications\n }\n })\n });\n } else {\n // For YOLO and DETR, save detected objects\n response = await fetch('/api/add-detected-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n objects: data.detections,\n imageId: generateUUID()\n })\n });\n }\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n setSaveSuccess(true);\n setTimeout(() => setSaveSuccess(false), 5000);\n } catch (err) {\n console.error('Error saving to vector DB:', err);\n setSaveError(`Error saving to vector DB: ${err.message}`);\n } finally {\n setIsSaving(false);\n }\n };\n \n // Handle opening search dialog\n const handleOpenSearchDialog = () => {\n setOpenSearchDialog(true);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle closing search dialog\n const handleCloseSearchDialog = () => {\n setOpenSearchDialog(false);\n };\n \n // Handle search type change\n const handleSearchTypeChange = (event) => {\n setSearchType(event.target.value);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle search class change\n const handleSearchClassChange = (event) => {\n setSearchClass(event.target.value);\n };\n \n // Handle search\n const handleSearch = async () => {\n setIsSearching(true);\n setSearchError(null);\n \n try {\n let requestBody = {};\n \n if (searchType === 'image') {\n // Search by current image\n requestBody = {\n searchType: 'image',\n image: data.image,\n n_results: 5\n };\n } else {\n // Search by class name\n if (!searchClass.trim()) {\n throw new Error('Please enter a class name');\n }\n \n requestBody = {\n searchType: 'class',\n class_name: searchClass.trim(),\n n_results: 5\n };\n }\n \n const response = await fetch('/api/search-similar-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody)\n });\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n console.log('Search API response:', result);\n \n // The backend responds with {success, searchType, results} structure, so extract only the results array\n if (result.success && Array.isArray(result.results)) {\n console.log('Setting search results array:', result.results);\n console.log('Results array length:', result.results.length);\n console.log('First result item:', result.results[0]);\n setSearchResults(result.results);\n } else {\n console.error('Unexpected API response format:', result);\n throw new Error('Unexpected API response format');\n }\n } catch (err) {\n console.error('Error searching vector DB:', err);\n setSearchError(`Error searching vector DB: ${err.message}`);\n } finally {\n setIsSearching(false);\n }\n };\n \n // Generate UUID for image ID\n const generateUUID = () => {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n };\n \n // Render search results\n const renderSearchResults = () => {\n console.log('Rendering search results:', searchResults);\n console.log('Search results length:', searchResults.length);\n \n if (searchResults.length === 0) {\n console.log('No results to render');\n return (\n <Typography variant=\"body1\">No results found.</Typography>\n );\n }\n \n return (\n <Grid container spacing={2}>\n {searchResults.map((result, index) => {\n const similarity = (1 - result.distance) * 100;\n \n return (\n <Grid item xs={12} sm={6} key={index}>\n <Card className={classes.resultCard}>\n {result.metadata && result.metadata.image_data ? (\n <CardMedia\n className={classes.resultImage}\n component=\"img\"\n height=\"200\"\n image={`data:image/jpeg;base64,${result.metadata.image_data}`}\n alt={result.metadata && result.metadata.class ? result.metadata.class : 'Object'}\n />\n ) : (\n <Box \n className={classes.resultImage}\n style={{ \n backgroundColor: '#f0f0f0', \n display: 'flex', \n alignItems: 'center', \n justifyContent: 'center',\n height: 200\n }}\n >\n <Typography variant=\"body2\" color=\"textSecondary\">\n {result.metadata && result.metadata.class ? result.metadata.class : 'Object'} Image\n </Typography>\n </Box>\n )}\n <CardContent>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\" mb={1}>\n <Typography variant=\"subtitle1\">Result #{index + 1}</Typography>\n <Chip \n label={`Similarity: ${similarity.toFixed(2)}%`}\n className={classes.similarityChip}\n size=\"small\"\n />\n </Box>\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Class:</strong> {result.metadata.class || 'N/A'}\n </Typography>\n {result.metadata.confidence && (\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Confidence:</strong> {(result.metadata.confidence * 100).toFixed(2)}%\n </Typography>\n )}\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Object ID:</strong> {result.id}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n );\n })}\n </Grid>\n );\n };\n \n return (\n <Box className={classes.root}>\n <Typography variant=\"h6\" gutterBottom>\n Vector Database Actions\n </Typography>\n \n <Box display=\"flex\" alignItems=\"center\" mb={2}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={handleSaveToVectorDB}\n disabled={isSaving}\n className={classes.button}\n >\n {isSaving ? (\n <>\n <CircularProgress size={20} color=\"inherit\" style={{ marginRight: 8 }} />\n Saving...\n </>\n ) : (\n 'Save to Vector DB'\n )}\n </Button>\n \n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={handleOpenSearchDialog}\n className={classes.button}\n >\n Search Similar\n </Button>\n </Box>\n \n {saveError && (\n <Alert severity=\"error\" style={{ marginTop: 8 }}>\n {saveError}\n </Alert>\n )}\n \n <Snackbar open={saveSuccess} autoHideDuration={5000} onClose={() => setSaveSuccess(false)}>\n <Alert severity=\"success\">\n {model === 'vit' ? (\n 'Image and classifications successfully saved to vector DB!'\n ) : (\n 'Detected objects successfully saved to vector DB!'\n )}\n </Alert>\n </Snackbar>\n \n {/* Search Dialog */}\n <Dialog\n open={openSearchDialog}\n onClose={handleCloseSearchDialog}\n maxWidth=\"md\"\n fullWidth\n >\n <DialogTitle>Search Vector Database</DialogTitle>\n <DialogContent>\n <FormControl className={classes.formControl}>\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n id=\"search-type\"\n value={searchType}\n onChange={handleSearchTypeChange}\n >\n <MenuItem value=\"image\">Search by Current Image</MenuItem>\n <MenuItem value=\"class\">Search by Class Name</MenuItem>\n </Select>\n </FormControl>\n \n {searchType === 'class' && (\n <FormControl className={classes.formControl}>\n <TextField\n label=\"Class Name\"\n value={searchClass}\n onChange={handleSearchClassChange}\n placeholder=\"e.g. person, car, dog...\"\n fullWidth\n />\n </FormControl>\n )}\n \n {searchError && (\n <Alert severity=\"error\" style={{ marginBottom: 16 }}>\n {searchError}\n </Alert>\n )}\n \n <Box className={classes.searchResults}>\n {isSearching ? (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" p={4}>\n <CircularProgress />\n <Typography variant=\"body1\" style={{ marginLeft: 16 }}>\n Searching...\n </Typography>\n </Box>\n ) : (\n <>\n {console.log('Search dialog render - searchResults:', searchResults)}\n {searchResults.length > 0 ? renderSearchResults() : \n <Typography variant=\"body1\">No results found. Please try another search.</Typography>\n }\n </>\n )}\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={handleCloseSearchDialog} color=\"default\">\n Close\n </Button>\n <Button \n onClick={handleSearch} \n color=\"primary\" \n variant=\"contained\"\n disabled={isSearching || (searchType === 'class' && !searchClass.trim())}\n >\n Search\n </Button>\n </DialogActions>\n </Dialog>\n </Box>\n );\n};\n\nexport default VectorDBActions;\n","import React from 'react';\nimport { \n Paper, \n Typography, \n Box, \n List, \n ListItem, \n ListItemText, \n Divider,\n Grid,\n Chip\n} from '@material-ui/core';\nimport VectorDBActions from './VectorDBActions';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n resultImage: {\n maxWidth: '100%',\n maxHeight: '400px',\n objectFit: 'contain'\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n chipContainer: {\n display: 'flex',\n gap: theme.spacing(1),\n flexWrap: 'wrap'\n }\n}));\n\nconst ResultDisplay = ({ results }) => {\n const classes = useStyles();\n if (!results) return null;\n \n const { model, data } = results;\n \n // Helper to format times nicely\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n \n // Check if there's an error\n if (data.error) {\n return (\n <Paper sx={{ p: 2, bgcolor: '#ffebee' }}>\n <Typography color=\"error\">{data.error}</Typography>\n </Paper>\n );\n }\n\n // Display performance info\n const renderPerformanceInfo = () => {\n if (!data.performance) return null;\n \n return (\n <Box className=\"performance-info\">\n <Divider className={classes.dividerMargin} />\n <Typography variant=\"body2\">\n Inference time: {formatTime(data.performance.inference_time)} on {data.performance.device}\n </Typography>\n </Box>\n );\n };\n\n // Render for YOLO and DETR (object detection)\n if (model === 'yolo' || model === 'detr') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n {model === 'yolo' ? 'YOLOv8' : 'DETR'} Detection Results\n </Typography>\n \n <Grid container spacing={3}>\n <Grid item xs={12} md={6}>\n {data.image && (\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detection Result\n </Typography>\n <img \n src={`data:image/png;base64,${data.image}`} \n alt=\"Detection Result\" \n className={classes.resultImage}\n />\n </Box>\n )}\n </Grid>\n \n <Grid item xs={12} md={6}>\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detected Objects:\n </Typography>\n \n {data.detections && data.detections.length > 0 ? (\n <List>\n {data.detections.map((detection, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {detection.class}\n </Typography>\n <Chip \n label={`${(detection.confidence * 100).toFixed(0)}%`}\n size=\"small\"\n color=\"primary\"\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n secondary={`Bounding Box: [${detection.bbox.join(', ')}]`} \n />\n </ListItem>\n {index < data.detections.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No objects detected</Typography>\n )}\n </Box>\n </Grid>\n </Grid>\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for Object Detection */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n // Render for ViT (classification)\n if (model === 'vit') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n ViT Classification Results\n </Typography>\n \n <Typography variant=\"subtitle1\" gutterBottom>\n Top Predictions:\n </Typography>\n \n {data.top_predictions && data.top_predictions.length > 0 ? (\n <List>\n {data.top_predictions.map((prediction, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {prediction.rank}. {prediction.class}\n </Typography>\n <Chip \n label={`${(prediction.probability * 100).toFixed(1)}%`}\n size=\"small\"\n color={index === 0 ? \"primary\" : \"default\"}\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n />\n </ListItem>\n {index < data.top_predictions.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No classifications available</Typography>\n )}\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for ViT Classification */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n return null;\n};\n\nexport default ResultDisplay;\n","import React, { useState } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n TextField, \n Button, \n CircularProgress,\n Divider\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n marginTop: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n responseBox: {\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n marginTop: theme.spacing(2),\n whiteSpace: 'pre-wrap'\n },\n buttonProgress: {\n marginLeft: theme.spacing(1)\n }\n}));\n\nconst LlmAnalysis = ({ visionResults, model }) => {\n const classes = useStyles();\n const [userQuery, setUserQuery] = useState('');\n const [isAnalyzing, setIsAnalyzing] = useState(false);\n const [analysisResult, setAnalysisResult] = useState(null);\n const [error, setError] = useState(null);\n\n // Format time for display\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n\n const handleAnalyze = async () => {\n if (!userQuery.trim()) return;\n \n setIsAnalyzing(true);\n setError(null);\n \n try {\n const response = await fetch('/api/analyze', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n visionResults: visionResults,\n userQuery: userQuery\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n \n if (data.error) {\n setError(data.error);\n } else {\n setAnalysisResult(data);\n }\n } catch (err) {\n console.error('Error analyzing with LLM:', err);\n setError(`Error analyzing with LLM: ${err.message}`);\n } finally {\n setIsAnalyzing(false);\n }\n };\n\n if (!visionResults) return null;\n\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n Ask AI about the {model === 'vit' ? 'Classification' : 'Detection'} Results\n </Typography>\n \n <Typography variant=\"body2\" className={classes.marginBottom}>\n Ask a question about the detected objects or classifications to get an AI-powered analysis.\n </Typography>\n \n <TextField\n fullWidth\n label=\"Your question about the image\"\n variant=\"outlined\"\n value={userQuery}\n onChange={(e) => setUserQuery(e.target.value)}\n disabled={isAnalyzing}\n className={classes.marginBottom}\n placeholder={model === 'vit' \n ? \"E.g., What category does this image belong to?\" \n : \"E.g., How many people are in this image?\"}\n />\n \n <Button \n variant=\"contained\" \n color=\"primary\"\n onClick={handleAnalyze}\n disabled={isAnalyzing || !userQuery.trim()}\n >\n Analyze with AI\n {isAnalyzing && <CircularProgress size={24} className={classes.buttonProgress} />}\n </Button>\n \n {error && (\n <Box mt={2}>\n <Typography color=\"error\">{error}</Typography>\n </Box>\n )}\n \n {analysisResult && (\n <>\n <Divider className={classes.dividerMargin} />\n \n <Typography variant=\"subtitle1\" gutterBottom>\n AI Analysis:\n </Typography>\n \n <Box className={classes.responseBox}>\n <Typography variant=\"body1\">\n {analysisResult.response}\n </Typography>\n </Box>\n \n {analysisResult.performance && (\n <Box mt={1}>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Analysis time: {formatTime(analysisResult.performance.inference_time)} on {analysisResult.performance.device}\n </Typography>\n </Box>\n )}\n </>\n )}\n </Paper>\n );\n};\n\nexport default LlmAnalysis;\n","import React, { useState } from 'react';\nimport {\n Paper,\n Typography,\n Grid,\n TextField,\n Button,\n Divider,\n MenuItem,\n Select,\n FormControl,\n InputLabel\n} from '@material-ui/core';\n\n// Props:\n// - imageBase64: data URL string of the currently uploaded image (preferred for searchType=image)\nfunction OpenAIChat({ imageBase64 }) {\n const [apiKey, setApiKey] = useState('');\n const [prompt, setPrompt] = useState('');\n const [response, setResponse] = useState('');\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState('');\n\n // Vision RAG parameters\n const [searchType, setSearchType] = useState('image');\n const [objectId, setObjectId] = useState('');\n const [className, setClassName] = useState('');\n const [nResults, setNResults] = useState(5);\n\n const onSend = async () => {\n setError('');\n setResponse('');\n const q = (prompt || '').trim();\n if (!q) { setError('μ§λ¬Έμ μ
λ ₯νμΈμ.'); return; }\n\n // Build request body for /api/vision-rag/query\n const body = {\n userQuery: q,\n searchType: searchType,\n n_results: Number(nResults) || 5,\n };\n if (apiKey) body.api_key = apiKey;\n if (searchType === 'image') {\n if (!imageBase64) { setError('μ΄λ―Έμ§κ° νμν©λλ€. λ¨Όμ μ΄λ―Έμ§λ₯Ό μ
λ‘λνμΈμ.'); return; }\n body.image = imageBase64;\n } else if (searchType === 'object') {\n if (!objectId.trim()) { setError('objectIdλ₯Ό μ
λ ₯νμΈμ.'); return; }\n body.objectId = objectId.trim();\n } else if (searchType === 'class') {\n if (!className.trim()) { setError('class_nameμ μ
λ ₯νμΈμ.'); return; }\n body.class_name = className.trim();\n }\n\n setLoading(true);\n try {\n const res = await fetch('/api/vision-rag/query', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(body)\n });\n\n if (!res.ok) {\n let txt = await res.text();\n try { txt = JSON.stringify(JSON.parse(txt), null, 2); } catch {}\n throw new Error(txt);\n }\n const data = await res.json();\n const meta = `Model: ${data.model || '-'} | Latency: ${data.latency_sec || '-'}s`;\n setResponse((data.answer || '(λΉ μλ΅)') + '\\n\\n---\\n' + meta);\n } catch (e) {\n setError('Error: ' + e.message);\n } finally {\n setLoading(false);\n }\n };\n\n const onClear = () => {\n setPrompt('');\n setResponse('');\n setError('');\n };\n\n return (\n <Paper style={{ padding: 16 }}>\n <Typography variant=\"h5\" gutterBottom>\n Vision RAG (LangChain)\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n μλ²μ OPENAI_API_KEYκ° μ€μ λμ΄ μλ€λ©΄ API Keyλ μλ΅ κ°λ₯ν©λλ€. κ²μ μ νμ μ ννκ³ μ§λ¬Έμ 보λ΄λ©΄, λ²‘ν° DBμμ κ²μλ 컨ν
μ€νΈλ‘ λ΅λ³ν©λλ€.\n </Typography>\n <Grid container spacing={2}>\n <Grid item xs={12} md={6}>\n <FormControl fullWidth variant=\"outlined\" size=\"small\">\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n value={searchType}\n onChange={(e) => setSearchType(e.target.value)}\n label=\"Search Type\"\n >\n <MenuItem value=\"image\">image (current upload)</MenuItem>\n <MenuItem value=\"object\">object (objectId)</MenuItem>\n <MenuItem value=\"class\">class (class_name)</MenuItem>\n </Select>\n </FormControl>\n </Grid>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"OpenAI API Key (optional)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"password\"\n placeholder=\"sk-...\"\n />\n </Grid>\n\n {searchType === 'object' && (\n <Grid item xs={12} md={6}>\n <TextField\n label=\"objectId\"\n value={objectId}\n onChange={(e) => setObjectId(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n )}\n {searchType === 'class' && (\n <Grid item xs={12} md={6}>\n <TextField\n label=\"class_name\"\n value={className}\n onChange={(e) => setClassName(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n placeholder=\"e.g. person, car, dog\"\n />\n </Grid>\n )}\n <Grid item xs={12} md={6}>\n <TextField\n label=\"n_results\"\n value={nResults}\n onChange={(e) => setNResults(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"number\"\n inputProps={{ min: 1, max: 50 }}\n />\n </Grid>\n\n <Grid item xs={12}>\n <TextField\n label=\"User Question\"\n value={prompt}\n onChange={(e) => setPrompt(e.target.value)}\n fullWidth\n multiline\n rows={4}\n variant=\"outlined\"\n placeholder={searchType === 'image' ? 'μ
λ‘λν μ΄λ―Έμ§ κΈ°λ°μΌλ‘ λ΅λ³ν΄μ€' : 'κ²μλ κ°μ²΄ 컨ν
μ€νΈλ₯Ό μ¬μ©ν΄ λ΅λ³ν΄μ€'}\n />\n </Grid>\n {error && (\n <Grid item xs={12}>\n <Typography color=\"error\">{error}</Typography>\n </Grid>\n )}\n <Grid item xs={12}>\n <div style={{ display: 'flex', gap: 8 }}>\n <Button color=\"primary\" variant=\"contained\" onClick={onSend} disabled={loading}>\n {loading ? 'Sending...' : 'Send Question'}\n </Button>\n <Button variant=\"outlined\" onClick={onClear}>Clear</Button>\n </div>\n </Grid>\n <Grid item xs={12}>\n <Divider style={{ margin: '12px 0' }} />\n <Typography variant=\"subtitle2\" color=\"textSecondary\">Response</Typography>\n <pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'ui-monospace, monospace' }}>{response}</pre>\n </Grid>\n </Grid>\n </Paper>\n );\n}\n\nexport default OpenAIChat;\n","import React, { useState, useEffect } from 'react';\nimport { \n Container, \n Typography, \n Box, \n Paper, \n Grid, \n CircularProgress,\n AppBar,\n Toolbar,\n ThemeProvider,\n createMuiTheme\n} from '@material-ui/core';\nimport ImageUploader from './components/ImageUploader';\nimport ModelSelector from './components/ModelSelector';\nimport ResultDisplay from './components/ResultDisplay';\nimport LlmAnalysis from './components/LlmAnalysis';\nimport OpenAIChat from './components/OpenAIChat';\nimport './App.css';\n\n// Create a theme\nconst theme = createMuiTheme({\n palette: {\n primary: {\n main: '#3f51b5',\n },\n secondary: {\n main: '#f50057',\n },\n },\n typography: {\n fontFamily: 'Roboto, Arial, sans-serif',\n },\n});\n\nfunction App() {\n const [selectedImage, setSelectedImage] = useState(null);\n const [imageBase64, setImageBase64] = useState('');\n const [selectedModel, setSelectedModel] = useState('');\n const [isProcessing, setIsProcessing] = useState(false);\n const [results, setResults] = useState(null);\n const [error, setError] = useState(null);\n const [modelsStatus, setModelsStatus] = useState({\n yolo: false,\n detr: false,\n vit: false\n });\n\n // Check API status on component mount\n useEffect(() => {\n fetch('/api/status')\n .then(response => response.json())\n .then(data => {\n setModelsStatus(data.models);\n })\n .catch(err => {\n console.error('Error checking API status:', err);\n setError('Error connecting to the backend API. Please make sure the server is running.');\n });\n }, []);\n\n const handleImageUpload = (image) => {\n setSelectedImage(image);\n setResults(null);\n setError(null);\n\n // Convert uploaded File -> base64 data URL for Vision RAG image search\n if (image instanceof File) {\n const reader = new FileReader();\n reader.onload = () => {\n const dataUrl = reader.result; // e.g., data:image/png;base64,....\n setImageBase64(typeof dataUrl === 'string' ? dataUrl : '');\n };\n reader.onerror = () => setImageBase64('');\n reader.readAsDataURL(image);\n } else {\n setImageBase64('');\n }\n };\n\n const handleModelSelect = (model) => {\n setSelectedModel(model);\n setResults(null);\n setError(null);\n };\n\n const processImage = async () => {\n if (!selectedImage || !selectedModel) {\n setError('Please select both an image and a model');\n return;\n }\n\n setIsProcessing(true);\n setError(null);\n\n // Create form data for the image\n const formData = new FormData();\n formData.append('image', selectedImage);\n\n let endpoint = '';\n switch (selectedModel) {\n case 'yolo':\n endpoint = '/api/detect/yolo';\n break;\n case 'detr':\n endpoint = '/api/detect/detr';\n break;\n case 'vit':\n endpoint = '/api/classify/vit';\n break;\n default:\n setError('Invalid model selection');\n setIsProcessing(false);\n return;\n }\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n setResults({ model: selectedModel, data });\n } catch (err) {\n console.error('Error processing image:', err);\n setError(`Error processing image: ${err.message}`);\n } finally {\n setIsProcessing(false);\n }\n };\n\n return (\n <ThemeProvider theme={theme}>\n <Box style={{ flexGrow: 1 }}>\n <AppBar position=\"static\">\n <Toolbar>\n <Typography variant=\"h6\" style={{ flexGrow: 1 }}>\n Multi-Model Object Detection Demo\n </Typography>\n </Toolbar>\n </AppBar>\n <Container maxWidth=\"lg\" style={{ marginTop: theme.spacing(4), marginBottom: theme.spacing(4) }}>\n <Grid container spacing={3}>\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2) }}>\n <Typography variant=\"h5\" gutterBottom>\n Upload an image to see how each model performs!\n </Typography>\n <Typography variant=\"body1\" paragraph>\n This demo showcases three different object detection and image classification models:\n </Typography>\n <Typography variant=\"body1\" component=\"div\">\n <ul>\n <li><strong>YOLOv8</strong>: Fast and accurate object detection</li>\n <li><strong>DETR</strong>: DEtection TRansformer for object detection</li>\n <li><strong>ViT</strong>: Vision Transformer for image classification</li>\n </ul>\n </Typography>\n </Paper>\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ImageUploader onImageUpload={handleImageUpload} />\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ModelSelector \n onModelSelect={handleModelSelect} \n onProcess={processImage}\n isProcessing={isProcessing}\n modelsStatus={modelsStatus}\n selectedModel={selectedModel}\n imageSelected={!!selectedImage}\n />\n </Grid>\n \n {error && (\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2), backgroundColor: '#ffebee' }}>\n <Typography color=\"error\">{error}</Typography>\n </Paper>\n </Grid>\n )}\n \n {isProcessing && (\n <Grid item xs={12} style={{ textAlign: 'center', margin: `${theme.spacing(4)}px 0` }}>\n <CircularProgress />\n <Typography variant=\"h6\" style={{ marginTop: theme.spacing(2) }}>\n Processing image...\n </Typography>\n </Grid>\n )}\n \n {results && (\n <>\n <Grid item xs={12}>\n <ResultDisplay results={results} />\n </Grid>\n <Grid item xs={12}>\n <LlmAnalysis visionResults={results.data} model={results.model} />\n </Grid>\n </>\n )}\n\n {/* Vision RAG (LangChain) section at the end */}\n <Grid item xs={12}>\n <OpenAIChat imageBase64={imageBase64} />\n </Grid>\n </Grid>\n </Container>\n </Box>\n </ThemeProvider>\n );\n}\n\nexport default App;\n","const reportWebVitals = (onPerfEntry) => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"],"sourceRoot":""}
|
|
|
|
static/static/js/main.db88b5b1.chunk.js
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
(this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[]).push([[0],{102:function(e,a,t){},103:function(e,a,t){"use strict";t.r(a);var n=t(0),r=t.n(n),l=t(11),o=t.n(l),c=(t(97),t(78)),i=t(155),s=t(159),m=t(156),d=t(157),u=t(48),g=t(158),p=t(138),E=t(81),h=t(143),y=t(136),b=t(137),f=t(63),v=t.n(f),x=t(75),S=t.n(x),C=t(133);const j=Object(C.a)(e=>({paper:{padding:e.spacing(2),display:"flex",flexDirection:"column",alignItems:"center",height:"100%",minHeight:300,transition:"all 0.3s ease"},dragActive:{border:"2px dashed #3f51b5",backgroundColor:"rgba(63, 81, 181, 0.05)"},dragInactive:{border:"2px dashed #ccc",backgroundColor:"white"},uploadBox:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",height:"100%",width:"100%",cursor:"pointer"},uploadIcon:{fontSize:60,color:"#3f51b5",marginBottom:e.spacing(2)},supportText:{marginTop:e.spacing(2)},previewBox:{display:"flex",flexDirection:"column",alignItems:"center",width:"100%",height:"100%",position:"relative"},imageContainer:{position:"relative",width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",marginTop:e.spacing(2)},deleteButton:{position:"absolute",top:0,right:0,backgroundColor:"rgba(255, 255, 255, 0.7)","&:hover":{backgroundColor:"rgba(255, 255, 255, 0.9)"}}}));var O=e=>{let{onImageUpload:a}=e;const t=j(),[l,o]=Object(n.useState)(null),[c,i]=Object(n.useState)(!1),m=Object(n.useRef)(null),d=e=>{e.preventDefault(),e.stopPropagation(),"dragenter"===e.type||"dragover"===e.type?i(!0):"dragleave"===e.type&&i(!1)},g=e=>{e.type.startsWith("image/")?(o(URL.createObjectURL(e)),a(e)):alert("Please upload an image file")};return r.a.createElement(E.a,{className:"".concat(t.paper," ").concat(c?t.dragActive:t.dragInactive),onDragEnter:d,onDragLeave:d,onDragOver:d,onDrop:e=>{e.preventDefault(),e.stopPropagation(),i(!1),e.dataTransfer.files&&e.dataTransfer.files[0]&&g(e.dataTransfer.files[0])}},r.a.createElement("input",{ref:m,type:"file",accept:"image/*",onChange:e=>{e.preventDefault(),e.target.files&&e.target.files[0]&&g(e.target.files[0])},style:{display:"none"}}),l?r.a.createElement(s.a,{className:t.previewBox},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Preview"),r.a.createElement(s.a,{className:t.imageContainer},r.a.createElement("img",{src:l,alt:"Preview",className:"preview-image"}),r.a.createElement(b.a,{"aria-label":"delete",className:t.deleteButton,onClick:()=>{o(null),a(null),m.current.value=""}},r.a.createElement(S.a,null)))):r.a.createElement(s.a,{className:t.uploadBox,onClick:()=>{m.current.click()}},r.a.createElement(v.a,{className:t.uploadIcon}),r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Drag & Drop an image here"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"or"),r.a.createElement(y.a,{variant:"contained",color:"primary",component:"span",startIcon:r.a.createElement(v.a,null)},"Browse Files"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",className:t.supportText},"Supported formats: JPG, PNG, GIF")))},w=t(139),N=t(140),B=t(165),T=t(141),I=t(64),k=t.n(I),P=t(76),D=t.n(P),A=t(77),F=t.n(A);const R=Object(C.a)(e=>({card:{height:"100%",display:"flex",flexDirection:"column"},selectedCard:{border:"2px solid #3f51b5"},unavailableCard:{opacity:.6},cardContent:{flexGrow:1},chipContainer:{marginBottom:e.spacing(1.5)},successChip:{backgroundColor:"#34C759",color:"#fff"},errorChip:{backgroundColor:"#FF3B3F",color:"#fff"},modelType:{marginTop:e.spacing(1)},processButton:{marginTop:e.spacing(3),textAlign:"center"}}));var z=e=>{let{onModelSelect:a,onProcess:t,isProcessing:n,modelsStatus:l,selectedModel:o,imageSelected:c}=e;const i=R(),m=[{id:"yolo",name:"YOLOv8",description:"Fast and accurate object detection",icon:r.a.createElement(k.a,null),available:l.yolo},{id:"detr",name:"DETR",description:"DEtection TRansformer for object detection",icon:r.a.createElement(k.a,null),available:l.detr},{id:"vit",name:"ViT",description:"Vision Transformer for image classification",icon:r.a.createElement(D.a,null),available:l.vit}],d=e=>{m.find(a=>a.id===e).available&&a(e)};return r.a.createElement(s.a,{sx:{p:2,height:"100%"}},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Select Model"),r.a.createElement(p.a,{container:!0,spacing:2},m.map(e=>r.a.createElement(p.a,{item:!0,xs:12,sm:4,key:e.id},r.a.createElement(w.a,{className:"\n ".concat(i.card," \n ").concat(o===e.id?i.selectedCard:""," \n ").concat(e.available?"":i.unavailableCard,"\n "),onClick:()=>d(e.id)},r.a.createElement(N.a,{className:i.cardContent},r.a.createElement(s.a,{sx:{mb:2,color:"primary"}},e.icon),r.a.createElement(u.a,{variant:"h5",component:"div",gutterBottom:!0},e.name),r.a.createElement("div",{className:i.chipContainer},e.available?r.a.createElement(B.a,{label:"Available",className:i.successChip,size:"small"}):r.a.createElement(B.a,{label:"Not Available",className:i.errorChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.description)),r.a.createElement(T.a,null,r.a.createElement(y.a,{size:"small",onClick:()=>d(e.id),disabled:!e.available,color:o===e.id?"primary":"default",variant:o===e.id?"contained":"outlined",fullWidth:!0},o===e.id?"Selected":"Select")))))),r.a.createElement("div",{className:i.processButton},r.a.createElement(y.a,{variant:"contained",color:"primary",size:"large",startIcon:r.a.createElement(F.a,null),onClick:t,disabled:!o||!c||n},n?"Processing...":"Process Image")))},L=t(153),M=t(150),W=t(105),_=t(154),J=t(142),U=t(163),H=t(164),V=t(145),G=t(146),Y=t(147),q=t(166),K=t(160),Q=t(151),X=t(168),Z=t(152),$=t(162);const ee=Object(C.a)(e=>({root:{marginTop:e.spacing(2),marginBottom:e.spacing(2),padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius},button:{marginRight:e.spacing(2)},searchDialog:{minWidth:"500px"},formControl:{marginBottom:e.spacing(2),minWidth:"100%"},searchResults:{marginTop:e.spacing(2)},resultCard:{marginBottom:e.spacing(2)},resultImage:{height:140,objectFit:"contain"},chip:{margin:e.spacing(.5)},similarityChip:{backgroundColor:e.palette.primary.main,color:"white"}}));var ae=e=>{let{results:a}=e;const t=ee(),[l,o]=Object(n.useState)(!1),[c,i]=Object(n.useState)(!1),[m,d]=Object(n.useState)(null),[g,E]=Object(n.useState)(!1),[b,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,C]=Object(n.useState)([]),[j,O]=Object(n.useState)(!1),[T,I]=Object(n.useState)(null),{model:k,data:P}=a,D=()=>{E(!1)},A=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){const a=16*Math.random()|0;return("x"===e?a:3&a|8).toString(16)}));return r.a.createElement(s.a,{className:t.root},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Vector Database Actions"),r.a.createElement(s.a,{display:"flex",alignItems:"center",mb:2},r.a.createElement(y.a,{variant:"contained",color:"primary",onClick:async()=>{o(!0),d(null);try{let e;if(e="vit"===k?await fetch("/api/add-to-collection",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,metadata:{model:"vit",classifications:P.classifications}})}):await fetch("/api/add-detected-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,objects:P.detections,imageId:A()})}),!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const a=await e.json();if(a.error)throw new Error(a.error);i(!0),setTimeout(()=>i(!1),5e3)}catch(e){console.error("Error saving to vector DB:",e),d("Error saving to vector DB: ".concat(e.message))}finally{o(!1)}},disabled:l,className:t.button},l?r.a.createElement(r.a.Fragment,null,r.a.createElement(h.a,{size:20,color:"inherit",style:{marginRight:8}}),"Saving..."):"Save to Vector DB"),r.a.createElement(y.a,{variant:"outlined",color:"primary",onClick:()=>{E(!0),C([]),I(null)},className:t.button},"Search Similar")),m&&r.a.createElement($.a,{severity:"error",style:{marginTop:8}},m),r.a.createElement(U.a,{open:c,autoHideDuration:5e3,onClose:()=>i(!1)},r.a.createElement($.a,{severity:"success"},"vit"===k?"Image and classifications successfully saved to vector DB!":"Detected objects successfully saved to vector DB!")),r.a.createElement(H.a,{open:g,onClose:D,maxWidth:"md",fullWidth:!0},r.a.createElement(V.a,null,"Search Vector Database"),r.a.createElement(G.a,null,r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(q.a,{id:"search-type-label"},"Search Type"),r.a.createElement(K.a,{labelId:"search-type-label",id:"search-type",value:b,onChange:e=>{f(e.target.value),C([]),I(null)}},r.a.createElement(Q.a,{value:"image"},"Search by Current Image"),r.a.createElement(Q.a,{value:"class"},"Search by Class Name"))),"class"===b&&r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(X.a,{label:"Class Name",value:v,onChange:e=>{x(e.target.value)},placeholder:"e.g. person, car, dog...",fullWidth:!0})),T&&r.a.createElement($.a,{severity:"error",style:{marginBottom:16}},T),r.a.createElement(s.a,{className:t.searchResults},j?r.a.createElement(s.a,{display:"flex",justifyContent:"center",alignItems:"center",p:4},r.a.createElement(h.a,null),r.a.createElement(u.a,{variant:"body1",style:{marginLeft:16}},"Searching...")):r.a.createElement(r.a.Fragment,null,console.log("Search dialog render - searchResults:",S),S.length>0?(console.log("Rendering search results:",S),console.log("Search results length:",S.length),0===S.length?(console.log("No results to render"),r.a.createElement(u.a,{variant:"body1"},"No results found.")):r.a.createElement(p.a,{container:!0,spacing:2},S.map((e,a)=>{const n=100*(1-e.distance);return r.a.createElement(p.a,{item:!0,xs:12,sm:6,key:a},r.a.createElement(w.a,{className:t.resultCard},e.metadata&&e.metadata.image_data?r.a.createElement(J.a,{className:t.resultImage,component:"img",height:"200",image:"data:image/jpeg;base64,".concat(e.metadata.image_data),alt:e.metadata&&e.metadata.class?e.metadata.class:"Object"}):r.a.createElement(s.a,{className:t.resultImage,style:{backgroundColor:"#f0f0f0",display:"flex",alignItems:"center",justifyContent:"center",height:200}},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.metadata&&e.metadata.class?e.metadata.class:"Object"," Image")),r.a.createElement(N.a,null,r.a.createElement(s.a,{display:"flex",justifyContent:"space-between",alignItems:"center",mb:1},r.a.createElement(u.a,{variant:"subtitle1"},"Result #",a+1),r.a.createElement(B.a,{label:"Similarity: ".concat(n.toFixed(2),"%"),className:t.similarityChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Class:")," ",e.metadata.class||"N/A"),e.metadata.confidence&&r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Confidence:")," ",(100*e.metadata.confidence).toFixed(2),"%"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Object ID:")," ",e.id))))}))):r.a.createElement(u.a,{variant:"body1"},"No results found. Please try another search.")))),r.a.createElement(Z.a,null,r.a.createElement(y.a,{onClick:D,color:"default"},"Close"),r.a.createElement(y.a,{onClick:async()=>{O(!0),I(null);try{let e={};if("image"===b)e={searchType:"image",image:P.image,n_results:5};else{if(!v.trim())throw new Error("Please enter a class name");e={searchType:"class",class_name:v.trim(),n_results:5}}const a=await fetch("/api/search-similar-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!a.ok)throw new Error("HTTP error! Status: ".concat(a.status));const t=await a.json();if(t.error)throw new Error(t.error);if(console.log("Search API response:",t),!t.success||!Array.isArray(t.results))throw console.error("Unexpected API response format:",t),new Error("Unexpected API response format");console.log("Setting search results array:",t.results),console.log("Results array length:",t.results.length),console.log("First result item:",t.results[0]),C(t.results)}catch(e){console.error("Error searching vector DB:",e),I("Error searching vector DB: ".concat(e.message))}finally{O(!1)}},color:"primary",variant:"contained",disabled:j||"class"===b&&!v.trim()},"Search"))))};const te=Object(C.a)(e=>({paper:{padding:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},resultImage:{maxWidth:"100%",maxHeight:"400px",objectFit:"contain"},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},chipContainer:{display:"flex",gap:e.spacing(1),flexWrap:"wrap"}}));var ne=e=>{let{results:a}=e;const t=te();if(!a)return null;const{model:n,data:l}=a;if(l.error)return r.a.createElement(E.a,{sx:{p:2,bgcolor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},l.error));const o=()=>l.performance?r.a.createElement(s.a,{className:"performance-info"},r.a.createElement(L.a,{className:t.dividerMargin}),r.a.createElement(u.a,{variant:"body2"},"Inference time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(l.performance.inference_time)," on ",l.performance.device)):null;return"yolo"===n||"detr"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"yolo"===n?"YOLOv8":"DETR"," Detection Results"),r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12,md:6},l.image&&r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detection Result"),r.a.createElement("img",{src:"data:image/png;base64,".concat(l.image),alt:"Detection Result",className:t.resultImage}))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detected Objects:"),l.detections&&l.detections.length>0?r.a.createElement(M.a,null,l.detections.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(_.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.class),r.a.createElement(B.a,{label:"".concat((100*e.confidence).toFixed(0),"%"),size:"small",color:"primary",style:{marginLeft:8}})),secondary:"Bounding Box: [".concat(e.bbox.join(", "),"]")})),a<l.detections.length-1&&r.a.createElement(L.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No objects detected")))),o(),r.a.createElement(ae,{results:a})):"vit"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"ViT Classification Results"),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Top Predictions:"),l.top_predictions&&l.top_predictions.length>0?r.a.createElement(M.a,null,l.top_predictions.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(_.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.rank,". ",e.class),r.a.createElement(B.a,{label:"".concat((100*e.probability).toFixed(1),"%"),size:"small",color:0===a?"primary":"default",style:{marginLeft:8}}))})),a<l.top_predictions.length-1&&r.a.createElement(L.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No classifications available"),o(),r.a.createElement(ae,{results:a})):null};const re=Object(C.a)(e=>({paper:{padding:e.spacing(2),marginTop:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},responseBox:{padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius,marginTop:e.spacing(2),whiteSpace:"pre-wrap"},buttonProgress:{marginLeft:e.spacing(1)}}));var le=e=>{let{visionResults:a,model:t}=e;const l=re(),[o,c]=Object(n.useState)(""),[i,m]=Object(n.useState)(!1),[d,g]=Object(n.useState)(null),[p,b]=Object(n.useState)(null);return a?r.a.createElement(E.a,{className:l.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Ask AI about the ","vit"===t?"Classification":"Detection"," Results"),r.a.createElement(u.a,{variant:"body2",className:l.marginBottom},"Ask a question about the detected objects or classifications to get an AI-powered analysis."),r.a.createElement(X.a,{fullWidth:!0,label:"Your question about the image",variant:"outlined",value:o,onChange:e=>c(e.target.value),disabled:i,className:l.marginBottom,placeholder:"vit"===t?"E.g., What category does this image belong to?":"E.g., How many people are in this image?"}),r.a.createElement(y.a,{variant:"contained",color:"primary",onClick:async()=>{if(o.trim()){m(!0),b(null);try{const e=await fetch("/api/analyze",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({visionResults:a,userQuery:o})});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const t=await e.json();t.error?b(t.error):g(t)}catch(e){console.error("Error analyzing with LLM:",e),b("Error analyzing with LLM: ".concat(e.message))}finally{m(!1)}}},disabled:i||!o.trim()},"Analyze with AI",i&&r.a.createElement(h.a,{size:24,className:l.buttonProgress})),p&&r.a.createElement(s.a,{mt:2},r.a.createElement(u.a,{color:"error"},p)),d&&r.a.createElement(r.a.Fragment,null,r.a.createElement(L.a,{className:l.dividerMargin}),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"AI Analysis:"),r.a.createElement(s.a,{className:l.responseBox},r.a.createElement(u.a,{variant:"body1"},d.response)),d.performance&&r.a.createElement(s.a,{mt:1},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},"Analysis time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(d.performance.inference_time)," on ",d.performance.device)))):null};var oe=function(){const[e,a]=Object(n.useState)("gpt-4o-mini"),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)("You are a helpful assistant."),[i,s]=Object(n.useState)(""),[m,d]=Object(n.useState)(""),[g,h]=Object(n.useState)(!1),[b,f]=Object(n.useState)("");return r.a.createElement(E.a,{style:{padding:16}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"OpenAI Chat (OpenAI API)"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"If the server env var OPENAI_API_KEY is set, the API Key field is optional."),r.a.createElement(p.a,{container:!0,spacing:2},r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"Model",value:e,onChange:e=>a(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"OpenAI API Key (optional)",value:t,onChange:e=>l(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"password",placeholder:"sk-..."})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"System Prompt (optional)",value:o,onChange:e=>c(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"User Question",value:i,onChange:e=>s(e.target.value),fullWidth:!0,multiline:!0,rows:4,variant:"outlined"})),b&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(u.a,{color:"error"},b)),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement("div",{style:{display:"flex",gap:8}},r.a.createElement(y.a,{color:"primary",variant:"contained",onClick:async()=>{f(""),d("");const a=(i||"").trim();if(a){h(!0);try{const r=await fetch("/api/openai/chat",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({prompt:a,model:(e||"").trim()||"gpt-4o-mini",api_key:t||void 0,system:o||void 0})});if(!r.ok){let e=await r.text();try{e=JSON.stringify(JSON.parse(e),null,2)}catch(n){}throw new Error(e)}const l=await r.json(),c="Model: ".concat(l.model," | Latency: ").concat(l.latency_sec,"s")+(l.usage?" | Usage: ".concat(JSON.stringify(l.usage)):"");d((l.response||"(Empty response)")+"\n\n---\n"+c)}catch(r){f("Error: "+r.message)}finally{h(!1)}}else f("Please enter a question.")},disabled:g},g?"Sending...":"Send Question"),r.a.createElement(y.a,{variant:"outlined",onClick:()=>{s(""),d(""),f("")}},"Clear"))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(L.a,{style:{margin:"12px 0"}}),r.a.createElement(u.a,{variant:"subtitle2",color:"textSecondary"},"Response"),r.a.createElement("pre",{style:{whiteSpace:"pre-wrap",fontFamily:"ui-monospace, monospace"}},m))))};t(102);const ce=Object(c.a)({palette:{primary:{main:"#3f51b5"},secondary:{main:"#f50057"}},typography:{fontFamily:"Roboto, Arial, sans-serif"}});var ie=function(){const[e,a]=Object(n.useState)(null),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(!1),[y,b]=Object(n.useState)(null),[f,v]=Object(n.useState)(null),[x,S]=Object(n.useState)({yolo:!1,detr:!1,vit:!1});return Object(n.useEffect)(()=>{fetch("/api/status").then(e=>e.json()).then(e=>{S(e.models)}).catch(e=>{console.error("Error checking API status:",e),v("Error connecting to the backend API. Please make sure the server is running.")})},[]),r.a.createElement(i.a,{theme:ce},r.a.createElement(s.a,{style:{flexGrow:1}},r.a.createElement(m.a,{position:"static"},r.a.createElement(d.a,null,r.a.createElement(u.a,{variant:"h6",style:{flexGrow:1}},"Multi-Model Object Detection Demo"))),r.a.createElement(g.a,{maxWidth:"lg",style:{marginTop:ce.spacing(4),marginBottom:ce.spacing(4)}},r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2)}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Upload an image to see how each model performs!"),r.a.createElement(u.a,{variant:"body1",paragraph:!0},"This demo showcases three different object detection and image classification models:"),r.a.createElement(u.a,{variant:"body1",component:"div"},r.a.createElement("ul",null,r.a.createElement("li",null,r.a.createElement("strong",null,"YOLOv8"),": Fast and accurate object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"DETR"),": DEtection TRansformer for object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"ViT"),": Vision Transformer for image classification"))))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(O,{onImageUpload:e=>{a(e),b(null),v(null)}})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(z,{onModelSelect:e=>{l(e),b(null),v(null)},onProcess:async()=>{if(!e||!t)return void v("Please select both an image and a model");c(!0),v(null);const a=new FormData;a.append("image",e);let n="";switch(t){case"yolo":n="/api/detect/yolo";break;case"detr":n="/api/detect/detr";break;case"vit":n="/api/classify/vit";break;default:return v("Invalid model selection"),void c(!1)}try{const e=await fetch(n,{method:"POST",body:a});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const r=await e.json();b({model:t,data:r})}catch(r){console.error("Error processing image:",r),v("Error processing image: ".concat(r.message))}finally{c(!1)}},isProcessing:o,modelsStatus:x,selectedModel:t,imageSelected:!!e})),f&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2),backgroundColor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},f))),o&&r.a.createElement(p.a,{item:!0,xs:12,style:{textAlign:"center",margin:"".concat(ce.spacing(4),"px 0")}},r.a.createElement(h.a,null),r.a.createElement(u.a,{variant:"h6",style:{marginTop:ce.spacing(2)}},"Processing image...")),y&&r.a.createElement(r.a.Fragment,null,r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(ne,{results:y})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(le,{visionResults:y.data,model:y.model}))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(oe,null))))))};var se=e=>{e&&e instanceof Function&&t.e(3).then(t.bind(null,170)).then(a=>{let{getCLS:t,getFID:n,getFCP:r,getLCP:l,getTTFB:o}=a;t(e),n(e),r(e),l(e),o(e)})};o.a.render(r.a.createElement(r.a.StrictMode,null,r.a.createElement(ie,null)),document.getElementById("root")),se()},92:function(e,a,t){e.exports=t(103)},97:function(e,a,t){}},[[92,1,2]]]);
|
2 |
+
//# sourceMappingURL=main.db88b5b1.chunk.js.map
|
static/static/js/main.db88b5b1.chunk.js.map
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"version":3,"sources":["components/ImageUploader.js","components/ModelSelector.js","components/VectorDBActions.js","components/ResultDisplay.js","components/LlmAnalysis.js","components/OpenAIChat.js","App.js","reportWebVitals.js","index.js"],"names":["useStyles","makeStyles","theme","paper","padding","spacing","display","flexDirection","alignItems","height","minHeight","transition","dragActive","border","backgroundColor","dragInactive","uploadBox","justifyContent","width","cursor","uploadIcon","fontSize","color","marginBottom","supportText","marginTop","previewBox","position","imageContainer","overflow","deleteButton","top","right","ImageUploader","_ref","onImageUpload","classes","previewUrl","setPreviewUrl","useState","setDragActive","fileInputRef","useRef","handleDrag","e","preventDefault","stopPropagation","type","handleFiles","file","startsWith","URL","createObjectURL","alert","React","createElement","Paper","className","concat","onDragEnter","onDragLeave","onDragOver","onDrop","dataTransfer","files","ref","accept","onChange","target","style","Box","Typography","variant","gutterBottom","src","alt","IconButton","aria-label","onClick","handleRemoveImage","current","value","DeleteIcon","onButtonClick","click","CloudUploadIcon","Button","component","startIcon","card","selectedCard","unavailableCard","opacity","cardContent","flexGrow","chipContainer","successChip","errorChip","modelType","processButton","textAlign","ModelSelector","onModelSelect","onProcess","isProcessing","modelsStatus","selectedModel","imageSelected","models","id","name","description","icon","VisibilityIcon","available","yolo","detr","CategoryIcon","vit","handleModelClick","modelId","find","m","sx","p","Grid","container","map","model","item","xs","sm","key","Card","CardContent","mb","Chip","label","size","CardActions","disabled","fullWidth","PlayArrowIcon","root","borderRadius","shape","button","marginRight","searchDialog","minWidth","formControl","searchResults","resultCard","resultImage","objectFit","chip","margin","similarityChip","palette","primary","main","VectorDBActions","results","isSaving","setIsSaving","saveSuccess","setSaveSuccess","saveError","setSaveError","openSearchDialog","setOpenSearchDialog","searchType","setSearchType","searchClass","setSearchClass","setSearchResults","isSearching","setIsSearching","searchError","setSearchError","data","handleCloseSearchDialog","generateUUID","replace","c","r","Math","random","toString","async","response","fetch","method","headers","body","JSON","stringify","image","metadata","classifications","objects","detections","imageId","ok","Error","status","result","json","error","setTimeout","err","console","message","Fragment","CircularProgress","handleOpenSearchDialog","Alert","severity","Snackbar","open","autoHideDuration","onClose","Dialog","maxWidth","DialogTitle","DialogContent","FormControl","InputLabel","Select","labelId","event","MenuItem","TextField","placeholder","marginLeft","log","length","index","similarity","distance","image_data","CardMedia","class","toFixed","confidence","DialogActions","requestBody","n_results","trim","class_name","success","Array","isArray","maxHeight","dividerMargin","gap","flexWrap","ResultDisplay","bgcolor","renderPerformanceInfo","performance","Divider","ms","undefined","isNaN","num","Number","formatTime","inference_time","device","md","List","detection","ListItem","ListItemText","secondary","bbox","join","top_predictions","prediction","rank","probability","responseBox","whiteSpace","buttonProgress","LlmAnalysis","visionResults","userQuery","setUserQuery","isAnalyzing","setIsAnalyzing","analysisResult","setAnalysisResult","setError","mt","OpenAIChat","setModel","apiKey","setApiKey","system","setSystem","prompt","setPrompt","setResponse","loading","setLoading","multiline","rows","res","credentials","api_key","txt","text","parse","_unused","meta","latency_sec","usage","onClear","fontFamily","createMuiTheme","typography","App","selectedImage","setSelectedImage","setSelectedModel","setIsProcessing","setResults","setModelsStatus","useEffect","then","catch","ThemeProvider","AppBar","Toolbar","Container","paragraph","formData","FormData","append","endpoint","reportWebVitals","onPerfEntry","Function","getCLS","getFID","getFCP","getLCP","getTTFB","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"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","file":"static/js/main.db88b5b1.chunk.js","sourcesContent":["import React, { useState, useRef } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n Button, \n IconButton \n} from '@material-ui/core';\nimport CloudUploadIcon from '@material-ui/icons/CloudUpload';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n height: '100%',\n minHeight: 300,\n transition: 'all 0.3s ease'\n },\n dragActive: {\n border: '2px dashed #3f51b5',\n backgroundColor: 'rgba(63, 81, 181, 0.05)'\n },\n dragInactive: {\n border: '2px dashed #ccc',\n backgroundColor: 'white'\n },\n uploadBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n width: '100%',\n cursor: 'pointer'\n },\n uploadIcon: {\n fontSize: 60,\n color: '#3f51b5',\n marginBottom: theme.spacing(2)\n },\n supportText: {\n marginTop: theme.spacing(2)\n },\n previewBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n position: 'relative'\n },\n imageContainer: {\n position: 'relative',\n width: '100%',\n height: '100%',\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n overflow: 'hidden',\n marginTop: theme.spacing(2)\n },\n deleteButton: {\n position: 'absolute',\n top: 0,\n right: 0,\n backgroundColor: 'rgba(255, 255, 255, 0.7)',\n '&:hover': {\n backgroundColor: 'rgba(255, 255, 255, 0.9)',\n }\n }\n}));\n\nconst ImageUploader = ({ onImageUpload }) => {\n const classes = useStyles();\n const [previewUrl, setPreviewUrl] = useState(null);\n const [dragActive, setDragActive] = useState(false);\n const fileInputRef = useRef(null);\n\n const handleDrag = (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (e.type === 'dragenter' || e.type === 'dragover') {\n setDragActive(true);\n } else if (e.type === 'dragleave') {\n setDragActive(false);\n }\n };\n\n const handleDrop = (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragActive(false);\n if (e.dataTransfer.files && e.dataTransfer.files[0]) {\n handleFiles(e.dataTransfer.files[0]);\n }\n };\n\n const handleChange = (e) => {\n e.preventDefault();\n if (e.target.files && e.target.files[0]) {\n handleFiles(e.target.files[0]);\n }\n };\n\n const handleFiles = (file) => {\n if (file.type.startsWith('image/')) {\n setPreviewUrl(URL.createObjectURL(file));\n onImageUpload(file);\n } else {\n alert('Please upload an image file');\n }\n };\n\n const onButtonClick = () => {\n fileInputRef.current.click();\n };\n\n const handleRemoveImage = () => {\n setPreviewUrl(null);\n onImageUpload(null);\n fileInputRef.current.value = \"\";\n };\n\n return (\n <Paper \n className={`${classes.paper} ${dragActive ? classes.dragActive : classes.dragInactive}`}\n onDragEnter={handleDrag}\n onDragLeave={handleDrag}\n onDragOver={handleDrag}\n onDrop={handleDrop}\n >\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleChange}\n style={{ display: 'none' }}\n />\n\n {!previewUrl ? (\n <Box \n className={classes.uploadBox}\n onClick={onButtonClick}\n >\n <CloudUploadIcon className={classes.uploadIcon} />\n <Typography variant=\"h6\" gutterBottom>\n Drag & Drop an image here\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n or\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n component=\"span\"\n startIcon={<CloudUploadIcon />}\n >\n Browse Files\n </Button>\n <Typography variant=\"body2\" color=\"textSecondary\" className={classes.supportText}>\n Supported formats: JPG, PNG, GIF\n </Typography>\n </Box>\n ) : (\n <Box className={classes.previewBox}>\n <Typography variant=\"h6\" gutterBottom>\n Preview\n </Typography>\n <Box className={classes.imageContainer}>\n <img\n src={previewUrl}\n alt=\"Preview\"\n className=\"preview-image\"\n />\n <IconButton\n aria-label=\"delete\"\n className={classes.deleteButton}\n onClick={handleRemoveImage}\n >\n <DeleteIcon />\n </IconButton>\n </Box>\n </Box>\n )}\n </Paper>\n );\n};\n\nexport default ImageUploader;\n","import React from 'react';\nimport { \n Grid, \n Card, \n CardContent, \n CardActions, \n Typography, \n Button, \n Chip,\n Box\n} from '@material-ui/core';\nimport VisibilityIcon from '@material-ui/icons/Visibility';\nimport CategoryIcon from '@material-ui/icons/Category';\nimport PlayArrowIcon from '@material-ui/icons/PlayArrow';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n card: {\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n },\n selectedCard: {\n border: '2px solid #3f51b5',\n },\n unavailableCard: {\n opacity: 0.6,\n },\n cardContent: {\n flexGrow: 1,\n },\n chipContainer: {\n marginBottom: theme.spacing(1.5),\n },\n successChip: {\n backgroundColor: '#34C759',\n color: '#fff',\n },\n errorChip: {\n backgroundColor: '#FF3B3F',\n color: '#fff',\n },\n modelType: {\n marginTop: theme.spacing(1),\n },\n processButton: {\n marginTop: theme.spacing(3),\n textAlign: 'center',\n }\n}));\n\nconst ModelSelector = ({ \n onModelSelect, \n onProcess, \n isProcessing, \n modelsStatus, \n selectedModel,\n imageSelected \n}) => {\n const classes = useStyles();\n \n const models = [\n {\n id: 'yolo',\n name: 'YOLOv8',\n description: 'Fast and accurate object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.yolo\n },\n {\n id: 'detr',\n name: 'DETR',\n description: 'DEtection TRansformer for object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.detr\n },\n {\n id: 'vit',\n name: 'ViT',\n description: 'Vision Transformer for image classification',\n icon: <CategoryIcon />,\n available: modelsStatus.vit\n }\n ];\n\n const handleModelClick = (modelId) => {\n if (models.find(m => m.id === modelId).available) {\n onModelSelect(modelId);\n }\n };\n\n return (\n <Box sx={{ p: 2, height: '100%' }}>\n <Typography variant=\"h6\" gutterBottom>\n Select Model\n </Typography>\n \n <Grid container spacing={2}>\n {models.map((model) => (\n <Grid item xs={12} sm={4} key={model.id}>\n <Card \n className={`\n ${classes.card} \n ${selectedModel === model.id ? classes.selectedCard : ''} \n ${!model.available ? classes.unavailableCard : ''}\n `}\n onClick={() => handleModelClick(model.id)}\n >\n <CardContent className={classes.cardContent}>\n <Box sx={{ mb: 2, color: 'primary' }}>\n {model.icon}\n </Box>\n <Typography variant=\"h5\" component=\"div\" gutterBottom>\n {model.name}\n </Typography>\n <div className={classes.chipContainer}>\n {model.available ? (\n <Chip \n label=\"Available\" \n className={classes.successChip}\n size=\"small\" \n />\n ) : (\n <Chip \n label=\"Not Available\" \n className={classes.errorChip}\n size=\"small\" \n />\n )}\n </div>\n <Typography variant=\"body2\" color=\"textSecondary\">\n {model.description}\n </Typography>\n </CardContent>\n <CardActions>\n <Button \n size=\"small\" \n onClick={() => handleModelClick(model.id)}\n disabled={!model.available}\n color={selectedModel === model.id ? \"primary\" : \"default\"}\n variant={selectedModel === model.id ? \"contained\" : \"outlined\"}\n fullWidth\n >\n {selectedModel === model.id ? 'Selected' : 'Select'}\n </Button>\n </CardActions>\n </Card>\n </Grid>\n ))}\n </Grid>\n\n <div className={classes.processButton}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"large\"\n startIcon={<PlayArrowIcon />}\n onClick={onProcess}\n disabled={!selectedModel || !imageSelected || isProcessing}\n >\n {isProcessing ? 'Processing...' : 'Process Image'}\n </Button>\n </div>\n </Box>\n );\n};\n\nexport default ModelSelector;\n","import React, { useState } from 'react';\nimport { \n Button, \n Box, \n Typography, \n CircularProgress, \n Snackbar,\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n TextField,\n FormControl,\n InputLabel,\n Select,\n MenuItem,\n Grid,\n Card,\n CardMedia,\n CardContent,\n Chip\n} from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n marginTop: theme.spacing(2),\n marginBottom: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n },\n button: {\n marginRight: theme.spacing(2),\n },\n searchDialog: {\n minWidth: '500px',\n },\n formControl: {\n marginBottom: theme.spacing(2),\n minWidth: '100%',\n },\n searchResults: {\n marginTop: theme.spacing(2),\n },\n resultCard: {\n marginBottom: theme.spacing(2),\n },\n resultImage: {\n height: 140,\n objectFit: 'contain',\n },\n chip: {\n margin: theme.spacing(0.5),\n },\n similarityChip: {\n backgroundColor: theme.palette.primary.main,\n color: 'white',\n }\n}));\n\nconst VectorDBActions = ({ results }) => {\n const classes = useStyles();\n const [isSaving, setIsSaving] = useState(false);\n const [saveSuccess, setSaveSuccess] = useState(false);\n const [saveError, setSaveError] = useState(null);\n const [openSearchDialog, setOpenSearchDialog] = useState(false);\n const [searchType, setSearchType] = useState('image');\n const [searchClass, setSearchClass] = useState('');\n const [searchResults, setSearchResults] = useState([]);\n const [isSearching, setIsSearching] = useState(false);\n const [searchError, setSearchError] = useState(null);\n \n // Extract model and data from results\n const { model, data } = results;\n \n // Handle saving to vector DB\n const handleSaveToVectorDB = async () => {\n setIsSaving(true);\n setSaveError(null);\n \n try {\n let response;\n \n if (model === 'vit') {\n // For ViT, save the whole image with classifications\n response = await fetch('/api/add-to-collection', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n metadata: {\n model: 'vit',\n classifications: data.classifications\n }\n })\n });\n } else {\n // For YOLO and DETR, save detected objects\n response = await fetch('/api/add-detected-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n objects: data.detections,\n imageId: generateUUID()\n })\n });\n }\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n setSaveSuccess(true);\n setTimeout(() => setSaveSuccess(false), 5000);\n } catch (err) {\n console.error('Error saving to vector DB:', err);\n setSaveError(`Error saving to vector DB: ${err.message}`);\n } finally {\n setIsSaving(false);\n }\n };\n \n // Handle opening search dialog\n const handleOpenSearchDialog = () => {\n setOpenSearchDialog(true);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle closing search dialog\n const handleCloseSearchDialog = () => {\n setOpenSearchDialog(false);\n };\n \n // Handle search type change\n const handleSearchTypeChange = (event) => {\n setSearchType(event.target.value);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle search class change\n const handleSearchClassChange = (event) => {\n setSearchClass(event.target.value);\n };\n \n // Handle search\n const handleSearch = async () => {\n setIsSearching(true);\n setSearchError(null);\n \n try {\n let requestBody = {};\n \n if (searchType === 'image') {\n // Search by current image\n requestBody = {\n searchType: 'image',\n image: data.image,\n n_results: 5\n };\n } else {\n // Search by class name\n if (!searchClass.trim()) {\n throw new Error('Please enter a class name');\n }\n \n requestBody = {\n searchType: 'class',\n class_name: searchClass.trim(),\n n_results: 5\n };\n }\n \n const response = await fetch('/api/search-similar-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody)\n });\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n console.log('Search API response:', result);\n \n // The backend responds with {success, searchType, results} structure, so extract only the results array\n if (result.success && Array.isArray(result.results)) {\n console.log('Setting search results array:', result.results);\n console.log('Results array length:', result.results.length);\n console.log('First result item:', result.results[0]);\n setSearchResults(result.results);\n } else {\n console.error('Unexpected API response format:', result);\n throw new Error('Unexpected API response format');\n }\n } catch (err) {\n console.error('Error searching vector DB:', err);\n setSearchError(`Error searching vector DB: ${err.message}`);\n } finally {\n setIsSearching(false);\n }\n };\n \n // Generate UUID for image ID\n const generateUUID = () => {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n };\n \n // Render search results\n const renderSearchResults = () => {\n console.log('Rendering search results:', searchResults);\n console.log('Search results length:', searchResults.length);\n \n if (searchResults.length === 0) {\n console.log('No results to render');\n return (\n <Typography variant=\"body1\">No results found.</Typography>\n );\n }\n \n return (\n <Grid container spacing={2}>\n {searchResults.map((result, index) => {\n const similarity = (1 - result.distance) * 100;\n \n return (\n <Grid item xs={12} sm={6} key={index}>\n <Card className={classes.resultCard}>\n {result.metadata && result.metadata.image_data ? (\n <CardMedia\n className={classes.resultImage}\n component=\"img\"\n height=\"200\"\n image={`data:image/jpeg;base64,${result.metadata.image_data}`}\n alt={result.metadata && result.metadata.class ? result.metadata.class : 'Object'}\n />\n ) : (\n <Box \n className={classes.resultImage}\n style={{ \n backgroundColor: '#f0f0f0', \n display: 'flex', \n alignItems: 'center', \n justifyContent: 'center',\n height: 200\n }}\n >\n <Typography variant=\"body2\" color=\"textSecondary\">\n {result.metadata && result.metadata.class ? result.metadata.class : 'Object'} Image\n </Typography>\n </Box>\n )}\n <CardContent>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\" mb={1}>\n <Typography variant=\"subtitle1\">Result #{index + 1}</Typography>\n <Chip \n label={`Similarity: ${similarity.toFixed(2)}%`}\n className={classes.similarityChip}\n size=\"small\"\n />\n </Box>\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Class:</strong> {result.metadata.class || 'N/A'}\n </Typography>\n {result.metadata.confidence && (\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Confidence:</strong> {(result.metadata.confidence * 100).toFixed(2)}%\n </Typography>\n )}\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Object ID:</strong> {result.id}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n );\n })}\n </Grid>\n );\n };\n \n return (\n <Box className={classes.root}>\n <Typography variant=\"h6\" gutterBottom>\n Vector Database Actions\n </Typography>\n \n <Box display=\"flex\" alignItems=\"center\" mb={2}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={handleSaveToVectorDB}\n disabled={isSaving}\n className={classes.button}\n >\n {isSaving ? (\n <>\n <CircularProgress size={20} color=\"inherit\" style={{ marginRight: 8 }} />\n Saving...\n </>\n ) : (\n 'Save to Vector DB'\n )}\n </Button>\n \n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={handleOpenSearchDialog}\n className={classes.button}\n >\n Search Similar\n </Button>\n </Box>\n \n {saveError && (\n <Alert severity=\"error\" style={{ marginTop: 8 }}>\n {saveError}\n </Alert>\n )}\n \n <Snackbar open={saveSuccess} autoHideDuration={5000} onClose={() => setSaveSuccess(false)}>\n <Alert severity=\"success\">\n {model === 'vit' ? (\n 'Image and classifications successfully saved to vector DB!'\n ) : (\n 'Detected objects successfully saved to vector DB!'\n )}\n </Alert>\n </Snackbar>\n \n {/* Search Dialog */}\n <Dialog\n open={openSearchDialog}\n onClose={handleCloseSearchDialog}\n maxWidth=\"md\"\n fullWidth\n >\n <DialogTitle>Search Vector Database</DialogTitle>\n <DialogContent>\n <FormControl className={classes.formControl}>\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n id=\"search-type\"\n value={searchType}\n onChange={handleSearchTypeChange}\n >\n <MenuItem value=\"image\">Search by Current Image</MenuItem>\n <MenuItem value=\"class\">Search by Class Name</MenuItem>\n </Select>\n </FormControl>\n \n {searchType === 'class' && (\n <FormControl className={classes.formControl}>\n <TextField\n label=\"Class Name\"\n value={searchClass}\n onChange={handleSearchClassChange}\n placeholder=\"e.g. person, car, dog...\"\n fullWidth\n />\n </FormControl>\n )}\n \n {searchError && (\n <Alert severity=\"error\" style={{ marginBottom: 16 }}>\n {searchError}\n </Alert>\n )}\n \n <Box className={classes.searchResults}>\n {isSearching ? (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" p={4}>\n <CircularProgress />\n <Typography variant=\"body1\" style={{ marginLeft: 16 }}>\n Searching...\n </Typography>\n </Box>\n ) : (\n <>\n {console.log('Search dialog render - searchResults:', searchResults)}\n {searchResults.length > 0 ? renderSearchResults() : \n <Typography variant=\"body1\">No results found. Please try another search.</Typography>\n }\n </>\n )}\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={handleCloseSearchDialog} color=\"default\">\n Close\n </Button>\n <Button \n onClick={handleSearch} \n color=\"primary\" \n variant=\"contained\"\n disabled={isSearching || (searchType === 'class' && !searchClass.trim())}\n >\n Search\n </Button>\n </DialogActions>\n </Dialog>\n </Box>\n );\n};\n\nexport default VectorDBActions;\n","import React from 'react';\nimport { \n Paper, \n Typography, \n Box, \n List, \n ListItem, \n ListItemText, \n Divider,\n Grid,\n Chip\n} from '@material-ui/core';\nimport VectorDBActions from './VectorDBActions';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n resultImage: {\n maxWidth: '100%',\n maxHeight: '400px',\n objectFit: 'contain'\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n chipContainer: {\n display: 'flex',\n gap: theme.spacing(1),\n flexWrap: 'wrap'\n }\n}));\n\nconst ResultDisplay = ({ results }) => {\n const classes = useStyles();\n if (!results) return null;\n \n const { model, data } = results;\n \n // Helper to format times nicely\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n \n // Check if there's an error\n if (data.error) {\n return (\n <Paper sx={{ p: 2, bgcolor: '#ffebee' }}>\n <Typography color=\"error\">{data.error}</Typography>\n </Paper>\n );\n }\n\n // Display performance info\n const renderPerformanceInfo = () => {\n if (!data.performance) return null;\n \n return (\n <Box className=\"performance-info\">\n <Divider className={classes.dividerMargin} />\n <Typography variant=\"body2\">\n Inference time: {formatTime(data.performance.inference_time)} on {data.performance.device}\n </Typography>\n </Box>\n );\n };\n\n // Render for YOLO and DETR (object detection)\n if (model === 'yolo' || model === 'detr') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n {model === 'yolo' ? 'YOLOv8' : 'DETR'} Detection Results\n </Typography>\n \n <Grid container spacing={3}>\n <Grid item xs={12} md={6}>\n {data.image && (\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detection Result\n </Typography>\n <img \n src={`data:image/png;base64,${data.image}`} \n alt=\"Detection Result\" \n className={classes.resultImage}\n />\n </Box>\n )}\n </Grid>\n \n <Grid item xs={12} md={6}>\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detected Objects:\n </Typography>\n \n {data.detections && data.detections.length > 0 ? (\n <List>\n {data.detections.map((detection, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {detection.class}\n </Typography>\n <Chip \n label={`${(detection.confidence * 100).toFixed(0)}%`}\n size=\"small\"\n color=\"primary\"\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n secondary={`Bounding Box: [${detection.bbox.join(', ')}]`} \n />\n </ListItem>\n {index < data.detections.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No objects detected</Typography>\n )}\n </Box>\n </Grid>\n </Grid>\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for Object Detection */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n // Render for ViT (classification)\n if (model === 'vit') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n ViT Classification Results\n </Typography>\n \n <Typography variant=\"subtitle1\" gutterBottom>\n Top Predictions:\n </Typography>\n \n {data.top_predictions && data.top_predictions.length > 0 ? (\n <List>\n {data.top_predictions.map((prediction, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {prediction.rank}. {prediction.class}\n </Typography>\n <Chip \n label={`${(prediction.probability * 100).toFixed(1)}%`}\n size=\"small\"\n color={index === 0 ? \"primary\" : \"default\"}\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n />\n </ListItem>\n {index < data.top_predictions.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No classifications available</Typography>\n )}\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for ViT Classification */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n return null;\n};\n\nexport default ResultDisplay;\n","import React, { useState } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n TextField, \n Button, \n CircularProgress,\n Divider\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n marginTop: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n responseBox: {\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n marginTop: theme.spacing(2),\n whiteSpace: 'pre-wrap'\n },\n buttonProgress: {\n marginLeft: theme.spacing(1)\n }\n}));\n\nconst LlmAnalysis = ({ visionResults, model }) => {\n const classes = useStyles();\n const [userQuery, setUserQuery] = useState('');\n const [isAnalyzing, setIsAnalyzing] = useState(false);\n const [analysisResult, setAnalysisResult] = useState(null);\n const [error, setError] = useState(null);\n\n // Format time for display\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n\n const handleAnalyze = async () => {\n if (!userQuery.trim()) return;\n \n setIsAnalyzing(true);\n setError(null);\n \n try {\n const response = await fetch('/api/analyze', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n visionResults: visionResults,\n userQuery: userQuery\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n \n if (data.error) {\n setError(data.error);\n } else {\n setAnalysisResult(data);\n }\n } catch (err) {\n console.error('Error analyzing with LLM:', err);\n setError(`Error analyzing with LLM: ${err.message}`);\n } finally {\n setIsAnalyzing(false);\n }\n };\n\n if (!visionResults) return null;\n\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n Ask AI about the {model === 'vit' ? 'Classification' : 'Detection'} Results\n </Typography>\n \n <Typography variant=\"body2\" className={classes.marginBottom}>\n Ask a question about the detected objects or classifications to get an AI-powered analysis.\n </Typography>\n \n <TextField\n fullWidth\n label=\"Your question about the image\"\n variant=\"outlined\"\n value={userQuery}\n onChange={(e) => setUserQuery(e.target.value)}\n disabled={isAnalyzing}\n className={classes.marginBottom}\n placeholder={model === 'vit' \n ? \"E.g., What category does this image belong to?\" \n : \"E.g., How many people are in this image?\"}\n />\n \n <Button \n variant=\"contained\" \n color=\"primary\"\n onClick={handleAnalyze}\n disabled={isAnalyzing || !userQuery.trim()}\n >\n Analyze with AI\n {isAnalyzing && <CircularProgress size={24} className={classes.buttonProgress} />}\n </Button>\n \n {error && (\n <Box mt={2}>\n <Typography color=\"error\">{error}</Typography>\n </Box>\n )}\n \n {analysisResult && (\n <>\n <Divider className={classes.dividerMargin} />\n \n <Typography variant=\"subtitle1\" gutterBottom>\n AI Analysis:\n </Typography>\n \n <Box className={classes.responseBox}>\n <Typography variant=\"body1\">\n {analysisResult.response}\n </Typography>\n </Box>\n \n {analysisResult.performance && (\n <Box mt={1}>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Analysis time: {formatTime(analysisResult.performance.inference_time)} on {analysisResult.performance.device}\n </Typography>\n </Box>\n )}\n </>\n )}\n </Paper>\n );\n};\n\nexport default LlmAnalysis;\n","import React, { useState } from 'react';\nimport { \n Paper,\n Typography,\n Grid,\n TextField,\n Button,\n Divider\n} from '@material-ui/core';\n\nfunction OpenAIChat() {\n const [model, setModel] = useState('gpt-4o-mini');\n const [apiKey, setApiKey] = useState('');\n const [system, setSystem] = useState('You are a helpful assistant.');\n const [prompt, setPrompt] = useState('');\n const [response, setResponse] = useState('');\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState('');\n\n const onSend = async () => {\n setError('');\n setResponse('');\n const p = (prompt || '').trim();\n if (!p) { setError('Please enter a question.'); return; }\n\n setLoading(true);\n try {\n const res = await fetch('/api/openai/chat', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({\n prompt: p,\n model: (model || '').trim() || 'gpt-4o-mini',\n api_key: (apiKey || undefined),\n system: (system || undefined)\n })\n });\n\n if (!res.ok) {\n let txt = await res.text();\n try { txt = JSON.stringify(JSON.parse(txt), null, 2); } catch {}\n throw new Error(txt);\n }\n const data = await res.json();\n const meta = `Model: ${data.model} | Latency: ${data.latency_sec}s` + (data.usage ? ` | Usage: ${JSON.stringify(data.usage)}` : '');\n setResponse((data.response || '(Empty response)') + '\\n\\n---\\n' + meta);\n } catch (e) {\n setError('Error: ' + e.message);\n } finally {\n setLoading(false);\n }\n };\n\n const onClear = () => {\n setPrompt('');\n setResponse('');\n setError('');\n };\n\n return (\n <Paper style={{ padding: 16 }}>\n <Typography variant=\"h5\" gutterBottom>\n OpenAI Chat (OpenAI API)\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n If the server env var OPENAI_API_KEY is set, the API Key field is optional.\n </Typography>\n <Grid container spacing={2}>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"Model\"\n value={model}\n onChange={(e) => setModel(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"OpenAI API Key (optional)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"password\"\n placeholder=\"sk-...\"\n />\n </Grid>\n <Grid item xs={12}>\n <TextField\n label=\"System Prompt (optional)\"\n value={system}\n onChange={(e) => setSystem(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n <Grid item xs={12}>\n <TextField\n label=\"User Question\"\n value={prompt}\n onChange={(e) => setPrompt(e.target.value)}\n fullWidth\n multiline\n rows={4}\n variant=\"outlined\"\n />\n </Grid>\n {error && (\n <Grid item xs={12}>\n <Typography color=\"error\">{error}</Typography>\n </Grid>\n )}\n <Grid item xs={12}>\n <div style={{ display: 'flex', gap: 8 }}>\n <Button color=\"primary\" variant=\"contained\" onClick={onSend} disabled={loading}>\n {loading ? 'Sending...' : 'Send Question'}\n </Button>\n <Button variant=\"outlined\" onClick={onClear}>Clear</Button>\n </div>\n </Grid>\n <Grid item xs={12}>\n <Divider style={{ margin: '12px 0' }} />\n <Typography variant=\"subtitle2\" color=\"textSecondary\">Response</Typography>\n <pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'ui-monospace, monospace' }}>{response}</pre>\n </Grid>\n </Grid>\n </Paper>\n );\n}\n\nexport default OpenAIChat;\n","import React, { useState, useEffect } from 'react';\nimport { \n Container, \n Typography, \n Box, \n Paper, \n Grid, \n CircularProgress,\n AppBar,\n Toolbar,\n ThemeProvider,\n createMuiTheme\n} from '@material-ui/core';\nimport ImageUploader from './components/ImageUploader';\nimport ModelSelector from './components/ModelSelector';\nimport ResultDisplay from './components/ResultDisplay';\nimport LlmAnalysis from './components/LlmAnalysis';\nimport OpenAIChat from './components/OpenAIChat';\nimport './App.css';\n\n// Create a theme\nconst theme = createMuiTheme({\n palette: {\n primary: {\n main: '#3f51b5',\n },\n secondary: {\n main: '#f50057',\n },\n },\n typography: {\n fontFamily: 'Roboto, Arial, sans-serif',\n },\n});\n\nfunction App() {\n const [selectedImage, setSelectedImage] = useState(null);\n const [selectedModel, setSelectedModel] = useState('');\n const [isProcessing, setIsProcessing] = useState(false);\n const [results, setResults] = useState(null);\n const [error, setError] = useState(null);\n const [modelsStatus, setModelsStatus] = useState({\n yolo: false,\n detr: false,\n vit: false\n });\n\n // Check API status on component mount\n useEffect(() => {\n fetch('/api/status')\n .then(response => response.json())\n .then(data => {\n setModelsStatus(data.models);\n })\n .catch(err => {\n console.error('Error checking API status:', err);\n setError('Error connecting to the backend API. Please make sure the server is running.');\n });\n }, []);\n\n const handleImageUpload = (image) => {\n setSelectedImage(image);\n setResults(null);\n setError(null);\n };\n\n const handleModelSelect = (model) => {\n setSelectedModel(model);\n setResults(null);\n setError(null);\n };\n\n const processImage = async () => {\n if (!selectedImage || !selectedModel) {\n setError('Please select both an image and a model');\n return;\n }\n\n setIsProcessing(true);\n setError(null);\n\n // Create form data for the image\n const formData = new FormData();\n formData.append('image', selectedImage);\n\n let endpoint = '';\n switch (selectedModel) {\n case 'yolo':\n endpoint = '/api/detect/yolo';\n break;\n case 'detr':\n endpoint = '/api/detect/detr';\n break;\n case 'vit':\n endpoint = '/api/classify/vit';\n break;\n default:\n setError('Invalid model selection');\n setIsProcessing(false);\n return;\n }\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n setResults({ model: selectedModel, data });\n } catch (err) {\n console.error('Error processing image:', err);\n setError(`Error processing image: ${err.message}`);\n } finally {\n setIsProcessing(false);\n }\n };\n\n return (\n <ThemeProvider theme={theme}>\n <Box style={{ flexGrow: 1 }}>\n <AppBar position=\"static\">\n <Toolbar>\n <Typography variant=\"h6\" style={{ flexGrow: 1 }}>\n Multi-Model Object Detection Demo\n </Typography>\n </Toolbar>\n </AppBar>\n <Container maxWidth=\"lg\" style={{ marginTop: theme.spacing(4), marginBottom: theme.spacing(4) }}>\n <Grid container spacing={3}>\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2) }}>\n <Typography variant=\"h5\" gutterBottom>\n Upload an image to see how each model performs!\n </Typography>\n <Typography variant=\"body1\" paragraph>\n This demo showcases three different object detection and image classification models:\n </Typography>\n <Typography variant=\"body1\" component=\"div\">\n <ul>\n <li><strong>YOLOv8</strong>: Fast and accurate object detection</li>\n <li><strong>DETR</strong>: DEtection TRansformer for object detection</li>\n <li><strong>ViT</strong>: Vision Transformer for image classification</li>\n </ul>\n </Typography>\n </Paper>\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ImageUploader onImageUpload={handleImageUpload} />\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ModelSelector \n onModelSelect={handleModelSelect} \n onProcess={processImage}\n isProcessing={isProcessing}\n modelsStatus={modelsStatus}\n selectedModel={selectedModel}\n imageSelected={!!selectedImage}\n />\n </Grid>\n \n {error && (\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2), backgroundColor: '#ffebee' }}>\n <Typography color=\"error\">{error}</Typography>\n </Paper>\n </Grid>\n )}\n \n {isProcessing && (\n <Grid item xs={12} style={{ textAlign: 'center', margin: `${theme.spacing(4)}px 0` }}>\n <CircularProgress />\n <Typography variant=\"h6\" style={{ marginTop: theme.spacing(2) }}>\n Processing image...\n </Typography>\n </Grid>\n )}\n \n {results && (\n <>\n <Grid item xs={12}>\n <ResultDisplay results={results} />\n </Grid>\n <Grid item xs={12}>\n <LlmAnalysis visionResults={results.data} model={results.model} />\n </Grid>\n </>\n )}\n\n {/* OpenAI Chat section at the end */}\n <Grid item xs={12}>\n <OpenAIChat />\n </Grid>\n </Grid>\n </Container>\n </Box>\n </ThemeProvider>\n );\n}\n\nexport default App;\n","const reportWebVitals = (onPerfEntry) => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"],"sourceRoot":""}
|
static/static/js/main.fb4939ae.chunk.js
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
(this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[]).push([[0],{102:function(e,a,t){},103:function(e,a,t){"use strict";t.r(a);var n=t(0),r=t.n(n),l=t(11),o=t.n(l),c=(t(97),t(78)),i=t(155),s=t(159),m=t(156),d=t(157),u=t(48),g=t(158),p=t(138),E=t(81),h=t(143),b=t(136),y=t(137),f=t(63),v=t.n(f),x=t(75),S=t.n(x),j=t(133);const C=Object(j.a)(e=>({paper:{padding:e.spacing(2),display:"flex",flexDirection:"column",alignItems:"center",height:"100%",minHeight:300,transition:"all 0.3s ease"},dragActive:{border:"2px dashed #3f51b5",backgroundColor:"rgba(63, 81, 181, 0.05)"},dragInactive:{border:"2px dashed #ccc",backgroundColor:"white"},uploadBox:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",height:"100%",width:"100%",cursor:"pointer"},uploadIcon:{fontSize:60,color:"#3f51b5",marginBottom:e.spacing(2)},supportText:{marginTop:e.spacing(2)},previewBox:{display:"flex",flexDirection:"column",alignItems:"center",width:"100%",height:"100%",position:"relative"},imageContainer:{position:"relative",width:"100%",height:"60vh",[e.breakpoints.down("sm")]:{height:"45vh"},display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",marginTop:e.spacing(2)},deleteButton:{position:"absolute",top:0,right:0,backgroundColor:"rgba(255, 255, 255, 0.7)","&:hover":{backgroundColor:"rgba(255, 255, 255, 0.9)"}}}));var w=e=>{let{onImageUpload:a}=e;const t=C(),[l,o]=Object(n.useState)(null),[c,i]=Object(n.useState)(!1),m=Object(n.useRef)(null),d=e=>{e.preventDefault(),e.stopPropagation(),"dragenter"===e.type||"dragover"===e.type?i(!0):"dragleave"===e.type&&i(!1)},g=e=>{e.type.startsWith("image/")?(o(URL.createObjectURL(e)),a(e)):alert("Please upload an image file")};return r.a.createElement(E.a,{className:"".concat(t.paper," ").concat(c?t.dragActive:t.dragInactive),onDragEnter:d,onDragLeave:d,onDragOver:d,onDrop:e=>{e.preventDefault(),e.stopPropagation(),i(!1),e.dataTransfer.files&&e.dataTransfer.files[0]&&g(e.dataTransfer.files[0])}},r.a.createElement("input",{ref:m,type:"file",accept:"image/*",onChange:e=>{e.preventDefault(),e.target.files&&e.target.files[0]&&g(e.target.files[0])},style:{display:"none"}}),l?r.a.createElement(s.a,{className:t.previewBox},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Preview"),r.a.createElement(s.a,{className:t.imageContainer},r.a.createElement("img",{src:l,alt:"Preview",className:"preview-image",style:{width:"100%",height:"auto",maxHeight:"100%",objectFit:"contain",display:"block"}}),r.a.createElement(y.a,{"aria-label":"delete",className:t.deleteButton,onClick:()=>{o(null),a(null),m.current.value=""}},r.a.createElement(S.a,null)))):r.a.createElement(s.a,{className:t.uploadBox,onClick:()=>{m.current.click()}},r.a.createElement(v.a,{className:t.uploadIcon}),r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Drag & Drop an image here"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"or"),r.a.createElement(b.a,{variant:"contained",color:"primary",component:"span",startIcon:r.a.createElement(v.a,null)},"Browse Files"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",className:t.supportText},"Supported formats: JPG, PNG, GIF")))},O=t(139),N=t(140),B=t(166),I=t(141),T=t(64),k=t.n(T),P=t(76),D=t.n(P),F=t(77),A=t.n(F);const R=Object(j.a)(e=>({card:{height:"100%",display:"flex",flexDirection:"column"},selectedCard:{border:"2px solid #3f51b5"},unavailableCard:{opacity:.6},cardContent:{flexGrow:1},chipContainer:{marginBottom:e.spacing(1.5)},successChip:{backgroundColor:"#34C759",color:"#fff"},errorChip:{backgroundColor:"#FF3B3F",color:"#fff"},modelType:{marginTop:e.spacing(1)},processButton:{marginTop:e.spacing(3),textAlign:"center"}}));var _=e=>{let{onModelSelect:a,onProcess:t,isProcessing:n,modelsStatus:l,selectedModel:o,imageSelected:c}=e;const i=R(),m=[{id:"yolo",name:"YOLOv8",description:"Fast and accurate object detection",icon:r.a.createElement(k.a,null),available:l.yolo},{id:"detr",name:"DETR",description:"DEtection TRansformer for object detection",icon:r.a.createElement(k.a,null),available:l.detr},{id:"vit",name:"ViT",description:"Vision Transformer for image classification",icon:r.a.createElement(D.a,null),available:l.vit}],d=e=>{m.find(a=>a.id===e).available&&a(e)};return r.a.createElement(s.a,{sx:{p:2,height:"100%"}},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Select Model"),r.a.createElement(p.a,{container:!0,spacing:2},m.map(e=>r.a.createElement(p.a,{item:!0,xs:12,sm:4,key:e.id},r.a.createElement(O.a,{className:"\n ".concat(i.card," \n ").concat(o===e.id?i.selectedCard:""," \n ").concat(e.available?"":i.unavailableCard,"\n "),onClick:()=>d(e.id)},r.a.createElement(N.a,{className:i.cardContent},r.a.createElement(s.a,{sx:{mb:2,color:"primary"}},e.icon),r.a.createElement(u.a,{variant:"h5",component:"div",gutterBottom:!0},e.name),r.a.createElement("div",{className:i.chipContainer},e.available?r.a.createElement(B.a,{label:"Available",className:i.successChip,size:"small"}):r.a.createElement(B.a,{label:"Not Available",className:i.errorChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.description)),r.a.createElement(I.a,null,r.a.createElement(b.a,{size:"small",onClick:()=>d(e.id),disabled:!e.available,color:o===e.id?"primary":"default",variant:o===e.id?"contained":"outlined",fullWidth:!0},o===e.id?"Selected":"Select")))))),r.a.createElement("div",{className:i.processButton},r.a.createElement(b.a,{variant:"contained",color:"primary",size:"large",startIcon:r.a.createElement(A.a,null),onClick:t,disabled:!o||!c||n},n?"Processing...":"Process Image")))},z=t(153),L=t(150),W=t(105),M=t(154),J=t(142),H=t(164),U=t(163),V=t(145),G=t(146),q=t(147),Y=t(167),Q=t(160),K=t(151),X=t(169),Z=t(152),$=t(161);const ee=Object(j.a)(e=>({root:{marginTop:e.spacing(2),marginBottom:e.spacing(2),padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius},button:{marginRight:e.spacing(2)},searchDialog:{minWidth:"500px"},formControl:{marginBottom:e.spacing(2),minWidth:"100%"},searchResults:{marginTop:e.spacing(2)},resultCard:{marginBottom:e.spacing(2)},resultImage:{height:140,objectFit:"contain"},chip:{margin:e.spacing(.5)},similarityChip:{backgroundColor:e.palette.primary.main,color:"white"}}));var ae=e=>{let{results:a}=e;const t=ee(),[l,o]=Object(n.useState)(!1),[c,i]=Object(n.useState)(!1),[m,d]=Object(n.useState)(null),[g,E]=Object(n.useState)(!1),[y,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,j]=Object(n.useState)([]),[C,w]=Object(n.useState)(!1),[I,T]=Object(n.useState)(null),{model:k,data:P}=a,D=()=>{E(!1)},F=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){const a=16*Math.random()|0;return("x"===e?a:3&a|8).toString(16)}));return r.a.createElement(s.a,{className:t.root},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Vector Database Actions"),r.a.createElement(s.a,{display:"flex",alignItems:"center",mb:2},r.a.createElement(b.a,{variant:"contained",color:"primary",onClick:async()=>{o(!0),d(null);try{let e;if(e="vit"===k?await fetch("/api/add-to-collection",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,metadata:{model:"vit",classifications:P.classifications}})}):await fetch("/api/add-detected-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,objects:P.detections,imageId:F()})}),!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const a=await e.json();if(a.error)throw new Error(a.error);i(!0),setTimeout(()=>i(!1),5e3)}catch(e){console.error("Error saving to vector DB:",e),d("Error saving to vector DB: ".concat(e.message))}finally{o(!1)}},disabled:l,className:t.button},l?r.a.createElement(r.a.Fragment,null,r.a.createElement(h.a,{size:20,color:"inherit",style:{marginRight:8}}),"Saving..."):"Save to Vector DB"),r.a.createElement(b.a,{variant:"outlined",color:"primary",onClick:()=>{E(!0),j([]),T(null)},className:t.button},"Search Similar")),m&&r.a.createElement($.a,{severity:"error",style:{marginTop:8}},m),r.a.createElement(H.a,{open:c,autoHideDuration:5e3,onClose:()=>i(!1)},r.a.createElement($.a,{severity:"success"},"vit"===k?"Image and classifications successfully saved to vector DB!":"Detected objects successfully saved to vector DB!")),r.a.createElement(U.a,{open:g,onClose:D,maxWidth:"md",fullWidth:!0},r.a.createElement(V.a,null,"Search Vector Database"),r.a.createElement(G.a,null,r.a.createElement(q.a,{className:t.formControl},r.a.createElement(Y.a,{id:"search-type-label"},"Search Type"),r.a.createElement(Q.a,{labelId:"search-type-label",id:"search-type",value:y,onChange:e=>{f(e.target.value),j([]),T(null)}},r.a.createElement(K.a,{value:"image"},"Search by Current Image"),r.a.createElement(K.a,{value:"class"},"Search by Class Name"))),"class"===y&&r.a.createElement(q.a,{className:t.formControl},r.a.createElement(X.a,{label:"Class Name",value:v,onChange:e=>{x(e.target.value)},placeholder:"e.g. person, car, dog...",fullWidth:!0})),I&&r.a.createElement($.a,{severity:"error",style:{marginBottom:16}},I),r.a.createElement(s.a,{className:t.searchResults},C?r.a.createElement(s.a,{display:"flex",justifyContent:"center",alignItems:"center",p:4},r.a.createElement(h.a,null),r.a.createElement(u.a,{variant:"body1",style:{marginLeft:16}},"Searching...")):r.a.createElement(r.a.Fragment,null,console.log("Search dialog render - searchResults:",S),S.length>0?(console.log("Rendering search results:",S),console.log("Search results length:",S.length),0===S.length?(console.log("No results to render"),r.a.createElement(u.a,{variant:"body1"},"No results found.")):r.a.createElement(p.a,{container:!0,spacing:2},S.map((e,a)=>{const n=100*(1-e.distance);return r.a.createElement(p.a,{item:!0,xs:12,sm:6,key:a},r.a.createElement(O.a,{className:t.resultCard},e.metadata&&e.metadata.image_data?r.a.createElement(J.a,{className:t.resultImage,component:"img",height:"200",image:"data:image/jpeg;base64,".concat(e.metadata.image_data),alt:e.metadata&&e.metadata.class?e.metadata.class:"Object"}):r.a.createElement(s.a,{className:t.resultImage,style:{backgroundColor:"#f0f0f0",display:"flex",alignItems:"center",justifyContent:"center",height:200}},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.metadata&&e.metadata.class?e.metadata.class:"Object"," Image")),r.a.createElement(N.a,null,r.a.createElement(s.a,{display:"flex",justifyContent:"space-between",alignItems:"center",mb:1},r.a.createElement(u.a,{variant:"subtitle1"},"Result #",a+1),r.a.createElement(B.a,{label:"Similarity: ".concat(n.toFixed(2),"%"),className:t.similarityChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Class:")," ",e.metadata.class||"N/A"),e.metadata.confidence&&r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Confidence:")," ",(100*e.metadata.confidence).toFixed(2),"%"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Object ID:")," ",e.id))))}))):r.a.createElement(u.a,{variant:"body1"},"No results found. Please try another search.")))),r.a.createElement(Z.a,null,r.a.createElement(b.a,{onClick:D,color:"default"},"Close"),r.a.createElement(b.a,{onClick:async()=>{w(!0),T(null);try{let e={};if("image"===y)e={searchType:"image",image:P.image,n_results:5};else{if(!v.trim())throw new Error("Please enter a class name");e={searchType:"class",class_name:v.trim(),n_results:5}}const a=await fetch("/api/search-similar-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!a.ok)throw new Error("HTTP error! Status: ".concat(a.status));const t=await a.json();if(t.error)throw new Error(t.error);if(console.log("Search API response:",t),!t.success||!Array.isArray(t.results))throw console.error("Unexpected API response format:",t),new Error("Unexpected API response format");console.log("Setting search results array:",t.results),console.log("Results array length:",t.results.length),console.log("First result item:",t.results[0]),j(t.results)}catch(e){console.error("Error searching vector DB:",e),T("Error searching vector DB: ".concat(e.message))}finally{w(!1)}},color:"primary",variant:"contained",disabled:C||"class"===y&&!v.trim()},"Search"))))};const te=Object(j.a)(e=>({paper:{padding:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},resultImage:{maxWidth:"100%",maxHeight:"400px",objectFit:"contain"},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},chipContainer:{display:"flex",gap:e.spacing(1),flexWrap:"wrap"}}));var ne=e=>{let{results:a}=e;const t=te();if(!a)return null;const{model:n,data:l}=a;if(l.error)return r.a.createElement(E.a,{sx:{p:2,bgcolor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},l.error));const o=()=>l.performance?r.a.createElement(s.a,{className:"performance-info"},r.a.createElement(z.a,{className:t.dividerMargin}),r.a.createElement(u.a,{variant:"body2"},"Inference time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(l.performance.inference_time)," on ",l.performance.device)):null;return"yolo"===n||"detr"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"yolo"===n?"YOLOv8":"DETR"," Detection Results"),r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12,md:6},l.image&&r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detection Result"),r.a.createElement("img",{src:"data:image/png;base64,".concat(l.image),alt:"Detection Result",className:t.resultImage}))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detected Objects:"),l.detections&&l.detections.length>0?r.a.createElement(L.a,null,l.detections.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(M.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.class),r.a.createElement(B.a,{label:"".concat((100*e.confidence).toFixed(0),"%"),size:"small",color:"primary",style:{marginLeft:8}})),secondary:"Bounding Box: [".concat(e.bbox.join(", "),"]")})),a<l.detections.length-1&&r.a.createElement(z.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No objects detected")))),o(),r.a.createElement(ae,{results:a})):"vit"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"ViT Classification Results"),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Top Predictions:"),l.top_predictions&&l.top_predictions.length>0?r.a.createElement(L.a,null,l.top_predictions.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(M.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.rank,". ",e.class),r.a.createElement(B.a,{label:"".concat((100*e.probability).toFixed(1),"%"),size:"small",color:0===a?"primary":"default",style:{marginLeft:8}}))})),a<l.top_predictions.length-1&&r.a.createElement(z.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No classifications available"),o(),r.a.createElement(ae,{results:a})):null};const re=Object(j.a)(e=>({paper:{padding:e.spacing(2),marginTop:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},responseBox:{padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius,marginTop:e.spacing(2),whiteSpace:"pre-wrap"},buttonProgress:{marginLeft:e.spacing(1)}}));var le=e=>{let{visionResults:a,model:t}=e;const l=re(),[o,c]=Object(n.useState)(""),[i,m]=Object(n.useState)(!1),[d,g]=Object(n.useState)(null),[p,y]=Object(n.useState)(null);return a?r.a.createElement(E.a,{className:l.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Ask AI about the ","vit"===t?"Classification":"Detection"," Results"),r.a.createElement(u.a,{variant:"body2",className:l.marginBottom},"Ask a question about the detected objects or classifications to get an AI-powered analysis."),r.a.createElement(X.a,{fullWidth:!0,label:"Your question about the image",variant:"outlined",value:o,onChange:e=>c(e.target.value),disabled:i,className:l.marginBottom,placeholder:"vit"===t?"E.g., What category does this image belong to?":"E.g., How many people are in this image?"}),r.a.createElement(b.a,{variant:"contained",color:"primary",onClick:async()=>{if(o.trim()){m(!0),y(null);try{const e=await fetch("/api/analyze",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({visionResults:a,userQuery:o})});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const t=await e.json();t.error?y(t.error):g(t)}catch(e){console.error("Error analyzing with LLM:",e),y("Error analyzing with LLM: ".concat(e.message))}finally{m(!1)}}},disabled:i||!o.trim()},"Analyze with AI",i&&r.a.createElement(h.a,{size:24,className:l.buttonProgress})),p&&r.a.createElement(s.a,{mt:2},r.a.createElement(u.a,{color:"error"},p)),d&&r.a.createElement(r.a.Fragment,null,r.a.createElement(z.a,{className:l.dividerMargin}),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"AI Analysis:"),r.a.createElement(s.a,{className:l.responseBox},r.a.createElement(u.a,{variant:"body1"},d.response)),d.performance&&r.a.createElement(s.a,{mt:1},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},"Analysis time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(d.performance.inference_time)," on ",d.performance.device)))):null};var oe=function(e){let{imageBase64:a}=e;const[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(""),[i,s]=Object(n.useState)(""),[m,d]=Object(n.useState)(!1),[g,h]=Object(n.useState)(""),[y,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,j]=Object(n.useState)(""),[C,w]=Object(n.useState)(5);return r.a.createElement(E.a,{style:{padding:16}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Vision RAG (LangChain)"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"If OPENAI_API_KEY is set on the server, API Key is optional. Select a search type and send a question to get answers based on context retrieved from the vector DB."),r.a.createElement(p.a,{container:!0,spacing:2},r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(q.a,{fullWidth:!0,variant:"outlined",size:"small"},r.a.createElement(Y.a,{id:"search-type-label"},"Search Type"),r.a.createElement(Q.a,{labelId:"search-type-label",value:y,onChange:e=>f(e.target.value),label:"Search Type"},r.a.createElement(K.a,{value:"image"},"image (current upload)"),r.a.createElement(K.a,{value:"object"},"object (objectId)"),r.a.createElement(K.a,{value:"class"},"class (class_name)")))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"OpenAI API Key (optional)",value:t,onChange:e=>l(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"password",placeholder:"sk-..."})),"object"===y&&r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"objectId",value:v,onChange:e=>x(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),"class"===y&&r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"class_name",value:S,onChange:e=>j(e.target.value),fullWidth:!0,variant:"outlined",size:"small",placeholder:"e.g. person, car, dog"})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"n_results",value:C,onChange:e=>w(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"number",inputProps:{min:1,max:50}})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"User Question",value:o,onChange:e=>c(e.target.value),fullWidth:!0,multiline:!0,rows:4,variant:"outlined",placeholder:"image"===y?"Answer based on the uploaded image":"Answer using the retrieved object context"})),g&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(u.a,{color:"error"},g)),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement("div",{style:{display:"flex",gap:8}},r.a.createElement(b.a,{color:"primary",variant:"contained",onClick:async()=>{h(""),s("");const e=(o||"").trim();if(!e)return void h("Please enter a question.");const n={userQuery:e,searchType:y,n_results:Number(C)||5};if(t&&(n.api_key=t),"image"===y){if(!a)return void h("Image is required. Please upload an image first.");n.image=a}else if("object"===y){if(!v.trim())return void h("Please enter an objectId.");n.objectId=v.trim()}else if("class"===y){if(!S.trim())return void h("Please enter a class_name.");n.class_name=S.trim()}d(!0);try{const e=await fetch("/api/vision-rag/query",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(n)});if(!e.ok){let a=await e.text();try{a=JSON.stringify(JSON.parse(a),null,2)}catch(r){}throw new Error(a)}const a=await e.json(),t="Model: ".concat(a.model||"-"," | Latency: ").concat(a.latency_sec||"-","s");let l="";a.retrieved&&a.retrieved.length>0&&(l="\n\nSearch Results:\n",a.retrieved.forEach((e,a)=>{l+="".concat(a+1,". ID: ").concat(e.id||"-","\n"),e.meta&&(l+=" Class: ".concat(e.meta.class||"-","\n"),l+=" Confidence: ".concat(e.meta.confidence||"-","\n")),l+=" Similarity: ".concat(e.distance?e.distance.toFixed(4):"-","\n")})),s((a.answer||"(No response)")+l+"\n\n---\n"+t)}catch(l){h("Error: "+l.message)}finally{d(!1)}},disabled:m},m?"Sending...":"Send Question"),r.a.createElement(b.a,{variant:"outlined",onClick:()=>{c(""),s(""),h("")}},"Clear"))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(z.a,{style:{margin:"12px 0"}}),r.a.createElement(u.a,{variant:"subtitle2",color:"textSecondary"},"Response"),r.a.createElement("pre",{style:{whiteSpace:"pre-wrap",fontFamily:"ui-monospace, monospace"}},i))))};t(102);const ce=Object(c.a)({palette:{primary:{main:"#3f51b5"},secondary:{main:"#f50057"}},typography:{fontFamily:"Roboto, Arial, sans-serif"}});var ie=function(){const[e,a]=Object(n.useState)(null),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(""),[b,y]=Object(n.useState)(!1),[f,v]=Object(n.useState)(null),[x,S]=Object(n.useState)(null),[j,C]=Object(n.useState)({yolo:!1,detr:!1,vit:!1});return Object(n.useEffect)(()=>{fetch("/api/status").then(e=>e.json()).then(e=>{C(e.models)}).catch(e=>{console.error("Error checking API status:",e),S("Error connecting to the backend API. Please make sure the server is running.")})},[]),r.a.createElement(i.a,{theme:ce},r.a.createElement(s.a,{style:{flexGrow:1}},r.a.createElement(m.a,{position:"static"},r.a.createElement(d.a,null,r.a.createElement(u.a,{variant:"h6",style:{flexGrow:1}},"Multi-Model Object Detection Demo"))),r.a.createElement(g.a,{maxWidth:"lg",style:{marginTop:ce.spacing(4),marginBottom:ce.spacing(4)}},r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2)}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Upload an image to see how each model performs!"),r.a.createElement(u.a,{variant:"body1",paragraph:!0},"This demo showcases three different object detection and image classification models:"),r.a.createElement(u.a,{variant:"body1",component:"div"},r.a.createElement("ul",null,r.a.createElement("li",null,r.a.createElement("strong",null,"YOLOv8"),": Fast and accurate object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"DETR"),": DEtection TRansformer for object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"ViT"),": Vision Transformer for image classification"))))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(w,{onImageUpload:e=>{if(a(e),v(null),S(null),e instanceof File){const a=new FileReader;a.onload=()=>{const e=a.result;l("string"===typeof e?e:"")},a.onerror=()=>l(""),a.readAsDataURL(e)}else l("")}})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(_,{onModelSelect:e=>{c(e),v(null),S(null)},onProcess:async()=>{if(!e||!o)return void S("Please select both an image and a model");y(!0),S(null);const a=new FormData;a.append("image",e);let t="";switch(o){case"yolo":t="/api/detect/yolo";break;case"detr":t="/api/detect/detr";break;case"vit":t="/api/classify/vit";break;default:return S("Invalid model selection"),void y(!1)}try{const e=await fetch(t,{method:"POST",body:a});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const n=await e.json();v({model:o,data:n})}catch(n){console.error("Error processing image:",n),S("Error processing image: ".concat(n.message))}finally{y(!1)}},isProcessing:b,modelsStatus:j,selectedModel:o,imageSelected:!!e})),x&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2),backgroundColor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},x))),b&&r.a.createElement(p.a,{item:!0,xs:12,style:{textAlign:"center",margin:"".concat(ce.spacing(4),"px 0")}},r.a.createElement(h.a,null),r.a.createElement(u.a,{variant:"h6",style:{marginTop:ce.spacing(2)}},"Processing image...")),f&&r.a.createElement(r.a.Fragment,null,r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(ne,{results:f})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(le,{visionResults:f.data,model:f.model}))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(oe,{imageBase64:t}))))))};var se=e=>{e&&e instanceof Function&&t.e(3).then(t.bind(null,170)).then(a=>{let{getCLS:t,getFID:n,getFCP:r,getLCP:l,getTTFB:o}=a;t(e),n(e),r(e),l(e),o(e)})};o.a.render(r.a.createElement(r.a.StrictMode,null,r.a.createElement(ie,null)),document.getElementById("root")),se()},92:function(e,a,t){e.exports=t(103)},97:function(e,a,t){}},[[92,1,2]]]);
|
2 |
-
//# sourceMappingURL=main.fb4939ae.chunk.js.map
|
|
|
|
|
|
static/static/js/main.fb4939ae.chunk.js.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["components/ImageUploader.js","components/ModelSelector.js","components/VectorDBActions.js","components/ResultDisplay.js","components/LlmAnalysis.js","components/OpenAIChat.js","App.js","reportWebVitals.js","index.js"],"names":["useStyles","makeStyles","theme","paper","padding","spacing","display","flexDirection","alignItems","height","minHeight","transition","dragActive","border","backgroundColor","dragInactive","uploadBox","justifyContent","width","cursor","uploadIcon","fontSize","color","marginBottom","supportText","marginTop","previewBox","position","imageContainer","breakpoints","down","overflow","deleteButton","top","right","ImageUploader","_ref","onImageUpload","classes","previewUrl","setPreviewUrl","useState","setDragActive","fileInputRef","useRef","handleDrag","e","preventDefault","stopPropagation","type","handleFiles","file","startsWith","URL","createObjectURL","alert","React","createElement","Paper","className","concat","onDragEnter","onDragLeave","onDragOver","onDrop","dataTransfer","files","ref","accept","onChange","target","style","Box","Typography","variant","gutterBottom","src","alt","maxHeight","objectFit","IconButton","aria-label","onClick","handleRemoveImage","current","value","DeleteIcon","onButtonClick","click","CloudUploadIcon","Button","component","startIcon","card","selectedCard","unavailableCard","opacity","cardContent","flexGrow","chipContainer","successChip","errorChip","modelType","processButton","textAlign","ModelSelector","onModelSelect","onProcess","isProcessing","modelsStatus","selectedModel","imageSelected","models","id","name","description","icon","VisibilityIcon","available","yolo","detr","CategoryIcon","vit","handleModelClick","modelId","find","m","sx","p","Grid","container","map","model","item","xs","sm","key","Card","CardContent","mb","Chip","label","size","CardActions","disabled","fullWidth","PlayArrowIcon","root","borderRadius","shape","button","marginRight","searchDialog","minWidth","formControl","searchResults","resultCard","resultImage","chip","margin","similarityChip","palette","primary","main","VectorDBActions","results","isSaving","setIsSaving","saveSuccess","setSaveSuccess","saveError","setSaveError","openSearchDialog","setOpenSearchDialog","searchType","setSearchType","searchClass","setSearchClass","setSearchResults","isSearching","setIsSearching","searchError","setSearchError","data","handleCloseSearchDialog","generateUUID","replace","c","r","Math","random","toString","async","response","fetch","method","headers","body","JSON","stringify","image","metadata","classifications","objects","detections","imageId","ok","Error","status","result","json","error","setTimeout","err","console","message","Fragment","CircularProgress","handleOpenSearchDialog","Alert","severity","Snackbar","open","autoHideDuration","onClose","Dialog","maxWidth","DialogTitle","DialogContent","FormControl","InputLabel","Select","labelId","event","MenuItem","TextField","placeholder","marginLeft","log","length","index","similarity","distance","image_data","CardMedia","class","toFixed","confidence","DialogActions","requestBody","n_results","trim","class_name","success","Array","isArray","dividerMargin","gap","flexWrap","ResultDisplay","bgcolor","renderPerformanceInfo","performance","Divider","ms","undefined","isNaN","num","Number","formatTime","inference_time","device","md","List","detection","ListItem","ListItemText","secondary","bbox","join","top_predictions","prediction","rank","probability","responseBox","whiteSpace","buttonProgress","LlmAnalysis","visionResults","userQuery","setUserQuery","isAnalyzing","setIsAnalyzing","analysisResult","setAnalysisResult","setError","mt","OpenAIChat","imageBase64","apiKey","setApiKey","prompt","setPrompt","setResponse","loading","setLoading","objectId","setObjectId","setClassName","nResults","setNResults","inputProps","min","max","multiline","rows","q","api_key","res","credentials","txt","text","parse","_unused","meta","latency_sec","retrievedText","retrieved","forEach","answer","onClear","fontFamily","createMuiTheme","typography","App","selectedImage","setSelectedImage","setImageBase64","setSelectedModel","setIsProcessing","setResults","setModelsStatus","useEffect","then","catch","ThemeProvider","AppBar","Toolbar","Container","paragraph","File","reader","FileReader","onload","dataUrl","onerror","readAsDataURL","formData","FormData","append","endpoint","reportWebVitals","onPerfEntry","Function","getCLS","getFID","getFCP","getLCP","getTTFB","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"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","file":"static/js/main.fb4939ae.chunk.js","sourcesContent":["import React, { useState, useRef } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n Button, \n IconButton \n} from '@material-ui/core';\nimport CloudUploadIcon from '@material-ui/icons/CloudUpload';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n height: '100%',\n minHeight: 300,\n transition: 'all 0.3s ease'\n },\n dragActive: {\n border: '2px dashed #3f51b5',\n backgroundColor: 'rgba(63, 81, 181, 0.05)'\n },\n dragInactive: {\n border: '2px dashed #ccc',\n backgroundColor: 'white'\n },\n uploadBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n width: '100%',\n cursor: 'pointer'\n },\n uploadIcon: {\n fontSize: 60,\n color: '#3f51b5',\n marginBottom: theme.spacing(2)\n },\n supportText: {\n marginTop: theme.spacing(2)\n },\n previewBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n position: 'relative'\n },\n imageContainer: {\n position: 'relative',\n width: '100%',\n // Use viewport-based height so any aspect ratio fits inside\n height: '60vh',\n [theme.breakpoints.down('sm')]: {\n height: '45vh',\n },\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n overflow: 'hidden',\n marginTop: theme.spacing(2),\n },\n deleteButton: {\n position: 'absolute',\n top: 0,\n right: 0,\n backgroundColor: 'rgba(255, 255, 255, 0.7)',\n '&:hover': {\n backgroundColor: 'rgba(255, 255, 255, 0.9)',\n }\n }\n}));\n\nconst ImageUploader = ({ onImageUpload }) => {\n const classes = useStyles();\n const [previewUrl, setPreviewUrl] = useState(null);\n const [dragActive, setDragActive] = useState(false);\n const fileInputRef = useRef(null);\n\n const handleDrag = (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (e.type === 'dragenter' || e.type === 'dragover') {\n setDragActive(true);\n } else if (e.type === 'dragleave') {\n setDragActive(false);\n }\n };\n\n const handleDrop = (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragActive(false);\n if (e.dataTransfer.files && e.dataTransfer.files[0]) {\n handleFiles(e.dataTransfer.files[0]);\n }\n };\n\n const handleChange = (e) => {\n e.preventDefault();\n if (e.target.files && e.target.files[0]) {\n handleFiles(e.target.files[0]);\n }\n };\n\n const handleFiles = (file) => {\n if (file.type.startsWith('image/')) {\n setPreviewUrl(URL.createObjectURL(file));\n onImageUpload(file);\n } else {\n alert('Please upload an image file');\n }\n };\n\n const onButtonClick = () => {\n fileInputRef.current.click();\n };\n\n const handleRemoveImage = () => {\n setPreviewUrl(null);\n onImageUpload(null);\n fileInputRef.current.value = \"\";\n };\n\n return (\n <Paper \n className={`${classes.paper} ${dragActive ? classes.dragActive : classes.dragInactive}`}\n onDragEnter={handleDrag}\n onDragLeave={handleDrag}\n onDragOver={handleDrag}\n onDrop={handleDrop}\n >\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleChange}\n style={{ display: 'none' }}\n />\n\n {!previewUrl ? (\n <Box \n className={classes.uploadBox}\n onClick={onButtonClick}\n >\n <CloudUploadIcon className={classes.uploadIcon} />\n <Typography variant=\"h6\" gutterBottom>\n Drag & Drop an image here\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n or\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n component=\"span\"\n startIcon={<CloudUploadIcon />}\n >\n Browse Files\n </Button>\n <Typography variant=\"body2\" color=\"textSecondary\" className={classes.supportText}>\n Supported formats: JPG, PNG, GIF\n </Typography>\n </Box>\n ) : (\n <Box className={classes.previewBox}>\n <Typography variant=\"h6\" gutterBottom>\n Preview\n </Typography>\n <Box className={classes.imageContainer}>\n <img\n src={previewUrl}\n alt=\"Preview\"\n className=\"preview-image\"\n style={{\n width: '100%',\n height: 'auto',\n maxHeight: '100%',\n objectFit: 'contain',\n display: 'block'\n }}\n />\n <IconButton\n aria-label=\"delete\"\n className={classes.deleteButton}\n onClick={handleRemoveImage}\n >\n <DeleteIcon />\n </IconButton>\n </Box>\n </Box>\n )}\n </Paper>\n );\n};\n\nexport default ImageUploader;\n","import React from 'react';\nimport { \n Grid, \n Card, \n CardContent, \n CardActions, \n Typography, \n Button, \n Chip,\n Box\n} from '@material-ui/core';\nimport VisibilityIcon from '@material-ui/icons/Visibility';\nimport CategoryIcon from '@material-ui/icons/Category';\nimport PlayArrowIcon from '@material-ui/icons/PlayArrow';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n card: {\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n },\n selectedCard: {\n border: '2px solid #3f51b5',\n },\n unavailableCard: {\n opacity: 0.6,\n },\n cardContent: {\n flexGrow: 1,\n },\n chipContainer: {\n marginBottom: theme.spacing(1.5),\n },\n successChip: {\n backgroundColor: '#34C759',\n color: '#fff',\n },\n errorChip: {\n backgroundColor: '#FF3B3F',\n color: '#fff',\n },\n modelType: {\n marginTop: theme.spacing(1),\n },\n processButton: {\n marginTop: theme.spacing(3),\n textAlign: 'center',\n }\n}));\n\nconst ModelSelector = ({ \n onModelSelect, \n onProcess, \n isProcessing, \n modelsStatus, \n selectedModel,\n imageSelected \n}) => {\n const classes = useStyles();\n \n const models = [\n {\n id: 'yolo',\n name: 'YOLOv8',\n description: 'Fast and accurate object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.yolo\n },\n {\n id: 'detr',\n name: 'DETR',\n description: 'DEtection TRansformer for object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.detr\n },\n {\n id: 'vit',\n name: 'ViT',\n description: 'Vision Transformer for image classification',\n icon: <CategoryIcon />,\n available: modelsStatus.vit\n }\n ];\n\n const handleModelClick = (modelId) => {\n if (models.find(m => m.id === modelId).available) {\n onModelSelect(modelId);\n }\n };\n\n return (\n <Box sx={{ p: 2, height: '100%' }}>\n <Typography variant=\"h6\" gutterBottom>\n Select Model\n </Typography>\n \n <Grid container spacing={2}>\n {models.map((model) => (\n <Grid item xs={12} sm={4} key={model.id}>\n <Card \n className={`\n ${classes.card} \n ${selectedModel === model.id ? classes.selectedCard : ''} \n ${!model.available ? classes.unavailableCard : ''}\n `}\n onClick={() => handleModelClick(model.id)}\n >\n <CardContent className={classes.cardContent}>\n <Box sx={{ mb: 2, color: 'primary' }}>\n {model.icon}\n </Box>\n <Typography variant=\"h5\" component=\"div\" gutterBottom>\n {model.name}\n </Typography>\n <div className={classes.chipContainer}>\n {model.available ? (\n <Chip \n label=\"Available\" \n className={classes.successChip}\n size=\"small\" \n />\n ) : (\n <Chip \n label=\"Not Available\" \n className={classes.errorChip}\n size=\"small\" \n />\n )}\n </div>\n <Typography variant=\"body2\" color=\"textSecondary\">\n {model.description}\n </Typography>\n </CardContent>\n <CardActions>\n <Button \n size=\"small\" \n onClick={() => handleModelClick(model.id)}\n disabled={!model.available}\n color={selectedModel === model.id ? \"primary\" : \"default\"}\n variant={selectedModel === model.id ? \"contained\" : \"outlined\"}\n fullWidth\n >\n {selectedModel === model.id ? 'Selected' : 'Select'}\n </Button>\n </CardActions>\n </Card>\n </Grid>\n ))}\n </Grid>\n\n <div className={classes.processButton}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"large\"\n startIcon={<PlayArrowIcon />}\n onClick={onProcess}\n disabled={!selectedModel || !imageSelected || isProcessing}\n >\n {isProcessing ? 'Processing...' : 'Process Image'}\n </Button>\n </div>\n </Box>\n );\n};\n\nexport default ModelSelector;\n","import React, { useState } from 'react';\nimport { \n Button, \n Box, \n Typography, \n CircularProgress, \n Snackbar,\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n TextField,\n FormControl,\n InputLabel,\n Select,\n MenuItem,\n Grid,\n Card,\n CardMedia,\n CardContent,\n Chip\n} from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n marginTop: theme.spacing(2),\n marginBottom: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n },\n button: {\n marginRight: theme.spacing(2),\n },\n searchDialog: {\n minWidth: '500px',\n },\n formControl: {\n marginBottom: theme.spacing(2),\n minWidth: '100%',\n },\n searchResults: {\n marginTop: theme.spacing(2),\n },\n resultCard: {\n marginBottom: theme.spacing(2),\n },\n resultImage: {\n height: 140,\n objectFit: 'contain',\n },\n chip: {\n margin: theme.spacing(0.5),\n },\n similarityChip: {\n backgroundColor: theme.palette.primary.main,\n color: 'white',\n }\n}));\n\nconst VectorDBActions = ({ results }) => {\n const classes = useStyles();\n const [isSaving, setIsSaving] = useState(false);\n const [saveSuccess, setSaveSuccess] = useState(false);\n const [saveError, setSaveError] = useState(null);\n const [openSearchDialog, setOpenSearchDialog] = useState(false);\n const [searchType, setSearchType] = useState('image');\n const [searchClass, setSearchClass] = useState('');\n const [searchResults, setSearchResults] = useState([]);\n const [isSearching, setIsSearching] = useState(false);\n const [searchError, setSearchError] = useState(null);\n \n // Extract model and data from results\n const { model, data } = results;\n \n // Handle saving to vector DB\n const handleSaveToVectorDB = async () => {\n setIsSaving(true);\n setSaveError(null);\n \n try {\n let response;\n \n if (model === 'vit') {\n // For ViT, save the whole image with classifications\n response = await fetch('/api/add-to-collection', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n metadata: {\n model: 'vit',\n classifications: data.classifications\n }\n })\n });\n } else {\n // For YOLO and DETR, save detected objects\n response = await fetch('/api/add-detected-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n objects: data.detections,\n imageId: generateUUID()\n })\n });\n }\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n setSaveSuccess(true);\n setTimeout(() => setSaveSuccess(false), 5000);\n } catch (err) {\n console.error('Error saving to vector DB:', err);\n setSaveError(`Error saving to vector DB: ${err.message}`);\n } finally {\n setIsSaving(false);\n }\n };\n \n // Handle opening search dialog\n const handleOpenSearchDialog = () => {\n setOpenSearchDialog(true);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle closing search dialog\n const handleCloseSearchDialog = () => {\n setOpenSearchDialog(false);\n };\n \n // Handle search type change\n const handleSearchTypeChange = (event) => {\n setSearchType(event.target.value);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle search class change\n const handleSearchClassChange = (event) => {\n setSearchClass(event.target.value);\n };\n \n // Handle search\n const handleSearch = async () => {\n setIsSearching(true);\n setSearchError(null);\n \n try {\n let requestBody = {};\n \n if (searchType === 'image') {\n // Search by current image\n requestBody = {\n searchType: 'image',\n image: data.image,\n n_results: 5\n };\n } else {\n // Search by class name\n if (!searchClass.trim()) {\n throw new Error('Please enter a class name');\n }\n \n requestBody = {\n searchType: 'class',\n class_name: searchClass.trim(),\n n_results: 5\n };\n }\n \n const response = await fetch('/api/search-similar-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody)\n });\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n console.log('Search API response:', result);\n \n // The backend responds with {success, searchType, results} structure, so extract only the results array\n if (result.success && Array.isArray(result.results)) {\n console.log('Setting search results array:', result.results);\n console.log('Results array length:', result.results.length);\n console.log('First result item:', result.results[0]);\n setSearchResults(result.results);\n } else {\n console.error('Unexpected API response format:', result);\n throw new Error('Unexpected API response format');\n }\n } catch (err) {\n console.error('Error searching vector DB:', err);\n setSearchError(`Error searching vector DB: ${err.message}`);\n } finally {\n setIsSearching(false);\n }\n };\n \n // Generate UUID for image ID\n const generateUUID = () => {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n };\n \n // Render search results\n const renderSearchResults = () => {\n console.log('Rendering search results:', searchResults);\n console.log('Search results length:', searchResults.length);\n \n if (searchResults.length === 0) {\n console.log('No results to render');\n return (\n <Typography variant=\"body1\">No results found.</Typography>\n );\n }\n \n return (\n <Grid container spacing={2}>\n {searchResults.map((result, index) => {\n const similarity = (1 - result.distance) * 100;\n \n return (\n <Grid item xs={12} sm={6} key={index}>\n <Card className={classes.resultCard}>\n {result.metadata && result.metadata.image_data ? (\n <CardMedia\n className={classes.resultImage}\n component=\"img\"\n height=\"200\"\n image={`data:image/jpeg;base64,${result.metadata.image_data}`}\n alt={result.metadata && result.metadata.class ? result.metadata.class : 'Object'}\n />\n ) : (\n <Box \n className={classes.resultImage}\n style={{ \n backgroundColor: '#f0f0f0', \n display: 'flex', \n alignItems: 'center', \n justifyContent: 'center',\n height: 200\n }}\n >\n <Typography variant=\"body2\" color=\"textSecondary\">\n {result.metadata && result.metadata.class ? result.metadata.class : 'Object'} Image\n </Typography>\n </Box>\n )}\n <CardContent>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\" mb={1}>\n <Typography variant=\"subtitle1\">Result #{index + 1}</Typography>\n <Chip \n label={`Similarity: ${similarity.toFixed(2)}%`}\n className={classes.similarityChip}\n size=\"small\"\n />\n </Box>\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Class:</strong> {result.metadata.class || 'N/A'}\n </Typography>\n {result.metadata.confidence && (\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Confidence:</strong> {(result.metadata.confidence * 100).toFixed(2)}%\n </Typography>\n )}\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Object ID:</strong> {result.id}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n );\n })}\n </Grid>\n );\n };\n \n return (\n <Box className={classes.root}>\n <Typography variant=\"h6\" gutterBottom>\n Vector Database Actions\n </Typography>\n \n <Box display=\"flex\" alignItems=\"center\" mb={2}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={handleSaveToVectorDB}\n disabled={isSaving}\n className={classes.button}\n >\n {isSaving ? (\n <>\n <CircularProgress size={20} color=\"inherit\" style={{ marginRight: 8 }} />\n Saving...\n </>\n ) : (\n 'Save to Vector DB'\n )}\n </Button>\n \n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={handleOpenSearchDialog}\n className={classes.button}\n >\n Search Similar\n </Button>\n </Box>\n \n {saveError && (\n <Alert severity=\"error\" style={{ marginTop: 8 }}>\n {saveError}\n </Alert>\n )}\n \n <Snackbar open={saveSuccess} autoHideDuration={5000} onClose={() => setSaveSuccess(false)}>\n <Alert severity=\"success\">\n {model === 'vit' ? (\n 'Image and classifications successfully saved to vector DB!'\n ) : (\n 'Detected objects successfully saved to vector DB!'\n )}\n </Alert>\n </Snackbar>\n \n {/* Search Dialog */}\n <Dialog\n open={openSearchDialog}\n onClose={handleCloseSearchDialog}\n maxWidth=\"md\"\n fullWidth\n >\n <DialogTitle>Search Vector Database</DialogTitle>\n <DialogContent>\n <FormControl className={classes.formControl}>\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n id=\"search-type\"\n value={searchType}\n onChange={handleSearchTypeChange}\n >\n <MenuItem value=\"image\">Search by Current Image</MenuItem>\n <MenuItem value=\"class\">Search by Class Name</MenuItem>\n </Select>\n </FormControl>\n \n {searchType === 'class' && (\n <FormControl className={classes.formControl}>\n <TextField\n label=\"Class Name\"\n value={searchClass}\n onChange={handleSearchClassChange}\n placeholder=\"e.g. person, car, dog...\"\n fullWidth\n />\n </FormControl>\n )}\n \n {searchError && (\n <Alert severity=\"error\" style={{ marginBottom: 16 }}>\n {searchError}\n </Alert>\n )}\n \n <Box className={classes.searchResults}>\n {isSearching ? (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" p={4}>\n <CircularProgress />\n <Typography variant=\"body1\" style={{ marginLeft: 16 }}>\n Searching...\n </Typography>\n </Box>\n ) : (\n <>\n {console.log('Search dialog render - searchResults:', searchResults)}\n {searchResults.length > 0 ? renderSearchResults() : \n <Typography variant=\"body1\">No results found. Please try another search.</Typography>\n }\n </>\n )}\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={handleCloseSearchDialog} color=\"default\">\n Close\n </Button>\n <Button \n onClick={handleSearch} \n color=\"primary\" \n variant=\"contained\"\n disabled={isSearching || (searchType === 'class' && !searchClass.trim())}\n >\n Search\n </Button>\n </DialogActions>\n </Dialog>\n </Box>\n );\n};\n\nexport default VectorDBActions;\n","import React from 'react';\nimport { \n Paper, \n Typography, \n Box, \n List, \n ListItem, \n ListItemText, \n Divider,\n Grid,\n Chip\n} from '@material-ui/core';\nimport VectorDBActions from './VectorDBActions';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n resultImage: {\n maxWidth: '100%',\n maxHeight: '400px',\n objectFit: 'contain'\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n chipContainer: {\n display: 'flex',\n gap: theme.spacing(1),\n flexWrap: 'wrap'\n }\n}));\n\nconst ResultDisplay = ({ results }) => {\n const classes = useStyles();\n if (!results) return null;\n \n const { model, data } = results;\n \n // Helper to format times nicely\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n \n // Check if there's an error\n if (data.error) {\n return (\n <Paper sx={{ p: 2, bgcolor: '#ffebee' }}>\n <Typography color=\"error\">{data.error}</Typography>\n </Paper>\n );\n }\n\n // Display performance info\n const renderPerformanceInfo = () => {\n if (!data.performance) return null;\n \n return (\n <Box className=\"performance-info\">\n <Divider className={classes.dividerMargin} />\n <Typography variant=\"body2\">\n Inference time: {formatTime(data.performance.inference_time)} on {data.performance.device}\n </Typography>\n </Box>\n );\n };\n\n // Render for YOLO and DETR (object detection)\n if (model === 'yolo' || model === 'detr') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n {model === 'yolo' ? 'YOLOv8' : 'DETR'} Detection Results\n </Typography>\n \n <Grid container spacing={3}>\n <Grid item xs={12} md={6}>\n {data.image && (\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detection Result\n </Typography>\n <img \n src={`data:image/png;base64,${data.image}`} \n alt=\"Detection Result\" \n className={classes.resultImage}\n />\n </Box>\n )}\n </Grid>\n \n <Grid item xs={12} md={6}>\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detected Objects:\n </Typography>\n \n {data.detections && data.detections.length > 0 ? (\n <List>\n {data.detections.map((detection, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {detection.class}\n </Typography>\n <Chip \n label={`${(detection.confidence * 100).toFixed(0)}%`}\n size=\"small\"\n color=\"primary\"\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n secondary={`Bounding Box: [${detection.bbox.join(', ')}]`} \n />\n </ListItem>\n {index < data.detections.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No objects detected</Typography>\n )}\n </Box>\n </Grid>\n </Grid>\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for Object Detection */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n // Render for ViT (classification)\n if (model === 'vit') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n ViT Classification Results\n </Typography>\n \n <Typography variant=\"subtitle1\" gutterBottom>\n Top Predictions:\n </Typography>\n \n {data.top_predictions && data.top_predictions.length > 0 ? (\n <List>\n {data.top_predictions.map((prediction, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {prediction.rank}. {prediction.class}\n </Typography>\n <Chip \n label={`${(prediction.probability * 100).toFixed(1)}%`}\n size=\"small\"\n color={index === 0 ? \"primary\" : \"default\"}\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n />\n </ListItem>\n {index < data.top_predictions.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No classifications available</Typography>\n )}\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for ViT Classification */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n return null;\n};\n\nexport default ResultDisplay;\n","import React, { useState } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n TextField, \n Button, \n CircularProgress,\n Divider\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n marginTop: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n responseBox: {\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n marginTop: theme.spacing(2),\n whiteSpace: 'pre-wrap'\n },\n buttonProgress: {\n marginLeft: theme.spacing(1)\n }\n}));\n\nconst LlmAnalysis = ({ visionResults, model }) => {\n const classes = useStyles();\n const [userQuery, setUserQuery] = useState('');\n const [isAnalyzing, setIsAnalyzing] = useState(false);\n const [analysisResult, setAnalysisResult] = useState(null);\n const [error, setError] = useState(null);\n\n // Format time for display\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n\n const handleAnalyze = async () => {\n if (!userQuery.trim()) return;\n \n setIsAnalyzing(true);\n setError(null);\n \n try {\n const response = await fetch('/api/analyze', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n visionResults: visionResults,\n userQuery: userQuery\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n \n if (data.error) {\n setError(data.error);\n } else {\n setAnalysisResult(data);\n }\n } catch (err) {\n console.error('Error analyzing with LLM:', err);\n setError(`Error analyzing with LLM: ${err.message}`);\n } finally {\n setIsAnalyzing(false);\n }\n };\n\n if (!visionResults) return null;\n\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n Ask AI about the {model === 'vit' ? 'Classification' : 'Detection'} Results\n </Typography>\n \n <Typography variant=\"body2\" className={classes.marginBottom}>\n Ask a question about the detected objects or classifications to get an AI-powered analysis.\n </Typography>\n \n <TextField\n fullWidth\n label=\"Your question about the image\"\n variant=\"outlined\"\n value={userQuery}\n onChange={(e) => setUserQuery(e.target.value)}\n disabled={isAnalyzing}\n className={classes.marginBottom}\n placeholder={model === 'vit' \n ? \"E.g., What category does this image belong to?\" \n : \"E.g., How many people are in this image?\"}\n />\n \n <Button \n variant=\"contained\" \n color=\"primary\"\n onClick={handleAnalyze}\n disabled={isAnalyzing || !userQuery.trim()}\n >\n Analyze with AI\n {isAnalyzing && <CircularProgress size={24} className={classes.buttonProgress} />}\n </Button>\n \n {error && (\n <Box mt={2}>\n <Typography color=\"error\">{error}</Typography>\n </Box>\n )}\n \n {analysisResult && (\n <>\n <Divider className={classes.dividerMargin} />\n \n <Typography variant=\"subtitle1\" gutterBottom>\n AI Analysis:\n </Typography>\n \n <Box className={classes.responseBox}>\n <Typography variant=\"body1\">\n {analysisResult.response}\n </Typography>\n </Box>\n \n {analysisResult.performance && (\n <Box mt={1}>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Analysis time: {formatTime(analysisResult.performance.inference_time)} on {analysisResult.performance.device}\n </Typography>\n </Box>\n )}\n </>\n )}\n </Paper>\n );\n};\n\nexport default LlmAnalysis;\n","import React, { useState } from 'react';\nimport {\n Paper,\n Typography,\n Grid,\n TextField,\n Button,\n Divider,\n MenuItem,\n Select,\n FormControl,\n InputLabel\n} from '@material-ui/core';\n\n// Props:\n// - imageBase64: data URL string of the currently uploaded image (preferred for searchType=image)\nfunction OpenAIChat({ imageBase64 }) {\n const [apiKey, setApiKey] = useState('');\n const [prompt, setPrompt] = useState('');\n const [response, setResponse] = useState('');\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState('');\n\n // Vision RAG parameters\n const [searchType, setSearchType] = useState('image');\n const [objectId, setObjectId] = useState('');\n const [className, setClassName] = useState('');\n const [nResults, setNResults] = useState(5);\n\n const onSend = async () => {\n setError('');\n setResponse('');\n const q = (prompt || '').trim();\n if (!q) { setError('Please enter a question.'); return; }\n\n // Build request body for /api/vision-rag/query\n const body = {\n userQuery: q,\n searchType: searchType,\n n_results: Number(nResults) || 5,\n };\n if (apiKey) body.api_key = apiKey;\n if (searchType === 'image') {\n if (!imageBase64) { setError('Image is required. Please upload an image first.'); return; }\n body.image = imageBase64;\n } else if (searchType === 'object') {\n if (!objectId.trim()) { setError('Please enter an objectId.'); return; }\n body.objectId = objectId.trim();\n } else if (searchType === 'class') {\n if (!className.trim()) { setError('Please enter a class_name.'); return; }\n body.class_name = className.trim();\n }\n\n setLoading(true);\n try {\n const res = await fetch('/api/vision-rag/query', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(body)\n });\n\n if (!res.ok) {\n let txt = await res.text();\n try { txt = JSON.stringify(JSON.parse(txt), null, 2); } catch {}\n throw new Error(txt);\n }\n const data = await res.json();\n const meta = `Model: ${data.model || '-'} | Latency: ${data.latency_sec || '-'}s`;\n \n // Format search results\n let retrievedText = '';\n if (data.retrieved && data.retrieved.length > 0) {\n retrievedText = '\\n\\nSearch Results:\\n';\n data.retrieved.forEach((item, index) => {\n retrievedText += `${index + 1}. ID: ${item.id || '-'}\\n`;\n if (item.meta) {\n retrievedText += ` Class: ${item.meta.class || '-'}\\n`;\n retrievedText += ` Confidence: ${item.meta.confidence || '-'}\\n`;\n }\n retrievedText += ` Similarity: ${item.distance ? item.distance.toFixed(4) : '-'}\\n`;\n });\n }\n \n setResponse((data.answer || '(No response)') + retrievedText + '\\n\\n---\\n' + meta);\n } catch (e) {\n setError('Error: ' + e.message);\n } finally {\n setLoading(false);\n }\n };\n\n const onClear = () => {\n setPrompt('');\n setResponse('');\n setError('');\n };\n\n return (\n <Paper style={{ padding: 16 }}>\n <Typography variant=\"h5\" gutterBottom>\n Vision RAG (LangChain)\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n If OPENAI_API_KEY is set on the server, API Key is optional. Select a search type and send a question to get answers based on context retrieved from the vector DB.\n </Typography>\n <Grid container spacing={2}>\n <Grid item xs={12} md={6}>\n <FormControl fullWidth variant=\"outlined\" size=\"small\">\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n value={searchType}\n onChange={(e) => setSearchType(e.target.value)}\n label=\"Search Type\"\n >\n <MenuItem value=\"image\">image (current upload)</MenuItem>\n <MenuItem value=\"object\">object (objectId)</MenuItem>\n <MenuItem value=\"class\">class (class_name)</MenuItem>\n </Select>\n </FormControl>\n </Grid>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"OpenAI API Key (optional)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"password\"\n placeholder=\"sk-...\"\n />\n </Grid>\n\n {searchType === 'object' && (\n <Grid item xs={12} md={6}>\n <TextField\n label=\"objectId\"\n value={objectId}\n onChange={(e) => setObjectId(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n )}\n {searchType === 'class' && (\n <Grid item xs={12} md={6}>\n <TextField\n label=\"class_name\"\n value={className}\n onChange={(e) => setClassName(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n placeholder=\"e.g. person, car, dog\"\n />\n </Grid>\n )}\n <Grid item xs={12} md={6}>\n <TextField\n label=\"n_results\"\n value={nResults}\n onChange={(e) => setNResults(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"number\"\n inputProps={{ min: 1, max: 50 }}\n />\n </Grid>\n\n <Grid item xs={12}>\n <TextField\n label=\"User Question\"\n value={prompt}\n onChange={(e) => setPrompt(e.target.value)}\n fullWidth\n multiline\n rows={4}\n variant=\"outlined\"\n placeholder={searchType === 'image' ? 'Answer based on the uploaded image' : 'Answer using the retrieved object context'}\n />\n </Grid>\n {error && (\n <Grid item xs={12}>\n <Typography color=\"error\">{error}</Typography>\n </Grid>\n )}\n <Grid item xs={12}>\n <div style={{ display: 'flex', gap: 8 }}>\n <Button color=\"primary\" variant=\"contained\" onClick={onSend} disabled={loading}>\n {loading ? 'Sending...' : 'Send Question'}\n </Button>\n <Button variant=\"outlined\" onClick={onClear}>Clear</Button>\n </div>\n </Grid>\n <Grid item xs={12}>\n <Divider style={{ margin: '12px 0' }} />\n <Typography variant=\"subtitle2\" color=\"textSecondary\">Response</Typography>\n <pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'ui-monospace, monospace' }}>{response}</pre>\n </Grid>\n </Grid>\n </Paper>\n );\n}\n\nexport default OpenAIChat;\n","import React, { useState, useEffect } from 'react';\nimport { \n Container, \n Typography, \n Box, \n Paper, \n Grid, \n CircularProgress,\n AppBar,\n Toolbar,\n ThemeProvider,\n createMuiTheme\n} from '@material-ui/core';\nimport ImageUploader from './components/ImageUploader';\nimport ModelSelector from './components/ModelSelector';\nimport ResultDisplay from './components/ResultDisplay';\nimport LlmAnalysis from './components/LlmAnalysis';\nimport OpenAIChat from './components/OpenAIChat';\nimport './App.css';\n\n// Create a theme\nconst theme = createMuiTheme({\n palette: {\n primary: {\n main: '#3f51b5',\n },\n secondary: {\n main: '#f50057',\n },\n },\n typography: {\n fontFamily: 'Roboto, Arial, sans-serif',\n },\n});\n\nfunction App() {\n const [selectedImage, setSelectedImage] = useState(null);\n const [imageBase64, setImageBase64] = useState('');\n const [selectedModel, setSelectedModel] = useState('');\n const [isProcessing, setIsProcessing] = useState(false);\n const [results, setResults] = useState(null);\n const [error, setError] = useState(null);\n const [modelsStatus, setModelsStatus] = useState({\n yolo: false,\n detr: false,\n vit: false\n });\n\n // Check API status on component mount\n useEffect(() => {\n fetch('/api/status')\n .then(response => response.json())\n .then(data => {\n setModelsStatus(data.models);\n })\n .catch(err => {\n console.error('Error checking API status:', err);\n setError('Error connecting to the backend API. Please make sure the server is running.');\n });\n }, []);\n\n const handleImageUpload = (image) => {\n setSelectedImage(image);\n setResults(null);\n setError(null);\n\n // Convert uploaded File -> base64 data URL for Vision RAG image search\n if (image instanceof File) {\n const reader = new FileReader();\n reader.onload = () => {\n const dataUrl = reader.result; // e.g., data:image/png;base64,....\n setImageBase64(typeof dataUrl === 'string' ? dataUrl : '');\n };\n reader.onerror = () => setImageBase64('');\n reader.readAsDataURL(image);\n } else {\n setImageBase64('');\n }\n };\n\n const handleModelSelect = (model) => {\n setSelectedModel(model);\n setResults(null);\n setError(null);\n };\n\n const processImage = async () => {\n if (!selectedImage || !selectedModel) {\n setError('Please select both an image and a model');\n return;\n }\n\n setIsProcessing(true);\n setError(null);\n\n // Create form data for the image\n const formData = new FormData();\n formData.append('image', selectedImage);\n\n let endpoint = '';\n switch (selectedModel) {\n case 'yolo':\n endpoint = '/api/detect/yolo';\n break;\n case 'detr':\n endpoint = '/api/detect/detr';\n break;\n case 'vit':\n endpoint = '/api/classify/vit';\n break;\n default:\n setError('Invalid model selection');\n setIsProcessing(false);\n return;\n }\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n setResults({ model: selectedModel, data });\n } catch (err) {\n console.error('Error processing image:', err);\n setError(`Error processing image: ${err.message}`);\n } finally {\n setIsProcessing(false);\n }\n };\n\n return (\n <ThemeProvider theme={theme}>\n <Box style={{ flexGrow: 1 }}>\n <AppBar position=\"static\">\n <Toolbar>\n <Typography variant=\"h6\" style={{ flexGrow: 1 }}>\n Multi-Model Object Detection Demo\n </Typography>\n </Toolbar>\n </AppBar>\n <Container maxWidth=\"lg\" style={{ marginTop: theme.spacing(4), marginBottom: theme.spacing(4) }}>\n <Grid container spacing={3}>\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2) }}>\n <Typography variant=\"h5\" gutterBottom>\n Upload an image to see how each model performs!\n </Typography>\n <Typography variant=\"body1\" paragraph>\n This demo showcases three different object detection and image classification models:\n </Typography>\n <Typography variant=\"body1\" component=\"div\">\n <ul>\n <li><strong>YOLOv8</strong>: Fast and accurate object detection</li>\n <li><strong>DETR</strong>: DEtection TRansformer for object detection</li>\n <li><strong>ViT</strong>: Vision Transformer for image classification</li>\n </ul>\n </Typography>\n </Paper>\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ImageUploader onImageUpload={handleImageUpload} />\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ModelSelector \n onModelSelect={handleModelSelect} \n onProcess={processImage}\n isProcessing={isProcessing}\n modelsStatus={modelsStatus}\n selectedModel={selectedModel}\n imageSelected={!!selectedImage}\n />\n </Grid>\n \n {error && (\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2), backgroundColor: '#ffebee' }}>\n <Typography color=\"error\">{error}</Typography>\n </Paper>\n </Grid>\n )}\n \n {isProcessing && (\n <Grid item xs={12} style={{ textAlign: 'center', margin: `${theme.spacing(4)}px 0` }}>\n <CircularProgress />\n <Typography variant=\"h6\" style={{ marginTop: theme.spacing(2) }}>\n Processing image...\n </Typography>\n </Grid>\n )}\n \n {results && (\n <>\n <Grid item xs={12}>\n <ResultDisplay results={results} />\n </Grid>\n <Grid item xs={12}>\n <LlmAnalysis visionResults={results.data} model={results.model} />\n </Grid>\n </>\n )}\n\n {/* Vision RAG (LangChain) section at the end */}\n <Grid item xs={12}>\n <OpenAIChat imageBase64={imageBase64} />\n </Grid>\n </Grid>\n </Container>\n </Box>\n </ThemeProvider>\n );\n}\n\nexport default App;\n","const reportWebVitals = (onPerfEntry) => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"],"sourceRoot":""}
|
|
|
|
static/static/js/main.fc0e2dc7.chunk.js
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
(this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[]).push([[0],{102:function(e,a,t){},103:function(e,a,t){"use strict";t.r(a);var n=t(0),r=t.n(n),l=t(11),o=t.n(l),c=(t(97),t(78)),i=t(155),s=t(159),m=t(156),d=t(157),u=t(48),g=t(158),p=t(138),E=t(81),h=t(143),y=t(136),b=t(137),f=t(63),v=t.n(f),x=t(75),S=t.n(x),C=t(133);const j=Object(C.a)(e=>({paper:{padding:e.spacing(2),display:"flex",flexDirection:"column",alignItems:"center",height:"100%",minHeight:300,transition:"all 0.3s ease"},dragActive:{border:"2px dashed #3f51b5",backgroundColor:"rgba(63, 81, 181, 0.05)"},dragInactive:{border:"2px dashed #ccc",backgroundColor:"white"},uploadBox:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",height:"100%",width:"100%",cursor:"pointer"},uploadIcon:{fontSize:60,color:"#3f51b5",marginBottom:e.spacing(2)},supportText:{marginTop:e.spacing(2)},previewBox:{display:"flex",flexDirection:"column",alignItems:"center",width:"100%",height:"100%",position:"relative"},imageContainer:{position:"relative",width:"100%",height:"60vh",[e.breakpoints.down("sm")]:{height:"45vh"},display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",marginTop:e.spacing(2)},deleteButton:{position:"absolute",top:0,right:0,backgroundColor:"rgba(255, 255, 255, 0.7)","&:hover":{backgroundColor:"rgba(255, 255, 255, 0.9)"}}}));var w=e=>{let{onImageUpload:a}=e;const t=j(),[l,o]=Object(n.useState)(null),[c,i]=Object(n.useState)(!1),m=Object(n.useRef)(null),d=e=>{e.preventDefault(),e.stopPropagation(),"dragenter"===e.type||"dragover"===e.type?i(!0):"dragleave"===e.type&&i(!1)},g=e=>{e.type.startsWith("image/")?(o(URL.createObjectURL(e)),a(e)):alert("Please upload an image file")};return r.a.createElement(E.a,{className:"".concat(t.paper," ").concat(c?t.dragActive:t.dragInactive),onDragEnter:d,onDragLeave:d,onDragOver:d,onDrop:e=>{e.preventDefault(),e.stopPropagation(),i(!1),e.dataTransfer.files&&e.dataTransfer.files[0]&&g(e.dataTransfer.files[0])}},r.a.createElement("input",{ref:m,type:"file",accept:"image/*",onChange:e=>{e.preventDefault(),e.target.files&&e.target.files[0]&&g(e.target.files[0])},style:{display:"none"}}),l?r.a.createElement(s.a,{className:t.previewBox},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Preview"),r.a.createElement(s.a,{className:t.imageContainer},r.a.createElement("img",{src:l,alt:"Preview",className:"preview-image",style:{width:"100%",height:"auto",maxHeight:"100%",objectFit:"contain",display:"block"}}),r.a.createElement(b.a,{"aria-label":"delete",className:t.deleteButton,onClick:()=>{o(null),a(null),m.current.value=""}},r.a.createElement(S.a,null)))):r.a.createElement(s.a,{className:t.uploadBox,onClick:()=>{m.current.click()}},r.a.createElement(v.a,{className:t.uploadIcon}),r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Drag & Drop an image here"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"or"),r.a.createElement(y.a,{variant:"contained",color:"primary",component:"span",startIcon:r.a.createElement(v.a,null)},"Browse Files"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",className:t.supportText},"Supported formats: JPG, PNG, GIF")))},O=t(139),N=t(140),B=t(166),T=t(141),I=t(64),k=t.n(I),P=t(76),D=t.n(P),A=t(77),F=t.n(A);const R=Object(C.a)(e=>({card:{height:"100%",display:"flex",flexDirection:"column"},selectedCard:{border:"2px solid #3f51b5"},unavailableCard:{opacity:.6},cardContent:{flexGrow:1},chipContainer:{marginBottom:e.spacing(1.5)},successChip:{backgroundColor:"#34C759",color:"#fff"},errorChip:{backgroundColor:"#FF3B3F",color:"#fff"},modelType:{marginTop:e.spacing(1)},processButton:{marginTop:e.spacing(3),textAlign:"center"}}));var z=e=>{let{onModelSelect:a,onProcess:t,isProcessing:n,modelsStatus:l,selectedModel:o,imageSelected:c}=e;const i=R(),m=[{id:"yolo",name:"YOLOv8",description:"Fast and accurate object detection",icon:r.a.createElement(k.a,null),available:l.yolo},{id:"detr",name:"DETR",description:"DEtection TRansformer for object detection",icon:r.a.createElement(k.a,null),available:l.detr},{id:"vit",name:"ViT",description:"Vision Transformer for image classification",icon:r.a.createElement(D.a,null),available:l.vit}],d=e=>{m.find(a=>a.id===e).available&&a(e)};return r.a.createElement(s.a,{sx:{p:2,height:"100%"}},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Select Model"),r.a.createElement(p.a,{container:!0,spacing:2},m.map(e=>r.a.createElement(p.a,{item:!0,xs:12,sm:4,key:e.id},r.a.createElement(O.a,{className:"\n ".concat(i.card," \n ").concat(o===e.id?i.selectedCard:""," \n ").concat(e.available?"":i.unavailableCard,"\n "),onClick:()=>d(e.id)},r.a.createElement(N.a,{className:i.cardContent},r.a.createElement(s.a,{sx:{mb:2,color:"primary"}},e.icon),r.a.createElement(u.a,{variant:"h5",component:"div",gutterBottom:!0},e.name),r.a.createElement("div",{className:i.chipContainer},e.available?r.a.createElement(B.a,{label:"Available",className:i.successChip,size:"small"}):r.a.createElement(B.a,{label:"Not Available",className:i.errorChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.description)),r.a.createElement(T.a,null,r.a.createElement(y.a,{size:"small",onClick:()=>d(e.id),disabled:!e.available,color:o===e.id?"primary":"default",variant:o===e.id?"contained":"outlined",fullWidth:!0},o===e.id?"Selected":"Select")))))),r.a.createElement("div",{className:i.processButton},r.a.createElement(y.a,{variant:"contained",color:"primary",size:"large",startIcon:r.a.createElement(F.a,null),onClick:t,disabled:!o||!c||n},n?"Processing...":"Process Image")))},L=t(153),M=t(150),W=t(105),_=t(154),J=t(142),H=t(164),U=t(163),V=t(145),G=t(146),Y=t(147),q=t(167),K=t(160),Q=t(151),X=t(169),Z=t(152),$=t(161);const ee=Object(C.a)(e=>({root:{marginTop:e.spacing(2),marginBottom:e.spacing(2),padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius},button:{marginRight:e.spacing(2)},searchDialog:{minWidth:"500px"},formControl:{marginBottom:e.spacing(2),minWidth:"100%"},searchResults:{marginTop:e.spacing(2)},resultCard:{marginBottom:e.spacing(2)},resultImage:{height:140,objectFit:"contain"},chip:{margin:e.spacing(.5)},similarityChip:{backgroundColor:e.palette.primary.main,color:"white"}}));var ae=e=>{let{results:a}=e;const t=ee(),[l,o]=Object(n.useState)(!1),[c,i]=Object(n.useState)(!1),[m,d]=Object(n.useState)(null),[g,E]=Object(n.useState)(!1),[b,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,C]=Object(n.useState)([]),[j,w]=Object(n.useState)(!1),[T,I]=Object(n.useState)(null),{model:k,data:P}=a,D=()=>{E(!1)},A=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){const a=16*Math.random()|0;return("x"===e?a:3&a|8).toString(16)}));return r.a.createElement(s.a,{className:t.root},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Vector Database Actions"),r.a.createElement(s.a,{display:"flex",alignItems:"center",mb:2},r.a.createElement(y.a,{variant:"contained",color:"primary",onClick:async()=>{o(!0),d(null);try{let e;if(e="vit"===k?await fetch("/api/add-to-collection",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,metadata:{model:"vit",classifications:P.classifications}})}):await fetch("/api/add-detected-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,objects:P.detections,imageId:A()})}),!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const a=await e.json();if(a.error)throw new Error(a.error);i(!0),setTimeout(()=>i(!1),5e3)}catch(e){console.error("Error saving to vector DB:",e),d("Error saving to vector DB: ".concat(e.message))}finally{o(!1)}},disabled:l,className:t.button},l?r.a.createElement(r.a.Fragment,null,r.a.createElement(h.a,{size:20,color:"inherit",style:{marginRight:8}}),"Saving..."):"Save to Vector DB"),r.a.createElement(y.a,{variant:"outlined",color:"primary",onClick:()=>{E(!0),C([]),I(null)},className:t.button},"Search Similar")),m&&r.a.createElement($.a,{severity:"error",style:{marginTop:8}},m),r.a.createElement(H.a,{open:c,autoHideDuration:5e3,onClose:()=>i(!1)},r.a.createElement($.a,{severity:"success"},"vit"===k?"Image and classifications successfully saved to vector DB!":"Detected objects successfully saved to vector DB!")),r.a.createElement(U.a,{open:g,onClose:D,maxWidth:"md",fullWidth:!0},r.a.createElement(V.a,null,"Search Vector Database"),r.a.createElement(G.a,null,r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(q.a,{id:"search-type-label"},"Search Type"),r.a.createElement(K.a,{labelId:"search-type-label",id:"search-type",value:b,onChange:e=>{f(e.target.value),C([]),I(null)}},r.a.createElement(Q.a,{value:"image"},"Search by Current Image"),r.a.createElement(Q.a,{value:"class"},"Search by Class Name"))),"class"===b&&r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(X.a,{label:"Class Name",value:v,onChange:e=>{x(e.target.value)},placeholder:"e.g. person, car, dog...",fullWidth:!0})),T&&r.a.createElement($.a,{severity:"error",style:{marginBottom:16}},T),r.a.createElement(s.a,{className:t.searchResults},j?r.a.createElement(s.a,{display:"flex",justifyContent:"center",alignItems:"center",p:4},r.a.createElement(h.a,null),r.a.createElement(u.a,{variant:"body1",style:{marginLeft:16}},"Searching...")):r.a.createElement(r.a.Fragment,null,console.log("Search dialog render - searchResults:",S),S.length>0?(console.log("Rendering search results:",S),console.log("Search results length:",S.length),0===S.length?(console.log("No results to render"),r.a.createElement(u.a,{variant:"body1"},"No results found.")):r.a.createElement(p.a,{container:!0,spacing:2},S.map((e,a)=>{const n=100*(1-e.distance);return r.a.createElement(p.a,{item:!0,xs:12,sm:6,key:a},r.a.createElement(O.a,{className:t.resultCard},e.metadata&&e.metadata.image_data?r.a.createElement(J.a,{className:t.resultImage,component:"img",height:"200",image:"data:image/jpeg;base64,".concat(e.metadata.image_data),alt:e.metadata&&e.metadata.class?e.metadata.class:"Object"}):r.a.createElement(s.a,{className:t.resultImage,style:{backgroundColor:"#f0f0f0",display:"flex",alignItems:"center",justifyContent:"center",height:200}},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.metadata&&e.metadata.class?e.metadata.class:"Object"," Image")),r.a.createElement(N.a,null,r.a.createElement(s.a,{display:"flex",justifyContent:"space-between",alignItems:"center",mb:1},r.a.createElement(u.a,{variant:"subtitle1"},"Result #",a+1),r.a.createElement(B.a,{label:"Similarity: ".concat(n.toFixed(2),"%"),className:t.similarityChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Class:")," ",e.metadata.class||"N/A"),e.metadata.confidence&&r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Confidence:")," ",(100*e.metadata.confidence).toFixed(2),"%"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Object ID:")," ",e.id))))}))):r.a.createElement(u.a,{variant:"body1"},"No results found. Please try another search.")))),r.a.createElement(Z.a,null,r.a.createElement(y.a,{onClick:D,color:"default"},"Close"),r.a.createElement(y.a,{onClick:async()=>{w(!0),I(null);try{let e={};if("image"===b)e={searchType:"image",image:P.image,n_results:5};else{if(!v.trim())throw new Error("Please enter a class name");e={searchType:"class",class_name:v.trim(),n_results:5}}const a=await fetch("/api/search-similar-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!a.ok)throw new Error("HTTP error! Status: ".concat(a.status));const t=await a.json();if(t.error)throw new Error(t.error);if(console.log("Search API response:",t),!t.success||!Array.isArray(t.results))throw console.error("Unexpected API response format:",t),new Error("Unexpected API response format");console.log("Setting search results array:",t.results),console.log("Results array length:",t.results.length),console.log("First result item:",t.results[0]),C(t.results)}catch(e){console.error("Error searching vector DB:",e),I("Error searching vector DB: ".concat(e.message))}finally{w(!1)}},color:"primary",variant:"contained",disabled:j||"class"===b&&!v.trim()},"Search"))))};const te=Object(C.a)(e=>({paper:{padding:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},resultImage:{maxWidth:"100%",maxHeight:"400px",objectFit:"contain"},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},chipContainer:{display:"flex",gap:e.spacing(1),flexWrap:"wrap"}}));var ne=e=>{let{results:a}=e;const t=te();if(!a)return null;const{model:n,data:l}=a;if(l.error)return r.a.createElement(E.a,{sx:{p:2,bgcolor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},l.error));const o=()=>l.performance?r.a.createElement(s.a,{className:"performance-info"},r.a.createElement(L.a,{className:t.dividerMargin}),r.a.createElement(u.a,{variant:"body2"},"Inference time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(l.performance.inference_time)," on ",l.performance.device)):null;return"yolo"===n||"detr"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"yolo"===n?"YOLOv8":"DETR"," Detection Results"),r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12,md:6},l.image&&r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detection Result"),r.a.createElement("img",{src:"data:image/png;base64,".concat(l.image),alt:"Detection Result",className:t.resultImage}))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detected Objects:"),l.detections&&l.detections.length>0?r.a.createElement(M.a,null,l.detections.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(_.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.class),r.a.createElement(B.a,{label:"".concat((100*e.confidence).toFixed(0),"%"),size:"small",color:"primary",style:{marginLeft:8}})),secondary:"Bounding Box: [".concat(e.bbox.join(", "),"]")})),a<l.detections.length-1&&r.a.createElement(L.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No objects detected")))),o(),r.a.createElement(ae,{results:a})):"vit"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"ViT Classification Results"),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Top Predictions:"),l.top_predictions&&l.top_predictions.length>0?r.a.createElement(M.a,null,l.top_predictions.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(_.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.rank,". ",e.class),r.a.createElement(B.a,{label:"".concat((100*e.probability).toFixed(1),"%"),size:"small",color:0===a?"primary":"default",style:{marginLeft:8}}))})),a<l.top_predictions.length-1&&r.a.createElement(L.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No classifications available"),o(),r.a.createElement(ae,{results:a})):null};const re=Object(C.a)(e=>({paper:{padding:e.spacing(2),marginTop:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},responseBox:{padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius,marginTop:e.spacing(2),whiteSpace:"pre-wrap"},buttonProgress:{marginLeft:e.spacing(1)}}));var le=e=>{let{visionResults:a,model:t}=e;const l=re(),[o,c]=Object(n.useState)(""),[i,m]=Object(n.useState)(!1),[d,g]=Object(n.useState)(null),[p,b]=Object(n.useState)(null);return a?r.a.createElement(E.a,{className:l.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Ask AI about the ","vit"===t?"Classification":"Detection"," Results"),r.a.createElement(u.a,{variant:"body2",className:l.marginBottom},"Ask a question about the detected objects or classifications to get an AI-powered analysis."),r.a.createElement(X.a,{fullWidth:!0,label:"Your question about the image",variant:"outlined",value:o,onChange:e=>c(e.target.value),disabled:i,className:l.marginBottom,placeholder:"vit"===t?"E.g., What category does this image belong to?":"E.g., How many people are in this image?"}),r.a.createElement(y.a,{variant:"contained",color:"primary",onClick:async()=>{if(o.trim()){m(!0),b(null);try{const e=await fetch("/api/analyze",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({visionResults:a,userQuery:o})});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const t=await e.json();t.error?b(t.error):g(t)}catch(e){console.error("Error analyzing with LLM:",e),b("Error analyzing with LLM: ".concat(e.message))}finally{m(!1)}}},disabled:i||!o.trim()},"Analyze with AI",i&&r.a.createElement(h.a,{size:24,className:l.buttonProgress})),p&&r.a.createElement(s.a,{mt:2},r.a.createElement(u.a,{color:"error"},p)),d&&r.a.createElement(r.a.Fragment,null,r.a.createElement(L.a,{className:l.dividerMargin}),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"AI Analysis:"),r.a.createElement(s.a,{className:l.responseBox},r.a.createElement(u.a,{variant:"body1"},d.response)),d.performance&&r.a.createElement(s.a,{mt:1},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},"Analysis time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(d.performance.inference_time)," on ",d.performance.device)))):null};var oe=function(){const[e,a]=Object(n.useState)("gpt-4"),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)("You are a helpful assistant."),[i,s]=Object(n.useState)(""),[m,d]=Object(n.useState)(""),[g,h]=Object(n.useState)(!1),[b,f]=Object(n.useState)("");return r.a.createElement(E.a,{style:{padding:16}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"OpenAI Chat (OpenAI API)"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"If the server env var OPENAI_API_KEY is set, the API Key field is optional."),r.a.createElement(p.a,{container:!0,spacing:2},r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"Model",value:e,onChange:e=>a(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"OpenAI API Key (optional)",value:t,onChange:e=>l(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"password",placeholder:"sk-..."})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"System Prompt (optional)",value:o,onChange:e=>c(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"User Question",value:i,onChange:e=>s(e.target.value),fullWidth:!0,multiline:!0,rows:4,variant:"outlined"})),b&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(u.a,{color:"error"},b)),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement("div",{style:{display:"flex",gap:8}},r.a.createElement(y.a,{color:"primary",variant:"contained",onClick:async()=>{f(""),d("");const a=(i||"").trim();if(a){h(!0);try{const r=await fetch("/api/openai/chat",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({prompt:a,model:(e||"").trim()||"gpt-4",api_key:t||void 0,system:o||void 0})});if(!r.ok){let e=await r.text();try{e=JSON.stringify(JSON.parse(e),null,2)}catch(n){}throw new Error(e)}const l=await r.json(),c="Model: ".concat(l.model," | Latency: ").concat(l.latency_sec,"s")+(l.usage?" | Usage: ".concat(JSON.stringify(l.usage)):"");d((l.response||"(Empty response)")+"\n\n---\n"+c)}catch(r){f("Error: "+r.message)}finally{h(!1)}}else f("Please enter a question.")},disabled:g},g?"Sending...":"Send Question"),r.a.createElement(y.a,{variant:"outlined",onClick:()=>{s(""),d(""),f("")}},"Clear"))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(L.a,{style:{margin:"12px 0"}}),r.a.createElement(u.a,{variant:"subtitle2",color:"textSecondary"},"Response"),r.a.createElement("pre",{style:{whiteSpace:"pre-wrap",fontFamily:"ui-monospace, monospace"}},m))))};t(102);const ce=Object(c.a)({palette:{primary:{main:"#3f51b5"},secondary:{main:"#f50057"}},typography:{fontFamily:"Roboto, Arial, sans-serif"}});var ie=function(){const[e,a]=Object(n.useState)(null),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(!1),[y,b]=Object(n.useState)(null),[f,v]=Object(n.useState)(null),[x,S]=Object(n.useState)({yolo:!1,detr:!1,vit:!1});return Object(n.useEffect)(()=>{fetch("/api/status").then(e=>e.json()).then(e=>{S(e.models)}).catch(e=>{console.error("Error checking API status:",e),v("Error connecting to the backend API. Please make sure the server is running.")})},[]),r.a.createElement(i.a,{theme:ce},r.a.createElement(s.a,{style:{flexGrow:1}},r.a.createElement(m.a,{position:"static"},r.a.createElement(d.a,null,r.a.createElement(u.a,{variant:"h6",style:{flexGrow:1}},"Multi-Model Object Detection Demo"))),r.a.createElement(g.a,{maxWidth:"lg",style:{marginTop:ce.spacing(4),marginBottom:ce.spacing(4)}},r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2)}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Upload an image to see how each model performs!"),r.a.createElement(u.a,{variant:"body1",paragraph:!0},"This demo showcases three different object detection and image classification models:"),r.a.createElement(u.a,{variant:"body1",component:"div"},r.a.createElement("ul",null,r.a.createElement("li",null,r.a.createElement("strong",null,"YOLOv8"),": Fast and accurate object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"DETR"),": DEtection TRansformer for object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"ViT"),": Vision Transformer for image classification"))))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(w,{onImageUpload:e=>{a(e),b(null),v(null)}})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(z,{onModelSelect:e=>{l(e),b(null),v(null)},onProcess:async()=>{if(!e||!t)return void v("Please select both an image and a model");c(!0),v(null);const a=new FormData;a.append("image",e);let n="";switch(t){case"yolo":n="/api/detect/yolo";break;case"detr":n="/api/detect/detr";break;case"vit":n="/api/classify/vit";break;default:return v("Invalid model selection"),void c(!1)}try{const e=await fetch(n,{method:"POST",body:a});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const r=await e.json();b({model:t,data:r})}catch(r){console.error("Error processing image:",r),v("Error processing image: ".concat(r.message))}finally{c(!1)}},isProcessing:o,modelsStatus:x,selectedModel:t,imageSelected:!!e})),f&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2),backgroundColor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},f))),o&&r.a.createElement(p.a,{item:!0,xs:12,style:{textAlign:"center",margin:"".concat(ce.spacing(4),"px 0")}},r.a.createElement(h.a,null),r.a.createElement(u.a,{variant:"h6",style:{marginTop:ce.spacing(2)}},"Processing image...")),y&&r.a.createElement(r.a.Fragment,null,r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(ne,{results:y})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(le,{visionResults:y.data,model:y.model}))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(oe,null))))))};var se=e=>{e&&e instanceof Function&&t.e(3).then(t.bind(null,170)).then(a=>{let{getCLS:t,getFID:n,getFCP:r,getLCP:l,getTTFB:o}=a;t(e),n(e),r(e),l(e),o(e)})};o.a.render(r.a.createElement(r.a.StrictMode,null,r.a.createElement(ie,null)),document.getElementById("root")),se()},92:function(e,a,t){e.exports=t(103)},97:function(e,a,t){}},[[92,1,2]]]);
|
2 |
-
//# sourceMappingURL=main.fc0e2dc7.chunk.js.map
|
|
|
|
|
|
static/static/js/main.fc0e2dc7.chunk.js.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["components/ImageUploader.js","components/ModelSelector.js","components/VectorDBActions.js","components/ResultDisplay.js","components/LlmAnalysis.js","components/OpenAIChat.js","App.js","reportWebVitals.js","index.js"],"names":["useStyles","makeStyles","theme","paper","padding","spacing","display","flexDirection","alignItems","height","minHeight","transition","dragActive","border","backgroundColor","dragInactive","uploadBox","justifyContent","width","cursor","uploadIcon","fontSize","color","marginBottom","supportText","marginTop","previewBox","position","imageContainer","breakpoints","down","overflow","deleteButton","top","right","ImageUploader","_ref","onImageUpload","classes","previewUrl","setPreviewUrl","useState","setDragActive","fileInputRef","useRef","handleDrag","e","preventDefault","stopPropagation","type","handleFiles","file","startsWith","URL","createObjectURL","alert","React","createElement","Paper","className","concat","onDragEnter","onDragLeave","onDragOver","onDrop","dataTransfer","files","ref","accept","onChange","target","style","Box","Typography","variant","gutterBottom","src","alt","maxHeight","objectFit","IconButton","aria-label","onClick","handleRemoveImage","current","value","DeleteIcon","onButtonClick","click","CloudUploadIcon","Button","component","startIcon","card","selectedCard","unavailableCard","opacity","cardContent","flexGrow","chipContainer","successChip","errorChip","modelType","processButton","textAlign","ModelSelector","onModelSelect","onProcess","isProcessing","modelsStatus","selectedModel","imageSelected","models","id","name","description","icon","VisibilityIcon","available","yolo","detr","CategoryIcon","vit","handleModelClick","modelId","find","m","sx","p","Grid","container","map","model","item","xs","sm","key","Card","CardContent","mb","Chip","label","size","CardActions","disabled","fullWidth","PlayArrowIcon","root","borderRadius","shape","button","marginRight","searchDialog","minWidth","formControl","searchResults","resultCard","resultImage","chip","margin","similarityChip","palette","primary","main","VectorDBActions","results","isSaving","setIsSaving","saveSuccess","setSaveSuccess","saveError","setSaveError","openSearchDialog","setOpenSearchDialog","searchType","setSearchType","searchClass","setSearchClass","setSearchResults","isSearching","setIsSearching","searchError","setSearchError","data","handleCloseSearchDialog","generateUUID","replace","c","r","Math","random","toString","async","response","fetch","method","headers","body","JSON","stringify","image","metadata","classifications","objects","detections","imageId","ok","Error","status","result","json","error","setTimeout","err","console","message","Fragment","CircularProgress","handleOpenSearchDialog","Alert","severity","Snackbar","open","autoHideDuration","onClose","Dialog","maxWidth","DialogTitle","DialogContent","FormControl","InputLabel","Select","labelId","event","MenuItem","TextField","placeholder","marginLeft","log","length","index","similarity","distance","image_data","CardMedia","class","toFixed","confidence","DialogActions","requestBody","n_results","trim","class_name","success","Array","isArray","dividerMargin","gap","flexWrap","ResultDisplay","bgcolor","renderPerformanceInfo","performance","Divider","ms","undefined","isNaN","num","Number","formatTime","inference_time","device","md","List","detection","ListItem","ListItemText","secondary","bbox","join","top_predictions","prediction","rank","probability","responseBox","whiteSpace","buttonProgress","LlmAnalysis","visionResults","userQuery","setUserQuery","isAnalyzing","setIsAnalyzing","analysisResult","setAnalysisResult","setError","mt","OpenAIChat","setModel","apiKey","setApiKey","system","setSystem","prompt","setPrompt","setResponse","loading","setLoading","multiline","rows","res","credentials","api_key","txt","text","parse","_unused","meta","latency_sec","usage","onClear","fontFamily","createMuiTheme","typography","App","selectedImage","setSelectedImage","setSelectedModel","setIsProcessing","setResults","setModelsStatus","useEffect","then","catch","ThemeProvider","AppBar","Toolbar","Container","paragraph","formData","FormData","append","endpoint","reportWebVitals","onPerfEntry","Function","getCLS","getFID","getFCP","getLCP","getTTFB","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"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","file":"static/js/main.fc0e2dc7.chunk.js","sourcesContent":["import React, { useState, useRef } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n Button, \n IconButton \n} from '@material-ui/core';\nimport CloudUploadIcon from '@material-ui/icons/CloudUpload';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n height: '100%',\n minHeight: 300,\n transition: 'all 0.3s ease'\n },\n dragActive: {\n border: '2px dashed #3f51b5',\n backgroundColor: 'rgba(63, 81, 181, 0.05)'\n },\n dragInactive: {\n border: '2px dashed #ccc',\n backgroundColor: 'white'\n },\n uploadBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n width: '100%',\n cursor: 'pointer'\n },\n uploadIcon: {\n fontSize: 60,\n color: '#3f51b5',\n marginBottom: theme.spacing(2)\n },\n supportText: {\n marginTop: theme.spacing(2)\n },\n previewBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n position: 'relative'\n },\n imageContainer: {\n position: 'relative',\n width: '100%',\n // Use viewport-based height so any aspect ratio fits inside\n height: '60vh',\n [theme.breakpoints.down('sm')]: {\n height: '45vh',\n },\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n overflow: 'hidden',\n marginTop: theme.spacing(2),\n },\n deleteButton: {\n position: 'absolute',\n top: 0,\n right: 0,\n backgroundColor: 'rgba(255, 255, 255, 0.7)',\n '&:hover': {\n backgroundColor: 'rgba(255, 255, 255, 0.9)',\n }\n }\n}));\n\nconst ImageUploader = ({ onImageUpload }) => {\n const classes = useStyles();\n const [previewUrl, setPreviewUrl] = useState(null);\n const [dragActive, setDragActive] = useState(false);\n const fileInputRef = useRef(null);\n\n const handleDrag = (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (e.type === 'dragenter' || e.type === 'dragover') {\n setDragActive(true);\n } else if (e.type === 'dragleave') {\n setDragActive(false);\n }\n };\n\n const handleDrop = (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragActive(false);\n if (e.dataTransfer.files && e.dataTransfer.files[0]) {\n handleFiles(e.dataTransfer.files[0]);\n }\n };\n\n const handleChange = (e) => {\n e.preventDefault();\n if (e.target.files && e.target.files[0]) {\n handleFiles(e.target.files[0]);\n }\n };\n\n const handleFiles = (file) => {\n if (file.type.startsWith('image/')) {\n setPreviewUrl(URL.createObjectURL(file));\n onImageUpload(file);\n } else {\n alert('Please upload an image file');\n }\n };\n\n const onButtonClick = () => {\n fileInputRef.current.click();\n };\n\n const handleRemoveImage = () => {\n setPreviewUrl(null);\n onImageUpload(null);\n fileInputRef.current.value = \"\";\n };\n\n return (\n <Paper \n className={`${classes.paper} ${dragActive ? classes.dragActive : classes.dragInactive}`}\n onDragEnter={handleDrag}\n onDragLeave={handleDrag}\n onDragOver={handleDrag}\n onDrop={handleDrop}\n >\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleChange}\n style={{ display: 'none' }}\n />\n\n {!previewUrl ? (\n <Box \n className={classes.uploadBox}\n onClick={onButtonClick}\n >\n <CloudUploadIcon className={classes.uploadIcon} />\n <Typography variant=\"h6\" gutterBottom>\n Drag & Drop an image here\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n or\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n component=\"span\"\n startIcon={<CloudUploadIcon />}\n >\n Browse Files\n </Button>\n <Typography variant=\"body2\" color=\"textSecondary\" className={classes.supportText}>\n Supported formats: JPG, PNG, GIF\n </Typography>\n </Box>\n ) : (\n <Box className={classes.previewBox}>\n <Typography variant=\"h6\" gutterBottom>\n Preview\n </Typography>\n <Box className={classes.imageContainer}>\n <img\n src={previewUrl}\n alt=\"Preview\"\n className=\"preview-image\"\n style={{\n width: '100%',\n height: 'auto',\n maxHeight: '100%',\n objectFit: 'contain',\n display: 'block'\n }}\n />\n <IconButton\n aria-label=\"delete\"\n className={classes.deleteButton}\n onClick={handleRemoveImage}\n >\n <DeleteIcon />\n </IconButton>\n </Box>\n </Box>\n )}\n </Paper>\n );\n};\n\nexport default ImageUploader;\n","import React from 'react';\nimport { \n Grid, \n Card, \n CardContent, \n CardActions, \n Typography, \n Button, \n Chip,\n Box\n} from '@material-ui/core';\nimport VisibilityIcon from '@material-ui/icons/Visibility';\nimport CategoryIcon from '@material-ui/icons/Category';\nimport PlayArrowIcon from '@material-ui/icons/PlayArrow';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n card: {\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n },\n selectedCard: {\n border: '2px solid #3f51b5',\n },\n unavailableCard: {\n opacity: 0.6,\n },\n cardContent: {\n flexGrow: 1,\n },\n chipContainer: {\n marginBottom: theme.spacing(1.5),\n },\n successChip: {\n backgroundColor: '#34C759',\n color: '#fff',\n },\n errorChip: {\n backgroundColor: '#FF3B3F',\n color: '#fff',\n },\n modelType: {\n marginTop: theme.spacing(1),\n },\n processButton: {\n marginTop: theme.spacing(3),\n textAlign: 'center',\n }\n}));\n\nconst ModelSelector = ({ \n onModelSelect, \n onProcess, \n isProcessing, \n modelsStatus, \n selectedModel,\n imageSelected \n}) => {\n const classes = useStyles();\n \n const models = [\n {\n id: 'yolo',\n name: 'YOLOv8',\n description: 'Fast and accurate object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.yolo\n },\n {\n id: 'detr',\n name: 'DETR',\n description: 'DEtection TRansformer for object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.detr\n },\n {\n id: 'vit',\n name: 'ViT',\n description: 'Vision Transformer for image classification',\n icon: <CategoryIcon />,\n available: modelsStatus.vit\n }\n ];\n\n const handleModelClick = (modelId) => {\n if (models.find(m => m.id === modelId).available) {\n onModelSelect(modelId);\n }\n };\n\n return (\n <Box sx={{ p: 2, height: '100%' }}>\n <Typography variant=\"h6\" gutterBottom>\n Select Model\n </Typography>\n \n <Grid container spacing={2}>\n {models.map((model) => (\n <Grid item xs={12} sm={4} key={model.id}>\n <Card \n className={`\n ${classes.card} \n ${selectedModel === model.id ? classes.selectedCard : ''} \n ${!model.available ? classes.unavailableCard : ''}\n `}\n onClick={() => handleModelClick(model.id)}\n >\n <CardContent className={classes.cardContent}>\n <Box sx={{ mb: 2, color: 'primary' }}>\n {model.icon}\n </Box>\n <Typography variant=\"h5\" component=\"div\" gutterBottom>\n {model.name}\n </Typography>\n <div className={classes.chipContainer}>\n {model.available ? (\n <Chip \n label=\"Available\" \n className={classes.successChip}\n size=\"small\" \n />\n ) : (\n <Chip \n label=\"Not Available\" \n className={classes.errorChip}\n size=\"small\" \n />\n )}\n </div>\n <Typography variant=\"body2\" color=\"textSecondary\">\n {model.description}\n </Typography>\n </CardContent>\n <CardActions>\n <Button \n size=\"small\" \n onClick={() => handleModelClick(model.id)}\n disabled={!model.available}\n color={selectedModel === model.id ? \"primary\" : \"default\"}\n variant={selectedModel === model.id ? \"contained\" : \"outlined\"}\n fullWidth\n >\n {selectedModel === model.id ? 'Selected' : 'Select'}\n </Button>\n </CardActions>\n </Card>\n </Grid>\n ))}\n </Grid>\n\n <div className={classes.processButton}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"large\"\n startIcon={<PlayArrowIcon />}\n onClick={onProcess}\n disabled={!selectedModel || !imageSelected || isProcessing}\n >\n {isProcessing ? 'Processing...' : 'Process Image'}\n </Button>\n </div>\n </Box>\n );\n};\n\nexport default ModelSelector;\n","import React, { useState } from 'react';\nimport { \n Button, \n Box, \n Typography, \n CircularProgress, \n Snackbar,\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n TextField,\n FormControl,\n InputLabel,\n Select,\n MenuItem,\n Grid,\n Card,\n CardMedia,\n CardContent,\n Chip\n} from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n marginTop: theme.spacing(2),\n marginBottom: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n },\n button: {\n marginRight: theme.spacing(2),\n },\n searchDialog: {\n minWidth: '500px',\n },\n formControl: {\n marginBottom: theme.spacing(2),\n minWidth: '100%',\n },\n searchResults: {\n marginTop: theme.spacing(2),\n },\n resultCard: {\n marginBottom: theme.spacing(2),\n },\n resultImage: {\n height: 140,\n objectFit: 'contain',\n },\n chip: {\n margin: theme.spacing(0.5),\n },\n similarityChip: {\n backgroundColor: theme.palette.primary.main,\n color: 'white',\n }\n}));\n\nconst VectorDBActions = ({ results }) => {\n const classes = useStyles();\n const [isSaving, setIsSaving] = useState(false);\n const [saveSuccess, setSaveSuccess] = useState(false);\n const [saveError, setSaveError] = useState(null);\n const [openSearchDialog, setOpenSearchDialog] = useState(false);\n const [searchType, setSearchType] = useState('image');\n const [searchClass, setSearchClass] = useState('');\n const [searchResults, setSearchResults] = useState([]);\n const [isSearching, setIsSearching] = useState(false);\n const [searchError, setSearchError] = useState(null);\n \n // Extract model and data from results\n const { model, data } = results;\n \n // Handle saving to vector DB\n const handleSaveToVectorDB = async () => {\n setIsSaving(true);\n setSaveError(null);\n \n try {\n let response;\n \n if (model === 'vit') {\n // For ViT, save the whole image with classifications\n response = await fetch('/api/add-to-collection', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n metadata: {\n model: 'vit',\n classifications: data.classifications\n }\n })\n });\n } else {\n // For YOLO and DETR, save detected objects\n response = await fetch('/api/add-detected-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n objects: data.detections,\n imageId: generateUUID()\n })\n });\n }\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n setSaveSuccess(true);\n setTimeout(() => setSaveSuccess(false), 5000);\n } catch (err) {\n console.error('Error saving to vector DB:', err);\n setSaveError(`Error saving to vector DB: ${err.message}`);\n } finally {\n setIsSaving(false);\n }\n };\n \n // Handle opening search dialog\n const handleOpenSearchDialog = () => {\n setOpenSearchDialog(true);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle closing search dialog\n const handleCloseSearchDialog = () => {\n setOpenSearchDialog(false);\n };\n \n // Handle search type change\n const handleSearchTypeChange = (event) => {\n setSearchType(event.target.value);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle search class change\n const handleSearchClassChange = (event) => {\n setSearchClass(event.target.value);\n };\n \n // Handle search\n const handleSearch = async () => {\n setIsSearching(true);\n setSearchError(null);\n \n try {\n let requestBody = {};\n \n if (searchType === 'image') {\n // Search by current image\n requestBody = {\n searchType: 'image',\n image: data.image,\n n_results: 5\n };\n } else {\n // Search by class name\n if (!searchClass.trim()) {\n throw new Error('Please enter a class name');\n }\n \n requestBody = {\n searchType: 'class',\n class_name: searchClass.trim(),\n n_results: 5\n };\n }\n \n const response = await fetch('/api/search-similar-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody)\n });\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n console.log('Search API response:', result);\n \n // The backend responds with {success, searchType, results} structure, so extract only the results array\n if (result.success && Array.isArray(result.results)) {\n console.log('Setting search results array:', result.results);\n console.log('Results array length:', result.results.length);\n console.log('First result item:', result.results[0]);\n setSearchResults(result.results);\n } else {\n console.error('Unexpected API response format:', result);\n throw new Error('Unexpected API response format');\n }\n } catch (err) {\n console.error('Error searching vector DB:', err);\n setSearchError(`Error searching vector DB: ${err.message}`);\n } finally {\n setIsSearching(false);\n }\n };\n \n // Generate UUID for image ID\n const generateUUID = () => {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n };\n \n // Render search results\n const renderSearchResults = () => {\n console.log('Rendering search results:', searchResults);\n console.log('Search results length:', searchResults.length);\n \n if (searchResults.length === 0) {\n console.log('No results to render');\n return (\n <Typography variant=\"body1\">No results found.</Typography>\n );\n }\n \n return (\n <Grid container spacing={2}>\n {searchResults.map((result, index) => {\n const similarity = (1 - result.distance) * 100;\n \n return (\n <Grid item xs={12} sm={6} key={index}>\n <Card className={classes.resultCard}>\n {result.metadata && result.metadata.image_data ? (\n <CardMedia\n className={classes.resultImage}\n component=\"img\"\n height=\"200\"\n image={`data:image/jpeg;base64,${result.metadata.image_data}`}\n alt={result.metadata && result.metadata.class ? result.metadata.class : 'Object'}\n />\n ) : (\n <Box \n className={classes.resultImage}\n style={{ \n backgroundColor: '#f0f0f0', \n display: 'flex', \n alignItems: 'center', \n justifyContent: 'center',\n height: 200\n }}\n >\n <Typography variant=\"body2\" color=\"textSecondary\">\n {result.metadata && result.metadata.class ? result.metadata.class : 'Object'} Image\n </Typography>\n </Box>\n )}\n <CardContent>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\" mb={1}>\n <Typography variant=\"subtitle1\">Result #{index + 1}</Typography>\n <Chip \n label={`Similarity: ${similarity.toFixed(2)}%`}\n className={classes.similarityChip}\n size=\"small\"\n />\n </Box>\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Class:</strong> {result.metadata.class || 'N/A'}\n </Typography>\n {result.metadata.confidence && (\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Confidence:</strong> {(result.metadata.confidence * 100).toFixed(2)}%\n </Typography>\n )}\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Object ID:</strong> {result.id}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n );\n })}\n </Grid>\n );\n };\n \n return (\n <Box className={classes.root}>\n <Typography variant=\"h6\" gutterBottom>\n Vector Database Actions\n </Typography>\n \n <Box display=\"flex\" alignItems=\"center\" mb={2}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={handleSaveToVectorDB}\n disabled={isSaving}\n className={classes.button}\n >\n {isSaving ? (\n <>\n <CircularProgress size={20} color=\"inherit\" style={{ marginRight: 8 }} />\n Saving...\n </>\n ) : (\n 'Save to Vector DB'\n )}\n </Button>\n \n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={handleOpenSearchDialog}\n className={classes.button}\n >\n Search Similar\n </Button>\n </Box>\n \n {saveError && (\n <Alert severity=\"error\" style={{ marginTop: 8 }}>\n {saveError}\n </Alert>\n )}\n \n <Snackbar open={saveSuccess} autoHideDuration={5000} onClose={() => setSaveSuccess(false)}>\n <Alert severity=\"success\">\n {model === 'vit' ? (\n 'Image and classifications successfully saved to vector DB!'\n ) : (\n 'Detected objects successfully saved to vector DB!'\n )}\n </Alert>\n </Snackbar>\n \n {/* Search Dialog */}\n <Dialog\n open={openSearchDialog}\n onClose={handleCloseSearchDialog}\n maxWidth=\"md\"\n fullWidth\n >\n <DialogTitle>Search Vector Database</DialogTitle>\n <DialogContent>\n <FormControl className={classes.formControl}>\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n id=\"search-type\"\n value={searchType}\n onChange={handleSearchTypeChange}\n >\n <MenuItem value=\"image\">Search by Current Image</MenuItem>\n <MenuItem value=\"class\">Search by Class Name</MenuItem>\n </Select>\n </FormControl>\n \n {searchType === 'class' && (\n <FormControl className={classes.formControl}>\n <TextField\n label=\"Class Name\"\n value={searchClass}\n onChange={handleSearchClassChange}\n placeholder=\"e.g. person, car, dog...\"\n fullWidth\n />\n </FormControl>\n )}\n \n {searchError && (\n <Alert severity=\"error\" style={{ marginBottom: 16 }}>\n {searchError}\n </Alert>\n )}\n \n <Box className={classes.searchResults}>\n {isSearching ? (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" p={4}>\n <CircularProgress />\n <Typography variant=\"body1\" style={{ marginLeft: 16 }}>\n Searching...\n </Typography>\n </Box>\n ) : (\n <>\n {console.log('Search dialog render - searchResults:', searchResults)}\n {searchResults.length > 0 ? renderSearchResults() : \n <Typography variant=\"body1\">No results found. Please try another search.</Typography>\n }\n </>\n )}\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={handleCloseSearchDialog} color=\"default\">\n Close\n </Button>\n <Button \n onClick={handleSearch} \n color=\"primary\" \n variant=\"contained\"\n disabled={isSearching || (searchType === 'class' && !searchClass.trim())}\n >\n Search\n </Button>\n </DialogActions>\n </Dialog>\n </Box>\n );\n};\n\nexport default VectorDBActions;\n","import React from 'react';\nimport { \n Paper, \n Typography, \n Box, \n List, \n ListItem, \n ListItemText, \n Divider,\n Grid,\n Chip\n} from '@material-ui/core';\nimport VectorDBActions from './VectorDBActions';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n resultImage: {\n maxWidth: '100%',\n maxHeight: '400px',\n objectFit: 'contain'\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n chipContainer: {\n display: 'flex',\n gap: theme.spacing(1),\n flexWrap: 'wrap'\n }\n}));\n\nconst ResultDisplay = ({ results }) => {\n const classes = useStyles();\n if (!results) return null;\n \n const { model, data } = results;\n \n // Helper to format times nicely\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n \n // Check if there's an error\n if (data.error) {\n return (\n <Paper sx={{ p: 2, bgcolor: '#ffebee' }}>\n <Typography color=\"error\">{data.error}</Typography>\n </Paper>\n );\n }\n\n // Display performance info\n const renderPerformanceInfo = () => {\n if (!data.performance) return null;\n \n return (\n <Box className=\"performance-info\">\n <Divider className={classes.dividerMargin} />\n <Typography variant=\"body2\">\n Inference time: {formatTime(data.performance.inference_time)} on {data.performance.device}\n </Typography>\n </Box>\n );\n };\n\n // Render for YOLO and DETR (object detection)\n if (model === 'yolo' || model === 'detr') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n {model === 'yolo' ? 'YOLOv8' : 'DETR'} Detection Results\n </Typography>\n \n <Grid container spacing={3}>\n <Grid item xs={12} md={6}>\n {data.image && (\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detection Result\n </Typography>\n <img \n src={`data:image/png;base64,${data.image}`} \n alt=\"Detection Result\" \n className={classes.resultImage}\n />\n </Box>\n )}\n </Grid>\n \n <Grid item xs={12} md={6}>\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detected Objects:\n </Typography>\n \n {data.detections && data.detections.length > 0 ? (\n <List>\n {data.detections.map((detection, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {detection.class}\n </Typography>\n <Chip \n label={`${(detection.confidence * 100).toFixed(0)}%`}\n size=\"small\"\n color=\"primary\"\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n secondary={`Bounding Box: [${detection.bbox.join(', ')}]`} \n />\n </ListItem>\n {index < data.detections.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No objects detected</Typography>\n )}\n </Box>\n </Grid>\n </Grid>\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for Object Detection */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n // Render for ViT (classification)\n if (model === 'vit') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n ViT Classification Results\n </Typography>\n \n <Typography variant=\"subtitle1\" gutterBottom>\n Top Predictions:\n </Typography>\n \n {data.top_predictions && data.top_predictions.length > 0 ? (\n <List>\n {data.top_predictions.map((prediction, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {prediction.rank}. {prediction.class}\n </Typography>\n <Chip \n label={`${(prediction.probability * 100).toFixed(1)}%`}\n size=\"small\"\n color={index === 0 ? \"primary\" : \"default\"}\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n />\n </ListItem>\n {index < data.top_predictions.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No classifications available</Typography>\n )}\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for ViT Classification */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n return null;\n};\n\nexport default ResultDisplay;\n","import React, { useState } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n TextField, \n Button, \n CircularProgress,\n Divider\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n marginTop: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n responseBox: {\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n marginTop: theme.spacing(2),\n whiteSpace: 'pre-wrap'\n },\n buttonProgress: {\n marginLeft: theme.spacing(1)\n }\n}));\n\nconst LlmAnalysis = ({ visionResults, model }) => {\n const classes = useStyles();\n const [userQuery, setUserQuery] = useState('');\n const [isAnalyzing, setIsAnalyzing] = useState(false);\n const [analysisResult, setAnalysisResult] = useState(null);\n const [error, setError] = useState(null);\n\n // Format time for display\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n\n const handleAnalyze = async () => {\n if (!userQuery.trim()) return;\n \n setIsAnalyzing(true);\n setError(null);\n \n try {\n const response = await fetch('/api/analyze', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n visionResults: visionResults,\n userQuery: userQuery\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n \n if (data.error) {\n setError(data.error);\n } else {\n setAnalysisResult(data);\n }\n } catch (err) {\n console.error('Error analyzing with LLM:', err);\n setError(`Error analyzing with LLM: ${err.message}`);\n } finally {\n setIsAnalyzing(false);\n }\n };\n\n if (!visionResults) return null;\n\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n Ask AI about the {model === 'vit' ? 'Classification' : 'Detection'} Results\n </Typography>\n \n <Typography variant=\"body2\" className={classes.marginBottom}>\n Ask a question about the detected objects or classifications to get an AI-powered analysis.\n </Typography>\n \n <TextField\n fullWidth\n label=\"Your question about the image\"\n variant=\"outlined\"\n value={userQuery}\n onChange={(e) => setUserQuery(e.target.value)}\n disabled={isAnalyzing}\n className={classes.marginBottom}\n placeholder={model === 'vit' \n ? \"E.g., What category does this image belong to?\" \n : \"E.g., How many people are in this image?\"}\n />\n \n <Button \n variant=\"contained\" \n color=\"primary\"\n onClick={handleAnalyze}\n disabled={isAnalyzing || !userQuery.trim()}\n >\n Analyze with AI\n {isAnalyzing && <CircularProgress size={24} className={classes.buttonProgress} />}\n </Button>\n \n {error && (\n <Box mt={2}>\n <Typography color=\"error\">{error}</Typography>\n </Box>\n )}\n \n {analysisResult && (\n <>\n <Divider className={classes.dividerMargin} />\n \n <Typography variant=\"subtitle1\" gutterBottom>\n AI Analysis:\n </Typography>\n \n <Box className={classes.responseBox}>\n <Typography variant=\"body1\">\n {analysisResult.response}\n </Typography>\n </Box>\n \n {analysisResult.performance && (\n <Box mt={1}>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Analysis time: {formatTime(analysisResult.performance.inference_time)} on {analysisResult.performance.device}\n </Typography>\n </Box>\n )}\n </>\n )}\n </Paper>\n );\n};\n\nexport default LlmAnalysis;\n","import React, { useState } from 'react';\nimport { \n Paper,\n Typography,\n Grid,\n TextField,\n Button,\n Divider\n} from '@material-ui/core';\n\nfunction OpenAIChat() {\n const [model, setModel] = useState('gpt-4');\n const [apiKey, setApiKey] = useState('');\n const [system, setSystem] = useState('You are a helpful assistant.');\n const [prompt, setPrompt] = useState('');\n const [response, setResponse] = useState('');\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState('');\n\n const onSend = async () => {\n setError('');\n setResponse('');\n const p = (prompt || '').trim();\n if (!p) { setError('Please enter a question.'); return; }\n\n setLoading(true);\n try {\n const res = await fetch('/api/openai/chat', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({\n prompt: p,\n model: (model || '').trim() || 'gpt-4',\n api_key: (apiKey || undefined),\n system: (system || undefined)\n })\n });\n\n if (!res.ok) {\n let txt = await res.text();\n try { txt = JSON.stringify(JSON.parse(txt), null, 2); } catch {}\n throw new Error(txt);\n }\n const data = await res.json();\n const meta = `Model: ${data.model} | Latency: ${data.latency_sec}s` + (data.usage ? ` | Usage: ${JSON.stringify(data.usage)}` : '');\n setResponse((data.response || '(Empty response)') + '\\n\\n---\\n' + meta);\n } catch (e) {\n setError('Error: ' + e.message);\n } finally {\n setLoading(false);\n }\n };\n\n const onClear = () => {\n setPrompt('');\n setResponse('');\n setError('');\n };\n\n return (\n <Paper style={{ padding: 16 }}>\n <Typography variant=\"h5\" gutterBottom>\n OpenAI Chat (OpenAI API)\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n If the server env var OPENAI_API_KEY is set, the API Key field is optional.\n </Typography>\n <Grid container spacing={2}>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"Model\"\n value={model}\n onChange={(e) => setModel(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"OpenAI API Key (optional)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"password\"\n placeholder=\"sk-...\"\n />\n </Grid>\n <Grid item xs={12}>\n <TextField\n label=\"System Prompt (optional)\"\n value={system}\n onChange={(e) => setSystem(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n <Grid item xs={12}>\n <TextField\n label=\"User Question\"\n value={prompt}\n onChange={(e) => setPrompt(e.target.value)}\n fullWidth\n multiline\n rows={4}\n variant=\"outlined\"\n />\n </Grid>\n {error && (\n <Grid item xs={12}>\n <Typography color=\"error\">{error}</Typography>\n </Grid>\n )}\n <Grid item xs={12}>\n <div style={{ display: 'flex', gap: 8 }}>\n <Button color=\"primary\" variant=\"contained\" onClick={onSend} disabled={loading}>\n {loading ? 'Sending...' : 'Send Question'}\n </Button>\n <Button variant=\"outlined\" onClick={onClear}>Clear</Button>\n </div>\n </Grid>\n <Grid item xs={12}>\n <Divider style={{ margin: '12px 0' }} />\n <Typography variant=\"subtitle2\" color=\"textSecondary\">Response</Typography>\n <pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'ui-monospace, monospace' }}>{response}</pre>\n </Grid>\n </Grid>\n </Paper>\n );\n}\n\nexport default OpenAIChat;\n","import React, { useState, useEffect } from 'react';\nimport { \n Container, \n Typography, \n Box, \n Paper, \n Grid, \n CircularProgress,\n AppBar,\n Toolbar,\n ThemeProvider,\n createMuiTheme\n} from '@material-ui/core';\nimport ImageUploader from './components/ImageUploader';\nimport ModelSelector from './components/ModelSelector';\nimport ResultDisplay from './components/ResultDisplay';\nimport LlmAnalysis from './components/LlmAnalysis';\nimport OpenAIChat from './components/OpenAIChat';\nimport './App.css';\n\n// Create a theme\nconst theme = createMuiTheme({\n palette: {\n primary: {\n main: '#3f51b5',\n },\n secondary: {\n main: '#f50057',\n },\n },\n typography: {\n fontFamily: 'Roboto, Arial, sans-serif',\n },\n});\n\nfunction App() {\n const [selectedImage, setSelectedImage] = useState(null);\n const [selectedModel, setSelectedModel] = useState('');\n const [isProcessing, setIsProcessing] = useState(false);\n const [results, setResults] = useState(null);\n const [error, setError] = useState(null);\n const [modelsStatus, setModelsStatus] = useState({\n yolo: false,\n detr: false,\n vit: false\n });\n\n // Check API status on component mount\n useEffect(() => {\n fetch('/api/status')\n .then(response => response.json())\n .then(data => {\n setModelsStatus(data.models);\n })\n .catch(err => {\n console.error('Error checking API status:', err);\n setError('Error connecting to the backend API. Please make sure the server is running.');\n });\n }, []);\n\n const handleImageUpload = (image) => {\n setSelectedImage(image);\n setResults(null);\n setError(null);\n };\n\n const handleModelSelect = (model) => {\n setSelectedModel(model);\n setResults(null);\n setError(null);\n };\n\n const processImage = async () => {\n if (!selectedImage || !selectedModel) {\n setError('Please select both an image and a model');\n return;\n }\n\n setIsProcessing(true);\n setError(null);\n\n // Create form data for the image\n const formData = new FormData();\n formData.append('image', selectedImage);\n\n let endpoint = '';\n switch (selectedModel) {\n case 'yolo':\n endpoint = '/api/detect/yolo';\n break;\n case 'detr':\n endpoint = '/api/detect/detr';\n break;\n case 'vit':\n endpoint = '/api/classify/vit';\n break;\n default:\n setError('Invalid model selection');\n setIsProcessing(false);\n return;\n }\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n setResults({ model: selectedModel, data });\n } catch (err) {\n console.error('Error processing image:', err);\n setError(`Error processing image: ${err.message}`);\n } finally {\n setIsProcessing(false);\n }\n };\n\n return (\n <ThemeProvider theme={theme}>\n <Box style={{ flexGrow: 1 }}>\n <AppBar position=\"static\">\n <Toolbar>\n <Typography variant=\"h6\" style={{ flexGrow: 1 }}>\n Multi-Model Object Detection Demo\n </Typography>\n </Toolbar>\n </AppBar>\n <Container maxWidth=\"lg\" style={{ marginTop: theme.spacing(4), marginBottom: theme.spacing(4) }}>\n <Grid container spacing={3}>\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2) }}>\n <Typography variant=\"h5\" gutterBottom>\n Upload an image to see how each model performs!\n </Typography>\n <Typography variant=\"body1\" paragraph>\n This demo showcases three different object detection and image classification models:\n </Typography>\n <Typography variant=\"body1\" component=\"div\">\n <ul>\n <li><strong>YOLOv8</strong>: Fast and accurate object detection</li>\n <li><strong>DETR</strong>: DEtection TRansformer for object detection</li>\n <li><strong>ViT</strong>: Vision Transformer for image classification</li>\n </ul>\n </Typography>\n </Paper>\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ImageUploader onImageUpload={handleImageUpload} />\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ModelSelector \n onModelSelect={handleModelSelect} \n onProcess={processImage}\n isProcessing={isProcessing}\n modelsStatus={modelsStatus}\n selectedModel={selectedModel}\n imageSelected={!!selectedImage}\n />\n </Grid>\n \n {error && (\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2), backgroundColor: '#ffebee' }}>\n <Typography color=\"error\">{error}</Typography>\n </Paper>\n </Grid>\n )}\n \n {isProcessing && (\n <Grid item xs={12} style={{ textAlign: 'center', margin: `${theme.spacing(4)}px 0` }}>\n <CircularProgress />\n <Typography variant=\"h6\" style={{ marginTop: theme.spacing(2) }}>\n Processing image...\n </Typography>\n </Grid>\n )}\n \n {results && (\n <>\n <Grid item xs={12}>\n <ResultDisplay results={results} />\n </Grid>\n <Grid item xs={12}>\n <LlmAnalysis visionResults={results.data} model={results.model} />\n </Grid>\n </>\n )}\n\n {/* OpenAI Chat section at the end */}\n <Grid item xs={12}>\n <OpenAIChat />\n </Grid>\n </Grid>\n </Container>\n </Box>\n </ThemeProvider>\n );\n}\n\nexport default App;\n","const reportWebVitals = (onPerfEntry) => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"],"sourceRoot":""}
|
|
|
|
static/static/js/main.fe247ab2.chunk.js
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
(this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[]).push([[0],{102:function(e,a,t){},103:function(e,a,t){"use strict";t.r(a);var n=t(0),r=t.n(n),l=t(11),o=t.n(l),c=(t(97),t(78)),i=t(155),s=t(159),m=t(156),d=t(157),u=t(48),g=t(158),p=t(138),E=t(81),b=t(143),h=t(136),y=t(137),f=t(63),v=t.n(f),x=t(75),S=t.n(x),j=t(133);const C=Object(j.a)(e=>({paper:{padding:e.spacing(2),display:"flex",flexDirection:"column",alignItems:"center",height:"100%",minHeight:300,transition:"all 0.3s ease"},dragActive:{border:"2px dashed #3f51b5",backgroundColor:"rgba(63, 81, 181, 0.05)"},dragInactive:{border:"2px dashed #ccc",backgroundColor:"white"},uploadBox:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",height:"100%",width:"100%",cursor:"pointer"},uploadIcon:{fontSize:60,color:"#3f51b5",marginBottom:e.spacing(2)},supportText:{marginTop:e.spacing(2)},previewBox:{display:"flex",flexDirection:"column",alignItems:"center",width:"100%",height:"100%",position:"relative"},imageContainer:{position:"relative",width:"100%",height:"60vh",[e.breakpoints.down("sm")]:{height:"45vh"},display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",marginTop:e.spacing(2)},deleteButton:{position:"absolute",top:0,right:0,backgroundColor:"rgba(255, 255, 255, 0.7)","&:hover":{backgroundColor:"rgba(255, 255, 255, 0.9)"}}}));var w=e=>{let{onImageUpload:a}=e;const t=C(),[l,o]=Object(n.useState)(null),[c,i]=Object(n.useState)(!1),m=Object(n.useRef)(null),d=e=>{e.preventDefault(),e.stopPropagation(),"dragenter"===e.type||"dragover"===e.type?i(!0):"dragleave"===e.type&&i(!1)},g=e=>{e.type.startsWith("image/")?(o(URL.createObjectURL(e)),a(e)):alert("Please upload an image file")};return r.a.createElement(E.a,{className:"".concat(t.paper," ").concat(c?t.dragActive:t.dragInactive),onDragEnter:d,onDragLeave:d,onDragOver:d,onDrop:e=>{e.preventDefault(),e.stopPropagation(),i(!1),e.dataTransfer.files&&e.dataTransfer.files[0]&&g(e.dataTransfer.files[0])}},r.a.createElement("input",{ref:m,type:"file",accept:"image/*",onChange:e=>{e.preventDefault(),e.target.files&&e.target.files[0]&&g(e.target.files[0])},style:{display:"none"}}),l?r.a.createElement(s.a,{className:t.previewBox},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Preview"),r.a.createElement(s.a,{className:t.imageContainer},r.a.createElement("img",{src:l,alt:"Preview",className:"preview-image",style:{width:"100%",height:"auto",maxHeight:"100%",objectFit:"contain",display:"block"}}),r.a.createElement(y.a,{"aria-label":"delete",className:t.deleteButton,onClick:()=>{o(null),a(null),m.current.value=""}},r.a.createElement(S.a,null)))):r.a.createElement(s.a,{className:t.uploadBox,onClick:()=>{m.current.click()}},r.a.createElement(v.a,{className:t.uploadIcon}),r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Drag & Drop an image here"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"or"),r.a.createElement(h.a,{variant:"contained",color:"primary",component:"span",startIcon:r.a.createElement(v.a,null)},"Browse Files"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",className:t.supportText},"Supported formats: JPG, PNG, GIF")))},O=t(139),N=t(140),B=t(166),T=t(141),I=t(64),k=t.n(I),D=t(76),P=t.n(D),F=t(77),R=t.n(F);const A=Object(j.a)(e=>({card:{height:"100%",display:"flex",flexDirection:"column"},selectedCard:{border:"2px solid #3f51b5"},unavailableCard:{opacity:.6},cardContent:{flexGrow:1},chipContainer:{marginBottom:e.spacing(1.5)},successChip:{backgroundColor:"#34C759",color:"#fff"},errorChip:{backgroundColor:"#FF3B3F",color:"#fff"},modelType:{marginTop:e.spacing(1)},processButton:{marginTop:e.spacing(3),textAlign:"center"}}));var _=e=>{let{onModelSelect:a,onProcess:t,isProcessing:n,modelsStatus:l,selectedModel:o,imageSelected:c}=e;const i=A(),m=[{id:"yolo",name:"YOLOv8",description:"Fast and accurate object detection",icon:r.a.createElement(k.a,null),available:l.yolo},{id:"detr",name:"DETR",description:"DEtection TRansformer for object detection",icon:r.a.createElement(k.a,null),available:l.detr},{id:"vit",name:"ViT",description:"Vision Transformer for image classification",icon:r.a.createElement(P.a,null),available:l.vit}],d=e=>{m.find(a=>a.id===e).available&&a(e)};return r.a.createElement(s.a,{sx:{p:2,height:"100%"}},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Select Model"),r.a.createElement(p.a,{container:!0,spacing:2},m.map(e=>r.a.createElement(p.a,{item:!0,xs:12,sm:4,key:e.id},r.a.createElement(O.a,{className:"\n ".concat(i.card," \n ").concat(o===e.id?i.selectedCard:""," \n ").concat(e.available?"":i.unavailableCard,"\n "),onClick:()=>d(e.id)},r.a.createElement(N.a,{className:i.cardContent},r.a.createElement(s.a,{sx:{mb:2,color:"primary"}},e.icon),r.a.createElement(u.a,{variant:"h5",component:"div",gutterBottom:!0},e.name),r.a.createElement("div",{className:i.chipContainer},e.available?r.a.createElement(B.a,{label:"Available",className:i.successChip,size:"small"}):r.a.createElement(B.a,{label:"Not Available",className:i.errorChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.description)),r.a.createElement(T.a,null,r.a.createElement(h.a,{size:"small",onClick:()=>d(e.id),disabled:!e.available,color:o===e.id?"primary":"default",variant:o===e.id?"contained":"outlined",fullWidth:!0},o===e.id?"Selected":"Select")))))),r.a.createElement("div",{className:i.processButton},r.a.createElement(h.a,{variant:"contained",color:"primary",size:"large",startIcon:r.a.createElement(R.a,null),onClick:t,disabled:!o||!c||n},n?"Processing...":"Process Image")))},z=t(153),L=t(150),W=t(105),M=t(154),J=t(142),H=t(164),U=t(163),V=t(145),G=t(146),Y=t(147),Q=t(167),q=t(160),K=t(151),X=t(169),Z=t(152),$=t(161);const ee=Object(j.a)(e=>({root:{marginTop:e.spacing(2),marginBottom:e.spacing(2),padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius},button:{marginRight:e.spacing(2)},searchDialog:{minWidth:"500px"},formControl:{marginBottom:e.spacing(2),minWidth:"100%"},searchResults:{marginTop:e.spacing(2)},resultCard:{marginBottom:e.spacing(2)},resultImage:{height:140,objectFit:"contain"},chip:{margin:e.spacing(.5)},similarityChip:{backgroundColor:e.palette.primary.main,color:"white"}}));var ae=e=>{let{results:a}=e;const t=ee(),[l,o]=Object(n.useState)(!1),[c,i]=Object(n.useState)(!1),[m,d]=Object(n.useState)(null),[g,E]=Object(n.useState)(!1),[y,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,j]=Object(n.useState)([]),[C,w]=Object(n.useState)(!1),[T,I]=Object(n.useState)(null),{model:k,data:D}=a,P=()=>{E(!1)},F=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){const a=16*Math.random()|0;return("x"===e?a:3&a|8).toString(16)}));return r.a.createElement(s.a,{className:t.root},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Vector Database Actions"),r.a.createElement(s.a,{display:"flex",alignItems:"center",mb:2},r.a.createElement(h.a,{variant:"contained",color:"primary",onClick:async()=>{o(!0),d(null);try{let e;if(e="vit"===k?await fetch("/api/add-to-collection",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:D.image,metadata:{model:"vit",classifications:D.classifications}})}):await fetch("/api/add-detected-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:D.image,objects:D.detections,imageId:F()})}),!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const a=await e.json();if(a.error)throw new Error(a.error);i(!0),setTimeout(()=>i(!1),5e3)}catch(e){console.error("Error saving to vector DB:",e),d("Error saving to vector DB: ".concat(e.message))}finally{o(!1)}},disabled:l,className:t.button},l?r.a.createElement(r.a.Fragment,null,r.a.createElement(b.a,{size:20,color:"inherit",style:{marginRight:8}}),"Saving..."):"Save to Vector DB"),r.a.createElement(h.a,{variant:"outlined",color:"primary",onClick:()=>{E(!0),j([]),I(null)},className:t.button},"Search Similar")),m&&r.a.createElement($.a,{severity:"error",style:{marginTop:8}},m),r.a.createElement(H.a,{open:c,autoHideDuration:5e3,onClose:()=>i(!1)},r.a.createElement($.a,{severity:"success"},"vit"===k?"Image and classifications successfully saved to vector DB!":"Detected objects successfully saved to vector DB!")),r.a.createElement(U.a,{open:g,onClose:P,maxWidth:"md",fullWidth:!0},r.a.createElement(V.a,null,"Search Vector Database"),r.a.createElement(G.a,null,r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(Q.a,{id:"search-type-label"},"Search Type"),r.a.createElement(q.a,{labelId:"search-type-label",id:"search-type",value:y,onChange:e=>{f(e.target.value),j([]),I(null)}},r.a.createElement(K.a,{value:"image"},"Search by Current Image"),r.a.createElement(K.a,{value:"class"},"Search by Class Name"))),"class"===y&&r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(X.a,{label:"Class Name",value:v,onChange:e=>{x(e.target.value)},placeholder:"e.g. person, car, dog...",fullWidth:!0})),T&&r.a.createElement($.a,{severity:"error",style:{marginBottom:16}},T),r.a.createElement(s.a,{className:t.searchResults},C?r.a.createElement(s.a,{display:"flex",justifyContent:"center",alignItems:"center",p:4},r.a.createElement(b.a,null),r.a.createElement(u.a,{variant:"body1",style:{marginLeft:16}},"Searching...")):r.a.createElement(r.a.Fragment,null,console.log("Search dialog render - searchResults:",S),S.length>0?(console.log("Rendering search results:",S),console.log("Search results length:",S.length),0===S.length?(console.log("No results to render"),r.a.createElement(u.a,{variant:"body1"},"No results found.")):r.a.createElement(p.a,{container:!0,spacing:2},S.map((e,a)=>{const n=100*(1-e.distance);return r.a.createElement(p.a,{item:!0,xs:12,sm:6,key:a},r.a.createElement(O.a,{className:t.resultCard},e.metadata&&e.metadata.image_data?r.a.createElement(J.a,{className:t.resultImage,component:"img",height:"200",image:"data:image/jpeg;base64,".concat(e.metadata.image_data),alt:e.metadata&&e.metadata.class?e.metadata.class:"Object"}):r.a.createElement(s.a,{className:t.resultImage,style:{backgroundColor:"#f0f0f0",display:"flex",alignItems:"center",justifyContent:"center",height:200}},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.metadata&&e.metadata.class?e.metadata.class:"Object"," Image")),r.a.createElement(N.a,null,r.a.createElement(s.a,{display:"flex",justifyContent:"space-between",alignItems:"center",mb:1},r.a.createElement(u.a,{variant:"subtitle1"},"Result #",a+1),r.a.createElement(B.a,{label:"Similarity: ".concat(n.toFixed(2),"%"),className:t.similarityChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Class:")," ",e.metadata.class||"N/A"),e.metadata.confidence&&r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Confidence:")," ",(100*e.metadata.confidence).toFixed(2),"%"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Object ID:")," ",e.id))))}))):r.a.createElement(u.a,{variant:"body1"},"No results found. Please try another search.")))),r.a.createElement(Z.a,null,r.a.createElement(h.a,{onClick:P,color:"default"},"Close"),r.a.createElement(h.a,{onClick:async()=>{w(!0),I(null);try{let e={};if("image"===y)e={searchType:"image",image:D.image,n_results:5};else{if(!v.trim())throw new Error("Please enter a class name");e={searchType:"class",class_name:v.trim(),n_results:5}}const a=await fetch("/api/search-similar-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!a.ok)throw new Error("HTTP error! Status: ".concat(a.status));const t=await a.json();if(t.error)throw new Error(t.error);if(console.log("Search API response:",t),!t.success||!Array.isArray(t.results))throw console.error("Unexpected API response format:",t),new Error("Unexpected API response format");console.log("Setting search results array:",t.results),console.log("Results array length:",t.results.length),console.log("First result item:",t.results[0]),j(t.results)}catch(e){console.error("Error searching vector DB:",e),I("Error searching vector DB: ".concat(e.message))}finally{w(!1)}},color:"primary",variant:"contained",disabled:C||"class"===y&&!v.trim()},"Search"))))};const te=Object(j.a)(e=>({paper:{padding:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},resultImage:{maxWidth:"100%",maxHeight:"400px",objectFit:"contain"},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},chipContainer:{display:"flex",gap:e.spacing(1),flexWrap:"wrap"}}));var ne=e=>{let{results:a}=e;const t=te();if(!a)return null;const{model:n,data:l}=a;if(l.error)return r.a.createElement(E.a,{sx:{p:2,bgcolor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},l.error));const o=()=>l.performance?r.a.createElement(s.a,{className:"performance-info"},r.a.createElement(z.a,{className:t.dividerMargin}),r.a.createElement(u.a,{variant:"body2"},"Inference time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(l.performance.inference_time)," on ",l.performance.device)):null;return"yolo"===n||"detr"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"yolo"===n?"YOLOv8":"DETR"," Detection Results"),r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12,md:6},l.image&&r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detection Result"),r.a.createElement("img",{src:"data:image/png;base64,".concat(l.image),alt:"Detection Result",className:t.resultImage}))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detected Objects:"),l.detections&&l.detections.length>0?r.a.createElement(L.a,null,l.detections.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(M.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.class),r.a.createElement(B.a,{label:"".concat((100*e.confidence).toFixed(0),"%"),size:"small",color:"primary",style:{marginLeft:8}})),secondary:"Bounding Box: [".concat(e.bbox.join(", "),"]")})),a<l.detections.length-1&&r.a.createElement(z.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No objects detected")))),o(),r.a.createElement(ae,{results:a})):"vit"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"ViT Classification Results"),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Top Predictions:"),l.top_predictions&&l.top_predictions.length>0?r.a.createElement(L.a,null,l.top_predictions.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(M.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.rank,". ",e.class),r.a.createElement(B.a,{label:"".concat((100*e.probability).toFixed(1),"%"),size:"small",color:0===a?"primary":"default",style:{marginLeft:8}}))})),a<l.top_predictions.length-1&&r.a.createElement(z.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No classifications available"),o(),r.a.createElement(ae,{results:a})):null};const re=Object(j.a)(e=>({paper:{padding:e.spacing(2),marginTop:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},responseBox:{padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius,marginTop:e.spacing(2),whiteSpace:"pre-wrap"},buttonProgress:{marginLeft:e.spacing(1)}}));var le=e=>{let{visionResults:a,model:t}=e;const l=re(),[o,c]=Object(n.useState)(""),[i,m]=Object(n.useState)(!1),[d,g]=Object(n.useState)(null),[p,y]=Object(n.useState)(null);return a?r.a.createElement(E.a,{className:l.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Ask AI about the ","vit"===t?"Classification":"Detection"," Results"),r.a.createElement(u.a,{variant:"body2",className:l.marginBottom},"Ask a question about the detected objects or classifications to get an AI-powered analysis."),r.a.createElement(X.a,{fullWidth:!0,label:"Your question about the image",variant:"outlined",value:o,onChange:e=>c(e.target.value),disabled:i,className:l.marginBottom,placeholder:"vit"===t?"E.g., What category does this image belong to?":"E.g., How many people are in this image?"}),r.a.createElement(h.a,{variant:"contained",color:"primary",onClick:async()=>{if(o.trim()){m(!0),y(null);try{const e=await fetch("/api/analyze",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({visionResults:a,userQuery:o})});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const t=await e.json();t.error?y(t.error):g(t)}catch(e){console.error("Error analyzing with LLM:",e),y("Error analyzing with LLM: ".concat(e.message))}finally{m(!1)}}},disabled:i||!o.trim()},"Analyze with AI",i&&r.a.createElement(b.a,{size:24,className:l.buttonProgress})),p&&r.a.createElement(s.a,{mt:2},r.a.createElement(u.a,{color:"error"},p)),d&&r.a.createElement(r.a.Fragment,null,r.a.createElement(z.a,{className:l.dividerMargin}),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"AI Analysis:"),r.a.createElement(s.a,{className:l.responseBox},r.a.createElement(u.a,{variant:"body1"},d.response)),d.performance&&r.a.createElement(s.a,{mt:1},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},"Analysis time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(d.performance.inference_time)," on ",d.performance.device)))):null};var oe=function(e){let{imageBase64:a}=e;const[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(""),[i,s]=Object(n.useState)(""),[m,d]=Object(n.useState)(!1),[g,b]=Object(n.useState)(""),[y,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,j]=Object(n.useState)(""),[C,w]=Object(n.useState)(5);return r.a.createElement(E.a,{style:{padding:16}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Vision RAG (LangChain)"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"\uc11c\ubc84\uc5d0 OPENAI_API_KEY\uac00 \uc124\uc815\ub418\uc5b4 \uc788\ub2e4\uba74 API Key\ub294 \uc0dd\ub7b5 \uac00\ub2a5\ud569\ub2c8\ub2e4. \uac80\uc0c9 \uc720\ud615\uc744 \uc120\ud0dd\ud558\uace0 \uc9c8\ubb38\uc744 \ubcf4\ub0b4\uba74, \ubca1\ud130 DB\uc5d0\uc11c \uac80\uc0c9\ub41c \ucee8\ud14d\uc2a4\ud2b8\ub85c \ub2f5\ubcc0\ud569\ub2c8\ub2e4."),r.a.createElement(p.a,{container:!0,spacing:2},r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(Y.a,{fullWidth:!0,variant:"outlined",size:"small"},r.a.createElement(Q.a,{id:"search-type-label"},"Search Type"),r.a.createElement(q.a,{labelId:"search-type-label",value:y,onChange:e=>f(e.target.value),label:"Search Type"},r.a.createElement(K.a,{value:"image"},"image (current upload)"),r.a.createElement(K.a,{value:"object"},"object (objectId)"),r.a.createElement(K.a,{value:"class"},"class (class_name)")))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"OpenAI API Key (optional)",value:t,onChange:e=>l(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"password",placeholder:"sk-..."})),"object"===y&&r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"objectId",value:v,onChange:e=>x(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),"class"===y&&r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"class_name",value:S,onChange:e=>j(e.target.value),fullWidth:!0,variant:"outlined",size:"small",placeholder:"e.g. person, car, dog"})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"n_results",value:C,onChange:e=>w(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"number",inputProps:{min:1,max:50}})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"User Question",value:o,onChange:e=>c(e.target.value),fullWidth:!0,multiline:!0,rows:4,variant:"outlined",placeholder:"image"===y?"\uc5c5\ub85c\ub4dc\ud55c \uc774\ubbf8\uc9c0 \uae30\ubc18\uc73c\ub85c \ub2f5\ubcc0\ud574\uc918":"\uac80\uc0c9\ub41c \uac1d\uccb4 \ucee8\ud14d\uc2a4\ud2b8\ub97c \uc0ac\uc6a9\ud574 \ub2f5\ubcc0\ud574\uc918"})),g&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(u.a,{color:"error"},g)),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement("div",{style:{display:"flex",gap:8}},r.a.createElement(h.a,{color:"primary",variant:"contained",onClick:async()=>{b(""),s("");const e=(o||"").trim();if(!e)return void b("\uc9c8\ubb38\uc744 \uc785\ub825\ud558\uc138\uc694.");const n={userQuery:e,searchType:y,n_results:Number(C)||5};if(t&&(n.api_key=t),"image"===y){if(!a)return void b("\uc774\ubbf8\uc9c0\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \uba3c\uc800 \uc774\ubbf8\uc9c0\ub97c \uc5c5\ub85c\ub4dc\ud558\uc138\uc694.");n.image=a}else if("object"===y){if(!v.trim())return void b("objectId\ub97c \uc785\ub825\ud558\uc138\uc694.");n.objectId=v.trim()}else if("class"===y){if(!S.trim())return void b("class_name\uc744 \uc785\ub825\ud558\uc138\uc694.");n.class_name=S.trim()}d(!0);try{const e=await fetch("/api/vision-rag/query",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(n)});if(!e.ok){let a=await e.text();try{a=JSON.stringify(JSON.parse(a),null,2)}catch(r){}throw new Error(a)}const a=await e.json(),t="Model: ".concat(a.model||"-"," | Latency: ").concat(a.latency_sec||"-","s");let l="";a.retrieved&&a.retrieved.length>0&&(l="\n\n\uac80\uc0c9 \uacb0\uacfc:\n",a.retrieved.forEach((e,a)=>{l+="".concat(a+1,". ID: ").concat(e.id||"-","\n"),e.meta&&(l+=" \ud074\ub798\uc2a4: ".concat(e.meta.class||"-","\n"),l+=" \uc2e0\ub8b0\ub3c4: ".concat(e.meta.confidence||"-","\n")),l+=" \uc720\uc0ac\ub3c4: ".concat(e.distance?e.distance.toFixed(4):"-","\n")})),s((a.answer||"(\ube48 \uc751\ub2f5)")+l+"\n\n---\n"+t)}catch(l){b("Error: "+l.message)}finally{d(!1)}},disabled:m},m?"Sending...":"Send Question"),r.a.createElement(h.a,{variant:"outlined",onClick:()=>{c(""),s(""),b("")}},"Clear"))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(z.a,{style:{margin:"12px 0"}}),r.a.createElement(u.a,{variant:"subtitle2",color:"textSecondary"},"Response"),r.a.createElement("pre",{style:{whiteSpace:"pre-wrap",fontFamily:"ui-monospace, monospace"}},i))))};t(102);const ce=Object(c.a)({palette:{primary:{main:"#3f51b5"},secondary:{main:"#f50057"}},typography:{fontFamily:"Roboto, Arial, sans-serif"}});var ie=function(){const[e,a]=Object(n.useState)(null),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(""),[h,y]=Object(n.useState)(!1),[f,v]=Object(n.useState)(null),[x,S]=Object(n.useState)(null),[j,C]=Object(n.useState)({yolo:!1,detr:!1,vit:!1});return Object(n.useEffect)(()=>{fetch("/api/status").then(e=>e.json()).then(e=>{C(e.models)}).catch(e=>{console.error("Error checking API status:",e),S("Error connecting to the backend API. Please make sure the server is running.")})},[]),r.a.createElement(i.a,{theme:ce},r.a.createElement(s.a,{style:{flexGrow:1}},r.a.createElement(m.a,{position:"static"},r.a.createElement(d.a,null,r.a.createElement(u.a,{variant:"h6",style:{flexGrow:1}},"Multi-Model Object Detection Demo"))),r.a.createElement(g.a,{maxWidth:"lg",style:{marginTop:ce.spacing(4),marginBottom:ce.spacing(4)}},r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2)}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Upload an image to see how each model performs!"),r.a.createElement(u.a,{variant:"body1",paragraph:!0},"This demo showcases three different object detection and image classification models:"),r.a.createElement(u.a,{variant:"body1",component:"div"},r.a.createElement("ul",null,r.a.createElement("li",null,r.a.createElement("strong",null,"YOLOv8"),": Fast and accurate object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"DETR"),": DEtection TRansformer for object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"ViT"),": Vision Transformer for image classification"))))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(w,{onImageUpload:e=>{if(a(e),v(null),S(null),e instanceof File){const a=new FileReader;a.onload=()=>{const e=a.result;l("string"===typeof e?e:"")},a.onerror=()=>l(""),a.readAsDataURL(e)}else l("")}})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(_,{onModelSelect:e=>{c(e),v(null),S(null)},onProcess:async()=>{if(!e||!o)return void S("Please select both an image and a model");y(!0),S(null);const a=new FormData;a.append("image",e);let t="";switch(o){case"yolo":t="/api/detect/yolo";break;case"detr":t="/api/detect/detr";break;case"vit":t="/api/classify/vit";break;default:return S("Invalid model selection"),void y(!1)}try{const e=await fetch(t,{method:"POST",body:a});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const n=await e.json();v({model:o,data:n})}catch(n){console.error("Error processing image:",n),S("Error processing image: ".concat(n.message))}finally{y(!1)}},isProcessing:h,modelsStatus:j,selectedModel:o,imageSelected:!!e})),x&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2),backgroundColor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},x))),h&&r.a.createElement(p.a,{item:!0,xs:12,style:{textAlign:"center",margin:"".concat(ce.spacing(4),"px 0")}},r.a.createElement(b.a,null),r.a.createElement(u.a,{variant:"h6",style:{marginTop:ce.spacing(2)}},"Processing image...")),f&&r.a.createElement(r.a.Fragment,null,r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(ne,{results:f})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(le,{visionResults:f.data,model:f.model}))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(oe,{imageBase64:t}))))))};var se=e=>{e&&e instanceof Function&&t.e(3).then(t.bind(null,170)).then(a=>{let{getCLS:t,getFID:n,getFCP:r,getLCP:l,getTTFB:o}=a;t(e),n(e),r(e),l(e),o(e)})};o.a.render(r.a.createElement(r.a.StrictMode,null,r.a.createElement(ie,null)),document.getElementById("root")),se()},92:function(e,a,t){e.exports=t(103)},97:function(e,a,t){}},[[92,1,2]]]);
|
2 |
-
//# sourceMappingURL=main.fe247ab2.chunk.js.map
|
|
|
|
|
|
static/static/js/main.fe247ab2.chunk.js.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["components/ImageUploader.js","components/ModelSelector.js","components/VectorDBActions.js","components/ResultDisplay.js","components/LlmAnalysis.js","components/OpenAIChat.js","App.js","reportWebVitals.js","index.js"],"names":["useStyles","makeStyles","theme","paper","padding","spacing","display","flexDirection","alignItems","height","minHeight","transition","dragActive","border","backgroundColor","dragInactive","uploadBox","justifyContent","width","cursor","uploadIcon","fontSize","color","marginBottom","supportText","marginTop","previewBox","position","imageContainer","breakpoints","down","overflow","deleteButton","top","right","ImageUploader","_ref","onImageUpload","classes","previewUrl","setPreviewUrl","useState","setDragActive","fileInputRef","useRef","handleDrag","e","preventDefault","stopPropagation","type","handleFiles","file","startsWith","URL","createObjectURL","alert","React","createElement","Paper","className","concat","onDragEnter","onDragLeave","onDragOver","onDrop","dataTransfer","files","ref","accept","onChange","target","style","Box","Typography","variant","gutterBottom","src","alt","maxHeight","objectFit","IconButton","aria-label","onClick","handleRemoveImage","current","value","DeleteIcon","onButtonClick","click","CloudUploadIcon","Button","component","startIcon","card","selectedCard","unavailableCard","opacity","cardContent","flexGrow","chipContainer","successChip","errorChip","modelType","processButton","textAlign","ModelSelector","onModelSelect","onProcess","isProcessing","modelsStatus","selectedModel","imageSelected","models","id","name","description","icon","VisibilityIcon","available","yolo","detr","CategoryIcon","vit","handleModelClick","modelId","find","m","sx","p","Grid","container","map","model","item","xs","sm","key","Card","CardContent","mb","Chip","label","size","CardActions","disabled","fullWidth","PlayArrowIcon","root","borderRadius","shape","button","marginRight","searchDialog","minWidth","formControl","searchResults","resultCard","resultImage","chip","margin","similarityChip","palette","primary","main","VectorDBActions","results","isSaving","setIsSaving","saveSuccess","setSaveSuccess","saveError","setSaveError","openSearchDialog","setOpenSearchDialog","searchType","setSearchType","searchClass","setSearchClass","setSearchResults","isSearching","setIsSearching","searchError","setSearchError","data","handleCloseSearchDialog","generateUUID","replace","c","r","Math","random","toString","async","response","fetch","method","headers","body","JSON","stringify","image","metadata","classifications","objects","detections","imageId","ok","Error","status","result","json","error","setTimeout","err","console","message","Fragment","CircularProgress","handleOpenSearchDialog","Alert","severity","Snackbar","open","autoHideDuration","onClose","Dialog","maxWidth","DialogTitle","DialogContent","FormControl","InputLabel","Select","labelId","event","MenuItem","TextField","placeholder","marginLeft","log","length","index","similarity","distance","image_data","CardMedia","class","toFixed","confidence","DialogActions","requestBody","n_results","trim","class_name","success","Array","isArray","dividerMargin","gap","flexWrap","ResultDisplay","bgcolor","renderPerformanceInfo","performance","Divider","ms","undefined","isNaN","num","Number","formatTime","inference_time","device","md","List","detection","ListItem","ListItemText","secondary","bbox","join","top_predictions","prediction","rank","probability","responseBox","whiteSpace","buttonProgress","LlmAnalysis","visionResults","userQuery","setUserQuery","isAnalyzing","setIsAnalyzing","analysisResult","setAnalysisResult","setError","mt","OpenAIChat","imageBase64","apiKey","setApiKey","prompt","setPrompt","setResponse","loading","setLoading","objectId","setObjectId","setClassName","nResults","setNResults","inputProps","min","max","multiline","rows","q","api_key","res","credentials","txt","text","parse","_unused","meta","latency_sec","retrievedText","retrieved","forEach","answer","onClear","fontFamily","createMuiTheme","typography","App","selectedImage","setSelectedImage","setImageBase64","setSelectedModel","setIsProcessing","setResults","setModelsStatus","useEffect","then","catch","ThemeProvider","AppBar","Toolbar","Container","paragraph","File","reader","FileReader","onload","dataUrl","onerror","readAsDataURL","formData","FormData","append","endpoint","reportWebVitals","onPerfEntry","Function","getCLS","getFID","getFCP","getLCP","getTTFB","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"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","file":"static/js/main.fe247ab2.chunk.js","sourcesContent":["import React, { useState, useRef } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n Button, \n IconButton \n} from '@material-ui/core';\nimport CloudUploadIcon from '@material-ui/icons/CloudUpload';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n height: '100%',\n minHeight: 300,\n transition: 'all 0.3s ease'\n },\n dragActive: {\n border: '2px dashed #3f51b5',\n backgroundColor: 'rgba(63, 81, 181, 0.05)'\n },\n dragInactive: {\n border: '2px dashed #ccc',\n backgroundColor: 'white'\n },\n uploadBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n width: '100%',\n cursor: 'pointer'\n },\n uploadIcon: {\n fontSize: 60,\n color: '#3f51b5',\n marginBottom: theme.spacing(2)\n },\n supportText: {\n marginTop: theme.spacing(2)\n },\n previewBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n position: 'relative'\n },\n imageContainer: {\n position: 'relative',\n width: '100%',\n // Use viewport-based height so any aspect ratio fits inside\n height: '60vh',\n [theme.breakpoints.down('sm')]: {\n height: '45vh',\n },\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n overflow: 'hidden',\n marginTop: theme.spacing(2),\n },\n deleteButton: {\n position: 'absolute',\n top: 0,\n right: 0,\n backgroundColor: 'rgba(255, 255, 255, 0.7)',\n '&:hover': {\n backgroundColor: 'rgba(255, 255, 255, 0.9)',\n }\n }\n}));\n\nconst ImageUploader = ({ onImageUpload }) => {\n const classes = useStyles();\n const [previewUrl, setPreviewUrl] = useState(null);\n const [dragActive, setDragActive] = useState(false);\n const fileInputRef = useRef(null);\n\n const handleDrag = (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (e.type === 'dragenter' || e.type === 'dragover') {\n setDragActive(true);\n } else if (e.type === 'dragleave') {\n setDragActive(false);\n }\n };\n\n const handleDrop = (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragActive(false);\n if (e.dataTransfer.files && e.dataTransfer.files[0]) {\n handleFiles(e.dataTransfer.files[0]);\n }\n };\n\n const handleChange = (e) => {\n e.preventDefault();\n if (e.target.files && e.target.files[0]) {\n handleFiles(e.target.files[0]);\n }\n };\n\n const handleFiles = (file) => {\n if (file.type.startsWith('image/')) {\n setPreviewUrl(URL.createObjectURL(file));\n onImageUpload(file);\n } else {\n alert('Please upload an image file');\n }\n };\n\n const onButtonClick = () => {\n fileInputRef.current.click();\n };\n\n const handleRemoveImage = () => {\n setPreviewUrl(null);\n onImageUpload(null);\n fileInputRef.current.value = \"\";\n };\n\n return (\n <Paper \n className={`${classes.paper} ${dragActive ? classes.dragActive : classes.dragInactive}`}\n onDragEnter={handleDrag}\n onDragLeave={handleDrag}\n onDragOver={handleDrag}\n onDrop={handleDrop}\n >\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleChange}\n style={{ display: 'none' }}\n />\n\n {!previewUrl ? (\n <Box \n className={classes.uploadBox}\n onClick={onButtonClick}\n >\n <CloudUploadIcon className={classes.uploadIcon} />\n <Typography variant=\"h6\" gutterBottom>\n Drag & Drop an image here\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n or\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n component=\"span\"\n startIcon={<CloudUploadIcon />}\n >\n Browse Files\n </Button>\n <Typography variant=\"body2\" color=\"textSecondary\" className={classes.supportText}>\n Supported formats: JPG, PNG, GIF\n </Typography>\n </Box>\n ) : (\n <Box className={classes.previewBox}>\n <Typography variant=\"h6\" gutterBottom>\n Preview\n </Typography>\n <Box className={classes.imageContainer}>\n <img\n src={previewUrl}\n alt=\"Preview\"\n className=\"preview-image\"\n style={{\n width: '100%',\n height: 'auto',\n maxHeight: '100%',\n objectFit: 'contain',\n display: 'block'\n }}\n />\n <IconButton\n aria-label=\"delete\"\n className={classes.deleteButton}\n onClick={handleRemoveImage}\n >\n <DeleteIcon />\n </IconButton>\n </Box>\n </Box>\n )}\n </Paper>\n );\n};\n\nexport default ImageUploader;\n","import React from 'react';\nimport { \n Grid, \n Card, \n CardContent, \n CardActions, \n Typography, \n Button, \n Chip,\n Box\n} from '@material-ui/core';\nimport VisibilityIcon from '@material-ui/icons/Visibility';\nimport CategoryIcon from '@material-ui/icons/Category';\nimport PlayArrowIcon from '@material-ui/icons/PlayArrow';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n card: {\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n },\n selectedCard: {\n border: '2px solid #3f51b5',\n },\n unavailableCard: {\n opacity: 0.6,\n },\n cardContent: {\n flexGrow: 1,\n },\n chipContainer: {\n marginBottom: theme.spacing(1.5),\n },\n successChip: {\n backgroundColor: '#34C759',\n color: '#fff',\n },\n errorChip: {\n backgroundColor: '#FF3B3F',\n color: '#fff',\n },\n modelType: {\n marginTop: theme.spacing(1),\n },\n processButton: {\n marginTop: theme.spacing(3),\n textAlign: 'center',\n }\n}));\n\nconst ModelSelector = ({ \n onModelSelect, \n onProcess, \n isProcessing, \n modelsStatus, \n selectedModel,\n imageSelected \n}) => {\n const classes = useStyles();\n \n const models = [\n {\n id: 'yolo',\n name: 'YOLOv8',\n description: 'Fast and accurate object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.yolo\n },\n {\n id: 'detr',\n name: 'DETR',\n description: 'DEtection TRansformer for object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.detr\n },\n {\n id: 'vit',\n name: 'ViT',\n description: 'Vision Transformer for image classification',\n icon: <CategoryIcon />,\n available: modelsStatus.vit\n }\n ];\n\n const handleModelClick = (modelId) => {\n if (models.find(m => m.id === modelId).available) {\n onModelSelect(modelId);\n }\n };\n\n return (\n <Box sx={{ p: 2, height: '100%' }}>\n <Typography variant=\"h6\" gutterBottom>\n Select Model\n </Typography>\n \n <Grid container spacing={2}>\n {models.map((model) => (\n <Grid item xs={12} sm={4} key={model.id}>\n <Card \n className={`\n ${classes.card} \n ${selectedModel === model.id ? classes.selectedCard : ''} \n ${!model.available ? classes.unavailableCard : ''}\n `}\n onClick={() => handleModelClick(model.id)}\n >\n <CardContent className={classes.cardContent}>\n <Box sx={{ mb: 2, color: 'primary' }}>\n {model.icon}\n </Box>\n <Typography variant=\"h5\" component=\"div\" gutterBottom>\n {model.name}\n </Typography>\n <div className={classes.chipContainer}>\n {model.available ? (\n <Chip \n label=\"Available\" \n className={classes.successChip}\n size=\"small\" \n />\n ) : (\n <Chip \n label=\"Not Available\" \n className={classes.errorChip}\n size=\"small\" \n />\n )}\n </div>\n <Typography variant=\"body2\" color=\"textSecondary\">\n {model.description}\n </Typography>\n </CardContent>\n <CardActions>\n <Button \n size=\"small\" \n onClick={() => handleModelClick(model.id)}\n disabled={!model.available}\n color={selectedModel === model.id ? \"primary\" : \"default\"}\n variant={selectedModel === model.id ? \"contained\" : \"outlined\"}\n fullWidth\n >\n {selectedModel === model.id ? 'Selected' : 'Select'}\n </Button>\n </CardActions>\n </Card>\n </Grid>\n ))}\n </Grid>\n\n <div className={classes.processButton}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"large\"\n startIcon={<PlayArrowIcon />}\n onClick={onProcess}\n disabled={!selectedModel || !imageSelected || isProcessing}\n >\n {isProcessing ? 'Processing...' : 'Process Image'}\n </Button>\n </div>\n </Box>\n );\n};\n\nexport default ModelSelector;\n","import React, { useState } from 'react';\nimport { \n Button, \n Box, \n Typography, \n CircularProgress, \n Snackbar,\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n TextField,\n FormControl,\n InputLabel,\n Select,\n MenuItem,\n Grid,\n Card,\n CardMedia,\n CardContent,\n Chip\n} from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n marginTop: theme.spacing(2),\n marginBottom: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n },\n button: {\n marginRight: theme.spacing(2),\n },\n searchDialog: {\n minWidth: '500px',\n },\n formControl: {\n marginBottom: theme.spacing(2),\n minWidth: '100%',\n },\n searchResults: {\n marginTop: theme.spacing(2),\n },\n resultCard: {\n marginBottom: theme.spacing(2),\n },\n resultImage: {\n height: 140,\n objectFit: 'contain',\n },\n chip: {\n margin: theme.spacing(0.5),\n },\n similarityChip: {\n backgroundColor: theme.palette.primary.main,\n color: 'white',\n }\n}));\n\nconst VectorDBActions = ({ results }) => {\n const classes = useStyles();\n const [isSaving, setIsSaving] = useState(false);\n const [saveSuccess, setSaveSuccess] = useState(false);\n const [saveError, setSaveError] = useState(null);\n const [openSearchDialog, setOpenSearchDialog] = useState(false);\n const [searchType, setSearchType] = useState('image');\n const [searchClass, setSearchClass] = useState('');\n const [searchResults, setSearchResults] = useState([]);\n const [isSearching, setIsSearching] = useState(false);\n const [searchError, setSearchError] = useState(null);\n \n // Extract model and data from results\n const { model, data } = results;\n \n // Handle saving to vector DB\n const handleSaveToVectorDB = async () => {\n setIsSaving(true);\n setSaveError(null);\n \n try {\n let response;\n \n if (model === 'vit') {\n // For ViT, save the whole image with classifications\n response = await fetch('/api/add-to-collection', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n metadata: {\n model: 'vit',\n classifications: data.classifications\n }\n })\n });\n } else {\n // For YOLO and DETR, save detected objects\n response = await fetch('/api/add-detected-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n objects: data.detections,\n imageId: generateUUID()\n })\n });\n }\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n setSaveSuccess(true);\n setTimeout(() => setSaveSuccess(false), 5000);\n } catch (err) {\n console.error('Error saving to vector DB:', err);\n setSaveError(`Error saving to vector DB: ${err.message}`);\n } finally {\n setIsSaving(false);\n }\n };\n \n // Handle opening search dialog\n const handleOpenSearchDialog = () => {\n setOpenSearchDialog(true);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle closing search dialog\n const handleCloseSearchDialog = () => {\n setOpenSearchDialog(false);\n };\n \n // Handle search type change\n const handleSearchTypeChange = (event) => {\n setSearchType(event.target.value);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle search class change\n const handleSearchClassChange = (event) => {\n setSearchClass(event.target.value);\n };\n \n // Handle search\n const handleSearch = async () => {\n setIsSearching(true);\n setSearchError(null);\n \n try {\n let requestBody = {};\n \n if (searchType === 'image') {\n // Search by current image\n requestBody = {\n searchType: 'image',\n image: data.image,\n n_results: 5\n };\n } else {\n // Search by class name\n if (!searchClass.trim()) {\n throw new Error('Please enter a class name');\n }\n \n requestBody = {\n searchType: 'class',\n class_name: searchClass.trim(),\n n_results: 5\n };\n }\n \n const response = await fetch('/api/search-similar-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody)\n });\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n console.log('Search API response:', result);\n \n // The backend responds with {success, searchType, results} structure, so extract only the results array\n if (result.success && Array.isArray(result.results)) {\n console.log('Setting search results array:', result.results);\n console.log('Results array length:', result.results.length);\n console.log('First result item:', result.results[0]);\n setSearchResults(result.results);\n } else {\n console.error('Unexpected API response format:', result);\n throw new Error('Unexpected API response format');\n }\n } catch (err) {\n console.error('Error searching vector DB:', err);\n setSearchError(`Error searching vector DB: ${err.message}`);\n } finally {\n setIsSearching(false);\n }\n };\n \n // Generate UUID for image ID\n const generateUUID = () => {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n };\n \n // Render search results\n const renderSearchResults = () => {\n console.log('Rendering search results:', searchResults);\n console.log('Search results length:', searchResults.length);\n \n if (searchResults.length === 0) {\n console.log('No results to render');\n return (\n <Typography variant=\"body1\">No results found.</Typography>\n );\n }\n \n return (\n <Grid container spacing={2}>\n {searchResults.map((result, index) => {\n const similarity = (1 - result.distance) * 100;\n \n return (\n <Grid item xs={12} sm={6} key={index}>\n <Card className={classes.resultCard}>\n {result.metadata && result.metadata.image_data ? (\n <CardMedia\n className={classes.resultImage}\n component=\"img\"\n height=\"200\"\n image={`data:image/jpeg;base64,${result.metadata.image_data}`}\n alt={result.metadata && result.metadata.class ? result.metadata.class : 'Object'}\n />\n ) : (\n <Box \n className={classes.resultImage}\n style={{ \n backgroundColor: '#f0f0f0', \n display: 'flex', \n alignItems: 'center', \n justifyContent: 'center',\n height: 200\n }}\n >\n <Typography variant=\"body2\" color=\"textSecondary\">\n {result.metadata && result.metadata.class ? result.metadata.class : 'Object'} Image\n </Typography>\n </Box>\n )}\n <CardContent>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\" mb={1}>\n <Typography variant=\"subtitle1\">Result #{index + 1}</Typography>\n <Chip \n label={`Similarity: ${similarity.toFixed(2)}%`}\n className={classes.similarityChip}\n size=\"small\"\n />\n </Box>\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Class:</strong> {result.metadata.class || 'N/A'}\n </Typography>\n {result.metadata.confidence && (\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Confidence:</strong> {(result.metadata.confidence * 100).toFixed(2)}%\n </Typography>\n )}\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Object ID:</strong> {result.id}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n );\n })}\n </Grid>\n );\n };\n \n return (\n <Box className={classes.root}>\n <Typography variant=\"h6\" gutterBottom>\n Vector Database Actions\n </Typography>\n \n <Box display=\"flex\" alignItems=\"center\" mb={2}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={handleSaveToVectorDB}\n disabled={isSaving}\n className={classes.button}\n >\n {isSaving ? (\n <>\n <CircularProgress size={20} color=\"inherit\" style={{ marginRight: 8 }} />\n Saving...\n </>\n ) : (\n 'Save to Vector DB'\n )}\n </Button>\n \n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={handleOpenSearchDialog}\n className={classes.button}\n >\n Search Similar\n </Button>\n </Box>\n \n {saveError && (\n <Alert severity=\"error\" style={{ marginTop: 8 }}>\n {saveError}\n </Alert>\n )}\n \n <Snackbar open={saveSuccess} autoHideDuration={5000} onClose={() => setSaveSuccess(false)}>\n <Alert severity=\"success\">\n {model === 'vit' ? (\n 'Image and classifications successfully saved to vector DB!'\n ) : (\n 'Detected objects successfully saved to vector DB!'\n )}\n </Alert>\n </Snackbar>\n \n {/* Search Dialog */}\n <Dialog\n open={openSearchDialog}\n onClose={handleCloseSearchDialog}\n maxWidth=\"md\"\n fullWidth\n >\n <DialogTitle>Search Vector Database</DialogTitle>\n <DialogContent>\n <FormControl className={classes.formControl}>\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n id=\"search-type\"\n value={searchType}\n onChange={handleSearchTypeChange}\n >\n <MenuItem value=\"image\">Search by Current Image</MenuItem>\n <MenuItem value=\"class\">Search by Class Name</MenuItem>\n </Select>\n </FormControl>\n \n {searchType === 'class' && (\n <FormControl className={classes.formControl}>\n <TextField\n label=\"Class Name\"\n value={searchClass}\n onChange={handleSearchClassChange}\n placeholder=\"e.g. person, car, dog...\"\n fullWidth\n />\n </FormControl>\n )}\n \n {searchError && (\n <Alert severity=\"error\" style={{ marginBottom: 16 }}>\n {searchError}\n </Alert>\n )}\n \n <Box className={classes.searchResults}>\n {isSearching ? (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" p={4}>\n <CircularProgress />\n <Typography variant=\"body1\" style={{ marginLeft: 16 }}>\n Searching...\n </Typography>\n </Box>\n ) : (\n <>\n {console.log('Search dialog render - searchResults:', searchResults)}\n {searchResults.length > 0 ? renderSearchResults() : \n <Typography variant=\"body1\">No results found. Please try another search.</Typography>\n }\n </>\n )}\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={handleCloseSearchDialog} color=\"default\">\n Close\n </Button>\n <Button \n onClick={handleSearch} \n color=\"primary\" \n variant=\"contained\"\n disabled={isSearching || (searchType === 'class' && !searchClass.trim())}\n >\n Search\n </Button>\n </DialogActions>\n </Dialog>\n </Box>\n );\n};\n\nexport default VectorDBActions;\n","import React from 'react';\nimport { \n Paper, \n Typography, \n Box, \n List, \n ListItem, \n ListItemText, \n Divider,\n Grid,\n Chip\n} from '@material-ui/core';\nimport VectorDBActions from './VectorDBActions';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n resultImage: {\n maxWidth: '100%',\n maxHeight: '400px',\n objectFit: 'contain'\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n chipContainer: {\n display: 'flex',\n gap: theme.spacing(1),\n flexWrap: 'wrap'\n }\n}));\n\nconst ResultDisplay = ({ results }) => {\n const classes = useStyles();\n if (!results) return null;\n \n const { model, data } = results;\n \n // Helper to format times nicely\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n \n // Check if there's an error\n if (data.error) {\n return (\n <Paper sx={{ p: 2, bgcolor: '#ffebee' }}>\n <Typography color=\"error\">{data.error}</Typography>\n </Paper>\n );\n }\n\n // Display performance info\n const renderPerformanceInfo = () => {\n if (!data.performance) return null;\n \n return (\n <Box className=\"performance-info\">\n <Divider className={classes.dividerMargin} />\n <Typography variant=\"body2\">\n Inference time: {formatTime(data.performance.inference_time)} on {data.performance.device}\n </Typography>\n </Box>\n );\n };\n\n // Render for YOLO and DETR (object detection)\n if (model === 'yolo' || model === 'detr') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n {model === 'yolo' ? 'YOLOv8' : 'DETR'} Detection Results\n </Typography>\n \n <Grid container spacing={3}>\n <Grid item xs={12} md={6}>\n {data.image && (\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detection Result\n </Typography>\n <img \n src={`data:image/png;base64,${data.image}`} \n alt=\"Detection Result\" \n className={classes.resultImage}\n />\n </Box>\n )}\n </Grid>\n \n <Grid item xs={12} md={6}>\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detected Objects:\n </Typography>\n \n {data.detections && data.detections.length > 0 ? (\n <List>\n {data.detections.map((detection, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {detection.class}\n </Typography>\n <Chip \n label={`${(detection.confidence * 100).toFixed(0)}%`}\n size=\"small\"\n color=\"primary\"\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n secondary={`Bounding Box: [${detection.bbox.join(', ')}]`} \n />\n </ListItem>\n {index < data.detections.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No objects detected</Typography>\n )}\n </Box>\n </Grid>\n </Grid>\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for Object Detection */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n // Render for ViT (classification)\n if (model === 'vit') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n ViT Classification Results\n </Typography>\n \n <Typography variant=\"subtitle1\" gutterBottom>\n Top Predictions:\n </Typography>\n \n {data.top_predictions && data.top_predictions.length > 0 ? (\n <List>\n {data.top_predictions.map((prediction, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {prediction.rank}. {prediction.class}\n </Typography>\n <Chip \n label={`${(prediction.probability * 100).toFixed(1)}%`}\n size=\"small\"\n color={index === 0 ? \"primary\" : \"default\"}\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n />\n </ListItem>\n {index < data.top_predictions.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No classifications available</Typography>\n )}\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for ViT Classification */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n return null;\n};\n\nexport default ResultDisplay;\n","import React, { useState } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n TextField, \n Button, \n CircularProgress,\n Divider\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n marginTop: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n responseBox: {\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n marginTop: theme.spacing(2),\n whiteSpace: 'pre-wrap'\n },\n buttonProgress: {\n marginLeft: theme.spacing(1)\n }\n}));\n\nconst LlmAnalysis = ({ visionResults, model }) => {\n const classes = useStyles();\n const [userQuery, setUserQuery] = useState('');\n const [isAnalyzing, setIsAnalyzing] = useState(false);\n const [analysisResult, setAnalysisResult] = useState(null);\n const [error, setError] = useState(null);\n\n // Format time for display\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n\n const handleAnalyze = async () => {\n if (!userQuery.trim()) return;\n \n setIsAnalyzing(true);\n setError(null);\n \n try {\n const response = await fetch('/api/analyze', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n visionResults: visionResults,\n userQuery: userQuery\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n \n if (data.error) {\n setError(data.error);\n } else {\n setAnalysisResult(data);\n }\n } catch (err) {\n console.error('Error analyzing with LLM:', err);\n setError(`Error analyzing with LLM: ${err.message}`);\n } finally {\n setIsAnalyzing(false);\n }\n };\n\n if (!visionResults) return null;\n\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n Ask AI about the {model === 'vit' ? 'Classification' : 'Detection'} Results\n </Typography>\n \n <Typography variant=\"body2\" className={classes.marginBottom}>\n Ask a question about the detected objects or classifications to get an AI-powered analysis.\n </Typography>\n \n <TextField\n fullWidth\n label=\"Your question about the image\"\n variant=\"outlined\"\n value={userQuery}\n onChange={(e) => setUserQuery(e.target.value)}\n disabled={isAnalyzing}\n className={classes.marginBottom}\n placeholder={model === 'vit' \n ? \"E.g., What category does this image belong to?\" \n : \"E.g., How many people are in this image?\"}\n />\n \n <Button \n variant=\"contained\" \n color=\"primary\"\n onClick={handleAnalyze}\n disabled={isAnalyzing || !userQuery.trim()}\n >\n Analyze with AI\n {isAnalyzing && <CircularProgress size={24} className={classes.buttonProgress} />}\n </Button>\n \n {error && (\n <Box mt={2}>\n <Typography color=\"error\">{error}</Typography>\n </Box>\n )}\n \n {analysisResult && (\n <>\n <Divider className={classes.dividerMargin} />\n \n <Typography variant=\"subtitle1\" gutterBottom>\n AI Analysis:\n </Typography>\n \n <Box className={classes.responseBox}>\n <Typography variant=\"body1\">\n {analysisResult.response}\n </Typography>\n </Box>\n \n {analysisResult.performance && (\n <Box mt={1}>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Analysis time: {formatTime(analysisResult.performance.inference_time)} on {analysisResult.performance.device}\n </Typography>\n </Box>\n )}\n </>\n )}\n </Paper>\n );\n};\n\nexport default LlmAnalysis;\n","import React, { useState } from 'react';\nimport {\n Paper,\n Typography,\n Grid,\n TextField,\n Button,\n Divider,\n MenuItem,\n Select,\n FormControl,\n InputLabel\n} from '@material-ui/core';\n\n// Props:\n// - imageBase64: data URL string of the currently uploaded image (preferred for searchType=image)\nfunction OpenAIChat({ imageBase64 }) {\n const [apiKey, setApiKey] = useState('');\n const [prompt, setPrompt] = useState('');\n const [response, setResponse] = useState('');\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState('');\n\n // Vision RAG parameters\n const [searchType, setSearchType] = useState('image');\n const [objectId, setObjectId] = useState('');\n const [className, setClassName] = useState('');\n const [nResults, setNResults] = useState(5);\n\n const onSend = async () => {\n setError('');\n setResponse('');\n const q = (prompt || '').trim();\n if (!q) { setError('μ§λ¬Έμ μ
λ ₯νμΈμ.'); return; }\n\n // Build request body for /api/vision-rag/query\n const body = {\n userQuery: q,\n searchType: searchType,\n n_results: Number(nResults) || 5,\n };\n if (apiKey) body.api_key = apiKey;\n if (searchType === 'image') {\n if (!imageBase64) { setError('μ΄λ―Έμ§κ° νμν©λλ€. λ¨Όμ μ΄λ―Έμ§λ₯Ό μ
λ‘λνμΈμ.'); return; }\n body.image = imageBase64;\n } else if (searchType === 'object') {\n if (!objectId.trim()) { setError('objectIdλ₯Ό μ
λ ₯νμΈμ.'); return; }\n body.objectId = objectId.trim();\n } else if (searchType === 'class') {\n if (!className.trim()) { setError('class_nameμ μ
λ ₯νμΈμ.'); return; }\n body.class_name = className.trim();\n }\n\n setLoading(true);\n try {\n const res = await fetch('/api/vision-rag/query', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(body)\n });\n\n if (!res.ok) {\n let txt = await res.text();\n try { txt = JSON.stringify(JSON.parse(txt), null, 2); } catch {}\n throw new Error(txt);\n }\n const data = await res.json();\n const meta = `Model: ${data.model || '-'} | Latency: ${data.latency_sec || '-'}s`;\n \n // κ²μ κ²°κ³Ό ν¬λ§·ν
\n let retrievedText = '';\n if (data.retrieved && data.retrieved.length > 0) {\n retrievedText = '\\n\\nκ²μ κ²°κ³Ό:\\n';\n data.retrieved.forEach((item, index) => {\n retrievedText += `${index + 1}. ID: ${item.id || '-'}\\n`;\n if (item.meta) {\n retrievedText += ` ν΄λμ€: ${item.meta.class || '-'}\\n`;\n retrievedText += ` μ λ’°λ: ${item.meta.confidence || '-'}\\n`;\n }\n retrievedText += ` μ μ¬λ: ${item.distance ? item.distance.toFixed(4) : '-'}\\n`;\n });\n }\n \n setResponse((data.answer || '(λΉ μλ΅)') + retrievedText + '\\n\\n---\\n' + meta);\n } catch (e) {\n setError('Error: ' + e.message);\n } finally {\n setLoading(false);\n }\n };\n\n const onClear = () => {\n setPrompt('');\n setResponse('');\n setError('');\n };\n\n return (\n <Paper style={{ padding: 16 }}>\n <Typography variant=\"h5\" gutterBottom>\n Vision RAG (LangChain)\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n μλ²μ OPENAI_API_KEYκ° μ€μ λμ΄ μλ€λ©΄ API Keyλ μλ΅ κ°λ₯ν©λλ€. κ²μ μ νμ μ ννκ³ μ§λ¬Έμ 보λ΄λ©΄, λ²‘ν° DBμμ κ²μλ 컨ν
μ€νΈλ‘ λ΅λ³ν©λλ€.\n </Typography>\n <Grid container spacing={2}>\n <Grid item xs={12} md={6}>\n <FormControl fullWidth variant=\"outlined\" size=\"small\">\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n value={searchType}\n onChange={(e) => setSearchType(e.target.value)}\n label=\"Search Type\"\n >\n <MenuItem value=\"image\">image (current upload)</MenuItem>\n <MenuItem value=\"object\">object (objectId)</MenuItem>\n <MenuItem value=\"class\">class (class_name)</MenuItem>\n </Select>\n </FormControl>\n </Grid>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"OpenAI API Key (optional)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"password\"\n placeholder=\"sk-...\"\n />\n </Grid>\n\n {searchType === 'object' && (\n <Grid item xs={12} md={6}>\n <TextField\n label=\"objectId\"\n value={objectId}\n onChange={(e) => setObjectId(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n )}\n {searchType === 'class' && (\n <Grid item xs={12} md={6}>\n <TextField\n label=\"class_name\"\n value={className}\n onChange={(e) => setClassName(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n placeholder=\"e.g. person, car, dog\"\n />\n </Grid>\n )}\n <Grid item xs={12} md={6}>\n <TextField\n label=\"n_results\"\n value={nResults}\n onChange={(e) => setNResults(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"number\"\n inputProps={{ min: 1, max: 50 }}\n />\n </Grid>\n\n <Grid item xs={12}>\n <TextField\n label=\"User Question\"\n value={prompt}\n onChange={(e) => setPrompt(e.target.value)}\n fullWidth\n multiline\n rows={4}\n variant=\"outlined\"\n placeholder={searchType === 'image' ? 'μ
λ‘λν μ΄λ―Έμ§ κΈ°λ°μΌλ‘ λ΅λ³ν΄μ€' : 'κ²μλ κ°μ²΄ 컨ν
μ€νΈλ₯Ό μ¬μ©ν΄ λ΅λ³ν΄μ€'}\n />\n </Grid>\n {error && (\n <Grid item xs={12}>\n <Typography color=\"error\">{error}</Typography>\n </Grid>\n )}\n <Grid item xs={12}>\n <div style={{ display: 'flex', gap: 8 }}>\n <Button color=\"primary\" variant=\"contained\" onClick={onSend} disabled={loading}>\n {loading ? 'Sending...' : 'Send Question'}\n </Button>\n <Button variant=\"outlined\" onClick={onClear}>Clear</Button>\n </div>\n </Grid>\n <Grid item xs={12}>\n <Divider style={{ margin: '12px 0' }} />\n <Typography variant=\"subtitle2\" color=\"textSecondary\">Response</Typography>\n <pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'ui-monospace, monospace' }}>{response}</pre>\n </Grid>\n </Grid>\n </Paper>\n );\n}\n\nexport default OpenAIChat;\n","import React, { useState, useEffect } from 'react';\nimport { \n Container, \n Typography, \n Box, \n Paper, \n Grid, \n CircularProgress,\n AppBar,\n Toolbar,\n ThemeProvider,\n createMuiTheme\n} from '@material-ui/core';\nimport ImageUploader from './components/ImageUploader';\nimport ModelSelector from './components/ModelSelector';\nimport ResultDisplay from './components/ResultDisplay';\nimport LlmAnalysis from './components/LlmAnalysis';\nimport OpenAIChat from './components/OpenAIChat';\nimport './App.css';\n\n// Create a theme\nconst theme = createMuiTheme({\n palette: {\n primary: {\n main: '#3f51b5',\n },\n secondary: {\n main: '#f50057',\n },\n },\n typography: {\n fontFamily: 'Roboto, Arial, sans-serif',\n },\n});\n\nfunction App() {\n const [selectedImage, setSelectedImage] = useState(null);\n const [imageBase64, setImageBase64] = useState('');\n const [selectedModel, setSelectedModel] = useState('');\n const [isProcessing, setIsProcessing] = useState(false);\n const [results, setResults] = useState(null);\n const [error, setError] = useState(null);\n const [modelsStatus, setModelsStatus] = useState({\n yolo: false,\n detr: false,\n vit: false\n });\n\n // Check API status on component mount\n useEffect(() => {\n fetch('/api/status')\n .then(response => response.json())\n .then(data => {\n setModelsStatus(data.models);\n })\n .catch(err => {\n console.error('Error checking API status:', err);\n setError('Error connecting to the backend API. Please make sure the server is running.');\n });\n }, []);\n\n const handleImageUpload = (image) => {\n setSelectedImage(image);\n setResults(null);\n setError(null);\n\n // Convert uploaded File -> base64 data URL for Vision RAG image search\n if (image instanceof File) {\n const reader = new FileReader();\n reader.onload = () => {\n const dataUrl = reader.result; // e.g., data:image/png;base64,....\n setImageBase64(typeof dataUrl === 'string' ? dataUrl : '');\n };\n reader.onerror = () => setImageBase64('');\n reader.readAsDataURL(image);\n } else {\n setImageBase64('');\n }\n };\n\n const handleModelSelect = (model) => {\n setSelectedModel(model);\n setResults(null);\n setError(null);\n };\n\n const processImage = async () => {\n if (!selectedImage || !selectedModel) {\n setError('Please select both an image and a model');\n return;\n }\n\n setIsProcessing(true);\n setError(null);\n\n // Create form data for the image\n const formData = new FormData();\n formData.append('image', selectedImage);\n\n let endpoint = '';\n switch (selectedModel) {\n case 'yolo':\n endpoint = '/api/detect/yolo';\n break;\n case 'detr':\n endpoint = '/api/detect/detr';\n break;\n case 'vit':\n endpoint = '/api/classify/vit';\n break;\n default:\n setError('Invalid model selection');\n setIsProcessing(false);\n return;\n }\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n setResults({ model: selectedModel, data });\n } catch (err) {\n console.error('Error processing image:', err);\n setError(`Error processing image: ${err.message}`);\n } finally {\n setIsProcessing(false);\n }\n };\n\n return (\n <ThemeProvider theme={theme}>\n <Box style={{ flexGrow: 1 }}>\n <AppBar position=\"static\">\n <Toolbar>\n <Typography variant=\"h6\" style={{ flexGrow: 1 }}>\n Multi-Model Object Detection Demo\n </Typography>\n </Toolbar>\n </AppBar>\n <Container maxWidth=\"lg\" style={{ marginTop: theme.spacing(4), marginBottom: theme.spacing(4) }}>\n <Grid container spacing={3}>\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2) }}>\n <Typography variant=\"h5\" gutterBottom>\n Upload an image to see how each model performs!\n </Typography>\n <Typography variant=\"body1\" paragraph>\n This demo showcases three different object detection and image classification models:\n </Typography>\n <Typography variant=\"body1\" component=\"div\">\n <ul>\n <li><strong>YOLOv8</strong>: Fast and accurate object detection</li>\n <li><strong>DETR</strong>: DEtection TRansformer for object detection</li>\n <li><strong>ViT</strong>: Vision Transformer for image classification</li>\n </ul>\n </Typography>\n </Paper>\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ImageUploader onImageUpload={handleImageUpload} />\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ModelSelector \n onModelSelect={handleModelSelect} \n onProcess={processImage}\n isProcessing={isProcessing}\n modelsStatus={modelsStatus}\n selectedModel={selectedModel}\n imageSelected={!!selectedImage}\n />\n </Grid>\n \n {error && (\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2), backgroundColor: '#ffebee' }}>\n <Typography color=\"error\">{error}</Typography>\n </Paper>\n </Grid>\n )}\n \n {isProcessing && (\n <Grid item xs={12} style={{ textAlign: 'center', margin: `${theme.spacing(4)}px 0` }}>\n <CircularProgress />\n <Typography variant=\"h6\" style={{ marginTop: theme.spacing(2) }}>\n Processing image...\n </Typography>\n </Grid>\n )}\n \n {results && (\n <>\n <Grid item xs={12}>\n <ResultDisplay results={results} />\n </Grid>\n <Grid item xs={12}>\n <LlmAnalysis visionResults={results.data} model={results.model} />\n </Grid>\n </>\n )}\n\n {/* Vision RAG (LangChain) section at the end */}\n <Grid item xs={12}>\n <OpenAIChat imageBase64={imageBase64} />\n </Grid>\n </Grid>\n </Container>\n </Box>\n </ThemeProvider>\n );\n}\n\nexport default App;\n","const reportWebVitals = (onPerfEntry) => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"],"sourceRoot":""}
|
|
|
|
static_bak_20250820_085943/asset-manifest.json
DELETED
@@ -1,24 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"files": {
|
3 |
-
"main.css": "/static/css/main.3b4eede1.chunk.css",
|
4 |
-
"main.js": "/static/js/main.fc0e2dc7.chunk.js",
|
5 |
-
"main.js.map": "/static/js/main.fc0e2dc7.chunk.js.map",
|
6 |
-
"runtime-main.js": "/static/js/runtime-main.25710301.js",
|
7 |
-
"runtime-main.js.map": "/static/js/runtime-main.25710301.js.map",
|
8 |
-
"static/js/2.9b5b80e8.chunk.js": "/static/js/2.9b5b80e8.chunk.js",
|
9 |
-
"static/js/2.9b5b80e8.chunk.js.map": "/static/js/2.9b5b80e8.chunk.js.map",
|
10 |
-
"static/js/3.9013e23f.chunk.js": "/static/js/3.9013e23f.chunk.js",
|
11 |
-
"static/js/3.9013e23f.chunk.js.map": "/static/js/3.9013e23f.chunk.js.map",
|
12 |
-
"index.html": "/index.html",
|
13 |
-
"precache-manifest.4683f6cd857bd9aa85304ac13a82d061.js": "/precache-manifest.4683f6cd857bd9aa85304ac13a82d061.js",
|
14 |
-
"service-worker.js": "/service-worker.js",
|
15 |
-
"static/css/main.3b4eede1.chunk.css.map": "/static/css/main.3b4eede1.chunk.css.map",
|
16 |
-
"static/js/2.9b5b80e8.chunk.js.LICENSE.txt": "/static/js/2.9b5b80e8.chunk.js.LICENSE.txt"
|
17 |
-
},
|
18 |
-
"entrypoints": [
|
19 |
-
"static/js/runtime-main.25710301.js",
|
20 |
-
"static/js/2.9b5b80e8.chunk.js",
|
21 |
-
"static/css/main.3b4eede1.chunk.css",
|
22 |
-
"static/js/main.fc0e2dc7.chunk.js"
|
23 |
-
]
|
24 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static_bak_20250820_085943/index.html
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Multi-Model Object Detection Demo"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Vision Web App</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link href="/static/css/main.3b4eede1.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"9013e23f"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="/",i.oe=function(e){throw console.error(e),e};var a=this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([])</script><script src="/static/js/2.9b5b80e8.chunk.js"></script><script src="/static/js/main.fc0e2dc7.chunk.js"></script></body></html>
|
|
|
|
static_bak_20250820_085943/manifest.json
DELETED
@@ -1,15 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"short_name": "Vision Web App",
|
3 |
-
"name": "Multi-Model Object Detection Demo",
|
4 |
-
"icons": [
|
5 |
-
{
|
6 |
-
"src": "favicon.ico",
|
7 |
-
"sizes": "64x64 32x32 24x24 16x16",
|
8 |
-
"type": "image/x-icon"
|
9 |
-
}
|
10 |
-
],
|
11 |
-
"start_url": ".",
|
12 |
-
"display": "standalone",
|
13 |
-
"theme_color": "#000000",
|
14 |
-
"background_color": "#ffffff"
|
15 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static_bak_20250820_085943/precache-manifest.4683f6cd857bd9aa85304ac13a82d061.js
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
2 |
-
{
|
3 |
-
"revision": "d54f7d4646b42d5859a3c60dc17c2cbc",
|
4 |
-
"url": "/index.html"
|
5 |
-
},
|
6 |
-
{
|
7 |
-
"revision": "bb5a62e98165b2cca977",
|
8 |
-
"url": "/static/css/main.3b4eede1.chunk.css"
|
9 |
-
},
|
10 |
-
{
|
11 |
-
"revision": "ca6d8d363680165da358",
|
12 |
-
"url": "/static/js/2.9b5b80e8.chunk.js"
|
13 |
-
},
|
14 |
-
{
|
15 |
-
"revision": "89a1b2dcd30c03705b2bceeb141b76b6",
|
16 |
-
"url": "/static/js/2.9b5b80e8.chunk.js.LICENSE.txt"
|
17 |
-
},
|
18 |
-
{
|
19 |
-
"revision": "70d68b45d511e1e11f23",
|
20 |
-
"url": "/static/js/3.9013e23f.chunk.js"
|
21 |
-
},
|
22 |
-
{
|
23 |
-
"revision": "bb5a62e98165b2cca977",
|
24 |
-
"url": "/static/js/main.fc0e2dc7.chunk.js"
|
25 |
-
},
|
26 |
-
{
|
27 |
-
"revision": "d8c310b0ac7ffa6d8151",
|
28 |
-
"url": "/static/js/runtime-main.25710301.js"
|
29 |
-
}
|
30 |
-
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static_bak_20250820_085943/service-worker.js
DELETED
@@ -1,39 +0,0 @@
|
|
1 |
-
/**
|
2 |
-
* Welcome to your Workbox-powered service worker!
|
3 |
-
*
|
4 |
-
* You'll need to register this file in your web app and you should
|
5 |
-
* disable HTTP caching for this file too.
|
6 |
-
* See https://goo.gl/nhQhGp
|
7 |
-
*
|
8 |
-
* The rest of the code is auto-generated. Please don't update this file
|
9 |
-
* directly; instead, make changes to your Workbox build configuration
|
10 |
-
* and re-run your build process.
|
11 |
-
* See https://goo.gl/2aRDsh
|
12 |
-
*/
|
13 |
-
|
14 |
-
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
15 |
-
|
16 |
-
importScripts(
|
17 |
-
"/precache-manifest.4683f6cd857bd9aa85304ac13a82d061.js"
|
18 |
-
);
|
19 |
-
|
20 |
-
self.addEventListener('message', (event) => {
|
21 |
-
if (event.data && event.data.type === 'SKIP_WAITING') {
|
22 |
-
self.skipWaiting();
|
23 |
-
}
|
24 |
-
});
|
25 |
-
|
26 |
-
workbox.core.clientsClaim();
|
27 |
-
|
28 |
-
/**
|
29 |
-
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
|
30 |
-
* requests for URLs in the manifest.
|
31 |
-
* See https://goo.gl/S9QRab
|
32 |
-
*/
|
33 |
-
self.__precacheManifest = [].concat(self.__precacheManifest || []);
|
34 |
-
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
35 |
-
|
36 |
-
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
|
37 |
-
|
38 |
-
blacklist: [/^\/_/,/\/[^\/?]+\.[^\/]+$/],
|
39 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static_bak_20250820_085943/static/css/main.3b4eede1.chunk.css
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#f5f5f5}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}.App{text-align:center}.preview-image{width:100%;height:auto;max-height:100%;object-fit:contain;display:block;margin-top:0}.result-image{max-width:100%;border:1px solid #ddd;border-radius:4px;padding:4px}.detection-list{margin-top:16px;text-align:left}.model-card{cursor:pointer;transition:all .3s}.model-card:hover{transform:translateY(-5px);box-shadow:0 10px 20px rgba(0,0,0,.1)}.model-card.selected{border:2px solid #3f51b5;background-color:#e8eaf6}.model-card.disabled{opacity:.6;cursor:not-allowed}.performance-info{margin-top:16px;font-size:.9rem;color:#666}
|
2 |
-
/*# sourceMappingURL=main.3b4eede1.chunk.css.map */
|
|
|
|
|
|
static_bak_20250820_085943/static/css/main.3b4eede1.chunk.css.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["index.css","App.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mJAEY,CACZ,kCAAmC,CACnC,iCAAkC,CAClC,wBACF,CAEA,KACE,yEAEF,CCbA,KACE,iBACF,CAEA,eACE,UAAW,CACX,WAAY,CACZ,eAAgB,CAChB,kBAAmB,CACnB,aAAc,CACd,YACF,CAEA,cACE,cAAe,CACf,qBAAsB,CACtB,iBAAkB,CAClB,WACF,CAEA,gBACE,eAAgB,CAChB,eACF,CAEA,YACE,cAAe,CACf,kBACF,CAEA,kBACE,0BAA2B,CAC3B,qCACF,CAEA,qBACE,wBAAyB,CACzB,wBACF,CAEA,qBACE,UAAY,CACZ,kBACF,CAEA,kBACE,eAAgB,CAChB,eAAiB,CACjB,UACF","file":"main.3b4eede1.chunk.css","sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n background-color: #f5f5f5;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n",".App {\n text-align: center;\n}\n\n.preview-image {\n width: 100%;\n height: auto;\n max-height: 100%;\n object-fit: contain;\n display: block;\n margin-top: 0;\n}\n\n.result-image {\n max-width: 100%;\n border: 1px solid #ddd;\n border-radius: 4px;\n padding: 4px;\n}\n\n.detection-list {\n margin-top: 16px;\n text-align: left;\n}\n\n.model-card {\n cursor: pointer;\n transition: all 0.3s;\n}\n\n.model-card:hover {\n transform: translateY(-5px);\n box-shadow: 0 10px 20px rgba(0,0,0,0.1);\n}\n\n.model-card.selected {\n border: 2px solid #3f51b5;\n background-color: #e8eaf6;\n}\n\n.model-card.disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.performance-info {\n margin-top: 16px;\n font-size: 0.9rem;\n color: #666;\n}\n"]}
|
|
|
|
static_bak_20250820_085943/static/js/2.9b5b80e8.chunk.js
DELETED
The diff for this file is too large to render.
See raw diff
|
|
static_bak_20250820_085943/static/js/2.9b5b80e8.chunk.js.LICENSE.txt
DELETED
@@ -1,58 +0,0 @@
|
|
1 |
-
/*
|
2 |
-
object-assign
|
3 |
-
(c) Sindre Sorhus
|
4 |
-
@license MIT
|
5 |
-
*/
|
6 |
-
|
7 |
-
/**
|
8 |
-
* A better abstraction over CSS.
|
9 |
-
*
|
10 |
-
* @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present
|
11 |
-
* @website https://github.com/cssinjs/jss
|
12 |
-
* @license MIT
|
13 |
-
*/
|
14 |
-
|
15 |
-
/** @license React v0.19.1
|
16 |
-
* scheduler.production.min.js
|
17 |
-
*
|
18 |
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
19 |
-
*
|
20 |
-
* This source code is licensed under the MIT license found in the
|
21 |
-
* LICENSE file in the root directory of this source tree.
|
22 |
-
*/
|
23 |
-
|
24 |
-
/** @license React v16.13.1
|
25 |
-
* react-is.production.min.js
|
26 |
-
*
|
27 |
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
28 |
-
*
|
29 |
-
* This source code is licensed under the MIT license found in the
|
30 |
-
* LICENSE file in the root directory of this source tree.
|
31 |
-
*/
|
32 |
-
|
33 |
-
/** @license React v16.14.0
|
34 |
-
* react-dom.production.min.js
|
35 |
-
*
|
36 |
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
37 |
-
*
|
38 |
-
* This source code is licensed under the MIT license found in the
|
39 |
-
* LICENSE file in the root directory of this source tree.
|
40 |
-
*/
|
41 |
-
|
42 |
-
/** @license React v16.14.0
|
43 |
-
* react.production.min.js
|
44 |
-
*
|
45 |
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
46 |
-
*
|
47 |
-
* This source code is licensed under the MIT license found in the
|
48 |
-
* LICENSE file in the root directory of this source tree.
|
49 |
-
*/
|
50 |
-
|
51 |
-
/** @license React v17.0.2
|
52 |
-
* react-is.production.min.js
|
53 |
-
*
|
54 |
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
55 |
-
*
|
56 |
-
* This source code is licensed under the MIT license found in the
|
57 |
-
* LICENSE file in the root directory of this source tree.
|
58 |
-
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static_bak_20250820_085943/static/js/2.9b5b80e8.chunk.js.map
DELETED
The diff for this file is too large to render.
See raw diff
|
|
static_bak_20250820_085943/static/js/3.9013e23f.chunk.js
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
(this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[]).push([[3],{170:function(t,n,e){"use strict";e.r(n),e.d(n,"getCLS",(function(){return l})),e.d(n,"getFCP",(function(){return g})),e.d(n,"getFID",(function(){return h})),e.d(n,"getLCP",(function(){return y})),e.d(n,"getTTFB",(function(){return F}));var i,a,r=function(){return"".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)},o=function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;return{name:t,value:n,delta:0,entries:[],id:r(),isFinal:!1}},u=function(t,n){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var e=new PerformanceObserver((function(t){return t.getEntries().map(n)}));return e.observe({type:t,buffered:!0}),e}}catch(t){}},s=!1,c=!1,p=function(t){s=!t.persisted},d=function(){addEventListener("pagehide",p),addEventListener("beforeunload",(function(){}))},f=function(t){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];c||(d(),c=!0),addEventListener("visibilitychange",(function(n){var e=n.timeStamp;"hidden"===document.visibilityState&&t({timeStamp:e,isUnloading:s})}),{capture:!0,once:n})},v=function(t,n,e,i){var a;return function(){e&&n.isFinal&&e.disconnect(),n.value>=0&&(i||n.isFinal||"hidden"===document.visibilityState)&&(n.delta=n.value-(a||0),(n.delta||n.isFinal||void 0===a)&&(t(n),a=n.value))}},l=function(t){var n,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=o("CLS",0),a=function(t){t.hadRecentInput||(i.value+=t.value,i.entries.push(t),n())},r=u("layout-shift",a);r&&(n=v(t,i,r,e),f((function(t){var e=t.isUnloading;r.takeRecords().map(a),e&&(i.isFinal=!0),n()})))},m=function(){return void 0===i&&(i="hidden"===document.visibilityState?0:1/0,f((function(t){var n=t.timeStamp;return i=n}),!0)),{get timeStamp(){return i}}},g=function(t){var n,e=o("FCP"),i=m(),a=u("paint",(function(t){"first-contentful-paint"===t.name&&t.startTime<i.timeStamp&&(e.value=t.startTime,e.isFinal=!0,e.entries.push(t),n())}));a&&(n=v(t,e,a))},h=function(t){var n=o("FID"),e=m(),i=function(t){t.startTime<e.timeStamp&&(n.value=t.processingStart-t.startTime,n.entries.push(t),n.isFinal=!0,r())},a=u("first-input",i),r=v(t,n,a);a?f((function(){a.takeRecords().map(i),a.disconnect()}),!0):window.perfMetrics&&window.perfMetrics.onFirstInputDelay&&window.perfMetrics.onFirstInputDelay((function(t,i){i.timeStamp<e.timeStamp&&(n.value=t,n.isFinal=!0,n.entries=[{entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+t}],r())}))},S=function(){return a||(a=new Promise((function(t){return["scroll","keydown","pointerdown"].map((function(n){addEventListener(n,t,{once:!0,passive:!0,capture:!0})}))}))),a},y=function(t){var n,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=o("LCP"),a=m(),r=function(t){var e=t.startTime;e<a.timeStamp?(i.value=e,i.entries.push(t)):i.isFinal=!0,n()},s=u("largest-contentful-paint",r);if(s){n=v(t,i,s,e);var c=function(){i.isFinal||(s.takeRecords().map(r),i.isFinal=!0,n())};S().then(c),f(c,!0)}},F=function(t){var n,e=o("TTFB");n=function(){try{var n=performance.getEntriesByType("navigation")[0]||function(){var t=performance.timing,n={entryType:"navigation",startTime:0};for(var e in t)"navigationStart"!==e&&"toJSON"!==e&&(n[e]=Math.max(t[e]-t.navigationStart,0));return n}();e.value=e.delta=n.responseStart,e.entries=[n],e.isFinal=!0,t(e)}catch(t){}},"complete"===document.readyState?setTimeout(n,0):addEventListener("pageshow",n)}}}]);
|
2 |
-
//# sourceMappingURL=3.9013e23f.chunk.js.map
|
|
|
|
|
|
static_bak_20250820_085943/static/js/3.9013e23f.chunk.js.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["../node_modules/web-vitals/dist/web-vitals.es5.min.js"],"names":["v","t","n","e","concat","Date","now","Math","floor","random","i","arguments","length","name","value","delta","entries","id","isFinal","a","PerformanceObserver","supportedEntryTypes","includes","getEntries","map","observe","type","buffered","r","o","s","persisted","u","addEventListener","c","timeStamp","document","visibilityState","isUnloading","capture","once","l","disconnect","p","hadRecentInput","push","takeRecords","d","startTime","f","processingStart","window","perfMetrics","onFirstInputDelay","entryType","target","cancelable","m","Promise","passive","g","then","h","performance","getEntriesByType","timing","max","navigationStart","responseStart","readyState","setTimeout"],"mappings":"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","file":"static/js/3.9013e23f.chunk.js","sourcesContent":["var t,n,e=function(){return\"\".concat(Date.now(),\"-\").concat(Math.floor(8999999999999*Math.random())+1e12)},i=function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;return{name:t,value:n,delta:0,entries:[],id:e(),isFinal:!1}},a=function(t,n){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var e=new PerformanceObserver((function(t){return t.getEntries().map(n)}));return e.observe({type:t,buffered:!0}),e}}catch(t){}},r=!1,o=!1,s=function(t){r=!t.persisted},u=function(){addEventListener(\"pagehide\",s),addEventListener(\"beforeunload\",(function(){}))},c=function(t){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];o||(u(),o=!0),addEventListener(\"visibilitychange\",(function(n){var e=n.timeStamp;\"hidden\"===document.visibilityState&&t({timeStamp:e,isUnloading:r})}),{capture:!0,once:n})},l=function(t,n,e,i){var a;return function(){e&&n.isFinal&&e.disconnect(),n.value>=0&&(i||n.isFinal||\"hidden\"===document.visibilityState)&&(n.delta=n.value-(a||0),(n.delta||n.isFinal||void 0===a)&&(t(n),a=n.value))}},p=function(t){var n,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=i(\"CLS\",0),o=function(t){t.hadRecentInput||(r.value+=t.value,r.entries.push(t),n())},s=a(\"layout-shift\",o);s&&(n=l(t,r,s,e),c((function(t){var e=t.isUnloading;s.takeRecords().map(o),e&&(r.isFinal=!0),n()})))},d=function(){return void 0===t&&(t=\"hidden\"===document.visibilityState?0:1/0,c((function(n){var e=n.timeStamp;return t=e}),!0)),{get timeStamp(){return t}}},v=function(t){var n,e=i(\"FCP\"),r=d(),o=a(\"paint\",(function(t){\"first-contentful-paint\"===t.name&&t.startTime<r.timeStamp&&(e.value=t.startTime,e.isFinal=!0,e.entries.push(t),n())}));o&&(n=l(t,e,o))},f=function(t){var n=i(\"FID\"),e=d(),r=function(t){t.startTime<e.timeStamp&&(n.value=t.processingStart-t.startTime,n.entries.push(t),n.isFinal=!0,s())},o=a(\"first-input\",r),s=l(t,n,o);o?c((function(){o.takeRecords().map(r),o.disconnect()}),!0):window.perfMetrics&&window.perfMetrics.onFirstInputDelay&&window.perfMetrics.onFirstInputDelay((function(t,i){i.timeStamp<e.timeStamp&&(n.value=t,n.isFinal=!0,n.entries=[{entryType:\"first-input\",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+t}],s())}))},m=function(){return n||(n=new Promise((function(t){return[\"scroll\",\"keydown\",\"pointerdown\"].map((function(n){addEventListener(n,t,{once:!0,passive:!0,capture:!0})}))}))),n},g=function(t){var n,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=i(\"LCP\"),o=d(),s=function(t){var e=t.startTime;e<o.timeStamp?(r.value=e,r.entries.push(t)):r.isFinal=!0,n()},u=a(\"largest-contentful-paint\",s);if(u){n=l(t,r,u,e);var p=function(){r.isFinal||(u.takeRecords().map(s),r.isFinal=!0,n())};m().then(p),c(p,!0)}},h=function(t){var n,e=i(\"TTFB\");n=function(){try{var n=performance.getEntriesByType(\"navigation\")[0]||function(){var t=performance.timing,n={entryType:\"navigation\",startTime:0};for(var e in t)\"navigationStart\"!==e&&\"toJSON\"!==e&&(n[e]=Math.max(t[e]-t.navigationStart,0));return n}();e.value=e.delta=n.responseStart,e.entries=[n],e.isFinal=!0,t(e)}catch(t){}},\"complete\"===document.readyState?setTimeout(n,0):addEventListener(\"pageshow\",n)};export{p as getCLS,v as getFCP,f as getFID,g as getLCP,h as getTTFB};\n"],"sourceRoot":""}
|
|
|
|
static_bak_20250820_085943/static/js/main.fc0e2dc7.chunk.js
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
(this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[]).push([[0],{102:function(e,a,t){},103:function(e,a,t){"use strict";t.r(a);var n=t(0),r=t.n(n),l=t(11),o=t.n(l),c=(t(97),t(78)),i=t(155),s=t(159),m=t(156),d=t(157),u=t(48),g=t(158),p=t(138),E=t(81),h=t(143),y=t(136),b=t(137),f=t(63),v=t.n(f),x=t(75),S=t.n(x),C=t(133);const j=Object(C.a)(e=>({paper:{padding:e.spacing(2),display:"flex",flexDirection:"column",alignItems:"center",height:"100%",minHeight:300,transition:"all 0.3s ease"},dragActive:{border:"2px dashed #3f51b5",backgroundColor:"rgba(63, 81, 181, 0.05)"},dragInactive:{border:"2px dashed #ccc",backgroundColor:"white"},uploadBox:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",height:"100%",width:"100%",cursor:"pointer"},uploadIcon:{fontSize:60,color:"#3f51b5",marginBottom:e.spacing(2)},supportText:{marginTop:e.spacing(2)},previewBox:{display:"flex",flexDirection:"column",alignItems:"center",width:"100%",height:"100%",position:"relative"},imageContainer:{position:"relative",width:"100%",height:"60vh",[e.breakpoints.down("sm")]:{height:"45vh"},display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",marginTop:e.spacing(2)},deleteButton:{position:"absolute",top:0,right:0,backgroundColor:"rgba(255, 255, 255, 0.7)","&:hover":{backgroundColor:"rgba(255, 255, 255, 0.9)"}}}));var w=e=>{let{onImageUpload:a}=e;const t=j(),[l,o]=Object(n.useState)(null),[c,i]=Object(n.useState)(!1),m=Object(n.useRef)(null),d=e=>{e.preventDefault(),e.stopPropagation(),"dragenter"===e.type||"dragover"===e.type?i(!0):"dragleave"===e.type&&i(!1)},g=e=>{e.type.startsWith("image/")?(o(URL.createObjectURL(e)),a(e)):alert("Please upload an image file")};return r.a.createElement(E.a,{className:"".concat(t.paper," ").concat(c?t.dragActive:t.dragInactive),onDragEnter:d,onDragLeave:d,onDragOver:d,onDrop:e=>{e.preventDefault(),e.stopPropagation(),i(!1),e.dataTransfer.files&&e.dataTransfer.files[0]&&g(e.dataTransfer.files[0])}},r.a.createElement("input",{ref:m,type:"file",accept:"image/*",onChange:e=>{e.preventDefault(),e.target.files&&e.target.files[0]&&g(e.target.files[0])},style:{display:"none"}}),l?r.a.createElement(s.a,{className:t.previewBox},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Preview"),r.a.createElement(s.a,{className:t.imageContainer},r.a.createElement("img",{src:l,alt:"Preview",className:"preview-image",style:{width:"100%",height:"auto",maxHeight:"100%",objectFit:"contain",display:"block"}}),r.a.createElement(b.a,{"aria-label":"delete",className:t.deleteButton,onClick:()=>{o(null),a(null),m.current.value=""}},r.a.createElement(S.a,null)))):r.a.createElement(s.a,{className:t.uploadBox,onClick:()=>{m.current.click()}},r.a.createElement(v.a,{className:t.uploadIcon}),r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Drag & Drop an image here"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"or"),r.a.createElement(y.a,{variant:"contained",color:"primary",component:"span",startIcon:r.a.createElement(v.a,null)},"Browse Files"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",className:t.supportText},"Supported formats: JPG, PNG, GIF")))},O=t(139),N=t(140),B=t(166),T=t(141),I=t(64),k=t.n(I),P=t(76),D=t.n(P),A=t(77),F=t.n(A);const R=Object(C.a)(e=>({card:{height:"100%",display:"flex",flexDirection:"column"},selectedCard:{border:"2px solid #3f51b5"},unavailableCard:{opacity:.6},cardContent:{flexGrow:1},chipContainer:{marginBottom:e.spacing(1.5)},successChip:{backgroundColor:"#34C759",color:"#fff"},errorChip:{backgroundColor:"#FF3B3F",color:"#fff"},modelType:{marginTop:e.spacing(1)},processButton:{marginTop:e.spacing(3),textAlign:"center"}}));var z=e=>{let{onModelSelect:a,onProcess:t,isProcessing:n,modelsStatus:l,selectedModel:o,imageSelected:c}=e;const i=R(),m=[{id:"yolo",name:"YOLOv8",description:"Fast and accurate object detection",icon:r.a.createElement(k.a,null),available:l.yolo},{id:"detr",name:"DETR",description:"DEtection TRansformer for object detection",icon:r.a.createElement(k.a,null),available:l.detr},{id:"vit",name:"ViT",description:"Vision Transformer for image classification",icon:r.a.createElement(D.a,null),available:l.vit}],d=e=>{m.find(a=>a.id===e).available&&a(e)};return r.a.createElement(s.a,{sx:{p:2,height:"100%"}},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Select Model"),r.a.createElement(p.a,{container:!0,spacing:2},m.map(e=>r.a.createElement(p.a,{item:!0,xs:12,sm:4,key:e.id},r.a.createElement(O.a,{className:"\n ".concat(i.card," \n ").concat(o===e.id?i.selectedCard:""," \n ").concat(e.available?"":i.unavailableCard,"\n "),onClick:()=>d(e.id)},r.a.createElement(N.a,{className:i.cardContent},r.a.createElement(s.a,{sx:{mb:2,color:"primary"}},e.icon),r.a.createElement(u.a,{variant:"h5",component:"div",gutterBottom:!0},e.name),r.a.createElement("div",{className:i.chipContainer},e.available?r.a.createElement(B.a,{label:"Available",className:i.successChip,size:"small"}):r.a.createElement(B.a,{label:"Not Available",className:i.errorChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.description)),r.a.createElement(T.a,null,r.a.createElement(y.a,{size:"small",onClick:()=>d(e.id),disabled:!e.available,color:o===e.id?"primary":"default",variant:o===e.id?"contained":"outlined",fullWidth:!0},o===e.id?"Selected":"Select")))))),r.a.createElement("div",{className:i.processButton},r.a.createElement(y.a,{variant:"contained",color:"primary",size:"large",startIcon:r.a.createElement(F.a,null),onClick:t,disabled:!o||!c||n},n?"Processing...":"Process Image")))},L=t(153),M=t(150),W=t(105),_=t(154),J=t(142),H=t(164),U=t(163),V=t(145),G=t(146),Y=t(147),q=t(167),K=t(160),Q=t(151),X=t(169),Z=t(152),$=t(161);const ee=Object(C.a)(e=>({root:{marginTop:e.spacing(2),marginBottom:e.spacing(2),padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius},button:{marginRight:e.spacing(2)},searchDialog:{minWidth:"500px"},formControl:{marginBottom:e.spacing(2),minWidth:"100%"},searchResults:{marginTop:e.spacing(2)},resultCard:{marginBottom:e.spacing(2)},resultImage:{height:140,objectFit:"contain"},chip:{margin:e.spacing(.5)},similarityChip:{backgroundColor:e.palette.primary.main,color:"white"}}));var ae=e=>{let{results:a}=e;const t=ee(),[l,o]=Object(n.useState)(!1),[c,i]=Object(n.useState)(!1),[m,d]=Object(n.useState)(null),[g,E]=Object(n.useState)(!1),[b,f]=Object(n.useState)("image"),[v,x]=Object(n.useState)(""),[S,C]=Object(n.useState)([]),[j,w]=Object(n.useState)(!1),[T,I]=Object(n.useState)(null),{model:k,data:P}=a,D=()=>{E(!1)},A=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){const a=16*Math.random()|0;return("x"===e?a:3&a|8).toString(16)}));return r.a.createElement(s.a,{className:t.root},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Vector Database Actions"),r.a.createElement(s.a,{display:"flex",alignItems:"center",mb:2},r.a.createElement(y.a,{variant:"contained",color:"primary",onClick:async()=>{o(!0),d(null);try{let e;if(e="vit"===k?await fetch("/api/add-to-collection",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,metadata:{model:"vit",classifications:P.classifications}})}):await fetch("/api/add-detected-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:P.image,objects:P.detections,imageId:A()})}),!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const a=await e.json();if(a.error)throw new Error(a.error);i(!0),setTimeout(()=>i(!1),5e3)}catch(e){console.error("Error saving to vector DB:",e),d("Error saving to vector DB: ".concat(e.message))}finally{o(!1)}},disabled:l,className:t.button},l?r.a.createElement(r.a.Fragment,null,r.a.createElement(h.a,{size:20,color:"inherit",style:{marginRight:8}}),"Saving..."):"Save to Vector DB"),r.a.createElement(y.a,{variant:"outlined",color:"primary",onClick:()=>{E(!0),C([]),I(null)},className:t.button},"Search Similar")),m&&r.a.createElement($.a,{severity:"error",style:{marginTop:8}},m),r.a.createElement(H.a,{open:c,autoHideDuration:5e3,onClose:()=>i(!1)},r.a.createElement($.a,{severity:"success"},"vit"===k?"Image and classifications successfully saved to vector DB!":"Detected objects successfully saved to vector DB!")),r.a.createElement(U.a,{open:g,onClose:D,maxWidth:"md",fullWidth:!0},r.a.createElement(V.a,null,"Search Vector Database"),r.a.createElement(G.a,null,r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(q.a,{id:"search-type-label"},"Search Type"),r.a.createElement(K.a,{labelId:"search-type-label",id:"search-type",value:b,onChange:e=>{f(e.target.value),C([]),I(null)}},r.a.createElement(Q.a,{value:"image"},"Search by Current Image"),r.a.createElement(Q.a,{value:"class"},"Search by Class Name"))),"class"===b&&r.a.createElement(Y.a,{className:t.formControl},r.a.createElement(X.a,{label:"Class Name",value:v,onChange:e=>{x(e.target.value)},placeholder:"e.g. person, car, dog...",fullWidth:!0})),T&&r.a.createElement($.a,{severity:"error",style:{marginBottom:16}},T),r.a.createElement(s.a,{className:t.searchResults},j?r.a.createElement(s.a,{display:"flex",justifyContent:"center",alignItems:"center",p:4},r.a.createElement(h.a,null),r.a.createElement(u.a,{variant:"body1",style:{marginLeft:16}},"Searching...")):r.a.createElement(r.a.Fragment,null,console.log("Search dialog render - searchResults:",S),S.length>0?(console.log("Rendering search results:",S),console.log("Search results length:",S.length),0===S.length?(console.log("No results to render"),r.a.createElement(u.a,{variant:"body1"},"No results found.")):r.a.createElement(p.a,{container:!0,spacing:2},S.map((e,a)=>{const n=100*(1-e.distance);return r.a.createElement(p.a,{item:!0,xs:12,sm:6,key:a},r.a.createElement(O.a,{className:t.resultCard},e.metadata&&e.metadata.image_data?r.a.createElement(J.a,{className:t.resultImage,component:"img",height:"200",image:"data:image/jpeg;base64,".concat(e.metadata.image_data),alt:e.metadata&&e.metadata.class?e.metadata.class:"Object"}):r.a.createElement(s.a,{className:t.resultImage,style:{backgroundColor:"#f0f0f0",display:"flex",alignItems:"center",justifyContent:"center",height:200}},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},e.metadata&&e.metadata.class?e.metadata.class:"Object"," Image")),r.a.createElement(N.a,null,r.a.createElement(s.a,{display:"flex",justifyContent:"space-between",alignItems:"center",mb:1},r.a.createElement(u.a,{variant:"subtitle1"},"Result #",a+1),r.a.createElement(B.a,{label:"Similarity: ".concat(n.toFixed(2),"%"),className:t.similarityChip,size:"small"})),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Class:")," ",e.metadata.class||"N/A"),e.metadata.confidence&&r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Confidence:")," ",(100*e.metadata.confidence).toFixed(2),"%"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},r.a.createElement("strong",null,"Object ID:")," ",e.id))))}))):r.a.createElement(u.a,{variant:"body1"},"No results found. Please try another search.")))),r.a.createElement(Z.a,null,r.a.createElement(y.a,{onClick:D,color:"default"},"Close"),r.a.createElement(y.a,{onClick:async()=>{w(!0),I(null);try{let e={};if("image"===b)e={searchType:"image",image:P.image,n_results:5};else{if(!v.trim())throw new Error("Please enter a class name");e={searchType:"class",class_name:v.trim(),n_results:5}}const a=await fetch("/api/search-similar-objects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!a.ok)throw new Error("HTTP error! Status: ".concat(a.status));const t=await a.json();if(t.error)throw new Error(t.error);if(console.log("Search API response:",t),!t.success||!Array.isArray(t.results))throw console.error("Unexpected API response format:",t),new Error("Unexpected API response format");console.log("Setting search results array:",t.results),console.log("Results array length:",t.results.length),console.log("First result item:",t.results[0]),C(t.results)}catch(e){console.error("Error searching vector DB:",e),I("Error searching vector DB: ".concat(e.message))}finally{w(!1)}},color:"primary",variant:"contained",disabled:j||"class"===b&&!v.trim()},"Search"))))};const te=Object(C.a)(e=>({paper:{padding:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},resultImage:{maxWidth:"100%",maxHeight:"400px",objectFit:"contain"},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},chipContainer:{display:"flex",gap:e.spacing(1),flexWrap:"wrap"}}));var ne=e=>{let{results:a}=e;const t=te();if(!a)return null;const{model:n,data:l}=a;if(l.error)return r.a.createElement(E.a,{sx:{p:2,bgcolor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},l.error));const o=()=>l.performance?r.a.createElement(s.a,{className:"performance-info"},r.a.createElement(L.a,{className:t.dividerMargin}),r.a.createElement(u.a,{variant:"body2"},"Inference time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(l.performance.inference_time)," on ",l.performance.device)):null;return"yolo"===n||"detr"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"yolo"===n?"YOLOv8":"DETR"," Detection Results"),r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12,md:6},l.image&&r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detection Result"),r.a.createElement("img",{src:"data:image/png;base64,".concat(l.image),alt:"Detection Result",className:t.resultImage}))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(s.a,{className:t.marginBottom},r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Detected Objects:"),l.detections&&l.detections.length>0?r.a.createElement(M.a,null,l.detections.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(_.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.class),r.a.createElement(B.a,{label:"".concat((100*e.confidence).toFixed(0),"%"),size:"small",color:"primary",style:{marginLeft:8}})),secondary:"Bounding Box: [".concat(e.bbox.join(", "),"]")})),a<l.detections.length-1&&r.a.createElement(L.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No objects detected")))),o(),r.a.createElement(ae,{results:a})):"vit"===n?r.a.createElement(E.a,{className:t.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"ViT Classification Results"),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"Top Predictions:"),l.top_predictions&&l.top_predictions.length>0?r.a.createElement(M.a,null,l.top_predictions.map((e,a)=>r.a.createElement(r.a.Fragment,{key:a},r.a.createElement(W.a,null,r.a.createElement(_.a,{primary:r.a.createElement(s.a,{style:{display:"flex",alignItems:"center"}},r.a.createElement(u.a,{variant:"body1",component:"span"},e.rank,". ",e.class),r.a.createElement(B.a,{label:"".concat((100*e.probability).toFixed(1),"%"),size:"small",color:0===a?"primary":"default",style:{marginLeft:8}}))})),a<l.top_predictions.length-1&&r.a.createElement(L.a,null)))):r.a.createElement(u.a,{variant:"body1"},"No classifications available"),o(),r.a.createElement(ae,{results:a})):null};const re=Object(C.a)(e=>({paper:{padding:e.spacing(2),marginTop:e.spacing(2)},marginBottom:{marginBottom:e.spacing(2)},dividerMargin:{margin:"".concat(e.spacing(2),"px 0")},responseBox:{padding:e.spacing(2),backgroundColor:"#f5f5f5",borderRadius:e.shape.borderRadius,marginTop:e.spacing(2),whiteSpace:"pre-wrap"},buttonProgress:{marginLeft:e.spacing(1)}}));var le=e=>{let{visionResults:a,model:t}=e;const l=re(),[o,c]=Object(n.useState)(""),[i,m]=Object(n.useState)(!1),[d,g]=Object(n.useState)(null),[p,b]=Object(n.useState)(null);return a?r.a.createElement(E.a,{className:l.paper},r.a.createElement(u.a,{variant:"h6",gutterBottom:!0},"Ask AI about the ","vit"===t?"Classification":"Detection"," Results"),r.a.createElement(u.a,{variant:"body2",className:l.marginBottom},"Ask a question about the detected objects or classifications to get an AI-powered analysis."),r.a.createElement(X.a,{fullWidth:!0,label:"Your question about the image",variant:"outlined",value:o,onChange:e=>c(e.target.value),disabled:i,className:l.marginBottom,placeholder:"vit"===t?"E.g., What category does this image belong to?":"E.g., How many people are in this image?"}),r.a.createElement(y.a,{variant:"contained",color:"primary",onClick:async()=>{if(o.trim()){m(!0),b(null);try{const e=await fetch("/api/analyze",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({visionResults:a,userQuery:o})});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const t=await e.json();t.error?b(t.error):g(t)}catch(e){console.error("Error analyzing with LLM:",e),b("Error analyzing with LLM: ".concat(e.message))}finally{m(!1)}}},disabled:i||!o.trim()},"Analyze with AI",i&&r.a.createElement(h.a,{size:24,className:l.buttonProgress})),p&&r.a.createElement(s.a,{mt:2},r.a.createElement(u.a,{color:"error"},p)),d&&r.a.createElement(r.a.Fragment,null,r.a.createElement(L.a,{className:l.dividerMargin}),r.a.createElement(u.a,{variant:"subtitle1",gutterBottom:!0},"AI Analysis:"),r.a.createElement(s.a,{className:l.responseBox},r.a.createElement(u.a,{variant:"body1"},d.response)),d.performance&&r.a.createElement(s.a,{mt:1},r.a.createElement(u.a,{variant:"body2",color:"textSecondary"},"Analysis time: ",(e=>{if(void 0===e||null===e||isNaN(e))return"-";const a=Number(e);return a<1e3?"".concat(a.toFixed(2)," ms"):"".concat((a/1e3).toFixed(2)," s")})(d.performance.inference_time)," on ",d.performance.device)))):null};var oe=function(){const[e,a]=Object(n.useState)("gpt-4"),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)("You are a helpful assistant."),[i,s]=Object(n.useState)(""),[m,d]=Object(n.useState)(""),[g,h]=Object(n.useState)(!1),[b,f]=Object(n.useState)("");return r.a.createElement(E.a,{style:{padding:16}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"OpenAI Chat (OpenAI API)"),r.a.createElement(u.a,{variant:"body2",color:"textSecondary",gutterBottom:!0},"If the server env var OPENAI_API_KEY is set, the API Key field is optional."),r.a.createElement(p.a,{container:!0,spacing:2},r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"Model",value:e,onChange:e=>a(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(X.a,{label:"OpenAI API Key (optional)",value:t,onChange:e=>l(e.target.value),fullWidth:!0,variant:"outlined",size:"small",type:"password",placeholder:"sk-..."})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"System Prompt (optional)",value:o,onChange:e=>c(e.target.value),fullWidth:!0,variant:"outlined",size:"small"})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(X.a,{label:"User Question",value:i,onChange:e=>s(e.target.value),fullWidth:!0,multiline:!0,rows:4,variant:"outlined"})),b&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(u.a,{color:"error"},b)),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement("div",{style:{display:"flex",gap:8}},r.a.createElement(y.a,{color:"primary",variant:"contained",onClick:async()=>{f(""),d("");const a=(i||"").trim();if(a){h(!0);try{const r=await fetch("/api/openai/chat",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({prompt:a,model:(e||"").trim()||"gpt-4",api_key:t||void 0,system:o||void 0})});if(!r.ok){let e=await r.text();try{e=JSON.stringify(JSON.parse(e),null,2)}catch(n){}throw new Error(e)}const l=await r.json(),c="Model: ".concat(l.model," | Latency: ").concat(l.latency_sec,"s")+(l.usage?" | Usage: ".concat(JSON.stringify(l.usage)):"");d((l.response||"(Empty response)")+"\n\n---\n"+c)}catch(r){f("Error: "+r.message)}finally{h(!1)}}else f("Please enter a question.")},disabled:g},g?"Sending...":"Send Question"),r.a.createElement(y.a,{variant:"outlined",onClick:()=>{s(""),d(""),f("")}},"Clear"))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(L.a,{style:{margin:"12px 0"}}),r.a.createElement(u.a,{variant:"subtitle2",color:"textSecondary"},"Response"),r.a.createElement("pre",{style:{whiteSpace:"pre-wrap",fontFamily:"ui-monospace, monospace"}},m))))};t(102);const ce=Object(c.a)({palette:{primary:{main:"#3f51b5"},secondary:{main:"#f50057"}},typography:{fontFamily:"Roboto, Arial, sans-serif"}});var ie=function(){const[e,a]=Object(n.useState)(null),[t,l]=Object(n.useState)(""),[o,c]=Object(n.useState)(!1),[y,b]=Object(n.useState)(null),[f,v]=Object(n.useState)(null),[x,S]=Object(n.useState)({yolo:!1,detr:!1,vit:!1});return Object(n.useEffect)(()=>{fetch("/api/status").then(e=>e.json()).then(e=>{S(e.models)}).catch(e=>{console.error("Error checking API status:",e),v("Error connecting to the backend API. Please make sure the server is running.")})},[]),r.a.createElement(i.a,{theme:ce},r.a.createElement(s.a,{style:{flexGrow:1}},r.a.createElement(m.a,{position:"static"},r.a.createElement(d.a,null,r.a.createElement(u.a,{variant:"h6",style:{flexGrow:1}},"Multi-Model Object Detection Demo"))),r.a.createElement(g.a,{maxWidth:"lg",style:{marginTop:ce.spacing(4),marginBottom:ce.spacing(4)}},r.a.createElement(p.a,{container:!0,spacing:3},r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2)}},r.a.createElement(u.a,{variant:"h5",gutterBottom:!0},"Upload an image to see how each model performs!"),r.a.createElement(u.a,{variant:"body1",paragraph:!0},"This demo showcases three different object detection and image classification models:"),r.a.createElement(u.a,{variant:"body1",component:"div"},r.a.createElement("ul",null,r.a.createElement("li",null,r.a.createElement("strong",null,"YOLOv8"),": Fast and accurate object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"DETR"),": DEtection TRansformer for object detection"),r.a.createElement("li",null,r.a.createElement("strong",null,"ViT"),": Vision Transformer for image classification"))))),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(w,{onImageUpload:e=>{a(e),b(null),v(null)}})),r.a.createElement(p.a,{item:!0,xs:12,md:6},r.a.createElement(z,{onModelSelect:e=>{l(e),b(null),v(null)},onProcess:async()=>{if(!e||!t)return void v("Please select both an image and a model");c(!0),v(null);const a=new FormData;a.append("image",e);let n="";switch(t){case"yolo":n="/api/detect/yolo";break;case"detr":n="/api/detect/detr";break;case"vit":n="/api/classify/vit";break;default:return v("Invalid model selection"),void c(!1)}try{const e=await fetch(n,{method:"POST",body:a});if(!e.ok)throw new Error("HTTP error! Status: ".concat(e.status));const r=await e.json();b({model:t,data:r})}catch(r){console.error("Error processing image:",r),v("Error processing image: ".concat(r.message))}finally{c(!1)}},isProcessing:o,modelsStatus:x,selectedModel:t,imageSelected:!!e})),f&&r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(E.a,{style:{padding:ce.spacing(2),backgroundColor:"#ffebee"}},r.a.createElement(u.a,{color:"error"},f))),o&&r.a.createElement(p.a,{item:!0,xs:12,style:{textAlign:"center",margin:"".concat(ce.spacing(4),"px 0")}},r.a.createElement(h.a,null),r.a.createElement(u.a,{variant:"h6",style:{marginTop:ce.spacing(2)}},"Processing image...")),y&&r.a.createElement(r.a.Fragment,null,r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(ne,{results:y})),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(le,{visionResults:y.data,model:y.model}))),r.a.createElement(p.a,{item:!0,xs:12},r.a.createElement(oe,null))))))};var se=e=>{e&&e instanceof Function&&t.e(3).then(t.bind(null,170)).then(a=>{let{getCLS:t,getFID:n,getFCP:r,getLCP:l,getTTFB:o}=a;t(e),n(e),r(e),l(e),o(e)})};o.a.render(r.a.createElement(r.a.StrictMode,null,r.a.createElement(ie,null)),document.getElementById("root")),se()},92:function(e,a,t){e.exports=t(103)},97:function(e,a,t){}},[[92,1,2]]]);
|
2 |
-
//# sourceMappingURL=main.fc0e2dc7.chunk.js.map
|
|
|
|
|
|
static_bak_20250820_085943/static/js/main.fc0e2dc7.chunk.js.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["components/ImageUploader.js","components/ModelSelector.js","components/VectorDBActions.js","components/ResultDisplay.js","components/LlmAnalysis.js","components/OpenAIChat.js","App.js","reportWebVitals.js","index.js"],"names":["useStyles","makeStyles","theme","paper","padding","spacing","display","flexDirection","alignItems","height","minHeight","transition","dragActive","border","backgroundColor","dragInactive","uploadBox","justifyContent","width","cursor","uploadIcon","fontSize","color","marginBottom","supportText","marginTop","previewBox","position","imageContainer","breakpoints","down","overflow","deleteButton","top","right","ImageUploader","_ref","onImageUpload","classes","previewUrl","setPreviewUrl","useState","setDragActive","fileInputRef","useRef","handleDrag","e","preventDefault","stopPropagation","type","handleFiles","file","startsWith","URL","createObjectURL","alert","React","createElement","Paper","className","concat","onDragEnter","onDragLeave","onDragOver","onDrop","dataTransfer","files","ref","accept","onChange","target","style","Box","Typography","variant","gutterBottom","src","alt","maxHeight","objectFit","IconButton","aria-label","onClick","handleRemoveImage","current","value","DeleteIcon","onButtonClick","click","CloudUploadIcon","Button","component","startIcon","card","selectedCard","unavailableCard","opacity","cardContent","flexGrow","chipContainer","successChip","errorChip","modelType","processButton","textAlign","ModelSelector","onModelSelect","onProcess","isProcessing","modelsStatus","selectedModel","imageSelected","models","id","name","description","icon","VisibilityIcon","available","yolo","detr","CategoryIcon","vit","handleModelClick","modelId","find","m","sx","p","Grid","container","map","model","item","xs","sm","key","Card","CardContent","mb","Chip","label","size","CardActions","disabled","fullWidth","PlayArrowIcon","root","borderRadius","shape","button","marginRight","searchDialog","minWidth","formControl","searchResults","resultCard","resultImage","chip","margin","similarityChip","palette","primary","main","VectorDBActions","results","isSaving","setIsSaving","saveSuccess","setSaveSuccess","saveError","setSaveError","openSearchDialog","setOpenSearchDialog","searchType","setSearchType","searchClass","setSearchClass","setSearchResults","isSearching","setIsSearching","searchError","setSearchError","data","handleCloseSearchDialog","generateUUID","replace","c","r","Math","random","toString","async","response","fetch","method","headers","body","JSON","stringify","image","metadata","classifications","objects","detections","imageId","ok","Error","status","result","json","error","setTimeout","err","console","message","Fragment","CircularProgress","handleOpenSearchDialog","Alert","severity","Snackbar","open","autoHideDuration","onClose","Dialog","maxWidth","DialogTitle","DialogContent","FormControl","InputLabel","Select","labelId","event","MenuItem","TextField","placeholder","marginLeft","log","length","index","similarity","distance","image_data","CardMedia","class","toFixed","confidence","DialogActions","requestBody","n_results","trim","class_name","success","Array","isArray","dividerMargin","gap","flexWrap","ResultDisplay","bgcolor","renderPerformanceInfo","performance","Divider","ms","undefined","isNaN","num","Number","formatTime","inference_time","device","md","List","detection","ListItem","ListItemText","secondary","bbox","join","top_predictions","prediction","rank","probability","responseBox","whiteSpace","buttonProgress","LlmAnalysis","visionResults","userQuery","setUserQuery","isAnalyzing","setIsAnalyzing","analysisResult","setAnalysisResult","setError","mt","OpenAIChat","setModel","apiKey","setApiKey","system","setSystem","prompt","setPrompt","setResponse","loading","setLoading","multiline","rows","res","credentials","api_key","txt","text","parse","_unused","meta","latency_sec","usage","onClear","fontFamily","createMuiTheme","typography","App","selectedImage","setSelectedImage","setSelectedModel","setIsProcessing","setResults","setModelsStatus","useEffect","then","catch","ThemeProvider","AppBar","Toolbar","Container","paragraph","formData","FormData","append","endpoint","reportWebVitals","onPerfEntry","Function","getCLS","getFID","getFCP","getLCP","getTTFB","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"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","file":"static/js/main.fc0e2dc7.chunk.js","sourcesContent":["import React, { useState, useRef } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n Button, \n IconButton \n} from '@material-ui/core';\nimport CloudUploadIcon from '@material-ui/icons/CloudUpload';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n height: '100%',\n minHeight: 300,\n transition: 'all 0.3s ease'\n },\n dragActive: {\n border: '2px dashed #3f51b5',\n backgroundColor: 'rgba(63, 81, 181, 0.05)'\n },\n dragInactive: {\n border: '2px dashed #ccc',\n backgroundColor: 'white'\n },\n uploadBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n width: '100%',\n cursor: 'pointer'\n },\n uploadIcon: {\n fontSize: 60,\n color: '#3f51b5',\n marginBottom: theme.spacing(2)\n },\n supportText: {\n marginTop: theme.spacing(2)\n },\n previewBox: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n position: 'relative'\n },\n imageContainer: {\n position: 'relative',\n width: '100%',\n // Use viewport-based height so any aspect ratio fits inside\n height: '60vh',\n [theme.breakpoints.down('sm')]: {\n height: '45vh',\n },\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n overflow: 'hidden',\n marginTop: theme.spacing(2),\n },\n deleteButton: {\n position: 'absolute',\n top: 0,\n right: 0,\n backgroundColor: 'rgba(255, 255, 255, 0.7)',\n '&:hover': {\n backgroundColor: 'rgba(255, 255, 255, 0.9)',\n }\n }\n}));\n\nconst ImageUploader = ({ onImageUpload }) => {\n const classes = useStyles();\n const [previewUrl, setPreviewUrl] = useState(null);\n const [dragActive, setDragActive] = useState(false);\n const fileInputRef = useRef(null);\n\n const handleDrag = (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (e.type === 'dragenter' || e.type === 'dragover') {\n setDragActive(true);\n } else if (e.type === 'dragleave') {\n setDragActive(false);\n }\n };\n\n const handleDrop = (e) => {\n e.preventDefault();\n e.stopPropagation();\n setDragActive(false);\n if (e.dataTransfer.files && e.dataTransfer.files[0]) {\n handleFiles(e.dataTransfer.files[0]);\n }\n };\n\n const handleChange = (e) => {\n e.preventDefault();\n if (e.target.files && e.target.files[0]) {\n handleFiles(e.target.files[0]);\n }\n };\n\n const handleFiles = (file) => {\n if (file.type.startsWith('image/')) {\n setPreviewUrl(URL.createObjectURL(file));\n onImageUpload(file);\n } else {\n alert('Please upload an image file');\n }\n };\n\n const onButtonClick = () => {\n fileInputRef.current.click();\n };\n\n const handleRemoveImage = () => {\n setPreviewUrl(null);\n onImageUpload(null);\n fileInputRef.current.value = \"\";\n };\n\n return (\n <Paper \n className={`${classes.paper} ${dragActive ? classes.dragActive : classes.dragInactive}`}\n onDragEnter={handleDrag}\n onDragLeave={handleDrag}\n onDragOver={handleDrag}\n onDrop={handleDrop}\n >\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleChange}\n style={{ display: 'none' }}\n />\n\n {!previewUrl ? (\n <Box \n className={classes.uploadBox}\n onClick={onButtonClick}\n >\n <CloudUploadIcon className={classes.uploadIcon} />\n <Typography variant=\"h6\" gutterBottom>\n Drag & Drop an image here\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n or\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n component=\"span\"\n startIcon={<CloudUploadIcon />}\n >\n Browse Files\n </Button>\n <Typography variant=\"body2\" color=\"textSecondary\" className={classes.supportText}>\n Supported formats: JPG, PNG, GIF\n </Typography>\n </Box>\n ) : (\n <Box className={classes.previewBox}>\n <Typography variant=\"h6\" gutterBottom>\n Preview\n </Typography>\n <Box className={classes.imageContainer}>\n <img\n src={previewUrl}\n alt=\"Preview\"\n className=\"preview-image\"\n style={{\n width: '100%',\n height: 'auto',\n maxHeight: '100%',\n objectFit: 'contain',\n display: 'block'\n }}\n />\n <IconButton\n aria-label=\"delete\"\n className={classes.deleteButton}\n onClick={handleRemoveImage}\n >\n <DeleteIcon />\n </IconButton>\n </Box>\n </Box>\n )}\n </Paper>\n );\n};\n\nexport default ImageUploader;\n","import React from 'react';\nimport { \n Grid, \n Card, \n CardContent, \n CardActions, \n Typography, \n Button, \n Chip,\n Box\n} from '@material-ui/core';\nimport VisibilityIcon from '@material-ui/icons/Visibility';\nimport CategoryIcon from '@material-ui/icons/Category';\nimport PlayArrowIcon from '@material-ui/icons/PlayArrow';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n card: {\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n },\n selectedCard: {\n border: '2px solid #3f51b5',\n },\n unavailableCard: {\n opacity: 0.6,\n },\n cardContent: {\n flexGrow: 1,\n },\n chipContainer: {\n marginBottom: theme.spacing(1.5),\n },\n successChip: {\n backgroundColor: '#34C759',\n color: '#fff',\n },\n errorChip: {\n backgroundColor: '#FF3B3F',\n color: '#fff',\n },\n modelType: {\n marginTop: theme.spacing(1),\n },\n processButton: {\n marginTop: theme.spacing(3),\n textAlign: 'center',\n }\n}));\n\nconst ModelSelector = ({ \n onModelSelect, \n onProcess, \n isProcessing, \n modelsStatus, \n selectedModel,\n imageSelected \n}) => {\n const classes = useStyles();\n \n const models = [\n {\n id: 'yolo',\n name: 'YOLOv8',\n description: 'Fast and accurate object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.yolo\n },\n {\n id: 'detr',\n name: 'DETR',\n description: 'DEtection TRansformer for object detection',\n icon: <VisibilityIcon />,\n available: modelsStatus.detr\n },\n {\n id: 'vit',\n name: 'ViT',\n description: 'Vision Transformer for image classification',\n icon: <CategoryIcon />,\n available: modelsStatus.vit\n }\n ];\n\n const handleModelClick = (modelId) => {\n if (models.find(m => m.id === modelId).available) {\n onModelSelect(modelId);\n }\n };\n\n return (\n <Box sx={{ p: 2, height: '100%' }}>\n <Typography variant=\"h6\" gutterBottom>\n Select Model\n </Typography>\n \n <Grid container spacing={2}>\n {models.map((model) => (\n <Grid item xs={12} sm={4} key={model.id}>\n <Card \n className={`\n ${classes.card} \n ${selectedModel === model.id ? classes.selectedCard : ''} \n ${!model.available ? classes.unavailableCard : ''}\n `}\n onClick={() => handleModelClick(model.id)}\n >\n <CardContent className={classes.cardContent}>\n <Box sx={{ mb: 2, color: 'primary' }}>\n {model.icon}\n </Box>\n <Typography variant=\"h5\" component=\"div\" gutterBottom>\n {model.name}\n </Typography>\n <div className={classes.chipContainer}>\n {model.available ? (\n <Chip \n label=\"Available\" \n className={classes.successChip}\n size=\"small\" \n />\n ) : (\n <Chip \n label=\"Not Available\" \n className={classes.errorChip}\n size=\"small\" \n />\n )}\n </div>\n <Typography variant=\"body2\" color=\"textSecondary\">\n {model.description}\n </Typography>\n </CardContent>\n <CardActions>\n <Button \n size=\"small\" \n onClick={() => handleModelClick(model.id)}\n disabled={!model.available}\n color={selectedModel === model.id ? \"primary\" : \"default\"}\n variant={selectedModel === model.id ? \"contained\" : \"outlined\"}\n fullWidth\n >\n {selectedModel === model.id ? 'Selected' : 'Select'}\n </Button>\n </CardActions>\n </Card>\n </Grid>\n ))}\n </Grid>\n\n <div className={classes.processButton}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"large\"\n startIcon={<PlayArrowIcon />}\n onClick={onProcess}\n disabled={!selectedModel || !imageSelected || isProcessing}\n >\n {isProcessing ? 'Processing...' : 'Process Image'}\n </Button>\n </div>\n </Box>\n );\n};\n\nexport default ModelSelector;\n","import React, { useState } from 'react';\nimport { \n Button, \n Box, \n Typography, \n CircularProgress, \n Snackbar,\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n TextField,\n FormControl,\n InputLabel,\n Select,\n MenuItem,\n Grid,\n Card,\n CardMedia,\n CardContent,\n Chip\n} from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n marginTop: theme.spacing(2),\n marginBottom: theme.spacing(2),\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n },\n button: {\n marginRight: theme.spacing(2),\n },\n searchDialog: {\n minWidth: '500px',\n },\n formControl: {\n marginBottom: theme.spacing(2),\n minWidth: '100%',\n },\n searchResults: {\n marginTop: theme.spacing(2),\n },\n resultCard: {\n marginBottom: theme.spacing(2),\n },\n resultImage: {\n height: 140,\n objectFit: 'contain',\n },\n chip: {\n margin: theme.spacing(0.5),\n },\n similarityChip: {\n backgroundColor: theme.palette.primary.main,\n color: 'white',\n }\n}));\n\nconst VectorDBActions = ({ results }) => {\n const classes = useStyles();\n const [isSaving, setIsSaving] = useState(false);\n const [saveSuccess, setSaveSuccess] = useState(false);\n const [saveError, setSaveError] = useState(null);\n const [openSearchDialog, setOpenSearchDialog] = useState(false);\n const [searchType, setSearchType] = useState('image');\n const [searchClass, setSearchClass] = useState('');\n const [searchResults, setSearchResults] = useState([]);\n const [isSearching, setIsSearching] = useState(false);\n const [searchError, setSearchError] = useState(null);\n \n // Extract model and data from results\n const { model, data } = results;\n \n // Handle saving to vector DB\n const handleSaveToVectorDB = async () => {\n setIsSaving(true);\n setSaveError(null);\n \n try {\n let response;\n \n if (model === 'vit') {\n // For ViT, save the whole image with classifications\n response = await fetch('/api/add-to-collection', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n metadata: {\n model: 'vit',\n classifications: data.classifications\n }\n })\n });\n } else {\n // For YOLO and DETR, save detected objects\n response = await fetch('/api/add-detected-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n image: data.image,\n objects: data.detections,\n imageId: generateUUID()\n })\n });\n }\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n setSaveSuccess(true);\n setTimeout(() => setSaveSuccess(false), 5000);\n } catch (err) {\n console.error('Error saving to vector DB:', err);\n setSaveError(`Error saving to vector DB: ${err.message}`);\n } finally {\n setIsSaving(false);\n }\n };\n \n // Handle opening search dialog\n const handleOpenSearchDialog = () => {\n setOpenSearchDialog(true);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle closing search dialog\n const handleCloseSearchDialog = () => {\n setOpenSearchDialog(false);\n };\n \n // Handle search type change\n const handleSearchTypeChange = (event) => {\n setSearchType(event.target.value);\n setSearchResults([]);\n setSearchError(null);\n };\n \n // Handle search class change\n const handleSearchClassChange = (event) => {\n setSearchClass(event.target.value);\n };\n \n // Handle search\n const handleSearch = async () => {\n setIsSearching(true);\n setSearchError(null);\n \n try {\n let requestBody = {};\n \n if (searchType === 'image') {\n // Search by current image\n requestBody = {\n searchType: 'image',\n image: data.image,\n n_results: 5\n };\n } else {\n // Search by class name\n if (!searchClass.trim()) {\n throw new Error('Please enter a class name');\n }\n \n requestBody = {\n searchType: 'class',\n class_name: searchClass.trim(),\n n_results: 5\n };\n }\n \n const response = await fetch('/api/search-similar-objects', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody)\n });\n \n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n \n const result = await response.json();\n \n if (result.error) {\n throw new Error(result.error);\n }\n \n console.log('Search API response:', result);\n \n // The backend responds with {success, searchType, results} structure, so extract only the results array\n if (result.success && Array.isArray(result.results)) {\n console.log('Setting search results array:', result.results);\n console.log('Results array length:', result.results.length);\n console.log('First result item:', result.results[0]);\n setSearchResults(result.results);\n } else {\n console.error('Unexpected API response format:', result);\n throw new Error('Unexpected API response format');\n }\n } catch (err) {\n console.error('Error searching vector DB:', err);\n setSearchError(`Error searching vector DB: ${err.message}`);\n } finally {\n setIsSearching(false);\n }\n };\n \n // Generate UUID for image ID\n const generateUUID = () => {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n };\n \n // Render search results\n const renderSearchResults = () => {\n console.log('Rendering search results:', searchResults);\n console.log('Search results length:', searchResults.length);\n \n if (searchResults.length === 0) {\n console.log('No results to render');\n return (\n <Typography variant=\"body1\">No results found.</Typography>\n );\n }\n \n return (\n <Grid container spacing={2}>\n {searchResults.map((result, index) => {\n const similarity = (1 - result.distance) * 100;\n \n return (\n <Grid item xs={12} sm={6} key={index}>\n <Card className={classes.resultCard}>\n {result.metadata && result.metadata.image_data ? (\n <CardMedia\n className={classes.resultImage}\n component=\"img\"\n height=\"200\"\n image={`data:image/jpeg;base64,${result.metadata.image_data}`}\n alt={result.metadata && result.metadata.class ? result.metadata.class : 'Object'}\n />\n ) : (\n <Box \n className={classes.resultImage}\n style={{ \n backgroundColor: '#f0f0f0', \n display: 'flex', \n alignItems: 'center', \n justifyContent: 'center',\n height: 200\n }}\n >\n <Typography variant=\"body2\" color=\"textSecondary\">\n {result.metadata && result.metadata.class ? result.metadata.class : 'Object'} Image\n </Typography>\n </Box>\n )}\n <CardContent>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\" mb={1}>\n <Typography variant=\"subtitle1\">Result #{index + 1}</Typography>\n <Chip \n label={`Similarity: ${similarity.toFixed(2)}%`}\n className={classes.similarityChip}\n size=\"small\"\n />\n </Box>\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Class:</strong> {result.metadata.class || 'N/A'}\n </Typography>\n {result.metadata.confidence && (\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Confidence:</strong> {(result.metadata.confidence * 100).toFixed(2)}%\n </Typography>\n )}\n <Typography variant=\"body2\" color=\"textSecondary\">\n <strong>Object ID:</strong> {result.id}\n </Typography>\n </CardContent>\n </Card>\n </Grid>\n );\n })}\n </Grid>\n );\n };\n \n return (\n <Box className={classes.root}>\n <Typography variant=\"h6\" gutterBottom>\n Vector Database Actions\n </Typography>\n \n <Box display=\"flex\" alignItems=\"center\" mb={2}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={handleSaveToVectorDB}\n disabled={isSaving}\n className={classes.button}\n >\n {isSaving ? (\n <>\n <CircularProgress size={20} color=\"inherit\" style={{ marginRight: 8 }} />\n Saving...\n </>\n ) : (\n 'Save to Vector DB'\n )}\n </Button>\n \n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={handleOpenSearchDialog}\n className={classes.button}\n >\n Search Similar\n </Button>\n </Box>\n \n {saveError && (\n <Alert severity=\"error\" style={{ marginTop: 8 }}>\n {saveError}\n </Alert>\n )}\n \n <Snackbar open={saveSuccess} autoHideDuration={5000} onClose={() => setSaveSuccess(false)}>\n <Alert severity=\"success\">\n {model === 'vit' ? (\n 'Image and classifications successfully saved to vector DB!'\n ) : (\n 'Detected objects successfully saved to vector DB!'\n )}\n </Alert>\n </Snackbar>\n \n {/* Search Dialog */}\n <Dialog\n open={openSearchDialog}\n onClose={handleCloseSearchDialog}\n maxWidth=\"md\"\n fullWidth\n >\n <DialogTitle>Search Vector Database</DialogTitle>\n <DialogContent>\n <FormControl className={classes.formControl}>\n <InputLabel id=\"search-type-label\">Search Type</InputLabel>\n <Select\n labelId=\"search-type-label\"\n id=\"search-type\"\n value={searchType}\n onChange={handleSearchTypeChange}\n >\n <MenuItem value=\"image\">Search by Current Image</MenuItem>\n <MenuItem value=\"class\">Search by Class Name</MenuItem>\n </Select>\n </FormControl>\n \n {searchType === 'class' && (\n <FormControl className={classes.formControl}>\n <TextField\n label=\"Class Name\"\n value={searchClass}\n onChange={handleSearchClassChange}\n placeholder=\"e.g. person, car, dog...\"\n fullWidth\n />\n </FormControl>\n )}\n \n {searchError && (\n <Alert severity=\"error\" style={{ marginBottom: 16 }}>\n {searchError}\n </Alert>\n )}\n \n <Box className={classes.searchResults}>\n {isSearching ? (\n <Box display=\"flex\" justifyContent=\"center\" alignItems=\"center\" p={4}>\n <CircularProgress />\n <Typography variant=\"body1\" style={{ marginLeft: 16 }}>\n Searching...\n </Typography>\n </Box>\n ) : (\n <>\n {console.log('Search dialog render - searchResults:', searchResults)}\n {searchResults.length > 0 ? renderSearchResults() : \n <Typography variant=\"body1\">No results found. Please try another search.</Typography>\n }\n </>\n )}\n </Box>\n </DialogContent>\n <DialogActions>\n <Button onClick={handleCloseSearchDialog} color=\"default\">\n Close\n </Button>\n <Button \n onClick={handleSearch} \n color=\"primary\" \n variant=\"contained\"\n disabled={isSearching || (searchType === 'class' && !searchClass.trim())}\n >\n Search\n </Button>\n </DialogActions>\n </Dialog>\n </Box>\n );\n};\n\nexport default VectorDBActions;\n","import React from 'react';\nimport { \n Paper, \n Typography, \n Box, \n List, \n ListItem, \n ListItemText, \n Divider,\n Grid,\n Chip\n} from '@material-ui/core';\nimport VectorDBActions from './VectorDBActions';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n resultImage: {\n maxWidth: '100%',\n maxHeight: '400px',\n objectFit: 'contain'\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n chipContainer: {\n display: 'flex',\n gap: theme.spacing(1),\n flexWrap: 'wrap'\n }\n}));\n\nconst ResultDisplay = ({ results }) => {\n const classes = useStyles();\n if (!results) return null;\n \n const { model, data } = results;\n \n // Helper to format times nicely\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n \n // Check if there's an error\n if (data.error) {\n return (\n <Paper sx={{ p: 2, bgcolor: '#ffebee' }}>\n <Typography color=\"error\">{data.error}</Typography>\n </Paper>\n );\n }\n\n // Display performance info\n const renderPerformanceInfo = () => {\n if (!data.performance) return null;\n \n return (\n <Box className=\"performance-info\">\n <Divider className={classes.dividerMargin} />\n <Typography variant=\"body2\">\n Inference time: {formatTime(data.performance.inference_time)} on {data.performance.device}\n </Typography>\n </Box>\n );\n };\n\n // Render for YOLO and DETR (object detection)\n if (model === 'yolo' || model === 'detr') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n {model === 'yolo' ? 'YOLOv8' : 'DETR'} Detection Results\n </Typography>\n \n <Grid container spacing={3}>\n <Grid item xs={12} md={6}>\n {data.image && (\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detection Result\n </Typography>\n <img \n src={`data:image/png;base64,${data.image}`} \n alt=\"Detection Result\" \n className={classes.resultImage}\n />\n </Box>\n )}\n </Grid>\n \n <Grid item xs={12} md={6}>\n <Box className={classes.marginBottom}>\n <Typography variant=\"subtitle1\" gutterBottom>\n Detected Objects:\n </Typography>\n \n {data.detections && data.detections.length > 0 ? (\n <List>\n {data.detections.map((detection, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {detection.class}\n </Typography>\n <Chip \n label={`${(detection.confidence * 100).toFixed(0)}%`}\n size=\"small\"\n color=\"primary\"\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n secondary={`Bounding Box: [${detection.bbox.join(', ')}]`} \n />\n </ListItem>\n {index < data.detections.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No objects detected</Typography>\n )}\n </Box>\n </Grid>\n </Grid>\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for Object Detection */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n // Render for ViT (classification)\n if (model === 'vit') {\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n ViT Classification Results\n </Typography>\n \n <Typography variant=\"subtitle1\" gutterBottom>\n Top Predictions:\n </Typography>\n \n {data.top_predictions && data.top_predictions.length > 0 ? (\n <List>\n {data.top_predictions.map((prediction, index) => (\n <React.Fragment key={index}>\n <ListItem>\n <ListItemText \n primary={\n <Box style={{ display: 'flex', alignItems: 'center' }}>\n <Typography variant=\"body1\" component=\"span\">\n {prediction.rank}. {prediction.class}\n </Typography>\n <Chip \n label={`${(prediction.probability * 100).toFixed(1)}%`}\n size=\"small\"\n color={index === 0 ? \"primary\" : \"default\"}\n style={{ marginLeft: 8 }}\n />\n </Box>\n } \n />\n </ListItem>\n {index < data.top_predictions.length - 1 && <Divider />}\n </React.Fragment>\n ))}\n </List>\n ) : (\n <Typography variant=\"body1\">No classifications available</Typography>\n )}\n \n {renderPerformanceInfo()}\n \n {/* Vector DB Actions for ViT Classification */}\n <VectorDBActions results={results} />\n </Paper>\n );\n }\n \n return null;\n};\n\nexport default ResultDisplay;\n","import React, { useState } from 'react';\nimport { \n Paper, \n Typography, \n Box, \n TextField, \n Button, \n CircularProgress,\n Divider\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n padding: theme.spacing(2),\n marginTop: theme.spacing(2)\n },\n marginBottom: {\n marginBottom: theme.spacing(2)\n },\n dividerMargin: {\n margin: `${theme.spacing(2)}px 0`\n },\n responseBox: {\n padding: theme.spacing(2),\n backgroundColor: '#f5f5f5',\n borderRadius: theme.shape.borderRadius,\n marginTop: theme.spacing(2),\n whiteSpace: 'pre-wrap'\n },\n buttonProgress: {\n marginLeft: theme.spacing(1)\n }\n}));\n\nconst LlmAnalysis = ({ visionResults, model }) => {\n const classes = useStyles();\n const [userQuery, setUserQuery] = useState('');\n const [isAnalyzing, setIsAnalyzing] = useState(false);\n const [analysisResult, setAnalysisResult] = useState(null);\n const [error, setError] = useState(null);\n\n // Format time for display\n const formatTime = (ms) => {\n if (ms === undefined || ms === null || isNaN(ms)) return '-';\n const num = Number(ms);\n if (num < 1000) return `${num.toFixed(2)} ms`;\n return `${(num / 1000).toFixed(2)} s`;\n };\n\n const handleAnalyze = async () => {\n if (!userQuery.trim()) return;\n \n setIsAnalyzing(true);\n setError(null);\n \n try {\n const response = await fetch('/api/analyze', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n visionResults: visionResults,\n userQuery: userQuery\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n \n if (data.error) {\n setError(data.error);\n } else {\n setAnalysisResult(data);\n }\n } catch (err) {\n console.error('Error analyzing with LLM:', err);\n setError(`Error analyzing with LLM: ${err.message}`);\n } finally {\n setIsAnalyzing(false);\n }\n };\n\n if (!visionResults) return null;\n\n return (\n <Paper className={classes.paper}>\n <Typography variant=\"h6\" gutterBottom>\n Ask AI about the {model === 'vit' ? 'Classification' : 'Detection'} Results\n </Typography>\n \n <Typography variant=\"body2\" className={classes.marginBottom}>\n Ask a question about the detected objects or classifications to get an AI-powered analysis.\n </Typography>\n \n <TextField\n fullWidth\n label=\"Your question about the image\"\n variant=\"outlined\"\n value={userQuery}\n onChange={(e) => setUserQuery(e.target.value)}\n disabled={isAnalyzing}\n className={classes.marginBottom}\n placeholder={model === 'vit' \n ? \"E.g., What category does this image belong to?\" \n : \"E.g., How many people are in this image?\"}\n />\n \n <Button \n variant=\"contained\" \n color=\"primary\"\n onClick={handleAnalyze}\n disabled={isAnalyzing || !userQuery.trim()}\n >\n Analyze with AI\n {isAnalyzing && <CircularProgress size={24} className={classes.buttonProgress} />}\n </Button>\n \n {error && (\n <Box mt={2}>\n <Typography color=\"error\">{error}</Typography>\n </Box>\n )}\n \n {analysisResult && (\n <>\n <Divider className={classes.dividerMargin} />\n \n <Typography variant=\"subtitle1\" gutterBottom>\n AI Analysis:\n </Typography>\n \n <Box className={classes.responseBox}>\n <Typography variant=\"body1\">\n {analysisResult.response}\n </Typography>\n </Box>\n \n {analysisResult.performance && (\n <Box mt={1}>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Analysis time: {formatTime(analysisResult.performance.inference_time)} on {analysisResult.performance.device}\n </Typography>\n </Box>\n )}\n </>\n )}\n </Paper>\n );\n};\n\nexport default LlmAnalysis;\n","import React, { useState } from 'react';\nimport { \n Paper,\n Typography,\n Grid,\n TextField,\n Button,\n Divider\n} from '@material-ui/core';\n\nfunction OpenAIChat() {\n const [model, setModel] = useState('gpt-4');\n const [apiKey, setApiKey] = useState('');\n const [system, setSystem] = useState('You are a helpful assistant.');\n const [prompt, setPrompt] = useState('');\n const [response, setResponse] = useState('');\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState('');\n\n const onSend = async () => {\n setError('');\n setResponse('');\n const p = (prompt || '').trim();\n if (!p) { setError('Please enter a question.'); return; }\n\n setLoading(true);\n try {\n const res = await fetch('/api/openai/chat', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({\n prompt: p,\n model: (model || '').trim() || 'gpt-4',\n api_key: (apiKey || undefined),\n system: (system || undefined)\n })\n });\n\n if (!res.ok) {\n let txt = await res.text();\n try { txt = JSON.stringify(JSON.parse(txt), null, 2); } catch {}\n throw new Error(txt);\n }\n const data = await res.json();\n const meta = `Model: ${data.model} | Latency: ${data.latency_sec}s` + (data.usage ? ` | Usage: ${JSON.stringify(data.usage)}` : '');\n setResponse((data.response || '(Empty response)') + '\\n\\n---\\n' + meta);\n } catch (e) {\n setError('Error: ' + e.message);\n } finally {\n setLoading(false);\n }\n };\n\n const onClear = () => {\n setPrompt('');\n setResponse('');\n setError('');\n };\n\n return (\n <Paper style={{ padding: 16 }}>\n <Typography variant=\"h5\" gutterBottom>\n OpenAI Chat (OpenAI API)\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n If the server env var OPENAI_API_KEY is set, the API Key field is optional.\n </Typography>\n <Grid container spacing={2}>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"Model\"\n value={model}\n onChange={(e) => setModel(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n <Grid item xs={12} md={6}>\n <TextField\n label=\"OpenAI API Key (optional)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n type=\"password\"\n placeholder=\"sk-...\"\n />\n </Grid>\n <Grid item xs={12}>\n <TextField\n label=\"System Prompt (optional)\"\n value={system}\n onChange={(e) => setSystem(e.target.value)}\n fullWidth\n variant=\"outlined\"\n size=\"small\"\n />\n </Grid>\n <Grid item xs={12}>\n <TextField\n label=\"User Question\"\n value={prompt}\n onChange={(e) => setPrompt(e.target.value)}\n fullWidth\n multiline\n rows={4}\n variant=\"outlined\"\n />\n </Grid>\n {error && (\n <Grid item xs={12}>\n <Typography color=\"error\">{error}</Typography>\n </Grid>\n )}\n <Grid item xs={12}>\n <div style={{ display: 'flex', gap: 8 }}>\n <Button color=\"primary\" variant=\"contained\" onClick={onSend} disabled={loading}>\n {loading ? 'Sending...' : 'Send Question'}\n </Button>\n <Button variant=\"outlined\" onClick={onClear}>Clear</Button>\n </div>\n </Grid>\n <Grid item xs={12}>\n <Divider style={{ margin: '12px 0' }} />\n <Typography variant=\"subtitle2\" color=\"textSecondary\">Response</Typography>\n <pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'ui-monospace, monospace' }}>{response}</pre>\n </Grid>\n </Grid>\n </Paper>\n );\n}\n\nexport default OpenAIChat;\n","import React, { useState, useEffect } from 'react';\nimport { \n Container, \n Typography, \n Box, \n Paper, \n Grid, \n CircularProgress,\n AppBar,\n Toolbar,\n ThemeProvider,\n createMuiTheme\n} from '@material-ui/core';\nimport ImageUploader from './components/ImageUploader';\nimport ModelSelector from './components/ModelSelector';\nimport ResultDisplay from './components/ResultDisplay';\nimport LlmAnalysis from './components/LlmAnalysis';\nimport OpenAIChat from './components/OpenAIChat';\nimport './App.css';\n\n// Create a theme\nconst theme = createMuiTheme({\n palette: {\n primary: {\n main: '#3f51b5',\n },\n secondary: {\n main: '#f50057',\n },\n },\n typography: {\n fontFamily: 'Roboto, Arial, sans-serif',\n },\n});\n\nfunction App() {\n const [selectedImage, setSelectedImage] = useState(null);\n const [selectedModel, setSelectedModel] = useState('');\n const [isProcessing, setIsProcessing] = useState(false);\n const [results, setResults] = useState(null);\n const [error, setError] = useState(null);\n const [modelsStatus, setModelsStatus] = useState({\n yolo: false,\n detr: false,\n vit: false\n });\n\n // Check API status on component mount\n useEffect(() => {\n fetch('/api/status')\n .then(response => response.json())\n .then(data => {\n setModelsStatus(data.models);\n })\n .catch(err => {\n console.error('Error checking API status:', err);\n setError('Error connecting to the backend API. Please make sure the server is running.');\n });\n }, []);\n\n const handleImageUpload = (image) => {\n setSelectedImage(image);\n setResults(null);\n setError(null);\n };\n\n const handleModelSelect = (model) => {\n setSelectedModel(model);\n setResults(null);\n setError(null);\n };\n\n const processImage = async () => {\n if (!selectedImage || !selectedModel) {\n setError('Please select both an image and a model');\n return;\n }\n\n setIsProcessing(true);\n setError(null);\n\n // Create form data for the image\n const formData = new FormData();\n formData.append('image', selectedImage);\n\n let endpoint = '';\n switch (selectedModel) {\n case 'yolo':\n endpoint = '/api/detect/yolo';\n break;\n case 'detr':\n endpoint = '/api/detect/detr';\n break;\n case 'vit':\n endpoint = '/api/classify/vit';\n break;\n default:\n setError('Invalid model selection');\n setIsProcessing(false);\n return;\n }\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! Status: ${response.status}`);\n }\n\n const data = await response.json();\n setResults({ model: selectedModel, data });\n } catch (err) {\n console.error('Error processing image:', err);\n setError(`Error processing image: ${err.message}`);\n } finally {\n setIsProcessing(false);\n }\n };\n\n return (\n <ThemeProvider theme={theme}>\n <Box style={{ flexGrow: 1 }}>\n <AppBar position=\"static\">\n <Toolbar>\n <Typography variant=\"h6\" style={{ flexGrow: 1 }}>\n Multi-Model Object Detection Demo\n </Typography>\n </Toolbar>\n </AppBar>\n <Container maxWidth=\"lg\" style={{ marginTop: theme.spacing(4), marginBottom: theme.spacing(4) }}>\n <Grid container spacing={3}>\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2) }}>\n <Typography variant=\"h5\" gutterBottom>\n Upload an image to see how each model performs!\n </Typography>\n <Typography variant=\"body1\" paragraph>\n This demo showcases three different object detection and image classification models:\n </Typography>\n <Typography variant=\"body1\" component=\"div\">\n <ul>\n <li><strong>YOLOv8</strong>: Fast and accurate object detection</li>\n <li><strong>DETR</strong>: DEtection TRansformer for object detection</li>\n <li><strong>ViT</strong>: Vision Transformer for image classification</li>\n </ul>\n </Typography>\n </Paper>\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ImageUploader onImageUpload={handleImageUpload} />\n </Grid>\n \n <Grid item xs={12} md={6}>\n <ModelSelector \n onModelSelect={handleModelSelect} \n onProcess={processImage}\n isProcessing={isProcessing}\n modelsStatus={modelsStatus}\n selectedModel={selectedModel}\n imageSelected={!!selectedImage}\n />\n </Grid>\n \n {error && (\n <Grid item xs={12}>\n <Paper style={{ padding: theme.spacing(2), backgroundColor: '#ffebee' }}>\n <Typography color=\"error\">{error}</Typography>\n </Paper>\n </Grid>\n )}\n \n {isProcessing && (\n <Grid item xs={12} style={{ textAlign: 'center', margin: `${theme.spacing(4)}px 0` }}>\n <CircularProgress />\n <Typography variant=\"h6\" style={{ marginTop: theme.spacing(2) }}>\n Processing image...\n </Typography>\n </Grid>\n )}\n \n {results && (\n <>\n <Grid item xs={12}>\n <ResultDisplay results={results} />\n </Grid>\n <Grid item xs={12}>\n <LlmAnalysis visionResults={results.data} model={results.model} />\n </Grid>\n </>\n )}\n\n {/* OpenAI Chat section at the end */}\n <Grid item xs={12}>\n <OpenAIChat />\n </Grid>\n </Grid>\n </Container>\n </Box>\n </ThemeProvider>\n );\n}\n\nexport default App;\n","const reportWebVitals = (onPerfEntry) => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"],"sourceRoot":""}
|
|
|
|
static_bak_20250820_085943/static/js/runtime-main.25710301.js
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"9013e23f"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="/",i.oe=function(e){throw console.error(e),e};var a=this["webpackJsonpvision-web-app"]=this["webpackJsonpvision-web-app"]||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);
|
2 |
-
//# sourceMappingURL=runtime-main.25710301.js.map
|
|
|
|
|
|
static_bak_20250820_085943/static/js/runtime-main.25710301.js.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"sources":["../webpack/bootstrap"],"names":["webpackJsonpCallback","data","moduleId","chunkId","chunkIds","moreModules","executeModules","i","resolves","length","Object","prototype","hasOwnProperty","call","installedChunks","push","modules","parentJsonpFunction","shift","deferredModules","apply","checkDeferredModules","result","deferredModule","fulfilled","j","depId","splice","__webpack_require__","s","installedModules","1","exports","module","l","e","promises","installedChunkData","promise","Promise","resolve","reject","onScriptComplete","script","document","createElement","charset","timeout","nc","setAttribute","src","p","jsonpScriptSrc","error","Error","event","onerror","onload","clearTimeout","chunk","errorType","type","realSrc","target","message","name","request","undefined","setTimeout","head","appendChild","all","m","c","d","getter","o","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","oe","err","console","jsonpArray","this","oldJsonpFunction","slice"],"mappings":"aACE,SAASA,EAAqBC,GAQ7B,IAPA,IAMIC,EAAUC,EANVC,EAAWH,EAAK,GAChBI,EAAcJ,EAAK,GACnBK,EAAiBL,EAAK,GAIHM,EAAI,EAAGC,EAAW,GACpCD,EAAIH,EAASK,OAAQF,IACzBJ,EAAUC,EAASG,GAChBG,OAAOC,UAAUC,eAAeC,KAAKC,EAAiBX,IAAYW,EAAgBX,IACpFK,EAASO,KAAKD,EAAgBX,GAAS,IAExCW,EAAgBX,GAAW,EAE5B,IAAID,KAAYG,EACZK,OAAOC,UAAUC,eAAeC,KAAKR,EAAaH,KACpDc,EAAQd,GAAYG,EAAYH,IAKlC,IAFGe,GAAqBA,EAAoBhB,GAEtCO,EAASC,QACdD,EAASU,OAATV,GAOD,OAHAW,EAAgBJ,KAAKK,MAAMD,EAAiBb,GAAkB,IAGvDe,IAER,SAASA,IAER,IADA,IAAIC,EACIf,EAAI,EAAGA,EAAIY,EAAgBV,OAAQF,IAAK,CAG/C,IAFA,IAAIgB,EAAiBJ,EAAgBZ,GACjCiB,GAAY,EACRC,EAAI,EAAGA,EAAIF,EAAed,OAAQgB,IAAK,CAC9C,IAAIC,EAAQH,EAAeE,GACG,IAA3BX,EAAgBY,KAAcF,GAAY,GAE3CA,IACFL,EAAgBQ,OAAOpB,IAAK,GAC5Be,EAASM,EAAoBA,EAAoBC,EAAIN,EAAe,KAItE,OAAOD,EAIR,IAAIQ,EAAmB,GAKnBhB,EAAkB,CACrBiB,EAAG,GAGAZ,EAAkB,GAQtB,SAASS,EAAoB1B,GAG5B,GAAG4B,EAAiB5B,GACnB,OAAO4B,EAAiB5B,GAAU8B,QAGnC,IAAIC,EAASH,EAAiB5B,GAAY,CACzCK,EAAGL,EACHgC,GAAG,EACHF,QAAS,IAUV,OANAhB,EAAQd,GAAUW,KAAKoB,EAAOD,QAASC,EAAQA,EAAOD,QAASJ,GAG/DK,EAAOC,GAAI,EAGJD,EAAOD,QAKfJ,EAAoBO,EAAI,SAAuBhC,GAC9C,IAAIiC,EAAW,GAKXC,EAAqBvB,EAAgBX,GACzC,GAA0B,IAAvBkC,EAGF,GAAGA,EACFD,EAASrB,KAAKsB,EAAmB,QAC3B,CAEN,IAAIC,EAAU,IAAIC,SAAQ,SAASC,EAASC,GAC3CJ,EAAqBvB,EAAgBX,GAAW,CAACqC,EAASC,MAE3DL,EAASrB,KAAKsB,EAAmB,GAAKC,GAGtC,IACII,EADAC,EAASC,SAASC,cAAc,UAGpCF,EAAOG,QAAU,QACjBH,EAAOI,QAAU,IACbnB,EAAoBoB,IACvBL,EAAOM,aAAa,QAASrB,EAAoBoB,IAElDL,EAAOO,IA1DV,SAAwB/C,GACvB,OAAOyB,EAAoBuB,EAAI,cAAgB,GAAGhD,IAAUA,GAAW,IAAM,CAAC,EAAI,YAAYA,GAAW,YAyD1FiD,CAAejD,GAG5B,IAAIkD,EAAQ,IAAIC,MAChBZ,EAAmB,SAAUa,GAE5BZ,EAAOa,QAAUb,EAAOc,OAAS,KACjCC,aAAaX,GACb,IAAIY,EAAQ7C,EAAgBX,GAC5B,GAAa,IAAVwD,EAAa,CACf,GAAGA,EAAO,CACT,IAAIC,EAAYL,IAAyB,SAAfA,EAAMM,KAAkB,UAAYN,EAAMM,MAChEC,EAAUP,GAASA,EAAMQ,QAAUR,EAAMQ,OAAOb,IACpDG,EAAMW,QAAU,iBAAmB7D,EAAU,cAAgByD,EAAY,KAAOE,EAAU,IAC1FT,EAAMY,KAAO,iBACbZ,EAAMQ,KAAOD,EACbP,EAAMa,QAAUJ,EAChBH,EAAM,GAAGN,GAEVvC,EAAgBX,QAAWgE,IAG7B,IAAIpB,EAAUqB,YAAW,WACxB1B,EAAiB,CAAEmB,KAAM,UAAWE,OAAQpB,MAC1C,MACHA,EAAOa,QAAUb,EAAOc,OAASf,EACjCE,SAASyB,KAAKC,YAAY3B,GAG5B,OAAOJ,QAAQgC,IAAInC,IAIpBR,EAAoB4C,EAAIxD,EAGxBY,EAAoB6C,EAAI3C,EAGxBF,EAAoB8C,EAAI,SAAS1C,EAASiC,EAAMU,GAC3C/C,EAAoBgD,EAAE5C,EAASiC,IAClCvD,OAAOmE,eAAe7C,EAASiC,EAAM,CAAEa,YAAY,EAAMC,IAAKJ,KAKhE/C,EAAoBoD,EAAI,SAAShD,GACX,qBAAXiD,QAA0BA,OAAOC,aAC1CxE,OAAOmE,eAAe7C,EAASiD,OAAOC,YAAa,CAAEC,MAAO,WAE7DzE,OAAOmE,eAAe7C,EAAS,aAAc,CAAEmD,OAAO,KAQvDvD,EAAoBwD,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQvD,EAAoBuD,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,kBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAK7E,OAAO8E,OAAO,MAGvB,GAFA5D,EAAoBoD,EAAEO,GACtB7E,OAAOmE,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOvD,EAAoB8C,EAAEa,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIR3D,EAAoB+D,EAAI,SAAS1D,GAChC,IAAI0C,EAAS1C,GAAUA,EAAOqD,WAC7B,WAAwB,OAAOrD,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAL,EAAoB8C,EAAEC,EAAQ,IAAKA,GAC5BA,GAIR/C,EAAoBgD,EAAI,SAASgB,EAAQC,GAAY,OAAOnF,OAAOC,UAAUC,eAAeC,KAAK+E,EAAQC,IAGzGjE,EAAoBuB,EAAI,IAGxBvB,EAAoBkE,GAAK,SAASC,GAA2B,MAApBC,QAAQ3C,MAAM0C,GAAYA,GAEnE,IAAIE,EAAaC,KAAK,8BAAgCA,KAAK,+BAAiC,GACxFC,EAAmBF,EAAWlF,KAAK2E,KAAKO,GAC5CA,EAAWlF,KAAOf,EAClBiG,EAAaA,EAAWG,QACxB,IAAI,IAAI7F,EAAI,EAAGA,EAAI0F,EAAWxF,OAAQF,IAAKP,EAAqBiG,EAAW1F,IAC3E,IAAIU,EAAsBkF,EAI1B9E,I","file":"static/js/runtime-main.25710301.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t1: 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// script path function\n \tfunction jsonpScriptSrc(chunkId) {\n \t\treturn __webpack_require__.p + \"static/js/\" + ({}[chunkId]||chunkId) + \".\" + {\"3\":\"9013e23f\"}[chunkId] + \".chunk.js\"\n \t}\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId) {\n \t\tvar promises = [];\n\n\n \t\t// JSONP chunk loading for javascript\n\n \t\tvar installedChunkData = installedChunks[chunkId];\n \t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n \t\t\t// a Promise means \"currently loading\".\n \t\t\tif(installedChunkData) {\n \t\t\t\tpromises.push(installedChunkData[2]);\n \t\t\t} else {\n \t\t\t\t// setup Promise in chunk cache\n \t\t\t\tvar promise = new Promise(function(resolve, reject) {\n \t\t\t\t\tinstalledChunkData = installedChunks[chunkId] = [resolve, reject];\n \t\t\t\t});\n \t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n \t\t\t\t// start chunk loading\n \t\t\t\tvar script = document.createElement('script');\n \t\t\t\tvar onScriptComplete;\n\n \t\t\t\tscript.charset = 'utf-8';\n \t\t\t\tscript.timeout = 120;\n \t\t\t\tif (__webpack_require__.nc) {\n \t\t\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n \t\t\t\t}\n \t\t\t\tscript.src = jsonpScriptSrc(chunkId);\n\n \t\t\t\t// create error before stack unwound to get useful stacktrace later\n \t\t\t\tvar error = new Error();\n \t\t\t\tonScriptComplete = function (event) {\n \t\t\t\t\t// avoid mem leaks in IE.\n \t\t\t\t\tscript.onerror = script.onload = null;\n \t\t\t\t\tclearTimeout(timeout);\n \t\t\t\t\tvar chunk = installedChunks[chunkId];\n \t\t\t\t\tif(chunk !== 0) {\n \t\t\t\t\t\tif(chunk) {\n \t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n \t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n \t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n \t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n \t\t\t\t\t\t\terror.type = errorType;\n \t\t\t\t\t\t\terror.request = realSrc;\n \t\t\t\t\t\t\tchunk[1](error);\n \t\t\t\t\t\t}\n \t\t\t\t\t\tinstalledChunks[chunkId] = undefined;\n \t\t\t\t\t}\n \t\t\t\t};\n \t\t\t\tvar timeout = setTimeout(function(){\n \t\t\t\t\tonScriptComplete({ type: 'timeout', target: script });\n \t\t\t\t}, 120000);\n \t\t\t\tscript.onerror = script.onload = onScriptComplete;\n \t\t\t\tdocument.head.appendChild(script);\n \t\t\t}\n \t\t}\n \t\treturn Promise.all(promises);\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n \tvar jsonpArray = this[\"webpackJsonpvision-web-app\"] = this[\"webpackJsonpvision-web-app\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":""}
|
|
|
|
test_images/README.txt
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Product Comparison API Testing Instructions
|
2 |
+
|
3 |
+
To test the product comparison API, place test images in this directory. The test images can be:
|
4 |
+
|
5 |
+
1. Cars (e.g., car1.jpg, car2.jpg)
|
6 |
+
2. Electronics like smartphones or laptops (e.g., phone1.jpg, phone2.jpg)
|
7 |
+
3. Any other products you want to compare
|
8 |
+
|
9 |
+
## Testing Steps
|
10 |
+
|
11 |
+
1. Start the Flask app: `python app.py`
|
12 |
+
2. In a separate terminal, run the test script:
|
13 |
+
`python test_product_comparison_api.py --images test_images/image1.jpg test_images/image2.jpg`
|
14 |
+
|
15 |
+
## Expected Results
|
16 |
+
|
17 |
+
The test script will:
|
18 |
+
- Send images to the API endpoint
|
19 |
+
- Connect to the SSE stream to receive real-time updates
|
20 |
+
- Display the final analysis results
|
21 |
+
|
22 |
+
If successful, you should see structured output with product details, comparisons, and recommendations.
|
test_product_comparison_api.py
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Product Comparison API Test Script
|
4 |
+
|
5 |
+
This script tests the product comparison API endpoints with local image files.
|
6 |
+
It supports both the /api/product/compare/start and /api/product/compare/stream endpoints.
|
7 |
+
|
8 |
+
Requirements:
|
9 |
+
- pip install requests pillow sseclient-py
|
10 |
+
|
11 |
+
Usage:
|
12 |
+
python test_product_comparison_api.py --images path/to/image1.jpg path/to/image2.jpg
|
13 |
+
|
14 |
+
Example:
|
15 |
+
python test_product_comparison_api.py --images test_images/car1.jpg test_images/car2.jpg
|
16 |
+
"""
|
17 |
+
|
18 |
+
import requests
|
19 |
+
import base64
|
20 |
+
import json
|
21 |
+
import time
|
22 |
+
import os
|
23 |
+
import sys
|
24 |
+
from io import BytesIO
|
25 |
+
from PIL import Image
|
26 |
+
import argparse
|
27 |
+
|
28 |
+
try:
|
29 |
+
import sseclient
|
30 |
+
except ImportError:
|
31 |
+
print("Missing required package: sseclient-py")
|
32 |
+
print("Please install with: pip install sseclient-py")
|
33 |
+
sys.exit(1)
|
34 |
+
|
35 |
+
def load_and_encode_image(image_path):
|
36 |
+
"""Load an image file and convert to base64 string"""
|
37 |
+
try:
|
38 |
+
with Image.open(image_path) as img:
|
39 |
+
buffered = BytesIO()
|
40 |
+
img.save(buffered, format=img.format or "JPEG")
|
41 |
+
img_str = base64.b64encode(buffered.getvalue()).decode('utf-8')
|
42 |
+
return img_str
|
43 |
+
except Exception as e:
|
44 |
+
print(f"Error loading image {image_path}: {e}")
|
45 |
+
return None
|
46 |
+
|
47 |
+
def test_product_comparison_api(base_url, image_paths, timeout=300):
|
48 |
+
"""Test the product comparison API with provided images"""
|
49 |
+
print(f"Testing product comparison API with {len(image_paths)} images")
|
50 |
+
|
51 |
+
# 1. Encode images as base64
|
52 |
+
images = []
|
53 |
+
for path in image_paths:
|
54 |
+
if not os.path.exists(path):
|
55 |
+
print(f"Error: Image file {path} not found")
|
56 |
+
return
|
57 |
+
|
58 |
+
encoded = load_and_encode_image(path)
|
59 |
+
if encoded:
|
60 |
+
images.append(encoded)
|
61 |
+
print(f"Successfully encoded image: {path}")
|
62 |
+
|
63 |
+
if not images:
|
64 |
+
print("No valid images to process")
|
65 |
+
return
|
66 |
+
|
67 |
+
# 2. Start a comparison session
|
68 |
+
start_url = f"{base_url}/api/product/compare/start"
|
69 |
+
print(f"Sending request to {start_url}")
|
70 |
+
|
71 |
+
try:
|
72 |
+
response = requests.post(
|
73 |
+
start_url,
|
74 |
+
json={"images": images}
|
75 |
+
)
|
76 |
+
|
77 |
+
if not response.ok:
|
78 |
+
print(f"Error starting comparison: HTTP {response.status_code}")
|
79 |
+
print(response.text)
|
80 |
+
return
|
81 |
+
|
82 |
+
data = response.json()
|
83 |
+
session_id = data.get('session_id')
|
84 |
+
|
85 |
+
if not session_id:
|
86 |
+
print("Error: No session ID received")
|
87 |
+
return
|
88 |
+
|
89 |
+
print(f"Session started with ID: {session_id}")
|
90 |
+
print(f"Status: {data.get('status')}")
|
91 |
+
|
92 |
+
# 3. Connect to the streaming endpoint
|
93 |
+
stream_url = f"{base_url}/api/product/compare/stream/{session_id}"
|
94 |
+
print(f"Connecting to stream at {stream_url}")
|
95 |
+
|
96 |
+
headers = {'Accept': 'text/event-stream'}
|
97 |
+
response = requests.get(stream_url, headers=headers, stream=True)
|
98 |
+
|
99 |
+
client = sseclient.SSEClient(response)
|
100 |
+
|
101 |
+
start_time = time.time()
|
102 |
+
result = None
|
103 |
+
|
104 |
+
print("\n--- Streaming Updates ---")
|
105 |
+
try:
|
106 |
+
for event in client.events():
|
107 |
+
current_time = time.time()
|
108 |
+
if current_time - start_time > timeout:
|
109 |
+
print("Timeout reached. Ending stream.")
|
110 |
+
break
|
111 |
+
|
112 |
+
try:
|
113 |
+
data = json.loads(event.data)
|
114 |
+
|
115 |
+
if 'message' in data:
|
116 |
+
print(f"Message: {data['message']}")
|
117 |
+
|
118 |
+
if 'status' in data:
|
119 |
+
print(f"Status update: {data['status']}")
|
120 |
+
|
121 |
+
if 'result' in data:
|
122 |
+
print("\n--- Final Results ---")
|
123 |
+
result = data['result']
|
124 |
+
print(json.dumps(result, indent=2))
|
125 |
+
break
|
126 |
+
|
127 |
+
if 'error' in data:
|
128 |
+
print(f"Error: {data['error']}")
|
129 |
+
break
|
130 |
+
|
131 |
+
except json.JSONDecodeError:
|
132 |
+
print(f"Error decoding JSON from event data: {event.data}")
|
133 |
+
continue
|
134 |
+
except KeyboardInterrupt:
|
135 |
+
print("Stream monitoring interrupted by user")
|
136 |
+
|
137 |
+
return result
|
138 |
+
|
139 |
+
except Exception as e:
|
140 |
+
print(f"Error in API test: {e}")
|
141 |
+
return None
|
142 |
+
|
143 |
+
if __name__ == "__main__":
|
144 |
+
parser = argparse.ArgumentParser(description='Test Product Comparison API')
|
145 |
+
parser.add_argument('--url', default='http://localhost:5000', help='Base URL for the API')
|
146 |
+
parser.add_argument('--images', nargs='+', required=True, help='Paths to images for testing')
|
147 |
+
parser.add_argument('--timeout', type=int, default=300, help='Timeout in seconds')
|
148 |
+
|
149 |
+
args = parser.parse_args()
|
150 |
+
|
151 |
+
test_product_comparison_api(args.url, args.images, args.timeout)
|