harshadh01 commited on
Commit
83673a6
·
verified ·
1 Parent(s): dc0c6d1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +414 -412
app.py CHANGED
@@ -1,412 +1,414 @@
1
- import streamlit as st
2
- import uuid
3
- import os
4
- import json
5
- import shutil
6
- import zipfile
7
- from typing import Dict, List
8
-
9
- from spec_validator import validate_and_clean_spec
10
- from pipeline import run_pipeline
11
-
12
-
13
-
14
- # =====================================================
15
- # CONFIG
16
- # =====================================================
17
- BASE_RUN_DIR = "runs"
18
- SCHEMA_PATH = "spec_schema.json"
19
-
20
- st.set_page_config(
21
- page_title="AI REST API Generator",
22
- layout="wide",
23
- )
24
-
25
- # =====================================================
26
- # HELPERS
27
- # =====================================================
28
-
29
- def create_run_dir():
30
- run_id = uuid.uuid4().hex[:8]
31
- run_dir = os.path.join(BASE_RUN_DIR, f"run_{run_id}")
32
- os.makedirs(run_dir, exist_ok=True)
33
- return run_id, run_dir
34
-
35
-
36
- def zip_folder(folder_path: str) -> str:
37
- zip_path = f"{folder_path}.zip"
38
- with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
39
- for root, _, files in os.walk(folder_path):
40
- for file in files:
41
- full_path = os.path.join(root, file)
42
- arcname = os.path.relpath(full_path, folder_path)
43
- zipf.write(full_path, arcname)
44
- return zip_path
45
-
46
-
47
- def load_schema():
48
- with open(SCHEMA_PATH, "r", encoding="utf-8") as f:
49
- return json.load(f)
50
-
51
-
52
- # =====================================================
53
- # SESSION STATE INIT
54
- # =====================================================
55
-
56
- if "spec" not in st.session_state:
57
- st.session_state.spec = {
58
- "project_name": "",
59
- "description": "",
60
- "database": {},
61
- "auth": {"type": "jwt"},
62
- "api_config": {"base_url": "/api/"},
63
- "apps": {"core": {"models": {}, "apis": {}}},
64
- "use_ai_models": True,
65
- "llm": {}
66
- }
67
-
68
- if "models_ui" not in st.session_state:
69
- st.session_state.models_ui = []
70
-
71
- if "logs" not in st.session_state:
72
- st.session_state.logs = []
73
-
74
- log_box = st.empty()
75
-
76
- def log(msg: str):
77
- st.session_state.logs.append(msg)
78
- log_box.write("\n".join(st.session_state.logs))
79
-
80
-
81
- # =====================================================
82
- # UI HEADER
83
- # =====================================================
84
-
85
- st.title("🤖 AI REST API Generator")
86
- st.markdown(
87
- """
88
- **Build Django REST APIs using AI or manual configuration.**
89
- 🚧 *This website is under active development.*
90
- """
91
- )
92
-
93
- st.divider()
94
-
95
- # =====================================================
96
- # SECTION 1 — LLM CONFIGURATION
97
- # =====================================================
98
- st.header("🔑 LLM Configuration")
99
-
100
- llm_provider = st.selectbox(
101
- "Select LLM Provider",
102
- ["Groq", "OpenAI"]
103
- )
104
-
105
- api_key = st.text_input(
106
- f"{llm_provider} API Key",
107
- type="password",
108
- help="Your API key is used only for this session and never stored."
109
- )
110
-
111
- st.session_state.spec["llm"] = {
112
- "provider": llm_provider.lower()
113
- }
114
-
115
-
116
-
117
-
118
- # =====================================================
119
- # SECTION 2 — PROJECT BASICS
120
- # =====================================================
121
- st.header("📦 Project Information")
122
-
123
- col1, col2 = st.columns(2)
124
-
125
- with col1:
126
- st.session_state.spec["project_name"] = st.text_input(
127
- "Project Name",
128
- placeholder="my_backend_project"
129
- )
130
-
131
- with col2:
132
- st.session_state.spec["description"] = st.text_area(
133
- "Project Description",
134
- placeholder="Short description of your backend"
135
- )
136
-
137
- # =====================================================
138
- # SECTION 3 — DATABASE CONFIGURATION
139
- # =====================================================
140
- st.header("🗄 Database Configuration")
141
-
142
- db_engine = st.selectbox(
143
- "Database Engine",
144
- ["sqlite", "postgresql", "mysql"]
145
- )
146
-
147
- if db_engine == "sqlite":
148
- st.session_state.spec["database"] = {
149
- "engine": "sqlite",
150
- "name": "db.sqlite3"
151
- }
152
- else:
153
- col1, col2 = st.columns(2)
154
- with col1:
155
- db_name = st.text_input("Database Name")
156
- db_user = st.text_input("Database User")
157
- db_password = st.text_input("Database Password", type="password")
158
- with col2:
159
- db_host = st.text_input("Database Host", value="localhost")
160
- db_port = st.number_input("Database Port", value=5432)
161
-
162
- st.session_state.spec["database"] = {
163
- "engine": db_engine,
164
- "name": db_name,
165
- "user": db_user,
166
- "password": db_password,
167
- "host": db_host,
168
- "port": db_port
169
- }
170
-
171
- # =====================================================
172
- # SECTION 4 MODEL MODE
173
- # =====================================================
174
- st.header("🧱 Model Generation")
175
-
176
- use_ai = st.radio(
177
- "How do you want to create models?",
178
- ["AI Generated Models", "Manual Model Builder"]
179
- )
180
-
181
- st.session_state.spec["use_ai_models"] = (use_ai == "AI Generated Models")
182
-
183
- # =====================================================
184
- # SECTION 5 — MANUAL MODEL BUILDER
185
- # =====================================================
186
- if not st.session_state.spec["use_ai_models"]:
187
- st.subheader("🛠 Manual Model Builder")
188
-
189
- if st.button("➕ Add Model"):
190
- st.session_state.models_ui.append({
191
- "name": "",
192
- "fields": []
193
- })
194
-
195
- model_names = []
196
-
197
- for mi, model in enumerate(st.session_state.models_ui):
198
- with st.expander(f"Model {mi + 1}", expanded=True):
199
- model["name"] = st.text_input(
200
- "Model Name",
201
- model["name"],
202
- key=f"model_{mi}"
203
- )
204
-
205
- if model["name"]:
206
- model_names.append(model["name"])
207
-
208
- if st.button("➕ Add Column", key=f"add_col_{mi}"):
209
- model["fields"].append({
210
- "name": "",
211
- "type": "CharField",
212
- "primary_key": False,
213
- "unique": False,
214
- "null": False,
215
- "relation": None,
216
- "on_delete": "CASCADE"
217
- })
218
-
219
- for fi, field in enumerate(model["fields"]):
220
- cols = st.columns(7)
221
-
222
- field["name"] = cols[0].text_input(
223
- "Column",
224
- field["name"],
225
- key=f"fname_{mi}_{fi}"
226
- )
227
-
228
- field["type"] = cols[1].selectbox(
229
- "Type",
230
- [
231
- "CharField",
232
- "IntegerField",
233
- "UUIDField",
234
- "BooleanField",
235
- "DateField",
236
- "OneToOne",
237
- "OneToMany"
238
- ],
239
- key=f"ftype_{mi}_{fi}"
240
- )
241
-
242
- field["primary_key"] = cols[2].checkbox(
243
- "PK",
244
- key=f"fpk_{mi}_{fi}"
245
- )
246
- field["unique"] = cols[3].checkbox(
247
- "Unique",
248
- key=f"funq_{mi}_{fi}"
249
- )
250
- field["null"] = cols[4].checkbox(
251
- "Null",
252
- key=f"fnull_{mi}_{fi}"
253
- )
254
-
255
- if field["type"] in ["OneToOne", "OneToMany"]:
256
- field["relation"] = cols[5].selectbox(
257
- "Reference Model",
258
- model_names,
259
- key=f"frel_{mi}_{fi}"
260
- )
261
- field["on_delete"] = cols[6].selectbox(
262
- "On Delete",
263
- ["CASCADE", "SET_NULL", "PROTECT"],
264
- key=f"fdel_{mi}_{fi}"
265
- )
266
-
267
- # Convert UI → spec
268
- models_spec = {}
269
- apis_spec = {}
270
-
271
- for model in st.session_state.models_ui:
272
- if not model["name"]:
273
- continue
274
-
275
- fields_spec = {}
276
-
277
- for f in model["fields"]:
278
- if not f["name"]:
279
- continue
280
-
281
- if f["type"] == "OneToOne":
282
- fields_spec[f["name"]] = {
283
- "type": "OneToOneField",
284
- "to": f["relation"],
285
- "on_delete": f["on_delete"],
286
- "null": f["null"],
287
- "unique": True
288
- }
289
- elif f["type"] == "OneToMany":
290
- fields_spec[f["name"]] = {
291
- "type": "ForeignKey",
292
- "to": f["relation"],
293
- "on_delete": f["on_delete"],
294
- "null": f["null"]
295
- }
296
- else:
297
- fields_spec[f["name"]] = {
298
- "type": f["type"],
299
- "primary_key": f["primary_key"],
300
- "unique": f["unique"],
301
- "null": f["null"]
302
- }
303
-
304
- models_spec[model["name"]] = {"fields": fields_spec}
305
- apis_spec[model["name"]] = ["list", "create", "retrieve", "update", "delete"]
306
-
307
- st.session_state.spec["apps"]["core"]["models"] = models_spec
308
- st.session_state.spec["apps"]["core"]["apis"] = apis_spec
309
-
310
- # =====================================================
311
- # SECTION 6 GENERATION
312
- # =====================================================
313
- st.header("🚀 Generate Project")
314
-
315
-
316
-
317
- # UI elements (define once, above button)
318
- progress_bar = st.progress(0)
319
-
320
-
321
- def ui_log(msg):
322
- st.session_state.logs.append(msg)
323
- log_box.write("\n".join(st.session_state.logs))
324
-
325
- def ui_progress(value):
326
- progress_bar.progress(value)
327
-
328
-
329
- if st.button("Generate Backend"):
330
- # ----------------------------
331
- # 1️⃣ Basic validation
332
- # ----------------------------
333
- if not api_key:
334
- st.error("API key is required to generate the project.")
335
- st.stop()
336
-
337
- st.session_state.logs.clear()
338
- progress_bar.progress(0)
339
-
340
- # ----------------------------
341
- # 2️⃣ Load schema & validate UI spec
342
- # ----------------------------
343
- schema = load_schema()
344
-
345
- valid, cleaned_spec, errors, warnings = validate_and_clean_spec(
346
- st.session_state.spec,
347
- schema
348
- )
349
-
350
- if not valid:
351
- st.error("Specification validation failed.")
352
- st.json(errors)
353
- st.stop()
354
-
355
- # ----------------------------
356
- # 3️⃣ Create isolated run directory
357
- # ----------------------------
358
- run_id, run_dir = create_run_dir()
359
- ui_log(f"🆔 Run ID: {run_id}")
360
- ui_log("🚀 Starting generation pipeline...")
361
-
362
- # ----------------------------
363
- # 4️⃣ Run backend pipeline
364
- # ----------------------------
365
- try:
366
- run_pipeline(
367
- spec=cleaned_spec,
368
- run_dir=run_dir,
369
- llm_provider=llm_provider.lower(),
370
- api_key=api_key,
371
- log_callback=ui_log,
372
- progress_callback=ui_progress
373
- )
374
-
375
- ui_log("📦 Zipping project...")
376
- zip_path = zip_folder(run_dir)
377
-
378
- ui_log("✅ Project ready for download")
379
-
380
- with open(zip_path, "rb") as f:
381
- st.download_button(
382
- "⬇ Download Generated Project",
383
- f,
384
- file_name=f"{cleaned_spec['project_name']}.zip"
385
- )
386
-
387
- # ----------------------------
388
- # 5️⃣ Error handling
389
- # ----------------------------
390
- except Exception as e:
391
- ui_log("❌ Pipeline failed")
392
- ui_log(str(e))
393
- st.error(f"Generation failed at step: {e}")
394
-
395
-
396
- # ----------------------------
397
- # 6️⃣ Cleanup (safe)
398
- # ----------------------------
399
- finally:
400
- shutil.rmtree(run_dir, ignore_errors=True)
401
-
402
-
403
- # =====================================================
404
- # FOOTER
405
- # =====================================================
406
- st.divider()
407
- st.markdown(
408
- """
409
- 📧 **Contact:** harshadhole04@gmail.com
410
- © AI REST API Generator — Under Development
411
- """
412
- )
 
 
 
1
+ import streamlit as st
2
+ import uuid
3
+ import os
4
+ import json
5
+ import shutil
6
+ import zipfile
7
+ from typing import Dict, List
8
+
9
+ from spec_validator import validate_and_clean_spec
10
+ from pipeline import run_pipeline
11
+
12
+
13
+
14
+ # =====================================================
15
+ # CONFIG
16
+ # =====================================================
17
+ BASE_RUN_DIR = "runs"
18
+ SCHEMA_PATH = "spec_schema.json"
19
+
20
+ st.set_page_config(
21
+ page_title="AI REST API Generator",
22
+ layout="wide",
23
+ )
24
+
25
+ # =====================================================
26
+ # HELPERS
27
+ # =====================================================
28
+
29
+ def create_run_dir():
30
+ run_id = uuid.uuid4().hex[:8]
31
+ run_dir = os.path.join(BASE_RUN_DIR, f"run_{run_id}")
32
+ os.makedirs(run_dir, exist_ok=True)
33
+ return run_id, run_dir
34
+
35
+
36
+ def zip_folder(folder_path: str) -> str:
37
+ zip_path = f"{folder_path}.zip"
38
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
39
+ for root, _, files in os.walk(folder_path):
40
+ for file in files:
41
+ full_path = os.path.join(root, file)
42
+ arcname = os.path.relpath(full_path, folder_path)
43
+ zipf.write(full_path, arcname)
44
+ return zip_path
45
+
46
+
47
+ def load_schema():
48
+ with open(SCHEMA_PATH, "r", encoding="utf-8") as f:
49
+ return json.load(f)
50
+
51
+
52
+ # =====================================================
53
+ # SESSION STATE INIT
54
+ # =====================================================
55
+
56
+ if "spec" not in st.session_state:
57
+ st.session_state.spec = {
58
+ "project_name": "",
59
+ "description": "",
60
+ "database": {},
61
+ "auth": {"type": "jwt"},
62
+ "api_config": {"base_url": "/api/"},
63
+ "apps": {"core": {"models": {}, "apis": {}}},
64
+ "use_ai_models": True,
65
+ "llm": {}
66
+ }
67
+
68
+ if "models_ui" not in st.session_state:
69
+ st.session_state.models_ui = []
70
+
71
+ if "logs" not in st.session_state:
72
+ st.session_state.logs = []
73
+
74
+
75
+ # =====================================================
76
+ # UI HEADER
77
+ # =====================================================
78
+
79
+ st.title("🤖 AI REST API Generator")
80
+ st.markdown(
81
+ """
82
+ **Build Django REST APIs using AI or manual configuration.**
83
+ 🚧 *This website is under active development.*
84
+ """
85
+ )
86
+
87
+ st.divider()
88
+
89
+ # =====================================================
90
+ # SECTION 1 — LLM CONFIGURATION
91
+ # =====================================================
92
+ st.header("🔑 LLM Configuration")
93
+
94
+ llm_provider = st.selectbox(
95
+ "Select LLM Provider",
96
+ ["Groq", "OpenAI"]
97
+ )
98
+
99
+ api_key = st.text_input(
100
+ f"{llm_provider} API Key",
101
+ type="password",
102
+ help="Your API key is used only for this session and never stored."
103
+ )
104
+
105
+ st.session_state.spec["llm"] = {
106
+ "provider": llm_provider.lower()
107
+ }
108
+
109
+
110
+
111
+
112
+ # =====================================================
113
+ # SECTION 2 — PROJECT BASICS
114
+ # =====================================================
115
+ st.header("📦 Project Information")
116
+
117
+ col1, col2 = st.columns(2)
118
+
119
+ with col1:
120
+ st.session_state.spec["project_name"] = st.text_input(
121
+ "Project Name",
122
+ placeholder="my_backend_project"
123
+ )
124
+
125
+ with col2:
126
+ st.session_state.spec["description"] = st.text_area(
127
+ "Project Description",
128
+ placeholder="Short description of your backend"
129
+ )
130
+
131
+ # =====================================================
132
+ # SECTION 3 — DATABASE CONFIGURATION
133
+ # =====================================================
134
+ st.header("🗄 Database Configuration")
135
+
136
+ db_engine = st.selectbox(
137
+ "Database Engine",
138
+ ["sqlite", "postgresql", "mysql"]
139
+ )
140
+
141
+ if db_engine == "sqlite":
142
+ st.session_state.spec["database"] = {
143
+ "engine": "sqlite",
144
+ "name": "db.sqlite3"
145
+ }
146
+ else:
147
+ col1, col2 = st.columns(2)
148
+ with col1:
149
+ db_name = st.text_input("Database Name")
150
+ db_user = st.text_input("Database User")
151
+ db_password = st.text_input("Database Password", type="password")
152
+ with col2:
153
+ db_host = st.text_input("Database Host", value="localhost")
154
+ db_port = st.number_input("Database Port", value=5432)
155
+
156
+ st.session_state.spec["database"] = {
157
+ "engine": db_engine,
158
+ "name": db_name,
159
+ "user": db_user,
160
+ "password": db_password,
161
+ "host": db_host,
162
+ "port": db_port
163
+ }
164
+
165
+ # =====================================================
166
+ # SECTION 4 — MODEL MODE
167
+ # =====================================================
168
+ st.header("🧱 Model Generation")
169
+
170
+ use_ai = st.radio(
171
+ "How do you want to create models?",
172
+ ["AI Generated Models", "Manual Model Builder"]
173
+ )
174
+
175
+ st.session_state.spec["use_ai_models"] = (use_ai == "AI Generated Models")
176
+
177
+ # =====================================================
178
+ # SECTION 5 MANUAL MODEL BUILDER
179
+ # =====================================================
180
+ if not st.session_state.spec["use_ai_models"]:
181
+ st.subheader("🛠 Manual Model Builder")
182
+
183
+ if st.button("➕ Add Model"):
184
+ st.session_state.models_ui.append({
185
+ "name": "",
186
+ "fields": []
187
+ })
188
+
189
+ model_names = []
190
+
191
+ for mi, model in enumerate(st.session_state.models_ui):
192
+ with st.expander(f"Model {mi + 1}", expanded=True):
193
+ model["name"] = st.text_input(
194
+ "Model Name",
195
+ model["name"],
196
+ key=f"model_{mi}"
197
+ )
198
+
199
+ if model["name"]:
200
+ model_names.append(model["name"])
201
+
202
+ if st.button("➕ Add Column", key=f"add_col_{mi}"):
203
+ model["fields"].append({
204
+ "name": "",
205
+ "type": "CharField",
206
+ "primary_key": False,
207
+ "unique": False,
208
+ "null": False,
209
+ "relation": None,
210
+ "on_delete": "CASCADE"
211
+ })
212
+
213
+ for fi, field in enumerate(model["fields"]):
214
+ cols = st.columns(7)
215
+
216
+ field["name"] = cols[0].text_input(
217
+ "Column",
218
+ field["name"],
219
+ key=f"fname_{mi}_{fi}"
220
+ )
221
+
222
+ field["type"] = cols[1].selectbox(
223
+ "Type",
224
+ [
225
+ "CharField",
226
+ "IntegerField",
227
+ "UUIDField",
228
+ "BooleanField",
229
+ "DateField",
230
+ "OneToOne",
231
+ "OneToMany"
232
+ ],
233
+ key=f"ftype_{mi}_{fi}"
234
+ )
235
+
236
+ field["primary_key"] = cols[2].checkbox(
237
+ "PK",
238
+ key=f"fpk_{mi}_{fi}"
239
+ )
240
+ field["unique"] = cols[3].checkbox(
241
+ "Unique",
242
+ key=f"funq_{mi}_{fi}"
243
+ )
244
+ field["null"] = cols[4].checkbox(
245
+ "Null",
246
+ key=f"fnull_{mi}_{fi}"
247
+ )
248
+
249
+ if field["type"] in ["OneToOne", "OneToMany"]:
250
+ field["relation"] = cols[5].selectbox(
251
+ "Reference Model",
252
+ model_names,
253
+ key=f"frel_{mi}_{fi}"
254
+ )
255
+ field["on_delete"] = cols[6].selectbox(
256
+ "On Delete",
257
+ ["CASCADE", "SET_NULL", "PROTECT"],
258
+ key=f"fdel_{mi}_{fi}"
259
+ )
260
+
261
+ # Convert UI → spec
262
+ models_spec = {}
263
+ apis_spec = {}
264
+
265
+ for model in st.session_state.models_ui:
266
+ if not model["name"]:
267
+ continue
268
+
269
+ fields_spec = {}
270
+
271
+ for f in model["fields"]:
272
+ if not f["name"]:
273
+ continue
274
+
275
+ if f["type"] == "OneToOne":
276
+ fields_spec[f["name"]] = {
277
+ "type": "OneToOneField",
278
+ "to": f["relation"],
279
+ "on_delete": f["on_delete"],
280
+ "null": f["null"],
281
+ "unique": True
282
+ }
283
+ elif f["type"] == "OneToMany":
284
+ fields_spec[f["name"]] = {
285
+ "type": "ForeignKey",
286
+ "to": f["relation"],
287
+ "on_delete": f["on_delete"],
288
+ "null": f["null"]
289
+ }
290
+ else:
291
+ fields_spec[f["name"]] = {
292
+ "type": f["type"],
293
+ "primary_key": f["primary_key"],
294
+ "unique": f["unique"],
295
+ "null": f["null"]
296
+ }
297
+
298
+ models_spec[model["name"]] = {"fields": fields_spec}
299
+ apis_spec[model["name"]] = ["list", "create", "retrieve", "update", "delete"]
300
+
301
+ st.session_state.spec["apps"]["core"]["models"] = models_spec
302
+ st.session_state.spec["apps"]["core"]["apis"] = apis_spec
303
+
304
+ # =====================================================
305
+ # SECTION 6 GENERATION
306
+ # =====================================================
307
+ st.header("🚀 Generate Project")
308
+
309
+
310
+
311
+ # UI elements (define once, above button)
312
+ progress_bar = st.progress(0)
313
+
314
+ log_box = st.empty()
315
+
316
+ def log(msg: str):
317
+ st.session_state.logs.append(msg)
318
+ log_box.code(
319
+ "\n".join(st.session_state.logs),
320
+ language="text"
321
+ )
322
+
323
+ def ui_log(msg):
324
+ st.session_state.logs.append(msg)
325
+ log_box.write("\n".join(st.session_state.logs))
326
+
327
+ def ui_progress(value):
328
+ progress_bar.progress(value)
329
+
330
+
331
+ if st.button("Generate Backend"):
332
+ # ----------------------------
333
+ # 1️⃣ Basic validation
334
+ # ----------------------------
335
+ if not api_key:
336
+ st.error("API key is required to generate the project.")
337
+ st.stop()
338
+
339
+ st.session_state.logs.clear()
340
+ progress_bar.progress(0)
341
+
342
+ # ----------------------------
343
+ # 2️⃣ Load schema & validate UI spec
344
+ # ----------------------------
345
+ schema = load_schema()
346
+
347
+ valid, cleaned_spec, errors, warnings = validate_and_clean_spec(
348
+ st.session_state.spec,
349
+ schema
350
+ )
351
+
352
+ if not valid:
353
+ st.error("Specification validation failed.")
354
+ st.json(errors)
355
+ st.stop()
356
+
357
+ # ----------------------------
358
+ # 3️⃣ Create isolated run directory
359
+ # ----------------------------
360
+ run_id, run_dir = create_run_dir()
361
+ ui_log(f"🆔 Run ID: {run_id}")
362
+ ui_log("🚀 Starting generation pipeline...")
363
+
364
+ # ----------------------------
365
+ # 4️⃣ Run backend pipeline
366
+ # ----------------------------
367
+ try:
368
+ run_pipeline(
369
+ spec=cleaned_spec,
370
+ run_dir=run_dir,
371
+ llm_provider=llm_provider.lower(),
372
+ api_key=api_key,
373
+ log_callback=ui_log,
374
+ progress_callback=ui_progress
375
+ )
376
+
377
+ ui_log("📦 Zipping project...")
378
+ zip_path = zip_folder(run_dir)
379
+
380
+ ui_log(" Project ready for download")
381
+
382
+ with open(zip_path, "rb") as f:
383
+ st.download_button(
384
+ "⬇ Download Generated Project",
385
+ f,
386
+ file_name=f"{cleaned_spec['project_name']}.zip"
387
+ )
388
+
389
+ # ----------------------------
390
+ # 5️⃣ Error handling
391
+ # ----------------------------
392
+ except Exception as e:
393
+ ui_log(" Pipeline failed")
394
+ ui_log(str(e))
395
+ st.error(f"Generation failed at step: {e}")
396
+
397
+
398
+ # ----------------------------
399
+ # 6️⃣ Cleanup (safe)
400
+ # ----------------------------
401
+ finally:
402
+ shutil.rmtree(run_dir, ignore_errors=True)
403
+
404
+
405
+ # =====================================================
406
+ # FOOTER
407
+ # =====================================================
408
+ st.divider()
409
+ st.markdown(
410
+ """
411
+ 📧 **Contact:** harshadhole04@gmail.com
412
+ © AI REST API Generator — Under Development
413
+ """
414
+ )