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>
83 lines
3.7 KiB
HTML
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>
|