supercode168/enterprise-workflow-ai-agent
0
1from __future__ import annotations2 3import html4import json5import sys6from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer7from pathlib import Path8from urllib.parse import parse_qs, urlparse9 10from agent_core import (11 dashboard_metrics,12 investment_research_workflow,13 load_csv,14 SAMPLE_CSV,15 compliance_review_workflow,16 triage,17)18 19 20HOST = "127.0.0.1"21PORT = 876522 23 24def page_shell(body: str) -> bytes:25 return f"""<!doctype html>26<html lang="en">27<head>28 <meta charset="utf-8">29 <meta name="viewport" content="width=device-width, initial-scale=1">30 <title>Enterprise Workflow AI Agent</title>31 <style>32 :root {{33 --ink: #172033;34 --muted: #627089;35 --line: #dbe3ef;36 --fill: #f6f8fb;37 --accent: #2266cc;38 --accent-dark: #174a93;39 --warn: #9b5a00;40 --good: #146c43;41 }}42 * {{ box-sizing: border-box; }}43 body {{44 margin: 0;45 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;46 color: var(--ink);47 background: #ffffff;48 }}49 header {{50 border-bottom: 1px solid var(--line);51 padding: 22px 32px 18px;52 }}53 h1 {{ margin: 0 0 6px; font-size: 28px; letter-spacing: 0; }}54 h2 {{ margin: 28px 0 12px; font-size: 18px; }}55 p {{ line-height: 1.5; }}56 .sub {{ color: var(--muted); margin: 0; }}57 main {{ max-width: 1180px; margin: 0 auto; padding: 22px 28px 40px; }}58 .grid {{ display: grid; gap: 16px; }}59 .metrics {{ grid-template-columns: repeat(4, minmax(0, 1fr)); }}60 .two {{ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }}61 .card {{62 border: 1px solid var(--line);63 border-radius: 8px;64 padding: 16px;65 background: #fff;66 }}67 .metric-value {{ font-size: 28px; font-weight: 700; color: var(--accent-dark); }}68 .metric-label {{ color: var(--muted); font-size: 13px; margin-top: 4px; }}69 textarea {{70 width: 100%;71 min-height: 118px;72 padding: 12px;73 border: 1px solid var(--line);74 border-radius: 6px;75 font: inherit;76 resize: vertical;77 }}78 button {{79 margin-top: 10px;80 border: 0;81 border-radius: 6px;82 padding: 10px 14px;83 background: var(--accent);84 color: #fff;85 font-weight: 650;86 cursor: pointer;87 }}88 button:hover {{ background: var(--accent-dark); }}89 table {{ width: 100%; border-collapse: collapse; font-size: 14px; }}90 th, td {{ border-bottom: 1px solid var(--line); padding: 9px 8px; text-align: left; vertical-align: top; }}91 th {{ color: var(--muted); font-weight: 650; background: var(--fill); }}92 .pill {{ display: inline-block; padding: 3px 8px; border-radius: 999px; background: var(--fill); border: 1px solid var(--line); font-size: 12px; }}93 .high, .emergency {{ color: #8a1f11; font-weight: 700; }}94 .medium {{ color: var(--warn); font-weight: 700; }}95 .low {{ color: var(--good); font-weight: 700; }}96 ul {{ padding-left: 22px; }}97 @media (max-width: 840px) {{98 header {{ padding: 18px; }}99 main {{ padding: 18px; }}100 .metrics, .two {{ grid-template-columns: 1fr; }}101 }}102 </style>103</head>104<body>105 <header>106 <h1>Enterprise Workflow AI Agent</h1>107 <p class="sub">Service operations copilot: triage, retrieval, checklist generation, tenant reply drafting, and dashboarding.</p>108 </header>109 <main>{body}</main>110</body>111</html>""".encode("utf-8")112 113 114def render_dashboard(result=None, query_text: str = "") -> bytes:115 metrics = dashboard_metrics()116 categories = "".join(117 f"<tr><td>{html.escape(str(k))}</td><td>{v}</td></tr>" for k, v in metrics["by_category"][:8]118 )119 boroughs = "".join(120 f"<tr><td>{html.escape(str(k))}</td><td>{v}</td></tr>" for k, v in metrics["by_borough"][:8]121 )122 recent = "".join(123 "<tr>"124 f"<td>{html.escape(str(t['ticket_id']))}</td>"125 f"<td>{html.escape(str(t['complaint_type']))}</td>"126 f"<td>{html.escape(str(t['descriptor']))}</td>"127 f"<td>{html.escape(str(t['borough']))}</td>"128 f"<td>{html.escape(str(t['response_hours']))}</td>"129 "</tr>"130 for t in metrics["recent_tickets"]131 )132 result_html = ""133 if result:134 cases = "".join(135 "<tr>"136 f"<td>{html.escape(c['ticket_id'])}</td>"137 f"<td>{html.escape(c['complaint_type'])}</td>"138 f"<td>{html.escape(c['descriptor'])}</td>"139 f"<td>{html.escape(c['resolution_description'])}</td>"140 f"<td>{c['similarity']}</td>"141 "</tr>"142 for c in result.similar_cases143 )144 checklist = "".join(f"<li>{html.escape(item)}</li>" for item in result.checklist)145 missing = ", ".join(result.missing_information) if result.missing_information else "None"146 result_html = f"""147 <section class="card">148 <h2>Triage Result</h2>149 <p><span class="pill">Category</span> {html.escape(result.category)}150 <span class="pill">Urgency</span> <span class="{result.urgency}">{html.escape(result.urgency)}</span>151 <span class="pill">Team</span> {html.escape(result.responsible_team)}</p>152 <p><strong>Suggested next action:</strong> {html.escape(result.suggested_next_action)}</p>153 <p><strong>Missing information:</strong> {html.escape(missing)}</p>154 <h2>Technician Checklist</h2>155 <ul>{checklist}</ul>156 <h2>Tenant Reply Draft</h2>157 <p>{html.escape(result.tenant_reply)}</p>158 <h2>Similar Historical Cases</h2>159 <table><thead><tr><th>ID</th><th>Type</th><th>Descriptor</th><th>Resolution</th><th>Score</th></tr></thead><tbody>{cases}</tbody></table>160 </section>161 """162 body = f"""163 <section class="grid metrics">164 <div class="card"><div class="metric-value">{metrics["total_tickets"]}</div><div class="metric-label">Tickets in database</div></div>165 <div class="card"><div class="metric-value">{metrics["avg_response_hours"]}</div><div class="metric-label">Average response hours</div></div>166 <div class="card"><div class="metric-value">{len(metrics["by_category"])}</div><div class="metric-label">Complaint categories</div></div>167 <div class="card"><div class="metric-value">3</div><div class="metric-label">Enterprise scenarios planned</div></div>168 </section>169 170 <section class="card">171 <h2>Ticket Intake</h2>172 <form method="post" action="/triage">173 <textarea name="text" placeholder="Example: Tenant reports no hot water in unit 12B since this morning. There is a baby in the apartment.">{html.escape(query_text)}</textarea>174 <button type="submit">Run Triage</button>175 </form>176 </section>177 178 {result_html}179 180 <section class="grid two">181 <div class="card">182 <h2>Ticket Volume by Category</h2>183 <table><thead><tr><th>Category</th><th>Count</th></tr></thead><tbody>{categories}</tbody></table>184 </div>185 <div class="card">186 <h2>Ticket Volume by Borough</h2>187 <table><thead><tr><th>Borough</th><th>Count</th></tr></thead><tbody>{boroughs}</tbody></table>188 </div>189 </section>190 191 <section class="card">192 <h2>Recent Tickets</h2>193 <table><thead><tr><th>ID</th><th>Type</th><th>Descriptor</th><th>Borough</th><th>Response Hours</th></tr></thead><tbody>{recent}</tbody></table>194 </section>195 """196 return page_shell(body)197 198 199def render_document_workflow(kind: str, result=None, text: str = "") -> bytes:200 if kind == "investment":201 title = "Investment Research Copilot"202 subtitle = "Paste annual-report, company, market, or fund notes to generate an analyst-ready first pass."203 action = "/investment"204 placeholder = "Paste company report text, market notes, or fund description..."205 else:206 title = "Compliance Review Copilot"207 subtitle = "Paste contract, policy, or procedure text to extract obligations, risk flags, and review checklist."208 action = "/compliance"209 placeholder = "Paste contract or compliance policy text..."210 211 result_html = ""212 if result:213 fields = "".join(214 f"<tr><td>{html.escape(str(k))}</td><td>{html.escape(json.dumps(v, ensure_ascii=False))}</td></tr>"215 for k, v in result.extracted_fields.items()216 )217 risks = "".join(f"<li>{html.escape(item)}</li>" for item in result.risk_flags) or "<li>No obvious risk flags found in this short sample.</li>"218 checklist = "".join(f"<li>{html.escape(item)}</li>" for item in result.action_checklist)219 result_html = f"""220 <section class="card">221 <h2>Workflow Output</h2>222 <p><span class="pill">Workflow</span> {html.escape(result.workflow_type)}</p>223 <h2>Executive Summary</h2>224 <p>{html.escape(result.summary)}</p>225 <h2>Extracted Fields</h2>226 <table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>{fields}</tbody></table>227 <h2>Risk Flags</h2>228 <ul>{risks}</ul>229 <h2>Action Checklist</h2>230 <ul>{checklist}</ul>231 <h2>Stakeholder Draft</h2>232 <p>{html.escape(result.stakeholder_draft)}</p>233 </section>234 """235 236 body = f"""237 <section class="card">238 <p><a href="/">Service Operations</a> · <a href="/investment">Investment Research</a> · <a href="/compliance">Compliance Review</a></p>239 <h2>{title}</h2>240 <p>{subtitle}</p>241 <form method="post" action="{action}">242 <textarea name="text" placeholder="{html.escape(placeholder)}">{html.escape(text)}</textarea>243 <button type="submit">Run Workflow</button>244 </form>245 </section>246 {result_html}247 """248 return page_shell(body)249 250 251class Handler(BaseHTTPRequestHandler):252 def _send(self, data: bytes, content_type: str = "text/html; charset=utf-8") -> None:253 self.send_response(200)254 self.send_header("Content-Type", content_type)255 self.send_header("Content-Length", str(len(data)))256 self.end_headers()257 self.wfile.write(data)258 259 def do_GET(self) -> None:260 parsed = urlparse(self.path)261 if parsed.path == "/api/triage":262 text = parse_qs(parsed.query).get("text", [""])[0]263 result = triage(text)264 data = json.dumps(result.to_dict(), indent=2).encode("utf-8")265 self._send(data, "application/json; charset=utf-8")266 return267 if parsed.path == "/api/investment":268 text = parse_qs(parsed.query).get("text", [""])[0]269 data = json.dumps(investment_research_workflow(text).to_dict(), indent=2).encode("utf-8")270 self._send(data, "application/json; charset=utf-8")271 return272 if parsed.path == "/api/compliance":273 text = parse_qs(parsed.query).get("text", [""])[0]274 data = json.dumps(compliance_review_workflow(text).to_dict(), indent=2).encode("utf-8")275 self._send(data, "application/json; charset=utf-8")276 return277 if parsed.path == "/investment":278 self._send(render_document_workflow("investment"))279 return280 if parsed.path == "/compliance":281 self._send(render_document_workflow("compliance"))282 return283 self._send(render_dashboard())284 285 def do_POST(self) -> None:286 length = int(self.headers.get("Content-Length", "0"))287 raw = self.rfile.read(length).decode("utf-8")288 text = parse_qs(raw).get("text", [""])[0]289 if self.path == "/triage":290 self._send(render_dashboard(triage(text), text))291 return292 if self.path == "/investment":293 self._send(render_document_workflow("investment", investment_research_workflow(text), text))294 return295 if self.path == "/compliance":296 self._send(render_document_workflow("compliance", compliance_review_workflow(text), text))297 return298 self.send_error(404)299 300 301def main() -> None:302 if not Path("data/tickets.db").exists():303 load_csv(SAMPLE_CSV)304 port = int(sys.argv[1]) if len(sys.argv) > 1 else PORT305 server = ThreadingHTTPServer((HOST, port), Handler)306 print(f"Serving Enterprise Workflow AI Agent at http://{HOST}:{port}")307 server.serve_forever()308 309 310if __name__ == "__main__":311 main()312 