Syncbuz120 commited on
Commit
c70a5b7
Β·
1 Parent(s): f20e646

Convert Flask to Gradio

Browse files
Files changed (3) hide show
  1. Dockerfile +3 -3
  2. app.py +304 -123
  3. requirements.txt +0 -0
Dockerfile CHANGED
@@ -37,8 +37,8 @@ RUN python -c "from transformers import AutoTokenizer, AutoModelForCausalLM; \
37
  # βœ… Ensure cache directory is writable after model download
38
  RUN chmod -R 777 /app/cache
39
 
40
- # βœ… Expose correct port
41
  EXPOSE 7860
42
 
43
- # βœ… Run app
44
- CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "1", "--timeout", "120", "app:app"]
 
37
  # βœ… Ensure cache directory is writable after model download
38
  RUN chmod -R 777 /app/cache
39
 
40
+ # βœ… Expose Gradio port
41
  EXPOSE 7860
42
 
43
+ # βœ… Run Gradio app directly (no need for gunicorn)
44
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -1,6 +1,4 @@
1
- from flask import Flask, request, jsonify
2
- from flask_cors import CORS
3
- from model.generate import generate_test_cases, get_generator, monitor_memory
4
  import os
5
  import logging
6
  import gc
@@ -8,21 +6,16 @@ import psutil
8
  from functools import wraps
9
  import time
10
  import threading
 
 
11
 
12
- # Configure logging for Railway
13
  logging.basicConfig(
14
  level=logging.INFO,
15
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
16
  )
17
  logger = logging.getLogger(__name__)
18
 
19
- app = Flask(__name__)
20
- CORS(app)
21
-
22
- # Configuration for Railway
23
- app.config['JSON_SORT_KEYS'] = False
24
- app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False # Reduce response size
25
-
26
  # Thread-safe initialization
27
  _init_lock = threading.Lock()
28
  _initialized = False
@@ -32,7 +25,7 @@ def init_model():
32
  try:
33
  # Skip AI model loading in low memory environments
34
  memory_mb = psutil.Process().memory_info().rss / 1024 / 1024
35
- if memory_mb > 200 or os.environ.get('RAILWAY_ENVIRONMENT'):
36
  logger.info("⚠️ Skipping AI model loading due to memory constraints")
37
  logger.info("πŸ”§ Using template-based generation mode")
38
  return True
@@ -76,10 +69,10 @@ def smart_memory_monitor(func):
76
  return result
77
  except Exception as e:
78
  logger.error(f"❌ Error in {func.__name__}: {str(e)}")
79
- return jsonify({
80
  "error": "Internal server error occurred",
81
  "message": "Please try again or contact support"
82
- }), 500
83
  finally:
84
  final_memory = psutil.Process().memory_info().rss / 1024 / 1024
85
  execution_time = time.time() - start_time
@@ -99,7 +92,7 @@ def ensure_initialized():
99
  if not _initialized:
100
  with _init_lock:
101
  if not _initialized:
102
- logger.info("πŸš€ Flask app starting up on Railway...")
103
  success = init_model()
104
  if success:
105
  logger.info("βœ… Startup completed successfully")
@@ -107,71 +100,21 @@ def ensure_initialized():
107
  logger.warning("⚠️ Model initialization failed, using template mode")
108
  _initialized = True
109
 
110
- @app.before_request
111
- def before_request():
112
- """Initialize model on first request (Flask 2.2+ compatible)"""
113
- ensure_initialized()
114
 
115
- @app.route('/')
116
- def home():
117
- """Health check endpoint with system status"""
118
- health_data = check_health()
119
- try:
120
- generator = get_generator()
121
- model_info = generator.get_model_info()
122
- except Exception:
123
- model_info = {
124
- "model_name": "Template-Based Generator",
125
- "status": "template_mode",
126
- "optimization": "memory_safe"
127
- }
128
-
129
- return jsonify({
130
- "message": "AI Test Case Generator Backend is running",
131
- "status": health_data["status"],
132
- "memory_usage": health_data["memory_usage"],
133
- "model": {
134
- "name": model_info["model_name"],
135
- "status": model_info["status"],
136
- "optimization": model_info.get("optimization", "standard")
137
- },
138
- "version": "1.0.0-railway-optimized"
139
- })
140
-
141
- @app.route('/health')
142
- def health():
143
- """Dedicated health check for Railway monitoring"""
144
- health_status = check_health()
145
- try:
146
- generator = get_generator()
147
- model_info = generator.get_model_info()
148
- model_loaded = model_info["status"] == "loaded"
149
- except Exception:
150
- model_loaded = False
151
-
152
- return jsonify({
153
- "status": health_status["status"],
154
- "memory": health_status["memory_usage"],
155
- "model_loaded": model_loaded,
156
- "uptime": "ok"
157
- })
158
-
159
- @app.route('/generate_test_cases', methods=['POST'])
160
  @smart_memory_monitor
161
- def generate():
162
- """Generate test cases with enhanced error handling"""
163
- if not request.is_json:
164
- return jsonify({"error": "Request must be JSON"}), 400
165
-
166
- data = request.get_json()
167
- if not data:
168
- return jsonify({"error": "No JSON data provided"}), 400
169
-
170
- srs_text = data.get('srs', '').strip()
171
-
172
- if not srs_text:
173
- return jsonify({"error": "No SRS or prompt content provided"}), 400
174
 
 
 
175
  if len(srs_text) > 5000:
176
  logger.warning(f"SRS text truncated from {len(srs_text)} to 5000 characters")
177
  srs_text = srs_text[:5000]
@@ -182,7 +125,11 @@ def generate():
182
 
183
  if not test_cases or len(test_cases) == 0:
184
  logger.error("No test cases generated")
185
- return jsonify({"error": "Failed to generate test cases"}), 500
 
 
 
 
186
 
187
  try:
188
  generator = get_generator()
@@ -208,66 +155,300 @@ def generate():
208
 
209
  logger.info(f"βœ… Successfully generated {len(test_cases)} test cases")
210
 
211
- return jsonify({
212
  "test_cases": test_cases,
213
  "count": len(test_cases),
214
  "model_used": model_used,
215
  "generation_method": generation_method,
216
  "model_algorithm": model_algorithm,
217
  "model_reason": model_reason
218
- })
219
 
220
  except Exception as e:
221
  logger.error(f"❌ Test case generation failed: {str(e)}")
222
- return jsonify({
223
  "error": "Failed to generate test cases",
224
- "message": "Please try again with different input"
225
- }), 500
 
 
226
 
227
- @app.route('/model_info')
228
- def model_info():
229
- """Get current model information"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  try:
231
  generator = get_generator()
232
  info = generator.get_model_info()
233
  health_data = check_health()
234
 
235
- return jsonify({
236
- "model": info,
237
- "system": health_data
238
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  except Exception as e:
240
- logger.error(f"Error getting model info: {e}")
241
- return jsonify({"error": "Unable to get model information"}), 500
242
-
243
- @app.errorhandler(404)
244
- def not_found(error):
245
- return jsonify({"error": "Endpoint not found"}), 404
246
-
247
- @app.errorhandler(405)
248
- def method_not_allowed(error):
249
- return jsonify({"error": "Method not allowed"}), 405
250
-
251
- @app.errorhandler(500)
252
- def internal_error(error):
253
- logger.error(f"Internal server error: {error}")
254
- return jsonify({"error": "Internal server error"}), 500
255
-
256
- if __name__ == '__main__':
257
- port = int(os.environ.get("PORT", 5000))
258
- debug_mode = os.environ.get("FLASK_ENV") == "development"
259
-
260
- logger.info(f"πŸš€ Starting Flask app on port {port}")
261
- logger.info(f"πŸ”§ Debug mode: {debug_mode}")
262
- logger.info(f"πŸ–₯️ Environment: {'Railway' if os.environ.get('RAILWAY_ENVIRONMENT') else 'Local'}")
263
-
264
- if not os.environ.get('RAILWAY_ENVIRONMENT'):
265
- ensure_initialized()
266
-
267
- app.run(
268
- host='0.0.0.0',
269
- port=port,
270
- debug=debug_mode,
271
- threaded=True,
272
- use_reloader=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
 
 
2
  import os
3
  import logging
4
  import gc
 
6
  from functools import wraps
7
  import time
8
  import threading
9
+ import json
10
+ from model.generate import generate_test_cases, get_generator, monitor_memory
11
 
12
+ # Configure logging
13
  logging.basicConfig(
14
  level=logging.INFO,
15
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
16
  )
17
  logger = logging.getLogger(__name__)
18
 
 
 
 
 
 
 
 
19
  # Thread-safe initialization
20
  _init_lock = threading.Lock()
21
  _initialized = False
 
25
  try:
26
  # Skip AI model loading in low memory environments
27
  memory_mb = psutil.Process().memory_info().rss / 1024 / 1024
28
+ if memory_mb > 200 or os.environ.get('HUGGINGFACE_SPACE'):
29
  logger.info("⚠️ Skipping AI model loading due to memory constraints")
30
  logger.info("πŸ”§ Using template-based generation mode")
31
  return True
 
69
  return result
70
  except Exception as e:
71
  logger.error(f"❌ Error in {func.__name__}: {str(e)}")
72
+ return {
73
  "error": "Internal server error occurred",
74
  "message": "Please try again or contact support"
75
+ }
76
  finally:
77
  final_memory = psutil.Process().memory_info().rss / 1024 / 1024
78
  execution_time = time.time() - start_time
 
92
  if not _initialized:
93
  with _init_lock:
94
  if not _initialized:
95
+ logger.info("πŸš€ Gradio app starting up on Hugging Face Spaces...")
96
  success = init_model()
97
  if success:
98
  logger.info("βœ… Startup completed successfully")
 
100
  logger.warning("⚠️ Model initialization failed, using template mode")
101
  _initialized = True
102
 
103
+ # Initialize on startup
104
+ ensure_initialized()
 
 
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  @smart_memory_monitor
107
+ def generate_test_cases_api(srs_text):
108
+ """Main API function for generating test cases"""
109
+ if not srs_text or not srs_text.strip():
110
+ return {
111
+ "error": "No SRS or prompt content provided",
112
+ "test_cases": [],
113
+ "count": 0
114
+ }
 
 
 
 
 
115
 
116
+ srs_text = srs_text.strip()
117
+
118
  if len(srs_text) > 5000:
119
  logger.warning(f"SRS text truncated from {len(srs_text)} to 5000 characters")
120
  srs_text = srs_text[:5000]
 
125
 
126
  if not test_cases or len(test_cases) == 0:
127
  logger.error("No test cases generated")
128
+ return {
129
+ "error": "Failed to generate test cases",
130
+ "test_cases": [],
131
+ "count": 0
132
+ }
133
 
134
  try:
135
  generator = get_generator()
 
155
 
156
  logger.info(f"βœ… Successfully generated {len(test_cases)} test cases")
157
 
158
+ return {
159
  "test_cases": test_cases,
160
  "count": len(test_cases),
161
  "model_used": model_used,
162
  "generation_method": generation_method,
163
  "model_algorithm": model_algorithm,
164
  "model_reason": model_reason
165
+ }
166
 
167
  except Exception as e:
168
  logger.error(f"❌ Test case generation failed: {str(e)}")
169
+ return {
170
  "error": "Failed to generate test cases",
171
+ "message": "Please try again with different input",
172
+ "test_cases": [],
173
+ "count": 0
174
+ }
175
 
176
+ def format_test_cases_output(result):
177
+ """Format the test cases for display"""
178
+ if "error" in result:
179
+ return f"❌ Error: {result['error']}", ""
180
+
181
+ test_cases = result.get("test_cases", [])
182
+ if not test_cases:
183
+ return "No test cases generated", ""
184
+
185
+ # Format test cases for display
186
+ formatted_output = f"βœ… Generated {result['count']} Test Cases\n\n"
187
+ formatted_output += f"πŸ€– Model: {result['model_used']}\n"
188
+ formatted_output += f"πŸ”§ Algorithm: {result['model_algorithm']}\n"
189
+ formatted_output += f"πŸ’‘ Reason: {result['model_reason']}\n\n"
190
+
191
+ formatted_output += "=" * 50 + "\n"
192
+ formatted_output += "TEST CASES:\n"
193
+ formatted_output += "=" * 50 + "\n\n"
194
+
195
+ for i, tc in enumerate(test_cases, 1):
196
+ formatted_output += f"πŸ”Ή Test Case {i}:\n"
197
+ formatted_output += f" ID: {tc.get('id', f'TC_{i:03d}')}\n"
198
+ formatted_output += f" Title: {tc.get('title', 'N/A')}\n"
199
+ formatted_output += f" Description: {tc.get('description', 'N/A')}\n"
200
+
201
+ steps = tc.get('steps', [])
202
+ if isinstance(steps, list):
203
+ formatted_output += f" Steps:\n"
204
+ for j, step in enumerate(steps, 1):
205
+ formatted_output += f" {j}. {step}\n"
206
+ else:
207
+ formatted_output += f" Steps: {steps}\n"
208
+
209
+ formatted_output += f" Expected Result: {tc.get('expected', 'N/A')}\n\n"
210
+
211
+ # Return JSON for API access
212
+ json_output = json.dumps(result, indent=2)
213
+
214
+ return formatted_output, json_output
215
+
216
+ def gradio_generate_test_cases(srs_text):
217
+ """Gradio interface function"""
218
+ result = generate_test_cases_api(srs_text)
219
+ return format_test_cases_output(result)
220
+
221
+ def get_system_status():
222
+ """Get system status information"""
223
+ health_data = check_health()
224
+ try:
225
+ generator = get_generator()
226
+ model_info = generator.get_model_info()
227
+ except Exception:
228
+ model_info = {
229
+ "model_name": "Template-Based Generator",
230
+ "status": "template_mode",
231
+ "optimization": "memory_safe"
232
+ }
233
+
234
+ status_info = f"""
235
+ πŸ₯ SYSTEM STATUS
236
+ ================
237
+ Status: {health_data["status"]}
238
+ Memory Usage: {health_data["memory_usage"]}
239
+ Memory Limit: 512MB
240
+
241
+ πŸ€– MODEL INFORMATION
242
+ ===================
243
+ Model Name: {model_info["model_name"]}
244
+ Status: {model_info["status"]}
245
+ Optimization: {model_info.get("optimization", "standard")}
246
+
247
+ πŸš€ APPLICATION INFO
248
+ ==================
249
+ Version: 1.0.0-spaces-optimized
250
+ Environment: Hugging Face Spaces
251
+ Backend: Gradio
252
+ """
253
+ return status_info
254
+
255
+ def get_model_info_detailed():
256
+ """Get detailed model information"""
257
  try:
258
  generator = get_generator()
259
  info = generator.get_model_info()
260
  health_data = check_health()
261
 
262
+ detailed_info = f"""
263
+ πŸ”§ DETAILED MODEL INFORMATION
264
+ ============================
265
+ Model Name: {info.get('model_name', 'N/A')}
266
+ Status: {info.get('status', 'N/A')}
267
+ Memory Usage: {info.get('memory_usage', 'N/A')}
268
+ Optimization Level: {info.get('optimization', 'N/A')}
269
+
270
+ πŸ“Š SYSTEM METRICS
271
+ ================
272
+ System Status: {health_data['status']}
273
+ Current Memory: {health_data['memory_usage']}
274
+ Memory Limit: {health_data.get('memory_limit', 'N/A')}
275
+
276
+ βš™οΈ CONFIGURATION
277
+ ===============
278
+ Environment: {"Hugging Face Spaces" if os.environ.get('SPACE_ID') else "Local"}
279
+ Backend: Gradio
280
+ Threading: Enabled
281
+ Memory Monitoring: Active
282
+ """
283
+ return detailed_info
284
  except Exception as e:
285
+ return f"❌ Error getting model info: {str(e)}"
286
+
287
+ # Create Gradio interface
288
+ with gr.Blocks(title="AI Test Case Generator", theme=gr.themes.Soft()) as app:
289
+ gr.Markdown("""
290
+ # πŸ§ͺ AI Test Case Generator
291
+
292
+ Generate comprehensive test cases from Software Requirements Specification (SRS) documents using AI models.
293
+
294
+ **Features:**
295
+ - πŸ€– AI-powered test case generation
296
+ - πŸ“ Template-based fallback for low memory environments
297
+ - πŸ”§ Memory-optimized processing
298
+ - πŸ“Š Real-time system monitoring
299
+ """)
300
+
301
+ with gr.Tab("πŸ§ͺ Generate Test Cases"):
302
+ with gr.Row():
303
+ with gr.Column(scale=2):
304
+ srs_input = gr.Textbox(
305
+ label="πŸ“‹ Software Requirements Specification (SRS)",
306
+ placeholder="Enter your SRS document or requirements here...\n\nExample:\nThe system shall provide user authentication with username and password. Users should be able to login, logout, and reset passwords. The system should validate input and display appropriate error messages for invalid credentials.",
307
+ lines=10,
308
+ max_lines=20
309
+ )
310
+
311
+ generate_btn = gr.Button("πŸš€ Generate Test Cases", variant="primary", size="lg")
312
+
313
+ with gr.Column(scale=3):
314
+ output_display = gr.Textbox(
315
+ label="πŸ“‹ Generated Test Cases",
316
+ lines=20,
317
+ max_lines=30,
318
+ interactive=False
319
+ )
320
+
321
+ with gr.Row():
322
+ json_output = gr.Textbox(
323
+ label="πŸ“„ JSON Output (for API use)",
324
+ lines=10,
325
+ max_lines=15,
326
+ interactive=False
327
+ )
328
+
329
+ with gr.Tab("πŸ“Š System Status"):
330
+ with gr.Column():
331
+ status_display = gr.Textbox(
332
+ label="πŸ₯ System Health & Status",
333
+ lines=15,
334
+ interactive=False
335
+ )
336
+ refresh_status_btn = gr.Button("πŸ”„ Refresh Status", variant="secondary")
337
+
338
+ with gr.Tab("πŸ”§ Model Information"):
339
+ with gr.Column():
340
+ model_info_display = gr.Textbox(
341
+ label="πŸ€– Detailed Model Information",
342
+ lines=20,
343
+ interactive=False
344
+ )
345
+ refresh_model_btn = gr.Button("πŸ”„ Refresh Model Info", variant="secondary")
346
+
347
+ with gr.Tab("πŸ“š API Documentation"):
348
+ gr.Markdown("""
349
+ ## πŸ”Œ API Endpoints
350
+
351
+ This Gradio app automatically creates API endpoints for all functions:
352
+
353
+ ### Generate Test Cases
354
+ **Endpoint:** `/api/predict`
355
+ **Method:** POST
356
+ **Body:**
357
+ ```json
358
+ {
359
+ "data": ["Your SRS text here"]
360
+ }
361
+ ```
362
+
363
+ **Response:**
364
+ ```json
365
+ {
366
+ "data": [
367
+ "Formatted test cases output",
368
+ "JSON output with test cases"
369
+ ]
370
+ }
371
+ ```
372
+
373
+ ### Example Usage (Python):
374
+ ```python
375
+ import requests
376
+
377
+ response = requests.post(
378
+ "YOUR_SPACE_URL/api/predict",
379
+ json={"data": ["User login system requirements..."]}
380
+ )
381
+ result = response.json()
382
+ test_cases = result["data"][1] # JSON output
383
+ ```
384
+
385
+ ### Example Usage (cURL):
386
+ ```bash
387
+ curl -X POST "YOUR_SPACE_URL/api/predict" \\
388
+ -H "Content-Type: application/json" \\
389
+ -d '{"data":["Your SRS text here"]}'
390
+ ```
391
+
392
+ ## πŸ“‹ Response Format
393
+
394
+ The API returns test cases in this format:
395
+ ```json
396
+ {
397
+ "test_cases": [
398
+ {
399
+ "id": "TC_001",
400
+ "title": "Test Case Title",
401
+ "description": "Test description",
402
+ "steps": ["Step 1", "Step 2"],
403
+ "expected": "Expected result"
404
+ }
405
+ ],
406
+ "count": 3,
407
+ "model_used": "distilgpt2",
408
+ "model_algorithm": "Transformer-based LM",
409
+ "model_reason": "Selected for balanced performance..."
410
+ }
411
+ ```
412
+ """)
413
+
414
+ # Event handlers
415
+ generate_btn.click(
416
+ fn=gradio_generate_test_cases,
417
+ inputs=[srs_input],
418
+ outputs=[output_display, json_output]
419
+ )
420
+
421
+ refresh_status_btn.click(
422
+ fn=get_system_status,
423
+ outputs=[status_display]
424
+ )
425
+
426
+ refresh_model_btn.click(
427
+ fn=get_model_info_detailed,
428
+ outputs=[model_info_display]
429
  )
430
+
431
+ # Load initial status
432
+ app.load(
433
+ fn=get_system_status,
434
+ outputs=[status_display]
435
+ )
436
+
437
+ app.load(
438
+ fn=get_model_info_detailed,
439
+ outputs=[model_info_display]
440
+ )
441
+
442
+ # Launch the app
443
+ if __name__ == "__main__":
444
+ port = int(os.environ.get("PORT", 7860))
445
+
446
+ logger.info(f"πŸš€ Starting Gradio app on port {port}")
447
+ logger.info(f"πŸ–₯️ Environment: {'Hugging Face Spaces' if os.environ.get('SPACE_ID') else 'Local'}")
448
+
449
+ app.launch(
450
+ server_name="0.0.0.0",
451
+ server_port=port,
452
+ share=False,
453
+ show_error=True
454
+ )
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ