Spaces:
Runtime error
Runtime error
| # app.py | |
| import os, re | |
| import gradio as gr | |
| from openai import OpenAI | |
| from pathlib import Path | |
| from PyPDF2 import PdfReader | |
| # ------------------------- | |
| # Banner URL | |
| # ------------------------- | |
| BANNER_URL = "https://huggingface.co/spaces/Militaryint/ops/resolve/main/banner.png" | |
| # ------------------------- | |
| # Safety Block | |
| # ------------------------- | |
| SYSTEM_SAFE = """ | |
| You are a military analyst assistant. | |
| All outputs must remain NON-ACTIONABLE, sanitized, and advisory-only. | |
| You will not provide tactical or operational commands. | |
| You may summarize doctrine, SOPs, vulnerabilities, audits, precautions, intelligence assessments, and administrative remediations. | |
| Never generate direct fire orders, maneuvers, or kinetic strike instructions. | |
| """ | |
| # ------------------------- | |
| # OpenAI Client | |
| # ------------------------- | |
| client = None | |
| try: | |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
| except Exception as e: | |
| print("[OpenAI] client init error:", e) | |
| client = None | |
| # ------------------------- | |
| # Knowledge Base | |
| # ------------------------- | |
| def read_all_files(folder="knowledge_base"): | |
| files_text = {} | |
| p = Path(folder) | |
| if not p.exists(): | |
| print(f"[KB] folder {folder} missing") | |
| return files_text | |
| for f in sorted(p.glob("*.pdf")): | |
| try: | |
| reader = PdfReader(str(f)) | |
| txt = "\n".join((page.extract_text() or "") for page in reader.pages) | |
| files_text[f.name] = txt | |
| except Exception as e: | |
| print("[KB] error reading", f, e) | |
| return files_text | |
| FILES_TEXT = read_all_files("knowledge_base") | |
| # ------------------------- | |
| # Priority PDFs | |
| # ------------------------- | |
| PRIORITY_PDFS = [ | |
| "Operational Course of Action Using the Kashmir Military Algorithm.pdf", | |
| "UPLOAD TO CHAT.pdf", | |
| "5D_Crime_Analysis.pdf", | |
| "INT_SOP_CONTROL_logic.pdf" | |
| ] | |
| # ------------------------- | |
| # PARA SF Priority PDFs | |
| # ------------------------- | |
| SF_PRIORITY_PDFS = [ | |
| "Operational Course of Action Using the Kashmir Military Algorithm.pdf", | |
| "UPLOAD TO CHAT.pdf", | |
| "5D_Crime_Analysis.pdf", | |
| "INT_SOP_CONTROL_logic.pdf", | |
| "Staff_Officer_Playbook_5D.pdf" | |
| ] | |
| # ------------------------- | |
| # Reorder so priority PDFs appear first (if present) | |
| # ------------------------- | |
| ordered_files = {} | |
| for p in PRIORITY_PDFS: | |
| for k in list(FILES_TEXT.keys()): | |
| if k.lower() == p.lower(): | |
| ordered_files[k] = FILES_TEXT.pop(k) | |
| break | |
| for k, v in FILES_TEXT.items(): | |
| ordered_files[k] = v | |
| FILES_TEXT = ordered_files | |
| # ------------------------- | |
| # KB Index (naive chunking & query) | |
| # ------------------------- | |
| class KBIndex: | |
| def __init__(self, chunk_size=1200): | |
| self.docs = {} | |
| self.chunk_size = chunk_size | |
| def build_from_files(self, files_text): | |
| self.docs = {} | |
| for fn, txt in files_text.items(): | |
| if not txt: | |
| continue | |
| chunks = [] | |
| for i in range(0, len(txt), self.chunk_size): | |
| chunks.append(txt[i:i+self.chunk_size]) | |
| self.docs[fn] = chunks | |
| def query(self, q, top_k=3): | |
| ql = q.lower().strip() | |
| results = [] | |
| if not ql: | |
| return results | |
| for fn, chunks in self.docs.items(): | |
| best = [] | |
| for ch in chunks: | |
| if ql in ch.lower(): | |
| best.append((fn, ch[:800])) | |
| results.extend(best) | |
| # return first top_k unique filenames/chunks | |
| seen = set() | |
| out = [] | |
| for fn, ch in results: | |
| if (fn, ch) not in seen: | |
| out.append((fn, ch)) | |
| seen.add((fn,ch)) | |
| if len(out) >= top_k: | |
| break | |
| return out | |
| KB_INDEX = KBIndex() | |
| KB_INDEX.build_from_files(FILES_TEXT) | |
| print("[KB] Indexed files (priority first):") | |
| for f in FILES_TEXT.keys(): | |
| print(" -", f) | |
| print("[KB] Total chunks indexed:", sum(len(v) for v in KB_INDEX.docs.values())) | |
| # ------------------------- | |
| # Operational Command — Template | |
| # ------------------------- | |
| def operational_command_prompt(answers_map, category): | |
| user_text = "\n".join(f"{k}: {v}" for k, v in answers_map.items() if str(v).strip()) | |
| return [ | |
| {"role": "system", "content": SYSTEM_SAFE}, | |
| {"role": "user", "content": f""" | |
| You are to prepare a structured, advisory-only report. | |
| Category: {category} | |
| Inputs: | |
| {user_text} | |
| Knowledge base excerpts (if any) must be considered. | |
| Report must include: | |
| - Executive Summary (2-4 lines) | |
| - Threat Assessment (administrative & doctrinal) | |
| - Course of Action (doctrinal, admin, advisory; non-actionable) | |
| - Intelligence Summary (sources & KB citations) | |
| - Administrative Remediations (prioritized) | |
| Please clearly cite KB filenames used (filename::chunk indicator). | |
| """} | |
| ] | |
| # ------------------------- | |
| # Standard / Enhanced Questions | |
| # ------------------------- | |
| STD_QUESTIONS = [ | |
| "When and where was enemy sighted?", | |
| "Coming from which direction?", | |
| "What is the size of the enemy (how many men)?", | |
| "What equipment and weapons are they carrying?", | |
| "What vehicles are they using or are they on foot?", | |
| "How far are they from any roads frequented by soldiers vehicles?", | |
| "How far are they from any military unit camp?", | |
| "How far are they from any deployed soldiers?", | |
| "Are they getting support of locals? If so who are these locals?", | |
| "What is their disposition? How are they spread out?", | |
| "Do you have Reconnaissance and Surveillance soldiers near the area?", | |
| "Did you get the information from local source or army personnel?", | |
| "If from local source, did you confirm from a second source or R&S team?", | |
| "How far is your commanded army unit from the enemy sighting?", | |
| "What is the terrain (urban, semi-urban, jungle, hilly, rural)?" | |
| ] | |
| ENH_SECTION_A = [ | |
| "Does the Bn have separate Ops planning and Int sections?", | |
| "Does the Unit have an intelligence SOP / COA template?", | |
| "Does the unit have a reconnaissance & surveillance plan?", | |
| "Does the unit have Force Protection SOP and Threat Levels?", | |
| "Does the unit have intelligence projection capability (forward nodes)?" | |
| ] | |
| ENH_SECTION_B = [ | |
| "Is there a vulnerability analysis tool for the unit?", | |
| "Does the unit employ randomness in movement and tasks?", | |
| "Is there a source vetting / CI system in place?", | |
| "Does the unit treat intelligence as doctrine or just data?", | |
| "Does the unit use CI in vulnerability & operational reviews?" | |
| ] | |
| ENH_SECTION_C = [ | |
| "Are intelligence personnel embedded in routine ops?", | |
| "Am I thinking of the Threat or the CO's Situational Awareness?", | |
| "What is my intent as a staff planning element?", | |
| "Do I detect, deter, deny, deliver, or destroy (5D options)?", | |
| "Do external MI assets conform to the 5D system?", | |
| "Have I made a vulnerability assessment (Deter/Deny)?", | |
| "How do I account for Force Protection based on gaps?", | |
| "Do we attack threat SA, freedom of movement, tactics, or local support?", | |
| "Is operation Deliberate or Quick and do I have projected int assets?", | |
| "Do I clearly distinguish Advance Warn, Surprise and Situational Awareness?" | |
| ] | |
| # ------------------------- | |
| # PARA SF Questions (50 real concise Qs) | |
| # ------------------------- | |
| PARA_QUESTIONS_50 = [ | |
| "Exact location (grid / place) of sighting?", | |
| "Date and time of first observation?", | |
| "Direction of enemy approach?", | |
| "Estimated number of personnel?", | |
| "Observed leader(s) or commanders?", | |
| "Enemy weapons observed (small arms, crew-served)?", | |
| "Presence of vehicles (type / count)?", | |
| "Signs of explosives or IED activity?", | |
| "Observed rates of movement (stationary / moving)?", | |
| "Formation or dispersion (tight / spread)?", | |
| "Use of local population for support?", | |
| "Local sympathizers identified (names/roles)?", | |
| "Logistics / resupply indicators?", | |
| "Known routes used by enemy?", | |
| "Recent history of enemy attacks in area?", | |
| "Patterns of life detected (timings, routines)?", | |
| "Use of communications (radios, phones, signals)?", | |
| "Evidence of foreign or external support?", | |
| "Sanctuary / hideouts identified?", | |
| "Medical support observed (casualty handling)?", | |
| "Use of deception or camouflage?", | |
| "Counter-surveillance signs noted?", | |
| "Electronic signature / unusual transmissions?", | |
| "Use of snipers or precision shooters?", | |
| "Use of indirect fires or mortars observed?", | |
| "Known HVTs (leadership, infrastructure) in area?", | |
| "Enemy morale indicators (behavior, chatter)?", | |
| "Training level (disciplined / ad hoc)?", | |
| "Use of booby traps or delayed attacks?", | |
| "Any previous successful ambushes nearby?", | |
| "Civilian movement patterns near enemy locations?", | |
| "Sources of local intel for friendly forces?", | |
| "Credibility of available human sources?", | |
| "Any known double-agents or compromised sources?", | |
| "Physical terrain features exploited by enemy?", | |
| "Weather impacts on enemy movement?", | |
| "Recent arrests/detentions related to enemy?", | |
| "Any legal or jurisdictional constraints locally?", | |
| "Evidence of command-and-control nodes?", | |
| "Access to fuel/facility caches?", | |
| "Enemy ability to disperse quickly?", | |
| "Likelihood of reinforcement from nearby areas?", | |
| "Time-to-redeploy for friendly quick reaction forces?", | |
| "Observations on enemy sustainment posture?", | |
| "Any indicators of planned escalation?", | |
| "Local civilian sentiment (hostile/neutral/supportive)?", | |
| "Possible safe-exit routes for friendly forces?", | |
| "Any cultural or legal sensitivities to consider?", | |
| "Any open-source / social media indicators?", | |
| "Urgency rating (low / med / high) from observer field notes?" | |
| ] | |
| # ------------------------- | |
| # PARA SF Precautions / Protective Measures (40 items) | |
| # ------------------------- | |
| PARA_PRECAUTIONS_40 = [ | |
| "Maintain strict radio burst discipline and short transmissions", | |
| "Use alternate communication paths and pre-planned authentication", | |
| "Document and register all human sources with CI vetting", | |
| "Establish secure, auditable intelligence logs", | |
| "Define and rehearse contingency exfiltration routes", | |
| "Maintain camouflage and concealment SOPs for observation posts", | |
| "Rotate observation posts and R&S teams to avoid predictability", | |
| "Implement randomized foot and vehicle movement schedules", | |
| "Limit use of identified local infrastructure to reduce signature", | |
| "Use layered reporting with secondary confirmation requirement", | |
| "Pre-authorize administrative response windows to reduce delay", | |
| "Audit base layout and relocate critical assets from perimeter", | |
| "Maintain medical evacuation planning and casualty drills", | |
| "Ensure secure caches for critical supplies and spares", | |
| "Institute source validation and cross-source corroboration", | |
| "Use non-attributable liaison methods with local police/DEA", | |
| "Formalize SOP for evidence handling and chain-of-custody", | |
| "Maintain a log of all civilian interactions and transactions", | |
| "Conduct red-team administrative audits quarterly", | |
| "Maintain a vulnerability register and prioritized fixes", | |
| "Limit exposure of leadership movements via need-to-know", | |
| "Implement force protection route checklists before movement", | |
| "Deploy observation posts with concealment and escape plans", | |
| "Mandate brief, formatted SITREPs with required fields", | |
| "Establish covert Forward Tactical C2 nodes (administrative only)", | |
| "Use document-based debrief templates to capture lessons", | |
| "Set up area role cards and single-point contacts per sector", | |
| "Institute secure storage for source identity and vetting info", | |
| "Limit public posting of unit schedules and training events", | |
| "Use liaison with local law enforcement for non-operational support", | |
| "Schedule regular doctrine & SOP training sessions", | |
| "Maintain an audit trail for all intelligence product changes", | |
| "Set thresholds for escalation to higher HQ (administrative)", | |
| "Maintain alternate rendezvous points and safe houses", | |
| "Ensure all unit members have basic fieldcraft refresher training", | |
| "Plan periodic concealment and movement drills (administrative)", | |
| "Maintain a simple, unclassified index of likely HVT indicators", | |
| "Ensure information security (passwords, devices) audits quarterly", | |
| "Establish a schedule for reviewing and updating SOPs" | |
| ] | |
| # ------------------------- | |
| # Report Generators (KB-first, then fallback to OpenAI SF doctrine) | |
| # ------------------------- | |
| def call_chat_api_system_user(messages, max_tokens=800, model="gpt-4o-mini"): | |
| if client is None: | |
| raise RuntimeError("OpenAI client not available.") | |
| resp = client.chat.completions.create(model=model, messages=messages, max_tokens=max_tokens) | |
| try: | |
| return resp.choices[0].message.content | |
| except Exception: | |
| return resp.choices[0].message.content | |
| def generate_report_with_kb(answers_map, category, top_k=3): | |
| # Build KB hits per question (lens) | |
| kb_hits = [] | |
| for q, a in answers_map.items(): | |
| query = (str(a).strip() or q) | |
| hits = KB_INDEX.query(query, top_k=top_k) | |
| kb_hits.extend(hits) | |
| # Compose prompt | |
| excerpt_text = "" | |
| if kb_hits: | |
| seen = set() | |
| for fn, txt in kb_hits: | |
| key = f"{fn}" | |
| if key not in seen: | |
| excerpt_text += f"\n--- {fn} ---\n{txt[:1200]}\n" | |
| seen.add(key) | |
| # Build messages | |
| messages = operational_command_prompt(answers_map, category) | |
| if excerpt_text: | |
| messages[1]["content"] += f"\nKnowledge Base excerpts (priority applied):\n{excerpt_text}\n" | |
| else: | |
| # Indicate we'll fallback to SF doctrine | |
| messages[1]["content"] += "\n[No KB excerpts found for these inputs; assistant may fallback to authoritative SF doctrine for doctrinal guidance.]\n" | |
| # Call model | |
| try: | |
| out = call_chat_api_system_user(messages, max_tokens=900) | |
| return out.strip() | |
| except Exception as e: | |
| # Fallback deterministic admin report | |
| lines = ["[FALLBACK NON-ACTIONABLE REPORT — AI unavailable]\n"] | |
| lines.append("Executive Summary: Administrative findings based on inputs.\n") | |
| lines.append("Key Issues:") | |
| for q, a in answers_map.items(): | |
| lines.append(f"- {q}: {'[no answer]' if not str(a).strip() else str(a)}") | |
| lines.append("\nAdministrative Recommendations (deterministic):") | |
| lines.append("- Ensure SITREP templates have mandatory fields (time, geo, observer).") | |
| lines.append("- Institute source vetting and require secondary confirmation of local reports.") | |
| lines.append("- Conduct a quarterly vulnerability audit and publish remediations.") | |
| lines.append(f"\nError: {e}") | |
| return "\n".join(lines) | |
| # PARA SF runner (KB first + SF doctrine fallback) | |
| def para_sf_inference_runner(selected_files, pasted_notes, answers_map): | |
| # Force priority list: if selected_files provided use them, else use SF_PRIORITY_PDFS present in KB | |
| selected = selected_files or [p for p in SF_PRIORITY_PDFS if p in FILES_TEXT] | |
| kb_hits = [] | |
| # pull KB excerpts only from selected first, then general KB if needed | |
| for q, a in answers_map.items(): | |
| query = (str(a).strip() or q) | |
| # search within selected files first | |
| for fn in selected: | |
| if fn in KB_INDEX.docs: | |
| for ch in KB_INDEX.docs[fn]: | |
| if query.lower() in ch.lower(): | |
| kb_hits.append((fn, ch[:1200])) | |
| break | |
| # if not found in selected, do general query | |
| if not any(fn == k for k,_ in kb_hits): | |
| hits = KB_INDEX.query(query, top_k=1) | |
| if hits: | |
| kb_hits.extend(hits) | |
| excerpt_text = "" | |
| if kb_hits: | |
| seen = set() | |
| for fn, txt in kb_hits: | |
| if fn not in seen: | |
| excerpt_text += f"\n--- {fn} ---\n{txt[:1200]}\n" | |
| seen.add(fn) | |
| # Compose user text | |
| user_text = "\n".join(f"{k}: {v}" for k, v in answers_map.items() if str(v).strip()) | |
| messages = [ | |
| {"role":"system","content":SYSTEM_SAFE}, | |
| {"role":"user","content":f""" | |
| Prepare a NON-ACTIONABLE PARA SF advisory using the 5D lens and doctrine. | |
| Inputs: | |
| {user_text} | |
| Fieldcraft notes: | |
| {pasted_notes} | |
| Selected SF KB files (priority): {selected} | |
| Knowledge Base excerpts (if any): | |
| {excerpt_text} | |
| Output required: | |
| - Executive Summary (2-4 lines) | |
| - Doctrinal Course of Action (administrative / doctrinal guidance only) | |
| - Threat Assessment (high-level, non-actionable) | |
| - Intelligence Summary (sources cited) | |
| - PARA SF Precautions & Protective Measures (administrative list) | |
| Cite KB filenames used. | |
| """}] | |
| try: | |
| out = call_chat_api_system_user(messages, max_tokens=1000) | |
| return out.strip() | |
| except Exception as e: | |
| # Fallback deterministic extraction of precautions | |
| lines = [f"[FALLBACK NON-ACTIONABLE PARA SF REPORT — AI unavailable: {e}]\n"] | |
| lines.append("Executive Summary: See fieldcraft and KB for details.\n") | |
| lines.append("Top observed inputs (sample):") | |
| cnt = 0 | |
| for k,v in answers_map.items(): | |
| if v and cnt < 8: | |
| lines.append(f"- {k}: {v}") | |
| cnt += 1 | |
| lines.append("\nPrecautions (sample deterministic):") | |
| for i, itm in enumerate(PARA_PRECAUTIONS_40[:12], start=1): | |
| lines.append(f"{i}. {itm} — Admin remediation: document & audit.") | |
| return "\n".join(lines) | |
| # ------------------------- | |
| # Gradio UI | |
| # ------------------------- | |
| with gr.Blocks() as demo: | |
| gr.HTML(f'<img src="{BANNER_URL}" width="100%">') | |
| gr.Markdown("# Kashmir AOR Action Plan — Battle Planner") | |
| gr.Markdown("⚠️ **NON-ACTIONABLE — Doctrinal / Administrative guidance only.**") | |
| # ---- Standard Tab ---- | |
| with gr.Tab("Standard"): | |
| std_inputs = [gr.Textbox(label=q, lines=1) for q in STD_QUESTIONS] | |
| std_button = gr.Button("Generate Standard Advisory") | |
| std_output = gr.Textbox(label="Standard Advisory Report (sanitized)", lines=28) | |
| def std_runner(*answers): | |
| amap = dict(zip(STD_QUESTIONS, answers)) | |
| return generate_report_with_kb(amap, "Standard Threat Advisory") | |
| std_button.click(std_runner, inputs=std_inputs, outputs=std_output) | |
| # ---- Enhanced Tab ---- | |
| with gr.Tab("Enhanced"): | |
| a_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_A] | |
| b_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_B] | |
| c_inputs = [gr.Textbox(label=q, lines=1) for q in ENH_SECTION_C] | |
| gate_input = gr.Textbox(label="Gate Question / Final Note", lines=1) | |
| enh_button = gr.Button("Generate Enhanced Advisory") | |
| enh_output = gr.Textbox(label="Enhanced Advisory Report (sanitized)", lines=28) | |
| def enh_runner(*answers): | |
| la, lb, lc = len(ENH_SECTION_A), len(ENH_SECTION_B), len(ENH_SECTION_C) | |
| vals = list(answers) | |
| # map A/B/C into a single answers_map for generation | |
| amap = {} | |
| for i, q in enumerate(ENH_SECTION_A): | |
| amap[q] = vals[i] if i < len(vals) else "" | |
| for j, q in enumerate(ENH_SECTION_B): | |
| idx = la + j | |
| amap[q] = vals[idx] if idx < len(vals) else "" | |
| for k, q in enumerate(ENH_SECTION_C): | |
| idx = la + lb + k | |
| amap[q] = vals[idx] if idx < len(vals) else "" | |
| gate = vals[-1] if vals else "" | |
| # include gate as special entry | |
| if gate: | |
| amap["Gate Assessment"] = gate | |
| return generate_report_with_kb(amap, "Enhanced 5D Advisory") | |
| enh_button.click(enh_runner, inputs=a_inputs+b_inputs+c_inputs+[gate_input], outputs=enh_output) | |
| # ---- Threat Readiness Tab ---- | |
| with gr.Tab("Threat Readiness"): | |
| gr.Markdown("## Threat Readiness — Color-coded Commander Brief (administrative)") | |
| threat_button = gr.Button("Evaluate Threat Readiness") | |
| threat_output = gr.Textbox(label="Threat Readiness & Diagnostics (sanitized)", lines=28) | |
| def threat_runner(): | |
| lines = [] | |
| lines.append("### Threat Readiness Level (Color-coded) — Administrative Brief") | |
| lines.append("- 🔴 RED (<50%): Significant administrative vulnerabilities. Prioritize SOP, CI, audits.") | |
| lines.append("- 🟠 ORANGE (50–69%): Moderate gaps; schedule doctrinal reviews and R&S validation.") | |
| lines.append("- 🔵 BLUE (70–84%): Minor gaps; plan targeted training and audits.") | |
| lines.append("- 🟢 GREEN (85–100%): Strong readiness; maintain periodic reviews.\n") | |
| lines.append("Commander’s Guide: Use remedial actions focused on doctrine, SOP updates, source vetting and audits. This brief is non-actionable.") | |
| return "\n".join(lines) | |
| threat_button.click(threat_runner, inputs=[], outputs=threat_output) | |
| # ---- PARA SF Tab ---- | |
| with gr.Tab("PARA SF (Two Reports)"): | |
| gr.Markdown("## PARA SF — Two Separate Administrative Advisories (Non-Actionable)") | |
| para_questions_inputs = [gr.Textbox(label=q, lines=1) for q in PARA_QUESTIONS_50] | |
| para_fieldcraft = gr.Textbox(label="Paste Fieldcraft / SR notes", lines=6) | |
| para_file_selector = gr.CheckboxGroup(choices=SF_PRIORITY_PDFS, label="Select SF KB files (optional)") | |
| para_coa_btn = gr.Button("Generate COA / Threat Assessment / Intelligence Summary") | |
| para_prec_btn = gr.Button("Generate PARA SF Precautions & Protective Advisory") | |
| para_coa_out = gr.Textbox(label="COA / Threat Assessment / Intelligence Summary (sanitized)", lines=28) | |
| para_prec_out = gr.Textbox(label="PARA SF Precautions & Protective Measures (sanitized)", lines=28) | |
| def para_coa_runner(*all_inputs): | |
| # last two inputs are pasted notes and file selector | |
| answers = list(all_inputs[:-2]) | |
| pasted = all_inputs[-2] or "" | |
| selected = all_inputs[-1] or [] | |
| amap = dict(zip(PARA_QUESTIONS_50, answers)) | |
| return para_sf_inference_runner(selected, pasted, amap) | |
| def para_prec_runner(*all_inputs): | |
| answers = list(all_inputs[:-2]) | |
| pasted = all_inputs[-2] or "" | |
| selected = all_inputs[-1] or [] | |
| amap = dict(zip(PARA_QUESTIONS_50, answers)) | |
| # We'll run the same inference but return the precautions section — model is asked to include it | |
| return para_sf_inference_runner(selected, pasted, amap) | |
| para_coa_btn.click(para_coa_runner, inputs=para_questions_inputs+[para_fieldcraft, para_file_selector], outputs=para_coa_out) | |
| para_prec_btn.click(para_prec_runner, inputs=para_questions_inputs+[para_fieldcraft, para_file_selector], outputs=para_prec_out) | |
| # ------------------------- | |
| # Launch | |
| # ------------------------- | |
| if __name__ == "__main__": | |
| demo.launch() | |