@lsamien/yaovi 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-info.json +22 -3
- package/desktop/main.mjs +1 -1
- package/dist/assets/{AboutDialog-CnNkIRo8.js → AboutDialog-2Zq3b7ma.js} +2 -2
- package/dist/assets/{AboutDialog-CnNkIRo8.js.map → AboutDialog-2Zq3b7ma.js.map} +1 -1
- package/dist/assets/{BotAutomationsView-J9Y7Unoq.js → BotAutomationsView-C4EwHtYy.js} +2 -2
- package/dist/assets/{BotAutomationsView-J9Y7Unoq.js.map → BotAutomationsView-C4EwHtYy.js.map} +1 -1
- package/dist/assets/{BotControlSettingsPanel-CtXD24FZ.js → BotControlSettingsPanel-DJ71O3Kh.js} +2 -2
- package/dist/assets/{BotControlSettingsPanel-CtXD24FZ.js.map → BotControlSettingsPanel-DJ71O3Kh.js.map} +1 -1
- package/dist/assets/BotPluginsDialog-Cgqn4RlP.js +2 -0
- package/dist/assets/{BotPluginsDialog-qRw8dCbX.js.map → BotPluginsDialog-Cgqn4RlP.js.map} +1 -1
- package/dist/assets/{BotSettingsPanel-DbCMOfAW.js → BotSettingsPanel-C_1zAGWJ.js} +3 -3
- package/dist/assets/{BotSettingsPanel-DbCMOfAW.js.map → BotSettingsPanel-C_1zAGWJ.js.map} +1 -1
- package/dist/assets/{ComputerPanel-CI-jDmC3.js → ComputerPanel-CuVl9oX7.js} +2 -2
- package/dist/assets/{ComputerPanel-CI-jDmC3.js.map → ComputerPanel-CuVl9oX7.js.map} +1 -1
- package/dist/assets/{ComputerViewerView-B76D6Fln.js → ComputerViewerView-BoI_aqLr.js} +2 -2
- package/dist/assets/{ComputerViewerView-B76D6Fln.js.map → ComputerViewerView-BoI_aqLr.js.map} +1 -1
- package/dist/assets/ConversationsView-aPMgqa0O.css +1 -0
- package/dist/assets/{ConversationsView-DsuaLKva.js → ConversationsView-kztie5lF.js} +88 -88
- package/dist/assets/ConversationsView-kztie5lF.js.map +1 -0
- package/dist/assets/{LocalVmSettingsPanel-BfXFdMYF.js → LocalVmSettingsPanel-BZ1Jeqqk.js} +4 -4
- package/dist/assets/{LocalVmSettingsPanel-BfXFdMYF.js.map → LocalVmSettingsPanel-BZ1Jeqqk.js.map} +1 -1
- package/dist/assets/{ModelNetworkChoice-DB6cLOBw.js → ModelNetworkChoice-BQ8m2N3V.js} +2 -2
- package/dist/assets/{ModelNetworkChoice-DB6cLOBw.js.map → ModelNetworkChoice-BQ8m2N3V.js.map} +1 -1
- package/dist/assets/{StandaloneDialog-BWUp2Wxo.js → StandaloneDialog-hF3z7bfi.js} +2 -2
- package/dist/assets/{StandaloneDialog-BWUp2Wxo.js.map → StandaloneDialog-hF3z7bfi.js.map} +1 -1
- package/dist/assets/{UpdateCheckDialog-CpZSPlUI.js → UpdateCheckDialog-Bz8JhRh4.js} +2 -2
- package/dist/assets/{UpdateCheckDialog-CpZSPlUI.js.map → UpdateCheckDialog-Bz8JhRh4.js.map} +1 -1
- package/dist/assets/{UsageView-BhiRCw7p.js → UsageView-ZsSbQzIY.js} +3 -3
- package/dist/assets/{UsageView-BhiRCw7p.js.map → UsageView-ZsSbQzIY.js.map} +1 -1
- package/dist/assets/WorkspaceKnowledgePanel-E549Ct1E.js +3 -0
- package/dist/assets/{WorkspaceKnowledgePanel-D34YxglF.js.map → WorkspaceKnowledgePanel-E549Ct1E.js.map} +1 -1
- package/dist/assets/WorkspaceRoutinesPanel-NFWzy1R-.js +2 -0
- package/dist/assets/{WorkspaceRoutinesPanel-Bn-W52Im.js.map → WorkspaceRoutinesPanel-NFWzy1R-.js.map} +1 -1
- package/dist/assets/{WorkstreamsView-CH6qDPvj.js → WorkstreamsView-CxZ8YUvP.js} +2 -2
- package/dist/assets/{WorkstreamsView-CH6qDPvj.js.map → WorkstreamsView-CxZ8YUvP.js.map} +1 -1
- package/dist/assets/index-C-5Gq82f.css +1 -0
- package/dist/assets/{index-DjhQ-RMG.js → index-P_kZCZ2-.js} +18 -18
- package/dist/assets/index-P_kZCZ2-.js.map +1 -0
- package/dist/assets/{pdf-wqiF06Qz.js → pdf-CNS_Rszb.js} +2 -2
- package/dist/assets/{pdf-wqiF06Qz.js.map → pdf-CNS_Rszb.js.map} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/server/remoteAccess/dispatcher.js +5 -1
- package/dist-server/server/workspaceCollaboration.js +9 -12
- package/dist-server/server/workspaceStore.js +3 -1
- package/dist-server/shared/peerExchange.js +26 -0
- package/node_modules/@yaovi/remote-core/index.mjs +13 -10
- package/node_modules/@yaovi/remote-core/package.json +2 -2
- package/node_modules/@yaovi/remote-core/types/remote-core/src/control/recovery.d.ts +1 -1
- package/node_modules/@yaovi/remote-core/types/remote-core/src/control/remote.d.ts +2 -0
- package/node_modules/@yaovi/remote-core/types/remote-core/src/index.d.ts +1 -0
- package/node_modules/@yaovi/remote-core/types/remote-core/src/transport/lan.d.ts +10 -4
- package/node_modules/@yaovi/remote-core/types/remote-core/src/transport/packet.d.ts +2 -2
- package/package.json +2 -2
- package/release.json +3 -3
- package/vendor/yaovi-remote-core-0.2.0-remote.18.tgz +0 -0
- package/dist/assets/BotPluginsDialog-qRw8dCbX.js +0 -2
- package/dist/assets/ConversationsView-DoA0eNiz.css +0 -1
- package/dist/assets/ConversationsView-DsuaLKva.js.map +0 -1
- package/dist/assets/WorkspaceKnowledgePanel-D34YxglF.js +0 -3
- package/dist/assets/WorkspaceRoutinesPanel-Bn-W52Im.js +0 -2
- package/dist/assets/index-DjhQ-RMG.js.map +0 -1
- package/dist/assets/index-DrIgDgnN.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{A as e,F as t,G as n,L as r,M as i,Ot as a,_ as o,a as s,b as c,d as l,et as u,f as d,m as f,p,st as m,t as h,v as g}from"./_plugin-vue_export-helper-CD0r-vVb.js";import{
|
|
2
|
-
`).map(e=>e.trim()).filter(Boolean),...K.value?{allowInsecureLan:!0}:{},...H.value?{computers:{...m.value?{managedBy:`compose`}:{},network:m.value?`none`:E.value,runtime:U.value,imageId:W.value||O}}:{}},q.value.browser={enabled:D.value},
|
|
3
|
-
`],{type:`application/json`})),t=document.createElement(`a`);t.href=e,t.download=`runner.json`,t.click(),setTimeout(()=>URL.revokeObjectURL(e),1e3)}function Q(e){Y(async()=>{await v(`/api/app/admin/runners/${e.id}`,{method:`DELETE`,body:{}}),q.value?.runnerId===e.id&&(q.value=void 0),await J(),b.value=`已停用 ${e.name}`})}return i(()=>{Y(J)}),(e,i)=>(t(),f(`section`,k,[i[30]||=l(`p`,null,`执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。`,-1),d.value?(t(),f(`p`,A,[...i[10]||=[l(`a`,{href:`/api/app/admin/runners/bundle`,download:``},`下载配套执行节点程序`,-1),o(`(包含桌面资源,需要 Node.js 24 或更高版本)。`,-1)]])):p(``,!0),l(`div`,j,[i[11]||=l(`strong`,null,`已注册节点`,-1),l(`button`,{type:`button`,disabled:g.value,onClick:i[0]||=e=>Y(J)},`刷新状态`,8,M)]),h.value.length?(t(),f(`ul`,N,[(t(!0),f(s,null,r(h.value,e=>(t(),f(`li`,{key:e.id},[l(`div`,null,[l(`strong`,null,a(e.name),1),l(`span`,null,a(e.enabled?e.online?`已连接`:`待连接`:`已停用`),1)]),e.enabled?(t(),f(`button`,{key:0,type:`button`,disabled:g.value,"aria-label":`停用 ${e.name}`,onClick:t=>Q(e)},`停用`,8,P)):p(``,!0)]))),128))])):(t(),f(`p`,F,`尚未注册执行节点。`)),q.value?(t(),f(`div`,z,[l(`button`,{class:`solid-button`,type:`button`,onClick:Z},`下载 Runner 配置`),i[29]||=l(`p`,null,`配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:`,-1),d.value?(t(),f(`code`,B,[...i[27]||=[o(`mkdir -p yaovi-runner`,-1),l(`br`,null,null,-1),o(`tar -xzf yaovi-runner.tar.gz -C yaovi-runner`,-1),l(`br`,null,null,-1),o(`chmod 600 runner.json`,-1),l(`br`,null,null,-1),o(`node yaovi-runner/runner.mjs --config /完整路径/runner.json`,-1)]])):(t(),f(`code`,V,[...i[28]||=[o(`chmod 600 runner.json`,-1),l(`br`,null,null,-1),o(`npm run runner -- --config /完整路径/runner.json`,-1)]])),l(`small`,null,a(d.value?q.value.computers?`在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。`:`在执行电脑上启动节点。`:`在 yaovi 项目中先运行 npm run build。`)+`配置丢失后可停用节点并重新注册。`,1)])):(t(),f(`form`,{key:3,onSubmit:S(X,[`prevent`])},[l(`label`,null,[i[12]||=o(`节点名称`,-1),n(l(`input`,{"onUpdate:modelValue":i[1]||=e=>w.value=e,required:``,maxlength:`100`,autocomplete:`off`,placeholder:`我的 Mac`,disabled:g.value},null,8,I),[[C,w.value]])]),l(`label`,null,[i[13]||=o(`Yaovi服务地址`,-1),n(l(`input`,{"onUpdate:modelValue":i[2]||=e=>T.value=e,type:`url`,required:``,disabled:g.value},null,8,L),[[C,T.value]]),i[14]||=l(`small`,null,`必须能从 Runner 所在电脑访问。`,-1)]),l(`label`,null,[i[15]||=o(`允许导出的产物目录`,-1),n(l(`textarea`,{"onUpdate:modelValue":i[3]||=e=>G.value=e,rows:`2`,disabled:g.value,placeholder:`每行一个绝对路径,留空时禁止导出文件`},null,8,R),[[C,G.value]])]),l(`label`,ee,[n(l(`input`,{"onUpdate:modelValue":i[4]||=e=>K.value=e,type:`checkbox`,disabled:g.value},null,8,te),[[_,K.value]]),i[16]||=o(`使用可信局域网 HTTP`,-1)]),l(`label`,ne,[n(l(`input`,{"onUpdate:modelValue":i[5]||=e=>D.value=e,type:`checkbox`,disabled:g.value},null,8,re),[[_,D.value]]),i[17]||=o(`托管浏览器`,-1)]),i[26]||=l(`small`,null,`默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。`,-1),l(`label`,ie,[n(l(`input`,{"onUpdate:modelValue":i[6]||=e=>H.value=e,type:`checkbox`,disabled:g.value},null,8,ae),[[_,H.value]]),i[18]||=o(`启用隔离电脑 Worker`,-1)]),H.value?(t(),f(`fieldset`,oe,[i[24]||=l(`legend`,null,`执行电脑配置`,-1),m.value?(t(),f(`p`,se,`桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。`)):p(``,!0),m.value?p(``,!0):(t(),f(`label`,ce,[i[20]||=o(`容器运行时`,-1),n(l(`select`,{"onUpdate:modelValue":i[7]||=e=>U.value=e},[...i[19]||=[l(`option`,{value:`docker`},`Docker`,-1),l(`option`,{value:`podman`},`Podman`,-1)]],512),[[x,U.value]])])),m.value?p(``,!0):(t(),f(`label`,le,[i[22]||=o(`联网范围`,-1),n(l(`select`,{"onUpdate:modelValue":i[8]||=e=>E.value=e},[...i[21]||=[l(`option`,{value:`public-proxy`},`公网网页(HTTP / HTTPS)`,-1),l(`option`,{value:`none`},`不联网`,-1)]],512),[[x,E.value]])])),m.value?p(``,!0):(t(),f(`small`,ue,`公网模式通过受控代理访问,不开放宿主和内网地址。`)),m.value?p(``,!0):(t(),f(`label`,de,[i[23]||=o(`兼容镜像 ID`,-1),n(l(`input`,{"onUpdate:modelValue":i[9]||=e=>W.value=e,placeholder:`可在本地虚拟机设置中准备`},null,512),[[C,W.value,void 0,{trim:!0}]])])),i[25]||=l(`small`,null,`这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。`,-1)])):p(``,!0),l(`button`,{class:`solid-button`,disabled:g.value||!w.value.trim()},`注册节点`,8,fe)],32)),y.value?(t(),f(`p`,pe,a(y.value),1)):p(``,!0),b.value?(t(),f(`p`,me,a(b.value),1)):p(``,!0)]))}}),[[`__scopeId`,`data-v-da99aca0`]]),U={class:`local-vm-settings`,"aria-label":`本地虚拟机设置`},W={class:`row`},G={class:`status`},K=[`disabled`],q={key:1},J={key:2,role:`alert`},Y={key:3,role:`status`},X={key:0},Z=[`aria-expanded`],Q={key:1},he={key:2},ge={class:`segmented`,role:`group`,"aria-label":`虚拟机隔离方式`},_e=[`aria-pressed`,`disabled`],ve=[`aria-pressed`,`disabled`],ye={class:`row`},be=[`value`,`disabled`],xe=[`value`],Se={key:3},Ce={class:`row`},we=[`value`,`disabled`],Te=[`value`],Ee=[`value`],De={key:0},Oe={key:1},ke={key:4},Ae={key:0},je={class:`step`},Me={class:`step`},Ne={class:`step`},Pe={class:`image-preparation`},$=[`disabled`],Fe=[`value`],Ie=[`disabled`],Le={key:5},Re={key:0},ze={class:`row`},Be=[`disabled`,`onClick`],Ve=[`disabled`,`onClick`],He=[`disabled`,`onClick`],Ue={class:`row`},We=[`disabled`,`onClick`],Ge=[`disabled`,`onClick`],Ke=h(c({__name:`LocalVmSettingsPanel`,setup(c){let h=u(),_=u(``),S=u(!1),C=u(!1),O=u(!1),k=u(`standard`),A=u([]),j=!1,M;async function N(){if(!(S.value||C.value||j)){S.value=!0;try{let e=await v(`/api/app/admin/local-vm`);if(j)return;h.value=e;let t=await v(`/api/app/computers`),n=new Set,r=t.computers.map(e=>e.agent).filter(e=>(e.execution===`computer`||!!e.computerEnvironmentId)&&!e.remoteAgentId&&e.nodeId===`local`&&!n.has(e.computerEnvironmentId??e.id)&&!!n.add(e.computerEnvironmentId??e.id)),i=await Promise.all(r.map(async e=>({agent:e,vm:await v(`/api/app/agents/${e.id}/local-vm`)})));j||(A.value=i)}catch(e){j||(_.value=e instanceof Error?e.message:`无法读取本地虚拟机状态`)}finally{S.value=!1}}}async function P(e){C.value=!0,_.value=``;try{await e()}catch(e){_.value=e instanceof Error?e.message:`操作未完成`}finally{C.value=!1,await N()}}function F(){P(()=>v(`/api/app/admin/local-vm/prepare`,{method:`POST`,body:{requestId:b(),imageKey:k.value}}))}function I(e,t=h.value?.maxInstances??2){e===`shared`&&h.value?.mode!==`shared`&&!confirm(`当前账号的多个机器人将共用桌面、工作文件和浏览器登录。确认使用共享虚拟机?`)||P(()=>v(`/api/app/admin/local-vm/policy`,{method:`PUT`,body:{requestId:b(),mode:e,maxInstances:t}}))}function L(e,t){t===`remove`&&!confirm(`移除“${e.name}”的虚拟机实例?工作文件、软件配置和登录资料将保留。`)||P(()=>v(`/api/app/agents/${e.id}/local-vm/${t}`,{method:`POST`,body:{}}))}async function R(){await N(),j||(M=setTimeout(R,h.value?.busy?2e3:1e4))}return i(R),e(()=>{j=!0,clearTimeout(M)}),(e,i)=>(t(),f(`section`,U,[l(`article`,null,[l(`h3`,null,a(h.value?.fixedCapacity?`Compose 共享桌面`:`本地虚拟机`),1),l(`p`,null,a(h.value?.fixedCapacity?`桌面容器随 Web 的 Compose 文件部署,工作文件与浏览器资料保存在各桌面的数据卷中。`:`在 Yaovi 所在电脑的隔离 Linux 桌面中运行机器人,工作文件与浏览器资料持续保存。`),1),l(`div`,W,[l(`span`,G,[g(y,{name:h.value?.image?`check`:`monitor`},null,8,[`name`]),o(a(S.value&&!h.value?`正在检查…`:h.value?.maintenance?.phase===`ready`?`虚拟机环境已就绪`:h.value?.image?`镜像已准备`:`尚未就绪`),1)]),l(`button`,{disabled:S.value||C.value,onClick:i[0]||=e=>{_.value=``,N()}},[g(y,{name:`refresh`}),i[7]||=o(`重新检查`,-1)],8,K)]),h.value?.maintenance?(t(),d(E,{key:0,state:h.value.maintenance},null,8,[`state`])):p(``,!0),h.value?.problem?(t(),f(`p`,q,a(h.value.problem),1)):p(``,!0),_.value?(t(),f(`p`,J,a(_.value),1)):p(``,!0),h.value?.job&&h.value.job.state!==`complete`?(t(),f(`p`,Y,a(h.value.job.message),1)):p(``,!0)]),h.value?.executionHost===`runner`?(t(),f(`article`,X,[i[8]||=l(`h3`,null,`Yaovi 执行连接`,-1),l(`p`,null,a(h.value.fixedCapacity?`执行连接负责 Yaovi 模型与工具调用,桌面容器完全由这套 Compose 管理。`:`虚拟桌面由运行 Yaovi 的执行节点提供,Docker 版 Web 通过这条连接使用桌面。`)+a(h.value.runnerName?`当前节点:${h.value.runnerName}。`:``),1),l(`button`,{"aria-expanded":O.value,onClick:i[1]||=e=>O.value=!O.value},a(O.value?`收起执行节点设置`:`打开执行节点设置`),9,Z),O.value?(t(),d(H,{key:0,"computer-setup":``})):p(``,!0)])):p(``,!0),h.value?.fixedCapacity?(t(),f(`article`,Q,[i[9]||=l(`h3`,null,`Compose 共享桌面`,-1),l(`p`,null,`已配置 `+a(h.value.maxInstances)+` 台桌面,数量由 Compose 固定。请在机器人聊天的电脑面板选择已有桌面。`,1),(t(!0),f(s,null,r(h.value.desktops,e=>(t(),f(`div`,{key:e.id,class:`instance`},[l(`strong`,null,a(e.name)+` · `+a(e.imageKey===`cursor`?`Cursor Universal`:`标准桌面`),1),l(`span`,null,a(e.ready?`已就绪`:e.online?`正在启动`:`未连接`),1)]))),128))])):h.value?(t(),f(`article`,he,[i[11]||=l(`h3`,null,`隔离方式`,-1),i[12]||=l(`p`,null,`按机器人创建独立桌面,或让可信的机器人共用一个桌面。`,-1),l(`div`,ge,[l(`button`,{"aria-pressed":h.value?.mode===`shared`,disabled:!h.value?.configured||!!h.value?.setupRequired||C.value||h.value.busy,onClick:i[2]||=e=>I(`shared`)},`共享虚拟机`,8,_e),l(`button`,{"aria-pressed":h.value?.mode===`per-bot`,disabled:!h.value?.configured||!!h.value?.setupRequired||C.value||h.value.busy,onClick:i[3]||=e=>I(`per-bot`)},`每个机器人独立`,8,ve)]),i[13]||=l(`p`,{class:`sharing-explanation`},`共享指多个机器人同时使用同一台虚拟机:命令与文件操作并行执行,桌面操作交替进行,共用桌面和工作文件。`,-1),l(`label`,ye,[i[10]||=o(`虚拟机运行数量上限`,-1),l(`select`,{"aria-label":`虚拟机数量上限`,value:h.value?.maxInstances??2,disabled:!h.value?.configured||!!h.value?.setupRequired||C.value||h.value.busy,onChange:i[4]||=e=>I(h.value.mode,Number(e.target.value))},[(t(),f(s,null,r([1,2,3,4],e=>l(`option`,{key:e,value:e},a(e),9,xe)),64))],40,be)])])):p(``,!0),h.value&&!h.value.fixedCapacity?(t(),f(`article`,Se,[i[15]||=l(`h3`,null,`空闲自动停止`,-1),i[16]||=l(`p`,null,`从任务结束或交还控制权后开始计时,适用于该执行节点上的所有本地虚拟机。`,-1),l(`label`,Ce,[i[14]||=o(`停止时间`,-1),l(`select`,{"aria-label":`虚拟机空闲停止时间`,value:h.value.idleStopMinutes??5,disabled:C.value||!h.value.configured||h.value.idleStopMinutes===void 0,onChange:i[5]||=e=>P(()=>m(v)(`/api/app/admin/local-vm/idle-policy`,{method:`PUT`,body:{requestId:m(b)(),idleStopMinutes:Number(e.target.value)}}))},[(t(!0),f(s,null,r(m(w),e=>(t(),f(`option`,{key:e,value:e},a(m(T)(e)),9,Te))),128)),h.value.idleStopMinutes!==void 0&&!m(w).some(e=>e===h.value.idleStopMinutes)?(t(),f(`option`,{key:0,value:h.value.idleStopMinutes},a(m(T)(h.value.idleStopMinutes)),9,Ee)):p(``,!0)],40,we)]),h.value.idleStopMinutes===void 0?(t(),f(`p`,De,`连接并更新执行节点后可修改。`)):h.value.idleStopMinutes===0?(t(),f(`p`,Oe,`空闲时保持运行;仍可手动停止,执行节点关闭时也会停止。`)):p(``,!0)])):p(``,!0),h.value&&!h.value.fixedCapacity&&!h.value.setupRequired?(t(),f(`article`,ke,[i[23]||=l(`h3`,null,`准备运行环境`,-1),h.value?.executionHost===`runner`?(t(),f(`p`,Ae,`以下检查和镜像准备均在执行节点所在电脑完成。`)):p(``,!0),l(`ol`,null,[l(`li`,null,[l(`span`,je,a(h.value?.runtime?`✓`:`1`),1),l(`div`,null,[i[17]||=l(`strong`,null,`安装 Docker 或 Podman`,-1),l(`p`,null,a(h.value?.runtime?`已检测到 ${h.value.runtime}`:`请先安装 Docker Desktop 或 Podman。`),1)])]),l(`li`,null,[l(`span`,Me,a(h.value?.daemonUp?`✓`:`2`),1),l(`div`,null,[i[18]||=l(`strong`,null,`启动容器运行环境`,-1),l(`p`,null,a(h.value?.daemonUp?`运行环境已启动`:`打开 Docker / Podman,等待它完成启动后重新检查。`),1)])]),l(`li`,null,[l(`span`,Ne,a(h.value?.image?`✓`:`3`),1),l(`div`,Pe,[i[20]||=l(`strong`,null,`准备虚拟机镜像`,-1),i[21]||=l(`p`,null,`两个镜像可分别准备。独立桌面可各自选择;同一共享桌面的所有机器人使用一个镜像。`,-1),l(`label`,null,[i[19]||=o(`镜像`,-1),n(l(`select`,{"onUpdate:modelValue":i[6]||=e=>k.value=e,disabled:C.value||h.value.busy},[(t(!0),f(s,null,r(m(D),e=>(t(),f(`option`,{key:e.key,value:e.key},a(e.name)+` · `+a(h.value.images?.find(t=>t.key===e.key)?.ready?`已就绪`:`未准备`),9,Fe))),128))],8,$),[[x,k.value]])]),l(`p`,null,a(m(D).find(e=>e.key===k.value)?.description)+a(k.value===`cursor`?`;Apple 芯片电脑需要运行环境支持 amd64 模拟。`:``),1),l(`button`,{class:`primary`,disabled:C.value||h.value?.busy||!h.value?.daemonUp,onClick:F},a(h.value?.busy?`正在准备…`:h.value.images?.find(e=>e.key===k.value)?.ready?`重新检查所选镜像`:`准备所选镜像`),9,Ie)])]),l(`li`,null,[i[22]||=l(`span`,{class:`step`},`4`,-1),l(`div`,null,[l(`strong`,null,a(h.value.maintenance?`自动启动与维护`:`从机器人聊天中创建桌面`),1),l(`p`,null,a(h.value.maintenance?`运行环境就绪后会自动准备并启动桌面。可在聊天的“电脑”中查看和使用;手动停止后由新任务或明确启动唤醒。`:`打开聊天的“电脑”,选择“本地虚拟机”,然后创建该机器人的虚拟机。`),1)])])])])):p(``,!0),h.value&&!h.value.fixedCapacity?(t(),f(`article`,Le,[l(`h3`,null,a(h.value?.mode===`shared`?`共享虚拟机`:`机器人虚拟机`),1),A.value.length?p(``,!0):(t(),f(`p`,Re,`还没有机器人使用本地虚拟机。`)),(t(!0),f(s,null,r(A.value,e=>(t(),f(`div`,{key:e.agent.id,class:`instance`},[l(`div`,null,[l(`strong`,null,a(e.agent.computerEnvironmentName??e.agent.name),1),l(`p`,null,a(e.vm.inUse?`正在使用`:{running:`运行中`,stopped:`已停止`,missing:`尚未创建`}[e.vm.container]),1)]),l(`div`,ze,[e.vm.container===`running`?(t(),f(`button`,{key:0,disabled:C.value||e.vm.inUse,onClick:t=>L(e.agent,`stop`)},`停止`,8,Be)):(t(),f(`button`,{key:1,disabled:C.value||!h.value?.image,onClick:t=>L(e.agent,e.vm.container===`missing`?`create`:`start`)},a(e.vm.container===`missing`?`创建`:`启动`),9,Ve)),e.vm.container===`missing`?p(``,!0):(t(),f(`button`,{key:2,disabled:C.value||e.vm.inUse,onClick:t=>L(e.agent,`remove`)},`移除实例`,8,He))])]))),128)),(t(!0),f(s,null,r(h.value?.instances?.filter(e=>e.orphaned),e=>(t(),f(`div`,{key:e.id,class:`instance`},[l(`div`,null,[l(`strong`,null,a(e.name),1),l(`p`,null,`暂无机器人使用 · `+a(e.status===`free`?`已停止`:`运行中或待核对`),1)]),l(`div`,Ue,[l(`button`,{disabled:C.value,onClick:t=>P(()=>m(v)(`/api/app/admin/local-vm/instances/${e.id}/stop`,{method:`POST`,body:{requestId:m(b)()}}))},`停止`,8,We),l(`button`,{disabled:C.value,onClick:t=>P(()=>m(v)(`/api/app/admin/local-vm/instances/${e.id}/remove`,{method:`POST`,body:{requestId:m(b)()}}))},`移除实例`,8,Ge)])]))),128))])):p(``,!0)]))}}),[[`__scopeId`,`data-v-640d666b`]]);export{Ke as default};
|
|
4
|
-
//# sourceMappingURL=LocalVmSettingsPanel-
|
|
1
|
+
import{A as e,F as t,G as n,L as r,M as i,Ot as a,_ as o,a as s,b as c,d as l,et as u,f as d,m as f,p,st as m,t as h,v as g}from"./_plugin-vue_export-helper-CD0r-vVb.js";import{B as _,at as v,i as y,j as b,lt as x,nt as S,ot as C}from"./index-P_kZCZ2-.js";import{d as w,f as T,p as E,u as D}from"./ConversationsView-kztie5lF.js";var O=`sha256:`+`0`.repeat(64),k={class:`runner-panel`,"aria-label":`执行节点设置`},A={key:0},j={class:`runner-toolbar`},M=[`disabled`],N={key:1,class:`runner-list`},P=[`disabled`,`aria-label`,`onClick`],F={key:2},I=[`disabled`],L=[`disabled`],R=[`disabled`],ee={class:`runner-check`},te=[`disabled`],ne={class:`runner-check`},re=[`disabled`],ie={class:`runner-check`},ae=[`disabled`],oe={key:0,class:`runner-computer`},se={key:0},ce={key:1},le={key:2},ue={key:3},de={key:4},fe=[`disabled`],z={key:4,class:`runner-setup`},B={key:0},V={key:1},pe={key:5,class:`runner-error`,role:`alert`},me={key:6,role:`status`},H=h(c({__name:`RunnerSettingsPanel`,props:{computerSetup:{type:Boolean}},setup(e){let c=e,d=u(!1),m=u(!1),h=u([]),g=u(!1),_=u(``),y=u(``),w=u(``),T=u(window.location.origin),E=u(`public-proxy`),D=u(!0),H=u(!!c.computerSetup),U=u(`docker`),W=u(``),G=u(``),K=u(!1),q=u();async function J(){let e=await b(`/api/app/admin/runners`);h.value=e.runners,d.value=!!e.bundleAvailable,m.value=!!e.fixedDesktops}async function Y(e){if(!g.value){_.value=``,y.value=``,g.value=!0;try{await e()}catch(e){_.value=e instanceof Error?e.message:`执行节点操作失败`}finally{g.value=!1}}}async function X(){await Y(async()=>{let e=new URL(T.value);if(![`http:`,`https:`].includes(e.protocol)||e.username||e.password||e.search||e.hash||e.pathname!==`/`)throw Error(`请填写不带路径的Yaovi服务地址`);if(e.protocol===`http:`&&![`localhost`,`127.0.0.1`,`[::1]`].includes(e.hostname)&&!K.value)throw Error(`远程节点请使用 HTTPS,或勾选可信局域网 HTTP`);if(H.value&&W.value&&!/^sha256:[a-f0-9]{64}$/.test(W.value))throw Error(`自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。`);let t=await b(`/api/app/admin/runners`,{method:`POST`,body:{name:w.value.trim()}});q.value={protocol:1,serverURL:e.origin,runnerId:t.runner.id,token:t.token,artifactRoots:G.value.split(`
|
|
2
|
+
`).map(e=>e.trim()).filter(Boolean),...K.value?{allowInsecureLan:!0}:{},...H.value?{computers:{...m.value?{managedBy:`compose`}:{},network:m.value?`none`:E.value,runtime:U.value,imageId:W.value||O}}:{}},q.value.browser={enabled:D.value},y.value=`节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。`,await J()})}function Z(){if(!q.value)return;let e=URL.createObjectURL(new Blob([JSON.stringify(q.value,null,2)+`
|
|
3
|
+
`],{type:`application/json`})),t=document.createElement(`a`);t.href=e,t.download=`runner.json`,t.click(),setTimeout(()=>URL.revokeObjectURL(e),1e3)}function Q(e){Y(async()=>{await b(`/api/app/admin/runners/${e.id}`,{method:`DELETE`,body:{}}),q.value?.runnerId===e.id&&(q.value=void 0),await J(),y.value=`已停用 ${e.name}`})}return i(()=>{Y(J)}),(e,i)=>(t(),f(`section`,k,[i[30]||=l(`p`,null,`执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。`,-1),d.value?(t(),f(`p`,A,[...i[10]||=[l(`a`,{href:`/api/app/admin/runners/bundle`,download:``},`下载配套执行节点程序`,-1),o(`(包含桌面资源,需要 Node.js 24 或更高版本)。`,-1)]])):p(``,!0),l(`div`,j,[i[11]||=l(`strong`,null,`已注册节点`,-1),l(`button`,{type:`button`,disabled:g.value,onClick:i[0]||=e=>Y(J)},`刷新状态`,8,M)]),h.value.length?(t(),f(`ul`,N,[(t(!0),f(s,null,r(h.value,e=>(t(),f(`li`,{key:e.id},[l(`div`,null,[l(`strong`,null,a(e.name),1),l(`span`,null,a(e.enabled?e.online?`已连接`:`待连接`:`已停用`),1)]),e.enabled?(t(),f(`button`,{key:0,type:`button`,disabled:g.value,"aria-label":`停用 ${e.name}`,onClick:t=>Q(e)},`停用`,8,P)):p(``,!0)]))),128))])):(t(),f(`p`,F,`尚未注册执行节点。`)),q.value?(t(),f(`div`,z,[l(`button`,{class:`solid-button`,type:`button`,onClick:Z},`下载 Runner 配置`),i[29]||=l(`p`,null,`配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:`,-1),d.value?(t(),f(`code`,B,[...i[27]||=[o(`mkdir -p yaovi-runner`,-1),l(`br`,null,null,-1),o(`tar -xzf yaovi-runner.tar.gz -C yaovi-runner`,-1),l(`br`,null,null,-1),o(`chmod 600 runner.json`,-1),l(`br`,null,null,-1),o(`node yaovi-runner/runner.mjs --config /完整路径/runner.json`,-1)]])):(t(),f(`code`,V,[...i[28]||=[o(`chmod 600 runner.json`,-1),l(`br`,null,null,-1),o(`npm run runner -- --config /完整路径/runner.json`,-1)]])),l(`small`,null,a(d.value?q.value.computers?`在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。`:`在执行电脑上启动节点。`:`在 yaovi 项目中先运行 npm run build。`)+`配置丢失后可停用节点并重新注册。`,1)])):(t(),f(`form`,{key:3,onSubmit:x(X,[`prevent`])},[l(`label`,null,[i[12]||=o(`节点名称`,-1),n(l(`input`,{"onUpdate:modelValue":i[1]||=e=>w.value=e,required:``,maxlength:`100`,autocomplete:`off`,placeholder:`我的 Mac`,disabled:g.value},null,8,I),[[C,w.value]])]),l(`label`,null,[i[13]||=o(`Yaovi服务地址`,-1),n(l(`input`,{"onUpdate:modelValue":i[2]||=e=>T.value=e,type:`url`,required:``,disabled:g.value},null,8,L),[[C,T.value]]),i[14]||=l(`small`,null,`必须能从 Runner 所在电脑访问。`,-1)]),l(`label`,null,[i[15]||=o(`允许导出的产物目录`,-1),n(l(`textarea`,{"onUpdate:modelValue":i[3]||=e=>G.value=e,rows:`2`,disabled:g.value,placeholder:`每行一个绝对路径,留空时禁止导出文件`},null,8,R),[[C,G.value]])]),l(`label`,ee,[n(l(`input`,{"onUpdate:modelValue":i[4]||=e=>K.value=e,type:`checkbox`,disabled:g.value},null,8,te),[[S,K.value]]),i[16]||=o(`使用可信局域网 HTTP`,-1)]),l(`label`,ne,[n(l(`input`,{"onUpdate:modelValue":i[5]||=e=>D.value=e,type:`checkbox`,disabled:g.value},null,8,re),[[S,D.value]]),i[17]||=o(`托管浏览器`,-1)]),i[26]||=l(`small`,null,`默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。`,-1),l(`label`,ie,[n(l(`input`,{"onUpdate:modelValue":i[6]||=e=>H.value=e,type:`checkbox`,disabled:g.value},null,8,ae),[[S,H.value]]),i[18]||=o(`启用隔离电脑 Worker`,-1)]),H.value?(t(),f(`fieldset`,oe,[i[24]||=l(`legend`,null,`执行电脑配置`,-1),m.value?(t(),f(`p`,se,`桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。`)):p(``,!0),m.value?p(``,!0):(t(),f(`label`,ce,[i[20]||=o(`容器运行时`,-1),n(l(`select`,{"onUpdate:modelValue":i[7]||=e=>U.value=e},[...i[19]||=[l(`option`,{value:`docker`},`Docker`,-1),l(`option`,{value:`podman`},`Podman`,-1)]],512),[[v,U.value]])])),m.value?p(``,!0):(t(),f(`label`,le,[i[22]||=o(`联网范围`,-1),n(l(`select`,{"onUpdate:modelValue":i[8]||=e=>E.value=e},[...i[21]||=[l(`option`,{value:`public-proxy`},`公网网页(HTTP / HTTPS)`,-1),l(`option`,{value:`none`},`不联网`,-1)]],512),[[v,E.value]])])),m.value?p(``,!0):(t(),f(`small`,ue,`公网模式通过受控代理访问,不开放宿主和内网地址。`)),m.value?p(``,!0):(t(),f(`label`,de,[i[23]||=o(`兼容镜像 ID`,-1),n(l(`input`,{"onUpdate:modelValue":i[9]||=e=>W.value=e,placeholder:`可在本地虚拟机设置中准备`},null,512),[[C,W.value,void 0,{trim:!0}]])])),i[25]||=l(`small`,null,`这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。`,-1)])):p(``,!0),l(`button`,{class:`solid-button`,disabled:g.value||!w.value.trim()},`注册节点`,8,fe)],32)),_.value?(t(),f(`p`,pe,a(_.value),1)):p(``,!0),y.value?(t(),f(`p`,me,a(y.value),1)):p(``,!0)]))}}),[[`__scopeId`,`data-v-da99aca0`]]),U={class:`local-vm-settings`,"aria-label":`本地虚拟机设置`},W={class:`row`},G={class:`status`},K=[`disabled`],q={key:1},J={key:2,role:`alert`},Y={key:3,role:`status`},X={key:0},Z=[`aria-expanded`],Q={key:1},he={key:2},ge={class:`segmented`,role:`group`,"aria-label":`虚拟机隔离方式`},_e=[`aria-pressed`,`disabled`],ve=[`aria-pressed`,`disabled`],ye={class:`row`},be=[`value`,`disabled`],xe=[`value`],Se={key:3},Ce={class:`row`},we=[`value`,`disabled`],Te=[`value`],Ee=[`value`],De={key:0},Oe={key:1},ke={key:4},Ae={key:0},je={class:`step`},Me={class:`step`},Ne={class:`step`},Pe={class:`image-preparation`},$=[`disabled`],Fe=[`value`],Ie=[`disabled`],Le={key:5},Re={key:0},ze={class:`row`},Be=[`disabled`,`onClick`],Ve=[`disabled`,`onClick`],He=[`disabled`,`onClick`],Ue={class:`row`},We=[`disabled`,`onClick`],Ge=[`disabled`,`onClick`],Ke=h(c({__name:`LocalVmSettingsPanel`,setup(c){let h=u(),x=u(``),S=u(!1),C=u(!1),O=u(!1),k=u(`standard`),A=u([]),j=!1,M;async function N(){if(!(S.value||C.value||j)){S.value=!0;try{let e=await b(`/api/app/admin/local-vm`);if(j)return;h.value=e;let t=await b(`/api/app/computers`),n=new Set,r=t.computers.map(e=>e.agent).filter(e=>(e.execution===`computer`||!!e.computerEnvironmentId)&&!e.remoteAgentId&&e.nodeId===`local`&&!n.has(e.computerEnvironmentId??e.id)&&!!n.add(e.computerEnvironmentId??e.id)),i=await Promise.all(r.map(async e=>({agent:e,vm:await b(`/api/app/agents/${e.id}/local-vm`)})));j||(A.value=i)}catch(e){j||(x.value=e instanceof Error?e.message:`无法读取本地虚拟机状态`)}finally{S.value=!1}}}async function P(e){C.value=!0,x.value=``;try{await e()}catch(e){x.value=e instanceof Error?e.message:`操作未完成`}finally{C.value=!1,await N()}}function F(){P(()=>b(`/api/app/admin/local-vm/prepare`,{method:`POST`,body:{requestId:y(),imageKey:k.value}}))}function I(e,t=h.value?.maxInstances??2){e===`shared`&&h.value?.mode!==`shared`&&!confirm(`当前账号的多个机器人将共用桌面、工作文件和浏览器登录。确认使用共享虚拟机?`)||P(()=>b(`/api/app/admin/local-vm/policy`,{method:`PUT`,body:{requestId:y(),mode:e,maxInstances:t}}))}function L(e,t){t===`remove`&&!confirm(`移除“${e.name}”的虚拟机实例?工作文件、软件配置和登录资料将保留。`)||P(()=>b(`/api/app/agents/${e.id}/local-vm/${t}`,{method:`POST`,body:{}}))}async function R(){await N(),j||(M=setTimeout(R,h.value?.busy?2e3:1e4))}return i(R),e(()=>{j=!0,clearTimeout(M)}),(e,i)=>(t(),f(`section`,U,[l(`article`,null,[l(`h3`,null,a(h.value?.fixedCapacity?`Compose 共享桌面`:`本地虚拟机`),1),l(`p`,null,a(h.value?.fixedCapacity?`桌面容器随 Web 的 Compose 文件部署,工作文件与浏览器资料保存在各桌面的数据卷中。`:`在 Yaovi 所在电脑的隔离 Linux 桌面中运行机器人,工作文件与浏览器资料持续保存。`),1),l(`div`,W,[l(`span`,G,[g(_,{name:h.value?.image?`check`:`monitor`},null,8,[`name`]),o(a(S.value&&!h.value?`正在检查…`:h.value?.maintenance?.phase===`ready`?`虚拟机环境已就绪`:h.value?.image?`镜像已准备`:`尚未就绪`),1)]),l(`button`,{disabled:S.value||C.value,onClick:i[0]||=e=>{x.value=``,N()}},[g(_,{name:`refresh`}),i[7]||=o(`重新检查`,-1)],8,K)]),h.value?.maintenance?(t(),d(E,{key:0,state:h.value.maintenance},null,8,[`state`])):p(``,!0),h.value?.problem?(t(),f(`p`,q,a(h.value.problem),1)):p(``,!0),x.value?(t(),f(`p`,J,a(x.value),1)):p(``,!0),h.value?.job&&h.value.job.state!==`complete`?(t(),f(`p`,Y,a(h.value.job.message),1)):p(``,!0)]),h.value?.executionHost===`runner`?(t(),f(`article`,X,[i[8]||=l(`h3`,null,`Yaovi 执行连接`,-1),l(`p`,null,a(h.value.fixedCapacity?`执行连接负责 Yaovi 模型与工具调用,桌面容器完全由这套 Compose 管理。`:`虚拟桌面由运行 Yaovi 的执行节点提供,Docker 版 Web 通过这条连接使用桌面。`)+a(h.value.runnerName?`当前节点:${h.value.runnerName}。`:``),1),l(`button`,{"aria-expanded":O.value,onClick:i[1]||=e=>O.value=!O.value},a(O.value?`收起执行节点设置`:`打开执行节点设置`),9,Z),O.value?(t(),d(H,{key:0,"computer-setup":``})):p(``,!0)])):p(``,!0),h.value?.fixedCapacity?(t(),f(`article`,Q,[i[9]||=l(`h3`,null,`Compose 共享桌面`,-1),l(`p`,null,`已配置 `+a(h.value.maxInstances)+` 台桌面,数量由 Compose 固定。请在机器人聊天的电脑面板选择已有桌面。`,1),(t(!0),f(s,null,r(h.value.desktops,e=>(t(),f(`div`,{key:e.id,class:`instance`},[l(`strong`,null,a(e.name)+` · `+a(e.imageKey===`cursor`?`Cursor Universal`:`标准桌面`),1),l(`span`,null,a(e.ready?`已就绪`:e.online?`正在启动`:`未连接`),1)]))),128))])):h.value?(t(),f(`article`,he,[i[11]||=l(`h3`,null,`隔离方式`,-1),i[12]||=l(`p`,null,`按机器人创建独立桌面,或让可信的机器人共用一个桌面。`,-1),l(`div`,ge,[l(`button`,{"aria-pressed":h.value?.mode===`shared`,disabled:!h.value?.configured||!!h.value?.setupRequired||C.value||h.value.busy,onClick:i[2]||=e=>I(`shared`)},`共享虚拟机`,8,_e),l(`button`,{"aria-pressed":h.value?.mode===`per-bot`,disabled:!h.value?.configured||!!h.value?.setupRequired||C.value||h.value.busy,onClick:i[3]||=e=>I(`per-bot`)},`每个机器人独立`,8,ve)]),i[13]||=l(`p`,{class:`sharing-explanation`},`共享指多个机器人同时使用同一台虚拟机:命令与文件操作并行执行,桌面操作交替进行,共用桌面和工作文件。`,-1),l(`label`,ye,[i[10]||=o(`虚拟机运行数量上限`,-1),l(`select`,{"aria-label":`虚拟机数量上限`,value:h.value?.maxInstances??2,disabled:!h.value?.configured||!!h.value?.setupRequired||C.value||h.value.busy,onChange:i[4]||=e=>I(h.value.mode,Number(e.target.value))},[(t(),f(s,null,r([1,2,3,4],e=>l(`option`,{key:e,value:e},a(e),9,xe)),64))],40,be)])])):p(``,!0),h.value&&!h.value.fixedCapacity?(t(),f(`article`,Se,[i[15]||=l(`h3`,null,`空闲自动停止`,-1),i[16]||=l(`p`,null,`从任务结束或交还控制权后开始计时,适用于该执行节点上的所有本地虚拟机。`,-1),l(`label`,Ce,[i[14]||=o(`停止时间`,-1),l(`select`,{"aria-label":`虚拟机空闲停止时间`,value:h.value.idleStopMinutes??5,disabled:C.value||!h.value.configured||h.value.idleStopMinutes===void 0,onChange:i[5]||=e=>P(()=>m(b)(`/api/app/admin/local-vm/idle-policy`,{method:`PUT`,body:{requestId:m(y)(),idleStopMinutes:Number(e.target.value)}}))},[(t(!0),f(s,null,r(m(w),e=>(t(),f(`option`,{key:e,value:e},a(m(T)(e)),9,Te))),128)),h.value.idleStopMinutes!==void 0&&!m(w).some(e=>e===h.value.idleStopMinutes)?(t(),f(`option`,{key:0,value:h.value.idleStopMinutes},a(m(T)(h.value.idleStopMinutes)),9,Ee)):p(``,!0)],40,we)]),h.value.idleStopMinutes===void 0?(t(),f(`p`,De,`连接并更新执行节点后可修改。`)):h.value.idleStopMinutes===0?(t(),f(`p`,Oe,`空闲时保持运行;仍可手动停止,执行节点关闭时也会停止。`)):p(``,!0)])):p(``,!0),h.value&&!h.value.fixedCapacity&&!h.value.setupRequired?(t(),f(`article`,ke,[i[23]||=l(`h3`,null,`准备运行环境`,-1),h.value?.executionHost===`runner`?(t(),f(`p`,Ae,`以下检查和镜像准备均在执行节点所在电脑完成。`)):p(``,!0),l(`ol`,null,[l(`li`,null,[l(`span`,je,a(h.value?.runtime?`✓`:`1`),1),l(`div`,null,[i[17]||=l(`strong`,null,`安装 Docker 或 Podman`,-1),l(`p`,null,a(h.value?.runtime?`已检测到 ${h.value.runtime}`:`请先安装 Docker Desktop 或 Podman。`),1)])]),l(`li`,null,[l(`span`,Me,a(h.value?.daemonUp?`✓`:`2`),1),l(`div`,null,[i[18]||=l(`strong`,null,`启动容器运行环境`,-1),l(`p`,null,a(h.value?.daemonUp?`运行环境已启动`:`打开 Docker / Podman,等待它完成启动后重新检查。`),1)])]),l(`li`,null,[l(`span`,Ne,a(h.value?.image?`✓`:`3`),1),l(`div`,Pe,[i[20]||=l(`strong`,null,`准备虚拟机镜像`,-1),i[21]||=l(`p`,null,`两个镜像可分别准备。独立桌面可各自选择;同一共享桌面的所有机器人使用一个镜像。`,-1),l(`label`,null,[i[19]||=o(`镜像`,-1),n(l(`select`,{"onUpdate:modelValue":i[6]||=e=>k.value=e,disabled:C.value||h.value.busy},[(t(!0),f(s,null,r(m(D),e=>(t(),f(`option`,{key:e.key,value:e.key},a(e.name)+` · `+a(h.value.images?.find(t=>t.key===e.key)?.ready?`已就绪`:`未准备`),9,Fe))),128))],8,$),[[v,k.value]])]),l(`p`,null,a(m(D).find(e=>e.key===k.value)?.description)+a(k.value===`cursor`?`;Apple 芯片电脑需要运行环境支持 amd64 模拟。`:``),1),l(`button`,{class:`primary`,disabled:C.value||h.value?.busy||!h.value?.daemonUp,onClick:F},a(h.value?.busy?`正在准备…`:h.value.images?.find(e=>e.key===k.value)?.ready?`重新检查所选镜像`:`准备所选镜像`),9,Ie)])]),l(`li`,null,[i[22]||=l(`span`,{class:`step`},`4`,-1),l(`div`,null,[l(`strong`,null,a(h.value.maintenance?`自动启动与维护`:`从机器人聊天中创建桌面`),1),l(`p`,null,a(h.value.maintenance?`运行环境就绪后会自动准备并启动桌面。可在聊天的“电脑”中查看和使用;手动停止后由新任务或明确启动唤醒。`:`打开聊天的“电脑”,选择“本地虚拟机”,然后创建该机器人的虚拟机。`),1)])])])])):p(``,!0),h.value&&!h.value.fixedCapacity?(t(),f(`article`,Le,[l(`h3`,null,a(h.value?.mode===`shared`?`共享虚拟机`:`机器人虚拟机`),1),A.value.length?p(``,!0):(t(),f(`p`,Re,`还没有机器人使用本地虚拟机。`)),(t(!0),f(s,null,r(A.value,e=>(t(),f(`div`,{key:e.agent.id,class:`instance`},[l(`div`,null,[l(`strong`,null,a(e.agent.computerEnvironmentName??e.agent.name),1),l(`p`,null,a(e.vm.inUse?`正在使用`:{running:`运行中`,stopped:`已停止`,missing:`尚未创建`}[e.vm.container]),1)]),l(`div`,ze,[e.vm.container===`running`?(t(),f(`button`,{key:0,disabled:C.value||e.vm.inUse,onClick:t=>L(e.agent,`stop`)},`停止`,8,Be)):(t(),f(`button`,{key:1,disabled:C.value||!h.value?.image,onClick:t=>L(e.agent,e.vm.container===`missing`?`create`:`start`)},a(e.vm.container===`missing`?`创建`:`启动`),9,Ve)),e.vm.container===`missing`?p(``,!0):(t(),f(`button`,{key:2,disabled:C.value||e.vm.inUse,onClick:t=>L(e.agent,`remove`)},`移除实例`,8,He))])]))),128)),(t(!0),f(s,null,r(h.value?.instances?.filter(e=>e.orphaned),e=>(t(),f(`div`,{key:e.id,class:`instance`},[l(`div`,null,[l(`strong`,null,a(e.name),1),l(`p`,null,`暂无机器人使用 · `+a(e.status===`free`?`已停止`:`运行中或待核对`),1)]),l(`div`,Ue,[l(`button`,{disabled:C.value,onClick:t=>P(()=>m(b)(`/api/app/admin/local-vm/instances/${e.id}/stop`,{method:`POST`,body:{requestId:m(y)()}}))},`停止`,8,We),l(`button`,{disabled:C.value,onClick:t=>P(()=>m(b)(`/api/app/admin/local-vm/instances/${e.id}/remove`,{method:`POST`,body:{requestId:m(y)()}}))},`移除实例`,8,Ge)])]))),128))])):p(``,!0)]))}}),[[`__scopeId`,`data-v-640d666b`]]);export{Ke as default};
|
|
4
|
+
//# sourceMappingURL=LocalVmSettingsPanel-BZ1Jeqqk.js.map
|
package/dist/assets/{LocalVmSettingsPanel-BfXFdMYF.js.map → LocalVmSettingsPanel-BZ1Jeqqk.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalVmSettingsPanel-BfXFdMYF.js","names":[],"sources":["../../src/shared/runner.ts","../../src/client/components/app/RunnerSettingsPanel.vue","../../src/client/components/app/RunnerSettingsPanel.vue","../../src/client/components/app/LocalVmSettingsPanel.vue","../../src/client/components/app/LocalVmSettingsPanel.vue"],"sourcesContent":["export const RUNNER_PROTOCOL = 1\nexport const VM_NETWORK_FEATURE = 'vm-network-policy-v2'\nexport const VM_MAINTENANCE_FEATURE = 'vm-maintenance-v1'\nexport interface RunnerRecord {\n id: string\n name: string\n sourceNodeId: string\n sourceOwner: string\n tokenHash: string\n enabled: boolean\n createdAt: number\n}\nexport interface RunnerCommand {\n id: string\n kind: 'computer.cleanup' | 'network.configure' | 'network.test' | 'browser.invoke' | 'local-vm.manage' | 'computer.control' | 'computer.retire' | 'http' | 'gateway.open' | 'gateway.rpc' | 'gateway.close' | 'lease.create' | 'lease.bind' | 'lease.close'\n payload: Record<string, unknown>\n expiresAt: number\n}\nexport interface RunnerConfiguration {\n protocol: 1\n serverURL: string\n runnerId: string\n token: string\n artifactRoots: string[]\n allowInsecureLan?: boolean\n /** Opt-in, independent of the full Linux desktop. */\n browser?: { enabled: boolean; maxSessions?: number; idleTimeoutMs?: number }\n computers?: {managedBy?:'compose';network?:'none'|'public-proxy';runtime:'docker'|'podman';imageId:string;maxConcurrent?:number}\n}\nexport const UNCONFIGURED_COMPUTER_IMAGE='sha256:'+'0'.repeat(64)\n","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { apiRequest } from '@/api/client'\nimport { UNCONFIGURED_COMPUTER_IMAGE } from '@shared/runner'\nimport type { RunnerConfiguration, RunnerRecord } from '@shared/runner'\n\ntype Runner = Omit<RunnerRecord, 'tokenHash' | 'sourceOwner'> & {online: boolean}\nconst props=defineProps<{computerSetup?:boolean}>()\nconst bundleAvailable=ref(false),fixedDesktops=ref(false)\nconst runners = ref<Runner[]>([])\nconst busy = ref(false), error = ref(''), notice = ref('')\nconst name = ref('')\nconst serverURL = ref(window.location.origin)\nconst computerNetwork=ref<'none'|'public-proxy'>('public-proxy')\nconst managedBrowser=ref(true)\nconst computers=ref(!!props.computerSetup),computerRuntime=ref<'docker'|'podman'>('docker'),computerImage=ref('')\nconst artifactRoots = ref(''), insecure = ref(false), configuration = ref<RunnerConfiguration>()\n\nasync function refresh() {\n const data = await apiRequest<{runners:Runner[];bundleAvailable?:boolean;fixedDesktops?:boolean;modelProvider?:string}>('/api/app/admin/runners')\n runners.value = data.runners;bundleAvailable.value=!!data.bundleAvailable;fixedDesktops.value=!!data.fixedDesktops\n}\nasync function action(work:()=>Promise<void>) {\n if(busy.value) return\n error.value = ''; notice.value = ''; busy.value = true\n try { await work() } catch(cause) { error.value = cause instanceof Error ? cause.message : '执行节点操作失败' }\n finally { busy.value = false }\n}\nasync function register() {\n await action(async()=>{\n const web = new URL(serverURL.value)\n if(!['http:','https:'].includes(web.protocol)||web.username||web.password||web.search||web.hash||web.pathname!=='/')throw new Error('请填写不带路径的Yaovi服务地址')\n if(web.protocol==='http:'&&!['localhost','127.0.0.1','[::1]'].includes(web.hostname)&&!insecure.value)throw new Error('远程节点请使用 HTTPS,或勾选可信局域网 HTTP')\n if(computers.value&&((!!computerImage.value&&!/^sha256:[a-f0-9]{64}$/.test(computerImage.value))))throw new Error('自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。')\n const result = await apiRequest<{runner:Runner;token:string}>('/api/app/admin/runners', {method:'POST',body:{name:name.value.trim()}})\n configuration.value = {protocol:1,serverURL:web.origin,runnerId:result.runner.id,token:result.token,artifactRoots:artifactRoots.value.split('\\n').map(p=>p.trim()).filter(Boolean),...(insecure.value?{allowInsecureLan:true}:{}),...(computers.value?{computers:{...(fixedDesktops.value?{managedBy:'compose' as const}:{}),network:fixedDesktops.value?'none':computerNetwork.value,runtime:computerRuntime.value,imageId:computerImage.value||UNCONFIGURED_COMPUTER_IMAGE}}:{})}\n configuration.value.browser={enabled:managedBrowser.value}\n notice.value = '节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。'\n await refresh()\n })\n}\nfunction download() {\n if(!configuration.value) return\n const url = URL.createObjectURL(new Blob([JSON.stringify(configuration.value,null,2)+'\\n'],{type:'application/json'}))\n const anchor = document.createElement('a'); anchor.href = url; anchor.download = 'runner.json'; anchor.click()\n setTimeout(()=>URL.revokeObjectURL(url),1000)\n}\nfunction disable(runner:Runner) {\n void action(async()=>{\n await apiRequest(`/api/app/admin/runners/${runner.id}`,{method:'DELETE',body:{}})\n if(configuration.value?.runnerId===runner.id)configuration.value=undefined\n await refresh(); notice.value = `已停用 ${runner.name}`\n })\n}\nonMounted(()=>{void action(refresh)})\n</script>\n\n<template>\n <section class=\"runner-panel\" aria-label=\"执行节点设置\">\n <p>执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。</p>\n <p v-if=\"bundleAvailable\"><a href=\"/api/app/admin/runners/bundle\" download>下载配套执行节点程序</a>(包含桌面资源,需要 Node.js 24 或更高版本)。</p>\n <div class=\"runner-toolbar\"><strong>已注册节点</strong><button type=\"button\" :disabled=\"busy\" @click=\"action(refresh)\">刷新状态</button></div>\n <ul v-if=\"runners.length\" class=\"runner-list\">\n <li v-for=\"runner in runners\" :key=\"runner.id\">\n <div><strong>{{ runner.name }}</strong><span>{{ !runner.enabled ? '已停用' : runner.online ? '已连接' : '待连接' }}</span></div>\n <button v-if=\"runner.enabled\" type=\"button\" :disabled=\"busy\" :aria-label=\"`停用 ${runner.name}`\" @click=\"disable(runner)\">停用</button>\n </li>\n </ul>\n <p v-else>尚未注册执行节点。</p>\n <form v-if=\"!configuration\" @submit.prevent=\"register\">\n <label>节点名称<input v-model=\"name\" required maxlength=\"100\" autocomplete=\"off\" placeholder=\"我的 Mac\" :disabled=\"busy\"></label>\n <label>Yaovi服务地址<input v-model=\"serverURL\" type=\"url\" required :disabled=\"busy\"><small>必须能从 Runner 所在电脑访问。</small></label>\n <label>允许导出的产物目录<textarea v-model=\"artifactRoots\" rows=\"2\" :disabled=\"busy\" placeholder=\"每行一个绝对路径,留空时禁止导出文件\"></textarea></label>\n <label class=\"runner-check\"><input v-model=\"insecure\" type=\"checkbox\" :disabled=\"busy\">使用可信局域网 HTTP</label>\n <label class=\"runner-check\"><input v-model=\"managedBrowser\" type=\"checkbox\" :disabled=\"busy\">托管浏览器</label>\n <small>默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。</small>\n <label class=\"runner-check\"><input v-model=\"computers\" type=\"checkbox\" :disabled=\"busy\">启用隔离电脑 Worker</label>\n <fieldset v-if=\"computers\" class=\"runner-computer\">\n <legend>执行电脑配置</legend><p v-if=\"fixedDesktops\">桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。</p>\n <label v-if=\"!fixedDesktops\">容器运行时<select v-model=\"computerRuntime\"><option value=\"docker\">Docker</option><option value=\"podman\">Podman</option></select></label>\n <label v-if=\"!fixedDesktops\">联网范围<select v-model=\"computerNetwork\"><option value=\"public-proxy\">公网网页(HTTP / HTTPS)</option><option value=\"none\">不联网</option></select></label>\n <small v-if=\"!fixedDesktops\">公网模式通过受控代理访问,不开放宿主和内网地址。</small>\n <label v-if=\"!fixedDesktops\">兼容镜像 ID<input v-model.trim=\"computerImage\" placeholder=\"可在本地虚拟机设置中准备\"></label>\n <small>这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。</small>\n </fieldset>\n <button class=\"solid-button\" :disabled=\"busy || !name.trim()\">注册节点</button>\n </form>\n <div v-else class=\"runner-setup\">\n <button class=\"solid-button\" type=\"button\" @click=\"download\">下载 Runner 配置</button>\n <p>配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:</p>\n <code v-if=\"bundleAvailable\">mkdir -p yaovi-runner<br>tar -xzf yaovi-runner.tar.gz -C yaovi-runner<br>chmod 600 runner.json<br>node yaovi-runner/runner.mjs --config /完整路径/runner.json</code>\n <code v-else>chmod 600 runner.json<br>npm run runner -- --config /完整路径/runner.json</code>\n <small>{{bundleAvailable?(configuration.computers?'在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。':'在执行电脑上启动节点。'):'在 yaovi 项目中先运行 npm run build。'}}配置丢失后可停用节点并重新注册。</small>\n </div>\n <p v-if=\"error\" class=\"runner-error\" role=\"alert\">{{ error }}</p>\n <p v-if=\"notice\" role=\"status\">{{ notice }}</p>\n </section>\n</template>\n\n<style scoped>\n.runner-panel { display: grid; gap: 14px; min-width: 0; font-size: 13px; }\n.runner-panel p { margin: 0; line-height: 1.6; color: var(--text-secondary); }\n.runner-toolbar,.runner-list li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.runner-panel button { min-height: 36px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); cursor: pointer; }\n.runner-panel button:disabled { opacity: .5; cursor: default; }\n.runner-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }\n.runner-list li { padding: 12px; border: 1px solid var(--line); border-radius: 10px; }\n.runner-list li div { display: grid; gap: 5px; overflow-wrap: anywhere; }\n.runner-list span,.runner-panel small { color: var(--text-secondary); font-size: 12px; line-height: 1.5; }\n.runner-panel form,.runner-panel label,.runner-setup { display: grid; gap: 8px; min-width: 0; }\n.runner-panel form { gap: 14px; }\n.runner-computer { display: grid; gap: 12px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; min-width: 0; }\n.runner-panel input:not([type=checkbox]),.runner-panel select,.runner-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); font: inherit; }\n.runner-panel .runner-check { display: flex; align-items: center; }\n.runner-panel code { overflow-wrap: anywhere; line-height: 1.7; }\n.runner-panel .runner-error { color: var(--danger); }\n.runner-panel :is(input,select,textarea,button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n</style>","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { apiRequest } from '@/api/client'\nimport { UNCONFIGURED_COMPUTER_IMAGE } from '@shared/runner'\nimport type { RunnerConfiguration, RunnerRecord } from '@shared/runner'\n\ntype Runner = Omit<RunnerRecord, 'tokenHash' | 'sourceOwner'> & {online: boolean}\nconst props=defineProps<{computerSetup?:boolean}>()\nconst bundleAvailable=ref(false),fixedDesktops=ref(false)\nconst runners = ref<Runner[]>([])\nconst busy = ref(false), error = ref(''), notice = ref('')\nconst name = ref('')\nconst serverURL = ref(window.location.origin)\nconst computerNetwork=ref<'none'|'public-proxy'>('public-proxy')\nconst managedBrowser=ref(true)\nconst computers=ref(!!props.computerSetup),computerRuntime=ref<'docker'|'podman'>('docker'),computerImage=ref('')\nconst artifactRoots = ref(''), insecure = ref(false), configuration = ref<RunnerConfiguration>()\n\nasync function refresh() {\n const data = await apiRequest<{runners:Runner[];bundleAvailable?:boolean;fixedDesktops?:boolean;modelProvider?:string}>('/api/app/admin/runners')\n runners.value = data.runners;bundleAvailable.value=!!data.bundleAvailable;fixedDesktops.value=!!data.fixedDesktops\n}\nasync function action(work:()=>Promise<void>) {\n if(busy.value) return\n error.value = ''; notice.value = ''; busy.value = true\n try { await work() } catch(cause) { error.value = cause instanceof Error ? cause.message : '执行节点操作失败' }\n finally { busy.value = false }\n}\nasync function register() {\n await action(async()=>{\n const web = new URL(serverURL.value)\n if(!['http:','https:'].includes(web.protocol)||web.username||web.password||web.search||web.hash||web.pathname!=='/')throw new Error('请填写不带路径的Yaovi服务地址')\n if(web.protocol==='http:'&&!['localhost','127.0.0.1','[::1]'].includes(web.hostname)&&!insecure.value)throw new Error('远程节点请使用 HTTPS,或勾选可信局域网 HTTP')\n if(computers.value&&((!!computerImage.value&&!/^sha256:[a-f0-9]{64}$/.test(computerImage.value))))throw new Error('自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。')\n const result = await apiRequest<{runner:Runner;token:string}>('/api/app/admin/runners', {method:'POST',body:{name:name.value.trim()}})\n configuration.value = {protocol:1,serverURL:web.origin,runnerId:result.runner.id,token:result.token,artifactRoots:artifactRoots.value.split('\\n').map(p=>p.trim()).filter(Boolean),...(insecure.value?{allowInsecureLan:true}:{}),...(computers.value?{computers:{...(fixedDesktops.value?{managedBy:'compose' as const}:{}),network:fixedDesktops.value?'none':computerNetwork.value,runtime:computerRuntime.value,imageId:computerImage.value||UNCONFIGURED_COMPUTER_IMAGE}}:{})}\n configuration.value.browser={enabled:managedBrowser.value}\n notice.value = '节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。'\n await refresh()\n })\n}\nfunction download() {\n if(!configuration.value) return\n const url = URL.createObjectURL(new Blob([JSON.stringify(configuration.value,null,2)+'\\n'],{type:'application/json'}))\n const anchor = document.createElement('a'); anchor.href = url; anchor.download = 'runner.json'; anchor.click()\n setTimeout(()=>URL.revokeObjectURL(url),1000)\n}\nfunction disable(runner:Runner) {\n void action(async()=>{\n await apiRequest(`/api/app/admin/runners/${runner.id}`,{method:'DELETE',body:{}})\n if(configuration.value?.runnerId===runner.id)configuration.value=undefined\n await refresh(); notice.value = `已停用 ${runner.name}`\n })\n}\nonMounted(()=>{void action(refresh)})\n</script>\n\n<template>\n <section class=\"runner-panel\" aria-label=\"执行节点设置\">\n <p>执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。</p>\n <p v-if=\"bundleAvailable\"><a href=\"/api/app/admin/runners/bundle\" download>下载配套执行节点程序</a>(包含桌面资源,需要 Node.js 24 或更高版本)。</p>\n <div class=\"runner-toolbar\"><strong>已注册节点</strong><button type=\"button\" :disabled=\"busy\" @click=\"action(refresh)\">刷新状态</button></div>\n <ul v-if=\"runners.length\" class=\"runner-list\">\n <li v-for=\"runner in runners\" :key=\"runner.id\">\n <div><strong>{{ runner.name }}</strong><span>{{ !runner.enabled ? '已停用' : runner.online ? '已连接' : '待连接' }}</span></div>\n <button v-if=\"runner.enabled\" type=\"button\" :disabled=\"busy\" :aria-label=\"`停用 ${runner.name}`\" @click=\"disable(runner)\">停用</button>\n </li>\n </ul>\n <p v-else>尚未注册执行节点。</p>\n <form v-if=\"!configuration\" @submit.prevent=\"register\">\n <label>节点名称<input v-model=\"name\" required maxlength=\"100\" autocomplete=\"off\" placeholder=\"我的 Mac\" :disabled=\"busy\"></label>\n <label>Yaovi服务地址<input v-model=\"serverURL\" type=\"url\" required :disabled=\"busy\"><small>必须能从 Runner 所在电脑访问。</small></label>\n <label>允许导出的产物目录<textarea v-model=\"artifactRoots\" rows=\"2\" :disabled=\"busy\" placeholder=\"每行一个绝对路径,留空时禁止导出文件\"></textarea></label>\n <label class=\"runner-check\"><input v-model=\"insecure\" type=\"checkbox\" :disabled=\"busy\">使用可信局域网 HTTP</label>\n <label class=\"runner-check\"><input v-model=\"managedBrowser\" type=\"checkbox\" :disabled=\"busy\">托管浏览器</label>\n <small>默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。</small>\n <label class=\"runner-check\"><input v-model=\"computers\" type=\"checkbox\" :disabled=\"busy\">启用隔离电脑 Worker</label>\n <fieldset v-if=\"computers\" class=\"runner-computer\">\n <legend>执行电脑配置</legend><p v-if=\"fixedDesktops\">桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。</p>\n <label v-if=\"!fixedDesktops\">容器运行时<select v-model=\"computerRuntime\"><option value=\"docker\">Docker</option><option value=\"podman\">Podman</option></select></label>\n <label v-if=\"!fixedDesktops\">联网范围<select v-model=\"computerNetwork\"><option value=\"public-proxy\">公网网页(HTTP / HTTPS)</option><option value=\"none\">不联网</option></select></label>\n <small v-if=\"!fixedDesktops\">公网模式通过受控代理访问,不开放宿主和内网地址。</small>\n <label v-if=\"!fixedDesktops\">兼容镜像 ID<input v-model.trim=\"computerImage\" placeholder=\"可在本地虚拟机设置中准备\"></label>\n <small>这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。</small>\n </fieldset>\n <button class=\"solid-button\" :disabled=\"busy || !name.trim()\">注册节点</button>\n </form>\n <div v-else class=\"runner-setup\">\n <button class=\"solid-button\" type=\"button\" @click=\"download\">下载 Runner 配置</button>\n <p>配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:</p>\n <code v-if=\"bundleAvailable\">mkdir -p yaovi-runner<br>tar -xzf yaovi-runner.tar.gz -C yaovi-runner<br>chmod 600 runner.json<br>node yaovi-runner/runner.mjs --config /完整路径/runner.json</code>\n <code v-else>chmod 600 runner.json<br>npm run runner -- --config /完整路径/runner.json</code>\n <small>{{bundleAvailable?(configuration.computers?'在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。':'在执行电脑上启动节点。'):'在 yaovi 项目中先运行 npm run build。'}}配置丢失后可停用节点并重新注册。</small>\n </div>\n <p v-if=\"error\" class=\"runner-error\" role=\"alert\">{{ error }}</p>\n <p v-if=\"notice\" role=\"status\">{{ notice }}</p>\n </section>\n</template>\n\n<style scoped>\n.runner-panel { display: grid; gap: 14px; min-width: 0; font-size: 13px; }\n.runner-panel p { margin: 0; line-height: 1.6; color: var(--text-secondary); }\n.runner-toolbar,.runner-list li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.runner-panel button { min-height: 36px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); cursor: pointer; }\n.runner-panel button:disabled { opacity: .5; cursor: default; }\n.runner-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }\n.runner-list li { padding: 12px; border: 1px solid var(--line); border-radius: 10px; }\n.runner-list li div { display: grid; gap: 5px; overflow-wrap: anywhere; }\n.runner-list span,.runner-panel small { color: var(--text-secondary); font-size: 12px; line-height: 1.5; }\n.runner-panel form,.runner-panel label,.runner-setup { display: grid; gap: 8px; min-width: 0; }\n.runner-panel form { gap: 14px; }\n.runner-computer { display: grid; gap: 12px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; min-width: 0; }\n.runner-panel input:not([type=checkbox]),.runner-panel select,.runner-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); font: inherit; }\n.runner-panel .runner-check { display: flex; align-items: center; }\n.runner-panel code { overflow-wrap: anywhere; line-height: 1.7; }\n.runner-panel .runner-error { color: var(--danger); }\n.runner-panel :is(input,select,textarea,button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n</style>","<script setup lang=\"ts\">\nimport {ref,onMounted,onBeforeUnmount} from 'vue'\nimport {apiRequest} from '@/api/client'\nimport {createUuid} from '@/utils/id'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport VmMaintenanceStatus from '@/components/common/VmMaintenanceStatus.vue'\nimport RunnerSettingsPanel from './RunnerSettingsPanel.vue'\nimport type {LocalVmStatus,LocalVmMode,LocalVmAction,LocalVmInstance} from '@shared/localVm'\nimport {LOCAL_VM_IMAGES,VM_IDLE_STOP_OPTIONS,vmIdleStopLabel,type LocalVmImageKey} from '@shared/localVm'\nimport type {WorkspaceAgent} from '@shared/workspace'\nconst state=ref<LocalVmStatus>(),error=ref(''),loading=ref(false),acting=ref(false)\nconst runnerSettingsOpen=ref(false)\nconst prepareImage=ref<LocalVmImageKey>('standard')\nconst instances=ref<Array<{agent:WorkspaceAgent;vm:LocalVmInstance}>>([])\nlet closed=false,timer:ReturnType<typeof setTimeout>|undefined\nasync function refresh(){\n if(loading.value||acting.value||closed)return\n loading.value=true\n try{\n const next=await apiRequest<LocalVmStatus>('/api/app/admin/local-vm');if(closed)return;state.value=next\n const list=await apiRequest<{computers:Array<{agent:WorkspaceAgent}>}>('/api/app/computers')\n const seen=new Set<string>()\n const agents=list.computers.map(x=>x.agent).filter(a=>(a.execution==='computer'||!!a.computerEnvironmentId)&&!a.remoteAgentId&&a.nodeId==='local'&&!seen.has(a.computerEnvironmentId??a.id)&&!!seen.add(a.computerEnvironmentId??a.id))\n const rows=await Promise.all(agents.map(async agent=>({agent,vm:await apiRequest<LocalVmInstance>(`/api/app/agents/${agent.id}/local-vm`)})))\n if(!closed)instances.value=rows\n }catch(e){if(!closed)error.value=e instanceof Error?e.message:'无法读取本地虚拟机状态'}finally{loading.value=false}\n}\nasync function action(work:()=>Promise<unknown>){acting.value=true;error.value='';try{await work()}catch(e){error.value=e instanceof Error?e.message:'操作未完成'}finally{acting.value=false;await refresh()}}\nfunction prepare(){void action(()=>apiRequest('/api/app/admin/local-vm/prepare',{method:'POST',body:{requestId:createUuid(),imageKey:prepareImage.value}}))}\nfunction policy(mode:LocalVmMode,maxInstances=state.value?.maxInstances??2){\n if(mode==='shared'&&state.value?.mode!=='shared'&&!confirm('当前账号的多个机器人将共用桌面、工作文件和浏览器登录。确认使用共享虚拟机?'))return\n void action(()=>apiRequest('/api/app/admin/local-vm/policy',{method:'PUT',body:{requestId:createUuid(),mode,maxInstances}}))\n}\nfunction manage(agent:WorkspaceAgent,operation:LocalVmAction){\n if(operation==='remove'&&!confirm(`移除“${agent.name}”的虚拟机实例?工作文件、软件配置和登录资料将保留。`))return\n void action(()=>apiRequest(`/api/app/agents/${agent.id}/local-vm/${operation}`,{method:'POST',body:{}}))\n}\nasync function cycle(){await refresh();if(!closed)timer=setTimeout(cycle,state.value?.busy?2000:10000)}\nonMounted(cycle);onBeforeUnmount(()=>{closed=true;clearTimeout(timer)})\n</script>\n<template>\n <section class=\"local-vm-settings\" aria-label=\"本地虚拟机设置\">\n <article><h3>{{state?.fixedCapacity?'Compose 共享桌面':'本地虚拟机'}}</h3><p>{{state?.fixedCapacity?'桌面容器随 Web 的 Compose 文件部署,工作文件与浏览器资料保存在各桌面的数据卷中。':'在 Yaovi 所在电脑的隔离 Linux 桌面中运行机器人,工作文件与浏览器资料持续保存。'}}</p>\n <div class=\"row\"><span class=\"status\"><AppIcon :name=\"state?.image?'check':'monitor'\"/>{{loading&&!state?'正在检查…':state?.maintenance?.phase==='ready'?'虚拟机环境已就绪':state?.image?'镜像已准备':'尚未就绪'}}</span><button :disabled=\"loading||acting\" @click=\"error='';refresh()\"><AppIcon name=\"refresh\"/>重新检查</button></div>\n <VmMaintenanceStatus v-if=\"state?.maintenance\" :state=\"state.maintenance\" />\n <p v-if=\"state?.problem\">{{state.problem}}</p><p v-if=\"error\" role=\"alert\">{{error}}</p>\n <p v-if=\"state?.job&&state.job.state!=='complete'\" role=\"status\">{{state.job.message}}</p>\n </article>\n <article v-if=\"state?.executionHost==='runner'\"><h3>Yaovi 执行连接</h3><p>{{state.fixedCapacity?'执行连接负责 Yaovi 模型与工具调用,桌面容器完全由这套 Compose 管理。':'虚拟桌面由运行 Yaovi 的执行节点提供,Docker 版 Web 通过这条连接使用桌面。'}}{{state.runnerName?`当前节点:${state.runnerName}。`:''}}</p><button :aria-expanded=\"runnerSettingsOpen\" @click=\"runnerSettingsOpen=!runnerSettingsOpen\">{{runnerSettingsOpen?'收起执行节点设置':'打开执行节点设置'}}</button><RunnerSettingsPanel v-if=\"runnerSettingsOpen\" computer-setup /></article>\n <article v-if=\"state?.fixedCapacity\"><h3>Compose 共享桌面</h3><p>已配置 {{state.maxInstances}} 台桌面,数量由 Compose 固定。请在机器人聊天的电脑面板选择已有桌面。</p><div v-for=\"desktop in state.desktops\" :key=\"desktop.id\" class=\"instance\"><strong>{{desktop.name}} · {{desktop.imageKey==='cursor'?'Cursor Universal':'标准桌面'}}</strong><span>{{desktop.ready?'已就绪':desktop.online?'正在启动':'未连接'}}</span></div></article>\n <article v-else-if=\"state\"><h3>隔离方式</h3><p>按机器人创建独立桌面,或让可信的机器人共用一个桌面。</p>\n <div class=\"segmented\" role=\"group\" aria-label=\"虚拟机隔离方式\"><button :aria-pressed=\"state?.mode==='shared'\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @click=\"policy('shared')\">共享虚拟机</button><button :aria-pressed=\"state?.mode==='per-bot'\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @click=\"policy('per-bot')\">每个机器人独立</button></div>\n <p class=\"sharing-explanation\">共享指多个机器人同时使用同一台虚拟机:命令与文件操作并行执行,桌面操作交替进行,共用桌面和工作文件。</p>\n <label class=\"row\">虚拟机运行数量上限<select aria-label=\"虚拟机数量上限\" :value=\"state?.maxInstances??2\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @change=\"policy(state!.mode,Number(($event.target as HTMLSelectElement).value))\"><option v-for=\"n in [1,2,3,4]\" :key=\"n\" :value=\"n\">{{n}}</option></select></label>\n </article>\n <article v-if=\"state&&!state.fixedCapacity\"><h3>空闲自动停止</h3><p>从任务结束或交还控制权后开始计时,适用于该执行节点上的所有本地虚拟机。</p>\n <label class=\"row\">停止时间<select aria-label=\"虚拟机空闲停止时间\" :value=\"state.idleStopMinutes??5\" :disabled=\"acting||!state.configured||state.idleStopMinutes===undefined\" @change=\"action(()=>apiRequest('/api/app/admin/local-vm/idle-policy',{method:'PUT',body:{requestId:createUuid(),idleStopMinutes:Number(($event.target as HTMLSelectElement).value)}}))\"><option v-for=\"minutes in VM_IDLE_STOP_OPTIONS\" :key=\"minutes\" :value=\"minutes\">{{vmIdleStopLabel(minutes)}}</option><option v-if=\"state.idleStopMinutes!==undefined&&!VM_IDLE_STOP_OPTIONS.some(n=>n===state!.idleStopMinutes)\" :value=\"state.idleStopMinutes\">{{vmIdleStopLabel(state.idleStopMinutes)}}</option></select></label>\n <p v-if=\"state.idleStopMinutes===undefined\">连接并更新执行节点后可修改。</p><p v-else-if=\"state.idleStopMinutes===0\">空闲时保持运行;仍可手动停止,执行节点关闭时也会停止。</p>\n </article>\n <article v-if=\"state&&!state.fixedCapacity&&!state.setupRequired\"><h3>准备运行环境</h3><p v-if=\"state?.executionHost==='runner'\">以下检查和镜像准备均在执行节点所在电脑完成。</p><ol>\n <li><span class=\"step\">{{state?.runtime?'✓':'1'}}</span><div><strong>安装 Docker 或 Podman</strong><p>{{state?.runtime?`已检测到 ${state.runtime}`:'请先安装 Docker Desktop 或 Podman。'}}</p></div></li>\n <li><span class=\"step\">{{state?.daemonUp?'✓':'2'}}</span><div><strong>启动容器运行环境</strong><p>{{state?.daemonUp?'运行环境已启动':'打开 Docker / Podman,等待它完成启动后重新检查。'}}</p></div></li>\n <li><span class=\"step\">{{state?.image?'✓':'3'}}</span><div class=\"image-preparation\"><strong>准备虚拟机镜像</strong><p>两个镜像可分别准备。独立桌面可各自选择;同一共享桌面的所有机器人使用一个镜像。</p><label>镜像<select v-model=\"prepareImage\" :disabled=\"acting||state.busy\"><option v-for=\"image in LOCAL_VM_IMAGES\" :key=\"image.key\" :value=\"image.key\">{{image.name}} · {{state.images?.find(i=>i.key===image.key)?.ready?'已就绪':'未准备'}}</option></select></label><p>{{LOCAL_VM_IMAGES.find(i=>i.key===prepareImage)?.description}}{{prepareImage==='cursor'?';Apple 芯片电脑需要运行环境支持 amd64 模拟。':''}}</p><button class=\"primary\" :disabled=\"acting||state?.busy||!state?.daemonUp\" @click=\"prepare\">{{state?.busy?'正在准备…':state.images?.find(i=>i.key===prepareImage)?.ready?'重新检查所选镜像':'准备所选镜像'}}</button></div></li>\n <li><span class=\"step\">4</span><div><strong>{{state.maintenance?'自动启动与维护':'从机器人聊天中创建桌面'}}</strong><p>{{state.maintenance?'运行环境就绪后会自动准备并启动桌面。可在聊天的“电脑”中查看和使用;手动停止后由新任务或明确启动唤醒。':'打开聊天的“电脑”,选择“本地虚拟机”,然后创建该机器人的虚拟机。'}}</p></div></li>\n </ol></article>\n <article v-if=\"state&&!state.fixedCapacity\"><h3>{{state?.mode==='shared'?'共享虚拟机':'机器人虚拟机'}}</h3><p v-if=\"!instances.length\">还没有机器人使用本地虚拟机。</p>\n <div v-for=\"row in instances\" :key=\"row.agent.id\" class=\"instance\"><div><strong>{{row.agent.computerEnvironmentName??row.agent.name}}</strong><p>{{row.vm.inUse?'正在使用':({running:'运行中',stopped:'已停止',missing:'尚未创建'})[row.vm.container]}}</p></div><div class=\"row\">\n <button v-if=\"row.vm.container==='running'\" :disabled=\"acting||row.vm.inUse\" @click=\"manage(row.agent,'stop')\">停止</button>\n <button v-else :disabled=\"acting||!state?.image\" @click=\"manage(row.agent,row.vm.container==='missing'?'create':'start')\">{{row.vm.container==='missing'?'创建':'启动'}}</button>\n <button v-if=\"row.vm.container!=='missing'\" :disabled=\"acting||row.vm.inUse\" @click=\"manage(row.agent,'remove')\">移除实例</button>\n </div></div>\n <div v-for=\"item in state?.instances?.filter(i=>i.orphaned)\" :key=\"item.id\" class=\"instance\"><div><strong>{{item.name}}</strong><p>暂无机器人使用 · {{item.status==='free'?'已停止':'运行中或待核对'}}</p></div><div class=\"row\"><button :disabled=\"acting\" @click=\"action(()=>apiRequest(`/api/app/admin/local-vm/instances/${item.id}/stop`,{method:'POST',body:{requestId:createUuid()}}))\">停止</button><button :disabled=\"acting\" @click=\"action(()=>apiRequest(`/api/app/admin/local-vm/instances/${item.id}/remove`,{method:'POST',body:{requestId:createUuid()}}))\">移除实例</button></div></div>\n </article>\n </section>\n</template>\n<style scoped>\n.local-vm-settings{display:grid;gap:16px;max-width:820px}article{padding:20px;border:1px solid var(--line);border-radius:14px;display:grid;gap:12px;background:var(--surface)}h3,p{margin:0}h3{font-size:15px}p{font-size:13px;line-height:1.7;color:var(--text-secondary)}.row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.status{display:inline-flex;align-items:center;gap:7px;font-size:13px}.segmented{display:flex;border:1px solid var(--line);border-radius:9px;overflow:hidden}.segmented button{flex:1;border:0;border-radius:0}.segmented button[aria-pressed=true]{background:var(--surface-soft);font-weight:600}.instance{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-top:12px;border-top:1px solid var(--line)}button,select{font:inherit;color:var(--text-primary);background:var(--surface-raised);border:1px solid var(--line);border-radius:8px;min-height:40px;padding:7px 12px}button{display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer}button:disabled{opacity:.45;cursor:default}.primary{background:var(--accent);color:var(--text-on-solid);margin-top:8px}button:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}ol{list-style:none;margin:0;padding:0;display:grid;gap:20px}li{display:flex;gap:12px}.step{display:grid;place-items:center;border:1px solid var(--line);border-radius:50%;height:26px;width:26px;flex:none;font-size:12px}li strong{font-size:13px}[role=alert]{color:var(--danger)}\n</style>\n<style scoped>.image-preparation{display:grid;gap:10px;min-width:0}.image-preparation label{display:grid;gap:6px}.image-preparation select{width:100%;min-height:44px}.image-preparation button{justify-self:start}</style>\n","<script setup lang=\"ts\">\nimport {ref,onMounted,onBeforeUnmount} from 'vue'\nimport {apiRequest} from '@/api/client'\nimport {createUuid} from '@/utils/id'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport VmMaintenanceStatus from '@/components/common/VmMaintenanceStatus.vue'\nimport RunnerSettingsPanel from './RunnerSettingsPanel.vue'\nimport type {LocalVmStatus,LocalVmMode,LocalVmAction,LocalVmInstance} from '@shared/localVm'\nimport {LOCAL_VM_IMAGES,VM_IDLE_STOP_OPTIONS,vmIdleStopLabel,type LocalVmImageKey} from '@shared/localVm'\nimport type {WorkspaceAgent} from '@shared/workspace'\nconst state=ref<LocalVmStatus>(),error=ref(''),loading=ref(false),acting=ref(false)\nconst runnerSettingsOpen=ref(false)\nconst prepareImage=ref<LocalVmImageKey>('standard')\nconst instances=ref<Array<{agent:WorkspaceAgent;vm:LocalVmInstance}>>([])\nlet closed=false,timer:ReturnType<typeof setTimeout>|undefined\nasync function refresh(){\n if(loading.value||acting.value||closed)return\n loading.value=true\n try{\n const next=await apiRequest<LocalVmStatus>('/api/app/admin/local-vm');if(closed)return;state.value=next\n const list=await apiRequest<{computers:Array<{agent:WorkspaceAgent}>}>('/api/app/computers')\n const seen=new Set<string>()\n const agents=list.computers.map(x=>x.agent).filter(a=>(a.execution==='computer'||!!a.computerEnvironmentId)&&!a.remoteAgentId&&a.nodeId==='local'&&!seen.has(a.computerEnvironmentId??a.id)&&!!seen.add(a.computerEnvironmentId??a.id))\n const rows=await Promise.all(agents.map(async agent=>({agent,vm:await apiRequest<LocalVmInstance>(`/api/app/agents/${agent.id}/local-vm`)})))\n if(!closed)instances.value=rows\n }catch(e){if(!closed)error.value=e instanceof Error?e.message:'无法读取本地虚拟机状态'}finally{loading.value=false}\n}\nasync function action(work:()=>Promise<unknown>){acting.value=true;error.value='';try{await work()}catch(e){error.value=e instanceof Error?e.message:'操作未完成'}finally{acting.value=false;await refresh()}}\nfunction prepare(){void action(()=>apiRequest('/api/app/admin/local-vm/prepare',{method:'POST',body:{requestId:createUuid(),imageKey:prepareImage.value}}))}\nfunction policy(mode:LocalVmMode,maxInstances=state.value?.maxInstances??2){\n if(mode==='shared'&&state.value?.mode!=='shared'&&!confirm('当前账号的多个机器人将共用桌面、工作文件和浏览器登录。确认使用共享虚拟机?'))return\n void action(()=>apiRequest('/api/app/admin/local-vm/policy',{method:'PUT',body:{requestId:createUuid(),mode,maxInstances}}))\n}\nfunction manage(agent:WorkspaceAgent,operation:LocalVmAction){\n if(operation==='remove'&&!confirm(`移除“${agent.name}”的虚拟机实例?工作文件、软件配置和登录资料将保留。`))return\n void action(()=>apiRequest(`/api/app/agents/${agent.id}/local-vm/${operation}`,{method:'POST',body:{}}))\n}\nasync function cycle(){await refresh();if(!closed)timer=setTimeout(cycle,state.value?.busy?2000:10000)}\nonMounted(cycle);onBeforeUnmount(()=>{closed=true;clearTimeout(timer)})\n</script>\n<template>\n <section class=\"local-vm-settings\" aria-label=\"本地虚拟机设置\">\n <article><h3>{{state?.fixedCapacity?'Compose 共享桌面':'本地虚拟机'}}</h3><p>{{state?.fixedCapacity?'桌面容器随 Web 的 Compose 文件部署,工作文件与浏览器资料保存在各桌面的数据卷中。':'在 Yaovi 所在电脑的隔离 Linux 桌面中运行机器人,工作文件与浏览器资料持续保存。'}}</p>\n <div class=\"row\"><span class=\"status\"><AppIcon :name=\"state?.image?'check':'monitor'\"/>{{loading&&!state?'正在检查…':state?.maintenance?.phase==='ready'?'虚拟机环境已就绪':state?.image?'镜像已准备':'尚未就绪'}}</span><button :disabled=\"loading||acting\" @click=\"error='';refresh()\"><AppIcon name=\"refresh\"/>重新检查</button></div>\n <VmMaintenanceStatus v-if=\"state?.maintenance\" :state=\"state.maintenance\" />\n <p v-if=\"state?.problem\">{{state.problem}}</p><p v-if=\"error\" role=\"alert\">{{error}}</p>\n <p v-if=\"state?.job&&state.job.state!=='complete'\" role=\"status\">{{state.job.message}}</p>\n </article>\n <article v-if=\"state?.executionHost==='runner'\"><h3>Yaovi 执行连接</h3><p>{{state.fixedCapacity?'执行连接负责 Yaovi 模型与工具调用,桌面容器完全由这套 Compose 管理。':'虚拟桌面由运行 Yaovi 的执行节点提供,Docker 版 Web 通过这条连接使用桌面。'}}{{state.runnerName?`当前节点:${state.runnerName}。`:''}}</p><button :aria-expanded=\"runnerSettingsOpen\" @click=\"runnerSettingsOpen=!runnerSettingsOpen\">{{runnerSettingsOpen?'收起执行节点设置':'打开执行节点设置'}}</button><RunnerSettingsPanel v-if=\"runnerSettingsOpen\" computer-setup /></article>\n <article v-if=\"state?.fixedCapacity\"><h3>Compose 共享桌面</h3><p>已配置 {{state.maxInstances}} 台桌面,数量由 Compose 固定。请在机器人聊天的电脑面板选择已有桌面。</p><div v-for=\"desktop in state.desktops\" :key=\"desktop.id\" class=\"instance\"><strong>{{desktop.name}} · {{desktop.imageKey==='cursor'?'Cursor Universal':'标准桌面'}}</strong><span>{{desktop.ready?'已就绪':desktop.online?'正在启动':'未连接'}}</span></div></article>\n <article v-else-if=\"state\"><h3>隔离方式</h3><p>按机器人创建独立桌面,或让可信的机器人共用一个桌面。</p>\n <div class=\"segmented\" role=\"group\" aria-label=\"虚拟机隔离方式\"><button :aria-pressed=\"state?.mode==='shared'\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @click=\"policy('shared')\">共享虚拟机</button><button :aria-pressed=\"state?.mode==='per-bot'\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @click=\"policy('per-bot')\">每个机器人独立</button></div>\n <p class=\"sharing-explanation\">共享指多个机器人同时使用同一台虚拟机:命令与文件操作并行执行,桌面操作交替进行,共用桌面和工作文件。</p>\n <label class=\"row\">虚拟机运行数量上限<select aria-label=\"虚拟机数量上限\" :value=\"state?.maxInstances??2\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @change=\"policy(state!.mode,Number(($event.target as HTMLSelectElement).value))\"><option v-for=\"n in [1,2,3,4]\" :key=\"n\" :value=\"n\">{{n}}</option></select></label>\n </article>\n <article v-if=\"state&&!state.fixedCapacity\"><h3>空闲自动停止</h3><p>从任务结束或交还控制权后开始计时,适用于该执行节点上的所有本地虚拟机。</p>\n <label class=\"row\">停止时间<select aria-label=\"虚拟机空闲停止时间\" :value=\"state.idleStopMinutes??5\" :disabled=\"acting||!state.configured||state.idleStopMinutes===undefined\" @change=\"action(()=>apiRequest('/api/app/admin/local-vm/idle-policy',{method:'PUT',body:{requestId:createUuid(),idleStopMinutes:Number(($event.target as HTMLSelectElement).value)}}))\"><option v-for=\"minutes in VM_IDLE_STOP_OPTIONS\" :key=\"minutes\" :value=\"minutes\">{{vmIdleStopLabel(minutes)}}</option><option v-if=\"state.idleStopMinutes!==undefined&&!VM_IDLE_STOP_OPTIONS.some(n=>n===state!.idleStopMinutes)\" :value=\"state.idleStopMinutes\">{{vmIdleStopLabel(state.idleStopMinutes)}}</option></select></label>\n <p v-if=\"state.idleStopMinutes===undefined\">连接并更新执行节点后可修改。</p><p v-else-if=\"state.idleStopMinutes===0\">空闲时保持运行;仍可手动停止,执行节点关闭时也会停止。</p>\n </article>\n <article v-if=\"state&&!state.fixedCapacity&&!state.setupRequired\"><h3>准备运行环境</h3><p v-if=\"state?.executionHost==='runner'\">以下检查和镜像准备均在执行节点所在电脑完成。</p><ol>\n <li><span class=\"step\">{{state?.runtime?'✓':'1'}}</span><div><strong>安装 Docker 或 Podman</strong><p>{{state?.runtime?`已检测到 ${state.runtime}`:'请先安装 Docker Desktop 或 Podman。'}}</p></div></li>\n <li><span class=\"step\">{{state?.daemonUp?'✓':'2'}}</span><div><strong>启动容器运行环境</strong><p>{{state?.daemonUp?'运行环境已启动':'打开 Docker / Podman,等待它完成启动后重新检查。'}}</p></div></li>\n <li><span class=\"step\">{{state?.image?'✓':'3'}}</span><div class=\"image-preparation\"><strong>准备虚拟机镜像</strong><p>两个镜像可分别准备。独立桌面可各自选择;同一共享桌面的所有机器人使用一个镜像。</p><label>镜像<select v-model=\"prepareImage\" :disabled=\"acting||state.busy\"><option v-for=\"image in LOCAL_VM_IMAGES\" :key=\"image.key\" :value=\"image.key\">{{image.name}} · {{state.images?.find(i=>i.key===image.key)?.ready?'已就绪':'未准备'}}</option></select></label><p>{{LOCAL_VM_IMAGES.find(i=>i.key===prepareImage)?.description}}{{prepareImage==='cursor'?';Apple 芯片电脑需要运行环境支持 amd64 模拟。':''}}</p><button class=\"primary\" :disabled=\"acting||state?.busy||!state?.daemonUp\" @click=\"prepare\">{{state?.busy?'正在准备…':state.images?.find(i=>i.key===prepareImage)?.ready?'重新检查所选镜像':'准备所选镜像'}}</button></div></li>\n <li><span class=\"step\">4</span><div><strong>{{state.maintenance?'自动启动与维护':'从机器人聊天中创建桌面'}}</strong><p>{{state.maintenance?'运行环境就绪后会自动准备并启动桌面。可在聊天的“电脑”中查看和使用;手动停止后由新任务或明确启动唤醒。':'打开聊天的“电脑”,选择“本地虚拟机”,然后创建该机器人的虚拟机。'}}</p></div></li>\n </ol></article>\n <article v-if=\"state&&!state.fixedCapacity\"><h3>{{state?.mode==='shared'?'共享虚拟机':'机器人虚拟机'}}</h3><p v-if=\"!instances.length\">还没有机器人使用本地虚拟机。</p>\n <div v-for=\"row in instances\" :key=\"row.agent.id\" class=\"instance\"><div><strong>{{row.agent.computerEnvironmentName??row.agent.name}}</strong><p>{{row.vm.inUse?'正在使用':({running:'运行中',stopped:'已停止',missing:'尚未创建'})[row.vm.container]}}</p></div><div class=\"row\">\n <button v-if=\"row.vm.container==='running'\" :disabled=\"acting||row.vm.inUse\" @click=\"manage(row.agent,'stop')\">停止</button>\n <button v-else :disabled=\"acting||!state?.image\" @click=\"manage(row.agent,row.vm.container==='missing'?'create':'start')\">{{row.vm.container==='missing'?'创建':'启动'}}</button>\n <button v-if=\"row.vm.container!=='missing'\" :disabled=\"acting||row.vm.inUse\" @click=\"manage(row.agent,'remove')\">移除实例</button>\n </div></div>\n <div v-for=\"item in state?.instances?.filter(i=>i.orphaned)\" :key=\"item.id\" class=\"instance\"><div><strong>{{item.name}}</strong><p>暂无机器人使用 · {{item.status==='free'?'已停止':'运行中或待核对'}}</p></div><div class=\"row\"><button :disabled=\"acting\" @click=\"action(()=>apiRequest(`/api/app/admin/local-vm/instances/${item.id}/stop`,{method:'POST',body:{requestId:createUuid()}}))\">停止</button><button :disabled=\"acting\" @click=\"action(()=>apiRequest(`/api/app/admin/local-vm/instances/${item.id}/remove`,{method:'POST',body:{requestId:createUuid()}}))\">移除实例</button></div></div>\n </article>\n </section>\n</template>\n<style scoped>\n.local-vm-settings{display:grid;gap:16px;max-width:820px}article{padding:20px;border:1px solid var(--line);border-radius:14px;display:grid;gap:12px;background:var(--surface)}h3,p{margin:0}h3{font-size:15px}p{font-size:13px;line-height:1.7;color:var(--text-secondary)}.row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.status{display:inline-flex;align-items:center;gap:7px;font-size:13px}.segmented{display:flex;border:1px solid var(--line);border-radius:9px;overflow:hidden}.segmented button{flex:1;border:0;border-radius:0}.segmented button[aria-pressed=true]{background:var(--surface-soft);font-weight:600}.instance{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-top:12px;border-top:1px solid var(--line)}button,select{font:inherit;color:var(--text-primary);background:var(--surface-raised);border:1px solid var(--line);border-radius:8px;min-height:40px;padding:7px 12px}button{display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer}button:disabled{opacity:.45;cursor:default}.primary{background:var(--accent);color:var(--text-on-solid);margin-top:8px}button:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}ol{list-style:none;margin:0;padding:0;display:grid;gap:20px}li{display:flex;gap:12px}.step{display:grid;place-items:center;border:1px solid var(--line);border-radius:50%;height:26px;width:26px;flex:none;font-size:12px}li strong{font-size:13px}[role=alert]{color:var(--danger)}\n</style>\n<style scoped>.image-preparation{display:grid;gap:10px;min-width:0}.image-preparation label{display:grid;gap:6px}.image-preparation select{width:100%;min-height:44px}.image-preparation button{justify-self:start}</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LocalVmSettingsPanel-BZ1Jeqqk.js","names":[],"sources":["../../src/shared/runner.ts","../../src/client/components/app/RunnerSettingsPanel.vue","../../src/client/components/app/RunnerSettingsPanel.vue","../../src/client/components/app/LocalVmSettingsPanel.vue","../../src/client/components/app/LocalVmSettingsPanel.vue"],"sourcesContent":["export const RUNNER_PROTOCOL = 1\nexport const VM_NETWORK_FEATURE = 'vm-network-policy-v2'\nexport const VM_MAINTENANCE_FEATURE = 'vm-maintenance-v1'\nexport interface RunnerRecord {\n id: string\n name: string\n sourceNodeId: string\n sourceOwner: string\n tokenHash: string\n enabled: boolean\n createdAt: number\n}\nexport interface RunnerCommand {\n id: string\n kind: 'computer.cleanup' | 'network.configure' | 'network.test' | 'browser.invoke' | 'local-vm.manage' | 'computer.control' | 'computer.retire' | 'http' | 'gateway.open' | 'gateway.rpc' | 'gateway.close' | 'lease.create' | 'lease.bind' | 'lease.close'\n payload: Record<string, unknown>\n expiresAt: number\n}\nexport interface RunnerConfiguration {\n protocol: 1\n serverURL: string\n runnerId: string\n token: string\n artifactRoots: string[]\n allowInsecureLan?: boolean\n /** Opt-in, independent of the full Linux desktop. */\n browser?: { enabled: boolean; maxSessions?: number; idleTimeoutMs?: number }\n computers?: {managedBy?:'compose';network?:'none'|'public-proxy';runtime:'docker'|'podman';imageId:string;maxConcurrent?:number}\n}\nexport const UNCONFIGURED_COMPUTER_IMAGE='sha256:'+'0'.repeat(64)\n","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { apiRequest } from '@/api/client'\nimport { UNCONFIGURED_COMPUTER_IMAGE } from '@shared/runner'\nimport type { RunnerConfiguration, RunnerRecord } from '@shared/runner'\n\ntype Runner = Omit<RunnerRecord, 'tokenHash' | 'sourceOwner'> & {online: boolean}\nconst props=defineProps<{computerSetup?:boolean}>()\nconst bundleAvailable=ref(false),fixedDesktops=ref(false)\nconst runners = ref<Runner[]>([])\nconst busy = ref(false), error = ref(''), notice = ref('')\nconst name = ref('')\nconst serverURL = ref(window.location.origin)\nconst computerNetwork=ref<'none'|'public-proxy'>('public-proxy')\nconst managedBrowser=ref(true)\nconst computers=ref(!!props.computerSetup),computerRuntime=ref<'docker'|'podman'>('docker'),computerImage=ref('')\nconst artifactRoots = ref(''), insecure = ref(false), configuration = ref<RunnerConfiguration>()\n\nasync function refresh() {\n const data = await apiRequest<{runners:Runner[];bundleAvailable?:boolean;fixedDesktops?:boolean;modelProvider?:string}>('/api/app/admin/runners')\n runners.value = data.runners;bundleAvailable.value=!!data.bundleAvailable;fixedDesktops.value=!!data.fixedDesktops\n}\nasync function action(work:()=>Promise<void>) {\n if(busy.value) return\n error.value = ''; notice.value = ''; busy.value = true\n try { await work() } catch(cause) { error.value = cause instanceof Error ? cause.message : '执行节点操作失败' }\n finally { busy.value = false }\n}\nasync function register() {\n await action(async()=>{\n const web = new URL(serverURL.value)\n if(!['http:','https:'].includes(web.protocol)||web.username||web.password||web.search||web.hash||web.pathname!=='/')throw new Error('请填写不带路径的Yaovi服务地址')\n if(web.protocol==='http:'&&!['localhost','127.0.0.1','[::1]'].includes(web.hostname)&&!insecure.value)throw new Error('远程节点请使用 HTTPS,或勾选可信局域网 HTTP')\n if(computers.value&&((!!computerImage.value&&!/^sha256:[a-f0-9]{64}$/.test(computerImage.value))))throw new Error('自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。')\n const result = await apiRequest<{runner:Runner;token:string}>('/api/app/admin/runners', {method:'POST',body:{name:name.value.trim()}})\n configuration.value = {protocol:1,serverURL:web.origin,runnerId:result.runner.id,token:result.token,artifactRoots:artifactRoots.value.split('\\n').map(p=>p.trim()).filter(Boolean),...(insecure.value?{allowInsecureLan:true}:{}),...(computers.value?{computers:{...(fixedDesktops.value?{managedBy:'compose' as const}:{}),network:fixedDesktops.value?'none':computerNetwork.value,runtime:computerRuntime.value,imageId:computerImage.value||UNCONFIGURED_COMPUTER_IMAGE}}:{})}\n configuration.value.browser={enabled:managedBrowser.value}\n notice.value = '节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。'\n await refresh()\n })\n}\nfunction download() {\n if(!configuration.value) return\n const url = URL.createObjectURL(new Blob([JSON.stringify(configuration.value,null,2)+'\\n'],{type:'application/json'}))\n const anchor = document.createElement('a'); anchor.href = url; anchor.download = 'runner.json'; anchor.click()\n setTimeout(()=>URL.revokeObjectURL(url),1000)\n}\nfunction disable(runner:Runner) {\n void action(async()=>{\n await apiRequest(`/api/app/admin/runners/${runner.id}`,{method:'DELETE',body:{}})\n if(configuration.value?.runnerId===runner.id)configuration.value=undefined\n await refresh(); notice.value = `已停用 ${runner.name}`\n })\n}\nonMounted(()=>{void action(refresh)})\n</script>\n\n<template>\n <section class=\"runner-panel\" aria-label=\"执行节点设置\">\n <p>执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。</p>\n <p v-if=\"bundleAvailable\"><a href=\"/api/app/admin/runners/bundle\" download>下载配套执行节点程序</a>(包含桌面资源,需要 Node.js 24 或更高版本)。</p>\n <div class=\"runner-toolbar\"><strong>已注册节点</strong><button type=\"button\" :disabled=\"busy\" @click=\"action(refresh)\">刷新状态</button></div>\n <ul v-if=\"runners.length\" class=\"runner-list\">\n <li v-for=\"runner in runners\" :key=\"runner.id\">\n <div><strong>{{ runner.name }}</strong><span>{{ !runner.enabled ? '已停用' : runner.online ? '已连接' : '待连接' }}</span></div>\n <button v-if=\"runner.enabled\" type=\"button\" :disabled=\"busy\" :aria-label=\"`停用 ${runner.name}`\" @click=\"disable(runner)\">停用</button>\n </li>\n </ul>\n <p v-else>尚未注册执行节点。</p>\n <form v-if=\"!configuration\" @submit.prevent=\"register\">\n <label>节点名称<input v-model=\"name\" required maxlength=\"100\" autocomplete=\"off\" placeholder=\"我的 Mac\" :disabled=\"busy\"></label>\n <label>Yaovi服务地址<input v-model=\"serverURL\" type=\"url\" required :disabled=\"busy\"><small>必须能从 Runner 所在电脑访问。</small></label>\n <label>允许导出的产物目录<textarea v-model=\"artifactRoots\" rows=\"2\" :disabled=\"busy\" placeholder=\"每行一个绝对路径,留空时禁止导出文件\"></textarea></label>\n <label class=\"runner-check\"><input v-model=\"insecure\" type=\"checkbox\" :disabled=\"busy\">使用可信局域网 HTTP</label>\n <label class=\"runner-check\"><input v-model=\"managedBrowser\" type=\"checkbox\" :disabled=\"busy\">托管浏览器</label>\n <small>默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。</small>\n <label class=\"runner-check\"><input v-model=\"computers\" type=\"checkbox\" :disabled=\"busy\">启用隔离电脑 Worker</label>\n <fieldset v-if=\"computers\" class=\"runner-computer\">\n <legend>执行电脑配置</legend><p v-if=\"fixedDesktops\">桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。</p>\n <label v-if=\"!fixedDesktops\">容器运行时<select v-model=\"computerRuntime\"><option value=\"docker\">Docker</option><option value=\"podman\">Podman</option></select></label>\n <label v-if=\"!fixedDesktops\">联网范围<select v-model=\"computerNetwork\"><option value=\"public-proxy\">公网网页(HTTP / HTTPS)</option><option value=\"none\">不联网</option></select></label>\n <small v-if=\"!fixedDesktops\">公网模式通过受控代理访问,不开放宿主和内网地址。</small>\n <label v-if=\"!fixedDesktops\">兼容镜像 ID<input v-model.trim=\"computerImage\" placeholder=\"可在本地虚拟机设置中准备\"></label>\n <small>这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。</small>\n </fieldset>\n <button class=\"solid-button\" :disabled=\"busy || !name.trim()\">注册节点</button>\n </form>\n <div v-else class=\"runner-setup\">\n <button class=\"solid-button\" type=\"button\" @click=\"download\">下载 Runner 配置</button>\n <p>配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:</p>\n <code v-if=\"bundleAvailable\">mkdir -p yaovi-runner<br>tar -xzf yaovi-runner.tar.gz -C yaovi-runner<br>chmod 600 runner.json<br>node yaovi-runner/runner.mjs --config /完整路径/runner.json</code>\n <code v-else>chmod 600 runner.json<br>npm run runner -- --config /完整路径/runner.json</code>\n <small>{{bundleAvailable?(configuration.computers?'在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。':'在执行电脑上启动节点。'):'在 yaovi 项目中先运行 npm run build。'}}配置丢失后可停用节点并重新注册。</small>\n </div>\n <p v-if=\"error\" class=\"runner-error\" role=\"alert\">{{ error }}</p>\n <p v-if=\"notice\" role=\"status\">{{ notice }}</p>\n </section>\n</template>\n\n<style scoped>\n.runner-panel { display: grid; gap: 14px; min-width: 0; font-size: 13px; }\n.runner-panel p { margin: 0; line-height: 1.6; color: var(--text-secondary); }\n.runner-toolbar,.runner-list li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.runner-panel button { min-height: 36px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); cursor: pointer; }\n.runner-panel button:disabled { opacity: .5; cursor: default; }\n.runner-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }\n.runner-list li { padding: 12px; border: 1px solid var(--line); border-radius: 10px; }\n.runner-list li div { display: grid; gap: 5px; overflow-wrap: anywhere; }\n.runner-list span,.runner-panel small { color: var(--text-secondary); font-size: 12px; line-height: 1.5; }\n.runner-panel form,.runner-panel label,.runner-setup { display: grid; gap: 8px; min-width: 0; }\n.runner-panel form { gap: 14px; }\n.runner-computer { display: grid; gap: 12px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; min-width: 0; }\n.runner-panel input:not([type=checkbox]),.runner-panel select,.runner-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); font: inherit; }\n.runner-panel .runner-check { display: flex; align-items: center; }\n.runner-panel code { overflow-wrap: anywhere; line-height: 1.7; }\n.runner-panel .runner-error { color: var(--danger); }\n.runner-panel :is(input,select,textarea,button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n</style>","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { apiRequest } from '@/api/client'\nimport { UNCONFIGURED_COMPUTER_IMAGE } from '@shared/runner'\nimport type { RunnerConfiguration, RunnerRecord } from '@shared/runner'\n\ntype Runner = Omit<RunnerRecord, 'tokenHash' | 'sourceOwner'> & {online: boolean}\nconst props=defineProps<{computerSetup?:boolean}>()\nconst bundleAvailable=ref(false),fixedDesktops=ref(false)\nconst runners = ref<Runner[]>([])\nconst busy = ref(false), error = ref(''), notice = ref('')\nconst name = ref('')\nconst serverURL = ref(window.location.origin)\nconst computerNetwork=ref<'none'|'public-proxy'>('public-proxy')\nconst managedBrowser=ref(true)\nconst computers=ref(!!props.computerSetup),computerRuntime=ref<'docker'|'podman'>('docker'),computerImage=ref('')\nconst artifactRoots = ref(''), insecure = ref(false), configuration = ref<RunnerConfiguration>()\n\nasync function refresh() {\n const data = await apiRequest<{runners:Runner[];bundleAvailable?:boolean;fixedDesktops?:boolean;modelProvider?:string}>('/api/app/admin/runners')\n runners.value = data.runners;bundleAvailable.value=!!data.bundleAvailable;fixedDesktops.value=!!data.fixedDesktops\n}\nasync function action(work:()=>Promise<void>) {\n if(busy.value) return\n error.value = ''; notice.value = ''; busy.value = true\n try { await work() } catch(cause) { error.value = cause instanceof Error ? cause.message : '执行节点操作失败' }\n finally { busy.value = false }\n}\nasync function register() {\n await action(async()=>{\n const web = new URL(serverURL.value)\n if(!['http:','https:'].includes(web.protocol)||web.username||web.password||web.search||web.hash||web.pathname!=='/')throw new Error('请填写不带路径的Yaovi服务地址')\n if(web.protocol==='http:'&&!['localhost','127.0.0.1','[::1]'].includes(web.hostname)&&!insecure.value)throw new Error('远程节点请使用 HTTPS,或勾选可信局域网 HTTP')\n if(computers.value&&((!!computerImage.value&&!/^sha256:[a-f0-9]{64}$/.test(computerImage.value))))throw new Error('自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。')\n const result = await apiRequest<{runner:Runner;token:string}>('/api/app/admin/runners', {method:'POST',body:{name:name.value.trim()}})\n configuration.value = {protocol:1,serverURL:web.origin,runnerId:result.runner.id,token:result.token,artifactRoots:artifactRoots.value.split('\\n').map(p=>p.trim()).filter(Boolean),...(insecure.value?{allowInsecureLan:true}:{}),...(computers.value?{computers:{...(fixedDesktops.value?{managedBy:'compose' as const}:{}),network:fixedDesktops.value?'none':computerNetwork.value,runtime:computerRuntime.value,imageId:computerImage.value||UNCONFIGURED_COMPUTER_IMAGE}}:{})}\n configuration.value.browser={enabled:managedBrowser.value}\n notice.value = '节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。'\n await refresh()\n })\n}\nfunction download() {\n if(!configuration.value) return\n const url = URL.createObjectURL(new Blob([JSON.stringify(configuration.value,null,2)+'\\n'],{type:'application/json'}))\n const anchor = document.createElement('a'); anchor.href = url; anchor.download = 'runner.json'; anchor.click()\n setTimeout(()=>URL.revokeObjectURL(url),1000)\n}\nfunction disable(runner:Runner) {\n void action(async()=>{\n await apiRequest(`/api/app/admin/runners/${runner.id}`,{method:'DELETE',body:{}})\n if(configuration.value?.runnerId===runner.id)configuration.value=undefined\n await refresh(); notice.value = `已停用 ${runner.name}`\n })\n}\nonMounted(()=>{void action(refresh)})\n</script>\n\n<template>\n <section class=\"runner-panel\" aria-label=\"执行节点设置\">\n <p>执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。</p>\n <p v-if=\"bundleAvailable\"><a href=\"/api/app/admin/runners/bundle\" download>下载配套执行节点程序</a>(包含桌面资源,需要 Node.js 24 或更高版本)。</p>\n <div class=\"runner-toolbar\"><strong>已注册节点</strong><button type=\"button\" :disabled=\"busy\" @click=\"action(refresh)\">刷新状态</button></div>\n <ul v-if=\"runners.length\" class=\"runner-list\">\n <li v-for=\"runner in runners\" :key=\"runner.id\">\n <div><strong>{{ runner.name }}</strong><span>{{ !runner.enabled ? '已停用' : runner.online ? '已连接' : '待连接' }}</span></div>\n <button v-if=\"runner.enabled\" type=\"button\" :disabled=\"busy\" :aria-label=\"`停用 ${runner.name}`\" @click=\"disable(runner)\">停用</button>\n </li>\n </ul>\n <p v-else>尚未注册执行节点。</p>\n <form v-if=\"!configuration\" @submit.prevent=\"register\">\n <label>节点名称<input v-model=\"name\" required maxlength=\"100\" autocomplete=\"off\" placeholder=\"我的 Mac\" :disabled=\"busy\"></label>\n <label>Yaovi服务地址<input v-model=\"serverURL\" type=\"url\" required :disabled=\"busy\"><small>必须能从 Runner 所在电脑访问。</small></label>\n <label>允许导出的产物目录<textarea v-model=\"artifactRoots\" rows=\"2\" :disabled=\"busy\" placeholder=\"每行一个绝对路径,留空时禁止导出文件\"></textarea></label>\n <label class=\"runner-check\"><input v-model=\"insecure\" type=\"checkbox\" :disabled=\"busy\">使用可信局域网 HTTP</label>\n <label class=\"runner-check\"><input v-model=\"managedBrowser\" type=\"checkbox\" :disabled=\"busy\">托管浏览器</label>\n <small>默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。</small>\n <label class=\"runner-check\"><input v-model=\"computers\" type=\"checkbox\" :disabled=\"busy\">启用隔离电脑 Worker</label>\n <fieldset v-if=\"computers\" class=\"runner-computer\">\n <legend>执行电脑配置</legend><p v-if=\"fixedDesktops\">桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。</p>\n <label v-if=\"!fixedDesktops\">容器运行时<select v-model=\"computerRuntime\"><option value=\"docker\">Docker</option><option value=\"podman\">Podman</option></select></label>\n <label v-if=\"!fixedDesktops\">联网范围<select v-model=\"computerNetwork\"><option value=\"public-proxy\">公网网页(HTTP / HTTPS)</option><option value=\"none\">不联网</option></select></label>\n <small v-if=\"!fixedDesktops\">公网模式通过受控代理访问,不开放宿主和内网地址。</small>\n <label v-if=\"!fixedDesktops\">兼容镜像 ID<input v-model.trim=\"computerImage\" placeholder=\"可在本地虚拟机设置中准备\"></label>\n <small>这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。</small>\n </fieldset>\n <button class=\"solid-button\" :disabled=\"busy || !name.trim()\">注册节点</button>\n </form>\n <div v-else class=\"runner-setup\">\n <button class=\"solid-button\" type=\"button\" @click=\"download\">下载 Runner 配置</button>\n <p>配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:</p>\n <code v-if=\"bundleAvailable\">mkdir -p yaovi-runner<br>tar -xzf yaovi-runner.tar.gz -C yaovi-runner<br>chmod 600 runner.json<br>node yaovi-runner/runner.mjs --config /完整路径/runner.json</code>\n <code v-else>chmod 600 runner.json<br>npm run runner -- --config /完整路径/runner.json</code>\n <small>{{bundleAvailable?(configuration.computers?'在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。':'在执行电脑上启动节点。'):'在 yaovi 项目中先运行 npm run build。'}}配置丢失后可停用节点并重新注册。</small>\n </div>\n <p v-if=\"error\" class=\"runner-error\" role=\"alert\">{{ error }}</p>\n <p v-if=\"notice\" role=\"status\">{{ notice }}</p>\n </section>\n</template>\n\n<style scoped>\n.runner-panel { display: grid; gap: 14px; min-width: 0; font-size: 13px; }\n.runner-panel p { margin: 0; line-height: 1.6; color: var(--text-secondary); }\n.runner-toolbar,.runner-list li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.runner-panel button { min-height: 36px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); cursor: pointer; }\n.runner-panel button:disabled { opacity: .5; cursor: default; }\n.runner-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }\n.runner-list li { padding: 12px; border: 1px solid var(--line); border-radius: 10px; }\n.runner-list li div { display: grid; gap: 5px; overflow-wrap: anywhere; }\n.runner-list span,.runner-panel small { color: var(--text-secondary); font-size: 12px; line-height: 1.5; }\n.runner-panel form,.runner-panel label,.runner-setup { display: grid; gap: 8px; min-width: 0; }\n.runner-panel form { gap: 14px; }\n.runner-computer { display: grid; gap: 12px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; min-width: 0; }\n.runner-panel input:not([type=checkbox]),.runner-panel select,.runner-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); font: inherit; }\n.runner-panel .runner-check { display: flex; align-items: center; }\n.runner-panel code { overflow-wrap: anywhere; line-height: 1.7; }\n.runner-panel .runner-error { color: var(--danger); }\n.runner-panel :is(input,select,textarea,button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n</style>","<script setup lang=\"ts\">\nimport {ref,onMounted,onBeforeUnmount} from 'vue'\nimport {apiRequest} from '@/api/client'\nimport {createUuid} from '@/utils/id'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport VmMaintenanceStatus from '@/components/common/VmMaintenanceStatus.vue'\nimport RunnerSettingsPanel from './RunnerSettingsPanel.vue'\nimport type {LocalVmStatus,LocalVmMode,LocalVmAction,LocalVmInstance} from '@shared/localVm'\nimport {LOCAL_VM_IMAGES,VM_IDLE_STOP_OPTIONS,vmIdleStopLabel,type LocalVmImageKey} from '@shared/localVm'\nimport type {WorkspaceAgent} from '@shared/workspace'\nconst state=ref<LocalVmStatus>(),error=ref(''),loading=ref(false),acting=ref(false)\nconst runnerSettingsOpen=ref(false)\nconst prepareImage=ref<LocalVmImageKey>('standard')\nconst instances=ref<Array<{agent:WorkspaceAgent;vm:LocalVmInstance}>>([])\nlet closed=false,timer:ReturnType<typeof setTimeout>|undefined\nasync function refresh(){\n if(loading.value||acting.value||closed)return\n loading.value=true\n try{\n const next=await apiRequest<LocalVmStatus>('/api/app/admin/local-vm');if(closed)return;state.value=next\n const list=await apiRequest<{computers:Array<{agent:WorkspaceAgent}>}>('/api/app/computers')\n const seen=new Set<string>()\n const agents=list.computers.map(x=>x.agent).filter(a=>(a.execution==='computer'||!!a.computerEnvironmentId)&&!a.remoteAgentId&&a.nodeId==='local'&&!seen.has(a.computerEnvironmentId??a.id)&&!!seen.add(a.computerEnvironmentId??a.id))\n const rows=await Promise.all(agents.map(async agent=>({agent,vm:await apiRequest<LocalVmInstance>(`/api/app/agents/${agent.id}/local-vm`)})))\n if(!closed)instances.value=rows\n }catch(e){if(!closed)error.value=e instanceof Error?e.message:'无法读取本地虚拟机状态'}finally{loading.value=false}\n}\nasync function action(work:()=>Promise<unknown>){acting.value=true;error.value='';try{await work()}catch(e){error.value=e instanceof Error?e.message:'操作未完成'}finally{acting.value=false;await refresh()}}\nfunction prepare(){void action(()=>apiRequest('/api/app/admin/local-vm/prepare',{method:'POST',body:{requestId:createUuid(),imageKey:prepareImage.value}}))}\nfunction policy(mode:LocalVmMode,maxInstances=state.value?.maxInstances??2){\n if(mode==='shared'&&state.value?.mode!=='shared'&&!confirm('当前账号的多个机器人将共用桌面、工作文件和浏览器登录。确认使用共享虚拟机?'))return\n void action(()=>apiRequest('/api/app/admin/local-vm/policy',{method:'PUT',body:{requestId:createUuid(),mode,maxInstances}}))\n}\nfunction manage(agent:WorkspaceAgent,operation:LocalVmAction){\n if(operation==='remove'&&!confirm(`移除“${agent.name}”的虚拟机实例?工作文件、软件配置和登录资料将保留。`))return\n void action(()=>apiRequest(`/api/app/agents/${agent.id}/local-vm/${operation}`,{method:'POST',body:{}}))\n}\nasync function cycle(){await refresh();if(!closed)timer=setTimeout(cycle,state.value?.busy?2000:10000)}\nonMounted(cycle);onBeforeUnmount(()=>{closed=true;clearTimeout(timer)})\n</script>\n<template>\n <section class=\"local-vm-settings\" aria-label=\"本地虚拟机设置\">\n <article><h3>{{state?.fixedCapacity?'Compose 共享桌面':'本地虚拟机'}}</h3><p>{{state?.fixedCapacity?'桌面容器随 Web 的 Compose 文件部署,工作文件与浏览器资料保存在各桌面的数据卷中。':'在 Yaovi 所在电脑的隔离 Linux 桌面中运行机器人,工作文件与浏览器资料持续保存。'}}</p>\n <div class=\"row\"><span class=\"status\"><AppIcon :name=\"state?.image?'check':'monitor'\"/>{{loading&&!state?'正在检查…':state?.maintenance?.phase==='ready'?'虚拟机环境已就绪':state?.image?'镜像已准备':'尚未就绪'}}</span><button :disabled=\"loading||acting\" @click=\"error='';refresh()\"><AppIcon name=\"refresh\"/>重新检查</button></div>\n <VmMaintenanceStatus v-if=\"state?.maintenance\" :state=\"state.maintenance\" />\n <p v-if=\"state?.problem\">{{state.problem}}</p><p v-if=\"error\" role=\"alert\">{{error}}</p>\n <p v-if=\"state?.job&&state.job.state!=='complete'\" role=\"status\">{{state.job.message}}</p>\n </article>\n <article v-if=\"state?.executionHost==='runner'\"><h3>Yaovi 执行连接</h3><p>{{state.fixedCapacity?'执行连接负责 Yaovi 模型与工具调用,桌面容器完全由这套 Compose 管理。':'虚拟桌面由运行 Yaovi 的执行节点提供,Docker 版 Web 通过这条连接使用桌面。'}}{{state.runnerName?`当前节点:${state.runnerName}。`:''}}</p><button :aria-expanded=\"runnerSettingsOpen\" @click=\"runnerSettingsOpen=!runnerSettingsOpen\">{{runnerSettingsOpen?'收起执行节点设置':'打开执行节点设置'}}</button><RunnerSettingsPanel v-if=\"runnerSettingsOpen\" computer-setup /></article>\n <article v-if=\"state?.fixedCapacity\"><h3>Compose 共享桌面</h3><p>已配置 {{state.maxInstances}} 台桌面,数量由 Compose 固定。请在机器人聊天的电脑面板选择已有桌面。</p><div v-for=\"desktop in state.desktops\" :key=\"desktop.id\" class=\"instance\"><strong>{{desktop.name}} · {{desktop.imageKey==='cursor'?'Cursor Universal':'标准桌面'}}</strong><span>{{desktop.ready?'已就绪':desktop.online?'正在启动':'未连接'}}</span></div></article>\n <article v-else-if=\"state\"><h3>隔离方式</h3><p>按机器人创建独立桌面,或让可信的机器人共用一个桌面。</p>\n <div class=\"segmented\" role=\"group\" aria-label=\"虚拟机隔离方式\"><button :aria-pressed=\"state?.mode==='shared'\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @click=\"policy('shared')\">共享虚拟机</button><button :aria-pressed=\"state?.mode==='per-bot'\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @click=\"policy('per-bot')\">每个机器人独立</button></div>\n <p class=\"sharing-explanation\">共享指多个机器人同时使用同一台虚拟机:命令与文件操作并行执行,桌面操作交替进行,共用桌面和工作文件。</p>\n <label class=\"row\">虚拟机运行数量上限<select aria-label=\"虚拟机数量上限\" :value=\"state?.maxInstances??2\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @change=\"policy(state!.mode,Number(($event.target as HTMLSelectElement).value))\"><option v-for=\"n in [1,2,3,4]\" :key=\"n\" :value=\"n\">{{n}}</option></select></label>\n </article>\n <article v-if=\"state&&!state.fixedCapacity\"><h3>空闲自动停止</h3><p>从任务结束或交还控制权后开始计时,适用于该执行节点上的所有本地虚拟机。</p>\n <label class=\"row\">停止时间<select aria-label=\"虚拟机空闲停止时间\" :value=\"state.idleStopMinutes??5\" :disabled=\"acting||!state.configured||state.idleStopMinutes===undefined\" @change=\"action(()=>apiRequest('/api/app/admin/local-vm/idle-policy',{method:'PUT',body:{requestId:createUuid(),idleStopMinutes:Number(($event.target as HTMLSelectElement).value)}}))\"><option v-for=\"minutes in VM_IDLE_STOP_OPTIONS\" :key=\"minutes\" :value=\"minutes\">{{vmIdleStopLabel(minutes)}}</option><option v-if=\"state.idleStopMinutes!==undefined&&!VM_IDLE_STOP_OPTIONS.some(n=>n===state!.idleStopMinutes)\" :value=\"state.idleStopMinutes\">{{vmIdleStopLabel(state.idleStopMinutes)}}</option></select></label>\n <p v-if=\"state.idleStopMinutes===undefined\">连接并更新执行节点后可修改。</p><p v-else-if=\"state.idleStopMinutes===0\">空闲时保持运行;仍可手动停止,执行节点关闭时也会停止。</p>\n </article>\n <article v-if=\"state&&!state.fixedCapacity&&!state.setupRequired\"><h3>准备运行环境</h3><p v-if=\"state?.executionHost==='runner'\">以下检查和镜像准备均在执行节点所在电脑完成。</p><ol>\n <li><span class=\"step\">{{state?.runtime?'✓':'1'}}</span><div><strong>安装 Docker 或 Podman</strong><p>{{state?.runtime?`已检测到 ${state.runtime}`:'请先安装 Docker Desktop 或 Podman。'}}</p></div></li>\n <li><span class=\"step\">{{state?.daemonUp?'✓':'2'}}</span><div><strong>启动容器运行环境</strong><p>{{state?.daemonUp?'运行环境已启动':'打开 Docker / Podman,等待它完成启动后重新检查。'}}</p></div></li>\n <li><span class=\"step\">{{state?.image?'✓':'3'}}</span><div class=\"image-preparation\"><strong>准备虚拟机镜像</strong><p>两个镜像可分别准备。独立桌面可各自选择;同一共享桌面的所有机器人使用一个镜像。</p><label>镜像<select v-model=\"prepareImage\" :disabled=\"acting||state.busy\"><option v-for=\"image in LOCAL_VM_IMAGES\" :key=\"image.key\" :value=\"image.key\">{{image.name}} · {{state.images?.find(i=>i.key===image.key)?.ready?'已就绪':'未准备'}}</option></select></label><p>{{LOCAL_VM_IMAGES.find(i=>i.key===prepareImage)?.description}}{{prepareImage==='cursor'?';Apple 芯片电脑需要运行环境支持 amd64 模拟。':''}}</p><button class=\"primary\" :disabled=\"acting||state?.busy||!state?.daemonUp\" @click=\"prepare\">{{state?.busy?'正在准备…':state.images?.find(i=>i.key===prepareImage)?.ready?'重新检查所选镜像':'准备所选镜像'}}</button></div></li>\n <li><span class=\"step\">4</span><div><strong>{{state.maintenance?'自动启动与维护':'从机器人聊天中创建桌面'}}</strong><p>{{state.maintenance?'运行环境就绪后会自动准备并启动桌面。可在聊天的“电脑”中查看和使用;手动停止后由新任务或明确启动唤醒。':'打开聊天的“电脑”,选择“本地虚拟机”,然后创建该机器人的虚拟机。'}}</p></div></li>\n </ol></article>\n <article v-if=\"state&&!state.fixedCapacity\"><h3>{{state?.mode==='shared'?'共享虚拟机':'机器人虚拟机'}}</h3><p v-if=\"!instances.length\">还没有机器人使用本地虚拟机。</p>\n <div v-for=\"row in instances\" :key=\"row.agent.id\" class=\"instance\"><div><strong>{{row.agent.computerEnvironmentName??row.agent.name}}</strong><p>{{row.vm.inUse?'正在使用':({running:'运行中',stopped:'已停止',missing:'尚未创建'})[row.vm.container]}}</p></div><div class=\"row\">\n <button v-if=\"row.vm.container==='running'\" :disabled=\"acting||row.vm.inUse\" @click=\"manage(row.agent,'stop')\">停止</button>\n <button v-else :disabled=\"acting||!state?.image\" @click=\"manage(row.agent,row.vm.container==='missing'?'create':'start')\">{{row.vm.container==='missing'?'创建':'启动'}}</button>\n <button v-if=\"row.vm.container!=='missing'\" :disabled=\"acting||row.vm.inUse\" @click=\"manage(row.agent,'remove')\">移除实例</button>\n </div></div>\n <div v-for=\"item in state?.instances?.filter(i=>i.orphaned)\" :key=\"item.id\" class=\"instance\"><div><strong>{{item.name}}</strong><p>暂无机器人使用 · {{item.status==='free'?'已停止':'运行中或待核对'}}</p></div><div class=\"row\"><button :disabled=\"acting\" @click=\"action(()=>apiRequest(`/api/app/admin/local-vm/instances/${item.id}/stop`,{method:'POST',body:{requestId:createUuid()}}))\">停止</button><button :disabled=\"acting\" @click=\"action(()=>apiRequest(`/api/app/admin/local-vm/instances/${item.id}/remove`,{method:'POST',body:{requestId:createUuid()}}))\">移除实例</button></div></div>\n </article>\n </section>\n</template>\n<style scoped>\n.local-vm-settings{display:grid;gap:16px;max-width:820px}article{padding:20px;border:1px solid var(--line);border-radius:14px;display:grid;gap:12px;background:var(--surface)}h3,p{margin:0}h3{font-size:15px}p{font-size:13px;line-height:1.7;color:var(--text-secondary)}.row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.status{display:inline-flex;align-items:center;gap:7px;font-size:13px}.segmented{display:flex;border:1px solid var(--line);border-radius:9px;overflow:hidden}.segmented button{flex:1;border:0;border-radius:0}.segmented button[aria-pressed=true]{background:var(--surface-soft);font-weight:600}.instance{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-top:12px;border-top:1px solid var(--line)}button,select{font:inherit;color:var(--text-primary);background:var(--surface-raised);border:1px solid var(--line);border-radius:8px;min-height:40px;padding:7px 12px}button{display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer}button:disabled{opacity:.45;cursor:default}.primary{background:var(--accent);color:var(--text-on-solid);margin-top:8px}button:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}ol{list-style:none;margin:0;padding:0;display:grid;gap:20px}li{display:flex;gap:12px}.step{display:grid;place-items:center;border:1px solid var(--line);border-radius:50%;height:26px;width:26px;flex:none;font-size:12px}li strong{font-size:13px}[role=alert]{color:var(--danger)}\n</style>\n<style scoped>.image-preparation{display:grid;gap:10px;min-width:0}.image-preparation label{display:grid;gap:6px}.image-preparation select{width:100%;min-height:44px}.image-preparation button{justify-self:start}</style>\n","<script setup lang=\"ts\">\nimport {ref,onMounted,onBeforeUnmount} from 'vue'\nimport {apiRequest} from '@/api/client'\nimport {createUuid} from '@/utils/id'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport VmMaintenanceStatus from '@/components/common/VmMaintenanceStatus.vue'\nimport RunnerSettingsPanel from './RunnerSettingsPanel.vue'\nimport type {LocalVmStatus,LocalVmMode,LocalVmAction,LocalVmInstance} from '@shared/localVm'\nimport {LOCAL_VM_IMAGES,VM_IDLE_STOP_OPTIONS,vmIdleStopLabel,type LocalVmImageKey} from '@shared/localVm'\nimport type {WorkspaceAgent} from '@shared/workspace'\nconst state=ref<LocalVmStatus>(),error=ref(''),loading=ref(false),acting=ref(false)\nconst runnerSettingsOpen=ref(false)\nconst prepareImage=ref<LocalVmImageKey>('standard')\nconst instances=ref<Array<{agent:WorkspaceAgent;vm:LocalVmInstance}>>([])\nlet closed=false,timer:ReturnType<typeof setTimeout>|undefined\nasync function refresh(){\n if(loading.value||acting.value||closed)return\n loading.value=true\n try{\n const next=await apiRequest<LocalVmStatus>('/api/app/admin/local-vm');if(closed)return;state.value=next\n const list=await apiRequest<{computers:Array<{agent:WorkspaceAgent}>}>('/api/app/computers')\n const seen=new Set<string>()\n const agents=list.computers.map(x=>x.agent).filter(a=>(a.execution==='computer'||!!a.computerEnvironmentId)&&!a.remoteAgentId&&a.nodeId==='local'&&!seen.has(a.computerEnvironmentId??a.id)&&!!seen.add(a.computerEnvironmentId??a.id))\n const rows=await Promise.all(agents.map(async agent=>({agent,vm:await apiRequest<LocalVmInstance>(`/api/app/agents/${agent.id}/local-vm`)})))\n if(!closed)instances.value=rows\n }catch(e){if(!closed)error.value=e instanceof Error?e.message:'无法读取本地虚拟机状态'}finally{loading.value=false}\n}\nasync function action(work:()=>Promise<unknown>){acting.value=true;error.value='';try{await work()}catch(e){error.value=e instanceof Error?e.message:'操作未完成'}finally{acting.value=false;await refresh()}}\nfunction prepare(){void action(()=>apiRequest('/api/app/admin/local-vm/prepare',{method:'POST',body:{requestId:createUuid(),imageKey:prepareImage.value}}))}\nfunction policy(mode:LocalVmMode,maxInstances=state.value?.maxInstances??2){\n if(mode==='shared'&&state.value?.mode!=='shared'&&!confirm('当前账号的多个机器人将共用桌面、工作文件和浏览器登录。确认使用共享虚拟机?'))return\n void action(()=>apiRequest('/api/app/admin/local-vm/policy',{method:'PUT',body:{requestId:createUuid(),mode,maxInstances}}))\n}\nfunction manage(agent:WorkspaceAgent,operation:LocalVmAction){\n if(operation==='remove'&&!confirm(`移除“${agent.name}”的虚拟机实例?工作文件、软件配置和登录资料将保留。`))return\n void action(()=>apiRequest(`/api/app/agents/${agent.id}/local-vm/${operation}`,{method:'POST',body:{}}))\n}\nasync function cycle(){await refresh();if(!closed)timer=setTimeout(cycle,state.value?.busy?2000:10000)}\nonMounted(cycle);onBeforeUnmount(()=>{closed=true;clearTimeout(timer)})\n</script>\n<template>\n <section class=\"local-vm-settings\" aria-label=\"本地虚拟机设置\">\n <article><h3>{{state?.fixedCapacity?'Compose 共享桌面':'本地虚拟机'}}</h3><p>{{state?.fixedCapacity?'桌面容器随 Web 的 Compose 文件部署,工作文件与浏览器资料保存在各桌面的数据卷中。':'在 Yaovi 所在电脑的隔离 Linux 桌面中运行机器人,工作文件与浏览器资料持续保存。'}}</p>\n <div class=\"row\"><span class=\"status\"><AppIcon :name=\"state?.image?'check':'monitor'\"/>{{loading&&!state?'正在检查…':state?.maintenance?.phase==='ready'?'虚拟机环境已就绪':state?.image?'镜像已准备':'尚未就绪'}}</span><button :disabled=\"loading||acting\" @click=\"error='';refresh()\"><AppIcon name=\"refresh\"/>重新检查</button></div>\n <VmMaintenanceStatus v-if=\"state?.maintenance\" :state=\"state.maintenance\" />\n <p v-if=\"state?.problem\">{{state.problem}}</p><p v-if=\"error\" role=\"alert\">{{error}}</p>\n <p v-if=\"state?.job&&state.job.state!=='complete'\" role=\"status\">{{state.job.message}}</p>\n </article>\n <article v-if=\"state?.executionHost==='runner'\"><h3>Yaovi 执行连接</h3><p>{{state.fixedCapacity?'执行连接负责 Yaovi 模型与工具调用,桌面容器完全由这套 Compose 管理。':'虚拟桌面由运行 Yaovi 的执行节点提供,Docker 版 Web 通过这条连接使用桌面。'}}{{state.runnerName?`当前节点:${state.runnerName}。`:''}}</p><button :aria-expanded=\"runnerSettingsOpen\" @click=\"runnerSettingsOpen=!runnerSettingsOpen\">{{runnerSettingsOpen?'收起执行节点设置':'打开执行节点设置'}}</button><RunnerSettingsPanel v-if=\"runnerSettingsOpen\" computer-setup /></article>\n <article v-if=\"state?.fixedCapacity\"><h3>Compose 共享桌面</h3><p>已配置 {{state.maxInstances}} 台桌面,数量由 Compose 固定。请在机器人聊天的电脑面板选择已有桌面。</p><div v-for=\"desktop in state.desktops\" :key=\"desktop.id\" class=\"instance\"><strong>{{desktop.name}} · {{desktop.imageKey==='cursor'?'Cursor Universal':'标准桌面'}}</strong><span>{{desktop.ready?'已就绪':desktop.online?'正在启动':'未连接'}}</span></div></article>\n <article v-else-if=\"state\"><h3>隔离方式</h3><p>按机器人创建独立桌面,或让可信的机器人共用一个桌面。</p>\n <div class=\"segmented\" role=\"group\" aria-label=\"虚拟机隔离方式\"><button :aria-pressed=\"state?.mode==='shared'\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @click=\"policy('shared')\">共享虚拟机</button><button :aria-pressed=\"state?.mode==='per-bot'\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @click=\"policy('per-bot')\">每个机器人独立</button></div>\n <p class=\"sharing-explanation\">共享指多个机器人同时使用同一台虚拟机:命令与文件操作并行执行,桌面操作交替进行,共用桌面和工作文件。</p>\n <label class=\"row\">虚拟机运行数量上限<select aria-label=\"虚拟机数量上限\" :value=\"state?.maxInstances??2\" :disabled=\"!state?.configured||!!state?.setupRequired||acting||state.busy\" @change=\"policy(state!.mode,Number(($event.target as HTMLSelectElement).value))\"><option v-for=\"n in [1,2,3,4]\" :key=\"n\" :value=\"n\">{{n}}</option></select></label>\n </article>\n <article v-if=\"state&&!state.fixedCapacity\"><h3>空闲自动停止</h3><p>从任务结束或交还控制权后开始计时,适用于该执行节点上的所有本地虚拟机。</p>\n <label class=\"row\">停止时间<select aria-label=\"虚拟机空闲停止时间\" :value=\"state.idleStopMinutes??5\" :disabled=\"acting||!state.configured||state.idleStopMinutes===undefined\" @change=\"action(()=>apiRequest('/api/app/admin/local-vm/idle-policy',{method:'PUT',body:{requestId:createUuid(),idleStopMinutes:Number(($event.target as HTMLSelectElement).value)}}))\"><option v-for=\"minutes in VM_IDLE_STOP_OPTIONS\" :key=\"minutes\" :value=\"minutes\">{{vmIdleStopLabel(minutes)}}</option><option v-if=\"state.idleStopMinutes!==undefined&&!VM_IDLE_STOP_OPTIONS.some(n=>n===state!.idleStopMinutes)\" :value=\"state.idleStopMinutes\">{{vmIdleStopLabel(state.idleStopMinutes)}}</option></select></label>\n <p v-if=\"state.idleStopMinutes===undefined\">连接并更新执行节点后可修改。</p><p v-else-if=\"state.idleStopMinutes===0\">空闲时保持运行;仍可手动停止,执行节点关闭时也会停止。</p>\n </article>\n <article v-if=\"state&&!state.fixedCapacity&&!state.setupRequired\"><h3>准备运行环境</h3><p v-if=\"state?.executionHost==='runner'\">以下检查和镜像准备均在执行节点所在电脑完成。</p><ol>\n <li><span class=\"step\">{{state?.runtime?'✓':'1'}}</span><div><strong>安装 Docker 或 Podman</strong><p>{{state?.runtime?`已检测到 ${state.runtime}`:'请先安装 Docker Desktop 或 Podman。'}}</p></div></li>\n <li><span class=\"step\">{{state?.daemonUp?'✓':'2'}}</span><div><strong>启动容器运行环境</strong><p>{{state?.daemonUp?'运行环境已启动':'打开 Docker / Podman,等待它完成启动后重新检查。'}}</p></div></li>\n <li><span class=\"step\">{{state?.image?'✓':'3'}}</span><div class=\"image-preparation\"><strong>准备虚拟机镜像</strong><p>两个镜像可分别准备。独立桌面可各自选择;同一共享桌面的所有机器人使用一个镜像。</p><label>镜像<select v-model=\"prepareImage\" :disabled=\"acting||state.busy\"><option v-for=\"image in LOCAL_VM_IMAGES\" :key=\"image.key\" :value=\"image.key\">{{image.name}} · {{state.images?.find(i=>i.key===image.key)?.ready?'已就绪':'未准备'}}</option></select></label><p>{{LOCAL_VM_IMAGES.find(i=>i.key===prepareImage)?.description}}{{prepareImage==='cursor'?';Apple 芯片电脑需要运行环境支持 amd64 模拟。':''}}</p><button class=\"primary\" :disabled=\"acting||state?.busy||!state?.daemonUp\" @click=\"prepare\">{{state?.busy?'正在准备…':state.images?.find(i=>i.key===prepareImage)?.ready?'重新检查所选镜像':'准备所选镜像'}}</button></div></li>\n <li><span class=\"step\">4</span><div><strong>{{state.maintenance?'自动启动与维护':'从机器人聊天中创建桌面'}}</strong><p>{{state.maintenance?'运行环境就绪后会自动准备并启动桌面。可在聊天的“电脑”中查看和使用;手动停止后由新任务或明确启动唤醒。':'打开聊天的“电脑”,选择“本地虚拟机”,然后创建该机器人的虚拟机。'}}</p></div></li>\n </ol></article>\n <article v-if=\"state&&!state.fixedCapacity\"><h3>{{state?.mode==='shared'?'共享虚拟机':'机器人虚拟机'}}</h3><p v-if=\"!instances.length\">还没有机器人使用本地虚拟机。</p>\n <div v-for=\"row in instances\" :key=\"row.agent.id\" class=\"instance\"><div><strong>{{row.agent.computerEnvironmentName??row.agent.name}}</strong><p>{{row.vm.inUse?'正在使用':({running:'运行中',stopped:'已停止',missing:'尚未创建'})[row.vm.container]}}</p></div><div class=\"row\">\n <button v-if=\"row.vm.container==='running'\" :disabled=\"acting||row.vm.inUse\" @click=\"manage(row.agent,'stop')\">停止</button>\n <button v-else :disabled=\"acting||!state?.image\" @click=\"manage(row.agent,row.vm.container==='missing'?'create':'start')\">{{row.vm.container==='missing'?'创建':'启动'}}</button>\n <button v-if=\"row.vm.container!=='missing'\" :disabled=\"acting||row.vm.inUse\" @click=\"manage(row.agent,'remove')\">移除实例</button>\n </div></div>\n <div v-for=\"item in state?.instances?.filter(i=>i.orphaned)\" :key=\"item.id\" class=\"instance\"><div><strong>{{item.name}}</strong><p>暂无机器人使用 · {{item.status==='free'?'已停止':'运行中或待核对'}}</p></div><div class=\"row\"><button :disabled=\"acting\" @click=\"action(()=>apiRequest(`/api/app/admin/local-vm/instances/${item.id}/stop`,{method:'POST',body:{requestId:createUuid()}}))\">停止</button><button :disabled=\"acting\" @click=\"action(()=>apiRequest(`/api/app/admin/local-vm/instances/${item.id}/remove`,{method:'POST',body:{requestId:createUuid()}}))\">移除实例</button></div></div>\n </article>\n </section>\n</template>\n<style scoped>\n.local-vm-settings{display:grid;gap:16px;max-width:820px}article{padding:20px;border:1px solid var(--line);border-radius:14px;display:grid;gap:12px;background:var(--surface)}h3,p{margin:0}h3{font-size:15px}p{font-size:13px;line-height:1.7;color:var(--text-secondary)}.row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.status{display:inline-flex;align-items:center;gap:7px;font-size:13px}.segmented{display:flex;border:1px solid var(--line);border-radius:9px;overflow:hidden}.segmented button{flex:1;border:0;border-radius:0}.segmented button[aria-pressed=true]{background:var(--surface-soft);font-weight:600}.instance{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-top:12px;border-top:1px solid var(--line)}button,select{font:inherit;color:var(--text-primary);background:var(--surface-raised);border:1px solid var(--line);border-radius:8px;min-height:40px;padding:7px 12px}button{display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer}button:disabled{opacity:.45;cursor:default}.primary{background:var(--accent);color:var(--text-on-solid);margin-top:8px}button:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}ol{list-style:none;margin:0;padding:0;display:grid;gap:20px}li{display:flex;gap:12px}.step{display:grid;place-items:center;border:1px solid var(--line);border-radius:50%;height:26px;width:26px;flex:none;font-size:12px}li strong{font-size:13px}[role=alert]{color:var(--danger)}\n</style>\n<style scoped>.image-preparation{display:grid;gap:10px;min-width:0}.image-preparation label{display:grid;gap:6px}.image-preparation select{width:100%;min-height:44px}.image-preparation button{justify-self:start}</style>\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Et as e,F as t,H as n,L as r,Ot as i,V as a,_ as o,a as s,b as c,d as l,et as u,m as d,p as f,st as p,t as m,u as h}from"./_plugin-vue_export-helper-CD0r-vVb.js";import{
|
|
2
|
-
//# sourceMappingURL=ModelNetworkChoice-
|
|
1
|
+
import{Et as e,F as t,H as n,L as r,Ot as i,V as a,_ as o,a as s,b as c,d as l,et as u,m as d,p as f,st as p,t as m,u as h}from"./_plugin-vue_export-helper-CD0r-vVb.js";import{j as g}from"./index-P_kZCZ2-.js";var _=`/api/app/admin/hermes/network`,v=e=>e?`?bot=${encodeURIComponent(e)}`:``,y={get:e=>g(_+v(e)),save:(e,t,n,r)=>g(`${_}/${n?`connections/`+encodeURIComponent(n):`default`}${v(r)}`,{method:`PUT`,body:{revision:e,policy:t}}),proxy:(e,t)=>g(`/api/app/admin/hermes/network/proxies`,{method:`POST`,body:{revision:e,proxy:t}}),remove:(e,t)=>g(`/api/app/admin/hermes/network/proxies/`+encodeURIComponent(t),{method:`DELETE`,body:{revision:e}}),test:(e,t)=>g(`/api/app/admin/hermes/network/test`+v(t),{method:`POST`,body:e})};function b(e,t){return t.mode===`inherit`?`跟随默认 · `+b(e,e.default):t.mode===`environment`?e.environmentDetected?`服务器环境代理`:`服务器环境(未检测到代理)`:t.mode===`direct`?`直接连接`:e.proxies.find(e=>e.id===t.proxyId)?.name??`代理已不可用`}var x={key:0,class:`compact-choice`},S=[`value`,`disabled`],C=[`value`],w=[`disabled`],T=[`name`,`checked`,`value`,`onChange`],E={key:2,class:`proxy-select`},D={key:0},O=[`value`,`disabled`],k=[`value`],A={key:0},j={key:1},M={key:3,class:`summary`},N={key:4,class:`network-diagnostics`},P={key:1},F=[`disabled`],I={key:5,role:`alert`},L=m(c({__name:`ModelNetworkChoice`,props:{view:{},modelValue:{},provider:{},bot:{},disabled:{type:Boolean},compact:{type:Boolean},global:{type:Boolean},summaryOnly:{type:Boolean},hideTest:{type:Boolean}},emits:[`update:modelValue`,`manage`,`busy`],setup(c,{expose:m,emit:g}){let _=c,v=g,L=a(),R=u(!1),z=u(),B=u(``),V=h(()=>b(_.view,_.modelValue)),H=h(()=>_.view.proxies.find(e=>e.id===_.modelValue.proxyId)),U=h(()=>_.global?[{id:`environment`,name:`沿用服务器环境`,hint:_.view.environmentDetected?`已检测到环境代理,保留当前网络行为`:`未检测到环境代理`},{id:`direct`,name:`直接连接`,hint:`不使用代理`},{id:`proxy`,name:`指定代理`,hint:`通过已保存的代理连接`}]:[{id:`inherit`,name:`跟随服务器默认`,hint:b(_.view,_.view.default)},{id:`direct`,name:`直接连接`,hint:`此连接不使用代理`},{id:`proxy`,name:`指定代理`,hint:`仅此连接使用所选代理`}]);function W(e){v(`update:modelValue`,e===`proxy`?{mode:e,proxyId:_.modelValue.proxyId??_.view.proxies[0]?.id}:{mode:e})}n(()=>JSON.stringify([_.modelValue,_.view.revision]),()=>{z.value=void 0,B.value=``});async function G(){if(!R.value){R.value=!0,v(`busy`,!0),B.value=``,z.value=void 0;try{z.value=await y.test({provider:_.provider,policy:_.modelValue.mode===`environment`?void 0:_.modelValue},_.bot)}catch(e){B.value=e instanceof Error?e.message:`测试失败,请重试。`}finally{R.value=!1,v(`busy`,!1)}}}return m({summary:V,test:G}),(n,a)=>(t(),d(`div`,{class:e([`network-choice`,{"global-choice":c.global}])},[c.compact&&!c.summaryOnly?(t(),d(`label`,x,[o(i(c.global?`默认连接方式`:`连接网络`),1),l(`select`,{value:c.modelValue.mode,disabled:c.disabled||R.value,onChange:a[0]||=e=>W(e.target.value)},[(t(!0),d(s,null,r(U.value,e=>(t(),d(`option`,{key:e.id,value:e.id},i(e.name),9,C))),128))],40,S)])):c.summaryOnly?f(``,!0):(t(),d(`fieldset`,{key:1,disabled:c.disabled||R.value},[l(`legend`,null,i(c.global?`默认连接方式`:`连接方式`),1),(t(!0),d(s,null,r(U.value,n=>(t(),d(`label`,{key:n.id,class:e([`network-option`,{selected:c.modelValue.mode===n.id}])},[l(`input`,{type:`radio`,name:p(L),checked:c.modelValue.mode===n.id,value:n.id,onChange:e=>W(n.id)},null,40,T),l(`span`,null,[l(`strong`,null,i(n.name),1),l(`small`,null,i(n.hint),1)])],2))),128))],8,w)),c.modelValue.mode===`proxy`&&!c.summaryOnly?(t(),d(`div`,E,[l(`label`,null,[c.compact||c.global?(t(),d(`span`,D,`使用代理`)):f(``,!0),l(`select`,{"aria-label":`使用代理`,value:c.modelValue.proxyId??``,disabled:c.disabled||R.value,onChange:a[1]||=e=>v(`update:modelValue`,{mode:`proxy`,proxyId:e.target.value})},[a[2]||=l(`option`,{value:``,disabled:``},`请选择代理`,-1),(t(!0),d(s,null,r(c.view.proxies,e=>(t(),d(`option`,{key:e.id,value:e.id},i(e.name),9,k))),128))],40,O)]),H.value?(t(),d(`small`,A,i(H.value.protocol.toUpperCase())+` · `+i(H.value.host)+`:`+i(H.value.port),1)):f(``,!0),c.view.proxies.length?f(``,!0):(t(),d(`p`,j,`尚未添加代理。请先在「环境与权限 → 模型网络」添加。`))])):f(``,!0),c.compact&&!c.global&&!c.summaryOnly?(t(),d(`p`,M,i(V.value),1)):f(``,!0),!c.global&&!c.hideTest?(t(),d(`div`,N,[z.value?(t(),d(`div`,{key:0,class:e([`test-result`,{failed:z.value.proxy===`failed`||z.value.target===`failed`}]),role:`status`},[l(`strong`,null,[o(`代理:`+i({passed:`端口可达`,failed:`连接失败`,skipped:`未检测`}[z.value.proxy]),1),c.provider?(t(),d(s,{key:0},[o(` · 目标服务:`+i({passed:`可达`,failed:`不可达`,skipped:`尚未检测`}[z.value.target]),1)],64)):f(``,!0)]),l(`small`,null,i(z.value.message),1)],2)):(t(),d(`div`,P,[...a[3]||=[l(`strong`,null,`连接测试`,-1),l(`small`,null,`由运行 Yaovi 的服务器发起`,-1)]])),l(`button`,{type:`button`,disabled:c.disabled||R.value||c.modelValue.mode===`proxy`&&!H.value,onClick:G},i(R.value?`正在测试…`:`测试当前选择`),9,F)])):f(``,!0),B.value?(t(),d(`p`,I,i(B.value),1)):f(``,!0)],2))}}),[[`__scopeId`,`data-v-a1f2e27a`]]);export{y as n,b as r,L as t};
|
|
2
|
+
//# sourceMappingURL=ModelNetworkChoice-BQ8m2N3V.js.map
|