Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- PIPELINE_FIXES.md +0 -0
- api/main.py +8 -2
- frontend/court/court.html +3 -24
- frontend/index_old_judicial.html +296 -0
- src/court/orchestrator.py +178 -34
PIPELINE_FIXES.md
ADDED
|
File without changes
|
api/main.py
CHANGED
|
@@ -330,7 +330,7 @@ def court_new_session(request: NewSessionRequest):
|
|
| 330 |
)
|
| 331 |
|
| 332 |
# Registrar opens the session
|
| 333 |
-
from src.court.session import get_session, add_transcript_entry
|
| 334 |
session = get_session(session_id)
|
| 335 |
opening = build_round_announcement(session, 0, "briefing")
|
| 336 |
add_transcript_entry(
|
|
@@ -341,6 +341,9 @@ def court_new_session(request: NewSessionRequest):
|
|
| 341 |
entry_type="announcement",
|
| 342 |
)
|
| 343 |
|
|
|
|
|
|
|
|
|
|
| 344 |
return {
|
| 345 |
"session_id": session_id,
|
| 346 |
"case_brief": case_brief,
|
|
@@ -386,7 +389,7 @@ def court_import_session(request: ImportSessionRequest):
|
|
| 386 |
case_brief=case_brief,
|
| 387 |
)
|
| 388 |
|
| 389 |
-
from src.court.session import get_session
|
| 390 |
session = get_session(session_id)
|
| 391 |
opening = build_round_announcement(session, 0, "briefing")
|
| 392 |
add_transcript_entry(
|
|
@@ -397,6 +400,9 @@ def court_import_session(request: ImportSessionRequest):
|
|
| 397 |
entry_type="announcement",
|
| 398 |
)
|
| 399 |
|
|
|
|
|
|
|
|
|
|
| 400 |
return {
|
| 401 |
"session_id": session_id,
|
| 402 |
"case_brief": case_brief,
|
|
|
|
| 330 |
)
|
| 331 |
|
| 332 |
# Registrar opens the session
|
| 333 |
+
from src.court.session import get_session, add_transcript_entry, update_session
|
| 334 |
session = get_session(session_id)
|
| 335 |
opening = build_round_announcement(session, 0, "briefing")
|
| 336 |
add_transcript_entry(
|
|
|
|
| 341 |
entry_type="announcement",
|
| 342 |
)
|
| 343 |
|
| 344 |
+
# Initialize: User is ready to submit briefing (opening argument)
|
| 345 |
+
update_session(session_id, {"awaiting_action": "user"})
|
| 346 |
+
|
| 347 |
return {
|
| 348 |
"session_id": session_id,
|
| 349 |
"case_brief": case_brief,
|
|
|
|
| 389 |
case_brief=case_brief,
|
| 390 |
)
|
| 391 |
|
| 392 |
+
from src.court.session import get_session, add_transcript_entry, update_session
|
| 393 |
session = get_session(session_id)
|
| 394 |
opening = build_round_announcement(session, 0, "briefing")
|
| 395 |
add_transcript_entry(
|
|
|
|
| 400 |
entry_type="announcement",
|
| 401 |
)
|
| 402 |
|
| 403 |
+
# Initialize: User is ready to submit briefing (opening argument)
|
| 404 |
+
update_session(session_id, {"awaiting_action": "user"})
|
| 405 |
+
|
| 406 |
return {
|
| 407 |
"session_id": session_id,
|
| 408 |
"case_brief": case_brief,
|
frontend/court/court.html
CHANGED
|
@@ -93,9 +93,9 @@
|
|
| 93 |
</nav>
|
| 94 |
</header>
|
| 95 |
|
| 96 |
-
<main class="relative z-10 pt-28 pb-12 px-8
|
| 97 |
<!-- Centre card -->
|
| 98 |
-
<div class="
|
| 99 |
<div class="clay-card p-8 lg:p-10 rounded-xl w-full max-w-2xl text-center">
|
| 100 |
<!-- Gavel icon -->
|
| 101 |
<div class="mb-6 inline-flex items-center justify-center w-16 h-16 rounded-full bg-surface-container clay-puffy">
|
|
@@ -107,35 +107,14 @@
|
|
| 107 |
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-8">
|
| 108 |
<button onclick="showScreen('setup')" class="clay-btn-primary flex items-center justify-center gap-2 px-6 py-3 rounded-lg font-sans font-bold text-sm text-white">
|
| 109 |
<span class="material-symbols-outlined text-lg">scale</span>
|
| 110 |
-
|
| 111 |
-
</button>
|
| 112 |
-
<button onclick="showImportFlow()" class="clay-btn-secondary flex items-center justify-center gap-2 border-2 border-secondary/30 px-6 py-3 rounded-lg font-sans font-bold text-sm text-secondary">
|
| 113 |
-
<span class="material-symbols-outlined text-lg">folder_open</span>
|
| 114 |
-
Continue
|
| 115 |
</button>
|
| 116 |
</div>
|
| 117 |
-
<a onclick="showScreen('sessions')" class="text-secondary font-sans font-bold text-sm hover:underline flex items-center justify-center gap-2 mb-6 cursor-pointer">
|
| 118 |
-
<span class="material-symbols-outlined text-sm">list_alt</span>
|
| 119 |
-
Past Sessions
|
| 120 |
-
</a>
|
| 121 |
<div class="pt-4 border-t border-primary/5">
|
| 122 |
<p class="text-primary/40 text-xs font-sans italic">For educational simulation only. Not legal advice.</p>
|
| 123 |
</div>
|
| 124 |
</div>
|
| 125 |
</div>
|
| 126 |
-
|
| 127 |
-
<!-- Recent sessions sidebar -->
|
| 128 |
-
<div class="col-span-12 lg:col-span-5 flex flex-col gap-4">
|
| 129 |
-
<div class="flex items-center justify-between px-3">
|
| 130 |
-
<h3 class="font-serif text-lg font-bold text-primary">Recent Sessions</h3>
|
| 131 |
-
<span class="text-[10px] font-mono text-secondary bg-secondary-fixed/30 px-1.5 py-0.5 rounded">LIVE</span>
|
| 132 |
-
</div>
|
| 133 |
-
<div id="recent-sessions-list" class="space-y-3">
|
| 134 |
-
<div class="clay-card p-4 rounded-lg text-center text-primary/40 text-xs font-sans">
|
| 135 |
-
Loading...
|
| 136 |
-
</div>
|
| 137 |
-
</div>
|
| 138 |
-
</div>
|
| 139 |
</main>
|
| 140 |
|
| 141 |
<!-- Registrar clock -->
|
|
|
|
| 93 |
</nav>
|
| 94 |
</header>
|
| 95 |
|
| 96 |
+
<main class="relative z-10 pt-28 pb-12 px-8 flex justify-center items-center min-h-screen">
|
| 97 |
<!-- Centre card -->
|
| 98 |
+
<div class="flex justify-center items-center">
|
| 99 |
<div class="clay-card p-8 lg:p-10 rounded-xl w-full max-w-2xl text-center">
|
| 100 |
<!-- Gavel icon -->
|
| 101 |
<div class="mb-6 inline-flex items-center justify-center w-16 h-16 rounded-full bg-surface-container clay-puffy">
|
|
|
|
| 107 |
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-8">
|
| 108 |
<button onclick="showScreen('setup')" class="clay-btn-primary flex items-center justify-center gap-2 px-6 py-3 rounded-lg font-sans font-bold text-sm text-white">
|
| 109 |
<span class="material-symbols-outlined text-lg">scale</span>
|
| 110 |
+
Fight A Case
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
</button>
|
| 112 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
<div class="pt-4 border-t border-primary/5">
|
| 114 |
<p class="text-primary/40 text-xs font-sans italic">For educational simulation only. Not legal advice.</p>
|
| 115 |
</div>
|
| 116 |
</div>
|
| 117 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
</main>
|
| 119 |
|
| 120 |
<!-- Registrar clock -->
|
frontend/index_old_judicial.html
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="light" lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>NYAYA SETU — Legal Research Registry</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,600;0,700;1,600;1,700&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
|
| 10 |
+
<script id="tailwind-config">
|
| 11 |
+
tailwind.config = {
|
| 12 |
+
darkMode: "class",
|
| 13 |
+
theme: {
|
| 14 |
+
extend: {
|
| 15 |
+
colors: {
|
| 16 |
+
"on-secondary": "#ffffff",
|
| 17 |
+
"surface-container-high": "#ece8e0",
|
| 18 |
+
"surface-container": "#f2ede5",
|
| 19 |
+
"on-tertiary-fixed-variant": "#4e3e6f",
|
| 20 |
+
"tertiary": "#342554",
|
| 21 |
+
"secondary-container": "#fed174",
|
| 22 |
+
"primary-container": "#2c4a3e",
|
| 23 |
+
"outline": "#727974",
|
| 24 |
+
"on-tertiary-fixed": "#221141",
|
| 25 |
+
"surface-container-highest": "#e7e2da",
|
| 26 |
+
"secondary": "#795900",
|
| 27 |
+
"on-surface": "#1d1c17",
|
| 28 |
+
"secondary-fixed-dim": "#ecc165",
|
| 29 |
+
"on-tertiary": "#ffffff",
|
| 30 |
+
"error-container": "#ffdad6",
|
| 31 |
+
"on-error-container": "#93000a",
|
| 32 |
+
"surface-tint": "#466558",
|
| 33 |
+
"error": "#ba1a1a",
|
| 34 |
+
"surface-bright": "#fef9f1",
|
| 35 |
+
"on-tertiary-container": "#bba8e1",
|
| 36 |
+
"secondary-fixed": "#ffdfa0",
|
| 37 |
+
"inverse-on-surface": "#f5f0e8",
|
| 38 |
+
"primary": "#153328",
|
| 39 |
+
"outline-variant": "#c1c8c3",
|
| 40 |
+
"on-secondary-container": "#785800",
|
| 41 |
+
"on-secondary-fixed": "#261a00",
|
| 42 |
+
"on-primary-fixed": "#012016",
|
| 43 |
+
"surface-variant": "#e7e2da",
|
| 44 |
+
"on-surface-variant": "#414845",
|
| 45 |
+
"on-primary-container": "#98b9a9",
|
| 46 |
+
"tertiary-container": "#4b3b6c",
|
| 47 |
+
"on-background": "#1d1c17",
|
| 48 |
+
"inverse-surface": "#32302b",
|
| 49 |
+
"surface-container-lowest": "#ffffff",
|
| 50 |
+
"background": "#fef9f1",
|
| 51 |
+
"tertiary-fixed-dim": "#d1bdf7",
|
| 52 |
+
"on-primary": "#ffffff",
|
| 53 |
+
"on-error": "#ffffff",
|
| 54 |
+
"surface-dim": "#ded9d2",
|
| 55 |
+
"on-secondary-fixed-variant": "#5c4300",
|
| 56 |
+
"inverse-primary": "#adcebe",
|
| 57 |
+
"tertiary-fixed": "#eaddff",
|
| 58 |
+
"surface": "#fef9f1",
|
| 59 |
+
"on-primary-fixed-variant": "#2f4d41",
|
| 60 |
+
"surface-container-low": "#f8f3eb",
|
| 61 |
+
"primary-fixed": "#c8eada",
|
| 62 |
+
"primary-fixed-dim": "#adcebe"
|
| 63 |
+
},
|
| 64 |
+
borderRadius: {
|
| 65 |
+
DEFAULT: "1rem",
|
| 66 |
+
lg: "2rem",
|
| 67 |
+
xl: "3rem",
|
| 68 |
+
full: "9999px"
|
| 69 |
+
},
|
| 70 |
+
fontFamily: {
|
| 71 |
+
headline: ["Cormorant Garamond", "serif"],
|
| 72 |
+
body: ["Plus Jakarta Sans", "sans-serif"],
|
| 73 |
+
label: ["Cormorant Garamond", "serif"]
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
</script>
|
| 79 |
+
<style>
|
| 80 |
+
.clay-card {
|
| 81 |
+
background: #ffffff;
|
| 82 |
+
box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.08), inset 2px 2px 4px rgba(255, 255, 255, 0.8);
|
| 83 |
+
border-radius: 24px;
|
| 84 |
+
}
|
| 85 |
+
.clay-inset {
|
| 86 |
+
background: #f0ebe2;
|
| 87 |
+
box-shadow: inset 4px 4px 8px rgba(0, 0, 0, 0.06), inset -4px -4px 8px rgba(255, 255, 255, 0.9);
|
| 88 |
+
border-radius: 20px;
|
| 89 |
+
}
|
| 90 |
+
.clay-button-primary {
|
| 91 |
+
background: #2c4a3e;
|
| 92 |
+
box-shadow: 0 10px 20px -5px rgba(44, 74, 62, 0.4), inset 2px 2px 4px rgba(255, 255, 255, 0.3);
|
| 93 |
+
transition: all 0.2s ease;
|
| 94 |
+
cursor: pointer;
|
| 95 |
+
}
|
| 96 |
+
.clay-button-primary:hover {
|
| 97 |
+
transform: translateY(-2px);
|
| 98 |
+
box-shadow: 0 12px 24px -5px rgba(44, 74, 62, 0.5), inset 2px 2px 4px rgba(255, 255, 255, 0.3);
|
| 99 |
+
}
|
| 100 |
+
.clay-button-primary:active {
|
| 101 |
+
transform: scale(0.98);
|
| 102 |
+
box-shadow: inset 4px 4px 8px rgba(0, 0, 0, 0.3);
|
| 103 |
+
}
|
| 104 |
+
.material-symbols-outlined {
|
| 105 |
+
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
| 106 |
+
}
|
| 107 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 108 |
+
width: 4px;
|
| 109 |
+
}
|
| 110 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 111 |
+
background: transparent;
|
| 112 |
+
}
|
| 113 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 114 |
+
background: #dcd7ce;
|
| 115 |
+
border-radius: 10px;
|
| 116 |
+
}
|
| 117 |
+
</style>
|
| 118 |
+
</head>
|
| 119 |
+
<body class="bg-[#F5F0E8] text-on-surface font-body h-screen flex overflow-hidden">
|
| 120 |
+
<div class="app-container">
|
| 121 |
+
|
| 122 |
+
<!-- SIDEBAR - JUDICIAL PANEL -->
|
| 123 |
+
<aside class="sidebar" id="sidebar">
|
| 124 |
+
<!-- Header with Judicial Seal -->
|
| 125 |
+
<div class="sidebar-header">
|
| 126 |
+
<div class="judicial-seal">
|
| 127 |
+
<svg viewBox="0 0 100 100" class="seal-svg">
|
| 128 |
+
<circle cx="50" cy="50" r="48" fill="none" stroke="currentColor" stroke-width="2"/>
|
| 129 |
+
<circle cx="50" cy="50" r="42" fill="none" stroke="currentColor" stroke-width="1" opacity="0.5"/>
|
| 130 |
+
<!-- Scale/Balance symbol -->
|
| 131 |
+
<path d="M50 20 L35 45 L65 45 Z" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
| 132 |
+
<line x1="35" y1="45" x2="65" y2="45" stroke="currentColor" stroke-width="2"/>
|
| 133 |
+
<circle cx="35" cy="55" r="3" fill="currentColor"/>
|
| 134 |
+
<circle cx="65" cy="55" r="3" fill="currentColor"/>
|
| 135 |
+
<text x="50" y="75" font-size="8" text-anchor="middle" font-weight="bold" letter-spacing="1">NYAYASETU</text>
|
| 136 |
+
</svg>
|
| 137 |
+
</div>
|
| 138 |
+
<div class="brand-text">
|
| 139 |
+
<div class="brand-name">NYAYA SETU</div>
|
| 140 |
+
<div class="brand-sub">Legal Research Registry</div>
|
| 141 |
+
</div>
|
| 142 |
+
<button class="sidebar-toggle" onclick="toggleSidebar()">≡</button>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
<!-- Primary Actions -->
|
| 146 |
+
<div class="action-group">
|
| 147 |
+
<button class="btn-primary" onclick="newChat()">⊕ New Petition</button>
|
| 148 |
+
<button class="btn-secondary" onclick="showAnalytics()">📋 Case Analytics</button>
|
| 149 |
+
<button class="btn-secondary" onclick="window.location.href='/court/ui'">⚔ Moot Court</button>
|
| 150 |
+
</div>
|
| 151 |
+
|
| 152 |
+
<!-- Case Registry -->
|
| 153 |
+
<div class="registry-section">
|
| 154 |
+
<div class="registry-header">
|
| 155 |
+
<span class="registry-title">CASE REGISTRY</span>
|
| 156 |
+
<div class="registry-divider"></div>
|
| 157 |
+
</div>
|
| 158 |
+
<div class="sessions-list" id="sessions-list">
|
| 159 |
+
<div class="sessions-empty">No active cases</div>
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
|
| 163 |
+
<!-- Sidebar Footer - Court Info -->
|
| 164 |
+
<div class="sidebar-footer">
|
| 165 |
+
<div class="court-info">
|
| 166 |
+
<p class="disclaimer">⚠ DISCLAIMER</p>
|
| 167 |
+
<p class="disclaimer-text">Not legal advice. Consult qualified advocate.</p>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="court-meta">
|
| 170 |
+
<p class="meta-text">Supreme Court of India</p>
|
| 171 |
+
<p class="meta-text">Established 1950</p>
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
</aside>
|
| 175 |
+
|
| 176 |
+
<!-- MAIN CONTENT AREA -->
|
| 177 |
+
<main class="main-content">
|
| 178 |
+
<!-- Header/Bench -->
|
| 179 |
+
<header class="topbar">
|
| 180 |
+
<div class="topbar-left">
|
| 181 |
+
<h1 class="topbar-title" id="topbar-title">Research Session</h1>
|
| 182 |
+
<p class="topbar-subtitle" id="topbar-subtitle">Indian Legal Research System</p>
|
| 183 |
+
</div>
|
| 184 |
+
<div class="status-indicator" id="status-pill">
|
| 185 |
+
<span class="status-dot"></span>
|
| 186 |
+
<span id="status-text">Ready</span>
|
| 187 |
+
</div>
|
| 188 |
+
</header>
|
| 189 |
+
|
| 190 |
+
<!-- Welcome/Research Section -->
|
| 191 |
+
<section id="screen-welcome" class="screen active">
|
| 192 |
+
<div class="welcome-container">
|
| 193 |
+
<div class="welcome-graphic">
|
| 194 |
+
<svg viewBox="0 0 200 200" class="welcome-icon">
|
| 195 |
+
<!-- Judge's Bench -->
|
| 196 |
+
<rect x="20" y="120" width="160" height="40" fill="none" stroke="currentColor" stroke-width="2" rx="2"/>
|
| 197 |
+
<!-- Gavel -->
|
| 198 |
+
<rect x="85" y="30" width="30" height="50" fill="none" stroke="currentColor" stroke-width="2"/>
|
| 199 |
+
<circle cx="100" cy="20" r="12" fill="currentColor" opacity="0.3"/>
|
| 200 |
+
<!-- Scales -->
|
| 201 |
+
<line x1="50" y1="80" x2="50" y2="120" stroke="currentColor" stroke-width="2"/>
|
| 202 |
+
<line x1="35" y1="100" x2="65" y2="100" stroke="currentColor" stroke-width="2"/>
|
| 203 |
+
<circle cx="35" cy="105" r="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
| 204 |
+
<circle cx="65" cy="105" r="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
| 205 |
+
<!-- Books -->
|
| 206 |
+
<rect x="130" y="70" width="15" height="35" fill="currentColor" opacity="0.2" stroke="currentColor" stroke-width="1.5"/>
|
| 207 |
+
<rect x="148" y="65" width="15" height="40" fill="currentColor" opacity="0.2" stroke="currentColor" stroke-width="1.5"/>
|
| 208 |
+
</svg>
|
| 209 |
+
</div>
|
| 210 |
+
<h2>Indian Legal Research</h2>
|
| 211 |
+
<p class="welcome-subtitle">Query 26,688 Supreme Court Judgments<br><span class="text-muted">Cited sources | Verified citations | Complete legal brief</span></p>
|
| 212 |
+
|
| 213 |
+
<div class="suggestions">
|
| 214 |
+
<div class="suggestion-label">Sample Petitions:</div>
|
| 215 |
+
<button class="suggestion" onclick="usesuggestion(this)">Fundamental Rights — Definition & Scope</button>
|
| 216 |
+
<button class="suggestion" onclick="usesuggestion(this)">Right to Privacy under Article 21</button>
|
| 217 |
+
<button class="suggestion" onclick="usesuggestion(this)">Bail — Legal Framework & Supreme Court Tests</button>
|
| 218 |
+
<button class="suggestion" onclick="usesuggestion(this)">Basic Structure Doctrine — Implications</button>
|
| 219 |
+
<button class="suggestion" onclick="usesuggestion(this)">Article 15 — Non-discrimination Doctrine</button>
|
| 220 |
+
<button class="suggestion" onclick="usesuggestion(this)">Freedom of Speech — Reasonable Restrictions</button>
|
| 221 |
+
</div>
|
| 222 |
+
</div>
|
| 223 |
+
</section>
|
| 224 |
+
|
| 225 |
+
<!-- Chat/Case Proceedings Section -->
|
| 226 |
+
<section id="screen-chat" class="screen">
|
| 227 |
+
<div class="chat-area">
|
| 228 |
+
<div class="proceedings" id="messages-list"></div>
|
| 229 |
+
</div>
|
| 230 |
+
<div class="input-bench">
|
| 231 |
+
<textarea id="query-input" placeholder="Address the court... (Enter to submit, Shift+Enter for new line)" class="query-input"></textarea>
|
| 232 |
+
<button id="send-btn" class="btn-submit" onclick="submitQuery()">📤 Submit Petition</button>
|
| 233 |
+
</div>
|
| 234 |
+
</section>
|
| 235 |
+
|
| 236 |
+
<!-- Analytics Dashboard -->
|
| 237 |
+
<section id="screen-analytics" class="screen">
|
| 238 |
+
<div class="analytics-container">
|
| 239 |
+
<div class="analytics-header">
|
| 240 |
+
<h2>Case Analytics & System Metrics</h2>
|
| 241 |
+
<p>Live inference data from NyayaSetu research engine</p>
|
| 242 |
+
</div>
|
| 243 |
+
<div class="stats-grid">
|
| 244 |
+
<div class="stat-card">
|
| 245 |
+
<div class="stat-icon">📊</div>
|
| 246 |
+
<div class="stat-value" id="stat-total">—</div>
|
| 247 |
+
<div class="stat-label">Total Queries</div>
|
| 248 |
+
</div>
|
| 249 |
+
<div class="stat-card">
|
| 250 |
+
<div class="stat-icon">✓</div>
|
| 251 |
+
<div class="stat-value" id="stat-verified">—</div>
|
| 252 |
+
<div class="stat-label">Verified Answers</div>
|
| 253 |
+
</div>
|
| 254 |
+
<div class="stat-card">
|
| 255 |
+
<div class="stat-icon">⚡</div>
|
| 256 |
+
<div class="stat-value" id="stat-latency">—</div>
|
| 257 |
+
<div class="stat-label">Avg Latency</div>
|
| 258 |
+
</div>
|
| 259 |
+
<div class="stat-card">
|
| 260 |
+
<div class="stat-icon">🔍</div>
|
| 261 |
+
<div class="stat-value" id="stat-ood">—</div>
|
| 262 |
+
<div class="stat-label">Out-of-Domain</div>
|
| 263 |
+
</div>
|
| 264 |
+
<div class="stat-card">
|
| 265 |
+
<div class="stat-icon">📚</div>
|
| 266 |
+
<div class="stat-value" id="stat-sources">—</div>
|
| 267 |
+
<div class="stat-label">Avg Sources Used</div>
|
| 268 |
+
</div>
|
| 269 |
+
</div>
|
| 270 |
+
</div>
|
| 271 |
+
</section>
|
| 272 |
+
</main>
|
| 273 |
+
</div>
|
| 274 |
+
|
| 275 |
+
<script src="/static/app.js"></script>
|
| 276 |
+
</body>
|
| 277 |
+
</html>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</section>
|
| 282 |
+
|
| 283 |
+
<!-- INPUT AREA -->
|
| 284 |
+
<div class="input-area">
|
| 285 |
+
<div class="input-wrapper">
|
| 286 |
+
<textarea id="query-input" class="query-input" placeholder="Ask about Indian law..." maxlength="1000"></textarea>
|
| 287 |
+
<button id="send-btn" class="send-btn" onclick="submitQuery()">→</button>
|
| 288 |
+
</div>
|
| 289 |
+
<p class="disclaimer-line">Not a substitute for professional legal advice</p>
|
| 290 |
+
</div>
|
| 291 |
+
</main>
|
| 292 |
+
</div>
|
| 293 |
+
|
| 294 |
+
<script src="/static/app.js"></script>
|
| 295 |
+
</body>
|
| 296 |
+
</html>
|
src/court/orchestrator.py
CHANGED
|
@@ -197,6 +197,10 @@ def process_user_argument(
|
|
| 197 |
|
| 198 |
phase = session["phase"]
|
| 199 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
# Route to appropriate handler
|
| 201 |
try:
|
| 202 |
if phase == "briefing":
|
|
@@ -319,6 +323,9 @@ def _handle_briefing(session_id: str, user_argument: str, session: Dict) -> Dict
|
|
| 319 |
user_fell_in=False,
|
| 320 |
)
|
| 321 |
|
|
|
|
|
|
|
|
|
|
| 322 |
return {
|
| 323 |
"opposing_response": opposing_response,
|
| 324 |
"judge_question": judge_question,
|
|
@@ -340,6 +347,11 @@ def _handle_round(session_id: str, user_argument: str, session: Dict) -> Dict:
|
|
| 340 |
user_side = session["user_side"]
|
| 341 |
user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
|
| 342 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
# Add user argument to transcript
|
| 344 |
add_transcript_entry(
|
| 345 |
session_id=session_id,
|
|
@@ -439,10 +451,17 @@ def _handle_round(session_id: str, user_argument: str, session: Dict) -> Dict:
|
|
| 439 |
# Registrar announcement
|
| 440 |
if new_phase == "cross_examination":
|
| 441 |
registrar_note = build_round_announcement(session, new_round, "cross_examination")
|
|
|
|
|
|
|
| 442 |
elif new_round <= max_rounds:
|
| 443 |
registrar_note = build_round_announcement(session, new_round, "rounds")
|
|
|
|
| 444 |
else:
|
| 445 |
registrar_note = build_round_announcement(session, new_round, "closing")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 446 |
|
| 447 |
add_transcript_entry(
|
| 448 |
session_id=session_id,
|
|
@@ -452,6 +471,36 @@ def _handle_round(session_id: str, user_argument: str, session: Dict) -> Dict:
|
|
| 452 |
entry_type="announcement",
|
| 453 |
)
|
| 454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
# Detect concessions
|
| 456 |
new_concessions = _detect_concessions(user_argument, session_id, current_round)
|
| 457 |
|
|
@@ -478,12 +527,18 @@ def _handle_cross_exam_answer(
|
|
| 478 |
user_side = session["user_side"]
|
| 479 |
user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
|
| 480 |
|
| 481 |
-
#
|
| 482 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
e for e in session.get("transcript", [])
|
| 484 |
-
if e.get("
|
| 485 |
]
|
| 486 |
-
|
|
|
|
| 487 |
|
| 488 |
# Add user's answer
|
| 489 |
add_transcript_entry(
|
|
@@ -497,8 +552,8 @@ def _handle_cross_exam_answer(
|
|
| 497 |
# Detect concessions in answer
|
| 498 |
new_concessions = _detect_concessions(user_answer, session_id, session["current_round"])
|
| 499 |
|
| 500 |
-
# If more questions remaining (max 3), get next question
|
| 501 |
-
if
|
| 502 |
query = " ".join(session.get("legal_issues", []))
|
| 503 |
retrieved_precedents = _retrieve_for_court(query, session)
|
| 504 |
|
|
@@ -508,27 +563,30 @@ def _handle_cross_exam_answer(
|
|
| 508 |
|
| 509 |
cross_messages = build_cross_examination_prompt(
|
| 510 |
session=fresh_session,
|
| 511 |
-
question_number=
|
| 512 |
retrieved_context=combined_context,
|
| 513 |
)
|
| 514 |
|
| 515 |
try:
|
| 516 |
next_question = _call_llm(cross_messages)
|
| 517 |
except Exception as e:
|
| 518 |
-
next_question = f"Question {
|
| 519 |
|
| 520 |
add_transcript_entry(
|
| 521 |
session_id=session_id,
|
| 522 |
speaker="OPPOSING_COUNSEL",
|
| 523 |
role_label="RESPONDENT'S COUNSEL" if user_side == "petitioner" else "PETITIONER'S COUNSEL",
|
| 524 |
content=next_question,
|
| 525 |
-
entry_type="
|
| 526 |
)
|
| 527 |
|
|
|
|
|
|
|
|
|
|
| 528 |
return {
|
| 529 |
"opposing_response": next_question,
|
| 530 |
"judge_question": "",
|
| 531 |
-
"registrar_note": f"Question {
|
| 532 |
"trap_detected": False,
|
| 533 |
"trap_warning": "",
|
| 534 |
"new_concessions": new_concessions,
|
|
@@ -541,7 +599,8 @@ def _handle_cross_exam_answer(
|
|
| 541 |
else:
|
| 542 |
# Cross-examination complete — advance to closing
|
| 543 |
advance_phase(session_id)
|
| 544 |
-
|
|
|
|
| 545 |
|
| 546 |
add_transcript_entry(
|
| 547 |
session_id=session_id,
|
|
@@ -551,6 +610,30 @@ def _handle_cross_exam_answer(
|
|
| 551 |
entry_type="announcement",
|
| 552 |
)
|
| 553 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
return {
|
| 555 |
"opposing_response": "",
|
| 556 |
"judge_question": "",
|
|
@@ -558,7 +641,7 @@ def _handle_cross_exam_answer(
|
|
| 558 |
"trap_detected": False,
|
| 559 |
"trap_warning": "",
|
| 560 |
"new_concessions": new_concessions,
|
| 561 |
-
"round_number":
|
| 562 |
"phase": "closing",
|
| 563 |
"cross_exam_complete": True,
|
| 564 |
"session_ended": False,
|
|
@@ -566,11 +649,27 @@ def _handle_cross_exam_answer(
|
|
| 566 |
|
| 567 |
|
| 568 |
def _handle_closing(session_id: str, user_closing: str, session: Dict) -> Dict:
|
| 569 |
-
"""Handle closing arguments from both sides, then generate analysis.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
|
| 571 |
user_side = session["user_side"]
|
| 572 |
user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
|
| 573 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 574 |
# Add user's closing
|
| 575 |
add_transcript_entry(
|
| 576 |
session_id=session_id,
|
|
@@ -582,33 +681,68 @@ def _handle_closing(session_id: str, user_closing: str, session: Dict) -> Dict:
|
|
| 582 |
|
| 583 |
fresh_session = get_session(session_id)
|
| 584 |
|
| 585 |
-
#
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
|
| 595 |
-
|
| 596 |
-
session_id=session_id,
|
| 597 |
-
speaker="OPPOSING_COUNSEL",
|
| 598 |
-
role_label="RESPONDENT'S COUNSEL" if user_side == "petitioner" else "PETITIONER'S COUNSEL",
|
| 599 |
-
content=opposing_closing,
|
| 600 |
-
entry_type="closing_argument",
|
| 601 |
-
)
|
| 602 |
-
|
| 603 |
-
# Judge's final observations
|
| 604 |
fresh_session = get_session(session_id)
|
| 605 |
judge_closing_messages = build_judge_closing_prompt(fresh_session)
|
| 606 |
try:
|
| 607 |
judge_final = _call_llm(judge_closing_messages)
|
| 608 |
except Exception as e:
|
| 609 |
judge_final = (
|
| 610 |
-
"The court has heard submissions from both sides. "
|
| 611 |
-
"The matter is reserved for orders."
|
| 612 |
)
|
| 613 |
|
| 614 |
add_transcript_entry(
|
|
@@ -632,7 +766,7 @@ def _handle_closing(session_id: str, user_closing: str, session: Dict) -> Dict:
|
|
| 632 |
advance_phase(session_id)
|
| 633 |
|
| 634 |
return {
|
| 635 |
-
"opposing_response":
|
| 636 |
"judge_question": judge_final,
|
| 637 |
"registrar_note": registrar_final,
|
| 638 |
"trap_detected": False,
|
|
@@ -687,11 +821,16 @@ def process_objection(
|
|
| 687 |
objection_type: str,
|
| 688 |
objection_text: str,
|
| 689 |
) -> Dict:
|
| 690 |
-
"""Handle a user-raised objection."""
|
| 691 |
session = get_session(session_id)
|
| 692 |
if not session:
|
| 693 |
return {"error": "Session not found"}
|
| 694 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 695 |
# Get what the objection is about from last transcript entry
|
| 696 |
transcript = session.get("transcript", [])
|
| 697 |
last_entry = transcript[-1] if transcript else {}
|
|
@@ -722,9 +861,14 @@ def process_objection(
|
|
| 722 |
|
| 723 |
sustained = "sustained" in ruling.lower()
|
| 724 |
|
|
|
|
|
|
|
|
|
|
| 725 |
return {
|
| 726 |
"ruling": ruling,
|
| 727 |
"sustained": sustained,
|
|
|
|
|
|
|
| 728 |
}
|
| 729 |
|
| 730 |
|
|
|
|
| 197 |
|
| 198 |
phase = session["phase"]
|
| 199 |
|
| 200 |
+
# Validate user is allowed to submit
|
| 201 |
+
if phase not in ["briefing", "rounds", "cross_examination", "closing"]:
|
| 202 |
+
return {"error": f"Session is in {phase} phase. Cannot accept submissions."}
|
| 203 |
+
|
| 204 |
# Route to appropriate handler
|
| 205 |
try:
|
| 206 |
if phase == "briefing":
|
|
|
|
| 323 |
user_fell_in=False,
|
| 324 |
)
|
| 325 |
|
| 326 |
+
# UPDATE: After briefing, user is now in Round 1 and should submit their argument
|
| 327 |
+
update_session(session_id, {"awaiting_action": "user"})
|
| 328 |
+
|
| 329 |
return {
|
| 330 |
"opposing_response": opposing_response,
|
| 331 |
"judge_question": judge_question,
|
|
|
|
| 347 |
user_side = session["user_side"]
|
| 348 |
user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
|
| 349 |
|
| 350 |
+
# TURN VALIDATION: Only user should be able to submit arguments in "rounds" phase
|
| 351 |
+
awaiting = session.get("awaiting_action", "user")
|
| 352 |
+
if awaiting != "user":
|
| 353 |
+
return {"error": f"It is currently waiting for {awaiting}'s submission. You cannot speak now."}
|
| 354 |
+
|
| 355 |
# Add user argument to transcript
|
| 356 |
add_transcript_entry(
|
| 357 |
session_id=session_id,
|
|
|
|
| 451 |
# Registrar announcement
|
| 452 |
if new_phase == "cross_examination":
|
| 453 |
registrar_note = build_round_announcement(session, new_round, "cross_examination")
|
| 454 |
+
# First cross-exam question will be generated, user will answer it
|
| 455 |
+
next_awaiting = "user"
|
| 456 |
elif new_round <= max_rounds:
|
| 457 |
registrar_note = build_round_announcement(session, new_round, "rounds")
|
| 458 |
+
next_awaiting = "user" # User gets to submit next round argument
|
| 459 |
else:
|
| 460 |
registrar_note = build_round_announcement(session, new_round, "closing")
|
| 461 |
+
next_awaiting = "user" # User gives closing arguments
|
| 462 |
+
|
| 463 |
+
# UPDATE: Mark that awaiting_action should be set AFTER round advances
|
| 464 |
+
update_session(session_id, {"awaiting_action": next_awaiting})
|
| 465 |
|
| 466 |
add_transcript_entry(
|
| 467 |
session_id=session_id,
|
|
|
|
| 471 |
entry_type="announcement",
|
| 472 |
)
|
| 473 |
|
| 474 |
+
# If transitioning to cross-examination, generate the first cross-exam question now
|
| 475 |
+
if new_phase == "cross_examination":
|
| 476 |
+
fresh_session = get_session(session_id)
|
| 477 |
+
|
| 478 |
+
# Retrieve precedents for cross-exam context
|
| 479 |
+
combined_context = _build_full_context(fresh_session)
|
| 480 |
+
|
| 481 |
+
cross_exam_messages = build_cross_examination_prompt(
|
| 482 |
+
session=fresh_session,
|
| 483 |
+
question_number=1,
|
| 484 |
+
retrieved_context=combined_context,
|
| 485 |
+
)
|
| 486 |
+
try:
|
| 487 |
+
first_cross_question = _call_llm(cross_exam_messages)
|
| 488 |
+
except Exception as e:
|
| 489 |
+
logger.error(f"Cross-exam question generation failed: {e}")
|
| 490 |
+
first_cross_question = (
|
| 491 |
+
"Counsel, in your submitted brief, you stated that [key assertion]. "
|
| 492 |
+
"Can you elaborate on the legal precedent supporting this position?"
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
add_transcript_entry(
|
| 496 |
+
session_id=session_id,
|
| 497 |
+
speaker="OPPOSING_COUNSEL",
|
| 498 |
+
role_label="RESPONDENT'S COUNSEL" if session["user_side"] == "petitioner" else "PETITIONER'S COUNSEL",
|
| 499 |
+
content=first_cross_question,
|
| 500 |
+
entry_type="cross_exam_question",
|
| 501 |
+
metadata={"question_number": 1},
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
# Detect concessions
|
| 505 |
new_concessions = _detect_concessions(user_argument, session_id, current_round)
|
| 506 |
|
|
|
|
| 527 |
user_side = session["user_side"]
|
| 528 |
user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
|
| 529 |
|
| 530 |
+
# TURN VALIDATION: Only user should answer during cross-exam
|
| 531 |
+
awaiting = session.get("awaiting_action", "user")
|
| 532 |
+
if awaiting != "user":
|
| 533 |
+
return {"error": f"Cannot submit answer now. Awaiting {awaiting}'s action."}
|
| 534 |
+
|
| 535 |
+
# Count how many QUESTIONS have been asked by opposing counsel
|
| 536 |
+
all_questions = [
|
| 537 |
e for e in session.get("transcript", [])
|
| 538 |
+
if e.get("speaker") == "OPPOSING_COUNSEL" and e.get("entry_type") in ["cross_exam_question", "question", "answer_request"]
|
| 539 |
]
|
| 540 |
+
current_question_number = len(all_questions) # This is the question the user is answering
|
| 541 |
+
next_question_number = current_question_number + 1 # This will be the next question if there is one
|
| 542 |
|
| 543 |
# Add user's answer
|
| 544 |
add_transcript_entry(
|
|
|
|
| 552 |
# Detect concessions in answer
|
| 553 |
new_concessions = _detect_concessions(user_answer, session_id, session["current_round"])
|
| 554 |
|
| 555 |
+
# If more questions remaining (max 3 total), get next question
|
| 556 |
+
if next_question_number <= 3:
|
| 557 |
query = " ".join(session.get("legal_issues", []))
|
| 558 |
retrieved_precedents = _retrieve_for_court(query, session)
|
| 559 |
|
|
|
|
| 563 |
|
| 564 |
cross_messages = build_cross_examination_prompt(
|
| 565 |
session=fresh_session,
|
| 566 |
+
question_number=next_question_number,
|
| 567 |
retrieved_context=combined_context,
|
| 568 |
)
|
| 569 |
|
| 570 |
try:
|
| 571 |
next_question = _call_llm(cross_messages)
|
| 572 |
except Exception as e:
|
| 573 |
+
next_question = f"Question {next_question_number}: Counsel, would you agree that [question]?"
|
| 574 |
|
| 575 |
add_transcript_entry(
|
| 576 |
session_id=session_id,
|
| 577 |
speaker="OPPOSING_COUNSEL",
|
| 578 |
role_label="RESPONDENT'S COUNSEL" if user_side == "petitioner" else "PETITIONER'S COUNSEL",
|
| 579 |
content=next_question,
|
| 580 |
+
entry_type="cross_exam_question",
|
| 581 |
)
|
| 582 |
|
| 583 |
+
# UPDATE awaiting_action: Waiting for answer to next question
|
| 584 |
+
update_session(session_id, {"awaiting_action": "user"})
|
| 585 |
+
|
| 586 |
return {
|
| 587 |
"opposing_response": next_question,
|
| 588 |
"judge_question": "",
|
| 589 |
+
"registrar_note": f"Question {next_question_number} of 3.",
|
| 590 |
"trap_detected": False,
|
| 591 |
"trap_warning": "",
|
| 592 |
"new_concessions": new_concessions,
|
|
|
|
| 599 |
else:
|
| 600 |
# Cross-examination complete — advance to closing
|
| 601 |
advance_phase(session_id)
|
| 602 |
+
fresh_session = get_session(session_id)
|
| 603 |
+
registrar_note = build_round_announcement(fresh_session, fresh_session["current_round"], "closing")
|
| 604 |
|
| 605 |
add_transcript_entry(
|
| 606 |
session_id=session_id,
|
|
|
|
| 610 |
entry_type="announcement",
|
| 611 |
)
|
| 612 |
|
| 613 |
+
# If user is RESPONDENT, opposing (PETITIONER) closes first → user rebuts
|
| 614 |
+
# If user is PETITIONER, user closes first → opposing rebuts
|
| 615 |
+
if session["user_side"] == "respondent":
|
| 616 |
+
# Generate petitioner's closing first
|
| 617 |
+
closing_messages = build_opposing_closing_prompt(fresh_session)
|
| 618 |
+
try:
|
| 619 |
+
opposing_closing = _call_llm(closing_messages)
|
| 620 |
+
except Exception as e:
|
| 621 |
+
opposing_closing = (
|
| 622 |
+
"My Lords, the arguments advanced by Respondent's Counsel are fundamentally flawed. "
|
| 623 |
+
"For the reasons submitted, we pray for relief in the Petition."
|
| 624 |
+
)
|
| 625 |
+
|
| 626 |
+
add_transcript_entry(
|
| 627 |
+
session_id=session_id,
|
| 628 |
+
speaker="OPPOSING_COUNSEL",
|
| 629 |
+
role_label="PETITIONER'S COUNSEL",
|
| 630 |
+
content=opposing_closing,
|
| 631 |
+
entry_type="closing_argument",
|
| 632 |
+
)
|
| 633 |
+
|
| 634 |
+
# UPDATE awaiting_action: User gets to submit their closing
|
| 635 |
+
update_session(session_id, {"awaiting_action": "user"})
|
| 636 |
+
|
| 637 |
return {
|
| 638 |
"opposing_response": "",
|
| 639 |
"judge_question": "",
|
|
|
|
| 641 |
"trap_detected": False,
|
| 642 |
"trap_warning": "",
|
| 643 |
"new_concessions": new_concessions,
|
| 644 |
+
"round_number": fresh_session["current_round"],
|
| 645 |
"phase": "closing",
|
| 646 |
"cross_exam_complete": True,
|
| 647 |
"session_ended": False,
|
|
|
|
| 649 |
|
| 650 |
|
| 651 |
def _handle_closing(session_id: str, user_closing: str, session: Dict) -> Dict:
|
| 652 |
+
"""Handle closing arguments from both sides, then generate analysis.
|
| 653 |
+
|
| 654 |
+
In proper moot court procedure:
|
| 655 |
+
- Petitioner's counsel gives closing first
|
| 656 |
+
- Respondent's counsel gives rebuttal closing
|
| 657 |
+
- Petitioner does NOT get final rebuttal
|
| 658 |
+
|
| 659 |
+
This handler manages both sides appropriately based on user_side.
|
| 660 |
+
"""
|
| 661 |
|
| 662 |
user_side = session["user_side"]
|
| 663 |
user_label = "PETITIONER'S COUNSEL" if user_side == "petitioner" else "RESPONDENT'S COUNSEL"
|
| 664 |
|
| 665 |
+
# Check if this is first or second closing
|
| 666 |
+
closing_count = len([e for e in session.get("transcript", []) if e.get("entry_type") == "closing_argument"])
|
| 667 |
+
|
| 668 |
+
# TURN VALIDATION
|
| 669 |
+
awaiting = session.get("awaiting_action", "user")
|
| 670 |
+
if awaiting != "user":
|
| 671 |
+
return {"error": f"Cannot submit closing now. It is {awaiting}'s turn."}
|
| 672 |
+
|
| 673 |
# Add user's closing
|
| 674 |
add_transcript_entry(
|
| 675 |
session_id=session_id,
|
|
|
|
| 681 |
|
| 682 |
fresh_session = get_session(session_id)
|
| 683 |
|
| 684 |
+
# If user is petitioner (first closing), get opposing rebuttal
|
| 685 |
+
# If user is respondent, we're done and can finalize
|
| 686 |
+
if user_side == "petitioner":
|
| 687 |
+
# Get respondent's rebuttal closing
|
| 688 |
+
closing_messages = build_opposing_closing_prompt(fresh_session)
|
| 689 |
+
try:
|
| 690 |
+
opposing_closing = _call_llm(closing_messages)
|
| 691 |
+
except Exception as e:
|
| 692 |
+
opposing_closing = (
|
| 693 |
+
"My Lords, with respect, the submissions of the Petitioner's Counsel "
|
| 694 |
+
"are fundamentally flawed. For the reasons we have advanced, the petition should be dismissed."
|
| 695 |
+
)
|
| 696 |
+
|
| 697 |
+
add_transcript_entry(
|
| 698 |
+
session_id=session_id,
|
| 699 |
+
speaker="OPPOSING_COUNSEL",
|
| 700 |
+
role_label="RESPONDENT'S COUNSEL",
|
| 701 |
+
content=opposing_closing,
|
| 702 |
+
entry_type="closing_argument",
|
| 703 |
)
|
| 704 |
+
|
| 705 |
+
# Mark that session is ending
|
| 706 |
+
update_session(session_id, {"awaiting_action": "judge"})
|
| 707 |
+
|
| 708 |
+
intermediate_return = {
|
| 709 |
+
"opposing_response": opposing_closing,
|
| 710 |
+
"judge_question": "",
|
| 711 |
+
"registrar_note": "Respondent's counsel has concluded. The matter is now with the Court.",
|
| 712 |
+
"trap_detected": False,
|
| 713 |
+
"trap_warning": "",
|
| 714 |
+
"new_concessions": [],
|
| 715 |
+
"round_number": session["current_round"],
|
| 716 |
+
"phase": "closing",
|
| 717 |
+
"closing_step": "second_closing_done",
|
| 718 |
+
"ready_for_analysis": False,
|
| 719 |
+
"session_ended": False,
|
| 720 |
+
}
|
| 721 |
+
else:
|
| 722 |
+
# User is respondent and just gave rebuttal — session is complete
|
| 723 |
+
update_session(session_id, {"awaiting_action": "judge"})
|
| 724 |
+
intermediate_return = {
|
| 725 |
+
"opposing_response": "",
|
| 726 |
+
"judge_question": "",
|
| 727 |
+
"registrar_note": "Respondent's counsel has concluded. The matter is now with the Court.",
|
| 728 |
+
"trap_detected": False,
|
| 729 |
+
"trap_warning": "",
|
| 730 |
+
"new_concessions": [],
|
| 731 |
+
"round_number": session["current_round"],
|
| 732 |
+
"phase": "closing",
|
| 733 |
+
"closing_step": "respondent_closing_done",
|
| 734 |
+
"ready_for_analysis": True,
|
| 735 |
+
"session_ended": False,
|
| 736 |
+
}
|
| 737 |
|
| 738 |
+
# Get judge's final observations
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
fresh_session = get_session(session_id)
|
| 740 |
judge_closing_messages = build_judge_closing_prompt(fresh_session)
|
| 741 |
try:
|
| 742 |
judge_final = _call_llm(judge_closing_messages)
|
| 743 |
except Exception as e:
|
| 744 |
judge_final = (
|
| 745 |
+
"The court has heard submissions from both sides. The judgment is reserved."
|
|
|
|
| 746 |
)
|
| 747 |
|
| 748 |
add_transcript_entry(
|
|
|
|
| 766 |
advance_phase(session_id)
|
| 767 |
|
| 768 |
return {
|
| 769 |
+
"opposing_response": intermediate_return["opposing_response"],
|
| 770 |
"judge_question": judge_final,
|
| 771 |
"registrar_note": registrar_final,
|
| 772 |
"trap_detected": False,
|
|
|
|
| 821 |
objection_type: str,
|
| 822 |
objection_text: str,
|
| 823 |
) -> Dict:
|
| 824 |
+
"""Handle a user-raised objection. Pauses round and requests judge ruling."""
|
| 825 |
session = get_session(session_id)
|
| 826 |
if not session:
|
| 827 |
return {"error": "Session not found"}
|
| 828 |
|
| 829 |
+
# Objection pauses the round — mark round as "objection_pending"
|
| 830 |
+
phase = session.get("phase", "rounds")
|
| 831 |
+
if phase not in ["rounds", "cross_examination"]:
|
| 832 |
+
return {"error": "Cannot raise objection in current phase"}
|
| 833 |
+
|
| 834 |
# Get what the objection is about from last transcript entry
|
| 835 |
transcript = session.get("transcript", [])
|
| 836 |
last_entry = transcript[-1] if transcript else {}
|
|
|
|
| 861 |
|
| 862 |
sustained = "sustained" in ruling.lower()
|
| 863 |
|
| 864 |
+
# After ruling, resume normal flow — mark awaiting_action as "user" to continue
|
| 865 |
+
update_session(session_id, {"awaiting_action": "user"})
|
| 866 |
+
|
| 867 |
return {
|
| 868 |
"ruling": ruling,
|
| 869 |
"sustained": sustained,
|
| 870 |
+
"objection_resolved": True,
|
| 871 |
+
"can_continue": True,
|
| 872 |
}
|
| 873 |
|
| 874 |
|