| from __future__ import annotations |
|
|
| import os |
| import sys |
| from pathlib import Path |
|
|
| sys.path.insert(0, str(Path(__file__).parent.parent)) |
|
|
| import gradio as gr |
|
|
| from .demo_data import load_demos |
| from .hf_inference import LORA_MODEL_ID, build_hf_runtime_config, has_hf_config |
| from .rendering import error_html, render_findings_html, render_result |
|
|
| _N = 6 |
|
|
| CARBON_CSS = """ |
| @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap'); |
| |
| /* ββ Global reset ββββββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| .gradio-container, .gradio-container * { |
| font-family: 'IBM Plex Sans', system-ui, -apple-system, sans-serif !important; |
| border-radius: 0 !important; |
| box-sizing: border-box; |
| } |
| .gradio-container { |
| background: #161616 !important; |
| padding: 0 !important; |
| max-width: 100% !important; |
| } |
| body { background: #161616 !important; } |
| |
| /* ββ Base text βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| .gradio-container p, |
| .gradio-container span, |
| .gradio-container div, |
| .gradio-container label, |
| .gradio-container li { |
| color: #c6c6c6; |
| } |
| |
| /* ββ Code editor (CodeMirror) ββββββββββββββββββββββββββββββββββββββββ */ |
| .cm-editor { background: #262626 !important; } |
| .cm-gutters { |
| background: #1e1e1e !important; |
| border-right: 1px solid #393939 !important; |
| color: #6f6f6f !important; |
| } |
| .cm-content, .cm-line, .cm-scroller { |
| font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace !important; |
| font-size: 13px !important; |
| line-height: 1.6 !important; |
| color: #f4f4f4 !important; |
| background: #262626 !important; |
| caret-color: #4589ff !important; |
| } |
| .cm-activeLine { background: #2d2d2d !important; } |
| .cm-selectionBackground { background: #001d6c !important; } |
| .codemirror-wrapper, .codemirror-wrapper > div { |
| background: #262626 !important; |
| border: 1px solid #393939 !important; |
| } |
| |
| /* ββ Primary button ββββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| button.primary { |
| background: #0f62fe !important; |
| color: #ffffff !important; |
| border: none !important; |
| font-size: 14px !important; |
| font-weight: 400 !important; |
| letter-spacing: 0.16px !important; |
| height: 48px !important; |
| transition: background 70ms ease !important; |
| } |
| button.primary:hover { background: #0050e6 !important; } |
| button.primary:active { background: #002d9c !important; } |
| |
| /* ββ Secondary button ββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| button.secondary { |
| background: #262626 !important; |
| color: #78a9ff !important; |
| border: 1px solid #4589ff !important; |
| } |
| button.secondary:hover { background: #353535 !important; } |
| |
| /* ββ Tabs ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| div[role="tablist"] { |
| border-bottom: 1px solid #393939 !important; |
| background: #262626 !important; |
| padding: 0 16px !important; |
| gap: 0 !important; |
| } |
| button[role="tab"] { |
| font-size: 14px !important; |
| letter-spacing: 0.16px !important; |
| color: #8d8d8d !important; |
| background: transparent !important; |
| border: none !important; |
| border-bottom: 3px solid transparent !important; |
| padding: 12px 16px !important; |
| font-weight: 400 !important; |
| margin-bottom: -1px !important; |
| transition: color 70ms ease !important; |
| } |
| button[role="tab"]:hover { color: #c6c6c6 !important; } |
| button[role="tab"][aria-selected="true"] { |
| color: #f4f4f4 !important; |
| border-bottom: 3px solid #4589ff !important; |
| font-weight: 600 !important; |
| } |
| |
| /* ββ Labels ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| label > span:first-child, .label-wrap > span { |
| font-size: 11px !important; |
| font-weight: 600 !important; |
| letter-spacing: 0.32px !important; |
| text-transform: uppercase !important; |
| color: #8d8d8d !important; |
| } |
| |
| /* ββ Inputs / selects ββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| input, textarea, select { |
| background: #262626 !important; |
| color: #f4f4f4 !important; |
| border: 1px solid #525252 !important; |
| } |
| input:focus, textarea:focus, select:focus { |
| border-color: #4589ff !important; |
| outline: none !important; |
| } |
| select option { background: #262626 !important; color: #f4f4f4 !important; } |
| |
| /* ββ Dropdown (Gradio) βββββββββββββββββββββββββββββββββββββββββββββββ */ |
| .dropdown, .multiselect, [data-testid="dropdown"] { |
| background: #262626 !important; |
| border: 1px solid #525252 !important; |
| color: #f4f4f4 !important; |
| } |
| .dropdown ul, .multiselect ul, [data-testid="dropdown"] ul { |
| background: #262626 !important; |
| border: 1px solid #525252 !important; |
| } |
| .dropdown li, .multiselect li { color: #c6c6c6 !important; } |
| .dropdown li:hover, .multiselect li:hover { background: #353535 !important; } |
| |
| /* ββ Markdown headings βββββββββββββββββββββββββββββββββββββββββββββββ */ |
| .prose h3 { |
| font-size: 11px !important; |
| font-weight: 600 !important; |
| letter-spacing: 0.32px !important; |
| text-transform: uppercase !important; |
| color: #8d8d8d !important; |
| border-bottom: 1px solid #393939 !important; |
| padding-bottom: 8px !important; |
| margin: 20px 0 10px !important; |
| } |
| .prose code { |
| font-family: 'IBM Plex Mono', monospace !important; |
| background: #353535 !important; |
| border: 1px solid #525252 !important; |
| font-size: 0.85em !important; |
| padding: 1px 5px !important; |
| color: #78a9ff !important; |
| } |
| |
| /* ββ Container blocks ββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| .block { |
| background: #262626 !important; |
| border-color: #393939 !important; |
| } |
| .tabitem { |
| background: #1e1e1e !important; |
| padding: 20px !important; |
| } |
| |
| /* ββ Scrollbars ββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ |
| ::-webkit-scrollbar { width: 6px; height: 6px; } |
| ::-webkit-scrollbar-track { background: #161616; } |
| ::-webkit-scrollbar-thumb { background: #525252; } |
| ::-webkit-scrollbar-thumb:hover { background: #6f6f6f; } |
| """ |
|
|
| _F = "IBM Plex Sans,system-ui,sans-serif" |
| _M = "IBM Plex Mono,SFMono-Regular,Consolas,monospace" |
|
|
| _HEADER_HTML = """ |
| <div style="background:#161616;padding:28px 32px 24px;margin:-8px -8px 0;border-bottom:1px solid #393939"> |
| <p style="font-size:11px;font-weight:600;letter-spacing:0.32px;text-transform:uppercase; |
| color:#6f6f6f;margin:0 0 6px;font-family:'IBM Plex Sans',sans-serif"> |
| |
| </p> |
| <h1 style="font-size:2rem;font-weight:300;letter-spacing:-0.64px;color:#ffffff; |
| margin:0 0 8px;font-family:'IBM Plex Sans',sans-serif"> |
| IaC-SecFix |
| </h1> |
| <p style="font-size:14px;color:#a8a8a8;margin:0 0 20px;font-family:'IBM Plex Sans',sans-serif;max-width:640px"> |
| Paste a Terraform or Kubernetes file. The agent pipeline scans for security misconfigurations, |
| generates a verified patch, and reports exactly which rules were fixed β powered by a fine-tuned |
| Qwen3.5-9B LoRA model trained on AMD MI300X. |
| </p> |
| <div style="display:flex;flex-wrap:wrap;gap:0;align-items:stretch"> |
| <div style="display:flex;align-items:center;gap:6px;background:#262626; |
| padding:8px 12px;border-left:3px solid #4589ff"> |
| <span style="font-size:11px;font-weight:600;color:#8d8d8d;letter-spacing:0.32px; |
| text-transform:uppercase;font-family:'IBM Plex Sans',sans-serif">Pipeline</span> |
| </div> |
| <div style="display:flex;flex-wrap:wrap;align-items:center;background:#1e1e1e; |
| padding:8px 16px;gap:6px;border-left:1px solid #393939"> |
| <span style="font-size:11px;color:#78a9ff;font-family:'IBM Plex Mono',monospace; |
| background:#001d6c;padding:2px 8px">1 Β· Ingest</span> |
| <span style="color:#525252;font-size:11px">β</span> |
| <span style="font-size:11px;color:#78a9ff;font-family:'IBM Plex Mono',monospace; |
| background:#001d6c;padding:2px 8px">2 Β· Policy Context</span> |
| <span style="color:#525252;font-size:11px">β</span> |
| <span style="font-size:11px;color:#78a9ff;font-family:'IBM Plex Mono',monospace; |
| background:#001d6c;padding:2px 8px">3 Β· Patch</span> |
| <span style="color:#525252;font-size:11px">β</span> |
| <span style="font-size:11px;color:#78a9ff;font-family:'IBM Plex Mono',monospace; |
| background:#001d6c;padding:2px 8px">4 Β· Validate</span> |
| <span style="color:#525252;font-size:11px">β</span> |
| <span style="font-size:11px;color:#78a9ff;font-family:'IBM Plex Mono',monospace; |
| background:#001d6c;padding:2px 8px">5 Β· Safety</span> |
| <span style="color:#525252;font-size:11px">β</span> |
| <span style="font-size:11px;color:#42be65;font-family:'IBM Plex Mono',monospace; |
| background:#071908;padding:2px 8px">6 Β· Human Gate</span> |
| </div> |
| </div> |
| </div> |
| """ |
|
|
| _HOW_IT_WORKS_HTML = """ |
| <div style="background:#262626;border-left:3px solid #4589ff;padding:16px 20px;margin-bottom:20px"> |
| <p style="font-size:11px;font-weight:600;letter-spacing:0.32px;text-transform:uppercase; |
| color:#4589ff;margin:0 0 12px;font-family:'IBM Plex Sans',sans-serif">How it works</p> |
| <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px"> |
| <div> |
| <p style="font-size:12px;font-weight:600;color:#f4f4f4;margin:0 0 4px; |
| font-family:'IBM Plex Sans',sans-serif">β Scan</p> |
| <p style="font-size:12px;color:#8d8d8d;margin:0;line-height:1.5; |
| font-family:'IBM Plex Sans',sans-serif"> |
| Checkov runs 500+ rules against your file and records every failed check with its severity. |
| </p> |
| </div> |
| <div> |
| <p style="font-size:12px;font-weight:600;color:#f4f4f4;margin:0 0 4px; |
| font-family:'IBM Plex Sans',sans-serif">β‘ Contextualize</p> |
| <p style="font-size:12px;color:#8d8d8d;margin:0;line-height:1.5; |
| font-family:'IBM Plex Sans',sans-serif"> |
| A policy agent retrieves the CIS/NIST remediation guidance most relevant to each finding. |
| </p> |
| </div> |
| <div> |
| <p style="font-size:12px;font-weight:600;color:#f4f4f4;margin:0 0 4px; |
| font-family:'IBM Plex Sans',sans-serif">β’ Patch</p> |
| <p style="font-size:12px;color:#8d8d8d;margin:0;line-height:1.5; |
| font-family:'IBM Plex Sans',sans-serif"> |
| The fine-tuned Qwen3.5-9B LoRA model rewrites only the unsafe blocks, leaving the rest untouched. |
| </p> |
| </div> |
| <div> |
| <p style="font-size:12px;font-weight:600;color:#f4f4f4;margin:0 0 4px; |
| font-family:'IBM Plex Sans',sans-serif">β£ Validate</p> |
| <p style="font-size:12px;color:#8d8d8d;margin:0;line-height:1.5; |
| font-family:'IBM Plex Sans',sans-serif"> |
| Checkov runs again on the patched file. Only checks that were originally failing are tracked. |
| </p> |
| </div> |
| <div> |
| <p style="font-size:12px;font-weight:600;color:#f4f4f4;margin:0 0 4px; |
| font-family:'IBM Plex Sans',sans-serif">β€ Safety gate</p> |
| <p style="font-size:12px;color:#8d8d8d;margin:0;line-height:1.5; |
| font-family:'IBM Plex Sans',sans-serif"> |
| A safety agent checks the patch introduces no destructive side-effects before approving. |
| </p> |
| </div> |
| <div> |
| <p style="font-size:12px;font-weight:600;color:#f4f4f4;margin:0 0 4px; |
| font-family:'IBM Plex Sans',sans-serif">β₯ Result</p> |
| <p style="font-size:12px;color:#8d8d8d;margin:0;line-height:1.5; |
| font-family:'IBM Plex Sans',sans-serif"> |
| You see the decision (Approved / Needs Review / Rejected), the diff, and the resolved rules. |
| </p> |
| </div> |
| </div> |
| </div> |
| """ |
|
|
| _READING_GUIDE_HTML = """ |
| <div style="background:#1e1e1e;border:1px solid #393939;padding:14px 20px;margin-bottom:20px"> |
| <p style="font-size:11px;font-weight:600;letter-spacing:0.32px;text-transform:uppercase; |
| color:#8d8d8d;margin:0 0 10px;font-family:'IBM Plex Sans',sans-serif">Reading the results</p> |
| <div style="display:flex;flex-wrap:wrap;gap:16px"> |
| <span style="font-size:12px;color:#8d8d8d;font-family:'IBM Plex Sans',sans-serif"> |
| <b style="color:#42be65">Approved</b> β patch verified; all targeted issues resolved, no unsafe changes. |
| </span> |
| <span style="font-size:12px;color:#8d8d8d;font-family:'IBM Plex Sans',sans-serif"> |
| <b style="color:#f1c21b">Needs Review</b> β patch improves security but at least one issue remains; human sign-off required. |
| </span> |
| <span style="font-size:12px;color:#8d8d8d;font-family:'IBM Plex Sans',sans-serif"> |
| <b style="color:#ff8389">Rejected</b> β patch made things worse or introduced a safety risk. |
| </span> |
| <span style="font-size:12px;color:#8d8d8d;font-family:'IBM Plex Sans',sans-serif"> |
| <b style="color:#c6c6c6">Findings Before / After</b> β count and severity of failed checks before and after the patch. |
| </span> |
| <span style="font-size:12px;color:#8d8d8d;font-family:'IBM Plex Sans',sans-serif"> |
| <b style="color:#c6c6c6">Analysis Details</b> β which rules were resolved, which remain, and how many lines changed. |
| </span> |
| </div> |
| </div> |
| """ |
|
|
| _FOOTER_HTML = """ |
| <div style="background:#262626;border-top:1px solid #393939;padding:16px 32px; |
| display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px"> |
| <div style="display:flex;gap:24px;flex-wrap:wrap"> |
| <span style="font-size:12px;color:#8d8d8d;font-family:'IBM Plex Sans',sans-serif"> |
| <b style="color:#f4f4f4">IaC-SecFix</b> Β· Developed by Selcuk Senturk |
| </span> |
| </div> |
| </div> |
| """ |
|
|
|
|
| def _empty() -> tuple: |
| return ("",) * _N |
|
|
|
|
| def _demo_outputs(label: str | None, demos: dict) -> tuple: |
| if not label or label not in demos: |
| return _empty() |
| return render_result(demos[label]) |
|
|
|
|
| def _demo_prefill(label: str | None, demos: dict) -> str: |
| """Return just the original file content for pre-filling the editor.""" |
| if not label or label not in demos: |
| return "" |
| return demos[label].get("original_file", "") |
|
|
|
|
| def _lora_outputs(file_content: str, iac_type: str) -> tuple: |
| if not file_content.strip(): |
| return _empty() |
| try: |
| from iac_secfix_agents import IngestRequest, RemediationOrchestrator |
|
|
| config = build_hf_runtime_config() |
| decision = RemediationOrchestrator(config=config).remediate( |
| IngestRequest( |
| file_name="input.tf" if iac_type == "terraform" else "input.yaml", |
| file_content=file_content, |
| iac_type=iac_type, |
| ) |
| ) |
| val = decision.validation |
| data = { |
| "original_file": file_content, |
| "fixed_file": decision.patch.fixed_file if decision.patch else "", |
| "decision": decision.model_dump(mode="json"), |
| "pre_rule_ids": [], |
| "post_rule_ids": val.targeted_unresolved if val else [], |
| "pre_summary": val.pre_summary if val else {}, |
| "post_summary": val.post_summary if val else {}, |
| } |
| return render_result(data) |
| except Exception as exc: |
| return (file_content, "", error_html("Agent error", exc), "", "", "") |
|
|
|
|
| def _live_outputs(file_content: str, iac_type: str) -> tuple: |
| if not file_content.strip(): |
| return _empty() |
| try: |
| from iac_secfix_agents import IngestRequest, RemediationOrchestrator |
|
|
| decision = RemediationOrchestrator().remediate( |
| IngestRequest( |
| file_name="input.tf" if iac_type == "terraform" else "input.yaml", |
| file_content=file_content, |
| iac_type=iac_type, |
| ) |
| ) |
| val = decision.validation |
| data = { |
| "original_file": file_content, |
| "fixed_file": decision.patch.fixed_file if decision.patch else "", |
| "decision": decision.model_dump(mode="json"), |
| "pre_rule_ids": [], |
| "post_rule_ids": val.targeted_unresolved if val else [], |
| "pre_summary": val.pre_summary if val else {}, |
| "post_summary": val.post_summary if val else {}, |
| } |
| return render_result(data) |
| except Exception as exc: |
| return (file_content, "", error_html("Error", exc), "", "", "") |
|
|
|
|
| def _result_section() -> tuple: |
| status = gr.HTML() |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML( |
| "<p style='font-size:11px;font-weight:600;letter-spacing:0.32px;" |
| "text-transform:uppercase;color:#8d8d8d;margin:0 0 6px;" |
| f"font-family:{_F}'>Findings before patch</p>" |
| "<p style='font-size:12px;color:#6f6f6f;margin:0 0 8px;" |
| f"font-family:{_F}'>Security checks that failed on the original file.</p>" |
| ) |
| before = gr.HTML() |
| with gr.Column(): |
| gr.HTML( |
| "<p style='font-size:11px;font-weight:600;letter-spacing:0.32px;" |
| "text-transform:uppercase;color:#8d8d8d;margin:0 0 6px;" |
| f"font-family:{_F}'>Findings after patch</p>" |
| "<p style='font-size:12px;color:#6f6f6f;margin:0 0 8px;" |
| f"font-family:{_F}'>Remaining failures after the agent applied its fix.</p>" |
| ) |
| after = gr.HTML() |
| gr.HTML( |
| "<p style='font-size:11px;font-weight:600;letter-spacing:0.32px;" |
| "text-transform:uppercase;color:#8d8d8d;margin:16px 0 6px;" |
| f"font-family:{_F}'>Analysis details</p>" |
| "<p style='font-size:12px;color:#6f6f6f;margin:0 0 8px;" |
| f"font-family:{_F}'>" |
| "Resolved / unresolved rules, parse status, and how much of the file changed.</p>" |
| ) |
| details = gr.HTML() |
| return status, before, after, details |
|
|
|
|
| def build_demo() -> gr.Blocks: |
| demos = load_demos() |
| choices = list(demos.keys()) |
| has_lora = has_hf_config() |
| has_live_vllm = bool(os.getenv("IAC_SECFIX_BASE_URL")) |
|
|
| with gr.Blocks(title="IaC-SecFix", css=CARBON_CSS, theme=gr.themes.Base()) as app: |
|
|
| gr.HTML(_HEADER_HTML) |
|
|
| with gr.Tabs(): |
|
|
| |
| with gr.Tab("Try It"): |
| gr.HTML(_HOW_IT_WORKS_HTML) |
|
|
| |
| gr.HTML( |
| f"<p style='font-size:11px;font-weight:600;letter-spacing:0.32px;" |
| f"text-transform:uppercase;color:#8d8d8d;margin:0 0 4px;font-family:{_F}'>" |
| f"Step 1 β choose your input</p>" |
| f"<p style='font-size:13px;color:#a8a8a8;margin:0 0 12px;font-family:{_F}'>" |
| f"Pick a prebuilt example to load it into the editor, or paste your own " |
| f"Terraform / Kubernetes file directly.</p>" |
| ) |
| with gr.Row(): |
| demo_dd = gr.Dropdown( |
| choices=choices, |
| value=choices[0] if choices else None, |
| label="Prebuilt example", |
| scale=4, |
| info="Selecting an example pre-fills the editor below.", |
| ) |
| iac_type = gr.Dropdown( |
| choices=["terraform", "kubernetes"], |
| value="terraform", |
| label="IaC type", |
| scale=1, |
| info="Match this to the file you're analysing.", |
| ) |
|
|
| iac_code = gr.Code( |
| label="IaC file (editable β paste your own or use the example above)", |
| language="yaml", |
| interactive=True, |
| lines=22, |
| ) |
|
|
| |
| gr.HTML( |
| f"<p style='font-size:11px;font-weight:600;letter-spacing:0.32px;" |
| f"text-transform:uppercase;color:#8d8d8d;margin:16px 0 4px;font-family:{_F}'>" |
| f"Step 2 β analyse</p>" |
| ) |
| with gr.Row(): |
| demo_btn = gr.Button( |
| "Load precomputed result", |
| variant="secondary", |
| scale=1, |
| ) |
| if has_lora: |
| run_btn = gr.Button( |
| "Run fine-tuned model", |
| variant="primary", |
| scale=1, |
| ) |
|
|
| if has_lora: |
| gr.HTML( |
| f"<p style='font-size:12px;color:#6f6f6f;margin:6px 0 0;font-family:{_F}'>" |
| f"<b style='color:#a8a8a8'>Load precomputed result</b> β instant, no GPU. " |
| f"Shows the benchmark trace for the selected example.<br>" |
| f"<b style='color:#a8a8a8'>Run fine-tuned model</b> β calls the live " |
| f"<a href='https://huggingface.co/{LORA_MODEL_ID}' " |
| f"style='color:#78a9ff;text-decoration:none'>{LORA_MODEL_ID}</a> " |
| f"endpoint (Qwen3.5-9B, r=8 LoRA). Works on any file you paste.</p>" |
| ) |
| else: |
| gr.HTML( |
| f"<p style='font-size:12px;color:#6f6f6f;margin:6px 0 0;font-family:{_F}'>" |
| f"<b style='color:#a8a8a8'>Load precomputed result</b> β shows the " |
| f"benchmark trace for the selected example. " |
| f"To enable live inference, set <code style='color:#78a9ff;font-family:{_M}'>" |
| f"HF_TOKEN</code> or <code style='color:#78a9ff;font-family:{_M}'>" |
| f"IAC_SECFIX_HF_ENDPOINT</code> in Space secrets.</p>" |
| ) |
|
|
| |
| gr.HTML( |
| f"<p style='font-size:11px;font-weight:600;letter-spacing:0.32px;" |
| f"text-transform:uppercase;color:#8d8d8d;margin:20px 0 4px;font-family:{_F}'>" |
| f"Step 3 β review results</p>" |
| ) |
| gr.HTML(_READING_GUIDE_HTML) |
|
|
| status, before, after, details = _result_section() |
|
|
| with gr.Row(): |
| orig_out = gr.Code( |
| label="Original file", |
| language="yaml", |
| interactive=False, |
| lines=20, |
| ) |
| fixed_out = gr.Code( |
| label="Fixed file (agent output)", |
| language="yaml", |
| interactive=False, |
| lines=20, |
| ) |
|
|
| outs = [orig_out, fixed_out, status, before, after, details] |
|
|
| |
| demo_dd.change( |
| fn=lambda l: _demo_prefill(l, demos), |
| inputs=[demo_dd], |
| outputs=[iac_code], |
| ) |
|
|
| |
| demo_btn.click( |
| fn=lambda l: _demo_outputs(l, demos), |
| inputs=[demo_dd], |
| outputs=outs, |
| ) |
|
|
| if has_lora: |
| run_btn.click( |
| fn=_lora_outputs, |
| inputs=[iac_code, iac_type], |
| outputs=outs, |
| ) |
|
|
| |
| app.load( |
| fn=lambda: ( |
| _demo_prefill(choices[0] if choices else None, demos), |
| *_demo_outputs(choices[0] if choices else None, demos), |
| ), |
| outputs=[iac_code] + outs, |
| ) |
|
|
| |
| if has_live_vllm: |
| with gr.Tab("Live Endpoint"): |
| gr.HTML( |
| f"<div style='background:#262626;border-left:3px solid #f1c21b;" |
| f"padding:12px 16px;margin-bottom:16px'>" |
| f"<p style='font-size:12px;font-weight:600;color:#f1c21b;margin:0 0 4px;" |
| f"font-family:{_F}'>Advanced β custom vLLM endpoint</p>" |
| f"<p style='font-size:13px;color:#a8a8a8;margin:0;font-family:{_F}'>" |
| f"This tab calls a self-hosted OpenAI-compatible vLLM server directly. " |
| f"Configure the endpoint by setting " |
| f"<code style='color:#78a9ff;font-family:{_M}'>IAC_SECFIX_BASE_URL</code>, " |
| f"<code style='color:#78a9ff;font-family:{_M}'>IAC_SECFIX_API_KEY</code>, and " |
| f"<code style='color:#78a9ff;font-family:{_M}'>IAC_SECFIX_MODEL</code> " |
| f"in the Space secrets panel.</p></div>" |
| ) |
| with gr.Row(): |
| live_code = gr.Code( |
| label="IaC file", |
| language="yaml", |
| interactive=True, |
| lines=22, |
| ) |
| live_type = gr.Dropdown( |
| choices=["terraform", "kubernetes"], |
| value="terraform", |
| label="IaC type", |
| scale=0, |
| ) |
| live_btn = gr.Button("Run agent loop", variant="primary") |
| live_status, live_before, live_after, live_details = _result_section() |
| with gr.Row(): |
| live_orig = gr.Code(label="Original file", language="yaml", |
| interactive=False, lines=20) |
| live_fixed = gr.Code(label="Fixed file", language="yaml", |
| interactive=False, lines=20) |
| live_btn.click( |
| fn=_live_outputs, |
| inputs=[live_code, live_type], |
| outputs=[live_orig, live_fixed, live_status, |
| live_before, live_after, live_details], |
| ) |
|
|
| gr.HTML(_FOOTER_HTML) |
|
|
| return app |
|
|