Files
hncb-fusion-deid-demo/ui/index.html
Conan Scott 78d67a2469 Implement reversible PII de-identification round trip (T1–T7)
Build the AWS side of the HNCB demo end to end (region ap-southeast-1):

- T1 /tokenize + T2 /restore: Lambdas behind a public API Gateway (shared-secret
  auth), Presidio detection, random per-request tokens, DynamoDB vault; overlap
  resolution so a ROC ID stays TW_ROC_ID.
- T3: Presidio made private (SG-locked to the tokenize Lambda in-VPC; DynamoDB
  gateway endpoint); only /tokenize + /restore are public.
- T4: RAG Lambda registered as an MCP tool on an AgentCore Gateway (AWS_IAM/SigV4);
  agentcore_setup.sh + a SigV4 MCP invoke test.
- T5: Strands agent deployed to AgentCore Runtime; SigV4 gateway auth, apac
  inference profile, pinned deps.
- T6: advisor UI on S3+CloudFront with a Fusion-less demo orchestrator (/demo)
  chaining tokenize -> runtime -> restore.
- T7: README runbook + trace check; teardown deletes gateway/runtime/memory/ECR.

Verified live: the cloud AgentCore/Bedrock trace shows only tokens, never the
real name. Secrets stay in gitignored local.auto.tfvars.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 17:10:58 +10:00

83 lines
3.7 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>HNCB · Advisor Assistant (de-identification demo)</title>
<style>
:root { font-family: system-ui, -apple-system, "Noto Sans TC", sans-serif; }
body { margin: 0; background: #0f172a; color: #e2e8f0; }
.wrap { max-width: 820px; margin: 40px auto; padding: 0 20px; }
h1 { font-size: 20px; font-weight: 650; }
.sub { color: #94a3b8; font-size: 13px; margin-top: -6px; }
textarea { width: 100%; box-sizing: border-box; min-height: 78px; padding: 12px;
border-radius: 10px; border: 1px solid #334155; background: #1e293b; color: #e2e8f0; font-size: 14px; }
button { margin-top: 12px; padding: 10px 18px; border: 0; border-radius: 10px;
background: #2563eb; color: white; font-weight: 600; cursor: pointer; }
button:disabled { opacity: .5; cursor: default; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 22px; }
.card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 14px; }
.card h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; margin: 0 0 8px; color: #94a3b8; }
.restored { border-color: #16a34a; }
.cloud { border-color: #d97706; }
pre { white-space: pre-wrap; word-break: break-word; font-size: 13px; margin: 0; }
.tag { font-size: 11px; color: #f59e0b; }
</style>
</head>
<body>
<div class="wrap">
<h1>HNCB · Advisor Assistant</h1>
<p class="sub">Reversible PII de-identification through Amplify AI Gateway (Fusion). Everything the cloud sees is tokenized.</p>
<textarea id="q">請幫我整理王小明最近三個月的理財往來,並給我下次拜訪話術。</textarea><br />
<button id="go" onclick="run()">Send to gateway</button>
<div class="grid">
<div class="card restored">
<h2>Advisor sees (identity restored on-prem)</h2>
<pre id="final"></pre>
</div>
<div class="card cloud">
<h2>What the cloud actually saw <span class="tag">tokenized</span></h2>
<pre id="deid"></pre>
<h2 style="margin-top:12px">Agent talking points <span class="tag">tokenized</span></h2>
<pre id="agent"></pre>
</div>
</div>
</div>
<!-- Injected at deploy time (S3) with the live demo orchestrator URL. -->
<script src="config.js"></script>
<script>
// Fusion is shared SaaS; for a self-contained dry run the UI calls the demo
// orchestrator (/demo), which runs tokenize -> agent -> restore server-side.
// In production, point this at the Fusion gateway entrypoint instead.
const GATEWAY_URL = window.DEMO_ENDPOINT || "http://REPLACE_ME_FUSION_HOST:8080/advisor";
async function run() {
const btn = document.getElementById("go");
btn.disabled = true;
document.getElementById("final").textContent = "…thinking…";
document.getElementById("deid").textContent = "…";
document.getElementById("agent").textContent = "…";
try {
const res = await fetch(GATEWAY_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: document.getElementById("q").value }),
});
const data = await res.json();
// Orchestrator (or Fusion) returns { final, deidentified_prompt, agent_tokenized }.
document.getElementById("final").textContent = data.final || JSON.stringify(data, null, 2);
document.getElementById("deid").textContent = data.deidentified_prompt || "(no de-identified prompt echoed)";
document.getElementById("agent").textContent = data.agent_tokenized || "(no agent answer)";
} catch (e) {
document.getElementById("final").textContent = "Error: " + e.message;
} finally {
btn.disabled = false;
}
}
</script>
</body>
</html>