hadinicknam commited on
Commit
05ec3fd
·
1 Parent(s): f03da0e

Solve that OutputNode part

Browse files
backend/.DS_Store ADDED
Binary file (6.15 kB). View file
 
backend/.gitignore CHANGED
@@ -1,39 +1,5 @@
1
- # Python
2
- __pycache__/
3
- *.pyc
4
- *.pyo
5
- *.pyd
6
- *build/
7
- *develop-eggs/
8
- *dist/
9
- *downloads/
10
- *eggs/
11
- *.egg-info/
12
- *lib/
13
- *lib64/
14
- *parts/
15
- *sdist/
16
- *var/
17
- *wheels/
18
- *share/python-wheels/
19
- *.egg-info/
20
- *pip-wheel-metadata/
21
- *share/jupyter/
22
- *profile_default/
23
- *ipython_config.py
24
 
25
- # Virtualenv
26
- venv/
27
- env/
28
- ENV/
29
- .venv/
30
- .env/
31
-
32
- # Database
33
- db.sqlite3
34
- *.sqlite3
35
-
36
- # Django
37
- *.log
38
- local_settings.py
39
- .DS_Store
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ # User-generated files
3
+ /media/
4
+ /uploads/
5
+ /gradio_output/
 
 
 
 
 
 
 
 
 
 
 
backend/api/__pycache__/middleware.cpython-313.pyc ADDED
Binary file (1.34 kB). View file
 
backend/api/__pycache__/urls.cpython-313.pyc CHANGED
Binary files a/backend/api/__pycache__/urls.cpython-313.pyc and b/backend/api/__pycache__/urls.cpython-313.pyc differ
 
backend/api/__pycache__/views.cpython-313.pyc CHANGED
Binary files a/backend/api/__pycache__/views.cpython-313.pyc and b/backend/api/__pycache__/views.cpython-313.pyc differ
 
backend/api/middleware.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.contrib.auth.models import User
2
+ from django.contrib.auth import login
3
+
4
+ class AutoUserCreationMiddleware:
5
+ def __init__(self, get_response):
6
+ self.get_response = get_response
7
+
8
+ def __call__(self, request):
9
+ if not request.user.is_authenticated:
10
+ # Create a new user with a unique username
11
+ username = f"user_{User.objects.count() + 1}"
12
+ user = User.objects.create_user(username=username)
13
+ user.save()
14
+
15
+ # Log the user in to establish a session
16
+ login(request, user)
17
+
18
+ response = self.get_response(request)
19
+ return response
backend/api/urls.py CHANGED
@@ -1,9 +1,10 @@
1
  from django.urls import path
2
- from .views import GradioView, PredictView, ResultView, FileUploadView
3
 
4
  urlpatterns = [
5
  path('space/', GradioView.as_view(), name='get_space_details'),
6
  path('predict/', PredictView.as_view(), name='predict_endpoint'),
7
  path('result/<str:job_id>/', ResultView.as_view(), name='get_result'),
8
  path('upload/', FileUploadView.as_view(), name='file_upload'),
 
9
  ]
 
1
  from django.urls import path
2
+ from .views import GradioView, PredictView, ResultView, FileUploadView, BackendLogView
3
 
4
  urlpatterns = [
5
  path('space/', GradioView.as_view(), name='get_space_details'),
6
  path('predict/', PredictView.as_view(), name='predict_endpoint'),
7
  path('result/<str:job_id>/', ResultView.as_view(), name='get_result'),
8
  path('upload/', FileUploadView.as_view(), name='file_upload'),
9
+ path('logs/', BackendLogView.as_view(), name='get_backend_logs'),
10
  ]
backend/api/views.py CHANGED
@@ -2,135 +2,244 @@ import logging
2
  import uuid
3
  import os
4
  import shutil
 
5
  from urllib.parse import urlparse
6
  from django.conf import settings
7
  from django.core.files.storage import FileSystemStorage
8
  from rest_framework.views import APIView
9
  from rest_framework.response import Response
10
- from rest_framework import status
11
  from .gradio_helpers import get_space_details
12
  from gradio_client import Client, exceptions
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
 
16
  JOBS = {}
17
  CLIENT_CACHE = {}
18
 
19
  class FileUploadView(APIView):
 
 
20
  def post(self, request, *args, **kwargs):
 
21
  file_obj = request.FILES.get('file')
22
  if not file_obj:
 
23
  return Response({"error": "File not provided"}, status=status.HTTP_400_BAD_REQUEST)
24
- fs = FileSystemStorage()
 
 
 
 
 
 
25
  filename = fs.save(file_obj.name, file_obj)
26
  file_path = fs.path(filename)
 
 
27
  return Response({"path": file_path}, status=status.HTTP_201_CREATED)
28
 
29
  class GradioView(APIView):
 
 
30
  def get(self, request, *args, **kwargs):
 
31
  space_id = request.query_params.get('space_id')
32
- logging.info(f"Received request for space_id: {space_id}")
33
  if not space_id:
34
- logging.warning("space_id parameter is missing")
35
  return Response({"error": "space_id is required"}, status=status.HTTP_400_BAD_REQUEST)
 
36
  if space_id.startswith('http'):
 
37
  try:
38
  parsed_url = urlparse(space_id)
39
  path_parts = parsed_url.path.strip('/').split('/')
40
  if len(path_parts) >= 2 and path_parts[0] == 'spaces':
41
  extracted_id = f"{path_parts[1]}/{path_parts[2]}"
42
- logging.info(f"Extracted space_id '{extracted_id}' from URL '{space_id}'")
43
  space_id = extracted_id
44
  except Exception as e:
45
- logging.error(f"Failed to parse space_id from URL '{space_id}': {e}")
46
  return Response({"error": "Invalid Hugging Face Space URL provided."}, status=status.HTTP_400_BAD_REQUEST)
 
47
  try:
48
- logging.info(f"Fetching details for space: {space_id}")
49
  details = get_space_details(space_id)
50
- logging.info(f"Successfully fetched details for space: {space_id}")
51
  return Response(details)
52
  except Exception as e:
53
  error_str = str(e)
54
- logging.error(f"An error occurred while processing space '{space_id}': {error_str}", exc_info=True)
55
- if "argument of type 'bool' is not iterable" in error_str or "Could not fetch config" in error_str:
56
- error_message = "The remote Hugging Face space is either invalid or has an incompatible API configuration. Please try a different space."
57
- logging.warning(f"Incompatible API for space '{space_id}': {error_message}")
58
- return Response({"error": error_message}, status=status.HTTP_502_BAD_GATEWAY)
59
  return Response({"error": error_str}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
60
 
61
  class PredictView(APIView):
 
 
62
  def post(self, request, *args, **kwargs):
 
 
63
  space_id = request.data.get('space_id')
64
  api_name = request.data.get('api_name')
65
  inputs = request.data.get('inputs', [])
66
- logging.info(f"Received prediction request for space_id: {space_id}, api_name: {api_name}")
67
- logging.info(f"Inputs received: {inputs}")
68
  if not all([space_id, api_name]):
69
- logging.warning("Missing space_id or api_name in prediction request")
70
  return Response({"error": "space_id and api_name are required"}, status=status.HTTP_400_BAD_REQUEST)
 
71
  try:
72
  if space_id in CLIENT_CACHE:
73
- logging.info(f"Reusing existing Gradio client for space: {space_id}")
74
  client = CLIENT_CACHE[space_id]
75
  else:
76
- logging.info(f"Initializing new Gradio client for space: {space_id}")
77
  client = Client(space_id)
78
  CLIENT_CACHE[space_id] = client
79
- logging.info(f"New client for {space_id} cached.")
 
 
80
  job = client.submit(*inputs, api_name=api_name)
81
  job_id = str(uuid.uuid4())
82
- JOBS[job_id] = job
83
- logging.info(f"Job submitted with ID: {job_id} for space: {space_id}")
84
  return Response({"job_id": job_id}, status=status.HTTP_202_ACCEPTED)
85
  except Exception as e:
86
- logging.error(f"Prediction failed for space '{space_id}' with api_name '{api_name}': {str(e)}", exc_info=True)
87
  return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
88
 
89
  class ResultView(APIView):
 
 
90
  def _process_result(self, result, request):
91
- if isinstance(result, list):
 
 
 
 
92
  return [self._process_result(item, request) for item in result]
93
- if isinstance(result, str) and os.path.exists(result):
 
 
94
  try:
95
- unique_filename = str(uuid.uuid4()) + os.path.splitext(result)[1]
96
- os.makedirs(settings.MEDIA_ROOT, exist_ok=True)
97
- shutil.copy(result, os.path.join(settings.MEDIA_ROOT, unique_filename))
98
- url = request.build_absolute_uri(os.path.join(settings.MEDIA_URL, unique_filename))
99
- logging.info(f"Converted file path '{result}' to public URL '{url}'")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  return url
 
 
 
 
101
  except Exception as e:
102
- logging.error(f"Failed to process file result '{result}': {e}")
 
103
  return result
 
 
104
  return result
105
 
106
  def get(self, request, job_id, *args, **kwargs):
107
- logging.info(f"Received result request for job_id: {job_id}")
108
- job = JOBS.get(job_id)
109
- if job is None:
110
- logging.warning(f"Job with ID {job_id} not found.")
111
  return Response({"error": "Job not found"}, status=status.HTTP_404_NOT_FOUND)
 
 
 
 
 
 
112
  try:
113
- if not job.done():
114
- status_name = job.status().code.name.lower()
115
- logging.debug(f"Job {job_id} still processing (state={status_name}).")
116
- return Response({"status": "processing", "detail": status_name}, status=status.HTTP_200_OK)
117
- raw_result = job.result()
118
- logging.info(f"Job {job_id} completed with raw result: {raw_result}")
119
- final_result = self._process_result(raw_result, request)
120
- JOBS.pop(job_id, None)
121
- return Response({"status": "completed", "result": final_result}, status=status.HTTP_200_OK)
122
- except exceptions.AppError as e:
123
- logging.error(f"Gradio AppError processing job {job_id}: {e}", exc_info=True)
124
- JOBS.pop(job_id, None)
125
- return Response({"status": "error", "error": f"The remote Gradio app encountered a runtime error: {e}"}, status=status.HTTP_502_BAD_GATEWAY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  except Exception as e:
127
- logging.error(f"Error processing job {job_id}: {e}", exc_info=True)
128
  JOBS.pop(job_id, None)
129
- try:
130
- status_name = job.status().code.name.lower()
131
- if status_name in ("cancelled", "canceled"):
132
- logging.info(f"Job {job_id} was cancelled.")
133
- return Response({"status": "cancelled"}, status=status.HTTP_200_OK)
134
- except Exception:
135
- pass
136
- return Response({"status": "error", "error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
 
2
  import uuid
3
  import os
4
  import shutil
5
+ import pathlib
6
  from urllib.parse import urlparse
7
  from django.conf import settings
8
  from django.core.files.storage import FileSystemStorage
9
  from rest_framework.views import APIView
10
  from rest_framework.response import Response
11
+ from rest_framework import status, permissions
12
  from .gradio_helpers import get_space_details
13
  from gradio_client import Client, exceptions
14
 
15
+ # In-memory log storage for debugging
16
+ BACKEND_LOGS = []
17
+
18
+ def log_backend_message(message):
19
+ """Adds a message to the in-memory log store."""
20
+ logging.info(message)
21
+ BACKEND_LOGS.append(f"INFO: {message}")
22
+
23
+ def log_backend_error(message, exc_info=False):
24
+ """Adds an error to the in-memory log store."""
25
+ logging.error(message, exc_info=exc_info)
26
+ BACKEND_LOGS.append(f"ERROR: {message}")
27
+
28
+
29
+ class BackendLogView(APIView):
30
+ """An endpoint to fetch and clear backend logs for debugging."""
31
+ permission_classes = [permissions.IsAuthenticated]
32
+
33
+ def get(self, request, *args, **kwargs):
34
+ logs = BACKEND_LOGS.copy()
35
+ BACKEND_LOGS.clear()
36
+ return Response({"logs": logs})
37
+
38
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
39
 
40
  JOBS = {}
41
  CLIENT_CACHE = {}
42
 
43
  class FileUploadView(APIView):
44
+ permission_classes = [permissions.IsAuthenticated]
45
+
46
  def post(self, request, *args, **kwargs):
47
+ log_backend_message("FileUploadView: Received file upload request.")
48
  file_obj = request.FILES.get('file')
49
  if not file_obj:
50
+ log_backend_error("FileUploadView: No file provided in the request.")
51
  return Response({"error": "File not provided"}, status=status.HTTP_400_BAD_REQUEST)
52
+
53
+ user_id = str(request.user.id)
54
+ log_backend_message(f"FileUploadView: Upload initiated by user_id: {user_id}")
55
+ upload_dir = os.path.join(settings.BASE_DIR, 'uploads', user_id)
56
+ os.makedirs(upload_dir, exist_ok=True)
57
+
58
+ fs = FileSystemStorage(location=upload_dir)
59
  filename = fs.save(file_obj.name, file_obj)
60
  file_path = fs.path(filename)
61
+
62
+ log_backend_message(f"FileUploadView: File '{filename}' saved to '{file_path}'.")
63
  return Response({"path": file_path}, status=status.HTTP_201_CREATED)
64
 
65
  class GradioView(APIView):
66
+ permission_classes = [permissions.IsAuthenticated]
67
+
68
  def get(self, request, *args, **kwargs):
69
+ log_backend_message("GradioView: Received request to fetch space details.")
70
  space_id = request.query_params.get('space_id')
71
+ log_backend_message(f"GradioView: Requested space_id: '{space_id}'")
72
  if not space_id:
73
+ log_backend_error("GradioView: space_id parameter is missing.")
74
  return Response({"error": "space_id is required"}, status=status.HTTP_400_BAD_REQUEST)
75
+
76
  if space_id.startswith('http'):
77
+ log_backend_message("GradioView: space_id is a URL, attempting to parse.")
78
  try:
79
  parsed_url = urlparse(space_id)
80
  path_parts = parsed_url.path.strip('/').split('/')
81
  if len(path_parts) >= 2 and path_parts[0] == 'spaces':
82
  extracted_id = f"{path_parts[1]}/{path_parts[2]}"
83
+ log_backend_message(f"GradioView: Extracted space_id '{extracted_id}' from URL.")
84
  space_id = extracted_id
85
  except Exception as e:
86
+ log_backend_error(f"GradioView: Failed to parse URL: {e}")
87
  return Response({"error": "Invalid Hugging Face Space URL provided."}, status=status.HTTP_400_BAD_REQUEST)
88
+
89
  try:
90
+ log_backend_message(f"GradioView: Calling get_space_details for '{space_id}'.")
91
  details = get_space_details(space_id)
92
+ log_backend_message(f"GradioView: Successfully fetched details for '{space_id}'.")
93
  return Response(details)
94
  except Exception as e:
95
  error_str = str(e)
96
+ log_backend_error(f"GradioView: Failed to get space details for '{space_id}'. Error: {error_str}")
 
 
 
 
97
  return Response({"error": error_str}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
98
 
99
  class PredictView(APIView):
100
+ permission_classes = [permissions.IsAuthenticated]
101
+
102
  def post(self, request, *args, **kwargs):
103
+ BACKEND_LOGS.clear() # Clear logs for new run
104
+ log_backend_message("PredictView: Received new prediction request.")
105
  space_id = request.data.get('space_id')
106
  api_name = request.data.get('api_name')
107
  inputs = request.data.get('inputs', [])
108
+ log_backend_message(f"PredictView: Space: {space_id}, API: {api_name}, Inputs: {inputs}")
109
+
110
  if not all([space_id, api_name]):
111
+ log_backend_error("PredictView: Missing space_id or api_name.")
112
  return Response({"error": "space_id and api_name are required"}, status=status.HTTP_400_BAD_REQUEST)
113
+
114
  try:
115
  if space_id in CLIENT_CACHE:
116
+ log_backend_message(f"PredictView: Reusing existing Gradio client for space: {space_id}")
117
  client = CLIENT_CACHE[space_id]
118
  else:
119
+ log_backend_message(f"PredictView: Initializing new Gradio client for space: {space_id}")
120
  client = Client(space_id)
121
  CLIENT_CACHE[space_id] = client
122
+ log_backend_message(f"PredictView: New client for {space_id} cached.")
123
+
124
+ log_backend_message("PredictView: Submitting job to Gradio client...")
125
  job = client.submit(*inputs, api_name=api_name)
126
  job_id = str(uuid.uuid4())
127
+ JOBS[job_id] = {'job': job, 'user': request.user}
128
+ log_backend_message(f"PredictView: Job submitted with temporary ID: {job_id}")
129
  return Response({"job_id": job_id}, status=status.HTTP_202_ACCEPTED)
130
  except Exception as e:
131
+ log_backend_error(f"PredictView: Prediction failed for space '{space_id}': {str(e)}", exc_info=True)
132
  return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
133
 
134
  class ResultView(APIView):
135
+ permission_classes = [permissions.IsAuthenticated]
136
+
137
  def _process_result(self, result, request):
138
+ log_backend_message(f"ResultView._process_result: Processing item. Type: {type(result)}. Value: {result}")
139
+
140
+ # Recursively process lists and tuples
141
+ if isinstance(result, list) or isinstance(result, tuple):
142
+ log_backend_message(f"ResultView._process_result: Item is a {type(result).__name__}, processing each element recursively.")
143
  return [self._process_result(item, request) for item in result]
144
+
145
+ if isinstance(result, str):
146
+ # EAFP (Easier to Ask for Forgiveness than Permission) approach
147
  try:
148
+ # Attempt to treat the string as a file path
149
+ log_backend_message(f"ResultView._process_result: Item is a string. Attempting to copy '{result}' as a file.")
150
+
151
+ # An extra check to avoid trying to copy things that are clearly not paths
152
+ if not os.path.sep in result:
153
+ log_backend_message(f"ResultView._process_result: '{result}' does not contain a path separator. Assuming it's a regular string.")
154
+ return result
155
+
156
+ user_id = str(request.user.id)
157
+ user_media_dir = os.path.join(settings.MEDIA_ROOT, user_id)
158
+
159
+ if not os.path.exists(user_media_dir):
160
+ os.makedirs(user_media_dir)
161
+ log_backend_message(f"ResultView._process_result: Created user media directory: {user_media_dir}")
162
+
163
+ # Create a unique filename to avoid conflicts
164
+ original_filename = os.path.basename(result)
165
+ unique_filename = str(uuid.uuid4()) + os.path.splitext(original_filename)[1]
166
+ destination_path = os.path.join(user_media_dir, unique_filename)
167
+
168
+ log_backend_message(f"ResultView._process_result: Destination path for copy: {destination_path}")
169
+
170
+ shutil.copy(result, destination_path)
171
+ log_backend_message("ResultView._process_result: File copied successfully.")
172
+
173
+ # Construct the public URL
174
+ file_url_path = f"{settings.MEDIA_URL}{user_id}/{unique_filename}"
175
+ url = request.build_absolute_uri(file_url_path)
176
+
177
+ log_backend_message(f"ResultView._process_result: Successfully converted file path to public URL: '{url}'")
178
  return url
179
+ except (FileNotFoundError, IsADirectoryError, OSError) as e:
180
+ # This will trigger if `result` is not a valid file path
181
+ log_backend_message(f"ResultView._process_result: Could not treat '{result}' as a file. It's likely a regular string. Error: {e}")
182
+ return result
183
  except Exception as e:
184
+ # Catch any other unexpected errors during the copy
185
+ log_backend_error(f"ResultView._process_result: An unexpected error occurred while processing '{result}'. Error: {e}", exc_info=True)
186
  return result
187
+
188
+ log_backend_message(f"ResultView._process_result: Item is not a string, list, or tuple ({type(result).__name__}), returning as is.")
189
  return result
190
 
191
  def get(self, request, job_id, *args, **kwargs):
192
+ log_backend_message(f"ResultView: Received result request for job_id: {job_id}")
193
+ job_info = JOBS.get(job_id)
194
+ if job_info is None:
195
+ log_backend_error(f"ResultView: Job with ID {job_id} not found.")
196
  return Response({"error": "Job not found"}, status=status.HTTP_404_NOT_FOUND)
197
+
198
+ if job_info['user'] != request.user:
199
+ log_backend_error(f"ResultView: User {request.user} attempted to access job {job_id} owned by {job_info['user']}")
200
+ return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
201
+
202
+ job = job_info['job']
203
  try:
204
+ status_name = job.status().code.name.lower()
205
+ log_backend_message(f"ResultView: Job {job_id} status is '{status_name}'.")
206
+
207
+ if status_name == "finished":
208
+ log_backend_message(f"ResultView: Job {job_id} is finished. Processing final result.")
209
+ raw_results = job.result()
210
+ log_backend_message(f"ResultView: Job {job_id} completed with raw result: {raw_results}")
211
+
212
+ if not isinstance(raw_results, list):
213
+ raw_results = [raw_results]
214
+
215
+ final_result = self._process_result(raw_results, request)
216
+
217
+ JOBS.pop(job_id, None)
218
+
219
+ # Capture the logs from this final processing step
220
+ final_logs = BACKEND_LOGS.copy()
221
+ BACKEND_LOGS.clear()
222
+
223
+ log_backend_message(f"ResultView: Final processed result for job {job_id}: {final_result}")
224
+ return Response({
225
+ "status": "completed",
226
+ "result": final_result,
227
+ "logs": final_logs # Include final logs in the response
228
+ }, status=status.HTTP_200_OK)
229
+
230
+ elif status_name in ["cancelled", "failed"]:
231
+ log_backend_message(f"ResultView: Job {job_id} ended with terminal status: {status_name}")
232
+ JOBS.pop(job_id, None)
233
+ return Response({"status": status_name, "error": f"Job ended with status: {status_name}"}, status=status.HTTP_200_OK)
234
+
235
+ else: # The job is still running
236
+ log_backend_message(f"ResultView: Job {job_id} still processing.")
237
+ # For polling requests, return the current logs
238
+ logs = BACKEND_LOGS.copy()
239
+ BACKEND_LOGS.clear()
240
+ return Response({"status": "processing", "detail": status_name, "logs": logs}, status=status.HTTP_200_OK)
241
+
242
  except Exception as e:
243
+ log_backend_error(f"ResultView: Error processing job {job_id}: {e}", exc_info=True)
244
  JOBS.pop(job_id, None)
245
+ return Response({"status": "error", "error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
 
 
 
 
 
 
 
backend/core/__pycache__/settings.cpython-313.pyc CHANGED
Binary files a/backend/core/__pycache__/settings.cpython-313.pyc and b/backend/core/__pycache__/settings.cpython-313.pyc differ
 
backend/core/__pycache__/urls.cpython-313.pyc CHANGED
Binary files a/backend/core/__pycache__/urls.cpython-313.pyc and b/backend/core/__pycache__/urls.cpython-313.pyc differ
 
backend/core/settings.py CHANGED
@@ -27,6 +27,10 @@ DEBUG = True
27
 
28
  ALLOWED_HOSTS = []
29
 
 
 
 
 
30
 
31
  # Application definition
32
 
@@ -42,6 +46,22 @@ INSTALLED_APPS = [
42
  'api',
43
  ]
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  MIDDLEWARE = [
46
  'django.middleware.security.SecurityMiddleware',
47
  'django.contrib.sessions.middleware.SessionMiddleware',
@@ -51,6 +71,7 @@ MIDDLEWARE = [
51
  'django.contrib.auth.middleware.AuthenticationMiddleware',
52
  'django.contrib.messages.middleware.MessageMiddleware',
53
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
 
54
  ]
55
 
56
  ROOT_URLCONF = 'core.urls'
 
27
 
28
  ALLOWED_HOSTS = []
29
 
30
+ CSRF_TRUSTED_ORIGINS = [
31
+ 'http://localhost:3000',
32
+ ]
33
+
34
 
35
  # Application definition
36
 
 
46
  'api',
47
  ]
48
 
49
+
50
+
51
+ REST_FRAMEWORK = {
52
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
53
+ 'rest_framework.authentication.SessionAuthentication',
54
+ ],
55
+ 'DEFAULT_PERMISSION_CLASSES': [
56
+ 'rest_framework.permissions.IsAuthenticated',
57
+ ],
58
+ }
59
+
60
+ CORS_ALLOW_CREDENTIALS = True
61
+
62
+
63
+
64
+
65
  MIDDLEWARE = [
66
  'django.middleware.security.SecurityMiddleware',
67
  'django.contrib.sessions.middleware.SessionMiddleware',
 
71
  'django.contrib.auth.middleware.AuthenticationMiddleware',
72
  'django.contrib.messages.middleware.MessageMiddleware',
73
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
74
+ 'api.middleware.AutoUserCreationMiddleware',
75
  ]
76
 
77
  ROOT_URLCONF = 'core.urls'
backend/db.sqlite3 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:32260ae775b444b3d50b69232c5544b48d362ac31e6b59904740207cda0b5438
3
- size 139264
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:61db9a29d3062089c1aac27a27f5040eb8280ded478267a97a0fbda34ba2b803
3
+ size 258048
frontend/src/App.js CHANGED
@@ -1,5 +1,5 @@
1
  import React, { useState, useCallback, useMemo, useContext, createContext } from 'react';
2
- import axios from 'axios';
3
  import ReactFlow, {
4
  MiniMap,
5
  Controls,
@@ -124,7 +124,7 @@ function App() {
124
  setNodes(nds => nds.map(n => n.id === nodeIdToReplace ? { ...n, data: { ...n.data, error: null } } : n));
125
 
126
  try {
127
- const response = await axios.get(`http://localhost:8000/api/space/?space_id=${spaceId}`);
128
  const { endpoints } = response.data;
129
  const priorityEndpoint = endpoints.find(e => e.category === 'priority') || endpoints[0];
130
 
@@ -159,19 +159,30 @@ function App() {
159
  const node = nodesMap.get(nodeId);
160
  addLog(`[${node.data.label}] Polling for result (Job ID: ${jobId})...`);
161
 
 
 
 
 
 
 
 
 
162
  const poll = async () => {
163
  try {
164
- const response = await axios.get(`http://localhost:8000/api/result/${jobId}`);
165
 
 
 
 
166
  if (response.data.status === 'processing') {
167
- setTimeout(poll, 2000);
168
  } else if (response.data.status === 'completed') {
169
  addLog(`[${node.data.label}] Job completed successfully.`, 'SUCCESS');
170
  executionResults.set(nodeId, response.data.result);
171
  setNodes(nds => nds.map(n => n.id === nodeId ? { ...n, data: { ...n.data, status: 'success', result: response.data.result } } : n));
172
  await processNextNodes(nodeId, executionResults, nodesMap);
173
- } else if (response.data.status === 'error') {
174
- throw new Error(response.data.error);
175
  }
176
  } catch (e) {
177
  addLog(`[${node.data.label}] Polling failed: ${e.message}`, 'ERROR');
@@ -234,7 +245,7 @@ function App() {
234
  }).filter(input => input !== null);
235
 
236
  addLog(`[${node.data.label}] Sending request to /api/predict/ with inputs: ${JSON.stringify(apiInputs)}`);
237
- const response = await axios.post('http://localhost:8000/api/predict/', {
238
  space_id: node.data.label,
239
  api_name: node.data.apiName,
240
  inputs: apiInputs,
 
1
  import React, { useState, useCallback, useMemo, useContext, createContext } from 'react';
2
+ import api from './api'; // Import the centralized api
3
  import ReactFlow, {
4
  MiniMap,
5
  Controls,
 
124
  setNodes(nds => nds.map(n => n.id === nodeIdToReplace ? { ...n, data: { ...n.data, error: null } } : n));
125
 
126
  try {
127
+ const response = await api.get(`/space/?space_id=${spaceId}`);
128
  const { endpoints } = response.data;
129
  const priorityEndpoint = endpoints.find(e => e.category === 'priority') || endpoints[0];
130
 
 
159
  const node = nodesMap.get(nodeId);
160
  addLog(`[${node.data.label}] Polling for result (Job ID: ${jobId})...`);
161
 
162
+ const processLogs = (logs) => {
163
+ if (logs && logs.length > 0) {
164
+ logs.forEach(logMsg => {
165
+ addLog(`[BACKEND] ${logMsg}`, 'INFO');
166
+ });
167
+ }
168
+ };
169
+
170
  const poll = async () => {
171
  try {
172
+ const response = await api.get(`/result/${jobId}`);
173
 
174
+ // Always process logs, whether the job is running or complete
175
+ processLogs(response.data.logs);
176
+
177
  if (response.data.status === 'processing') {
178
+ setTimeout(poll, 2000); // Continue polling
179
  } else if (response.data.status === 'completed') {
180
  addLog(`[${node.data.label}] Job completed successfully.`, 'SUCCESS');
181
  executionResults.set(nodeId, response.data.result);
182
  setNodes(nds => nds.map(n => n.id === nodeId ? { ...n, data: { ...n.data, status: 'success', result: response.data.result } } : n));
183
  await processNextNodes(nodeId, executionResults, nodesMap);
184
+ } else if (response.data.status === 'error' || response.data.status === 'failed' || response.data.status === 'cancelled') {
185
+ throw new Error(response.data.error || `Job ended with status: ${response.data.status}`);
186
  }
187
  } catch (e) {
188
  addLog(`[${node.data.label}] Polling failed: ${e.message}`, 'ERROR');
 
245
  }).filter(input => input !== null);
246
 
247
  addLog(`[${node.data.label}] Sending request to /api/predict/ with inputs: ${JSON.stringify(apiInputs)}`);
248
+ const response = await api.post('/predict/', {
249
  space_id: node.data.label,
250
  api_name: node.data.apiName,
251
  inputs: apiInputs,
frontend/src/CustomNode.js CHANGED
@@ -1,6 +1,6 @@
1
  import React, { useState } from 'react';
2
  import { Handle, Position } from 'reactflow';
3
- import axios from 'axios';
4
  import './CustomNode.css';
5
 
6
  // A dedicated component for the slider to manage its own state
@@ -39,7 +39,7 @@ const InputRow = ({ node_id, input, isConnected, isConnectable }) => {
39
  setUploadStatus('uploading');
40
 
41
  try {
42
- const response = await axios.post('http://localhost:8000/api/upload/', formData, {
43
  headers: {
44
  'Content-Type': 'multipart/form-data',
45
  },
 
1
  import React, { useState } from 'react';
2
  import { Handle, Position } from 'reactflow';
3
+ import api from './api'; // Import the centralized api
4
  import './CustomNode.css';
5
 
6
  // A dedicated component for the slider to manage its own state
 
39
  setUploadStatus('uploading');
40
 
41
  try {
42
+ const response = await api.post('/upload/', formData, {
43
  headers: {
44
  'Content-Type': 'multipart/form-data',
45
  },
frontend/src/api.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from 'axios';
2
+
3
+ // Function to get the CSRF token from the cookie
4
+ function getCookie(name) {
5
+ let cookieValue = null;
6
+ if (document.cookie && document.cookie !== '') {
7
+ const cookies = document.cookie.split(';');
8
+ for (let i = 0; i < cookies.length; i++) {
9
+ const cookie = cookies[i].trim();
10
+ // Does this cookie string begin with the name we want?
11
+ if (cookie.substring(0, name.length + 1) === (name + '=')) {
12
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
13
+ break;
14
+ }
15
+ }
16
+ }
17
+ return cookieValue;
18
+ }
19
+
20
+ const api = axios.create({
21
+ baseURL: 'http://localhost:8000/api', // Your Django API base URL
22
+ withCredentials: true, // This is crucial for sending session cookies
23
+ });
24
+
25
+ // Add a request interceptor to include the CSRF token
26
+ api.interceptors.request.use(
27
+ (config) => {
28
+ const token = getCookie('csrftoken');
29
+ if (token) {
30
+ config.headers['X-CSRFToken'] = token;
31
+ }
32
+ return config;
33
+ },
34
+ (error) => {
35
+ return Promise.reject(error);
36
+ }
37
+ );
38
+
39
+ export default api;