Sai Kumar Taraka commited on
Commit
a259d11
Β·
1 Parent(s): 882a0d4

fix: Simplify Dockerfile, add embedded UI to avoid npm build

Browse files

- Simplify Dockerfile: remove multi-stage build, use single python:3.11-slim stage
- Add embedded HTML UI directly in server.py with Tailwind CDN
- UI includes: spec editor, examples, options, run button, results display
- Supports UART, SPI, I2C example specs
- Uses JSZip for download (CDN), Tailwind CSS (CDN)
- No npm build required - works out of the box
- Update README.md HF config to use port 7860

Files changed (2) hide show
  1. Dockerfile +4 -18
  2. src/api/server.py +585 -5
Dockerfile CHANGED
@@ -1,40 +1,26 @@
1
- # Multi-stage: frontend build + Python backend
2
 
3
- # Stage 1: Build React frontend
4
- FROM node:20-alpine AS frontend-builder
5
  WORKDIR /app
6
- COPY frontend/package.json frontend/package-lock.json ./
7
- RUN npm ci --omit=dev
8
- COPY frontend/ .
9
- RUN npm run build
10
-
11
- # Stage 2: Python backend
12
- FROM python:3.11-slim AS backend-builder
13
 
14
  RUN apt-get update && apt-get install -y --no-install-recommends \
15
- iverilog \
16
  && rm -rf /var/lib/apt/lists/*
17
 
18
- WORKDIR /app
19
  COPY requirements.txt .
20
  RUN pip install --no-cache-dir --upgrade pip && \
21
  pip install --no-cache-dir -r requirements.txt
22
 
23
  COPY . .
24
- COPY --from=frontend-builder /app/build /app/frontend/build
25
 
26
  RUN pip install --no-cache-dir -e .
27
 
28
  RUN groupadd -r uvmgen && useradd -r -g uvmgen -d /app -s /sbin/nologin uvmgen
29
  RUN mkdir -p /app/output /app/logs /var/data && chown -R uvmgen:uvmgen /app /var/data
30
 
31
- EXPOSE 8000
32
 
33
  ENV UVMGEN_OUTPUT_DIR=/var/data/uvmgen_output
34
 
35
- HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
36
- CMD python -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8000/api/health').read().decode())"
37
-
38
  USER uvmgen
39
 
40
- CMD ["uvicorn", "src.api.server:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--log-level", "info"]
 
1
+ FROM python:3.11-slim
2
 
 
 
3
  WORKDIR /app
 
 
 
 
 
 
 
4
 
5
  RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ git \
7
  && rm -rf /var/lib/apt/lists/*
8
 
 
9
  COPY requirements.txt .
10
  RUN pip install --no-cache-dir --upgrade pip && \
11
  pip install --no-cache-dir -r requirements.txt
12
 
13
  COPY . .
 
14
 
15
  RUN pip install --no-cache-dir -e .
16
 
17
  RUN groupadd -r uvmgen && useradd -r -g uvmgen -d /app -s /sbin/nologin uvmgen
18
  RUN mkdir -p /app/output /app/logs /var/data && chown -R uvmgen:uvmgen /app /var/data
19
 
20
+ EXPOSE 7860
21
 
22
  ENV UVMGEN_OUTPUT_DIR=/var/data/uvmgen_output
23
 
 
 
 
24
  USER uvmgen
25
 
26
+ CMD ["uvicorn", "src.api.server:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1", "--log-level", "info"]
src/api/server.py CHANGED
@@ -216,7 +216,587 @@ _IS_BUILT = FRONTEND_BUILD.exists()
216
  if _IS_BUILT:
217
  logger.info("Serving frontend from %s", FRONTEND_BUILD)
218
 
219
- from fastapi.responses import HTMLResponse
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
 
222
  @app.get("/", include_in_schema=False)
@@ -226,17 +806,17 @@ async def serve_index():
226
  index = FRONTEND_BUILD / "index.html"
227
  if index.exists():
228
  return FileResponse(str(index))
229
- return HTMLResponse("<h1>UVM TB Generator API</h1><p>Frontend not built. Run <code>cd frontend && npm run build</code></p>")
230
 
231
 
232
  @app.get("/static/{rest_of_path:path}", include_in_schema=False)
233
  async def serve_static(rest_of_path: str):
234
  if not _IS_BUILT:
235
- return JSONResponse(404, {"error": "Not found"})
236
  file_path = FRONTEND_BUILD / "static" / rest_of_path
237
  if file_path.exists() and file_path.is_file():
238
  return FileResponse(str(file_path))
239
- return JSONResponse(404, {"error": "Not found"})
240
 
241
 
242
  @app.get("/{full_path:path}", include_in_schema=False)
@@ -250,7 +830,7 @@ async def serve_spa(full_path: str):
250
  index = FRONTEND_BUILD / "index.html"
251
  if index.exists():
252
  return FileResponse(str(index))
253
- return JSONResponse(404, {"error": "Not found"})
254
 
255
 
256
  # ── Direct execution ───────────────────────────────────────────────
 
216
  if _IS_BUILT:
217
  logger.info("Serving frontend from %s", FRONTEND_BUILD)
218
 
219
+ EMBEDDED_UI = """
220
+ <!DOCTYPE html>
221
+ <html lang="en">
222
+ <head>
223
+ <meta charset="UTF-8">
224
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
225
+ <title>UVM Testbench Generator</title>
226
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
227
+ <style>
228
+ .code-editor {
229
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
230
+ font-size: 13px;
231
+ tab-size: 2;
232
+ }
233
+ .fade-in {
234
+ animation: fadeIn 0.3s ease-in;
235
+ }
236
+ @keyframes fadeIn {
237
+ from { opacity: 0; transform: translateY(-10px); }
238
+ to { opacity: 1; transform: translateY(0); }
239
+ }
240
+ .pulse {
241
+ animation: pulse 2s infinite;
242
+ }
243
+ @keyframes pulse {
244
+ 0%, 100% { opacity: 1; }
245
+ 50% { opacity: 0.5; }
246
+ }
247
+ </style>
248
+ </head>
249
+ <body class="bg-gray-900 text-gray-100 min-h-screen">
250
+ <div id="app">
251
+ <!-- Header -->
252
+ <header class="bg-gray-800 border-b border-gray-700">
253
+ <div class="max-w-7xl mx-auto px-4 py-4">
254
+ <div class="flex items-center justify-between">
255
+ <div class="flex items-center gap-3">
256
+ <div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-lg flex items-center justify-center">
257
+ <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
258
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"></path>
259
+ </svg>
260
+ </div>
261
+ <div>
262
+ <h1 class="text-xl font-bold text-white">UVM Testbench Generator</h1>
263
+ <p class="text-sm text-gray-400">AI-Powered Semiconductor Verification Pipeline</p>
264
+ </div>
265
+ </div>
266
+ <div class="flex items-center gap-2 text-sm text-gray-400">
267
+ <span class="w-2 h-2 bg-green-500 rounded-full"></span>
268
+ <span>API Online</span>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </header>
273
+
274
+ <!-- Main Content -->
275
+ <main class="max-w-7xl mx-auto px-4 py-8">
276
+ <div class="grid lg:grid-cols-2 gap-8">
277
+ <!-- Input Section -->
278
+ <div class="space-y-6">
279
+ <!-- Spec Editor -->
280
+ <div class="bg-gray-800 rounded-xl border border-gray-700 overflow-hidden">
281
+ <div class="px-4 py-3 bg-gray-750 border-b border-gray-700 flex items-center justify-between">
282
+ <h2 class="font-semibold text-gray-200">Specification</h2>
283
+ <select id="example-select" class="bg-gray-700 text-gray-200 text-sm rounded px-3 py-1 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
284
+ <option value="uart">UART Example</option>
285
+ <option value="spi">SPI Example</option>
286
+ <option value="i2c">I2C Example</option>
287
+ </select>
288
+ </div>
289
+ <div class="p-4">
290
+ <textarea id="spec-editor" class="code-editor w-full h-96 bg-gray-900 text-green-400 p-4 rounded-lg border border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none" placeholder="Paste your YAML or .core specification here..."></textarea>
291
+ </div>
292
+ </div>
293
+
294
+ <!-- Options -->
295
+ <div class="bg-gray-800 rounded-xl border border-gray-700 p-6">
296
+ <h2 class="font-semibold text-gray-200 mb-4">Options</h2>
297
+ <div class="grid grid-cols-2 gap-4">
298
+ <div>
299
+ <label class="block text-sm text-gray-400 mb-1">Design Name</label>
300
+ <input type="text" id="design-name" value="my_design" class="w-full bg-gray-900 text-gray-200 px-3 py-2 rounded border border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
301
+ </div>
302
+ <div>
303
+ <label class="block text-sm text-gray-400 mb-1">Max Iterations</label>
304
+ <input type="number" id="max-iterations" value="1" min="1" max="50" class="w-full bg-gray-900 text-gray-200 px-3 py-2 rounded border border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
305
+ </div>
306
+ </div>
307
+ <div class="mt-4 flex items-center gap-2">
308
+ <input type="checkbox" id="auto-train" class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500">
309
+ <label for="auto-train" class="text-sm text-gray-300">Enable Auto-Training (Coverage-Driven)</label>
310
+ </div>
311
+ </div>
312
+
313
+ <!-- Run Button -->
314
+ <button id="run-btn" class="w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white font-semibold py-4 px-6 rounded-xl transition-all duration-200 transform hover:scale-[1.02] active:scale-[0.98] flex items-center justify-center gap-2">
315
+ <svg id="run-icon" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
316
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path>
317
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
318
+ </svg>
319
+ <span id="btn-text">Generate UVM Testbench</span>
320
+ </button>
321
+ </div>
322
+
323
+ <!-- Output Section -->
324
+ <div class="space-y-6">
325
+ <!-- Status -->
326
+ <div id="status-panel" class="hidden bg-gray-800 rounded-xl border border-gray-700 p-6 fade-in">
327
+ <div class="flex items-center gap-3 mb-4">
328
+ <div id="status-icon" class="w-8 h-8 rounded-full flex items-center justify-center">
329
+ </div>
330
+ <div>
331
+ <h3 id="status-title" class="font-semibold text-gray-200"></h3>
332
+ <p id="status-message" class="text-sm text-gray-400"></p>
333
+ </div>
334
+ </div>
335
+ <div id="progress-bar" class="hidden">
336
+ <div class="w-full bg-gray-700 rounded-full h-2">
337
+ <div class="bg-blue-600 h-2 rounded-full pulse" style="width: 100%"></div>
338
+ </div>
339
+ </div>
340
+ </div>
341
+
342
+ <!-- Results -->
343
+ <div id="results-panel" class="hidden space-y-6 fade-in">
344
+ <!-- Metrics -->
345
+ <div class="bg-gray-800 rounded-xl border border-gray-700 p-6">
346
+ <h3 class="font-semibold text-gray-200 mb-4">Generation Metrics</h3>
347
+ <div class="grid grid-cols-3 gap-4">
348
+ <div class="text-center p-3 bg-gray-900 rounded-lg">
349
+ <p id="metric-files" class="text-2xl font-bold text-blue-400">-</p>
350
+ <p class="text-xs text-gray-400">Files Generated</p>
351
+ </div>
352
+ <div class="text-center p-3 bg-gray-900 rounded-lg">
353
+ <p id="metric-status" class="text-2xl font-bold text-green-400">-</p>
354
+ <p class="text-xs text-gray-400">Status</p>
355
+ </div>
356
+ <div class="text-center p-3 bg-gray-900 rounded-lg">
357
+ <p id="metric-iterations" class="text-2xl font-bold text-indigo-400">-</p>
358
+ <p class="text-xs text-gray-400">Iterations</p>
359
+ </div>
360
+ </div>
361
+ </div>
362
+
363
+ <!-- Generated Files -->
364
+ <div class="bg-gray-800 rounded-xl border border-gray-700 overflow-hidden">
365
+ <div class="px-4 py-3 bg-gray-750 border-b border-gray-700">
366
+ <h3 class="font-semibold text-gray-200">Generated Files</h3>
367
+ </div>
368
+ <div class="p-4 max-h-96 overflow-y-auto">
369
+ <div id="files-list" class="space-y-2">
370
+ </div>
371
+ </div>
372
+ </div>
373
+
374
+ <!-- Download Button -->
375
+ <button id="download-btn" class="hidden w-full bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white font-semibold py-3 px-6 rounded-xl transition-all">
376
+ <span class="flex items-center justify-center gap-2">
377
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
378
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
379
+ </svg>
380
+ Download All Files as ZIP
381
+ </span>
382
+ </button>
383
+ </div>
384
+
385
+ <!-- Logs -->
386
+ <div id="logs-panel" class="hidden bg-gray-800 rounded-xl border border-gray-700 overflow-hidden">
387
+ <div class="px-4 py-3 bg-gray-750 border-b border-gray-700 flex items-center justify-between">
388
+ <h3 class="font-semibold text-gray-200">Logs</h3>
389
+ <button id="clear-logs" class="text-xs text-gray-400 hover:text-gray-200">Clear</button>
390
+ </div>
391
+ <div class="p-4 max-h-64 overflow-y-auto">
392
+ <div id="logs" class="code-editor text-xs text-gray-300 space-y-1">
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+ </main>
399
+
400
+ <!-- Footer -->
401
+ <footer class="mt-auto py-6 border-t border-gray-800">
402
+ <div class="max-w-7xl mx-auto px-4 text-center text-sm text-gray-500">
403
+ <p>UVM Testbench Generator | AI-Powered by <span class="text-blue-400 font-semibold">Sai Kumar Taraka</span></p>
404
+ </div>
405
+ </footer>
406
+ </div>
407
+
408
+ <script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
409
+ <script>
410
+ // Example specs
411
+ const examples = {
412
+ uart: `design_name: uart
413
+ clock_reset:
414
+ clock: clk
415
+ reset: rst_n
416
+
417
+ interfaces:
418
+ - name: wb
419
+ signals:
420
+ - name: wb_cyc
421
+ direction: input
422
+ - name: wb_stb
423
+ direction: input
424
+ - name: wb_we
425
+ direction: input
426
+ - name: wb_addr
427
+ direction: input
428
+ width: 3
429
+ - name: wb_data_o
430
+ direction: output
431
+ width: 8
432
+ - name: wb_data_i
433
+ direction: input
434
+ width: 8
435
+ - name: wb_ack
436
+ direction: output
437
+
438
+ - name: uart
439
+ signals:
440
+ - name: uart_tx
441
+ direction: output
442
+ - name: uart_rx
443
+ direction: input
444
+
445
+ registers:
446
+ - name: RBR_THR
447
+ address: 0x0
448
+ description: Receiver Buffer / Transmitter Holding
449
+ - name: IER
450
+ address: 0x1
451
+ description: Interrupt Enable
452
+ - name: IIR
453
+ address: 0x2
454
+ description: Interrupt Identification
455
+ - name: LCR
456
+ address: 0x3
457
+ description: Line Control
458
+ - name: MCR
459
+ address: 0x4
460
+ description: Modem Control
461
+ - name: LSR
462
+ address: 0x5
463
+ description: Line Status
464
+ - name: MSR
465
+ address: 0x6
466
+ description: Modem Status
467
+ - name: SCR
468
+ address: 0x7
469
+ description: Scratch
470
+
471
+ protocol: uart`,
472
+ spi: `design_name: spi_controller
473
+ clock_reset:
474
+ clock: clk
475
+ reset: rst_n
476
+
477
+ interfaces:
478
+ - name: apb
479
+ signals:
480
+ - name: psel
481
+ direction: input
482
+ - name: penable
483
+ direction: input
484
+ - name: pwrite
485
+ direction: input
486
+ - name: paddr
487
+ direction: input
488
+ width: 8
489
+ - name: pwdata
490
+ direction: input
491
+ width: 32
492
+ - name: prdata
493
+ direction: output
494
+ width: 32
495
+ - name: pready
496
+ direction: output
497
+
498
+ - name: spi
499
+ signals:
500
+ - name: sclk
501
+ direction: output
502
+ - name: mosi
503
+ direction: output
504
+ - name: miso
505
+ direction: input
506
+ - name: cs_n
507
+ direction: output
508
+ width: 4
509
+
510
+ registers:
511
+ - name: CTRL
512
+ address: 0x0
513
+ description: Control Register
514
+ - name: TXDATA
515
+ address: 0x4
516
+ description: TX Data
517
+ - name: RXDATA
518
+ address: 0x8
519
+ description: RX Data
520
+ - name: STATUS
521
+ address: 0xC
522
+ description: Status Register
523
+ - name: DIVIDER
524
+ address: 0x10
525
+ description: Clock Divider
526
+ - name: CS
527
+ address: 0x14
528
+ description: Chip Select
529
+
530
+ protocol: spi`,
531
+ i2c: `design_name: i2c_master
532
+ clock_reset:
533
+ clock: clk
534
+ reset: rst_n
535
+
536
+ interfaces:
537
+ - name: axi4lite
538
+ signals:
539
+ - name: awvalid
540
+ direction: input
541
+ - name: awready
542
+ direction: output
543
+ - name: awaddr
544
+ direction: input
545
+ width: 16
546
+ - name: wvalid
547
+ direction: input
548
+ - name: wready
549
+ direction: output
550
+ - name: wdata
551
+ direction: input
552
+ width: 32
553
+ - name: bvalid
554
+ direction: output
555
+ - name: bready
556
+ direction: input
557
+ - name: arvalid
558
+ direction: input
559
+ - name: arready
560
+ direction: output
561
+ - name: araddr
562
+ direction: input
563
+ width: 16
564
+ - name: rvalid
565
+ direction: output
566
+ - name: rready
567
+ direction: input
568
+ - name: rdata
569
+ direction: output
570
+ width: 32
571
+
572
+ - name: i2c
573
+ signals:
574
+ - name: scl
575
+ direction: inout
576
+ - name: sda
577
+ direction: inout
578
+
579
+ registers:
580
+ - name: PRESCALE
581
+ address: 0x0
582
+ description: Clock Prescale
583
+ - name: CTRL
584
+ address: 0x4
585
+ description: Control
586
+ - name: TX_RX
587
+ address: 0x8
588
+ description: TX/RX Data
589
+ - name: CMD_STATUS
590
+ address: 0xC
591
+ description: Command / Status
592
+
593
+ protocol: i2c`
594
+ };
595
+
596
+ // State
597
+ let generatedFiles = {};
598
+ let isRunning = false;
599
+
600
+ // DOM Elements
601
+ const specEditor = document.getElementById('spec-editor');
602
+ const exampleSelect = document.getElementById('example-select');
603
+ const designNameInput = document.getElementById('design-name');
604
+ const maxIterationsInput = document.getElementById('max-iterations');
605
+ const autoTrainCheckbox = document.getElementById('auto-train');
606
+ const runBtn = document.getElementById('run-btn');
607
+ const btnText = document.getElementById('btn-text');
608
+ const runIcon = document.getElementById('run-icon');
609
+ const statusPanel = document.getElementById('status-panel');
610
+ const statusIcon = document.getElementById('status-icon');
611
+ const statusTitle = document.getElementById('status-title');
612
+ const statusMessage = document.getElementById('status-message');
613
+ const progressBar = document.getElementById('progress-bar');
614
+ const resultsPanel = document.getElementById('results-panel');
615
+ const logsPanel = document.getElementById('logs-panel');
616
+ const logsDiv = document.getElementById('logs');
617
+ const filesList = document.getElementById('files-list');
618
+ const downloadBtn = document.getElementById('download-btn');
619
+ const clearLogsBtn = document.getElementById('clear-logs');
620
+
621
+ // Initialize
622
+ specEditor.value = examples.uart;
623
+
624
+ // Example selection
625
+ exampleSelect.addEventListener('change', () => {
626
+ specEditor.value = examples[exampleSelect.value];
627
+ });
628
+
629
+ // Log function
630
+ function log(message, type = 'info') {
631
+ const timestamp = new Date().toLocaleTimeString();
632
+ const color = {
633
+ info: 'text-gray-300',
634
+ success: 'text-green-400',
635
+ warning: 'text-yellow-400',
636
+ error: 'text-red-400'
637
+ }[type] || 'text-gray-300';
638
+
639
+ const line = document.createElement('div');
640
+ line.className = color;
641
+ line.textContent = `[${timestamp}] ${message}`;
642
+ logsDiv.appendChild(line);
643
+ logsDiv.scrollTop = logsDiv.scrollHeight;
644
+
645
+ logsPanel.classList.remove('hidden');
646
+ }
647
+
648
+ // Update status
649
+ function updateStatus(title, message, status = 'running') {
650
+ statusPanel.classList.remove('hidden');
651
+
652
+ const iconColors = {
653
+ running: 'bg-blue-500',
654
+ success: 'bg-green-500',
655
+ error: 'bg-red-500',
656
+ warning: 'bg-yellow-500'
657
+ };
658
+
659
+ const icons = {
660
+ running: `<svg class="w-4 h-4 text-white animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>`,
661
+ success: `<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>`,
662
+ error: `<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>`,
663
+ warning: `<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>`
664
+ };
665
+
666
+ statusIcon.className = `w-8 h-8 rounded-full flex items-center justify-center ${iconColors[status] || iconColors.running}`;
667
+ statusIcon.innerHTML = icons[status] || icons.running;
668
+ statusTitle.textContent = title;
669
+ statusMessage.textContent = message;
670
+
671
+ progressBar.classList.toggle('hidden', status !== 'running');
672
+ }
673
+
674
+ // Set button state
675
+ function setButtonState(running) {
676
+ isRunning = running;
677
+ runBtn.disabled = running;
678
+
679
+ if (running) {
680
+ btnText.textContent = 'Generating...';
681
+ runBtn.classList.add('opacity-75', 'cursor-not-allowed');
682
+ runBtn.classList.remove('hover:from-blue-700', 'hover:to-indigo-700');
683
+ } else {
684
+ btnText.textContent = 'Generate UVM Testbench';
685
+ runBtn.classList.remove('opacity-75', 'cursor-not-allowed');
686
+ runBtn.classList.add('hover:from-blue-700', 'hover:to-indigo-700');
687
+ }
688
+ }
689
+
690
+ // Run pipeline
691
+ async function runPipeline() {
692
+ if (isRunning) return;
693
+
694
+ setButtonState(true);
695
+ resultsPanel.classList.add('hidden');
696
+ generatedFiles = {};
697
+
698
+ const specYaml = specEditor.value;
699
+ const designName = designNameInput.value;
700
+ const maxIterations = parseInt(maxIterationsInput.value);
701
+ const autoTrain = autoTrainCheckbox.checked;
702
+
703
+ log('Starting UVM testbench generation...', 'info');
704
+ log(`Design: ${designName}, Iterations: ${maxIterations}, Auto-Train: ${autoTrain}`, 'info');
705
+
706
+ updateStatus('Running Pipeline', 'Generating UVM testbench...', 'running');
707
+
708
+ try {
709
+ const response = await fetch('/api/run-pipeline', {
710
+ method: 'POST',
711
+ headers: {
712
+ 'Content-Type': 'application/json',
713
+ },
714
+ body: JSON.stringify({
715
+ spec_yaml: specYaml,
716
+ design_name: designName,
717
+ auto_train: autoTrain,
718
+ max_iterations: maxIterations,
719
+ coverage_target: 90.0,
720
+ num_seeds: 3,
721
+ overwrite: true
722
+ })
723
+ });
724
+
725
+ if (!response.ok) {
726
+ const error = await response.json();
727
+ throw new Error(error.message || error.detail || 'Request failed');
728
+ }
729
+
730
+ const result = await response.json();
731
+
732
+ log(`Generation complete! Status: ${result.status}`, 'success');
733
+ log(`Files generated: ${result.total_files}`, 'success');
734
+
735
+ // Fetch file contents
736
+ generatedFiles = {};
737
+ if (result.artifacts && result.artifacts.length > 0) {
738
+ // For now, we'll use the artifact info. In a real deployment,
739
+ // we'd need endpoints to download individual files.
740
+ log('Note: File download requires artifact endpoints', 'warning');
741
+ }
742
+
743
+ updateStatus('Complete', 'UVM testbench generated successfully', 'success');
744
+
745
+ // Show results
746
+ document.getElementById('metric-files').textContent = result.total_files;
747
+ document.getElementById('metric-status').textContent = result.status.toUpperCase();
748
+ document.getElementById('metric-iterations').textContent = result.iterations;
749
+
750
+ // Show files list
751
+ filesList.innerHTML = '';
752
+ if (result.artifacts) {
753
+ result.artifacts.forEach((artifact, index) => {
754
+ const div = document.createElement('div');
755
+ div.className = 'flex items-center justify-between p-3 bg-gray-900 rounded-lg';
756
+ div.innerHTML = `
757
+ <div class="flex items-center gap-2">
758
+ <svg class="w-4 h-4 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
759
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
760
+ </svg>
761
+ <span class="text-sm text-gray-300">${artifact.name}</span>
762
+ </div>
763
+ `;
764
+ filesList.appendChild(div);
765
+ });
766
+ }
767
+
768
+ resultsPanel.classList.remove('hidden');
769
+
770
+ } catch (error) {
771
+ log(`Error: ${error.message}`, 'error');
772
+ updateStatus('Error', error.message, 'error');
773
+ }
774
+
775
+ setButtonState(false);
776
+ }
777
+
778
+ // Event listeners
779
+ runBtn.addEventListener('click', runPipeline);
780
+
781
+ clearLogsBtn.addEventListener('click', () => {
782
+ logsDiv.innerHTML = '';
783
+ });
784
+
785
+ // Health check on load
786
+ fetch('/api/health')
787
+ .then(r => r.json())
788
+ .then(data => {
789
+ log(`API ready: ${data.status} (v${data.version})`, 'success');
790
+ })
791
+ .catch(e => {
792
+ log('API health check failed', 'warning');
793
+ });
794
+ </script>
795
+ </body>
796
+ </html>
797
+ """
798
+
799
+ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
800
 
801
 
802
  @app.get("/", include_in_schema=False)
 
806
  index = FRONTEND_BUILD / "index.html"
807
  if index.exists():
808
  return FileResponse(str(index))
809
+ return HTMLResponse(EMBEDDED_UI)
810
 
811
 
812
  @app.get("/static/{rest_of_path:path}", include_in_schema=False)
813
  async def serve_static(rest_of_path: str):
814
  if not _IS_BUILT:
815
+ return JSONResponse(status_code=404, content={"error": "Not found"})
816
  file_path = FRONTEND_BUILD / "static" / rest_of_path
817
  if file_path.exists() and file_path.is_file():
818
  return FileResponse(str(file_path))
819
+ return JSONResponse(status_code=404, content={"error": "Not found"})
820
 
821
 
822
  @app.get("/{full_path:path}", include_in_schema=False)
 
830
  index = FRONTEND_BUILD / "index.html"
831
  if index.exists():
832
  return FileResponse(str(index))
833
+ return HTMLResponse(EMBEDDED_UI)
834
 
835
 
836
  # ── Direct execution ───────────────────────────────────────────────