update
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
const logEl = document.querySelector("#log");
|
||||
const toolListEl = document.querySelector("#tool-list");
|
||||
const nativeStatusEl = document.querySelector("#native-status");
|
||||
const orderStateEl = document.querySelector("#order-state");
|
||||
|
||||
const nativeModelContext = window.navigator.modelContext;
|
||||
const registry = new Map();
|
||||
|
||||
const demoOrders = {
|
||||
"CHR-2026-0614": {
|
||||
orderId: "CHR-2026-0614",
|
||||
status: "delivered",
|
||||
risk: "missing_accessory",
|
||||
customerTier: "enterprise",
|
||||
nextAction: "open_refund_review",
|
||||
},
|
||||
};
|
||||
|
||||
const modelContext = nativeModelContext ?? createMockModelContext();
|
||||
|
||||
nativeStatusEl.textContent = nativeModelContext
|
||||
? "Native WebMCP detected"
|
||||
: "Mock WebMCP runtime";
|
||||
nativeStatusEl.className = `pill ${nativeModelContext ? "native" : "mock"}`;
|
||||
|
||||
registerDeclarativeForms();
|
||||
registerImperativeTools();
|
||||
renderTools();
|
||||
log("runtime.ready", {
|
||||
nativeWebMcp: Boolean(nativeModelContext),
|
||||
registeredTools: Array.from(registry.keys()),
|
||||
});
|
||||
|
||||
document.querySelector("#ticket-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const toolName = form.getAttribute("toolname");
|
||||
const payload = Object.fromEntries(new FormData(form).entries());
|
||||
const result = await runTool(toolName, payload);
|
||||
log("form.submit", result);
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-run-tool]").forEach((button) => {
|
||||
button.addEventListener("click", async () => {
|
||||
const name = button.getAttribute("data-run-tool");
|
||||
const input = sampleInputFor(name);
|
||||
const result = await runTool(name, input);
|
||||
log(`tool.${name}`, result);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector("#run-workflow").addEventListener("click", async () => {
|
||||
log("agent.plan", {
|
||||
task: "客户反馈缺少配件,请查询订单、判断是否进入退款审核,并生成回复草稿。",
|
||||
});
|
||||
const order = await runTool("lookup_order_status", { orderId: "CHR-2026-0614" });
|
||||
const refund = await runTool("approve_refund_review", {
|
||||
orderId: order.orderId,
|
||||
reason: "missing_accessory",
|
||||
});
|
||||
const reply = await runTool("draft_customer_reply", {
|
||||
email: "dev@example.com",
|
||||
category: "refund",
|
||||
orderId: order.orderId,
|
||||
});
|
||||
log("agent.workflow.complete", { order, refund, reply });
|
||||
});
|
||||
|
||||
function createMockModelContext() {
|
||||
return {
|
||||
registerTool(tool) {
|
||||
registry.set(tool.name, tool);
|
||||
return Promise.resolve({ name: tool.name, mock: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function registerDeclarativeForms() {
|
||||
document.querySelectorAll("form[toolname]").forEach((form) => {
|
||||
const name = form.getAttribute("toolname");
|
||||
const description = form.getAttribute("tooldescription") || `Submit ${name}`;
|
||||
const inputSchema = schemaFromForm(form);
|
||||
|
||||
registerTool({
|
||||
source: "declarative",
|
||||
name,
|
||||
description,
|
||||
inputSchema,
|
||||
execute: async (input) => {
|
||||
const ticketId = `SUP-${Date.now().toString().slice(-6)}`;
|
||||
return {
|
||||
ticketId,
|
||||
status: "created",
|
||||
summary: `${input.category} issue for ${input.email}`,
|
||||
visibleUpdate: "工单已创建,页面保留原有人类操作流程。",
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerImperativeTools() {
|
||||
registerTool({
|
||||
source: "imperative",
|
||||
name: "lookup_order_status",
|
||||
description: "Look up order status, risk and recommended next action by order id.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
orderId: { type: "string", description: "Order id shown in the support workspace." },
|
||||
},
|
||||
required: ["orderId"],
|
||||
},
|
||||
execute: async ({ orderId }) => {
|
||||
const order = demoOrders[orderId] ?? {
|
||||
orderId,
|
||||
status: "unknown",
|
||||
risk: "not_found",
|
||||
customerTier: "unknown",
|
||||
nextAction: "ask_user_for_more_information",
|
||||
};
|
||||
orderStateEl.textContent = order.nextAction;
|
||||
return order;
|
||||
},
|
||||
});
|
||||
|
||||
registerTool({
|
||||
source: "imperative",
|
||||
name: "approve_refund_review",
|
||||
description: "Move an eligible order into refund review after checking business policy.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
orderId: { type: "string" },
|
||||
reason: {
|
||||
type: "string",
|
||||
enum: ["missing_accessory", "damaged_item", "delivery_delay"],
|
||||
},
|
||||
},
|
||||
required: ["orderId", "reason"],
|
||||
},
|
||||
execute: async ({ orderId, reason }) => {
|
||||
orderStateEl.textContent = "已进入退款审核";
|
||||
return {
|
||||
orderId,
|
||||
reason,
|
||||
approvedForReview: true,
|
||||
needsHumanConfirmation: true,
|
||||
message: "敏感动作只进入审核,不直接退款。",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
registerTool({
|
||||
source: "imperative",
|
||||
name: "draft_customer_reply",
|
||||
description: "Draft a support reply based on customer issue category and order id.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
email: { type: "string", format: "email" },
|
||||
category: { type: "string", enum: ["refund", "delivery", "account"] },
|
||||
orderId: { type: "string" },
|
||||
},
|
||||
required: ["email", "category", "orderId"],
|
||||
},
|
||||
execute: async ({ email, category, orderId }) => ({
|
||||
to: email,
|
||||
category,
|
||||
orderId,
|
||||
draft:
|
||||
"我们已经定位到该订单的缺件问题,并将它提交到退款审核。审核完成前不会自动扣改账户,请等待人工确认。",
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function registerTool(tool) {
|
||||
registry.set(tool.name, tool);
|
||||
const nativePayload = {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
execute: tool.execute,
|
||||
};
|
||||
|
||||
try {
|
||||
modelContext.registerTool(nativePayload);
|
||||
} catch (error) {
|
||||
log("register.error", { tool: tool.name, message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function runTool(name, input) {
|
||||
const tool = registry.get(name);
|
||||
if (!tool) {
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
const result = await tool.execute(input);
|
||||
return { tool: name, input, output: result };
|
||||
}
|
||||
|
||||
function schemaFromForm(form) {
|
||||
const properties = {};
|
||||
const required = [];
|
||||
form.querySelectorAll("input[name], select[name], textarea[name]").forEach((field) => {
|
||||
const name = field.getAttribute("name");
|
||||
properties[name] = {
|
||||
type: inputTypeToJsonSchema(field),
|
||||
description: field.closest("label")?.firstChild?.textContent?.trim() || name,
|
||||
};
|
||||
if (field.hasAttribute("required")) {
|
||||
required.push(name);
|
||||
}
|
||||
});
|
||||
return { type: "object", properties, required };
|
||||
}
|
||||
|
||||
function inputTypeToJsonSchema(field) {
|
||||
if (field.tagName === "SELECT" || field.tagName === "TEXTAREA") {
|
||||
return "string";
|
||||
}
|
||||
if (field.type === "number" || field.type === "range") {
|
||||
return "number";
|
||||
}
|
||||
if (field.type === "checkbox") {
|
||||
return "boolean";
|
||||
}
|
||||
return "string";
|
||||
}
|
||||
|
||||
function renderTools() {
|
||||
toolListEl.innerHTML = "";
|
||||
registry.forEach((tool) => {
|
||||
const card = document.createElement("article");
|
||||
card.className = "tool-card";
|
||||
card.innerHTML = `
|
||||
<h3>${tool.name}</h3>
|
||||
<p>${tool.description}</p>
|
||||
<span class="pill">${tool.source}</span>
|
||||
<pre class="schema">${escapeHtml(JSON.stringify(tool.inputSchema, null, 2))}</pre>
|
||||
`;
|
||||
toolListEl.append(card);
|
||||
});
|
||||
}
|
||||
|
||||
function sampleInputFor(name) {
|
||||
const samples = {
|
||||
create_support_ticket: {
|
||||
email: "dev@example.com",
|
||||
category: "refund",
|
||||
description: "订单 CHR-2026-0614 到货后发现配件缺失。",
|
||||
},
|
||||
lookup_order_status: { orderId: "CHR-2026-0614" },
|
||||
approve_refund_review: { orderId: "CHR-2026-0614", reason: "missing_accessory" },
|
||||
draft_customer_reply: {
|
||||
email: "dev@example.com",
|
||||
category: "refund",
|
||||
orderId: "CHR-2026-0614",
|
||||
},
|
||||
};
|
||||
return samples[name] ?? {};
|
||||
}
|
||||
|
||||
function log(title, payload) {
|
||||
const entry = document.createElement("div");
|
||||
entry.className = "entry";
|
||||
entry.innerHTML = `<strong>${title}</strong><pre>${escapeHtml(JSON.stringify(payload, null, 2))}</pre>`;
|
||||
logEl.prepend(entry);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """);
|
||||
}
|
||||
Reference in New Issue
Block a user