Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
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
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
|
| 27 |
-
def get_client():
|
| 28 |
token = os.environ.get("HF_TOKEN", "")
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 73 |
-
if not file:
|
| 74 |
-
return ""
|
| 75 |
path = file.name if hasattr(file, "name") else str(file)
|
| 76 |
-
ext
|
| 77 |
-
|
| 78 |
if ext == ".pdf":
|
| 79 |
return extract_pdf_text(path)
|
| 80 |
-
elif ext in [".jpg",
|
| 81 |
return extract_image_text(path)
|
| 82 |
-
elif ext in [".mp3",
|
| 83 |
return transcribe_audio(path)
|
| 84 |
-
elif ext in [".mp4",
|
| 85 |
-
# Extract audio from video then transcribe
|
| 86 |
try:
|
| 87 |
-
|
| 88 |
-
subprocess.run(
|
| 89 |
-
|
| 90 |
-
|
| 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 |
-
# ──
|
| 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
|
| 124 |
-
"
|
| 125 |
-
"No
|
| 126 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
)
|
| 128 |
|
| 129 |
def analyse(files):
|
| 130 |
if not files:
|
| 131 |
-
return "
|
| 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 |
-
|
| 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
|
| 148 |
doc_chunk = combined[:14000]
|
| 149 |
-
base
|
| 150 |
|
| 151 |
r1 = call_llama(base +
|
| 152 |
-
"You are Introbot — part of the Metabolic Phoenix Framework
|
| 153 |
-
"
|
| 154 |
-
"From these documents
|
| 155 |
-
"- Every confirmed fact: who said it, when,
|
| 156 |
-
"- Every admission
|
| 157 |
-
"- Every denial
|
| 158 |
-
"- Every contradiction —
|
| 159 |
-
"- Every deliberate omission — what was required to be said, was not
|
| 160 |
-
"- Every statutory duty
|
| 161 |
-
"Do not hedge.
|
| 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
|
| 166 |
-
"
|
| 167 |
-
"
|
| 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
|
| 170 |
-
"- For every breach:
|
| 171 |
-
"-
|
| 172 |
-
"-
|
| 173 |
-
"-
|
| 174 |
-
"
|
| 175 |
-
"
|
| 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
|
| 180 |
-
"
|
| 181 |
-
"RING A — FORWARD SCAN:
|
| 182 |
-
"
|
| 183 |
-
"RING
|
| 184 |
-
"
|
| 185 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
)
|
|
|
|
| 187 |
r4 = call_llama(
|
| 188 |
-
f"You are Layoutbot — part of the Metabolic Phoenix Framework
|
| 189 |
-
f"
|
| 190 |
f"FACT LEDGER:\n{r1[:1500]}\n\n"
|
| 191 |
-
f"
|
| 192 |
f"LOOP OUTPUT:\n{r3[:1500]}\n\n"
|
| 193 |
f"{'='*60}\n\n"
|
| 194 |
-
"Produce a complete litigation-ready case summary.
|
| 195 |
-
"
|
| 196 |
-
"
|
| 197 |
-
"
|
| 198 |
-
"
|
| 199 |
-
"
|
|
|
|
| 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\
|
| 208 |
-
"
|
| 209 |
-
"
|
| 210 |
-
"
|
| 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 |
-
|
| 218 |
-
|
| 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)
|
| 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 |
-
"
|
| 226 |
-
"1. Opens
|
| 227 |
-
"2.
|
| 228 |
-
"3. States every loss
|
| 229 |
-
"4. States the specific
|
| 230 |
-
"5. States
|
| 231 |
-
"6. Closes
|
| 232 |
-
|
| 233 |
-
+ DOC_PLAIN
|
| 234 |
)
|
| 235 |
-
return call_llama(prompt, max_tokens=2000)
|
| 236 |
-
|
| 237 |
|
| 238 |
def generate_formal_complaint(r1, r2, r3, r4):
|
| 239 |
-
|
| 240 |
-
|
| 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
|
| 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 |
-
"
|
| 248 |
-
"1. Names the respondent
|
| 249 |
-
"2. States the date
|
| 250 |
-
"3. States every statutory provision breached with full citation
|
| 251 |
-
"4. States
|
| 252 |
-
"5. States what the respondent was required to do, what they did instead,
|
| 253 |
-
"6. States what remedy the Claimant requires from the regulator
|
| 254 |
-
"7. States
|
| 255 |
-
"8. Closes with a demand
|
| 256 |
-
|
| 257 |
-
+ DOC_PLAIN
|
| 258 |
)
|
| 259 |
-
return call_llama(prompt, max_tokens=2000)
|
| 260 |
-
|
| 261 |
|
| 262 |
def generate_witness_statement(r1, r2, r4):
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 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 |
-
"
|
| 271 |
-
"1. Opens
|
| 272 |
-
"
|
| 273 |
-
"
|
| 274 |
-
"
|
| 275 |
-
"
|
| 276 |
-
"
|
| 277 |
-
"
|
| 278 |
-
"
|
| 279 |
-
"
|
| 280 |
-
+ DOC_PLAIN
|
| 281 |
)
|
| 282 |
-
return call_llama(prompt, max_tokens=2000)
|
| 283 |
-
|
| 284 |
|
| 285 |
def generate_skeleton_argument(r1, r2, r3, r4):
|
| 286 |
-
|
| 287 |
-
|
| 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
|
| 292 |
-
"ANALYSIS:\n"
|
| 293 |
-
f"
|
| 294 |
-
"
|
| 295 |
-
"1. Opens
|
| 296 |
-
"
|
| 297 |
-
"
|
| 298 |
-
"
|
| 299 |
-
"
|
| 300 |
-
"
|
| 301 |
-
"
|
| 302 |
-
"
|
| 303 |
-
"Archibald v Fife
|
| 304 |
-
"
|
| 305 |
-
|
| 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 |
-
|
| 318 |
-
|
| 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 |
-
"
|
| 326 |
-
"1. States
|
| 327 |
-
"2. Identifies each regulated entity by name
|
| 328 |
-
"3. States each confirmed breach as a numbered finding:
|
| 329 |
-
"4. States
|
| 330 |
-
"
|
| 331 |
-
"
|
| 332 |
-
"
|
| 333 |
-
"
|
| 334 |
-
|
|
|
|
| 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 "", "", "", "", "
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 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, "
|
| 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.
|
| 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.
|
| 534 |
-
return tmp.name, "✅ Ready to download."
|
| 535 |
|
| 536 |
|
| 537 |
-
# ──
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
--
|
| 542 |
-
--
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
.hdr {
|
| 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;
|
| 553 |
-
.hdr .sub
|
| 554 |
-
.hdr .own
|
| 555 |
-
input[type=text],
|
| 556 |
-
border-radius:var(--r)!important;
|
| 557 |
-
font-size:.86rem!important;
|
| 558 |
-
input[type=text]:focus,
|
| 559 |
-
box-shadow:0 0 0 3px rgba(0,230,118,.15)!important;
|
| 560 |
-
label
|
| 561 |
-
font-size:.7rem!important;
|
| 562 |
-
button.primary
|
| 563 |
-
color:#030712!important;
|
| 564 |
-
font-family:'Syne',sans-serif!important;
|
| 565 |
-
padding:.75rem 2.8rem!important;
|
| 566 |
-
button.primary:hover
|
| 567 |
-
button.secondary
|
| 568 |
-
border:1px solid var(--green-border)!important;
|
| 569 |
-
font-family:'Syne',sans-serif!important;
|
| 570 |
-
padding:.5rem 1.2rem!important;
|
| 571 |
-
button.secondary:hover
|
| 572 |
-
.out textarea
|
| 573 |
-
border-left:3px solid var(--green)!important;
|
| 574 |
-
font-family:'DM Mono',monospace!important;
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
.
|
| 581 |
-
|
| 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 · Ms. Nala ·
|
| 592 |
<p class="own">Invented & Owned by Dwayne Anthony Brian Galloway · 2026</p>
|
| 593 |
-
</div>
|
| 594 |
-
""")
|
| 595 |
|
| 596 |
pdf_input = gr.File(
|
| 597 |
-
label="Upload Documents —
|
| 598 |
file_types=[
|
| 599 |
-
".pdf",
|
| 600 |
-
".jpg",
|
| 601 |
-
".mp3",
|
| 602 |
-
"audio/mpeg",
|
| 603 |
-
"audio/mp4",
|
| 604 |
-
".mp4",
|
| 605 |
-
"video/mp4",
|
| 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 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 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
|
| 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
|
| 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
|
| 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
|
| 646 |
audio4 = gr.Audio(label="Cycle 4 Audio", autoplay=True)
|
| 647 |
|
| 648 |
-
rball_btn = gr.Button("🔊 Read Full Analysis
|
| 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
|
| 653 |
save_msg = gr.Markdown("")
|
| 654 |
|
| 655 |
-
# ── DOCUMENT GENERATION ──────────────────────────────────────────────
|
| 656 |
gr.HTML("<hr style='border-color:#1a2544;margin:2rem 0;'>")
|
| 657 |
-
gr.HTML("""
|
| 658 |
-
|
| 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
|
| 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
|
| 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
|
| 674 |
-
gen_lba_btn = gr.Button("Generate
|
| 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="
|
| 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
|
| 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="
|
| 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.
|
| 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="
|
| 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="
|
| 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
|
| 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="
|
| 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 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 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 · Ms. Nala · Mistral 7B</p>
|
| 495 |
<p class="own">Invented & Owned by Dwayne Anthony Brian Galloway · 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()
|