Basementup commited on
Commit
9035f56
·
verified ·
1 Parent(s): 62922ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +391 -501
app.py CHANGED
@@ -1,17 +1,15 @@
1
  import gradio as gr
2
  import os
 
3
  import time
4
  import subprocess
5
  import tempfile
6
- import math
7
  from pathlib import Path
8
  from huggingface_hub import InferenceClient
9
 
10
- # ── INSTALL DEPENDENCIES ────────────────────────────────────────────────
11
  for pkg in ["pypdf", "edge-tts", "pydub", "pillow", "pytesseract", "openai-whisper", "ffmpeg-python"]:
12
  subprocess.run(["pip", "install", pkg, "-q"], capture_output=True)
13
-
14
- # Install ffmpeg system binary if not present
15
  subprocess.run(["apt-get", "install", "-y", "-q", "ffmpeg", "tesseract-ocr"], capture_output=True)
16
 
17
  from pypdf import PdfReader
@@ -21,15 +19,30 @@ from pydub import AudioSegment
21
  from PIL import Image
22
  import pytesseract
23
 
24
- MODEL = "meta-llama/Llama-3.3-70B-Instruct"
 
 
 
 
25
 
26
- # ── CLIENT ───────────────────────────────────────────────────────────────
27
- def get_client():
28
  token = os.environ.get("HF_TOKEN", "")
29
- return InferenceClient(model=MODEL, token=token if token else None)
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
 
32
- # ── FILE EXTRACTION ───────────────────────────────────────────────────────
33
  def extract_pdf_text(path):
34
  try:
35
  reader = PdfReader(path)
@@ -38,27 +51,19 @@ def extract_pdf_text(path):
38
  text = page.extract_text() or ""
39
  if text.strip():
40
  pages.append(f"--- Page {i+1} ---\n{text.strip()}")
41
- if not pages:
42
- return "[No extractable text — may be a scanned PDF]"
43
- return "\n\n".join(pages)
44
  except Exception as e:
45
  return f"[PDF read error: {e}]"
46
 
47
-
48
  def extract_image_text(path):
49
- """OCR an image using pytesseract."""
50
  try:
51
  img = Image.open(path)
52
  text = pytesseract.image_to_string(img, lang="eng")
53
- if text.strip():
54
- return f"[IMAGE OCR]\n{text.strip()}"
55
- return "[Image: no text detected by OCR]"
56
  except Exception as e:
57
  return f"[Image OCR error: {e}]"
58
 
59
-
60
  def transcribe_audio(path):
61
- """Transcribe audio/video using OpenAI Whisper (runs locally)."""
62
  try:
63
  import whisper
64
  model = whisper.load_model("base")
@@ -67,36 +72,24 @@ def transcribe_audio(path):
67
  except Exception as e:
68
  return f"[Transcription error: {e}]"
69
 
70
-
71
  def extract_content(file):
72
- """Route to correct extractor based on file type."""
73
- if not file:
74
- return ""
75
  path = file.name if hasattr(file, "name") else str(file)
76
- ext = Path(path).suffix.lower()
77
-
78
  if ext == ".pdf":
79
  return extract_pdf_text(path)
80
- elif ext in [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff"]:
81
  return extract_image_text(path)
82
- elif ext in [".mp3", ".wav", ".ogg", ".m4a", ".flac"]:
83
  return transcribe_audio(path)
84
- elif ext in [".mp4", ".mov", ".avi", ".mkv", ".webm"]:
85
- # Extract audio from video then transcribe
86
  try:
87
- tmp_audio = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False, dir="/tmp")
88
- subprocess.run(
89
- ["ffmpeg", "-i", path, "-q:a", "0", "-map", "a", tmp_audio.name, "-y"],
90
- capture_output=True, timeout=120
91
- )
92
- return transcribe_audio(tmp_audio.name)
93
  except Exception as e:
94
  return f"[Video extraction error: {e}]"
95
- elif ext in [".txt", ".md", ".csv"]:
96
- try:
97
- return Path(path).read_text(encoding="utf-8", errors="replace")
98
- except Exception as e:
99
- return f"[Text read error: {e}]"
100
  else:
101
  try:
102
  return Path(path).read_text(encoding="utf-8", errors="replace")
@@ -104,254 +97,211 @@ def extract_content(file):
104
  return f"[Unsupported file type: {ext}]"
105
 
106
 
107
- # ── AI ────────────────────────────────────────────────────────────────────
108
- def call_llama(prompt, max_tokens=2000):
109
- try:
110
- client = get_client()
111
- response = client.chat_completion(
112
- messages=[{"role": "user", "content": prompt}],
113
- max_tokens=max_tokens,
114
- temperature=0.7,
115
- )
116
- return response.choices[0].message.content
117
- except Exception as e:
118
- return f"⚠️ Error: {e}"
119
-
120
-
121
- # ── ANALYSIS ──────────────────────────────────────────────────────────────
122
  PLAIN_ENGLISH = (
123
- "\n\nCRITICAL: Write in plain clear English prose only. "
124
- "No JSON. No code. No brackets like {{ or }}. No markdown symbols. "
125
- "No asterisks. No hash symbols. Just clear numbered paragraphs and sentences "
126
- "a non-lawyer can read and understand."
 
 
 
 
127
  )
128
 
129
  def analyse(files):
130
  if not files:
131
- return "⚠️ Please upload at least one file.", "", "", ""
132
-
133
- # Accept single file or list
134
  if not isinstance(files, list):
135
  files = [files]
136
 
137
- # Extract content from all files and combine
138
  all_content = []
139
  for i, f in enumerate(files):
140
  content = extract_content(f)
141
  name = Path(f if isinstance(f, str) else f.name).name if f else f"Document {i+1}"
142
- if content and not content.startswith("["):
143
- all_content.append(f"DOCUMENT {i+1}: {name}\n{'─'*50}\n{content}")
144
- else:
145
- all_content.append(f"DOCUMENT {i+1}: {name}\n{'─'*50}\n{content or '[Empty]'}")
146
 
147
- combined = "\n\n".join(all_content)
148
  doc_chunk = combined[:14000]
149
- base = f"DOCUMENTS SUPPLIED ({len(files)} total):\n\n{doc_chunk}\n\n{'='*60}\n\n"
150
 
151
  r1 = call_llama(base +
152
- "You are Introbot — part of the Metabolic Phoenix Framework by Dwayne Anthony Brian Galloway.\n\n"
153
- "These documents contain evidence of institutional breaches. Your job is not to speculate — it is to record what is confirmed on the record.\n\n"
154
- "From these documents state with certainty:\n"
155
- "- Every confirmed fact: who said it, when, and what it proves\n"
156
- "- Every admission made, explicit or implied — label it ADMISSION\n"
157
- "- Every denial made — label it DENIAL and state what it contradicts\n"
158
- "- Every contradiction — state what was said, what contradicts it, and when\n"
159
- "- Every deliberate omission — what was required to be said, was not said, and why that is a breach\n"
160
- "- Every statutory duty that was triggered and the precise moment it was triggered\n\n"
161
- "Do not hedge. Do not say 'may indicate' or 'appears to suggest'. If it is in the document, it happened. State it as fact.\n\n"
162
- "Format as a clean numbered FACT LEDGER." + PLAIN_ENGLISH
163
  )
 
164
  r2 = call_llama(base +
165
- "You are RHB 2.0 + CPF — part of the Metabolic Phoenix Framework by Dwayne Anthony Brian Galloway.\n\n"
166
- "Breaches have occurred. Your role is to name them, prove them, and record them for litigation.\n\n"
167
- "From these documents confirm the following:\n"
168
  "- Each institution's statutory duties and the exact moment each duty was triggered\n"
169
- "- For each duty: state BREACH CONFIRMED or COMPLIED — never leave it neutral\n"
170
- "- For every breach: name the statute, the section, the precise act or omission, and the date it occurred\n"
171
- "- Every legal error is to be recorded as a finding, not a possibility\n"
172
- "- Trace the full causal chain: first breach → escalation → compounded harm → final injury\n"
173
- "- Equality Act 2010 breaches: confirm each s.13 direct discrimination and s.20 failure to make reasonable adjustments as established — not alleged\n"
174
- "- For each breach state the remedy the law requires and what has been unlawfully withheld\n\n"
175
- "Do not use the word 'may', 'potentially', 'appears' or 'suggests'. These are findings, not opinions.\n"
176
- "Be forensic. Cite statute sections." + PLAIN_ENGLISH
177
  )
 
178
  r3 = call_llama(base +
179
- "You are Phoenix Badger — part of the Metabolic Phoenix Framework by Dwayne Anthony Brian Galloway.\n\n"
180
- "Run all four Ouroboros rings on these documents. These rings do not speculate — they expose.\n\n"
181
- "RING A — FORWARD SCAN: State every evasion tactic, deflection, and bad faith manoeuvre confirmed in these documents. Name what each one is designed to obscure.\n\n"
182
- "RING B — BACKWARD SWEEP: State every past decision in these documents that was unlawful at the time it was made. Name the law that was breached and when.\n\n"
183
- "RING C — TYPOLOGY MUTATION: Name the patterns of institutional misconduct these documents confirm. State whether each pattern is systemic or targeted.\n\n"
184
- "RING D — DRIFT CORRECTION: Call out every internal contradiction and inconsistency across the documents. State what each contradiction proves.\n\n"
185
- "End with ESCALATION MAP: which regulatory bodies and courts must receive this, in priority order, with reasons." + PLAIN_ENGLISH
 
 
 
 
 
186
  )
 
187
  r4 = call_llama(
188
- f"You are Layoutbot — part of the Metabolic Phoenix Framework by Dwayne Anthony Brian Galloway.\n\n"
189
- f"DOCUMENT EXTRACT:\n{doc_chunk[:3000]}\n\n"
190
  f"FACT LEDGER:\n{r1[:1500]}\n\n"
191
- f"COMPLIANCE ANALYSIS:\n{r2[:1500]}\n\n"
192
  f"LOOP OUTPUT:\n{r3[:1500]}\n\n"
193
  f"{'='*60}\n\n"
194
- "Produce a complete litigation-ready case summary. This is not a risk assessment. Breaches have occurred and are on the record. Write accordingly.\n\n"
195
- "EXECUTIVE SUMMARY — plain English account of what happened, what was done wrong, and who did it\n"
196
- "CONFIRMED BREACHES — each breach named, statute and section cited, date of breach confirmed\n"
197
- "TOP 5 ARGUMENTS — strongest arguments first, each grounded in confirmed fact and statute\n"
198
- "REQUIRED NEXT STEPS — mandatory actions in priority order, not suggestions\n"
199
- "ESCALATION MAP — which body receives what, by what deadline, and what the failure to escalate costs\n\n"
 
200
  "Ms. Nala | Injectiv8 | Dwayne Anthony Brian Galloway" + PLAIN_ENGLISH
201
  )
202
  return r1, r2, r3, r4
203
 
204
 
205
- # ── DOCUMENT GENERATION ───────────────────────────────────────────────────
206
  DOC_PLAIN = (
207
- "\n\nCRITICAL FORMATTING RULES: "
208
- "Write in plain, clear, forceful English prose only. "
209
- "No JSON. No code. No markdown symbols, asterisks, or hash symbols. "
210
- "No brackets like {{ or }}. Use numbered paragraphs and clear headings in CAPITALS. "
211
- "This is a formal legal document. Every sentence must carry weight. "
212
- "Do not hedge. Do not say 'may' or 'allegedly' or 'it appears'. "
213
  "Breaches have occurred. Name them. Prove them. Demand remedy."
214
  )
215
 
216
  def generate_letter_before_action(r1, r2, r3, r4):
217
- """Letter Before Action — formal Pre-Action Protocol letter demanding remedy or litigation follows."""
218
- if not r1:
219
- return "Run the analysis first."
220
- prompt = (
221
  "You are a senior litigation solicitor drafting a Letter Before Action on behalf of "
222
- "Dwayne Anthony Brian Galloway (the Claimant) against the named respondents identified in the analysis below.\n\n"
223
- "ANALYSIS:\n"
224
  f"FACT LEDGER:\n{r1[:2000]}\n\nBREACH ANALYSIS:\n{r2[:2000]}\n\nCASE SUMMARY:\n{r4[:2000]}\n\n"
225
- "DRAFT A FORMAL LETTER BEFORE ACTION that:\n\n"
226
- "1. Opens by stating that this letter is sent under the Pre-Action Protocol and that proceedings will be issued in the absence of a satisfactory response within 14 days.\n"
227
- "2. States the full name of each respondent and each confirmed breach against them — by statute, section, and date. Do not allege. State.\n"
228
- "3. States every loss, harm and injury caused to the Claimant as a direct result of each breach, including financial loss, physical harm, psychological harm, and any aggravating circumstances relating to the Claimant's disability.\n"
229
- "4. States the specific legal remedies demanded: compensation, reasonable adjustments, correction of records, regulatory referral, costs — itemised.\n"
230
- "5. States clearly: if full remedy is not provided within 14 days, proceedings will be issued in the King's Bench Division without further notice, and the Claimant will seek indemnity costs on the basis of the respondents' conduct.\n"
231
- "6. Closes with the Claimant's name: Dwayne Anthony Brian Galloway, Litigant in Person.\n\n"
232
- "This letter must have a spine. It is not a complaint. It is a final warning before litigation."
233
- + DOC_PLAIN
234
  )
235
- return call_llama(prompt, max_tokens=2000)
236
-
237
 
238
  def generate_formal_complaint(r1, r2, r3, r4):
239
- """Formal Complaint — for submission to FOS, FCA, ICO, Parliamentary Ombudsman or regulator."""
240
- if not r1:
241
- return "Run the analysis first."
242
- prompt = (
243
  "You are drafting a formal regulatory complaint on behalf of Dwayne Anthony Brian Galloway "
244
- "for submission to the relevant regulatory body — Financial Ombudsman Service, FCA, ICO, or Parliamentary and Health Service Ombudsman as appropriate based on the breaches identified.\n\n"
245
- "ANALYSIS:\n"
246
  f"FACT LEDGER:\n{r1[:2000]}\n\nBREACH ANALYSIS:\n{r2[:2000]}\n\nLOOP ANALYSIS:\n{r3[:1500]}\n\n"
247
- "DRAFT A FORMAL REGULATORY COMPLAINT that:\n\n"
248
- "1. Names the respondent institution and the regulatory framework they are bound by.\n"
249
- "2. States the date the duty arose and the date it was breached — each breach as a numbered finding.\n"
250
- "3. States every statutory provision breached with full citation: Act, section, and subsection.\n"
251
- "4. States the harm caused by each breach in concrete terms: financial loss quantified, disability discrimination confirmed under Equality Act 2010 s.13 and s.20, hardship documented.\n"
252
- "5. States what the respondent was required to do, what they did instead, and what that proves about their conduct.\n"
253
- "6. States what remedy the Claimant requires from the regulator: investigation, enforcement, compensation, referral to FCA, referral to SRA, or public censure.\n"
254
- "7. States that the Claimant is a disabled person as defined under Equality Act 2010 s.6 and that the respondent's conduct throughout has been discriminatory in nature, intent, and effect.\n"
255
- "8. Closes with a demand that the regulator open a formal investigation and provide a substantive response within 8 weeks.\n\n"
256
- "This complaint must be comprehensive, numbered, and impossible to dismiss as informal."
257
- + DOC_PLAIN
258
  )
259
- return call_llama(prompt, max_tokens=2000)
260
-
261
 
262
  def generate_witness_statement(r1, r2, r4):
263
- """Witness Statement — first person, court-ready, signed with a statement of truth."""
264
- if not r1:
265
- return "Run the analysis first."
266
- prompt = (
267
- "You are drafting a formal Witness Statement for Dwayne Anthony Brian Galloway to file in civil proceedings.\n\n"
268
- "ANALYSIS:\n"
269
  f"FACT LEDGER:\n{r1[:2000]}\n\nBREACH ANALYSIS:\n{r2[:1500]}\n\nCASE SUMMARY:\n{r4[:1500]}\n\n"
270
- "DRAFT A FORMAL WITNESS STATEMENT in the first person (I, Dwayne Anthony Brian Galloway, state as follows:) that:\n\n"
271
- "1. Opens with the formal heading: IN THE HIGH COURT OF JUSTICE, KING'S BENCH DIVISION. Claim No. [TBC]. WITNESS STATEMENT OF DWAYNE ANTHONY BRIAN GALLOWAY.\n"
272
- "2. States the Claimant's identity, disability status under Equality Act 2010 s.6, and the basis of the claim.\n"
273
- "3. Narrates the full chronology of events in numbered paragraphs — what happened, when, who did it, and what was said or written.\n"
274
- "4. For each breach: states what the Claimant was told, what the Claimant did in response, and how the Claimant was harmed.\n"
275
- "5. States the financial losses, hardship, physical harm and psychological impact in the Claimant's own words.\n"
276
- "6. Addresses every denial by the respondents and states why each denial is contradicted by the documented record.\n"
277
- "7. States what the Claimant is asking the Court to find and to order.\n"
278
- "8. Closes with the formal Statement of Truth: I believe that the facts stated in this witness statement are true. I understand that proceedings for contempt of court may be brought against anyone who makes, or causes to be made, a false statement in a document verified by a statement of truth. Signed: Dwayne Anthony Brian Galloway. Date: [DATE].\n\n"
279
- "This statement must read as the account of a composed, credible, determined Claimant who knows exactly what was done to him and why it was unlawful."
280
- + DOC_PLAIN
281
  )
282
- return call_llama(prompt, max_tokens=2000)
283
-
284
 
285
  def generate_skeleton_argument(r1, r2, r3, r4):
286
- """Court Skeleton Argument — structured legal argument for hearing."""
287
- if not r1:
288
- return "Run the analysis first."
289
- prompt = (
290
  "You are a barrister drafting a Skeleton Argument for Dwayne Anthony Brian Galloway, "
291
- "Litigant in Person, for hearing in the King's Bench Division of the High Court.\n\n"
292
- "ANALYSIS:\n"
293
- f"FACT LEDGER:\n{r1[:1500]}\n\nBREACH ANALYSIS:\n{r2[:1500]}\n\nLOOP ANALYSIS:\n{r3[:1000]}\n\nCASE SUMMARY:\n{r4[:1500]}\n\n"
294
- "DRAFT A SKELETON ARGUMENT that:\n\n"
295
- "1. Opens with the case heading: IN THE HIGH COURT OF JUSTICE, KING'S BENCH DIVISION. Between: DWAYNE ANTHONY BRIAN GALLOWAY (Claimant) and [RESPONDENTS] (Defendants). SKELETON ARGUMENT OF THE CLAIMANT.\n"
296
- "2. States the issues for determination — numbered, clear, each one a question the Court must answer.\n"
297
- "3. Sets out the Legal Framework: every statute and case law authority relied upon, cited correctly (Act, section; case name [year] court, report).\n"
298
- "4. States the Claimant's submissions on each issue: what the law requires, what the Defendant did, why that is a breach, and what the Court must find.\n"
299
- "5. Addresses the Defendant's position and explains precisely why each element of it is wrong in law and on the facts.\n"
300
- "6. States the relief sought: damages quantified, declarations sought, costs on the indemnity basis, and any injunctive relief.\n"
301
- "7. Closes with a summary of why the Claimant must succeed and why the Defendants have no sustainable defence.\n\n"
302
- "Authorities to consider citing where the facts support them:\n"
303
- "Archibald v Fife Council [2004] UKHL 32 (reasonable adjustments — disability).\n"
304
- "Associated Provincial Picture Houses Ltd v Wednesbury Corporation [1948] 1 KB 223 (unreasonableness).\n"
305
- "Consumer Rights Act 2015 ss.9, 10, 11, 14, 19.\n"
306
- "Equality Act 2010 ss.6, 13, 20, 21, 26, 29, 110.\n"
307
- "Fraud Act 2006 s.2 (fraud by false representation).\n"
308
- "Consumer Protection from Unfair Trading Regulations 2008.\n"
309
- "Financial Services and Markets Act 2000 and FCA CONC/BCOBS sourcebooks.\n\n"
310
- "This skeleton must be tight, authoritative, and leave the Defendants nowhere to stand."
311
- + DOC_PLAIN
312
  )
313
- return call_llama(prompt, max_tokens=2000)
314
-
315
 
316
  def generate_regulatory_referral(r1, r2, r3):
317
- """Regulatory Referral — letter to FCA, SRA, ICO, or Parliamentary Ombudsman."""
318
- if not r1:
319
- return "Run the analysis first."
320
- prompt = (
321
  "You are drafting a formal referral letter to a regulatory authority on behalf of "
322
  "Dwayne Anthony Brian Galloway.\n\n"
323
- "ANALYSIS:\n"
324
  f"FACT LEDGER:\n{r1[:1500]}\n\nBREACH ANALYSIS:\n{r2[:1500]}\n\nLOOP ANALYSIS:\n{r3[:1500]}\n\n"
325
- "DRAFT A REGULATORY REFERRAL LETTER addressed to the appropriate regulator (FCA, SRA, ICO, or Ombudsman as the breaches dictate) that:\n\n"
326
- "1. States at the outset that this is a formal referral under the relevant statutory or regulatory framework and that the Claimant requires the regulator to open a formal investigation.\n"
327
- "2. Identifies each regulated entity by name and their regulatory authorisation number or registration.\n"
328
- "3. States each confirmed breach as a numbered finding: the conduct, the statutory provision breached, the date, and the harm caused.\n"
329
- "4. States that the conduct amounts to: regulatory misconduct, failure of regulated duty, discriminatory treatment of a disabled customer under Equality Act 2010, and where applicable, conduct unbecoming of a regulated legal professional under the SRA Code of Conduct 2019.\n"
330
- "5. States the specific regulatory action the Claimant requires: investigation, enforcement notice, fine, suspension, public censure, or referral to another authority.\n"
331
- "6. States that the Claimant is a disabled person under EA 2010 s.6 and that failure by the regulator to investigate constitutes a further failure to uphold equality obligations.\n"
332
- "7. Closes with a 28-day deadline for a substantive response and confirmation that in the absence of action, the Claimant will escalate to the Parliamentary and Health Service Ombudsman and the national press.\n\n"
333
- "This letter must leave the regulator in no doubt that inaction will have consequences."
334
- + DOC_PLAIN
 
335
  )
336
- return call_llama(prompt, max_tokens=2000)
337
-
338
 
339
  def generate_all_documents(r1, r2, r3, r4):
340
- """Generate all five documents from the analysis outputs."""
341
  if not r1:
342
- return "", "", "", "", "⚠️ Run the analysis first, then generate documents."
343
- lba = generate_letter_before_action(r1, r2, r3, r4)
344
- comp = generate_formal_complaint(r1, r2, r3, r4)
345
- wit = generate_witness_statement(r1, r2, r4)
346
- skel = generate_skeleton_argument(r1, r2, r3, r4)
347
- reg = generate_regulatory_referral(r1, r2, r3)
348
- return lba, comp, wit, skel, reg
349
-
350
 
351
  def save_documents(lba, comp, wit, skel, reg):
352
- """Save all generated documents to a single download file."""
353
  if not any([lba, comp, wit, skel, reg]):
354
- return None, "⚠️ Generate documents first."
355
  ts = time.strftime("%Y-%m-%d %H:%M")
356
  content = (
357
  f"METABOLIC PHOENIX FRAMEWORK — LEGAL DOCUMENTS\n"
@@ -368,156 +318,14 @@ def save_documents(lba, comp, wit, skel, reg):
368
  prefix=f"MPF_Documents_{time.strftime('%Y%m%d_%H%M')}_",
369
  mode="w", encoding="utf-8"
370
  )
371
- tmp.write(content)
372
- tmp.close()
373
- return tmp.name, "✅ All documents ready to download."
374
-
375
-
376
- # ── FULL TTS — NO 30-SECOND CAP ──────────────────────────────────────────
377
- # Microsoft Neural TTS via edge-tts — natural sounding, free, no API key
378
- # en-GB-SoniaNeural = natural British female voice
379
- TTS_VOICE = "en-GB-SoniaNeural"
380
- CHUNK_SIZE = 1500 # edge-tts handles much larger chunks than gTTS
381
-
382
- async def _speak_chunk(text: str, out_path: str):
383
- communicate = edge_tts.Communicate(text, TTS_VOICE)
384
- await communicate.save(out_path)
385
-
386
- def text_to_speech_full(text: str, speed: float = 1.0, pitch: float = 1.0) -> str | None:
387
- """
388
- Full-length neural TTS — Microsoft Sonia (en-GB neural voice).
389
- No length cap. speed: 0.5–2.0. pitch: 0.5–2.0.
390
- """
391
- if not text or not text.strip():
392
- return None
393
-
394
- text = text.strip()
395
- import re
396
- sentences = re.split(r'(?<=[.!?])\s+', text)
397
-
398
- chunks = []
399
- current = ""
400
- for s in sentences:
401
- if len(current) + len(s) + 1 <= CHUNK_SIZE:
402
- current = (current + " " + s).strip()
403
- else:
404
- if current:
405
- chunks.append(current)
406
- if len(s) > CHUNK_SIZE:
407
- words = s.split()
408
- wchunk = ""
409
- for w in words:
410
- if len(wchunk) + len(w) + 1 <= CHUNK_SIZE:
411
- wchunk = (wchunk + " " + w).strip()
412
- else:
413
- if wchunk:
414
- chunks.append(wchunk)
415
- wchunk = w
416
- if wchunk:
417
- chunks.append(wchunk)
418
- else:
419
- current = s
420
- if current:
421
- chunks.append(current)
422
-
423
- if not chunks:
424
- return None
425
-
426
- tmp_files = []
427
- try:
428
- loop = asyncio.new_event_loop()
429
- asyncio.set_event_loop(loop)
430
- for i, chunk in enumerate(chunks):
431
- out_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=f"_chunk{i}.mp3", dir="/tmp")
432
- out_tmp.close()
433
- try:
434
- loop.run_until_complete(_speak_chunk(chunk, out_tmp.name))
435
- tmp_files.append(out_tmp.name)
436
- except Exception:
437
- try: os.unlink(out_tmp.name)
438
- except Exception: pass
439
- loop.close()
440
-
441
- if not tmp_files:
442
- return None
443
-
444
- combined = AudioSegment.empty()
445
- pause = AudioSegment.silent(duration=150)
446
- for f in tmp_files:
447
- try:
448
- combined += AudioSegment.from_mp3(f) + pause
449
- except Exception:
450
- pass
451
-
452
- if pitch != 1.0:
453
- combined = combined._spawn(
454
- combined.raw_data,
455
- overrides={"frame_rate": int(combined.frame_rate * pitch)}
456
- ).set_frame_rate(44100)
457
-
458
- if speed != 1.0:
459
- combined = combined._spawn(
460
- combined.raw_data,
461
- overrides={"frame_rate": int(combined.frame_rate * speed)}
462
- ).set_frame_rate(44100)
463
 
464
- out = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3", dir="/tmp", prefix="mpf_tts_")
465
- combined.export(out.name, format="mp3")
466
- out.close()
467
-
468
- for f in tmp_files:
469
- try: os.unlink(f)
470
- except Exception: pass
471
-
472
- return out.name
473
-
474
- except Exception:
475
- return tmp_files[0] if tmp_files else None
476
-
477
-
478
- def make_audio(text, speed=1.0, pitch=1.0):
479
- return text_to_speech_full(text, speed=speed, pitch=pitch)
480
-
481
- def read_cycle1(t, sp, pi): return make_audio(t, sp, pi)
482
- def read_cycle2(t, sp, pi): return make_audio(t, sp, pi)
483
- def read_cycle3(t, sp, pi): return make_audio(t, sp, pi)
484
- def read_cycle4(t, sp, pi): return make_audio(t, sp, pi)
485
-
486
- def read_all(t1, t2, t3, t4, sp, pi):
487
- combined = "\n\n".join([x for x in [t1, t2, t3, t4] if x and x.strip()])
488
- return make_audio(combined, sp, pi)
489
-
490
-
491
- def read_document_only(files, speed, pitch):
492
- """Extract all content from uploaded files and read it aloud — no analysis."""
493
- if not files:
494
- return None, "⚠️ Please upload at least one file."
495
- if not isinstance(files, list):
496
- files = [files]
497
-
498
- all_content = []
499
- for i, f in enumerate(files):
500
- content = extract_content(f)
501
- name = Path(f if isinstance(f, str) else f.name).name if f else f"Document {i+1}"
502
- if content:
503
- all_content.append(f"Document {i+1}: {name}.\n\n{content}")
504
-
505
- if not all_content:
506
- return None, "⚠️ Could not extract content from the uploaded files."
507
-
508
- full_text = "\n\n".join(all_content)
509
- audio_path = text_to_speech_full(full_text, speed=speed, pitch=pitch)
510
- word_count = len(full_text.split())
511
- char_count = len(full_text)
512
- msg = f"✅ Reading {len(files)} file{'s' if len(files)>1 else ''} · {word_count:,} words · {char_count:,} characters"
513
- return audio_path, msg
514
  def save_analysis(r1, r2, r3, r4):
515
- if not r1:
516
- return None, "⚠️ Nothing to save — run an analysis first."
517
  ts = time.strftime("%Y-%m-%d %H:%M")
518
  content = (
519
- f"METABOLIC PHOENIX FRAMEWORK — FULL ANALYSIS\n"
520
- f"Generated: {ts}\n"
521
  f"Ms. Nala | Injectiv8 | Dwayne Anthony Brian Galloway\n\n"
522
  f"{'='*60}\n\nCYCLE 1 — FACT LEDGER\n\n{r1}\n\n"
523
  f"{'='*60}\n\nCYCLE 2 — COMPLIANCE ANALYSIS\n\n{r2}\n\n"
@@ -529,216 +337,298 @@ def save_analysis(r1, r2, r3, r4):
529
  prefix=f"MPF_Analysis_{time.strftime('%Y%m%d_%H%M')}_",
530
  mode="w", encoding="utf-8"
531
  )
532
- tmp.write(content)
533
- tmp.close()
534
- return tmp.name, "✅ Ready to download."
535
 
536
 
537
- # ── CSS ───────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  CSS = """
539
  @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=DM+Mono:wght@400;500&display=swap');
540
- :root {
541
- --bg: #030712; --surface: #0a0f1e; --surface2: #061a0f;
542
- --border: #1a2544; --accent: #00CFFF; --accent2: #FFE500;
543
- --accent3: #0066FF; --text: #E8F4FF; --muted: #4A6080; --r: 8px;
544
- --green: #00e676; --green-dim: rgba(0,230,118,0.08); --green-border: rgba(0,230,118,0.3);
545
- }
546
- body, .gradio-container { background:var(--bg)!important; font-family:'Syne',sans-serif!important; color:var(--text)!important; }
547
- .hdr { text-align:center; padding:2.4rem 1rem 1.6rem; border-bottom:1px solid var(--border);
548
- background: radial-gradient(ellipse at 30% 50%, rgba(0,102,255,0.12) 0%, transparent 60%),
549
- radial-gradient(ellipse at 70% 50%, rgba(0,207,255,0.08) 0%, transparent 60%); }
550
- .hdr h1 { font-size:2.2rem; font-weight:800; letter-spacing:-0.04em;
551
  background:linear-gradient(135deg,var(--accent) 0%,var(--accent2) 100%);
552
- -webkit-background-clip:text; -webkit-text-fill-color:transparent; margin:0 0 0.3rem; }
553
- .hdr .sub { color:var(--muted); font-family:'DM Mono',monospace; font-size:.75rem; letter-spacing:.06em; }
554
- .hdr .own { color:var(--accent2); font-family:'DM Mono',monospace; font-size:.68rem; margin-top:.3rem; letter-spacing:.1em; text-transform:uppercase; }
555
- input[type=text], textarea { background:#041a0c!important; border:1px solid var(--green-border)!important;
556
- border-radius:var(--r)!important; color:#ccffe6!important; font-family:'DM Mono',monospace!important;
557
- font-size:.86rem!important; padding:.6rem .85rem!important; transition:border-color .18s!important; }
558
- input[type=text]:focus, textarea:focus { border-color:var(--green)!important; outline:none!important;
559
- box-shadow:0 0 0 3px rgba(0,230,118,.15)!important; }
560
- label > span:first-child { color:var(--green)!important; font-family:'DM Mono',monospace!important;
561
- font-size:.7rem!important; letter-spacing:.08em!important; text-transform:uppercase!important; }
562
- button.primary { background:linear-gradient(135deg,#006600,var(--green))!important;
563
- color:#030712!important; border:none!important; border-radius:var(--r)!important;
564
- font-family:'Syne',sans-serif!important; font-weight:800!important; font-size:.92rem!important;
565
- padding:.75rem 2.8rem!important; cursor:pointer!important; transition:opacity .15s,transform .15s!important; }
566
- button.primary:hover { opacity:.88!important; transform:translateY(-1px)!important; }
567
- button.secondary { background:#041a0c!important; color:var(--green)!important;
568
- border:1px solid var(--green-border)!important; border-radius:var(--r)!important;
569
- font-family:'Syne',sans-serif!important; font-weight:700!important; font-size:.8rem!important;
570
- padding:.5rem 1.2rem!important; cursor:pointer!important; }
571
- button.secondary:hover { background:var(--green)!important; color:#030712!important; }
572
- .out textarea { background:#020e06!important; border:1px solid var(--green-border)!important;
573
- border-left:3px solid var(--green)!important; border-radius:0 var(--r) var(--r) 0!important;
574
- font-family:'DM Mono',monospace!important; font-size:.84rem!important; line-height:1.7!important;
575
- color:#ccffe6!important; }
576
- .gradio-accordion { background:#061a0f!important; border:1px solid var(--green-border)!important;
577
- border-radius:var(--r)!important; margin-bottom:.6rem!important; }
578
- .gradio-accordion .label-wrap span { color:var(--green)!important; font-family:'Syne',sans-serif!important;
579
- font-weight:700!important; font-size:.85rem!important; letter-spacing:.04em!important; }
580
- .gradio-accordion svg { color:var(--green)!important; stroke:var(--green)!important; }
581
- .foot { text-align:center; padding:1.5rem; color:var(--muted); font-family:'DM Mono',monospace;
582
- font-size:.68rem; border-top:1px solid var(--green-border); margin-top:2rem; letter-spacing:.05em; }
583
  """
584
 
585
- # ── UI ────────────────────────────────────────────────────────────────────
586
  with gr.Blocks(css=CSS, title="Metabolic Phoenix Framework") as demo:
587
 
588
- gr.HTML("""
589
- <div class="hdr">
590
  <h1>⚡ The Metabolic Phoenix Framework</h1>
591
- <p class="sub">Injectiv8 &nbsp;·&nbsp; Ms. Nala &nbsp;·&nbsp; Llama-3.3-70B</p>
592
  <p class="own">Invented &amp; Owned by Dwayne Anthony Brian Galloway &nbsp;·&nbsp; 2026</p>
593
- </div>
594
- """)
595
 
596
  pdf_input = gr.File(
597
- label="Upload Documents — drop multiple files — PDF · Image · Audio · Video · Text",
598
  file_types=[
599
- ".pdf", ".txt", ".md",
600
- ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff",
601
- ".mp3", ".wav", ".m4a", ".ogg", ".flac",
602
- "audio/mpeg", "audio/mp3", "audio/wav", "audio/x-wav",
603
- "audio/mp4", "audio/ogg", "audio/flac", "audio/*",
604
- ".mp4", ".mov", ".avi", ".mkv", ".webm",
605
- "video/mp4", "video/quicktime", "video/x-msvideo",
606
- "video/x-matroska", "video/webm", "video/*",
607
  ],
608
- file_count="multiple",
609
- type="filepath"
610
  )
611
  run_btn = gr.Button("⚡ Analyse Documents", variant="primary")
612
 
613
  with gr.Row():
614
- speed_slider = gr.Slider(
615
- minimum=0.5, maximum=2.0, value=1.0, step=0.05,
616
- label="🎚 Speed", info="0.5 = slow · 1.0 = normal · 2.0 = fast"
617
- )
618
- pitch_slider = gr.Slider(
619
- minimum=0.5, maximum=2.0, value=1.0, step=0.05,
620
- label="🎵 Pitch", info="0.5 = low · 1.0 = normal �� 2.0 = high"
621
- )
622
-
623
- with gr.Accordion("🔊 Just Read The Document — no analysis", open=True):
624
- read_doc_btn = gr.Button("🔊 Read Document(s) Aloud — full length, no analysis", variant="secondary")
625
- read_doc_msg = gr.Markdown("")
626
  read_doc_audio = gr.Audio(label="Document Audio", autoplay=True)
627
 
628
  with gr.Accordion("Cycle 1 — Fact Ledger", open=True):
629
  out1 = gr.Textbox(label="", lines=12, elem_classes=["out"])
630
- rb1_btn = gr.Button("🔊 Read Cycle 1 (full)", variant="secondary")
631
  audio1 = gr.Audio(label="Cycle 1 Audio", autoplay=True)
632
 
633
  with gr.Accordion("Cycle 2 — Compliance Analysis", open=False):
634
  out2 = gr.Textbox(label="", lines=12, elem_classes=["out"])
635
- rb2_btn = gr.Button("🔊 Read Cycle 2 (full)", variant="secondary")
636
  audio2 = gr.Audio(label="Cycle 2 Audio", autoplay=True)
637
 
638
  with gr.Accordion("Cycle 3 — Ouroboros Loops", open=False):
639
  out3 = gr.Textbox(label="", lines=12, elem_classes=["out"])
640
- rb3_btn = gr.Button("🔊 Read Cycle 3 (full)", variant="secondary")
641
  audio3 = gr.Audio(label="Cycle 3 Audio", autoplay=True)
642
 
643
  with gr.Accordion("Cycle 4 — Full Case Output", open=False):
644
  out4 = gr.Textbox(label="", lines=12, elem_classes=["out"])
645
- rb4_btn = gr.Button("🔊 Read Cycle 4 (full)", variant="secondary")
646
  audio4 = gr.Audio(label="Cycle 4 Audio", autoplay=True)
647
 
648
- rball_btn = gr.Button("🔊 Read Full Analysis (all cycles)", variant="primary")
649
  audio_all = gr.Audio(label="Full Analysis Audio", autoplay=True)
650
-
651
  save_btn = gr.Button("💾 Save & Download Analysis", variant="secondary")
652
- dl_file = gr.File(label="Download your analysis")
653
  save_msg = gr.Markdown("")
654
 
655
- # ── DOCUMENT GENERATION ──────────────────────────────────────────────
656
  gr.HTML("<hr style='border-color:#1a2544;margin:2rem 0;'>")
657
- gr.HTML("""
658
- <div style='text-align:center;padding:1.2rem 0 0.6rem;'>
659
- <h2 style='font-size:1.5rem;font-weight:800;letter-spacing:-0.03em;
660
  background:linear-gradient(135deg,#FFE500,#FF6B00);
661
- -webkit-background-clip:text;-webkit-text-fill-color:transparent;margin:0 0 0.3rem;'>
662
  ⚖️ Document Generation Engine
663
  </h2>
664
  <p style='color:#4A6080;font-family:"DM Mono",monospace;font-size:.73rem;letter-spacing:.05em;'>
665
- RUN ANALYSIS FIRST — THEN GENERATE DOCUMENTS WITH SPINE
666
  </p>
667
- </div>
668
- """)
669
 
670
  gen_all_btn = gr.Button("⚖️ Generate ALL Documents", variant="primary")
671
 
672
  with gr.Accordion("Document 1 — Letter Before Action", open=True):
673
- gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>Pre-Action Protocol letter. 14-day deadline. Litigation follows without further notice.</p>")
674
- gen_lba_btn = gr.Button("Generate Letter Before Action", variant="secondary")
675
  doc_lba = gr.Textbox(label="", lines=14, elem_classes=["out"])
676
  rd_lba_btn = gr.Button("🔊 Read Aloud", variant="secondary")
677
- aud_lba = gr.Audio(label="LBA Audio", autoplay=True)
678
 
679
  with gr.Accordion("Document 2 — Formal Regulatory Complaint", open=False):
680
  gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>For FOS, FCA, ICO, Parliamentary Ombudsman. Numbered findings. Demands investigation.</p>")
681
- gen_comp_btn = gr.Button("Generate Formal Complaint", variant="secondary")
682
  doc_comp = gr.Textbox(label="", lines=14, elem_classes=["out"])
683
  rd_comp_btn = gr.Button("🔊 Read Aloud", variant="secondary")
684
- aud_comp = gr.Audio(label="Complaint Audio", autoplay=True)
685
 
686
  with gr.Accordion("Document 3 — Witness Statement", open=False):
687
- gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>First person. Court-ready. Signed with Statement of Truth. King's Bench Division heading.</p>")
688
  gen_wit_btn = gr.Button("Generate Witness Statement", variant="secondary")
689
  doc_wit = gr.Textbox(label="", lines=14, elem_classes=["out"])
690
  rd_wit_btn = gr.Button("🔊 Read Aloud", variant="secondary")
691
- aud_wit = gr.Audio(label="Witness Statement Audio", autoplay=True)
692
 
693
  with gr.Accordion("Document 4 — Skeleton Argument", open=False):
694
  gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>Structured legal argument. Authorities cited. Issues for determination. Relief sought.</p>")
695
  gen_skel_btn = gr.Button("Generate Skeleton Argument", variant="secondary")
696
  doc_skel = gr.Textbox(label="", lines=14, elem_classes=["out"])
697
  rd_skel_btn = gr.Button("🔊 Read Aloud", variant="secondary")
698
- aud_skel = gr.Audio(label="Skeleton Audio", autoplay=True)
699
 
700
  with gr.Accordion("Document 5 — Regulatory Referral", open=False):
701
- gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>FCA / SRA / ICO referral. 28-day response deadline. Escalation to press if no action.</p>")
702
  gen_reg_btn = gr.Button("Generate Regulatory Referral", variant="secondary")
703
  doc_reg = gr.Textbox(label="", lines=14, elem_classes=["out"])
704
  rd_reg_btn = gr.Button("🔊 Read Aloud", variant="secondary")
705
- aud_reg = gr.Audio(label="Referral Audio", autoplay=True)
706
 
707
  save_docs_btn = gr.Button("💾 Save & Download All Documents", variant="secondary")
708
  dl_docs_file = gr.File(label="Download all documents")
709
  save_docs_msg = gr.Markdown("")
710
 
711
- run_btn.click(analyse, inputs=[pdf_input], outputs=[out1, out2, out3, out4])
712
- read_doc_btn.click(read_document_only, inputs=[pdf_input, speed_slider, pitch_slider], outputs=[read_doc_audio, read_doc_msg])
713
- rb1_btn.click(read_cycle1, inputs=[out1, speed_slider, pitch_slider], outputs=[audio1])
714
- rb2_btn.click(read_cycle2, inputs=[out2, speed_slider, pitch_slider], outputs=[audio2])
715
- rb3_btn.click(read_cycle3, inputs=[out3, speed_slider, pitch_slider], outputs=[audio3])
716
- rb4_btn.click(read_cycle4, inputs=[out4, speed_slider, pitch_slider], outputs=[audio4])
717
- rball_btn.click(read_all, inputs=[out1, out2, out3, out4, speed_slider, pitch_slider], outputs=[audio_all])
718
- save_btn.click(save_analysis, inputs=[out1, out2, out3, out4], outputs=[dl_file, save_msg])
719
-
720
- # Document generation bindings
721
- gen_all_btn.click(generate_all_documents, inputs=[out1, out2, out3, out4], outputs=[doc_lba, doc_comp, doc_wit, doc_skel, doc_reg])
722
- gen_lba_btn.click(generate_letter_before_action, inputs=[out1, out2, out3, out4], outputs=[doc_lba])
723
- gen_comp_btn.click(generate_formal_complaint, inputs=[out1, out2, out3, out4], outputs=[doc_comp])
724
- gen_wit_btn.click(generate_witness_statement, inputs=[out1, out2, out4], outputs=[doc_wit])
725
- gen_skel_btn.click(generate_skeleton_argument, inputs=[out1, out2, out3, out4], outputs=[doc_skel])
726
- gen_reg_btn.click(generate_regulatory_referral, inputs=[out1, out2, out3], outputs=[doc_reg])
727
-
728
- rd_lba_btn.click(make_audio, inputs=[doc_lba, speed_slider, pitch_slider], outputs=[aud_lba])
729
- rd_comp_btn.click(make_audio, inputs=[doc_comp, speed_slider, pitch_slider], outputs=[aud_comp])
730
- rd_wit_btn.click(make_audio, inputs=[doc_wit, speed_slider, pitch_slider], outputs=[aud_wit])
731
- rd_skel_btn.click(make_audio, inputs=[doc_skel, speed_slider, pitch_slider], outputs=[aud_skel])
732
- rd_reg_btn.click(make_audio, inputs=[doc_reg, speed_slider, pitch_slider], outputs=[aud_reg])
733
-
734
- save_docs_btn.click(save_documents, inputs=[doc_lba, doc_comp, doc_wit, doc_skel, doc_reg], outputs=[dl_docs_file, save_docs_msg])
735
-
736
- gr.HTML("""
737
- <div class="foot">
738
- THE METABOLIC PHOENIX FRAMEWORK &nbsp;·&nbsp; Injectiv8 &nbsp;·&nbsp; Ms. Nala
739
- &nbsp;·&nbsp; © Dwayne Anthony Brian Galloway 2026 &nbsp;·&nbsp; All Rights Reserved
740
- </div>
741
- """)
742
 
743
  if __name__ == "__main__":
744
  demo.launch()
 
1
  import gradio as gr
2
  import os
3
+ import re
4
  import time
5
  import subprocess
6
  import tempfile
 
7
  from pathlib import Path
8
  from huggingface_hub import InferenceClient
9
 
10
+ # ── INSTALL ───────────────────────────────────────────────────────────────────
11
  for pkg in ["pypdf", "edge-tts", "pydub", "pillow", "pytesseract", "openai-whisper", "ffmpeg-python"]:
12
  subprocess.run(["pip", "install", pkg, "-q"], capture_output=True)
 
 
13
  subprocess.run(["apt-get", "install", "-y", "-q", "ffmpeg", "tesseract-ocr"], capture_output=True)
14
 
15
  from pypdf import PdfReader
 
19
  from PIL import Image
20
  import pytesseract
21
 
22
+ # ── MODEL — free on HF Pro, no credit cost ───────────────────────────────────
23
+ # Primary: Mistral 7B — free, fast, no credit drain
24
+ # Fallback: Zephyr 7B — also free
25
+ PRIMARY_MODEL = "mistralai/Mistral-7B-Instruct-v0.3"
26
+ FALLBACK_MODEL = "HuggingFaceH4/zephyr-7b-beta"
27
 
28
+ def call_llama(prompt: str, max_tokens: int = 2000) -> str:
 
29
  token = os.environ.get("HF_TOKEN", "")
30
+ for model in [PRIMARY_MODEL, FALLBACK_MODEL]:
31
+ try:
32
+ client = InferenceClient(model=model, token=token if token else None)
33
+ response = client.chat_completion(
34
+ messages=[{"role": "user", "content": prompt}],
35
+ max_tokens=max_tokens,
36
+ temperature=0.7,
37
+ )
38
+ return response.choices[0].message.content
39
+ except Exception as e:
40
+ last_err = str(e)
41
+ continue
42
+ return f"Could not reach AI — check your HF_TOKEN is set in Space secrets. ({last_err})"
43
 
44
 
45
+ # ── FILE EXTRACTION ───────────────────────────────────────────────────────────
46
  def extract_pdf_text(path):
47
  try:
48
  reader = PdfReader(path)
 
51
  text = page.extract_text() or ""
52
  if text.strip():
53
  pages.append(f"--- Page {i+1} ---\n{text.strip()}")
54
+ return "\n\n".join(pages) if pages else "[No extractable text — may be a scanned PDF]"
 
 
55
  except Exception as e:
56
  return f"[PDF read error: {e}]"
57
 
 
58
  def extract_image_text(path):
 
59
  try:
60
  img = Image.open(path)
61
  text = pytesseract.image_to_string(img, lang="eng")
62
+ return f"[IMAGE OCR]\n{text.strip()}" if text.strip() else "[Image: no text detected]"
 
 
63
  except Exception as e:
64
  return f"[Image OCR error: {e}]"
65
 
 
66
  def transcribe_audio(path):
 
67
  try:
68
  import whisper
69
  model = whisper.load_model("base")
 
72
  except Exception as e:
73
  return f"[Transcription error: {e}]"
74
 
 
75
  def extract_content(file):
76
+ if not file: return ""
 
 
77
  path = file.name if hasattr(file, "name") else str(file)
78
+ ext = Path(path).suffix.lower()
 
79
  if ext == ".pdf":
80
  return extract_pdf_text(path)
81
+ elif ext in [".jpg",".jpeg",".png",".gif",".webp",".bmp",".tiff"]:
82
  return extract_image_text(path)
83
+ elif ext in [".mp3",".wav",".ogg",".m4a",".flac"]:
84
  return transcribe_audio(path)
85
+ elif ext in [".mp4",".mov",".avi",".mkv",".webm"]:
 
86
  try:
87
+ tmp = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False, dir="/tmp")
88
+ subprocess.run(["ffmpeg","-i",path,"-q:a","0","-map","a",tmp.name,"-y"],
89
+ capture_output=True, timeout=120)
90
+ return transcribe_audio(tmp.name)
 
 
91
  except Exception as e:
92
  return f"[Video extraction error: {e}]"
 
 
 
 
 
93
  else:
94
  try:
95
  return Path(path).read_text(encoding="utf-8", errors="replace")
 
97
  return f"[Unsupported file type: {ext}]"
98
 
99
 
100
+ # ── ANALYSIS ──────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  PLAIN_ENGLISH = (
102
+ "\n\nCRITICAL — FORMATTING RULES YOU MUST FOLLOW:\n"
103
+ "Write in plain clear English prose only.\n"
104
+ "No JSON. No code blocks. No curly brackets. No asterisks. No hash symbols.\n"
105
+ "No markdown formatting of any kind.\n"
106
+ "Use numbered paragraphs and CAPITAL LETTER headings only.\n"
107
+ "Every sentence must be clear enough for a non-lawyer to read and understand.\n"
108
+ "Do not hedge. Do not say 'may indicate' or 'appears to suggest'.\n"
109
+ "If it is in the document, it happened. State it as confirmed fact."
110
  )
111
 
112
  def analyse(files):
113
  if not files:
114
+ return "Please upload at least one file.", "", "", ""
 
 
115
  if not isinstance(files, list):
116
  files = [files]
117
 
 
118
  all_content = []
119
  for i, f in enumerate(files):
120
  content = extract_content(f)
121
  name = Path(f if isinstance(f, str) else f.name).name if f else f"Document {i+1}"
122
+ all_content.append(f"DOCUMENT {i+1}: {name}\n{'─'*50}\n{content or '[Empty]'}")
 
 
 
123
 
124
+ combined = "\n\n".join(all_content)
125
  doc_chunk = combined[:14000]
126
+ base = f"DOCUMENTS SUPPLIED ({len(files)} total):\n\n{doc_chunk}\n\n{'='*60}\n\n"
127
 
128
  r1 = call_llama(base +
129
+ "You are Introbot — part of the Metabolic Phoenix Framework "
130
+ "by Dwayne Anthony Brian Galloway.\n\n"
131
+ "From these documents extract every confirmed fact. State:\n"
132
+ "- Every confirmed fact: who said it, when, what it proves\n"
133
+ "- Every admission — label it ADMISSION\n"
134
+ "- Every denial — label it DENIAL, state what it contradicts\n"
135
+ "- Every contradiction — what was said, what contradicts it, when\n"
136
+ "- Every deliberate omission — what was required to be said, was not, why that is a breach\n"
137
+ "- Every statutory duty triggered and the precise moment it was triggered\n\n"
138
+ "Do not hedge. Format as a numbered FACT LEDGER." + PLAIN_ENGLISH
 
139
  )
140
+
141
  r2 = call_llama(base +
142
+ "You are RHB 2.0 + CPF — part of the Metabolic Phoenix Framework "
143
+ "by Dwayne Anthony Brian Galloway.\n\n"
144
+ "Breaches have occurred. Name them, prove them, record them.\n"
145
  "- Each institution's statutory duties and the exact moment each duty was triggered\n"
146
+ "- For each duty: state BREACH CONFIRMED or COMPLIED — never neutral\n"
147
+ "- For every breach: statute, section, precise act or omission, date\n"
148
+ "- Trace the full causal chain: first breach to final injury\n"
149
+ "- Equality Act 2010: confirm each s.13 and s.20 breach as established, not alleged\n"
150
+ "- For each breach state the remedy the law requires\n\n"
151
+ "Do not use 'may', 'potentially', 'appears'. These are findings.\n"
152
+ "Cite statute sections." + PLAIN_ENGLISH
 
153
  )
154
+
155
  r3 = call_llama(base +
156
+ "You are Phoenix Badger — part of the Metabolic Phoenix Framework "
157
+ "by Dwayne Anthony Brian Galloway.\n\n"
158
+ "RING A — FORWARD SCAN: Every evasion tactic and bad faith manoeuvre confirmed. "
159
+ "Name what each one is designed to obscure.\n\n"
160
+ "RING B — BACKWARD SWEEP: Every past decision that was unlawful at the time. "
161
+ "Name the law breached and when.\n\n"
162
+ "RING C — TYPOLOGY MUTATION: Name the patterns of institutional misconduct confirmed. "
163
+ "State whether each pattern is systemic or targeted.\n\n"
164
+ "RING D — DRIFT CORRECTION: Every internal contradiction across the documents. "
165
+ "State what each contradiction proves.\n\n"
166
+ "ESCALATION MAP: Which regulatory bodies and courts must receive this, "
167
+ "in priority order, with reasons." + PLAIN_ENGLISH
168
  )
169
+
170
  r4 = call_llama(
171
+ f"You are Layoutbot — part of the Metabolic Phoenix Framework "
172
+ f"by Dwayne Anthony Brian Galloway.\n\n"
173
  f"FACT LEDGER:\n{r1[:1500]}\n\n"
174
+ f"BREACH ANALYSIS:\n{r2[:1500]}\n\n"
175
  f"LOOP OUTPUT:\n{r3[:1500]}\n\n"
176
  f"{'='*60}\n\n"
177
+ "Produce a complete litigation-ready case summary. "
178
+ "Breaches have occurred and are on the record. Write accordingly.\n\n"
179
+ "EXECUTIVE SUMMARY — plain English: what happened, what was done wrong, who did it\n"
180
+ "CONFIRMED BREACHES — each named, statute and section cited, date confirmed\n"
181
+ "TOP 5 ARGUMENTS — strongest first, each grounded in confirmed fact and statute\n"
182
+ "REQUIRED NEXT STEPS — mandatory actions in priority order\n"
183
+ "ESCALATION MAP — which body receives what, by what deadline\n\n"
184
  "Ms. Nala | Injectiv8 | Dwayne Anthony Brian Galloway" + PLAIN_ENGLISH
185
  )
186
  return r1, r2, r3, r4
187
 
188
 
189
+ # ── DOCUMENT GENERATION ───────────────────────────────────────────────────────
190
  DOC_PLAIN = (
191
+ "\n\nFORMATTING RULES:\n"
192
+ "Plain forceful English prose only. No JSON. No markdown. No asterisks. No hash symbols.\n"
193
+ "Numbered paragraphs. CAPITAL LETTER headings.\n"
194
+ "Every sentence carries weight. Do not hedge.\n"
 
 
195
  "Breaches have occurred. Name them. Prove them. Demand remedy."
196
  )
197
 
198
  def generate_letter_before_action(r1, r2, r3, r4):
199
+ if not r1: return "Run the analysis first."
200
+ return call_llama(
 
 
201
  "You are a senior litigation solicitor drafting a Letter Before Action on behalf of "
202
+ "Dwayne Anthony Brian Galloway (the Claimant).\n\n"
 
203
  f"FACT LEDGER:\n{r1[:2000]}\n\nBREACH ANALYSIS:\n{r2[:2000]}\n\nCASE SUMMARY:\n{r4[:2000]}\n\n"
204
+ "Draft a formal Letter Before Action that:\n"
205
+ "1. Opens stating this is sent under Pre-Action Protocol and proceedings will issue in 14 days without satisfactory response.\n"
206
+ "2. Names each respondent and each confirmed breach — by statute, section, and date.\n"
207
+ "3. States every loss and harm caused: financial, physical, psychological, disability-related.\n"
208
+ "4. States the specific remedies demanded: compensation, reasonable adjustments, correction of records, costs — itemised.\n"
209
+ "5. States: if full remedy is not provided within 14 days, proceedings will issue in the King's Bench Division and the Claimant will seek indemnity costs.\n"
210
+ "6. Closes: Dwayne Anthony Brian Galloway, Litigant in Person."
211
+ + DOC_PLAIN, max_tokens=2000
 
212
  )
 
 
213
 
214
  def generate_formal_complaint(r1, r2, r3, r4):
215
+ if not r1: return "Run the analysis first."
216
+ return call_llama(
 
 
217
  "You are drafting a formal regulatory complaint on behalf of Dwayne Anthony Brian Galloway "
218
+ "for submission to FOS, FCA, ICO or Parliamentary Ombudsman as the breaches dictate.\n\n"
 
219
  f"FACT LEDGER:\n{r1[:2000]}\n\nBREACH ANALYSIS:\n{r2[:2000]}\n\nLOOP ANALYSIS:\n{r3[:1500]}\n\n"
220
+ "Draft a formal regulatory complaint that:\n"
221
+ "1. Names the respondent and the regulatory framework they are bound by.\n"
222
+ "2. States the date each duty arose and was breached — numbered findings.\n"
223
+ "3. States every statutory provision breached with full citation.\n"
224
+ "4. States harm caused: financial loss quantified, EA 2010 s.13 and s.20 confirmed, hardship documented.\n"
225
+ "5. States what the respondent was required to do, what they did instead, what that proves.\n"
226
+ "6. States what remedy the Claimant requires from the regulator.\n"
227
+ "7. States the Claimant is a disabled person under EA 2010 s.6 and the conduct was discriminatory.\n"
228
+ "8. Closes with a demand for substantive response within 8 weeks."
229
+ + DOC_PLAIN, max_tokens=2000
 
230
  )
 
 
231
 
232
  def generate_witness_statement(r1, r2, r4):
233
+ if not r1: return "Run the analysis first."
234
+ return call_llama(
235
+ "You are drafting a formal Witness Statement for Dwayne Anthony Brian Galloway "
236
+ "to file in civil proceedings.\n\n"
 
 
237
  f"FACT LEDGER:\n{r1[:2000]}\n\nBREACH ANALYSIS:\n{r2[:1500]}\n\nCASE SUMMARY:\n{r4[:1500]}\n\n"
238
+ "Draft a formal Witness Statement in the first person that:\n"
239
+ "1. Opens: IN THE HIGH COURT OF JUSTICE, KING'S BENCH DIVISION. "
240
+ "WITNESS STATEMENT OF DWAYNE ANTHONY BRIAN GALLOWAY.\n"
241
+ "2. States identity, disability status under EA 2010 s.6, and basis of claim.\n"
242
+ "3. Narrates the full chronology in numbered paragraphs — what happened, when, who, what was said.\n"
243
+ "4. For each breach: what the Claimant was told, what he did in response, how he was harmed.\n"
244
+ "5. States financial losses, hardship, physical and psychological impact.\n"
245
+ "6. Addresses every denial and states why each is contradicted by the documented record.\n"
246
+ "7. States what the Claimant asks the Court to find and order.\n"
247
+ "8. Closes with Statement of Truth. Signed: Dwayne Anthony Brian Galloway. Date: [DATE]."
248
+ + DOC_PLAIN, max_tokens=2000
249
  )
 
 
250
 
251
  def generate_skeleton_argument(r1, r2, r3, r4):
252
+ if not r1: return "Run the analysis first."
253
+ return call_llama(
 
 
254
  "You are a barrister drafting a Skeleton Argument for Dwayne Anthony Brian Galloway, "
255
+ "Litigant in Person, for hearing in the King's Bench Division.\n\n"
256
+ f"FACT LEDGER:\n{r1[:1500]}\n\nBREACH ANALYSIS:\n{r2[:1500]}\n\n"
257
+ f"LOOP ANALYSIS:\n{r3[:1000]}\n\nCASE SUMMARY:\n{r4[:1500]}\n\n"
258
+ "Draft a Skeleton Argument that:\n"
259
+ "1. Opens: IN THE HIGH COURT OF JUSTICE, KING'S BENCH DIVISION. "
260
+ "DWAYNE ANTHONY BRIAN GALLOWAY (Claimant) v [RESPONDENTS] (Defendants). SKELETON ARGUMENT.\n"
261
+ "2. States the issues for determination — numbered.\n"
262
+ "3. Sets out the Legal Framework: every statute and authority relied upon, cited correctly.\n"
263
+ "4. States submissions on each issue: what the law requires, what the Defendant did, why that is a breach.\n"
264
+ "5. Addresses the Defendant's position and explains why each element is wrong in law and fact.\n"
265
+ "6. States the relief sought: damages quantified, declarations, indemnity costs, injunctive relief.\n"
266
+ "7. Closes with why the Claimant must succeed and why the Defendants have no sustainable defence.\n\n"
267
+ "Authorities: Archibald v Fife [2004] UKHL 32. Wednesbury [1948] 1 KB 223. "
268
+ "CRA 2015. EA 2010. Fraud Act 2006 s.2. CPUTR 2008. FSMA 2000."
269
+ + DOC_PLAIN, max_tokens=2000
 
 
 
 
 
 
270
  )
 
 
271
 
272
  def generate_regulatory_referral(r1, r2, r3):
273
+ if not r1: return "Run the analysis first."
274
+ return call_llama(
 
 
275
  "You are drafting a formal referral letter to a regulatory authority on behalf of "
276
  "Dwayne Anthony Brian Galloway.\n\n"
 
277
  f"FACT LEDGER:\n{r1[:1500]}\n\nBREACH ANALYSIS:\n{r2[:1500]}\n\nLOOP ANALYSIS:\n{r3[:1500]}\n\n"
278
+ "Draft a Regulatory Referral that:\n"
279
+ "1. States this is a formal referral requiring the regulator to open a formal investigation.\n"
280
+ "2. Identifies each regulated entity by name.\n"
281
+ "3. States each confirmed breach as a numbered finding: conduct, statute, date, harm.\n"
282
+ "4. States the conduct amounts to regulatory misconduct, failure of regulated duty, "
283
+ "and discrimination under EA 2010.\n"
284
+ "5. States the specific regulatory action required: investigation, enforcement, fine, censure.\n"
285
+ "6. States the Claimant is disabled under EA 2010 s.6 and failure to investigate is itself a breach.\n"
286
+ "7. Closes with a 28-day deadline and states that absence of action will result in escalation "
287
+ "to the Parliamentary Ombudsman and national press."
288
+ + DOC_PLAIN, max_tokens=2000
289
  )
 
 
290
 
291
  def generate_all_documents(r1, r2, r3, r4):
 
292
  if not r1:
293
+ return "", "", "", "", "Run the analysis first, then generate documents."
294
+ return (
295
+ generate_letter_before_action(r1, r2, r3, r4),
296
+ generate_formal_complaint(r1, r2, r3, r4),
297
+ generate_witness_statement(r1, r2, r4),
298
+ generate_skeleton_argument(r1, r2, r3, r4),
299
+ generate_regulatory_referral(r1, r2, r3),
300
+ )
301
 
302
  def save_documents(lba, comp, wit, skel, reg):
 
303
  if not any([lba, comp, wit, skel, reg]):
304
+ return None, "Generate documents first."
305
  ts = time.strftime("%Y-%m-%d %H:%M")
306
  content = (
307
  f"METABOLIC PHOENIX FRAMEWORK — LEGAL DOCUMENTS\n"
 
318
  prefix=f"MPF_Documents_{time.strftime('%Y%m%d_%H%M')}_",
319
  mode="w", encoding="utf-8"
320
  )
321
+ tmp.write(content); tmp.close()
322
+ return tmp.name, "Ready to download."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  def save_analysis(r1, r2, r3, r4):
325
+ if not r1: return None, "Nothing to save — run an analysis first."
 
326
  ts = time.strftime("%Y-%m-%d %H:%M")
327
  content = (
328
+ f"METABOLIC PHOENIX FRAMEWORK — FULL ANALYSIS\nGenerated: {ts}\n"
 
329
  f"Ms. Nala | Injectiv8 | Dwayne Anthony Brian Galloway\n\n"
330
  f"{'='*60}\n\nCYCLE 1 — FACT LEDGER\n\n{r1}\n\n"
331
  f"{'='*60}\n\nCYCLE 2 — COMPLIANCE ANALYSIS\n\n{r2}\n\n"
 
337
  prefix=f"MPF_Analysis_{time.strftime('%Y%m%d_%H%M')}_",
338
  mode="w", encoding="utf-8"
339
  )
340
+ tmp.write(content); tmp.close()
341
+ return tmp.name, "Ready to download."
 
342
 
343
 
344
+ # ── TTS — SONIA NEURAL, RYAN FALLBACK, FULL LENGTH ───────────────────────────
345
+ TTS_VOICE = "en-GB-SoniaNeural"
346
+ TTS_BACKUP = "en-GB-RyanNeural"
347
+ CHUNK_SIZE = 1500
348
+
349
+ async def _edge_speak(text, path, voice):
350
+ await edge_tts.Communicate(text, voice).save(path)
351
+
352
+ def _split_chunks(text):
353
+ text = re.sub(r"[=\-]{2,}|\[.*?\]|[#*`]", " ", text)
354
+ text = re.sub(r"\s+", " ", text).strip()
355
+ sentences = re.split(r"(?<=[.!?])\s+", text)
356
+ chunks, cur = [], ""
357
+ for s in sentences:
358
+ if len(cur)+len(s)+1 <= CHUNK_SIZE: cur = (cur+" "+s).strip()
359
+ else:
360
+ if cur: chunks.append(cur)
361
+ cur = s
362
+ if cur: chunks.append(cur)
363
+ return chunks or [text[:CHUNK_SIZE]]
364
+
365
+ def _build_audio(files, speed=1.0, pitch=1.0):
366
+ if not files: return None
367
+ combined = AudioSegment.empty()
368
+ for f in files:
369
+ try: combined += AudioSegment.from_mp3(f) + AudioSegment.silent(160)
370
+ except Exception: pass
371
+ if len(combined) == 0: return None
372
+ if pitch != 1.0:
373
+ combined = combined._spawn(combined.raw_data,
374
+ overrides={"frame_rate":int(combined.frame_rate*pitch)}).set_frame_rate(44100)
375
+ if speed != 1.0:
376
+ combined = combined._spawn(combined.raw_data,
377
+ overrides={"frame_rate":int(combined.frame_rate*speed)}).set_frame_rate(44100)
378
+ out = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3", dir="/tmp", prefix="mpf_")
379
+ combined.export(out.name, format="mp3", bitrate="128k")
380
+ out.close()
381
+ for f in files:
382
+ try: os.unlink(f)
383
+ except Exception: pass
384
+ return out.name
385
+
386
+ def text_to_speech_full(text: str, speed: float = 1.0, pitch: float = 1.0):
387
+ if not text or not text.strip(): return None
388
+ chunks = _split_chunks(text)
389
+
390
+ for voice in [TTS_VOICE, TTS_BACKUP]:
391
+ files = []
392
+ try:
393
+ loop = asyncio.new_event_loop(); asyncio.set_event_loop(loop)
394
+ for i, chunk in enumerate(chunks):
395
+ out = tempfile.NamedTemporaryFile(delete=False, suffix=f"_e{i}.mp3", dir="/tmp")
396
+ out.close()
397
+ try:
398
+ loop.run_until_complete(_edge_speak(chunk, out.name, voice))
399
+ if os.path.getsize(out.name) > 1000: files.append(out.name)
400
+ else: raise Exception("empty")
401
+ except Exception:
402
+ try: os.unlink(out.name)
403
+ except Exception: pass
404
+ loop.close()
405
+ if len(files) >= len(chunks)*0.8:
406
+ r = _build_audio(files, speed, pitch)
407
+ if r: return r
408
+ except Exception: pass
409
+
410
+ # gTTS fallback
411
+ try:
412
+ subprocess.run(["pip","install","gtts","-q"], capture_output=True)
413
+ from gtts import gTTS
414
+ files = []
415
+ for i, chunk in enumerate(chunks):
416
+ out = tempfile.NamedTemporaryFile(delete=False, suffix=f"_g{i}.mp3", dir="/tmp")
417
+ out.close()
418
+ try:
419
+ gTTS(text=chunk, lang="en", tld="co.uk", slow=False).save(out.name)
420
+ if os.path.getsize(out.name) > 500: files.append(out.name)
421
+ except Exception:
422
+ try: os.unlink(out.name)
423
+ except Exception: pass
424
+ if files: return _build_audio(files, speed, pitch)
425
+ except Exception: pass
426
+ return None
427
+
428
+ def make_audio(t, sp=1.0, pi=1.0): return text_to_speech_full(t, sp, pi)
429
+
430
+ def read_document_only(files, speed, pitch):
431
+ if not files: return None, "Please upload at least one file."
432
+ if not isinstance(files, list): files = [files]
433
+ all_content = []
434
+ for i, f in enumerate(files):
435
+ content = extract_content(f)
436
+ name = Path(f if isinstance(f, str) else f.name).name if f else f"Document {i+1}"
437
+ if content: all_content.append(f"Document {i+1}: {name}.\n\n{content}")
438
+ if not all_content: return None, "Could not extract content."
439
+ full_text = "\n\n".join(all_content)
440
+ audio = text_to_speech_full(full_text, speed=speed, pitch=pitch)
441
+ words = len(full_text.split())
442
+ return audio, f"Reading {len(files)} file{'s' if len(files)>1 else ''} — {words:,} words"
443
+
444
+
445
+ # ── CSS ───────────────────────────────────────────────────────────────────────
446
  CSS = """
447
  @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=DM+Mono:wght@400;500&display=swap');
448
+ :root{--bg:#030712;--surface2:#061a0f;--border:#1a2544;--accent:#00CFFF;--accent2:#FFE500;
449
+ --text:#E8F4FF;--muted:#4A6080;--r:8px;
450
+ --green:#00e676;--green-border:rgba(0,230,118,0.3);}
451
+ body,.gradio-container{background:var(--bg)!important;font-family:'Syne',sans-serif!important;color:var(--text)!important;}
452
+ .hdr{text-align:center;padding:2.4rem 1rem 1.6rem;border-bottom:1px solid var(--border);
453
+ background:radial-gradient(ellipse at 30% 50%,rgba(0,102,255,0.12) 0%,transparent 60%),
454
+ radial-gradient(ellipse at 70% 50%,rgba(0,207,255,0.08) 0%,transparent 60%);}
455
+ .hdr h1{font-size:2.2rem;font-weight:800;letter-spacing:-0.04em;
 
 
 
456
  background:linear-gradient(135deg,var(--accent) 0%,var(--accent2) 100%);
457
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;margin:0 0 .3rem;}
458
+ .hdr .sub{color:var(--muted);font-family:'DM Mono',monospace;font-size:.75rem;letter-spacing:.06em;}
459
+ .hdr .own{color:var(--accent2);font-family:'DM Mono',monospace;font-size:.68rem;margin-top:.3rem;letter-spacing:.1em;text-transform:uppercase;}
460
+ input[type=text],textarea{background:#041a0c!important;border:1px solid var(--green-border)!important;
461
+ border-radius:var(--r)!important;color:#ccffe6!important;font-family:'DM Mono',monospace!important;
462
+ font-size:.86rem!important;padding:.6rem .85rem!important;}
463
+ input[type=text]:focus,textarea:focus{border-color:var(--green)!important;outline:none!important;
464
+ box-shadow:0 0 0 3px rgba(0,230,118,.15)!important;}
465
+ label>span:first-child{color:var(--green)!important;font-family:'DM Mono',monospace!important;
466
+ font-size:.7rem!important;letter-spacing:.08em!important;text-transform:uppercase!important;}
467
+ button.primary,.gr-button-primary{background:linear-gradient(135deg,#006600,var(--green))!important;
468
+ color:#030712!important;border:none!important;border-radius:var(--r)!important;
469
+ font-family:'Syne',sans-serif!important;font-weight:800!important;font-size:.92rem!important;
470
+ padding:.75rem 2.8rem!important;}
471
+ button.primary:hover{opacity:.88!important;transform:translateY(-1px)!important;}
472
+ button.secondary,.gr-button-secondary{background:#041a0c!important;color:var(--green)!important;
473
+ border:1px solid var(--green-border)!important;border-radius:var(--r)!important;
474
+ font-family:'Syne',sans-serif!important;font-weight:700!important;font-size:.8rem!important;
475
+ padding:.5rem 1.2rem!important;}
476
+ button.secondary:hover{background:var(--green)!important;color:#030712!important;}
477
+ .out textarea{background:#020e06!important;border:1px solid var(--green-border)!important;
478
+ border-left:3px solid var(--green)!important;border-radius:0 var(--r) var(--r) 0!important;
479
+ font-family:'DM Mono',monospace!important;font-size:.84rem!important;line-height:1.7!important;color:#ccffe6!important;}
480
+ .gradio-accordion{background:#061a0f!important;border:1px solid var(--green-border)!important;
481
+ border-radius:var(--r)!important;margin-bottom:.6rem!important;}
482
+ .gradio-accordion .label-wrap span{color:var(--green)!important;font-family:'Syne',sans-serif!important;
483
+ font-weight:700!important;font-size:.85rem!important;}
484
+ .gradio-accordion svg{color:var(--green)!important;stroke:var(--green)!important;}
485
+ .foot{text-align:center;padding:1.5rem;color:var(--muted);font-family:'DM Mono',monospace;
486
+ font-size:.68rem;border-top:1px solid var(--green-border);margin-top:2rem;letter-spacing:.05em;}
 
487
  """
488
 
489
+ # ── UI ────────────────────────────────────────────────────────────────────────
490
  with gr.Blocks(css=CSS, title="Metabolic Phoenix Framework") as demo:
491
 
492
+ gr.HTML("""<div class="hdr">
 
493
  <h1>⚡ The Metabolic Phoenix Framework</h1>
494
+ <p class="sub">Injectiv8 &nbsp;·&nbsp; Ms. Nala &nbsp;·&nbsp; Mistral 7B</p>
495
  <p class="own">Invented &amp; Owned by Dwayne Anthony Brian Galloway &nbsp;·&nbsp; 2026</p>
496
+ </div>""")
 
497
 
498
  pdf_input = gr.File(
499
+ label="Upload Documents — PDF · Image · Audio · Video · Text — drop multiple",
500
  file_types=[
501
+ ".pdf",".txt",".md",
502
+ ".jpg",".jpeg",".png",".gif",".webp",".bmp",".tiff",
503
+ ".mp3",".wav",".m4a",".ogg",".flac",
504
+ "audio/mpeg","audio/mp3","audio/wav","audio/x-wav",
505
+ "audio/mp4","audio/ogg","audio/flac","audio/*",
506
+ ".mp4",".mov",".avi",".mkv",".webm",
507
+ "video/mp4","video/quicktime","video/x-msvideo","video/x-matroska","video/webm","video/*",
 
508
  ],
509
+ file_count="multiple", type="filepath"
 
510
  )
511
  run_btn = gr.Button("⚡ Analyse Documents", variant="primary")
512
 
513
  with gr.Row():
514
+ speed_slider = gr.Slider(0.5, 2.0, value=1.0, step=0.05, label="🎚 Speed")
515
+ pitch_slider = gr.Slider(0.5, 2.0, value=1.0, step=0.05, label="🎵 Pitch")
516
+
517
+ with gr.Accordion("🔊 Read Document — no analysis", open=True):
518
+ read_doc_btn = gr.Button("🔊 Read Document(s) Aloud — full length", variant="secondary")
519
+ read_doc_msg = gr.Markdown("")
 
 
 
 
 
 
520
  read_doc_audio = gr.Audio(label="Document Audio", autoplay=True)
521
 
522
  with gr.Accordion("Cycle 1 — Fact Ledger", open=True):
523
  out1 = gr.Textbox(label="", lines=12, elem_classes=["out"])
524
+ rb1_btn = gr.Button("🔊 Read Cycle 1", variant="secondary")
525
  audio1 = gr.Audio(label="Cycle 1 Audio", autoplay=True)
526
 
527
  with gr.Accordion("Cycle 2 — Compliance Analysis", open=False):
528
  out2 = gr.Textbox(label="", lines=12, elem_classes=["out"])
529
+ rb2_btn = gr.Button("🔊 Read Cycle 2", variant="secondary")
530
  audio2 = gr.Audio(label="Cycle 2 Audio", autoplay=True)
531
 
532
  with gr.Accordion("Cycle 3 — Ouroboros Loops", open=False):
533
  out3 = gr.Textbox(label="", lines=12, elem_classes=["out"])
534
+ rb3_btn = gr.Button("🔊 Read Cycle 3", variant="secondary")
535
  audio3 = gr.Audio(label="Cycle 3 Audio", autoplay=True)
536
 
537
  with gr.Accordion("Cycle 4 — Full Case Output", open=False):
538
  out4 = gr.Textbox(label="", lines=12, elem_classes=["out"])
539
+ rb4_btn = gr.Button("🔊 Read Cycle 4", variant="secondary")
540
  audio4 = gr.Audio(label="Cycle 4 Audio", autoplay=True)
541
 
542
+ rball_btn = gr.Button("🔊 Read Full Analysis", variant="primary")
543
  audio_all = gr.Audio(label="Full Analysis Audio", autoplay=True)
 
544
  save_btn = gr.Button("💾 Save & Download Analysis", variant="secondary")
545
+ dl_file = gr.File(label="Download analysis")
546
  save_msg = gr.Markdown("")
547
 
548
+ # ── DOCUMENT GENERATION ──────────────────────────────────────────────────
549
  gr.HTML("<hr style='border-color:#1a2544;margin:2rem 0;'>")
550
+ gr.HTML("""<div style='text-align:center;padding:1.2rem 0 .6rem;'>
551
+ <h2 style='font-size:1.5rem;font-weight:800;
 
552
  background:linear-gradient(135deg,#FFE500,#FF6B00);
553
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;margin:0 0 .3rem;'>
554
  ⚖️ Document Generation Engine
555
  </h2>
556
  <p style='color:#4A6080;font-family:"DM Mono",monospace;font-size:.73rem;letter-spacing:.05em;'>
557
+ RUN ANALYSIS FIRST — THEN GENERATE DOCUMENTS
558
  </p>
559
+ </div>""")
 
560
 
561
  gen_all_btn = gr.Button("⚖️ Generate ALL Documents", variant="primary")
562
 
563
  with gr.Accordion("Document 1 — Letter Before Action", open=True):
564
+ gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>Pre-Action Protocol. 14-day deadline. Litigation follows without further notice.</p>")
565
+ gen_lba_btn = gr.Button("Generate LBA", variant="secondary")
566
  doc_lba = gr.Textbox(label="", lines=14, elem_classes=["out"])
567
  rd_lba_btn = gr.Button("🔊 Read Aloud", variant="secondary")
568
+ aud_lba = gr.Audio(label="", autoplay=True)
569
 
570
  with gr.Accordion("Document 2 — Formal Regulatory Complaint", open=False):
571
  gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>For FOS, FCA, ICO, Parliamentary Ombudsman. Numbered findings. Demands investigation.</p>")
572
+ gen_comp_btn = gr.Button("Generate Complaint", variant="secondary")
573
  doc_comp = gr.Textbox(label="", lines=14, elem_classes=["out"])
574
  rd_comp_btn = gr.Button("🔊 Read Aloud", variant="secondary")
575
+ aud_comp = gr.Audio(label="", autoplay=True)
576
 
577
  with gr.Accordion("Document 3 — Witness Statement", open=False):
578
+ gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>First person. Court-ready. Statement of Truth. King's Bench Division heading.</p>")
579
  gen_wit_btn = gr.Button("Generate Witness Statement", variant="secondary")
580
  doc_wit = gr.Textbox(label="", lines=14, elem_classes=["out"])
581
  rd_wit_btn = gr.Button("🔊 Read Aloud", variant="secondary")
582
+ aud_wit = gr.Audio(label="", autoplay=True)
583
 
584
  with gr.Accordion("Document 4 — Skeleton Argument", open=False):
585
  gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>Structured legal argument. Authorities cited. Issues for determination. Relief sought.</p>")
586
  gen_skel_btn = gr.Button("Generate Skeleton Argument", variant="secondary")
587
  doc_skel = gr.Textbox(label="", lines=14, elem_classes=["out"])
588
  rd_skel_btn = gr.Button("🔊 Read Aloud", variant="secondary")
589
+ aud_skel = gr.Audio(label="", autoplay=True)
590
 
591
  with gr.Accordion("Document 5 — Regulatory Referral", open=False):
592
+ gr.HTML("<p style='color:#4A6080;font-size:.75rem;font-family:DM Mono,monospace;padding:.3rem 0;'>FCA / SRA / ICO referral. 28-day deadline. Escalation to press if no action.</p>")
593
  gen_reg_btn = gr.Button("Generate Regulatory Referral", variant="secondary")
594
  doc_reg = gr.Textbox(label="", lines=14, elem_classes=["out"])
595
  rd_reg_btn = gr.Button("🔊 Read Aloud", variant="secondary")
596
+ aud_reg = gr.Audio(label="", autoplay=True)
597
 
598
  save_docs_btn = gr.Button("💾 Save & Download All Documents", variant="secondary")
599
  dl_docs_file = gr.File(label="Download all documents")
600
  save_docs_msg = gr.Markdown("")
601
 
602
+ gr.HTML("""<div class="foot">
603
+ THE METABOLIC PHOENIX FRAMEWORK · Injectiv8 · Ms. Nala
604
+ · © Dwayne Anthony Brian Galloway 2026 · All Rights Reserved
605
+ </div>""")
606
+
607
+ # ── WIRE UP ───────────────────────────────────────────────────────────────
608
+ run_btn.click(analyse, inputs=[pdf_input], outputs=[out1,out2,out3,out4])
609
+ read_doc_btn.click(read_document_only, inputs=[pdf_input,speed_slider,pitch_slider], outputs=[read_doc_audio,read_doc_msg])
610
+
611
+ rb1_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[out1,speed_slider,pitch_slider], outputs=[audio1])
612
+ rb2_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[out2,speed_slider,pitch_slider], outputs=[audio2])
613
+ rb3_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[out3,speed_slider,pitch_slider], outputs=[audio3])
614
+ rb4_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[out4,speed_slider,pitch_slider], outputs=[audio4])
615
+ rball_btn.click(fn=lambda t1,t2,t3,t4,sp,pi:make_audio("\n\n".join(x for x in [t1,t2,t3,t4] if x),sp,pi),
616
+ inputs=[out1,out2,out3,out4,speed_slider,pitch_slider], outputs=[audio_all])
617
+ save_btn.click(save_analysis, inputs=[out1,out2,out3,out4], outputs=[dl_file,save_msg])
618
+
619
+ gen_all_btn.click(generate_all_documents, inputs=[out1,out2,out3,out4], outputs=[doc_lba,doc_comp,doc_wit,doc_skel,doc_reg])
620
+ gen_lba_btn.click(generate_letter_before_action, inputs=[out1,out2,out3,out4], outputs=[doc_lba])
621
+ gen_comp_btn.click(generate_formal_complaint, inputs=[out1,out2,out3,out4], outputs=[doc_comp])
622
+ gen_wit_btn.click(generate_witness_statement, inputs=[out1,out2,out4], outputs=[doc_wit])
623
+ gen_skel_btn.click(generate_skeleton_argument, inputs=[out1,out2,out3,out4], outputs=[doc_skel])
624
+ gen_reg_btn.click(generate_regulatory_referral, inputs=[out1,out2,out3], outputs=[doc_reg])
625
+
626
+ rd_lba_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[doc_lba,speed_slider,pitch_slider], outputs=[aud_lba])
627
+ rd_comp_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[doc_comp,speed_slider,pitch_slider], outputs=[aud_comp])
628
+ rd_wit_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[doc_wit,speed_slider,pitch_slider], outputs=[aud_wit])
629
+ rd_skel_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[doc_skel,speed_slider,pitch_slider], outputs=[aud_skel])
630
+ rd_reg_btn.click(fn=lambda t,sp,pi:make_audio(t,sp,pi), inputs=[doc_reg,speed_slider,pitch_slider], outputs=[aud_reg])
631
+ save_docs_btn.click(save_documents, inputs=[doc_lba,doc_comp,doc_wit,doc_skel,doc_reg], outputs=[dl_docs_file,save_docs_msg])
 
632
 
633
  if __name__ == "__main__":
634
  demo.launch()