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('"', """);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WebMCP Demo - Tool Contracts</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Runnable demo</p>
|
||||
<h1>WebMCP 工具契约演示</h1>
|
||||
</div>
|
||||
<div class="runtime">
|
||||
<span id="native-status" class="pill">检测中</span>
|
||||
<a href="https://developer.chrome.com/docs/ai/webmcp" target="_blank" rel="noreferrer">Chrome 官方文档</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="layout">
|
||||
<section class="workspace">
|
||||
<div class="intro">
|
||||
<p>
|
||||
这个页面模拟一个售后后台:左侧是人能操作的业务 UI,右侧是页面暴露给 Agent 的工具契约。
|
||||
如果当前 Chrome 没有开启 WebMCP,本页面会使用本地 mock,让现场仍然能看清楚“工具注册”和“工具调用”的流程。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">Declarative API</p>
|
||||
<h2>声明式:把 HTML 表单变成工具</h2>
|
||||
</div>
|
||||
<span class="mode">form attributes</span>
|
||||
</div>
|
||||
|
||||
<form
|
||||
id="ticket-form"
|
||||
class="business-form"
|
||||
toolname="create_support_ticket"
|
||||
tooldescription="Create a support ticket from user supplied issue details."
|
||||
>
|
||||
<label>
|
||||
客户邮箱
|
||||
<input name="email" type="email" required value="dev@example.com" />
|
||||
</label>
|
||||
<label>
|
||||
问题类型
|
||||
<select name="category" required>
|
||||
<option value="refund">退款</option>
|
||||
<option value="delivery">物流</option>
|
||||
<option value="account">账号</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
问题描述
|
||||
<textarea name="description" required>订单 CHR-2026-0614 到货后发现配件缺失。</textarea>
|
||||
</label>
|
||||
<button type="submit">提交工单</button>
|
||||
</form>
|
||||
|
||||
<pre class="code"><code><form
|
||||
toolname="create_support_ticket"
|
||||
tooldescription="Create a support ticket from user supplied issue details.">
|
||||
<input name="email" type="email" required />
|
||||
<select name="category" required>...</select>
|
||||
<textarea name="description" required></textarea>
|
||||
</form></code></pre>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">Imperative API</p>
|
||||
<h2>指令式:用 JavaScript 注册业务工具</h2>
|
||||
</div>
|
||||
<span class="mode">registerTool()</span>
|
||||
</div>
|
||||
|
||||
<div class="order-grid">
|
||||
<article>
|
||||
<span>订单号</span>
|
||||
<strong>CHR-2026-0614</strong>
|
||||
<small>已签收,缺件风险</small>
|
||||
</article>
|
||||
<article>
|
||||
<span>客户等级</span>
|
||||
<strong>Enterprise</strong>
|
||||
<small>允许优先退款审核</small>
|
||||
</article>
|
||||
<article>
|
||||
<span>当前状态</span>
|
||||
<strong id="order-state">等待处理</strong>
|
||||
<small>由工具调用更新</small>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="action-row">
|
||||
<button data-run-tool="lookup_order_status">运行 lookup_order_status</button>
|
||||
<button data-run-tool="draft_customer_reply">运行 draft_customer_reply</button>
|
||||
<button data-run-tool="approve_refund_review">运行 approve_refund_review</button>
|
||||
</div>
|
||||
|
||||
<pre class="code"><code>navigator.modelContext.registerTool({
|
||||
name: "lookup_order_status",
|
||||
description: "Look up order status by order id.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: { orderId: { type: "string" } },
|
||||
required: ["orderId"]
|
||||
},
|
||||
execute: async ({ orderId }) => lookupOrder(orderId)
|
||||
});</code></pre>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<aside class="agent">
|
||||
<section class="agent-panel">
|
||||
<p class="eyebrow">Agent view</p>
|
||||
<h2>已暴露工具</h2>
|
||||
<div id="tool-list" class="tool-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="agent-panel">
|
||||
<p class="eyebrow">Run script</p>
|
||||
<h2>现场演示步骤</h2>
|
||||
<ol class="steps">
|
||||
<li>先提交左侧表单,说明“人类 UI 仍然可用”。</li>
|
||||
<li>看右侧工具列表,说明 Agent 看到的是名称、描述和 JSON Schema。</li>
|
||||
<li>点击工具运行按钮,说明 Agent 不需要猜按钮位置,而是调用明确工具。</li>
|
||||
<li>点击“执行复合任务”,串起查询、决策和回复草稿。</li>
|
||||
</ol>
|
||||
<button id="run-workflow" class="primary">执行复合任务</button>
|
||||
</section>
|
||||
|
||||
<section class="agent-panel log-panel">
|
||||
<p class="eyebrow">Execution log</p>
|
||||
<h2>调用日志</h2>
|
||||
<div id="log" class="log"></div>
|
||||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<script type="module" src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,346 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #161a1d;
|
||||
--muted: #60717c;
|
||||
--paper: #fffdfa;
|
||||
--panel: #f3f0e8;
|
||||
--line: #d9d2c2;
|
||||
--blue: #174ea6;
|
||||
--green: #116b4f;
|
||||
--amber: #9a5c00;
|
||||
--red: #a7352a;
|
||||
--code: #1b2430;
|
||||
--shadow: 0 20px 70px rgb(50 45 34 / 0.16);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
background:
|
||||
linear-gradient(90deg, rgb(22 26 29 / 0.06) 1px, transparent 1px),
|
||||
linear-gradient(rgb(22 26 29 / 0.06) 1px, transparent 1px),
|
||||
#e7e1d6;
|
||||
background-size: 28px 28px;
|
||||
font-family: ui-sans-serif, "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--blue);
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 28px 32px 18px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: var(--blue);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: clamp(34px, 5vw, 68px);
|
||||
line-height: 0.95;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 0;
|
||||
font-size: 22px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.runtime {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.pill,
|
||||
.mode {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
border: 1px solid var(--line);
|
||||
padding: 4px 10px;
|
||||
background: var(--paper);
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.pill.native {
|
||||
border-color: rgb(17 107 79 / 0.35);
|
||||
background: #e8f4ee;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.pill.mock {
|
||||
border-color: rgb(154 92 0 / 0.35);
|
||||
background: #fff3d8;
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
|
||||
gap: 22px;
|
||||
padding: 0 32px 32px;
|
||||
}
|
||||
|
||||
.workspace,
|
||||
.agent {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.intro,
|
||||
.panel,
|
||||
.agent-panel {
|
||||
border: 1px solid var(--line);
|
||||
background: rgb(255 253 250 / 0.92);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.intro {
|
||||
padding: 18px 20px;
|
||||
}
|
||||
|
||||
.intro p {
|
||||
margin-bottom: 0;
|
||||
color: var(--muted);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.panel,
|
||||
.agent-panel {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.business-form {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
padding: 11px 12px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 92px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid var(--ink);
|
||||
background: var(--ink);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
min-height: 42px;
|
||||
padding: 10px 14px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.primary {
|
||||
width: 100%;
|
||||
background: var(--blue);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
|
||||
.code {
|
||||
overflow: auto;
|
||||
margin: 18px 0 0;
|
||||
border: 1px solid #303a47;
|
||||
background: var(--code);
|
||||
color: #d7e2ef;
|
||||
padding: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.order-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.order-grid article {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.order-grid span,
|
||||
.order-grid small {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.order-grid strong {
|
||||
display: block;
|
||||
margin: 8px 0 6px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.action-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.action-row button {
|
||||
min-height: 56px;
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.agent {
|
||||
position: sticky;
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
.tool-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.tool-card h3 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tool-card p {
|
||||
margin-bottom: 10px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.schema {
|
||||
overflow: auto;
|
||||
max-height: 150px;
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
color: #384551;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.steps {
|
||||
margin: 0 0 16px;
|
||||
padding-left: 20px;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.log {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
max-height: 370px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.entry {
|
||||
border-left: 4px solid var(--blue);
|
||||
background: #fff;
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.entry strong {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.entry pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.topbar,
|
||||
.layout {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.topbar,
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.agent {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.order-grid,
|
||||
.action-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user