misukisu commited on
Commit
7e4367c
·
verified ·
1 Parent(s): 5ab7720

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +271 -51
app.py CHANGED
@@ -20,6 +20,19 @@ DEFAULT_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
20
  DEFAULT_BASE_URL = os.environ.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
21
  DEFAULT_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  # =====================================================================
24
  # 2. STRICT VALIDATION & INPUT SANITIZATION LAYER
25
  # =====================================================================
@@ -55,6 +68,7 @@ def validate_http_url(url: str) -> str:
55
  return url
56
 
57
 
 
58
  class SubdomainEnumInput(BaseModel):
59
  domain: str = Field(..., description="Target domain to inspect (e.g., example.com)")
60
 
@@ -104,10 +118,146 @@ class PacketCaptureInput(BaseModel):
104
  return v
105
 
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  # =====================================================================
108
- # 3. ASYNCHRONOUS DIAGNOSTIC TOOL WRAPPERS
109
  # =====================================================================
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  async def run_subdomain_enumerator(domain: str) -> Dict[str, Any]:
112
  """Query certificate transparency logs (crt.sh) for domain asset mapping."""
113
  try:
@@ -140,11 +290,7 @@ async def run_subdomain_enumerator(domain: str) -> Dict[str, Any]:
140
  "subdomains": sorted(list(subdomains))[:100]
141
  }
142
  except Exception as exc:
143
- return {
144
- "status": "error",
145
- "message": f"Subdomain enumeration failed: {str(exc)}",
146
- "subdomains": []
147
- }
148
 
149
 
150
  async def run_nmap_port_scanner(target: str) -> Dict[str, Any]:
@@ -169,15 +315,9 @@ async def run_nmap_port_scanner(target: str) -> Dict[str, Any]:
169
  "stderr": stderr.decode("utf-8", errors="replace")
170
  }
171
  except asyncio.TimeoutError:
172
- return {
173
- "status": "error",
174
- "message": "Nmap scan execution timed out (45s limit)."
175
- }
176
  except Exception as exc:
177
- return {
178
- "status": "error",
179
- "message": f"Nmap execution error: {str(exc)}"
180
- }
181
 
182
 
183
  async def run_web_scraper_and_analyzer(url: str) -> Dict[str, Any]:
@@ -186,9 +326,7 @@ async def run_web_scraper_and_analyzer(url: str) -> Dict[str, Any]:
186
  validated = WebScraperInput(url=url)
187
  target_url = validated.url
188
 
189
- headers = {
190
- "User-Agent": "Diagnostic-Audit-Agent/1.0 (Compliance; Non-Intrusive)"
191
- }
192
 
193
  async with httpx.AsyncClient(timeout=15.0, follow_redirects=True) as client:
194
  resp = await client.get(target_url, headers=headers)
@@ -223,10 +361,7 @@ async def run_web_scraper_and_analyzer(url: str) -> Dict[str, Any]:
223
  "external_scripts": scripts
224
  }
225
  except Exception as exc:
226
- return {
227
- "status": "error",
228
- "message": f"Web analyzer error: {str(exc)}"
229
- }
230
 
231
 
232
  async def run_packet_capture_analyzer(pcap_path: str, display_filter: str = "") -> Dict[str, Any]:
@@ -237,10 +372,7 @@ async def run_packet_capture_analyzer(pcap_path: str, display_filter: str = "")
237
  filt = validated.display_filter
238
 
239
  if not os.path.exists(target_path):
240
- return {
241
- "status": "error",
242
- "message": f"PCAP file '{target_path}' not found in runtime directory."
243
- }
244
 
245
  cmd = ["tshark", "-r", target_path, "-c", "50"]
246
  if filt:
@@ -260,10 +392,7 @@ async def run_packet_capture_analyzer(pcap_path: str, display_filter: str = "")
260
  "stderr": stderr.decode("utf-8", errors="replace")
261
  }
262
  except Exception as exc:
263
- return {
264
- "status": "error",
265
- "message": f"Tshark analysis error: {str(exc)}"
266
- }
267
 
268
 
269
  # =====================================================================
@@ -271,6 +400,98 @@ async def run_packet_capture_analyzer(pcap_path: str, display_filter: str = "")
271
  # =====================================================================
272
 
273
  DIAGNOSTIC_TOOLS = [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  {
275
  "type": "function",
276
  "function": {
@@ -346,6 +567,10 @@ DIAGNOSTIC_TOOLS = [
346
  ]
347
 
348
  TOOL_HANDLERS = {
 
 
 
 
349
  "subdomain_enumerator": run_subdomain_enumerator,
350
  "nmap_port_scanner": run_nmap_port_scanner,
351
  "web_scraper_and_analyzer": run_web_scraper_and_analyzer,
@@ -357,18 +582,8 @@ TOOL_HANDLERS = {
357
  # 5. RECURSIVE AGENTIC REACTION LOOP
358
  # =====================================================================
359
 
360
- SYSTEM_PROMPT = """You are an automated System Administration and Infrastructure Diagnostics Agent.
361
- You assist system administrators and network engineers in auditing network assets, inspecting security configurations, and mapping infrastructure surfaces.
362
-
363
- Operational Guidelines:
364
- 1. Systematically investigate target infrastructure using the provided diagnostic tools.
365
- 2. If tool outputs indicate further investigation is necessary, call subsequent tools autonomously.
366
- 3. Present final summaries structured with: Target Profile, Technical Diagnostics, Configuration Observations, and Remediation/Hardening Recommendations.
367
- 4. Maintain a neutral, engineering-focused reporting format."""
368
-
369
-
370
  async def execute_tool(tool_name: str, arguments_json: str) -> str:
371
- """Safely parse arguments and route to designated diagnostic wrapper."""
372
  if tool_name not in TOOL_HANDLERS:
373
  return json.dumps({"error": f"Tool '{tool_name}' not recognized."})
374
 
@@ -391,10 +606,11 @@ async def run_agent_loop(
391
  api_key: str,
392
  base_url: str,
393
  model_name: str,
394
- max_steps: int = 5
 
395
  ):
396
  """
397
- Executes the asynchronous multi-turn tool calling loop against the OpenAI-compatible API.
398
  Streams execution progress, reasoning traces, and the final synthesis.
399
  """
400
  if not api_key:
@@ -403,7 +619,7 @@ async def run_agent_loop(
403
 
404
  client = AsyncOpenAI(api_key=api_key, base_url=base_url)
405
 
406
- messages = [{"role": "system", "content": SYSTEM_PROMPT}]
407
  for msg in chat_history:
408
  messages.append({"role": msg["role"], "content": msg["content"]})
409
  messages.append({"role": "user", "content": user_message})
@@ -511,7 +727,7 @@ def create_ui():
511
  """
512
  )
513
 
514
- with gr.Accordion("⚙️ Endpoint & API Settings", open=False):
515
  with gr.Row():
516
  api_key_input = gr.Textbox(
517
  label="API Key",
@@ -527,10 +743,14 @@ def create_ui():
527
  label="Model Identifier",
528
  value=DEFAULT_MODEL
529
  )
 
 
 
 
 
530
 
531
  with gr.Row():
532
  with gr.Column(scale=5):
533
- # Cleaned keyword arguments for universal Gradio 4/5/6 compatibility
534
  chatbot = gr.Chatbot(
535
  label="Diagnostic Workflow Session",
536
  height=520
@@ -538,7 +758,7 @@ def create_ui():
538
  with gr.Row():
539
  user_input = gr.Textbox(
540
  label="Audit Command / Target Specification",
541
- placeholder="E.g., Audit security headers and map open services for scanme.nmap.org",
542
  lines=2,
543
  scale=4
544
  )
@@ -552,7 +772,7 @@ def create_ui():
552
  elem_id="terminal-output"
553
  )
554
 
555
- async def on_submit(user_msg, chat_hist, key, url, model):
556
  if not user_msg.strip():
557
  yield chat_hist, "*No input provided.*"
558
  return
@@ -563,14 +783,14 @@ def create_ui():
563
  chat_history=chat_hist,
564
  api_key=key,
565
  base_url=url,
566
- model_name=model
 
567
  ):
568
  yield updated_chat, updated_logs
569
 
570
- # Submit handlers (Button click + Enter key press)
571
  submit_btn.click(
572
  fn=on_submit,
573
- inputs=[user_input, chatbot, api_key_input, base_url_input, model_name_input],
574
  outputs=[chatbot, terminal_logs]
575
  ).then(
576
  fn=lambda: "",
@@ -580,7 +800,7 @@ def create_ui():
580
 
581
  user_input.submit(
582
  fn=on_submit,
583
- inputs=[user_input, chatbot, api_key_input, base_url_input, model_name_input],
584
  outputs=[chatbot, terminal_logs]
585
  ).then(
586
  fn=lambda: "",
 
20
  DEFAULT_BASE_URL = os.environ.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
21
  DEFAULT_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
22
 
23
+ DEFAULT_SYSTEM_PROMPT = os.environ.get(
24
+ "SYSTEM_PROMPT",
25
+ """You are an automated System Administration and Infrastructure Diagnostics Agent.
26
+ You assist system administrators and network engineers in auditing network assets, inspecting configurations, executing terminal diagnostics, and mapping infrastructure surfaces.
27
+
28
+ Operational Guidelines:
29
+ 1. Systematically investigate target infrastructure using the provided diagnostic and terminal tools.
30
+ 2. When necessary, use the bash executor, curl requester, or Python interpreter to inspect live responses, process outputs, or run diagnostic commands.
31
+ 3. If tool outputs indicate further investigation is necessary, call subsequent tools autonomously.
32
+ 4. Present final summaries structured with: Target Profile, Technical Diagnostics, Configuration Observations, and Hardening Recommendations.
33
+ 5. Maintain a neutral, engineering-focused reporting format."""
34
+ )
35
+
36
  # =====================================================================
37
  # 2. STRICT VALIDATION & INPUT SANITIZATION LAYER
38
  # =====================================================================
 
68
  return url
69
 
70
 
71
+ # Pydantic Schemas for Tools
72
  class SubdomainEnumInput(BaseModel):
73
  domain: str = Field(..., description="Target domain to inspect (e.g., example.com)")
74
 
 
118
  return v
119
 
120
 
121
+ class BashCommandInput(BaseModel):
122
+ command: str = Field(..., description="Shell command to execute in the persistent container environment")
123
+
124
+
125
+ class PythonCodeInput(BaseModel):
126
+ code: str = Field(..., description="Python 3 script code to execute in the container")
127
+
128
+
129
+ class CurlRequestInput(BaseModel):
130
+ url: str = Field(..., description="Target URL to query with curl")
131
+ method: Optional[str] = Field(default="GET", description="HTTP Method (GET, POST, HEAD, OPTIONS)")
132
+ headers: Optional[List[str]] = Field(default=[], description="List of headers in 'Header: Value' format")
133
+ data: Optional[str] = Field(default="", description="Request body payload")
134
+ include_headers: Optional[bool] = Field(default=True, description="Whether to include response headers (-i)")
135
+
136
+
137
+ class DnsLookupInput(BaseModel):
138
+ domain: str = Field(..., description="Domain name or host to query")
139
+ record_type: Optional[str] = Field(default="ANY", description="DNS record type (A, AAAA, MX, NS, TXT, CNAME, ANY)")
140
+
141
+
142
  # =====================================================================
143
+ # 3. ASYNCHRONOUS TOOL IMPLEMENTATIONS
144
  # =====================================================================
145
 
146
+ async def run_bash_executor(command: str) -> Dict[str, Any]:
147
+ """Executes arbitrary shell commands in the persistent container workspace."""
148
+ try:
149
+ validated = BashCommandInput(command=command)
150
+ process = await asyncio.create_subprocess_shell(
151
+ validated.command,
152
+ stdout=asyncio.subprocess.PIPE,
153
+ stderr=asyncio.subprocess.PIPE,
154
+ cwd="/app"
155
+ )
156
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=60.0)
157
+
158
+ return {
159
+ "status": "success" if process.returncode == 0 else "error",
160
+ "exit_code": process.returncode,
161
+ "stdout": stdout.decode("utf-8", errors="replace"),
162
+ "stderr": stderr.decode("utf-8", errors="replace")
163
+ }
164
+ except asyncio.TimeoutError:
165
+ return {"status": "error", "message": "Command execution timed out (60s limit)."}
166
+ except Exception as exc:
167
+ return {"status": "error", "message": f"Execution error: {str(exc)}"}
168
+
169
+
170
+ async def run_python_interpreter(code: str) -> Dict[str, Any]:
171
+ """Executes Python code scripts asynchronously in the runtime container."""
172
+ try:
173
+ validated = PythonCodeInput(code=code)
174
+ process = await asyncio.create_subprocess_exec(
175
+ "python3", "-c", validated.code,
176
+ stdout=asyncio.subprocess.PIPE,
177
+ stderr=asyncio.subprocess.PIPE,
178
+ cwd="/app"
179
+ )
180
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30.0)
181
+
182
+ return {
183
+ "status": "success" if process.returncode == 0 else "error",
184
+ "exit_code": process.returncode,
185
+ "stdout": stdout.decode("utf-8", errors="replace"),
186
+ "stderr": stderr.decode("utf-8", errors="replace")
187
+ }
188
+ except asyncio.TimeoutError:
189
+ return {"status": "error", "message": "Python script execution timed out (30s limit)."}
190
+ except Exception as exc:
191
+ return {"status": "error", "message": f"Python execution error: {str(exc)}"}
192
+
193
+
194
+ async def run_curl_requester(
195
+ url: str,
196
+ method: str = "GET",
197
+ headers: Optional[List[str]] = None,
198
+ data: Optional[str] = "",
199
+ include_headers: bool = True
200
+ ) -> Dict[str, Any]:
201
+ """Performs HTTP/HTTPS network operations using system curl."""
202
+ try:
203
+ validated = CurlRequestInput(
204
+ url=url,
205
+ method=method,
206
+ headers=headers or [],
207
+ data=data or "",
208
+ include_headers=include_headers
209
+ )
210
+
211
+ cmd = ["curl", "-s", "-S", "-X", validated.method.upper()]
212
+ if validated.include_headers:
213
+ cmd.append("-i")
214
+ if validated.headers:
215
+ for h in validated.headers:
216
+ cmd.extend(["-H", h])
217
+ if validated.data:
218
+ cmd.extend(["--data", validated.data])
219
+ cmd.append(validated.url)
220
+
221
+ process = await asyncio.create_subprocess_exec(
222
+ *cmd,
223
+ stdout=asyncio.subprocess.PIPE,
224
+ stderr=asyncio.subprocess.PIPE
225
+ )
226
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30.0)
227
+
228
+ return {
229
+ "status": "success" if process.returncode == 0 else "error",
230
+ "exit_code": process.returncode,
231
+ "stdout": stdout.decode("utf-8", errors="replace"),
232
+ "stderr": stderr.decode("utf-8", errors="replace")
233
+ }
234
+ except Exception as exc:
235
+ return {"status": "error", "message": f"Curl execution error: {str(exc)}"}
236
+
237
+
238
+ async def run_dns_lookup(domain: str, record_type: str = "ANY") -> Dict[str, Any]:
239
+ """Queries DNS records using dig."""
240
+ try:
241
+ validated = DnsLookupInput(domain=domain, record_type=record_type)
242
+ cmd = ["dig", "+noall", "+answer", "+comments", validated.domain, validated.record_type.upper()]
243
+
244
+ process = await asyncio.create_subprocess_exec(
245
+ *cmd,
246
+ stdout=asyncio.subprocess.PIPE,
247
+ stderr=asyncio.subprocess.PIPE
248
+ )
249
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=15.0)
250
+
251
+ return {
252
+ "status": "success" if process.returncode == 0 else "error",
253
+ "exit_code": process.returncode,
254
+ "stdout": stdout.decode("utf-8", errors="replace"),
255
+ "stderr": stderr.decode("utf-8", errors="replace")
256
+ }
257
+ except Exception as exc:
258
+ return {"status": "error", "message": f"DNS query error: {str(exc)}"}
259
+
260
+
261
  async def run_subdomain_enumerator(domain: str) -> Dict[str, Any]:
262
  """Query certificate transparency logs (crt.sh) for domain asset mapping."""
263
  try:
 
290
  "subdomains": sorted(list(subdomains))[:100]
291
  }
292
  except Exception as exc:
293
+ return {"status": "error", "message": f"Subdomain enumeration failed: {str(exc)}", "subdomains": []}
 
 
 
 
294
 
295
 
296
  async def run_nmap_port_scanner(target: str) -> Dict[str, Any]:
 
315
  "stderr": stderr.decode("utf-8", errors="replace")
316
  }
317
  except asyncio.TimeoutError:
318
+ return {"status": "error", "message": "Nmap scan execution timed out (45s limit)."}
 
 
 
319
  except Exception as exc:
320
+ return {"status": "error", "message": f"Nmap execution error: {str(exc)}"}
 
 
 
321
 
322
 
323
  async def run_web_scraper_and_analyzer(url: str) -> Dict[str, Any]:
 
326
  validated = WebScraperInput(url=url)
327
  target_url = validated.url
328
 
329
+ headers = {"User-Agent": "Diagnostic-Audit-Agent/1.0 (Compliance; Non-Intrusive)"}
 
 
330
 
331
  async with httpx.AsyncClient(timeout=15.0, follow_redirects=True) as client:
332
  resp = await client.get(target_url, headers=headers)
 
361
  "external_scripts": scripts
362
  }
363
  except Exception as exc:
364
+ return {"status": "error", "message": f"Web analyzer error: {str(exc)}"}
 
 
 
365
 
366
 
367
  async def run_packet_capture_analyzer(pcap_path: str, display_filter: str = "") -> Dict[str, Any]:
 
372
  filt = validated.display_filter
373
 
374
  if not os.path.exists(target_path):
375
+ return {"status": "error", "message": f"PCAP file '{target_path}' not found in runtime directory."}
 
 
 
376
 
377
  cmd = ["tshark", "-r", target_path, "-c", "50"]
378
  if filt:
 
392
  "stderr": stderr.decode("utf-8", errors="replace")
393
  }
394
  except Exception as exc:
395
+ return {"status": "error", "message": f"Tshark analysis error: {str(exc)}"}
 
 
 
396
 
397
 
398
  # =====================================================================
 
400
  # =====================================================================
401
 
402
  DIAGNOSTIC_TOOLS = [
403
+ {
404
+ "type": "function",
405
+ "function": {
406
+ "name": "bash_executor",
407
+ "description": "Executes shell commands directly inside the persistent Linux container workspace (/app).",
408
+ "parameters": {
409
+ "type": "object",
410
+ "properties": {
411
+ "command": {
412
+ "type": "string",
413
+ "description": "The bash shell command to run."
414
+ }
415
+ },
416
+ "required": ["command"]
417
+ }
418
+ }
419
+ },
420
+ {
421
+ "type": "function",
422
+ "function": {
423
+ "name": "python_interpreter",
424
+ "description": "Runs Python 3 code in the container runtime environment.",
425
+ "parameters": {
426
+ "type": "object",
427
+ "properties": {
428
+ "code": {
429
+ "type": "string",
430
+ "description": "Python code snippet to execute."
431
+ }
432
+ },
433
+ "required": ["code"]
434
+ }
435
+ }
436
+ },
437
+ {
438
+ "type": "function",
439
+ "function": {
440
+ "name": "curl_requester",
441
+ "description": "Issues HTTP/HTTPS network requests using curl with custom methods, headers, and payloads.",
442
+ "parameters": {
443
+ "type": "object",
444
+ "properties": {
445
+ "url": {
446
+ "type": "string",
447
+ "description": "Target URL."
448
+ },
449
+ "method": {
450
+ "type": "string",
451
+ "description": "HTTP Method (GET, POST, HEAD, OPTIONS).",
452
+ "default": "GET"
453
+ },
454
+ "headers": {
455
+ "type": "array",
456
+ "items": {"type": "string"},
457
+ "description": "List of headers in 'Header: Value' format."
458
+ },
459
+ "data": {
460
+ "type": "string",
461
+ "description": "Request body data."
462
+ },
463
+ "include_headers": {
464
+ "type": "boolean",
465
+ "description": "Include response headers in output.",
466
+ "default": True
467
+ }
468
+ },
469
+ "required": ["url"]
470
+ }
471
+ }
472
+ },
473
+ {
474
+ "type": "function",
475
+ "function": {
476
+ "name": "dns_lookup",
477
+ "description": "Queries DNS records using dig for domain discovery and verification.",
478
+ "parameters": {
479
+ "type": "object",
480
+ "properties": {
481
+ "domain": {
482
+ "type": "string",
483
+ "description": "Domain or host to look up."
484
+ },
485
+ "record_type": {
486
+ "type": "string",
487
+ "description": "Record type: A, AAAA, MX, NS, TXT, CNAME, ANY.",
488
+ "default": "ANY"
489
+ }
490
+ },
491
+ "required": ["domain"]
492
+ }
493
+ }
494
+ },
495
  {
496
  "type": "function",
497
  "function": {
 
567
  ]
568
 
569
  TOOL_HANDLERS = {
570
+ "bash_executor": run_bash_executor,
571
+ "python_interpreter": run_python_interpreter,
572
+ "curl_requester": run_curl_requester,
573
+ "dns_lookup": run_dns_lookup,
574
  "subdomain_enumerator": run_subdomain_enumerator,
575
  "nmap_port_scanner": run_nmap_port_scanner,
576
  "web_scraper_and_analyzer": run_web_scraper_and_analyzer,
 
582
  # 5. RECURSIVE AGENTIC REACTION LOOP
583
  # =====================================================================
584
 
 
 
 
 
 
 
 
 
 
 
585
  async def execute_tool(tool_name: str, arguments_json: str) -> str:
586
+ """Safely parse arguments and route to designated tool wrapper."""
587
  if tool_name not in TOOL_HANDLERS:
588
  return json.dumps({"error": f"Tool '{tool_name}' not recognized."})
589
 
 
606
  api_key: str,
607
  base_url: str,
608
  model_name: str,
609
+ system_prompt: str,
610
+ max_steps: int = 6
611
  ):
612
  """
613
+ Executes the asynchronous multi-turn tool calling loop.
614
  Streams execution progress, reasoning traces, and the final synthesis.
615
  """
616
  if not api_key:
 
619
 
620
  client = AsyncOpenAI(api_key=api_key, base_url=base_url)
621
 
622
+ messages = [{"role": "system", "content": system_prompt}]
623
  for msg in chat_history:
624
  messages.append({"role": msg["role"], "content": msg["content"]})
625
  messages.append({"role": "user", "content": user_message})
 
727
  """
728
  )
729
 
730
+ with gr.Accordion("⚙️ Endpoint & System Settings", open=False):
731
  with gr.Row():
732
  api_key_input = gr.Textbox(
733
  label="API Key",
 
743
  label="Model Identifier",
744
  value=DEFAULT_MODEL
745
  )
746
+ system_prompt_input = gr.Textbox(
747
+ label="System Prompt (Loaded from SYSTEM_PROMPT env by default)",
748
+ value=DEFAULT_SYSTEM_PROMPT,
749
+ lines=4
750
+ )
751
 
752
  with gr.Row():
753
  with gr.Column(scale=5):
 
754
  chatbot = gr.Chatbot(
755
  label="Diagnostic Workflow Session",
756
  height=520
 
758
  with gr.Row():
759
  user_input = gr.Textbox(
760
  label="Audit Command / Target Specification",
761
+ placeholder="E.g., Query DNS records, check HTTP response headers, or run a diagnostic script for scanme.nmap.org",
762
  lines=2,
763
  scale=4
764
  )
 
772
  elem_id="terminal-output"
773
  )
774
 
775
+ async def on_submit(user_msg, chat_hist, key, url, model, sys_prompt):
776
  if not user_msg.strip():
777
  yield chat_hist, "*No input provided.*"
778
  return
 
783
  chat_history=chat_hist,
784
  api_key=key,
785
  base_url=url,
786
+ model_name=model,
787
+ system_prompt=sys_prompt
788
  ):
789
  yield updated_chat, updated_logs
790
 
 
791
  submit_btn.click(
792
  fn=on_submit,
793
+ inputs=[user_input, chatbot, api_key_input, base_url_input, model_name_input, system_prompt_input],
794
  outputs=[chatbot, terminal_logs]
795
  ).then(
796
  fn=lambda: "",
 
800
 
801
  user_input.submit(
802
  fn=on_submit,
803
+ inputs=[user_input, chatbot, api_key_input, base_url_input, model_name_input, system_prompt_input],
804
  outputs=[chatbot, terminal_logs]
805
  ).then(
806
  fn=lambda: "",