CCAI-Demo / mock_backend.py
Jordan Miller
Add demo trio personas and polish empty-state panel UX.
4944128
Raw
History Blame Contribute Delete
11.9 kB
"""Mock CCAI backend for UI rendering/screenshots ONLY.
Serves the exact JSON shapes the frontend expects (derived from the real
backend's api/personas.py, api/models.py, extra_personas.py, demo_questions.json)
plus a canned SSE panel for /api/chat/start so the multi-persona discussion UI
can be screenshotted without any real LLM/HANA access. Not used in production.
"""
import json, time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
PROVIDERS = [
{"id": "openai", "name": "OpenAI", "models": [
{"id": "gpt-5.4", "name": "GPT-5.4", "params": "Undisclosed"},
{"id": "gpt-4.1", "name": "GPT-4.1", "params": "Undisclosed"},
{"id": "gpt-4o", "name": "GPT-4o", "params": "~200B"},
{"id": "gpt-4o-mini", "name": "GPT-4o Mini", "params": "~8B"},
{"id": "o4-mini", "name": "o4-Mini", "params": "Undisclosed"},
]},
{"id": "gemini", "name": "Google Gemini", "models": [
{"id": "gemini-2.5-flash", "name": "Gemini 2.5 Flash", "params": "Undisclosed"},
{"id": "gemini-2.5-pro", "name": "Gemini 2.5 Pro", "params": "Undisclosed"},
]},
{"id": "mistral", "name": "Mistral", "models": [
{"id": "devstral-2512", "name": "Devstral2", "params": "123B"},
{"id": "mistral-small-2603", "name": "Mistral Small 4", "params": "119B"},
]},
{"id": "meta", "name": "Meta Llama", "models": [
{"id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", "name": "Llama 3.3 70B Turbo", "params": "70B"},
]},
{"id": "deepseek", "name": "DeepSeek", "models": [
{"id": "accounts/fireworks/models/deepseek-v3p1", "name": "DeepSeek V3.1", "params": "671B"},
]},
]
NEON_MODELS = [
{"model_id": "BrainForge/Security@2026.03.18", "name": "BrainForge/Security",
"personas": [
{"persona_name": "Athena", "enabled": True, "system_prompt": "You are Athena, a strategic advisor."},
{"persona_name": "Vanilla", "enabled": True, "system_prompt": "Plain assistant."},
]},
]
NEON_PERSONAS = [
{"participant_id": "neon:BrainForge/Security@2026.03.18:Athena", "kind": "neon",
"name": "Athena (Strategic Advisor)", "model_display": "Security",
"default_model_id": "neon:BrainForge/Security@2026.03.18:Athena",
"description": "Strategic advisor persona", "role_prompt": "You are Athena."},
]
EXTRA = [
{"participant_id": "extra_pragmatic_generalist", "name": "Pragmatic Finance Expert",
"default_model_id": "gpt-5.4", "model_display": "gpt-5.4", "kind": "extra", "role_prompt": "..."},
{"participant_id": "extra_skeptical_critic", "name": "Skeptical Philosopher",
"default_model_id": "gemini-2.5-flash", "model_display": "gemini-2.5-flash", "kind": "extra", "role_prompt": "..."},
{"participant_id": "extra_empathetic_humanist", "name": "Empathetic Historian",
"default_model_id": "devstral-2512", "model_display": "devstral-2512", "kind": "extra", "role_prompt": "..."},
{"participant_id": "extra_data_driven_analyst", "name": "Data-Driven Geologist",
"default_model_id": "meta-llama/Llama-3.3-70B-Instruct-Turbo",
"model_display": "Llama-3.3-70B-Instruct-Turbo", "kind": "extra", "role_prompt": "..."},
{"participant_id": "extra_elena_financial_strategist", "name": "Elena — Financial Strategist",
"default_model_id": "gpt-4.1", "model_display": "gpt-4.1", "kind": "extra", "role_prompt": "..."},
{"participant_id": "extra_marcus_technology_strategist", "name": "Marcus — Technology Strategist",
"default_model_id": "mistral-small-2603",
"model_display": "mistral-small-2603", "kind": "extra", "role_prompt": "..."},
{"participant_id": "extra_amira_security_advisor", "name": "Dr. Amira — Security & Privacy Advisor",
"default_model_id": "neon:BrainForge/Security@2026.05.13:CybersecurityExpert",
"model_display": "CybersecurityExpert", "kind": "extra", "role_prompt": "..."},
]
DEMO_QUESTIONS = json.load(open("backend/app/data/demo_questions.json"))["questions"]
FORMATS = {
"structures": [
{"id": "collaborative", "name": "Collaborative Discussion", "description": "Structured group reasoning toward consensus."},
{"id": "roberts_rules", "name": "Robert's Rules", "description": "Formal motion/second/vote procedure."},
],
"decisions": [
{"id": "consensus", "name": "Consensus", "description": "Seek agreement; majority report with dissent."},
{"id": "majority", "name": "Majority Vote", "description": "Simple majority."},
{"id": "ranked_choice", "name": "Ranked Choice", "description": "Ranked-choice tally."},
],
"default_structure_id": "collaborative",
"default_decision_id": "consensus",
}
def sse(event, data):
return f"event: {event}\ndata: {json.dumps(data)}\n\n".encode()
class H(BaseHTTPRequestHandler):
def log_message(self, *a): pass
def _json(self, obj, code=200):
body = json.dumps(obj).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
def do_OPTIONS(self):
self.send_response(204)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
def do_GET(self):
p = self.path.split("?")[0]
if p == "/api/models": return self._json({"neon_models": NEON_MODELS, "providers": PROVIDERS})
if p == "/api/personas": return self._json({"neon": NEON_PERSONAS, "extra": EXTRA})
if p == "/api/demo-questions": return self._json({"questions": DEMO_QUESTIONS})
if p == "/api/chat/orchestrator": return self._json({"model_id": "gpt-4o-mini"})
if p == "/api/chat/speed-priority": return self._json({"enabled": False})
if p == "/api/chat/conversation-formats": return self._json(FORMATS)
if p == "/api/auth/status": return self._json({"logged_in": False, "is_org_member": False, "remaining_conversations": 30})
if p == "/api/rate-limit/status": return self._json({"remaining": 30, "daily_limit": 30})
return self._json({}, 404)
def do_PUT(self):
self._read()
if self.path.startswith("/api/chat/orchestrator"): return self._json({"model_id": "gpt-4o-mini"})
if self.path.startswith("/api/chat/speed-priority"): return self._json({"enabled": False})
return self._json({})
def do_PATCH(self):
self._read(); return self._json({})
def _read(self):
n = int(self.headers.get("Content-Length", 0) or 0)
return self.rfile.read(n) if n else b""
def do_POST(self):
raw = self._read()
try: body = json.loads(raw or b"{}")
except Exception: body = {}
if self.path.startswith("/api/chat/suggest-model"):
return self._json({"recommended_model_id": "gemini-2.5-flash",
"rationale": "A philosophy-leaning critic benefits from a model with strong reasoning and concise argumentation; Gemini 2.5 Flash also diversifies the panel away from the GPT family already present."})
if self.path.startswith("/api/chat/generate-role-freeform") or self.path.startswith("/api/chat/generate-role"):
return self._json({"role_prompt": "You are " + (body.get("name") or "an expert") + ". (mock-generated role prompt for UI rendering)"})
if self.path.startswith("/api/chat/auto-select-participants"):
cands = body.get("candidates", [])
return self._json({"selected": [c["participant_id"] for c in cands[:body.get("count",3)]],
"rationale": "Picked the most topically relevant participants (mock)."})
if self.path.startswith("/api/chat/start"):
return self._sse_panel(body)
return self._json({})
def _sse_panel(self, body):
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Cache-Control", "no-cache")
self.end_headers()
parts = body.get("participants", [])
if len(parts) < 2:
parts = [{"participant_id": "extra_pragmatic_generalist", "name": "Pragmatic Finance Expert"},
{"participant_id": "extra_skeptical_critic", "name": "Skeptical Philosopher"},
{"participant_id": "extra_empathetic_humanist", "name": "Empathetic Historian"}]
q = body.get("question", "the question")
sid = "mock-" + str(int(time.time()))
roster = [{"participant_id": p["participant_id"], "name": p["name"],
"model_display": p.get("name")} for p in parts]
self.wfile.write(sse("session", {"session_id": sid, "participants": roster})); self.wfile.flush()
self.wfile.write(sse("status", {"message": "Phase 1: Initial Opinions"})); self.wfile.flush()
op = {
parts[0]["name"]: f"On '{q[:50]}...', my first take is to weigh cost against benefit. The numbers have to clear a return-on-investment bar before anything else.",
parts[1]["name"]: "I'd challenge the framing. Before we optimize, what assumption are we all making that nobody has examined? Let's pressure-test the premise.",
}
if len(parts) > 2:
op[parts[2]["name"]] = "I want to center the human stakes. Whoever is on the receiving end of this decision matters as much as the spreadsheet."
for i, p in enumerate(parts):
mid = f"m{i}"
txt = op.get(p["name"], "Here is my initial opinion, grounded in my area of expertise.")
self.wfile.write(sse("message", {"message_id": mid, "role": "participant",
"speaker_id": p["participant_id"], "speaker_name": p["name"],
"text": txt, "phase": "initial_opinions", "elapsed_seconds": 2.1 + i,
"timestamp": time.time()})); self.wfile.flush()
self.wfile.write(sse("orchestrator", {"message_id": "o1", "role": "orchestrator",
"kind": "status", "text": "All participants have given initial opinions. Moving to the critique phase, where each will respond to the others.",
"timestamp": time.time()})); self.wfile.flush()
self.wfile.write(sse("message", {"message_id": "m10", "role": "participant",
"speaker_id": parts[1]["participant_id"], "speaker_name": parts[1]["name"],
"text": "Responding directly to the finance framing: ROI is necessary but not sufficient. A positive ROI on paper can still be the wrong call if it erodes trust.",
"addressed_to": parts[0]["participant_id"], "replying_to": [parts[0]["participant_id"]],
"phase": "critique", "elapsed_seconds": 3.4, "timestamp": time.time()})); self.wfile.flush()
self.wfile.write(sse("orchestrator", {"message_id": "o2", "role": "orchestrator",
"kind": "majority_report",
"text": "**Majority Report.** The panel converges on a staged approach: validate ROI on a small pilot first, while explicitly protecting the human/trust factors the Historian raised. One participant dissents, preferring a faster full commitment.",
"timestamp": time.time()})); self.wfile.flush()
self.wfile.write(sse("system", {"text": "End of Chat"})); self.wfile.flush()
self.wfile.write(sse("done", {})); self.wfile.flush()
if __name__ == "__main__":
print("Mock backend on :8000")
ThreadingHTTPServer(("127.0.0.1", 8000), H).serve_forever()