desktop_rpa/rpa-workstation-prototype.html

336 lines
46 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<title>RPA工作站 · 高保真交互原型</title>
<style>
:root {
--primary:#ff5000; --rpa-primary:#ff5000; --rpa-primary-hover:#e64500; --rpa-primary-soft:#fff3e8;
--rpa-tech-blue:#ff8a00; --rpa-tech-blue-hover:#ff9d2e; --rpa-nav-bg:#373942; --rpa-nav-panel:#2b2d34;
--rpa-nav-border:#383a45; --rpa-hosting-bg:#0a091e; --rpa-bg:#f7f8fa; --rpa-surface:#ffffff;
--rpa-surface-subtle:#f3f4f6; --rpa-border:#e6e6e6; --rpa-border-strong:#cbd5e1; --rpa-text:#333333;
--rpa-heading:#1f2937; --rpa-muted:#666666; --rpa-subtle:#7c7c7c; --rpa-inverse:#e5e9ee;
--rpa-inverse-muted:#a3a3a3; --rpa-success:#4ab933; --rpa-success-bg:#edffe9; --rpa-success-border:#dcf9d6;
--rpa-info:#28a3ef; --rpa-info-bg:#eef8ff; --rpa-warning:#f59e0b; --rpa-warning-bg:#fffbeb;
--rpa-danger:#d44044; --rpa-danger-bg:#fff2f2; --rpa-danger-border:#ffe3e0;
--rpa-shadow-card:0 1px 2px rgba(15,23,42,.06); --rpa-shadow-float:0 8px 24px rgba(15,23,42,.14);
--rpa-shadow-blocking:0 24px 60px rgba(0,0,0,.35); --rpa-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;
}
* { box-sizing: border-box; }
body { overflow: hidden; color: var(--rpa-text); }
.scrollbar-thin::-webkit-scrollbar { width: 8px; height: 8px; }
.scrollbar-thin::-webkit-scrollbar-thumb { background: var(--rpa-border-strong); border-radius: 999px; }
.focus-ring:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--rpa-primary); }
.rpa-card { border:1px solid var(--rpa-border); background:var(--rpa-surface); border-radius:1rem; box-shadow:var(--rpa-shadow-card); }
.rpa-panel { border:1px solid var(--rpa-border); background:var(--rpa-surface); border-radius:1rem; box-shadow:var(--rpa-shadow-card); }
.rpa-table { overflow:hidden; border:1px solid var(--rpa-border); background:var(--rpa-surface); border-radius:1rem; box-shadow:var(--rpa-shadow-card); }
.rpa-table thead { background:var(--rpa-surface-subtle); color:var(--rpa-muted); }
.rpa-table tbody { color:var(--rpa-text); }
.rpa-table tbody tr { border-top:1px solid var(--rpa-border); }
.rpa-table tbody tr:hover { background:#f8fafc; }
.rpa-scrim { background:rgba(10,9,30,.55); }
.rpa-hosting-scrim { background:rgba(10,9,30,.85); }
.rpa-primary-btn { background:var(--rpa-primary); color:#fff; box-shadow:0 8px 20px rgba(255,80,0,.22); }
.rpa-primary-btn:hover { background:var(--rpa-primary-hover); }
.rpa-secondary-btn { border:1px solid var(--rpa-border); background:var(--rpa-surface); color:var(--rpa-text); }
.rpa-secondary-btn:hover { background:var(--rpa-surface-subtle); }
.rpa-danger-btn { background:var(--rpa-danger); color:#fff; box-shadow:0 8px 20px rgba(212,64,68,.22); }
.rpa-danger-btn:hover { filter:brightness(.95); }
.rpa-link-btn { color:var(--rpa-primary); }
.rpa-link-btn:hover { color:var(--rpa-primary-hover); }
.rpa-chip { display:inline-flex; align-items:center; gap:.375rem; border-radius:999px; padding:.25rem .625rem; font-size:.75rem; line-height:1rem; font-weight:500; border:1px solid transparent; }
.rpa-chip-primary { background:var(--rpa-primary-soft); color:var(--rpa-primary-hover); border-color:#ffd8bf; }
.rpa-chip-success { background:var(--rpa-success-bg); color:#2f8a20; border-color:var(--rpa-success-border); }
.rpa-chip-info { background:var(--rpa-info-bg); color:#0f7fbd; border-color:#d6efff; }
.rpa-chip-warning { background:var(--rpa-warning-bg); color:#b45309; border-color:#fde68a; }
.rpa-chip-danger { background:var(--rpa-danger-bg); color:var(--rpa-danger); border-color:var(--rpa-danger-border); }
.rpa-chip-neutral { background:var(--rpa-surface-subtle); color:var(--rpa-muted); border-color:var(--rpa-border); }
.rpa-nav-item { color:var(--rpa-inverse-muted); }
.rpa-nav-item:hover { background:rgba(255,255,255,.08); color:var(--rpa-inverse); }
.rpa-nav-item:hover i { color:var(--rpa-tech-blue); }
.rpa-nav-item-active { background:var(--rpa-primary); color:#fff; box-shadow:0 8px 20px rgba(255,80,0,.25); }
.rpa-nav-item-active i { color:#fff; }
.rpa-nav-status { background:var(--rpa-nav-panel); border:1px solid var(--rpa-nav-border); color:var(--rpa-inverse-muted); }
.rpa-code { font-family:var(--rpa-font-mono); background:var(--rpa-surface-subtle); color:var(--rpa-heading); border:1px solid var(--rpa-border); border-radius:.75rem; }
.rpa-progress { background:linear-gradient(90deg, var(--rpa-primary), var(--rpa-tech-blue)); }
.rpa-control, [data-app-root] input, [data-app-root] select, [data-app-root] textarea { border-color:var(--rpa-border-strong); }
[data-app-root] input:focus-visible, [data-app-root] select:focus-visible, [data-app-root] textarea:focus-visible { outline:none; box-shadow:0 0 0 2px var(--rpa-primary); }
</style>
</head>
<body class="min-h-screen p-6" style="background:linear-gradient(180deg, #fff3e8 0%, #f7f8fa 45%, #ffffff 100%); color:var(--rpa-text);">
<main class="mx-auto flex h-[calc(100vh-48px)] max-w-[1240px] flex-col items-center gap-4 overflow-hidden">
<header class="flex w-[1180px] shrink-0 items-center justify-between gap-4">
<div class="flex items-center gap-3">
<div class="grid h-10 w-10 place-items-center rounded-2xl text-white shadow-lg" style="background:var(--rpa-primary); box-shadow:0 10px 24px rgba(255,80,0,.25)">
<i data-lucide="workflow" class="h-5 w-5"></i>
</div>
<div>
<h1 class="m-0 text-xl font-semibold" style="color:var(--rpa-heading)">RPA工作站 原型</h1>
<p class="m-0 text-sm" style="color:var(--rpa-muted)">本地优先 AI + RPA 视觉标注与托管执行平台</p>
</div>
</div>
<div class="flex rounded-full border bg-white p-1 shadow-sm" style="border-color:var(--rpa-border)" aria-label="端切换">
<button type="button" data-platform-tab="windows" class="focus-ring h-10 rounded-full px-5 text-sm font-medium text-white" style="background:var(--rpa-primary)">Windows 端</button>
<button type="button" data-platform-tab="mac" class="focus-ring h-10 rounded-full px-5 text-sm font-medium" style="color:var(--rpa-muted)">Mac 端</button>
</div>
</header>
<section class="min-h-0 shrink-0" data-platform-panel="windows">
<div class="flex h-[760px] w-[1180px] flex-col overflow-hidden border border-slate-300 bg-white shadow-2xl">
<div class="flex h-10 shrink-0 items-center border-b border-slate-200 bg-white pl-3">
<div class="flex flex-1 items-center gap-2 text-sm font-medium text-[var(--rpa-text)]">
<span class="grid h-5 w-5 place-items-center rounded bg-[var(--rpa-primary)] text-white"><i data-lucide="workflow" class="h-3.5 w-3.5"></i></span>
<span>RPA工作站</span>
</div>
<button class="grid h-10 w-12 place-items-center hover:text-[var(--rpa-tech-blue)]" aria-label="最小化"></button>
<button class="grid h-10 w-12 place-items-center hover:text-[var(--rpa-tech-blue)]" aria-label="最大化"></button>
<button class="grid h-10 w-12 place-items-center text-[var(--rpa-muted)] hover:bg-red-500 hover:text-white" aria-label="关闭">×</button>
</div>
<div class="relative min-h-0 flex-1 overflow-hidden" style="background:var(--rpa-bg)" data-app-root data-platform="windows"></div>
</div>
</section>
<section class="hidden min-h-0 shrink-0" data-platform-panel="mac">
<div class="flex h-[760px] w-[1180px] flex-col overflow-hidden rounded-[1.75rem] bg-white shadow-2xl shadow-slate-900/20 ring-1 ring-slate-200">
<div class="flex h-11 shrink-0 items-center border-b border-slate-200 px-4" style="background:var(--rpa-bg)">
<div class="flex gap-2">
<span class="h-3 w-3 rounded-full bg-red-500"></span>
<span class="h-3 w-3 rounded-full bg-yellow-400"></span>
<span class="h-3 w-3 rounded-full bg-green-500"></span>
</div>
<div class="flex-1 text-center text-sm font-medium text-[var(--rpa-muted)]">RPA工作站</div>
<div class="w-16"></div>
</div>
<div class="relative min-h-0 flex-1 overflow-hidden" style="background:var(--rpa-bg)" data-app-root data-platform="mac"></div>
</div>
</section>
</main>
<div class="hidden" aria-hidden="true" data-page="工作台" data-page-secondary="自动化任务">PRD coverage marker: 名称、类型、索引状态、引用次数、更新时间、权限模式;时间、员工、应用、知识库、任务状态。</div>
<template id="app-template">
<div class="flex h-full overflow-hidden" style="background:var(--rpa-bg); color:var(--rpa-text);">
<aside class="flex w-[92px] shrink-0 flex-col items-center gap-2 border-r p-3" style="background:var(--rpa-nav-bg); border-color:var(--rpa-nav-border); color:var(--rpa-inverse-muted);" data-nav></aside>
<section class="relative flex min-w-0 flex-1 flex-col overflow-hidden">
<header class="flex h-20 shrink-0 items-center justify-between border-b px-6" style="border-color:var(--rpa-border); background:var(--rpa-surface);" data-header></header>
<main class="min-h-0 flex-1 overflow-y-auto p-6 scrollbar-thin" style="background:var(--rpa-bg);" data-content></main>
<div class="pointer-events-none absolute right-5 top-24 z-50 space-y-2" data-toasts></div>
<div class="absolute inset-0 z-40 hidden" data-overlay-layer></div>
</section>
</div>
</template>
<script>
const pages = [
['工作台','layout-dashboard'], ['标注应用','scan-line'], ['知识库','database'], ['AI 员工','bot'], ['自动化任务','list-checks'], ['设置','settings']
];
const pageSummaries = {
'工作台':'环境、资产、执行状态与核心闭环入口', '标注应用':'管理窗口、节点、流程与 Ctrl+E 标注模式', '知识库':'企业知识资产索引、权限与审计', 'AI 员工':'角色、模型、提示词与任务确认', '自动化任务':'任务状态、托管遮罩、失败重试与审计复盘', '设置':'模型、快捷键、诊断、备份与许可证'
};
const prototypeState = {
page:'工作台', activePlatform:'windows', modal:null, overlay:null, annotationStep:1, activeAppTab:'概览', auditOpen:false,
rpaHosting:false, hostingStep:0, hostingLogs:[], highRisk:false, toast:[], loadingModel:false,
configStep:0, auditFilters:{employee:'', status:''}, draftBounds:{x:236,y:164,width:128,height:52}, dragging:false,
kbs:[
{name:'财务报销制度', description:'报销制度与审批规则', type:'document', status:'ready', refs:8, updated:'2026-06-25 15:30', permission:'private'},
{name:'客服话术图片集', description:'截图与话术图片 OCR', type:'image', status:'indexing', refs:3, updated:'2026-06-24 11:10', permission:'selected_agents'},
{name:'HR 入职流程', description:'入职办理流程文件夹', type:'folder', status:'ready', refs:5, updated:'2026-06-23 09:42', permission:'workspace'},
{name:'培训视频材料', description:'培训视频关键帧', type:'video', status:'failed', refs:0, updated:'2026-06-22 18:08', permission:'private', error:'OCR 关键帧提取失败'}
],
agents:[
{name:'报销审核员', role:'财务审核', status:'running', kb:1, apps:1, last:'2026-06-26 09:10'},
{name:'客服录单员', role:'客服录入', status:'idle', kb:1, apps:1, last:'昨天 16:20'},
{name:'HR 入职助手', role:'人事流程', status:'waiting_user', kb:1, apps:0, last:'等待人工确认'}
],
nodes:[
{name:'提交按钮', type:'button', bounds:'{ x: 520, y: 430, width: 96, height: 36 }', actions:'click, wait', confidence:'0.96'},
{name:'金额输入框', type:'input', bounds:'{ x: 220, y: 285, width: 180, height: 38 }', actions:'type, read', confidence:'0.93'},
{name:'报销明细表', type:'table', bounds:'{ x: 160, y: 340, width: 460, height: 180 }', actions:'read, click', confidence:'0.88'},
{name:'审批菜单', type:'menu', bounds:'{ x: 42, y: 120, width: 140, height: 240 }', actions:'click, hotkey', confidence:'0.91'}
],
tasks:[
{name:'报销单托管演示', type:'RPA 托管', status:'running', employee:'报销审核员', app:'企业报销系统', updated:'09:12', error:'', code:''},
{name:'财务制度索引', type:'知识库索引', status:'succeeded', employee:'系统', app:'知识库', updated:'昨天', error:'', code:''},
{name:'提交按钮重试', type:'AI 员工执行', status:'failed', employee:'报销审核员', app:'企业报销系统', updated:'周三', error:'目标按钮置信度低,请重新标注节点', code:'RPA_NODE_CONFIDENCE_LOW'}
],
audits:[
{time:'09:10', employee:'报销审核员', app:'企业报销系统', kb:'财务报销制度', status:'running', text:'RPA 托管开始,动作范围已确认'},
{time:'09:11', employee:'报销审核员', app:'企业报销系统', kb:'财务报销制度', status:'running', text:'读取报销规则并定位金额输入框'},
{time:'昨天', employee:'系统', app:'知识库', kb:'财务报销制度', status:'succeeded', text:'知识库索引完成'},
{time:'周三', employee:'报销审核员', app:'企业报销系统', kb:'财务报销制度', status:'failed', text:'目标按钮置信度低,请重新标注节点'}
]
};
function clsBadge(kind){
const map = {ready:'rpa-chip rpa-chip-success', succeeded:'rpa-chip rpa-chip-success', indexing:'rpa-chip rpa-chip-warning', waiting_user:'rpa-chip rpa-chip-warning', pending:'rpa-chip rpa-chip-info', queued:'rpa-chip rpa-chip-info', failed:'rpa-chip rpa-chip-danger', running:'rpa-chip rpa-chip-primary', idle:'rpa-chip rpa-chip-neutral', canceled:'rpa-chip rpa-chip-neutral'};
return map[kind] || map.idle;
}
function icon(name, c='h-4 w-4'){ return `<i data-lucide="${name}" class="${c}"></i>`; }
function toast(text, type='success'){ prototypeState.toast.push({text,type,id:Date.now()}); setTimeout(()=>{ prototypeState.toast.shift(); renderAll(); }, 2400); renderAll(); }
function renderAll(){ document.querySelectorAll('[data-app-root]').forEach(root => renderRoot(root)); if (window.lucide) lucide.createIcons(); }
function init(){
const tpl = document.getElementById('app-template');
document.querySelectorAll('[data-app-root]').forEach(root => root.appendChild(tpl.content.cloneNode(true)));
renderAll();
}
function renderRoot(root){
root.dataset.appRoot = '';
const nav = root.querySelector('[data-nav]');
const hosting = prototypeState.rpaHosting;
nav.innerHTML = `<div class="flex h-full w-full flex-col"><div class="flex flex-1 flex-col items-center gap-2">${pages.map(([p,ic]) => { const active = prototypeState.page===p; return `<button type="button" data-page="${p}" aria-label="${p}" aria-current="${active?'page':'false'}" class="focus-ring group flex min-h-[56px] w-full flex-col items-center justify-center gap-1 rounded-2xl text-[11px] transition ${active?'rpa-nav-item-active':'rpa-nav-item'}"><span>${icon(ic,'h-5 w-5')}</span><span>${p}</span></button>`; }).join('')}</div><div class="rpa-nav-status mt-2 flex w-full flex-col items-center rounded-2xl px-2 py-2 text-[10px] leading-4"><span class="mb-1" style="color:${hosting?'var(--rpa-warning)':'var(--rpa-success)'}">${icon('circle-dot','h-3.5 w-3.5')}</span><span>${hosting?'托管中':'本地在线'}</span></div></div>`;
const header = root.querySelector('[data-header]');
header.innerHTML = `<div><h2 class="text-xl font-semibold" style="color:var(--rpa-heading)">${prototypeState.page}</h2><p class="mt-1 text-sm" style="color:var(--rpa-muted)">${pageSummaries[prototypeState.page]}</p></div><div class="flex flex-wrap justify-end gap-2">${chip('模型 在线','activity','emerald')}${chip(prototypeState.rpaHosting?'RPA 托管中':'RPA 空闲','radio','indigo')}${chip('本地模式','hard-drive','slate')}${chip('试用 14天','badge-check','amber')}</div>`;
root.querySelector('[data-content]').innerHTML = renderPage();
root.querySelector('[data-toasts]').innerHTML = prototypeState.toast.map(t => `<div class="pointer-events-auto flex min-w-[280px] items-start gap-2 rounded-xl border px-4 py-3 text-sm shadow-xl" style="background:${t.type==='error'?'var(--rpa-danger-bg)':'var(--rpa-success-bg)'}; border-color:${t.type==='error'?'var(--rpa-danger-border)':'var(--rpa-success-border)'}; color:${t.type==='error'?'var(--rpa-danger)':'var(--rpa-text)'}">${icon(t.type==='error'?'circle-alert':'circle-check','mt-0.5 h-4 w-4 shrink-0')}<span>${t.text}</span></div>`).join('');
renderOverlay(root);
}
function chip(text, ic, tone){ const map={emerald:'rpa-chip-success', indigo:'rpa-chip-primary', slate:'rpa-chip-neutral', amber:'rpa-chip-warning'}; return `<span class="rpa-chip ${map[tone] || 'rpa-chip-neutral'}">${icon(ic)}${text}</span>`; }
function card(title, body, footer=''){ return `<article class="rpa-card p-5"><h3 class="mb-4 flex items-center gap-2 font-semibold" style="color:var(--rpa-heading)"><span style="color:var(--rpa-primary)">${body.icon||''}</span>${title}</h3>${body.html||body}${footer}</article>`; }
function actionBtn(label, attrs='', primary=false){ return `<button type="button" ${attrs} class="focus-ring inline-flex h-10 items-center justify-center gap-2 rounded-xl ${primary?'rpa-primary-btn':'rpa-secondary-btn'} px-4 text-sm font-medium">${label}</button>`; }
function renderPage(){
if (prototypeState.page==='工作台') return dashboardPage();
if (prototypeState.page==='知识库') return kbPage();
if (prototypeState.page==='AI 员工') return agentPage();
if (prototypeState.page==='标注应用') return annotationPage();
if (prototypeState.page==='自动化任务') return tasksPage();
return settingsPage();
}
function dashboardPage(){
return `<div class="space-y-6">
<div class="grid grid-cols-3 gap-4">
${card('环境状态',{icon:icon('shield-check'),html:`<div class="space-y-3 text-sm"><div class="flex justify-between"><span>权限</span><b>3/4</b></div><div class="flex justify-between"><span>模型连通</span><span class="text-[var(--rpa-success)]">通过</span></div><div class="flex justify-between"><span>本地 GPU</span><span>中档</span></div><div class="flex justify-between"><span>存储</span><span class="text-[var(--rpa-success)]">可用</span></div>${actionBtn('查看首次配置','data-modal-open="config"')}</div>`})}
${card('资产状态',{icon:icon('boxes'),html:`<div class="grid grid-cols-2 gap-3 text-sm"><b>知识库 3</b><b>就绪索引 2</b><b>AI 员工 2</b><b>标注应用 1</b></div>`})}
${card('执行状态',{icon:icon('activity'),html:`<div class="space-y-3 text-sm"><div class="flex justify-between"><span>运行中</span><b>0</b></div><div class="flex justify-between"><span>失败任务</span><b class="text-[var(--rpa-danger)]">1</b></div><div class="flex justify-between"><span>最近托管</span><span class="text-[var(--rpa-success)]">成功</span></div><div class="flex justify-between"><span>RPA</span><span>${prototypeState.rpaHosting?'托管中':'空闲'}</span></div></div>`})}
</div>
<section class="rpa-panel p-5"><h3 class="mb-4 font-semibold">快速入口</h3><div class="flex flex-wrap gap-3">
${actionBtn(icon('database')+'创建知识库','data-modal-open="knowledge"',true)}
${actionBtn(icon('bot')+'创建 AI 员工','data-modal-open="agent"',true)}
${actionBtn(icon('scan-line')+'进入标注模式 Ctrl+E','data-overlay-open="annotation"')}
${actionBtn(icon('play')+'启动自动化任务','data-modal-open="hosting-confirm"')}
${actionBtn(icon('package-check')+'导出诊断包','data-action="diagnostic"')}
</div></section>
<section class="rpa-panel p-5"><h3 class="mb-4 font-semibold">核心闭环</h3><div class="flex items-center gap-2 text-sm text-[var(--rpa-muted)]">${['首次配置','知识库','AI 员工','标注应用','自动化任务','托管执行','审计复盘'].map((s,i)=>`<div class="flex items-center gap-2"><span class="grid h-8 w-8 place-items-center rounded-full font-semibold" style="background:var(--rpa-primary-soft); color:var(--rpa-primary)">${i+1}</span><span>${s}</span></div>${i<6?'<div class="h-px w-8 bg-slate-200"></div>':''}`).join('')}</div></section>
</div>`;
}
function kbPage(){
return `<div class="space-y-5"><div class="flex items-center gap-3 rpa-panel p-4"><div class="relative flex-1">${icon('search','absolute left-3 top-3 h-4 w-4 text-slate-400')}<input class="focus-ring h-10 w-full rounded-xl border border-slate-200 pl-9 text-sm" placeholder="搜索制度、流程、操作说明"></div>${actionBtn('来源','')}${actionBtn('类型','')}${actionBtn('按更新时间','')}${actionBtn(icon('upload')+'上传/创建知识库','data-modal-open="knowledge"',true)}</div>${table(['名称','类型','索引状态','引用次数','更新时间','权限模式'], prototypeState.kbs.map((k,i)=>[`<div><b>${k.name}</b><p class="text-xs text-[var(--rpa-muted)]">${k.description}${k.error?` · ${k.error} ${actionBtn('重试',`data-action="retry-kb" data-index="${i}"`)}`:''}</p></div>`,k.type,`<span class="${clsBadge(k.status)}">${k.status}</span>`,k.refs,k.updated,`<button data-action="permission" data-index="${i}" class="${clsBadge('idle')}">${k.permission}</button>`]))}</div>`;
}
function agentPage(){
return `<div class="space-y-5"><div class="flex justify-between rpa-panel p-4"><div><h3 class="font-semibold">AI 员工</h3><p class="text-sm text-[var(--rpa-muted)]">启动任务前必须展示知识库、应用和停止方式。</p></div>${actionBtn(icon('plus')+'创建 AI 员工','data-modal-open="agent"',true)}</div>${table(['名称','角色','状态','可访问知识库','可操作标注应用','最近执行时间','操作'], prototypeState.agents.map((a,i)=>[a.name,a.role,`<span class="${clsBadge(a.status)}">${a.status}</span>`,a.kb,a.apps,a.last,actionBtn('启动任务',`data-modal-open="agent-confirm" data-index="${i}"`)]))}</div>`;
}
function annotationPage(){
const tab = prototypeState.activeAppTab;
const tabHtml = ['概览','窗口','节点','流程'].map(t=>`<button type="button" data-tab="${t}" class="focus-ring h-10 rounded-xl px-4 text-sm font-medium ${tab===t?'rpa-primary-btn':'hover:text-[var(--rpa-tech-blue)]'}">${t}</button>`).join('');
let panel = tab==='节点' ? table(['节点名','node_type','bounds','allowed_actions','confidence'], prototypeState.nodes.map(n=>[n.name,n.type,n.bounds,n.actions,n.confidence])) : tab==='窗口' ? `<div class="grid grid-cols-2 gap-4 text-sm"><div class="rounded-xl border p-4"><b>主窗口</b><p>1180×760 · 已标注</p></div><div class="rounded-xl border p-4"><b>审批弹窗</b><p>520×360 · 已标注</p></div></div>` : tab==='流程' ? `<ol class="space-y-3 text-sm"><li>1. 读取制度规则</li><li>2. 填写金额输入框</li><li>3. 点击提交按钮前暂停确认</li></ol>` : `<div class="grid grid-cols-4 gap-4 text-sm"><div class="rounded-xl border p-4"><b>窗口数</b><p>2</p></div><div class="rounded-xl border p-4"><b>节点数</b><p>${prototypeState.nodes.length}</p></div><div class="rounded-xl border p-4"><b>流程数</b><p>3</p></div><div class="rounded-xl border p-4"><b>最近更新</b><p>今天 09:20</p></div></div>`;
return `<div class="space-y-5"><div class="rpa-panel p-5"><div class="flex items-center justify-between"><div><h3 class="font-semibold">企业报销系统</h3><p class="text-sm text-[var(--rpa-muted)]">窗口数 2、节点数 ${prototypeState.nodes.length}、流程数 3、最近更新 今天</p></div>${actionBtn(icon('scan-line')+'进入标注模式 Ctrl+E','data-overlay-open="annotation"',true)}</div></div><section class="rpa-panel p-5"><div class="mb-5 flex gap-2">${tabHtml}</div>${panel}</section></div>`;
}
function tasksPage(){
const rows = prototypeState.tasks.map((t,i)=>[t.name,t.type,`<span class="${clsBadge(t.status)}">${t.status}</span>`,t.employee,t.app,t.updated,t.error?`<div class="text-[var(--rpa-danger)]">${t.error}<br><code class="text-xs">${t.code}</code><br>${actionBtn('重试',`data-action="retry-task" data-index="${i}"`)}</div>`:actionBtn('查看','')]);
let audits = prototypeState.audits.filter(a=>(!prototypeState.auditFilters.employee||a.employee===prototypeState.auditFilters.employee)&&(!prototypeState.auditFilters.status||a.status===prototypeState.auditFilters.status));
return `<div class="space-y-5"><div class="flex items-center justify-between rpa-panel p-4"><div><h3 class="font-semibold">自动化任务</h3><p class="text-sm text-[var(--rpa-muted)]">状态只使用 queued | running | succeeded | failed | canceled。</p></div>${actionBtn(icon('play')+'启动托管演示','data-modal-open="hosting-confirm"',true)}</div><div class="grid grid-cols-3 gap-4">${['运行中任务','历史任务','失败任务'].map((s,i)=>`<div class="rpa-panel p-4"><h3 class="font-semibold">${s}</h3><p class="mt-2 text-2xl font-semibold">${i===0?prototypeState.tasks.filter(t=>t.status==='running').length:i===1?prototypeState.tasks.filter(t=>t.status==='succeeded').length:prototypeState.tasks.filter(t=>t.status==='failed').length}</p></div>`).join('')}</div>${table(['任务名','类型','状态','员工','目标应用','更新时间','操作'], rows)}<section class="rpa-panel p-5"><button type="button" data-action="toggle-audit" class="flex h-10 w-full items-center justify-between font-semibold">审计日志 ${icon(prototypeState.auditOpen?'chevron-up':'chevron-down')}</button><div class="${prototypeState.auditOpen?'':'hidden'} mt-4 space-y-4"><div class="grid grid-cols-5 gap-3 text-sm"><select data-filter="time" class="h-10 rounded-xl border px-3"><option>时间</option></select><select data-filter="employee" class="h-10 rounded-xl border px-3"><option value="">员工</option><option>报销审核员</option><option>系统</option></select><select data-filter="app" class="h-10 rounded-xl border px-3"><option>应用</option></select><select data-filter="kb" class="h-10 rounded-xl border px-3"><option>知识库</option></select><select data-filter="status" class="h-10 rounded-xl border px-3"><option value="">任务状态</option><option>running</option><option>succeeded</option><option>failed</option><option>canceled</option></select></div>${table(['时间','员工','应用','知识库','状态','记录'], audits.map(a=>[a.time,a.employee,a.app,a.kb,`<span class="${clsBadge(a.status)}">${a.status}</span>`,a.text]))}</div></section></div>`;
}
function settingsPage(){
const groups = [
['模型配置',`本地模型 Qwen2.5 · 云端模型 可选 · API Key sk-•••••••• ${actionBtn(prototypeState.loadingModel?'测试中...':'连通性测试','data-action="model-test"')}`],
['提示词配置','系统提示词模板、员工提示词、任务提示词'], ['存储位置','SQLite 数据库、知识库原文、索引文件、日志目录'],
['快捷键管理','Ctrl+E 标注模式 · ESC 紧急停止 · 悬浮窗显示/隐藏'], ['开关配置','本地/云端模式 · 托管确认 已开启 · 高风险动作二次确认 已开启'],
['诊断与日志',`${actionBtn('导出诊断包','data-action="diagnostic-full"')} ${actionBtn('查看本地日志目录','data-action="log-dir"')} ${actionBtn('复制环境信息','data-action="copy-env"')}`],
['数据备份与恢复','导出配置、导出知识库元数据、恢复 SQLite 与索引文件'], ['许可证管理','trial · 14 天试用 · 最多 3 个知识库 / 2 个 AI 员工 / 1 个标注应用 / 每日 20 次托管任务']
];
return `<div class="grid grid-cols-2 gap-4 pb-8">${groups.map(g=>`<section class="rpa-panel p-5"><h3 class="mb-3 font-semibold">${g[0]}</h3><div class="text-sm leading-7 text-[var(--rpa-muted)]">${g[1]}</div></section>`).join('')}</div>`;
}
function table(headers, rows){ return `<div class="rpa-table"><table class="w-full text-left text-sm"><thead class="text-xs"><tr>${headers.map(h=>`<th class="px-4 py-3 font-medium">${h}</th>`).join('')}</tr></thead><tbody>${rows.map(r=>`<tr class="align-top">${r.map(c=>`<td class="px-4 py-3">${c}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`; }
function renderOverlay(root){
const layer = root.querySelector('[data-overlay-layer]');
layer.className = 'absolute inset-0 z-40 ' + ((prototypeState.modal || prototypeState.overlay || prototypeState.rpaHosting || prototypeState.highRisk) ? '' : 'hidden');
if (prototypeState.rpaHosting) { layer.innerHTML = hostingOverlay(); return; }
if (prototypeState.highRisk) { layer.innerHTML = highRiskOverlay(); return; }
if (prototypeState.overlay==='annotation') { layer.innerHTML = annotationOverlay(); return; }
if (prototypeState.modal) { layer.innerHTML = modalHtml(prototypeState.modal); return; }
layer.innerHTML = '';
}
function modalFrame(title, body, wide=false){ return `<div class="rpa-scrim flex h-full items-center justify-center p-8"><div class="max-h-full ${wide?'w-[760px]':'w-[560px]'} overflow-y-auto rounded-2xl border p-6" style="background:var(--rpa-surface); border-color:var(--rpa-border); box-shadow:var(--rpa-shadow-blocking)"><div class="mb-5 flex items-center justify-between"><h3 class="text-lg font-semibold" style="color:var(--rpa-heading)">${title}</h3><button data-modal-close class="focus-ring grid h-9 w-9 place-items-center rounded-xl hover:bg-slate-100">${icon('x')}</button></div>${body}</div></div>`; }
function modalHtml(type){
if(type==='config') return configModal();
if(type==='knowledge') return modalFrame('上传/创建知识库', `<form data-form="knowledge" class="space-y-4"><input name="name" required class="focus-ring h-10 w-full rounded-xl border px-3" placeholder="名称"><textarea name="description" class="focus-ring w-full rounded-xl border p-3" placeholder="描述"></textarea><select name="type" class="focus-ring h-10 w-full rounded-xl border px-3"><option value="document">document</option><option value="image">image</option><option value="video">video</option><option value="folder">folder</option><option value="url">url</option></select><select name="permission" class="focus-ring h-10 w-full rounded-xl border px-3"><option value="private">private</option><option value="selected_agents">selected_agents</option><option value="workspace">workspace</option></select><button class="focus-ring h-10 rounded-xl rpa-primary-btn px-4">提交</button></form>`);
if(type==='agent') return modalFrame('创建/编辑 AI 员工', `<form data-form="agent" class="space-y-4"><input name="role" required class="focus-ring h-10 w-full rounded-xl border px-3" placeholder="角色描述"><textarea name="prompt" class="focus-ring w-full rounded-xl border p-3" placeholder="系统提示词"></textarea><input class="focus-ring h-10 w-full rounded-xl border px-3" placeholder="模型配置:本地模型"><input class="focus-ring h-10 w-full rounded-xl border px-3" placeholder="知识库权限:财务报销制度"><input class="focus-ring h-10 w-full rounded-xl border px-3" placeholder="可操作应用权限:企业报销系统"><button class="focus-ring h-10 rounded-xl rpa-primary-btn px-4">提交</button></form>`);
if(type==='permission') return modalFrame('权限模式', `<div class="space-y-3">${['private','selected_agents','workspace'].map(p=>`<button data-action="save-permission" data-value="${p}" class="focus-ring flex h-10 w-full items-center rounded-xl border px-3 text-sm hover:bg-slate-50">${p}</button>`).join('')}</div>`);
if(type==='agent-confirm' || type==='hosting-confirm') return modalFrame('启动前确认', `<div class="space-y-4 text-sm"><div class="rounded-xl p-4" style="background:var(--rpa-primary-soft)"><b>AI 员工</b><p>报销审核员</p></div><div class="rounded-xl p-4" style="background:var(--rpa-surface-subtle)"><b>将访问的知识库</b><p>财务报销制度</p></div><div class="rounded-xl p-4" style="background:var(--rpa-surface-subtle)"><b>将控制的应用</b><p>企业报销系统</p></div><div class="rounded-xl p-4" style="background:var(--rpa-surface-subtle)"><b>预计动作范围</b><p>读取报销规则、填写金额、点击提交前暂停确认</p></div><div class="rounded-xl p-4" style="background:var(--rpa-surface-subtle)"><b>停止方式</b><p>ESC 或遮罩上的停止托管按钮</p></div><div class="flex justify-end gap-3">${actionBtn('取消','data-modal-close')}${actionBtn('确认启动托管','data-action="start-hosting"',true)}</div></div>`);
return '';
}
function configModal(){
const steps=['权限检查','模型配置','性能评估','存储位置','安全说明']; const s=prototypeState.configStep;
const status=['通过','通过','通过','通过','需手动处理'];
return modalFrame('首次配置与环境检查', `<div class="flex gap-5"><aside class="w-40 space-y-2">${steps.map((x,i)=>`<button data-action="config-step" data-index="${i}" class="focus-ring h-10 w-full rounded-xl text-left text-sm ${s===i?'rpa-primary-btn px-3':'px-3 hover:bg-slate-100'}">${x}</button>`).join('')}</aside><div class="flex-1 space-y-4 text-sm"><p style="color:var(--rpa-muted)">状态枚举:未检查 | 检查中 | 通过 | 失败 | 需手动处理</p>${s===0?`<div class="space-y-2">${['屏幕录制','辅助功能/自动化控制','文件访问','网络访问'].map((x,i)=>`<div class="flex justify-between rounded-xl border p-3"><span>${x}</span><span class="${clsBadge(i===1?status[4]:'ready')}">${i===1?status[4]:'通过'}</span></div>`).join('')}</div><div class="rounded-xl p-4" style="background:var(--rpa-warning-bg); color:#92400e">原因:辅助功能权限需手动授权;恢复:打开系统设置或重新检测。</div><div class="flex gap-2">${actionBtn('打开系统设置','data-action="open-settings"')}${actionBtn('重新检测','data-action="recheck-permission"',true)}${actionBtn('跳过并以后配置','data-modal-close')}</div>`:`<div class="rounded-xl border p-4"><b>${steps[s]}</b><p class="mt-2">当前状态:通过。本页用于演示 ${steps[s]} 配置检查。</p></div>`}</div></div>`, true);
}
function annotationOverlay(){
const b=prototypeState.draftBounds;
return `<div class="rpa-hosting-scrim relative h-full p-8 text-white"><div class="mx-auto flex h-full max-w-4xl flex-col gap-4"><div class="flex items-center justify-between"><div><h3 class="text-lg font-semibold">Ctrl+E 标注模式</h3><p class="text-sm text-slate-300">选择当前标注应用 → 框选窗口/节点 → 命名节点 → 保存</p></div>${actionBtn('退出','data-overlay-close')}</div><div class="grid grid-cols-[1fr_280px] gap-4 min-h-0 flex-1"><div class="relative overflow-hidden rounded-2xl bg-slate-200 p-5 text-slate-900"><div class="h-full rounded-xl bg-white p-5 shadow-inner"><div class="mb-4 flex h-10 items-center gap-2 rounded bg-slate-100 px-3"><b>企业报销系统</b><span class="text-sm" style="color:var(--rpa-muted)">模拟桌面截图</span></div><div class="grid grid-cols-3 gap-4"><div class="h-80 rounded bg-slate-100"></div><div class="col-span-2 h-80 rounded bg-slate-50 p-4"><input class="mb-4 h-10 w-52 rounded border px-3" value="1280.00"><button class="absolute rounded rpa-primary-btn px-4 py-2" style="left:520px;top:430px">提交</button></div></div></div><div data-drag-box class="absolute cursor-move rounded-lg border-2" style="left:${b.x}px;top:${b.y}px;width:${b.width}px;height:${b.height}px; border-color:var(--rpa-primary); background:rgba(255,80,0,.16)"></div></div><form data-form="node" class="space-y-4 rounded-2xl bg-white p-5 text-slate-900"><label class="block text-sm font-medium">当前标注应用<select class="focus-ring mt-2 h-10 w-full rounded-xl border px-3"><option>企业报销系统</option></select></label><label class="block text-sm font-medium">节点名<input name="name" required class="focus-ring mt-2 h-10 w-full rounded-xl border px-3" placeholder="测试提交按钮"></label><p class="rpa-code p-3 text-xs">当前 bounds{ x: ${b.x}, y: ${b.y}, width: ${b.width}, height: ${b.height} }</p><button class="focus-ring h-10 w-full rounded-xl rpa-primary-btn">保存</button><button type="button" data-action="annotation-fail" class="h-9 text-sm rpa-link-btn" style="color:var(--rpa-danger)">模拟保存失败</button><button type="button" data-action="annotation-retry" class="hidden h-9 text-sm rpa-link-btn">重试保存</button><p data-annotation-error class="hidden rounded-xl p-3 text-sm" style="background:var(--rpa-danger-bg); color:var(--rpa-danger)">保存失败:窗口句柄已变化,可保留草稿并重试。原因:窗口句柄已变化;恢复:保留草稿并重试保存。</p></form></div></div></div>`;
}
function hostingOverlay(){
const actions=['读取报销规则','填写金额输入框','定位提交按钮','提交报销单'];
const current=actions[Math.min(prototypeState.hostingStep, actions.length-1)];
return `<div class="flex h-full items-center justify-center p-8 text-white" style="background:rgba(10,9,30,.90)"><div class="w-[760px] rounded-3xl border p-6" style="background:var(--rpa-nav-panel); border-color:rgba(255,255,255,.10); box-shadow:var(--rpa-shadow-blocking)"><div class="flex items-center justify-between"><div><h3 class="text-xl font-semibold">RPA 托管执行中</h3><p class="text-sm text-slate-300">ESC 紧急停止始终可用</p></div><button data-action="stop-hosting" class="h-11 rounded-xl rpa-danger-btn px-5 font-semibold">停止托管</button></div><div class="mt-5 rounded-2xl p-4" style="background:#111827"><p class="text-sm text-slate-400">当前动作</p><p class="mt-1 text-lg font-semibold">${current}</p><div class="mt-4 h-3 rounded-full bg-slate-800"><div class="rpa-progress h-3 rounded-full" style="width:${(prototypeState.hostingStep+1)*25}%"></div></div></div><div class="mt-5 max-h-48 overflow-y-auto rounded-2xl p-4 text-sm text-slate-300" style="background:#080b14; font-family:var(--rpa-font-mono)">${prototypeState.hostingLogs.map(l=>`<p>${l}</p>`).join('') || '<p>等待下一步动作。</p>'}</div><div class="mt-5 flex justify-end gap-3">${actionBtn('下一步动作','data-action="next-hosting"',true)}</div></div></div>`;
}
function highRiskOverlay(){ return `<div class="rpa-scrim flex h-full items-center justify-center p-8"><div class="w-[520px] rounded-2xl border p-6" style="background:var(--rpa-surface); border-color:var(--rpa-danger-border); box-shadow:var(--rpa-shadow-blocking)"><h3 class="text-lg font-semibold" style="color:var(--rpa-danger)">高风险动作:提交</h3><p class="mt-3 text-sm" style="color:var(--rpa-muted)">确认后将向业务系统提交单据。</p><div class="mt-5 flex justify-end gap-3">${actionBtn('取消并等待人工','data-action="cancel-high-risk"')}<button type="button" data-action="confirm-high-risk" class="focus-ring inline-flex h-10 items-center justify-center rounded-xl rpa-danger-btn px-4 text-sm font-medium">确认提交</button></div></div></div>`; }
document.addEventListener('click', event => {
const platformTab = event.target.closest('[data-platform-tab]');
if(platformTab){ const target=platformTab.dataset.platformTab; prototypeState.activePlatform=target; document.querySelectorAll('[data-platform-panel]').forEach(p=>p.classList.toggle('hidden',p.dataset.platformPanel!==target)); document.querySelectorAll('[data-platform-tab]').forEach(b=>{b.className='focus-ring h-10 rounded-full px-5 text-sm font-medium text-[var(--rpa-muted)]';}); platformTab.className='focus-ring h-10 rounded-full bg-[var(--rpa-primary)] px-5 text-sm font-medium text-white'; return; }
const page = event.target.closest('[data-page]'); if(page){ prototypeState.page=page.dataset.page; renderAll(); return; }
const tab = event.target.closest('[data-tab]'); if(tab){ prototypeState.activeAppTab=tab.dataset.tab; renderAll(); return; }
const open = event.target.closest('[data-modal-open]'); if(open){ prototypeState.modal=open.dataset.modalOpen; renderAll(); return; }
if(event.target.closest('[data-modal-close]')){ prototypeState.modal=null; renderAll(); return; }
const overlayOpen = event.target.closest('[data-overlay-open]'); if(overlayOpen){ prototypeState.overlay=overlayOpen.dataset.overlayOpen; renderAll(); return; }
if(event.target.closest('[data-overlay-close]')){ prototypeState.overlay=null; renderAll(); return; }
const action = event.target.closest('[data-action]'); if(!action) return;
handleAction(action, event);
});
document.addEventListener('change', event => { const f=event.target.closest('[data-filter]'); if(!f) return; prototypeState.auditFilters[f.dataset.filter]=f.value; renderAll(); });
document.addEventListener('submit', event => {
event.preventDefault(); const form=event.target;
if(form.dataset.form==='knowledge'){ const fd=new FormData(form); prototypeState.kbs.unshift({name:fd.get('name')||'测试制度库',description:fd.get('description')||'新建知识库',type:fd.get('type'),status:'pending',refs:0,updated:'刚刚',permission:fd.get('permission')}); prototypeState.page='知识库'; prototypeState.modal=null; toast('知识库已创建,正在进入索引队列'); }
if(form.dataset.form==='agent'){ const fd=new FormData(form); prototypeState.agents.unshift({name:fd.get('role')||'新 AI 员工',role:fd.get('role')||'业务助理',status:'idle',kb:1,apps:1,last:'未执行'}); prototypeState.page='AI 员工'; prototypeState.modal=null; toast('AI 员工已创建,默认未启动任务'); }
if(form.dataset.form==='node'){ const fd=new FormData(form); const b=prototypeState.draftBounds; prototypeState.nodes.unshift({name:fd.get('name')||'新建节点',type:'button',bounds:`{ x: ${b.x}, y: ${b.y}, width: ${b.width}, height: ${b.height} }`,actions:'click, wait',confidence:'0.89'}); prototypeState.page='标注应用'; prototypeState.activeAppTab='节点'; prototypeState.overlay=null; toast('节点已保存到 企业报销系统'); }
renderAll();
});
function handleAction(el){
const a=el.dataset.action;
if(a==='diagnostic') toast('诊断包已生成,已排除 API Key 与知识库原文');
if(a==='diagnostic-full') toast('诊断包已生成,不包含 API Key、知识库原文、用户隐私内容');
if(a==='log-dir') toast('已打开本地日志目录(原型模拟)');
if(a==='copy-env') toast('环境信息已复制(原型模拟)');
if(a==='retry-kb'){ prototypeState.kbs[+el.dataset.index].status='indexing'; delete prototypeState.kbs[+el.dataset.index].error; toast('已重新加入索引队列'); }
if(a==='permission'){ prototypeState.modal='permission'; prototypeState.permissionIndex=+el.dataset.index; }
if(a==='save-permission'){ prototypeState.kbs[prototypeState.permissionIndex].permission=el.dataset.value; prototypeState.modal=null; prototypeState.audits.unshift({time:'刚刚',employee:'系统',app:'知识库',kb:prototypeState.kbs[prototypeState.permissionIndex].name,status:'succeeded',text:'权限变更已写入审计日志'}); toast('权限变更已写入审计日志'); }
if(a==='start-hosting') startHosting();
if(a==='next-hosting') nextHosting();
if(a==='stop-hosting') stopHosting();
if(a==='cancel-high-risk'){ prototypeState.highRisk=false; prototypeState.rpaHosting=true; prototypeState.agents.find(x=>x.name==='报销审核员').status='waiting_user'; prototypeState.hostingLogs.push('人工确认被取消,任务等待人工处理。'); prototypeState.audits.unshift({time:'刚刚',employee:'报销审核员',app:'企业报销系统',kb:'财务报销制度',status:'running',text:'人工确认被取消'}); }
if(a==='confirm-high-risk'){ prototypeState.highRisk=false; prototypeState.rpaHosting=false; prototypeState.tasks[0].status='succeeded'; prototypeState.agents.find(x=>x.name==='报销审核员').status='idle'; toast('高风险提交已确认,托管任务完成'); }
if(a==='toggle-audit') prototypeState.auditOpen=!prototypeState.auditOpen;
if(a==='retry-task'){ prototypeState.tasks[+el.dataset.index].status='queued'; toast('任务已重新排队'); }
if(a==='config-step') prototypeState.configStep=+el.dataset.index;
if(a==='open-settings') toast('已打开系统设置指引(原型模拟)');
if(a==='recheck-permission'){ toast('权限重新检测通过'); prototypeState.modal=null; }
if(a==='annotation-fail'){ document.querySelectorAll('[data-annotation-error],[data-action="annotation-retry"]').forEach(x=>x.classList.remove('hidden')); return; }
if(a==='annotation-retry') toast('草稿已保留,请再次保存');
if(a==='model-test'){ prototypeState.loadingModel=true; renderAll(); setTimeout(()=>{ prototypeState.loadingModel=false; toast('模型连通性测试通过'); }, 420); return; }
renderAll();
}
function startHosting(){ prototypeState.modal=null; prototypeState.overlay=null; prototypeState.rpaHosting=true; prototypeState.hostingStep=0; prototypeState.hostingLogs=['托管已启动:报销审核员控制企业报销系统。']; prototypeState.tasks[0].status='running'; prototypeState.agents.find(x=>x.name==='报销审核员').status='running'; renderAll(); }
function nextHosting(){ const actions=['读取报销规则','填写金额输入框','定位提交按钮','提交报销单']; const msg=actions[Math.min(prototypeState.hostingStep,3)]; prototypeState.hostingLogs.push(new Date().toLocaleTimeString()+' '+msg); if(msg==='提交报销单'){ prototypeState.rpaHosting=false; prototypeState.highRisk=true; } else prototypeState.hostingStep++; renderAll(); }
function stopHosting(){ prototypeState.rpaHosting=false; prototypeState.highRisk=false; prototypeState.tasks[0].status='canceled'; prototypeState.agents.find(x=>x.name==='报销审核员').status='idle'; prototypeState.audits.unshift({time:'刚刚',employee:'报销审核员',app:'企业报销系统',kb:'财务报销制度',status:'canceled',text:'托管已人工终止,原因已写入审计日志'}); toast('托管已人工终止,原因已写入审计日志'); renderAll(); }
document.addEventListener('keydown', event => { if(event.key==='Escape' && (prototypeState.rpaHosting||prototypeState.highRisk)) stopHosting(); });
document.addEventListener('pointerdown', event => { const box=event.target.closest('[data-drag-box]'); if(!box) return; prototypeState.dragging={startX:event.clientX,startY:event.clientY,origX:prototypeState.draftBounds.x,origY:prototypeState.draftBounds.y}; box.setPointerCapture(event.pointerId); });
document.addEventListener('pointermove', event => { if(!prototypeState.dragging) return; prototypeState.draftBounds.x=Math.max(24, prototypeState.dragging.origX + Math.round(event.clientX-prototypeState.dragging.startX)); prototypeState.draftBounds.y=Math.max(24, prototypeState.dragging.origY + Math.round(event.clientY-prototypeState.dragging.startY)); renderAll(); });
document.addEventListener('pointerup', () => { prototypeState.dragging=false; });
init();
</script>
</body>
</html>