@lsamien/yaovi 0.1.1 → 0.1.2
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 +23 -3
- package/desktop/main.mjs +18 -9
- package/dist/assets/{AboutDialog-CBKv505Y.js → AboutDialog-CJ9tCjSs.js} +2 -2
- package/dist/assets/AboutDialog-CJ9tCjSs.js.map +1 -0
- package/dist/assets/{BotAutomationsView-S3aHciek.js → BotAutomationsView-CsoI3js3.js} +2 -2
- package/dist/assets/{BotAutomationsView-S3aHciek.js.map → BotAutomationsView-CsoI3js3.js.map} +1 -1
- package/dist/assets/{BotControlSettingsPanel-DJF4oA6g.js → BotControlSettingsPanel-DIuBZlLu.js} +2 -2
- package/dist/assets/{BotControlSettingsPanel-DJF4oA6g.js.map → BotControlSettingsPanel-DIuBZlLu.js.map} +1 -1
- package/dist/assets/{BotPluginsDialog-BVOQ3DTp.js → BotPluginsDialog-BsGszDMH.js} +2 -2
- package/dist/assets/{BotPluginsDialog-BVOQ3DTp.js.map → BotPluginsDialog-BsGszDMH.js.map} +1 -1
- package/dist/assets/{BotSettingsPanel-CEH3KSAO.js → BotSettingsPanel-j3ThYgt2.js} +3 -3
- package/dist/assets/{BotSettingsPanel-CEH3KSAO.js.map → BotSettingsPanel-j3ThYgt2.js.map} +1 -1
- package/dist/assets/{ComputerPanel-CC-xkAxr.js → ComputerPanel-DdlgOm-B.js} +2 -2
- package/dist/assets/{ComputerPanel-CC-xkAxr.js.map → ComputerPanel-DdlgOm-B.js.map} +1 -1
- package/dist/assets/{ComputerViewerView-CEQrOmy7.js → ComputerViewerView-I3adPI6O.js} +2 -2
- package/dist/assets/{ComputerViewerView-CEQrOmy7.js.map → ComputerViewerView-I3adPI6O.js.map} +1 -1
- package/dist/assets/ConversationsView-CBhzg-9w.css +1 -0
- package/dist/assets/{ConversationsView-CzFWn-79.js → ConversationsView-CxVFk5sQ.js} +73 -73
- package/dist/assets/ConversationsView-CxVFk5sQ.js.map +1 -0
- package/dist/assets/{DevicePairingDialog-ByYk05DC.js → DevicePairingDialog-C307ARt-.js} +2 -2
- package/dist/assets/{DevicePairingDialog-ByYk05DC.js.map → DevicePairingDialog-C307ARt-.js.map} +1 -1
- package/dist/assets/{LocalVmSettingsPanel-DBkqouAU.js → LocalVmSettingsPanel-Zs7CiXdf.js} +2 -2
- package/dist/assets/{LocalVmSettingsPanel-DBkqouAU.js.map → LocalVmSettingsPanel-Zs7CiXdf.js.map} +1 -1
- package/dist/assets/{ProviderLoginDialog-DdPS62uM.js → ProviderLoginDialog-D3mqdAlo.js} +2 -2
- package/dist/assets/{ProviderLoginDialog-DdPS62uM.js.map → ProviderLoginDialog-D3mqdAlo.js.map} +1 -1
- package/dist/assets/{RunnerSettingsPanel-gzA2xzOg.js → RunnerSettingsPanel-DjMGnXGP.js} +2 -2
- package/dist/assets/{RunnerSettingsPanel-gzA2xzOg.js.map → RunnerSettingsPanel-DjMGnXGP.js.map} +1 -1
- package/dist/assets/{StandaloneDialog-B9HCNEFb.js → StandaloneDialog-BXCK22Dx.js} +2 -2
- package/dist/assets/{StandaloneDialog-B9HCNEFb.js.map → StandaloneDialog-BXCK22Dx.js.map} +1 -1
- package/dist/assets/{UpdateCheckDialog-djkXTQgD.js → UpdateCheckDialog-DAY0VEQm.js} +2 -2
- package/dist/assets/{UpdateCheckDialog-djkXTQgD.js.map → UpdateCheckDialog-DAY0VEQm.js.map} +1 -1
- package/dist/assets/{UsageView-DFaWBB40.js → UsageView-DNwG8LAx.js} +2 -2
- package/dist/assets/{UsageView-DFaWBB40.js.map → UsageView-DNwG8LAx.js.map} +1 -1
- package/dist/assets/{WorkspaceKnowledgePanel-In2a_s_z.js → WorkspaceKnowledgePanel-hy_IkIJI.js} +2 -2
- package/dist/assets/{WorkspaceKnowledgePanel-In2a_s_z.js.map → WorkspaceKnowledgePanel-hy_IkIJI.js.map} +1 -1
- package/dist/assets/{WorkspaceRoutinesPanel-zOxFoqML.js → WorkspaceRoutinesPanel-B7PkBP7x.js} +2 -2
- package/dist/assets/{WorkspaceRoutinesPanel-zOxFoqML.js.map → WorkspaceRoutinesPanel-B7PkBP7x.js.map} +1 -1
- package/dist/assets/{WorkstreamsView-HvlivC3b.js → WorkstreamsView-BeE2TRXK.js} +2 -2
- package/dist/assets/{WorkstreamsView-HvlivC3b.js.map → WorkstreamsView-BeE2TRXK.js.map} +1 -1
- package/dist/assets/{index-BPE9bBla.js → index-D11cgXI9.js} +3 -3
- package/dist/assets/{index-BPE9bBla.js.map → index-D11cgXI9.js.map} +1 -1
- package/dist/assets/{pdf-Q2a37aK5.js → pdf-BjTcV6As.js} +2 -2
- package/dist/assets/{pdf-Q2a37aK5.js.map → pdf-BjTcV6As.js.map} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/server/botControl.js +1 -1
- package/dist-server/server/desktopEnvironments.js +10 -9
- package/dist-server/server/desktopHostProtocol.js +1 -1
- package/dist-server/server/workspacePrompt.js +2 -2
- package/dist-server/shared/botEnvironment.js +10 -4
- package/dist-server/shared/fileTransferEndpoint.mjs +5 -5
- package/dist-server/shared/hostPaths.mjs +14 -4
- package/package.json +3 -2
- package/release.json +3 -3
- package/dist/assets/AboutDialog-CBKv505Y.js.map +0 -1
- package/dist/assets/ConversationsView-B6JNGLt_.css +0 -1
- package/dist/assets/ConversationsView-CzFWn-79.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{i as e}from"./rolldown-runtime-Dd_uD5pT.js";import{A as t,Dt as n,F as r,G as i,H as a,L as o,M as s,W as c,_ as l,a as u,b as d,d as f,et as p,f as m,m as h,p as g,t as _,u as v,v as y}from"./_plugin-vue_export-helper-BumXM028.js";import{G as b,N as x,w as S}from"./index-
|
|
2
|
-
//# sourceMappingURL=DevicePairingDialog-
|
|
1
|
+
import{i as e}from"./rolldown-runtime-Dd_uD5pT.js";import{A as t,Dt as n,F as r,G as i,H as a,L as o,M as s,W as c,_ as l,a as u,b as d,d as f,et as p,f as m,m as h,p as g,t as _,u as v,v as y}from"./_plugin-vue_export-helper-BumXM028.js";import{G as b,N as x,w as S}from"./index-D11cgXI9.js";import{t as C}from"./StandaloneDialog-BXCK22Dx.js";var w=e(x(),1);async function T(e){return S(`/api/app/account-pairings`,{method:`POST`,body:e?{serverUrl:e}:{}})}async function E(e){return S(`/api/app/account-pairings/${encodeURIComponent(e)}`)}var ee=[`aria-busy`],te={class:`mobile-login`,"aria-label":`手机扫码登录`},ne=[`disabled`],re={id:`mobile-login-addresses`},D=[`value`],O={key:0,class:`address-hint`},k=[`disabled`],A={key:1,class:`qr-code`},j=[`src`],M={key:2,role:`status`},N={class:`manual-pairing`,"aria-label":`手动配对设备`},P=[`disabled`],F={key:0,class:`pair-code`,role:`status`},I={key:0,role:`alert`},L=[`disabled`,`onClick`],R={key:1,class:`confirmation`,role:`alertdialog`,"aria-label":`撤销设备`},z=[`disabled`],B=[`disabled`],V=_(d({__name:`DevicePairingPanel`,setup(e){let c=p([]),d=p(``),m=p(0),_=p(!1),y=p(``),x=p(),C=p([]),V=p(``),H=p(),U=p(``),W=p(``),G=!0,K=0,q,J=v(()=>{try{return/^(localhost|127(?:\.\d+){3}|\[?::1\]?)$/i.test(new URL(V.value).hostname)}catch{return!1}});function Y(){q&&clearTimeout(q),q=void 0}function X(){K++,Y(),H.value=void 0,U.value=``,W.value=``}a(V,X);async function Z(){let e=await S(`/api/app/devices`);G&&(c.value=e.devices)}async function Q(e){if(!_.value){_.value=!0,y.value=``;try{await e()}catch(e){G&&(y.value=e instanceof Error?e.message:`操作失败`)}finally{G&&(_.value=!1)}}}async function ie(){await Q(async()=>{let e=await S(`/api/app/devices/code`,{method:`POST`,body:{}});G&&(d.value=e.code,m.value=e.expiresAt)})}async function $(e){if(!(!G||e!==K||!H.value)){if(Date.now()>=H.value.expiresAt){X(),W.value=`二维码已过期,请重新生成。`;return}try{let t=await E(H.value.pairingId);if(!G||e!==K)return;if(t.state===`claimed`){X(),W.value=`手机已登录,可在下方管理或撤销。`,await Z();return}if(t.state===`expired`){X(),W.value=`二维码已过期,请重新生成。`;return}}catch{}G&&e===K&&(q=setTimeout(()=>void $(e),1e3))}}async function ae(){J.value||!V.value.trim()||await Q(async()=>{X();let e=K,t=await T(V.value.trim()),n=await w.toDataURL(t.qrPayload,{width:256,margin:2,errorCorrectionLevel:`M`});!G||e!==K||(H.value=t,U.value=n,W.value=`等待手机扫描,仅可使用一次。`,q=setTimeout(()=>void $(e),1e3))})}async function oe(){await Q(async()=>{await S(`/api/app/devices/`+x.value,{method:`DELETE`}),G&&(x.value=void 0,await Z())})}return s(()=>Q(async()=>{let[,e]=await Promise.all([Z(),S(`/api/app/devices/login-addresses`)]);G&&(C.value=e.addresses,V.value=e.addresses[0]??``)})),t(()=>{G=!1,X()}),(e,t)=>(r(),h(`section`,{class:`devices`,"aria-busy":_.value},[f(`section`,te,[t[3]||=f(`h4`,null,`手机扫码登录`,-1),t[4]||=f(`p`,null,`在手机 Yaovi 登录页点“扫码登录”,扫描此二维码即可连接服务器。`,-1),f(`label`,null,[t[2]||=l(`手机可访问的服务器地址`,-1),i(f(`input`,{"onUpdate:modelValue":t[0]||=e=>V.value=e,list:`mobile-login-addresses`,type:`url`,placeholder:`http://192.168.1.10:8980`,disabled:_.value,autocomplete:`off`},null,8,ne),[[b,V.value]])]),f(`datalist`,re,[(r(!0),h(u,null,o(C.value,e=>(r(),h(`option`,{key:e,value:e},null,8,D))),128))]),J.value?(r(),h(`p`,O,`手机无法访问这台电脑的本机地址。请先让服务器可通过局域网或 VPN 访问,再填写对应地址。`)):g(``,!0),f(`button`,{disabled:_.value||!V.value.trim()||J.value,onClick:ae},n(H.value?`重新生成二维码`:`生成登录二维码`),9,k),U.value?(r(),h(`div`,A,[f(`img`,{src:U.value,width:`256`,height:`256`,alt:`手机登录一次性二维码`},null,8,j),f(`small`,null,`有效至 `+n(new Date(H.value.expiresAt).toLocaleTimeString())+`,请勿分享给他人。`,1)])):g(``,!0),W.value?(r(),h(`p`,M,n(W.value),1)):g(``,!0)]),f(`section`,N,[t[5]||=f(`h4`,null,`手动配对`,-1),t[6]||=f(`p`,null,`也可在同一局域网或 VPN 内打开主机地址,填写设备名称和一次性配对码。`,-1),f(`button`,{disabled:_.value,onClick:ie},`生成配对码`,8,P),d.value?(r(),h(`div`,F,[f(`strong`,null,n(d.value.slice(0,6))+` `+n(d.value.slice(6)),1),f(`p`,null,`有效至 `+n(new Date(m.value).toLocaleTimeString())+`,使用一次后失效。`,1)])):g(``,!0)]),y.value?(r(),h(`p`,I,n(y.value),1)):g(``,!0),f(`ul`,null,[(r(!0),h(u,null,o(c.value,e=>(r(),h(`li`,{key:e.id},[f(`div`,null,[f(`strong`,null,n(e.name),1),f(`small`,null,n(e.current?`当前设备 · `:``)+n(new Date(e.pairedAt).toLocaleString()),1)]),e.current?g(``,!0):(r(),h(`button`,{key:0,disabled:_.value,onClick:t=>x.value=e.id},`撤销`,8,L))]))),128))]),x.value?(r(),h(`section`,R,[t[7]||=f(`p`,null,`撤销后,此设备需要重新配对才能访问服务器。`,-1),f(`div`,null,[f(`button`,{disabled:_.value,onClick:t[1]||=e=>x.value=void 0},`取消`,8,z),f(`button`,{disabled:_.value,onClick:oe},`确认撤销`,8,B)])])):g(``,!0)],8,ee))}}),[[`__scopeId`,`data-v-ec854f5b`]]),H=d({__name:`DevicePairingDialog`,emits:[`close`],setup(e,{emit:t}){let n=t;return(e,t)=>(r(),m(C,{title:`设备配对`,compact:``,wide:``,onClose:t[0]||=e=>n(`close`)},{default:c(()=>[y(V)]),_:1}))}});export{H as default};
|
|
2
|
+
//# sourceMappingURL=DevicePairingDialog-C307ARt-.js.map
|
package/dist/assets/{DevicePairingDialog-ByYk05DC.js.map → DevicePairingDialog-C307ARt-.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevicePairingDialog-ByYk05DC.js","names":[],"sources":["../../src/client/api/accountPairing.ts","../../src/client/components/app/DevicePairingPanel.vue","../../src/client/components/app/DevicePairingPanel.vue","../../src/client/components/app/DevicePairingDialog.vue","../../src/client/components/app/DevicePairingDialog.vue"],"sourcesContent":["import { apiRequest } from './client'\n\nexport interface AccountPairingSession {\n protocolVersion: number\n serviceType: string\n pairingId: string\n expiresAt: number\n qrPayload: string\n}\n\nexport async function createAccountPairing(serverUrl?:string): Promise<AccountPairingSession> {\n return apiRequest('/api/app/account-pairings', { method: 'POST', body: serverUrl?{serverUrl}:{} })\n}\n\nexport async function accountPairingStatus(\n pairingId: string,\n): Promise<{ state: 'pending' | 'claimed' | 'expired' }> {\n return apiRequest(`/api/app/account-pairings/${encodeURIComponent(pairingId)}`)\n}\n","<script setup lang=\"ts\">\nimport {computed,onBeforeUnmount,onMounted,ref,watch} from 'vue'\nimport QRCode from 'qrcode'\nimport {apiRequest} from '@/api/client'\nimport {accountPairingStatus,createAccountPairing,type AccountPairingSession} from '@/api/accountPairing'\nconst devices=ref<Array<{id:string;name:string;pairedAt:number;current:boolean;local:boolean}>>([])\nconst code=ref(''),expiresAt=ref(0),busy=ref(false),error=ref(''),pending=ref<string>()\nconst addresses=ref<string[]>([]),serverUrl=ref(''),pairing=ref<AccountPairingSession>(),qrImage=ref(''),qrStatus=ref('')\nlet alive=true,generation=0,timer:ReturnType<typeof setTimeout>|undefined\nconst loopback=computed(()=>{try{return /^(localhost|127(?:\\.\\d+){3}|\\[?::1\\]?)$/i.test(new URL(serverUrl.value).hostname)}catch{return false}})\nfunction stopPolling(){if(timer)clearTimeout(timer);timer=undefined}\nfunction clearQr(){generation++;stopPolling();pairing.value=undefined;qrImage.value='';qrStatus.value=''}\nwatch(serverUrl,clearQr)\nasync function load(){const result=await apiRequest<{devices:typeof devices.value}>('/api/app/devices');if(alive)devices.value=result.devices}\nasync function action(fn:()=>Promise<void>){if(busy.value)return;busy.value=true;error.value='';try{await fn()}catch(e){if(alive)error.value=e instanceof Error?e.message:'操作失败'}finally{if(alive)busy.value=false}}\nasync function generate(){await action(async()=>{const result=await apiRequest<{code:string;expiresAt:number}>('/api/app/devices/code',{method:'POST',body:{}});if(alive){code.value=result.code;expiresAt.value=result.expiresAt}})}\nasync function poll(token:number){\n if(!alive||token!==generation||!pairing.value)return\n if(Date.now()>=pairing.value.expiresAt){clearQr();qrStatus.value='二维码已过期,请重新生成。';return}\n try{\n const result=await accountPairingStatus(pairing.value.pairingId)\n if(!alive||token!==generation)return\n if(result.state==='claimed'){clearQr();qrStatus.value='手机已登录,可在下方管理或撤销。';await load();return}\n if(result.state==='expired'){clearQr();qrStatus.value='二维码已过期,请重新生成。';return}\n }catch{/* A transient status read must not invalidate an unused login code. */}\n if(alive&&token===generation)timer=setTimeout(()=>void poll(token),1000)\n}\nasync function generateQr(){\n if(loopback.value||!serverUrl.value.trim())return\n await action(async()=>{\n clearQr();const token=generation\n const created=await createAccountPairing(serverUrl.value.trim())\n const image=await QRCode.toDataURL(created.qrPayload,{width:256,margin:2,errorCorrectionLevel:'M'})\n if(!alive||token!==generation)return\n pairing.value=created;qrImage.value=image;qrStatus.value='等待手机扫描,仅可使用一次。'\n timer=setTimeout(()=>void poll(token),1000)\n })\n}\nasync function revoke(){await action(async()=>{await apiRequest('/api/app/devices/'+pending.value,{method:'DELETE'});if(alive){pending.value=undefined;await load()}})}\nonMounted(()=>action(async()=>{\n const [,result]=await Promise.all([load(),apiRequest<{addresses:string[];localOnly:boolean}>('/api/app/devices/login-addresses')])\n if(alive){addresses.value=result.addresses;serverUrl.value=result.addresses[0]??''}\n}))\nonBeforeUnmount(()=>{alive=false;clearQr()})\n</script>\n<template><section class=\"devices\" :aria-busy=\"busy\">\n <section class=\"mobile-login\" aria-label=\"手机扫码登录\">\n <h4>手机扫码登录</h4>\n <p>在手机 Yaovi 登录页点“扫码登录”,扫描此二维码即可连接服务器。</p>\n <label>手机可访问的服务器地址<input v-model=\"serverUrl\" list=\"mobile-login-addresses\" type=\"url\" placeholder=\"http://192.168.1.10:8980\" :disabled=\"busy\" autocomplete=\"off\" /></label>\n <datalist id=\"mobile-login-addresses\"><option v-for=\"address in addresses\" :key=\"address\" :value=\"address\" /></datalist>\n <p v-if=\"loopback\" class=\"address-hint\">手机无法访问这台电脑的本机地址。请先让服务器可通过局域网或 VPN 访问,再填写对应地址。</p>\n <button :disabled=\"busy||!serverUrl.trim()||loopback\" @click=\"generateQr\">{{pairing?'重新生成二维码':'生成登录二维码'}}</button>\n <div v-if=\"qrImage\" class=\"qr-code\"><img :src=\"qrImage\" width=\"256\" height=\"256\" alt=\"手机登录一次性二维码\" /><small>有效至 {{new Date(pairing!.expiresAt).toLocaleTimeString()}},请勿分享给他人。</small></div>\n <p v-if=\"qrStatus\" role=\"status\">{{qrStatus}}</p>\n </section>\n <section class=\"manual-pairing\" aria-label=\"手动配对设备\">\n <h4>手动配对</h4>\n <p>也可在同一局域网或 VPN 内打开主机地址,填写设备名称和一次性配对码。</p>\n <button :disabled=\"busy\" @click=\"generate\">生成配对码</button>\n <div v-if=\"code\" class=\"pair-code\" role=\"status\"><strong>{{code.slice(0,6)}} {{code.slice(6)}}</strong><p>有效至 {{new Date(expiresAt).toLocaleTimeString()}},使用一次后失效。</p></div>\n </section>\n <p v-if=\"error\" role=\"alert\">{{error}}</p>\n <ul><li v-for=\"device in devices\" :key=\"device.id\"><div><strong>{{device.name}}</strong><small>{{device.current?'当前设备 · ':''}}{{new Date(device.pairedAt).toLocaleString()}}</small></div><button v-if=\"!device.current\" :disabled=\"busy\" @click=\"pending=device.id\">撤销</button></li></ul>\n <section v-if=\"pending\" class=\"confirmation\" role=\"alertdialog\" aria-label=\"撤销设备\"><p>撤销后,此设备需要重新配对才能访问服务器。</p><div><button :disabled=\"busy\" @click=\"pending=undefined\">取消</button><button :disabled=\"busy\" @click=\"revoke\">确认撤销</button></div></section>\n</section></template>\n<style scoped>\n.devices,.mobile-login,.manual-pairing{display:grid;gap:18px;font-size:14px;min-width:0}.devices h4{margin:0;font-size:16px}.devices p{margin:0;line-height:1.65;color:var(--text-secondary)}.devices label{display:grid;gap:8px}.devices input,.devices button{box-sizing:border-box;min-height:44px;padding:10px 14px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);font:inherit}.devices input{width:100%;min-width:0}.devices button{justify-self:start;cursor:pointer}.devices button:disabled{opacity:.5;cursor:default}.devices ul{list-style:none;padding:0;margin:0}.devices li{display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--line);padding:16px 0}.devices li>div{display:grid;gap:6px;flex:1;min-width:0;overflow-wrap:anywhere}.devices small{color:var(--text-secondary)}.pair-code,.confirmation{padding:18px;border:1px solid var(--line);border-radius:12px}.pair-code strong{display:block;font-size:26px;letter-spacing:.1em;margin-bottom:12px;user-select:all}.qr-code{display:grid;gap:12px;justify-items:start}.qr-code img{width:256px;max-width:100%;height:auto;background:white;border-radius:12px}.manual-pairing{border-top:1px solid var(--line);padding-top:22px}.confirmation div{display:flex;gap:12px;margin-top:16px}.devices [role=alert]{color:var(--danger)}.devices :focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n</style>\n","<script setup lang=\"ts\">\nimport {computed,onBeforeUnmount,onMounted,ref,watch} from 'vue'\nimport QRCode from 'qrcode'\nimport {apiRequest} from '@/api/client'\nimport {accountPairingStatus,createAccountPairing,type AccountPairingSession} from '@/api/accountPairing'\nconst devices=ref<Array<{id:string;name:string;pairedAt:number;current:boolean;local:boolean}>>([])\nconst code=ref(''),expiresAt=ref(0),busy=ref(false),error=ref(''),pending=ref<string>()\nconst addresses=ref<string[]>([]),serverUrl=ref(''),pairing=ref<AccountPairingSession>(),qrImage=ref(''),qrStatus=ref('')\nlet alive=true,generation=0,timer:ReturnType<typeof setTimeout>|undefined\nconst loopback=computed(()=>{try{return /^(localhost|127(?:\\.\\d+){3}|\\[?::1\\]?)$/i.test(new URL(serverUrl.value).hostname)}catch{return false}})\nfunction stopPolling(){if(timer)clearTimeout(timer);timer=undefined}\nfunction clearQr(){generation++;stopPolling();pairing.value=undefined;qrImage.value='';qrStatus.value=''}\nwatch(serverUrl,clearQr)\nasync function load(){const result=await apiRequest<{devices:typeof devices.value}>('/api/app/devices');if(alive)devices.value=result.devices}\nasync function action(fn:()=>Promise<void>){if(busy.value)return;busy.value=true;error.value='';try{await fn()}catch(e){if(alive)error.value=e instanceof Error?e.message:'操作失败'}finally{if(alive)busy.value=false}}\nasync function generate(){await action(async()=>{const result=await apiRequest<{code:string;expiresAt:number}>('/api/app/devices/code',{method:'POST',body:{}});if(alive){code.value=result.code;expiresAt.value=result.expiresAt}})}\nasync function poll(token:number){\n if(!alive||token!==generation||!pairing.value)return\n if(Date.now()>=pairing.value.expiresAt){clearQr();qrStatus.value='二维码已过期,请重新生成。';return}\n try{\n const result=await accountPairingStatus(pairing.value.pairingId)\n if(!alive||token!==generation)return\n if(result.state==='claimed'){clearQr();qrStatus.value='手机已登录,可在下方管理或撤销。';await load();return}\n if(result.state==='expired'){clearQr();qrStatus.value='二维码已过期,请重新生成。';return}\n }catch{/* A transient status read must not invalidate an unused login code. */}\n if(alive&&token===generation)timer=setTimeout(()=>void poll(token),1000)\n}\nasync function generateQr(){\n if(loopback.value||!serverUrl.value.trim())return\n await action(async()=>{\n clearQr();const token=generation\n const created=await createAccountPairing(serverUrl.value.trim())\n const image=await QRCode.toDataURL(created.qrPayload,{width:256,margin:2,errorCorrectionLevel:'M'})\n if(!alive||token!==generation)return\n pairing.value=created;qrImage.value=image;qrStatus.value='等待手机扫描,仅可使用一次。'\n timer=setTimeout(()=>void poll(token),1000)\n })\n}\nasync function revoke(){await action(async()=>{await apiRequest('/api/app/devices/'+pending.value,{method:'DELETE'});if(alive){pending.value=undefined;await load()}})}\nonMounted(()=>action(async()=>{\n const [,result]=await Promise.all([load(),apiRequest<{addresses:string[];localOnly:boolean}>('/api/app/devices/login-addresses')])\n if(alive){addresses.value=result.addresses;serverUrl.value=result.addresses[0]??''}\n}))\nonBeforeUnmount(()=>{alive=false;clearQr()})\n</script>\n<template><section class=\"devices\" :aria-busy=\"busy\">\n <section class=\"mobile-login\" aria-label=\"手机扫码登录\">\n <h4>手机扫码登录</h4>\n <p>在手机 Yaovi 登录页点“扫码登录”,扫描此二维码即可连接服务器。</p>\n <label>手机可访问的服务器地址<input v-model=\"serverUrl\" list=\"mobile-login-addresses\" type=\"url\" placeholder=\"http://192.168.1.10:8980\" :disabled=\"busy\" autocomplete=\"off\" /></label>\n <datalist id=\"mobile-login-addresses\"><option v-for=\"address in addresses\" :key=\"address\" :value=\"address\" /></datalist>\n <p v-if=\"loopback\" class=\"address-hint\">手机无法访问这台电脑的本机地址。请先让服务器可通过局域网或 VPN 访问,再填写对应地址。</p>\n <button :disabled=\"busy||!serverUrl.trim()||loopback\" @click=\"generateQr\">{{pairing?'重新生成二维码':'生成登录二维码'}}</button>\n <div v-if=\"qrImage\" class=\"qr-code\"><img :src=\"qrImage\" width=\"256\" height=\"256\" alt=\"手机登录一次性二维码\" /><small>有效至 {{new Date(pairing!.expiresAt).toLocaleTimeString()}},请勿分享给他人。</small></div>\n <p v-if=\"qrStatus\" role=\"status\">{{qrStatus}}</p>\n </section>\n <section class=\"manual-pairing\" aria-label=\"手动配对设备\">\n <h4>手动配对</h4>\n <p>也可在同一局域网或 VPN 内打开主机地址,填写设备名称和一次性配对码。</p>\n <button :disabled=\"busy\" @click=\"generate\">生成配对码</button>\n <div v-if=\"code\" class=\"pair-code\" role=\"status\"><strong>{{code.slice(0,6)}} {{code.slice(6)}}</strong><p>有效至 {{new Date(expiresAt).toLocaleTimeString()}},使用一次后失效。</p></div>\n </section>\n <p v-if=\"error\" role=\"alert\">{{error}}</p>\n <ul><li v-for=\"device in devices\" :key=\"device.id\"><div><strong>{{device.name}}</strong><small>{{device.current?'当前设备 · ':''}}{{new Date(device.pairedAt).toLocaleString()}}</small></div><button v-if=\"!device.current\" :disabled=\"busy\" @click=\"pending=device.id\">撤销</button></li></ul>\n <section v-if=\"pending\" class=\"confirmation\" role=\"alertdialog\" aria-label=\"撤销设备\"><p>撤销后,此设备需要重新配对才能访问服务器。</p><div><button :disabled=\"busy\" @click=\"pending=undefined\">取消</button><button :disabled=\"busy\" @click=\"revoke\">确认撤销</button></div></section>\n</section></template>\n<style scoped>\n.devices,.mobile-login,.manual-pairing{display:grid;gap:18px;font-size:14px;min-width:0}.devices h4{margin:0;font-size:16px}.devices p{margin:0;line-height:1.65;color:var(--text-secondary)}.devices label{display:grid;gap:8px}.devices input,.devices button{box-sizing:border-box;min-height:44px;padding:10px 14px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);font:inherit}.devices input{width:100%;min-width:0}.devices button{justify-self:start;cursor:pointer}.devices button:disabled{opacity:.5;cursor:default}.devices ul{list-style:none;padding:0;margin:0}.devices li{display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--line);padding:16px 0}.devices li>div{display:grid;gap:6px;flex:1;min-width:0;overflow-wrap:anywhere}.devices small{color:var(--text-secondary)}.pair-code,.confirmation{padding:18px;border:1px solid var(--line);border-radius:12px}.pair-code strong{display:block;font-size:26px;letter-spacing:.1em;margin-bottom:12px;user-select:all}.qr-code{display:grid;gap:12px;justify-items:start}.qr-code img{width:256px;max-width:100%;height:auto;background:white;border-radius:12px}.manual-pairing{border-top:1px solid var(--line);padding-top:22px}.confirmation div{display:flex;gap:12px;margin-top:16px}.devices [role=alert]{color:var(--danger)}.devices :focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n</style>\n","<script setup lang=\"ts\">\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nimport DevicePairingPanel from './DevicePairingPanel.vue'\nconst emit = defineEmits<{ close: [] }>()\n</script>\n<template>\n <StandaloneDialog title=\"设备配对\" compact wide @close=\"emit('close')\">\n <DevicePairingPanel />\n </StandaloneDialog>\n</template>\n","<script setup lang=\"ts\">\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nimport DevicePairingPanel from './DevicePairingPanel.vue'\nconst emit = defineEmits<{ close: [] }>()\n</script>\n<template>\n <StandaloneDialog title=\"设备配对\" compact wide @close=\"emit('close')\">\n <DevicePairingPanel />\n </StandaloneDialog>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DevicePairingDialog-C307ARt-.js","names":[],"sources":["../../src/client/api/accountPairing.ts","../../src/client/components/app/DevicePairingPanel.vue","../../src/client/components/app/DevicePairingPanel.vue","../../src/client/components/app/DevicePairingDialog.vue","../../src/client/components/app/DevicePairingDialog.vue"],"sourcesContent":["import { apiRequest } from './client'\n\nexport interface AccountPairingSession {\n protocolVersion: number\n serviceType: string\n pairingId: string\n expiresAt: number\n qrPayload: string\n}\n\nexport async function createAccountPairing(serverUrl?:string): Promise<AccountPairingSession> {\n return apiRequest('/api/app/account-pairings', { method: 'POST', body: serverUrl?{serverUrl}:{} })\n}\n\nexport async function accountPairingStatus(\n pairingId: string,\n): Promise<{ state: 'pending' | 'claimed' | 'expired' }> {\n return apiRequest(`/api/app/account-pairings/${encodeURIComponent(pairingId)}`)\n}\n","<script setup lang=\"ts\">\nimport {computed,onBeforeUnmount,onMounted,ref,watch} from 'vue'\nimport QRCode from 'qrcode'\nimport {apiRequest} from '@/api/client'\nimport {accountPairingStatus,createAccountPairing,type AccountPairingSession} from '@/api/accountPairing'\nconst devices=ref<Array<{id:string;name:string;pairedAt:number;current:boolean;local:boolean}>>([])\nconst code=ref(''),expiresAt=ref(0),busy=ref(false),error=ref(''),pending=ref<string>()\nconst addresses=ref<string[]>([]),serverUrl=ref(''),pairing=ref<AccountPairingSession>(),qrImage=ref(''),qrStatus=ref('')\nlet alive=true,generation=0,timer:ReturnType<typeof setTimeout>|undefined\nconst loopback=computed(()=>{try{return /^(localhost|127(?:\\.\\d+){3}|\\[?::1\\]?)$/i.test(new URL(serverUrl.value).hostname)}catch{return false}})\nfunction stopPolling(){if(timer)clearTimeout(timer);timer=undefined}\nfunction clearQr(){generation++;stopPolling();pairing.value=undefined;qrImage.value='';qrStatus.value=''}\nwatch(serverUrl,clearQr)\nasync function load(){const result=await apiRequest<{devices:typeof devices.value}>('/api/app/devices');if(alive)devices.value=result.devices}\nasync function action(fn:()=>Promise<void>){if(busy.value)return;busy.value=true;error.value='';try{await fn()}catch(e){if(alive)error.value=e instanceof Error?e.message:'操作失败'}finally{if(alive)busy.value=false}}\nasync function generate(){await action(async()=>{const result=await apiRequest<{code:string;expiresAt:number}>('/api/app/devices/code',{method:'POST',body:{}});if(alive){code.value=result.code;expiresAt.value=result.expiresAt}})}\nasync function poll(token:number){\n if(!alive||token!==generation||!pairing.value)return\n if(Date.now()>=pairing.value.expiresAt){clearQr();qrStatus.value='二维码已过期,请重新生成。';return}\n try{\n const result=await accountPairingStatus(pairing.value.pairingId)\n if(!alive||token!==generation)return\n if(result.state==='claimed'){clearQr();qrStatus.value='手机已登录,可在下方管理或撤销。';await load();return}\n if(result.state==='expired'){clearQr();qrStatus.value='二维码已过期,请重新生成。';return}\n }catch{/* A transient status read must not invalidate an unused login code. */}\n if(alive&&token===generation)timer=setTimeout(()=>void poll(token),1000)\n}\nasync function generateQr(){\n if(loopback.value||!serverUrl.value.trim())return\n await action(async()=>{\n clearQr();const token=generation\n const created=await createAccountPairing(serverUrl.value.trim())\n const image=await QRCode.toDataURL(created.qrPayload,{width:256,margin:2,errorCorrectionLevel:'M'})\n if(!alive||token!==generation)return\n pairing.value=created;qrImage.value=image;qrStatus.value='等待手机扫描,仅可使用一次。'\n timer=setTimeout(()=>void poll(token),1000)\n })\n}\nasync function revoke(){await action(async()=>{await apiRequest('/api/app/devices/'+pending.value,{method:'DELETE'});if(alive){pending.value=undefined;await load()}})}\nonMounted(()=>action(async()=>{\n const [,result]=await Promise.all([load(),apiRequest<{addresses:string[];localOnly:boolean}>('/api/app/devices/login-addresses')])\n if(alive){addresses.value=result.addresses;serverUrl.value=result.addresses[0]??''}\n}))\nonBeforeUnmount(()=>{alive=false;clearQr()})\n</script>\n<template><section class=\"devices\" :aria-busy=\"busy\">\n <section class=\"mobile-login\" aria-label=\"手机扫码登录\">\n <h4>手机扫码登录</h4>\n <p>在手机 Yaovi 登录页点“扫码登录”,扫描此二维码即可连接服务器。</p>\n <label>手机可访问的服务器地址<input v-model=\"serverUrl\" list=\"mobile-login-addresses\" type=\"url\" placeholder=\"http://192.168.1.10:8980\" :disabled=\"busy\" autocomplete=\"off\" /></label>\n <datalist id=\"mobile-login-addresses\"><option v-for=\"address in addresses\" :key=\"address\" :value=\"address\" /></datalist>\n <p v-if=\"loopback\" class=\"address-hint\">手机无法访问这台电脑的本机地址。请先让服务器可通过局域网或 VPN 访问,再填写对应地址。</p>\n <button :disabled=\"busy||!serverUrl.trim()||loopback\" @click=\"generateQr\">{{pairing?'重新生成二维码':'生成登录二维码'}}</button>\n <div v-if=\"qrImage\" class=\"qr-code\"><img :src=\"qrImage\" width=\"256\" height=\"256\" alt=\"手机登录一次性二维码\" /><small>有效至 {{new Date(pairing!.expiresAt).toLocaleTimeString()}},请勿分享给他人。</small></div>\n <p v-if=\"qrStatus\" role=\"status\">{{qrStatus}}</p>\n </section>\n <section class=\"manual-pairing\" aria-label=\"手动配对设备\">\n <h4>手动配对</h4>\n <p>也可在同一局域网或 VPN 内打开主机地址,填写设备名称和一次性配对码。</p>\n <button :disabled=\"busy\" @click=\"generate\">生成配对码</button>\n <div v-if=\"code\" class=\"pair-code\" role=\"status\"><strong>{{code.slice(0,6)}} {{code.slice(6)}}</strong><p>有效至 {{new Date(expiresAt).toLocaleTimeString()}},使用一次后失效。</p></div>\n </section>\n <p v-if=\"error\" role=\"alert\">{{error}}</p>\n <ul><li v-for=\"device in devices\" :key=\"device.id\"><div><strong>{{device.name}}</strong><small>{{device.current?'当前设备 · ':''}}{{new Date(device.pairedAt).toLocaleString()}}</small></div><button v-if=\"!device.current\" :disabled=\"busy\" @click=\"pending=device.id\">撤销</button></li></ul>\n <section v-if=\"pending\" class=\"confirmation\" role=\"alertdialog\" aria-label=\"撤销设备\"><p>撤销后,此设备需要重新配对才能访问服务器。</p><div><button :disabled=\"busy\" @click=\"pending=undefined\">取消</button><button :disabled=\"busy\" @click=\"revoke\">确认撤销</button></div></section>\n</section></template>\n<style scoped>\n.devices,.mobile-login,.manual-pairing{display:grid;gap:18px;font-size:14px;min-width:0}.devices h4{margin:0;font-size:16px}.devices p{margin:0;line-height:1.65;color:var(--text-secondary)}.devices label{display:grid;gap:8px}.devices input,.devices button{box-sizing:border-box;min-height:44px;padding:10px 14px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);font:inherit}.devices input{width:100%;min-width:0}.devices button{justify-self:start;cursor:pointer}.devices button:disabled{opacity:.5;cursor:default}.devices ul{list-style:none;padding:0;margin:0}.devices li{display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--line);padding:16px 0}.devices li>div{display:grid;gap:6px;flex:1;min-width:0;overflow-wrap:anywhere}.devices small{color:var(--text-secondary)}.pair-code,.confirmation{padding:18px;border:1px solid var(--line);border-radius:12px}.pair-code strong{display:block;font-size:26px;letter-spacing:.1em;margin-bottom:12px;user-select:all}.qr-code{display:grid;gap:12px;justify-items:start}.qr-code img{width:256px;max-width:100%;height:auto;background:white;border-radius:12px}.manual-pairing{border-top:1px solid var(--line);padding-top:22px}.confirmation div{display:flex;gap:12px;margin-top:16px}.devices [role=alert]{color:var(--danger)}.devices :focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n</style>\n","<script setup lang=\"ts\">\nimport {computed,onBeforeUnmount,onMounted,ref,watch} from 'vue'\nimport QRCode from 'qrcode'\nimport {apiRequest} from '@/api/client'\nimport {accountPairingStatus,createAccountPairing,type AccountPairingSession} from '@/api/accountPairing'\nconst devices=ref<Array<{id:string;name:string;pairedAt:number;current:boolean;local:boolean}>>([])\nconst code=ref(''),expiresAt=ref(0),busy=ref(false),error=ref(''),pending=ref<string>()\nconst addresses=ref<string[]>([]),serverUrl=ref(''),pairing=ref<AccountPairingSession>(),qrImage=ref(''),qrStatus=ref('')\nlet alive=true,generation=0,timer:ReturnType<typeof setTimeout>|undefined\nconst loopback=computed(()=>{try{return /^(localhost|127(?:\\.\\d+){3}|\\[?::1\\]?)$/i.test(new URL(serverUrl.value).hostname)}catch{return false}})\nfunction stopPolling(){if(timer)clearTimeout(timer);timer=undefined}\nfunction clearQr(){generation++;stopPolling();pairing.value=undefined;qrImage.value='';qrStatus.value=''}\nwatch(serverUrl,clearQr)\nasync function load(){const result=await apiRequest<{devices:typeof devices.value}>('/api/app/devices');if(alive)devices.value=result.devices}\nasync function action(fn:()=>Promise<void>){if(busy.value)return;busy.value=true;error.value='';try{await fn()}catch(e){if(alive)error.value=e instanceof Error?e.message:'操作失败'}finally{if(alive)busy.value=false}}\nasync function generate(){await action(async()=>{const result=await apiRequest<{code:string;expiresAt:number}>('/api/app/devices/code',{method:'POST',body:{}});if(alive){code.value=result.code;expiresAt.value=result.expiresAt}})}\nasync function poll(token:number){\n if(!alive||token!==generation||!pairing.value)return\n if(Date.now()>=pairing.value.expiresAt){clearQr();qrStatus.value='二维码已过期,请重新生成。';return}\n try{\n const result=await accountPairingStatus(pairing.value.pairingId)\n if(!alive||token!==generation)return\n if(result.state==='claimed'){clearQr();qrStatus.value='手机已登录,可在下方管理或撤销。';await load();return}\n if(result.state==='expired'){clearQr();qrStatus.value='二维码已过期,请重新生成。';return}\n }catch{/* A transient status read must not invalidate an unused login code. */}\n if(alive&&token===generation)timer=setTimeout(()=>void poll(token),1000)\n}\nasync function generateQr(){\n if(loopback.value||!serverUrl.value.trim())return\n await action(async()=>{\n clearQr();const token=generation\n const created=await createAccountPairing(serverUrl.value.trim())\n const image=await QRCode.toDataURL(created.qrPayload,{width:256,margin:2,errorCorrectionLevel:'M'})\n if(!alive||token!==generation)return\n pairing.value=created;qrImage.value=image;qrStatus.value='等待手机扫描,仅可使用一次。'\n timer=setTimeout(()=>void poll(token),1000)\n })\n}\nasync function revoke(){await action(async()=>{await apiRequest('/api/app/devices/'+pending.value,{method:'DELETE'});if(alive){pending.value=undefined;await load()}})}\nonMounted(()=>action(async()=>{\n const [,result]=await Promise.all([load(),apiRequest<{addresses:string[];localOnly:boolean}>('/api/app/devices/login-addresses')])\n if(alive){addresses.value=result.addresses;serverUrl.value=result.addresses[0]??''}\n}))\nonBeforeUnmount(()=>{alive=false;clearQr()})\n</script>\n<template><section class=\"devices\" :aria-busy=\"busy\">\n <section class=\"mobile-login\" aria-label=\"手机扫码登录\">\n <h4>手机扫码登录</h4>\n <p>在手机 Yaovi 登录页点“扫码登录”,扫描此二维码即可连接服务器。</p>\n <label>手机可访问的服务器地址<input v-model=\"serverUrl\" list=\"mobile-login-addresses\" type=\"url\" placeholder=\"http://192.168.1.10:8980\" :disabled=\"busy\" autocomplete=\"off\" /></label>\n <datalist id=\"mobile-login-addresses\"><option v-for=\"address in addresses\" :key=\"address\" :value=\"address\" /></datalist>\n <p v-if=\"loopback\" class=\"address-hint\">手机无法访问这台电脑的本机地址。请先让服务器可通过局域网或 VPN 访问,再填写对应地址。</p>\n <button :disabled=\"busy||!serverUrl.trim()||loopback\" @click=\"generateQr\">{{pairing?'重新生成二维码':'生成登录二维码'}}</button>\n <div v-if=\"qrImage\" class=\"qr-code\"><img :src=\"qrImage\" width=\"256\" height=\"256\" alt=\"手机登录一次性二维码\" /><small>有效至 {{new Date(pairing!.expiresAt).toLocaleTimeString()}},请勿分享给他人。</small></div>\n <p v-if=\"qrStatus\" role=\"status\">{{qrStatus}}</p>\n </section>\n <section class=\"manual-pairing\" aria-label=\"手动配对设备\">\n <h4>手动配对</h4>\n <p>也可在同一局域网或 VPN 内打开主机地址,填写设备名称和一次性配对码。</p>\n <button :disabled=\"busy\" @click=\"generate\">生成配对码</button>\n <div v-if=\"code\" class=\"pair-code\" role=\"status\"><strong>{{code.slice(0,6)}} {{code.slice(6)}}</strong><p>有效至 {{new Date(expiresAt).toLocaleTimeString()}},使用一次后失效。</p></div>\n </section>\n <p v-if=\"error\" role=\"alert\">{{error}}</p>\n <ul><li v-for=\"device in devices\" :key=\"device.id\"><div><strong>{{device.name}}</strong><small>{{device.current?'当前设备 · ':''}}{{new Date(device.pairedAt).toLocaleString()}}</small></div><button v-if=\"!device.current\" :disabled=\"busy\" @click=\"pending=device.id\">撤销</button></li></ul>\n <section v-if=\"pending\" class=\"confirmation\" role=\"alertdialog\" aria-label=\"撤销设备\"><p>撤销后,此设备需要重新配对才能访问服务器。</p><div><button :disabled=\"busy\" @click=\"pending=undefined\">取消</button><button :disabled=\"busy\" @click=\"revoke\">确认撤销</button></div></section>\n</section></template>\n<style scoped>\n.devices,.mobile-login,.manual-pairing{display:grid;gap:18px;font-size:14px;min-width:0}.devices h4{margin:0;font-size:16px}.devices p{margin:0;line-height:1.65;color:var(--text-secondary)}.devices label{display:grid;gap:8px}.devices input,.devices button{box-sizing:border-box;min-height:44px;padding:10px 14px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);font:inherit}.devices input{width:100%;min-width:0}.devices button{justify-self:start;cursor:pointer}.devices button:disabled{opacity:.5;cursor:default}.devices ul{list-style:none;padding:0;margin:0}.devices li{display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--line);padding:16px 0}.devices li>div{display:grid;gap:6px;flex:1;min-width:0;overflow-wrap:anywhere}.devices small{color:var(--text-secondary)}.pair-code,.confirmation{padding:18px;border:1px solid var(--line);border-radius:12px}.pair-code strong{display:block;font-size:26px;letter-spacing:.1em;margin-bottom:12px;user-select:all}.qr-code{display:grid;gap:12px;justify-items:start}.qr-code img{width:256px;max-width:100%;height:auto;background:white;border-radius:12px}.manual-pairing{border-top:1px solid var(--line);padding-top:22px}.confirmation div{display:flex;gap:12px;margin-top:16px}.devices [role=alert]{color:var(--danger)}.devices :focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n</style>\n","<script setup lang=\"ts\">\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nimport DevicePairingPanel from './DevicePairingPanel.vue'\nconst emit = defineEmits<{ close: [] }>()\n</script>\n<template>\n <StandaloneDialog title=\"设备配对\" compact wide @close=\"emit('close')\">\n <DevicePairingPanel />\n </StandaloneDialog>\n</template>\n","<script setup lang=\"ts\">\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nimport DevicePairingPanel from './DevicePairingPanel.vue'\nconst emit = defineEmits<{ close: [] }>()\n</script>\n<template>\n <StandaloneDialog title=\"设备配对\" compact wide @close=\"emit('close')\">\n <DevicePairingPanel />\n </StandaloneDialog>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{A as e,Dt as t,F as n,G as r,L as i,M as a,_ as o,a as s,b as ee,d as c,et as l,f as u,m as d,ot as f,p,t as m,v as h}from"./_plugin-vue_export-helper-BumXM028.js";import{M as g,W as te,w as _}from"./index-
|
|
2
|
-
//# sourceMappingURL=LocalVmSettingsPanel-
|
|
1
|
+
import{A as e,Dt as t,F as n,G as r,L as i,M as a,_ as o,a as s,b as ee,d as c,et as l,f as u,m as d,ot as f,p,t as m,v as h}from"./_plugin-vue_export-helper-BumXM028.js";import{M as g,W as te,w as _}from"./index-D11cgXI9.js";import{n as v}from"./id-mzBN_4Vw.js";import{t as ne}from"./RunnerSettingsPanel-DjMGnXGP.js";import{d as y,f as b,p as re,u as x}from"./ConversationsView-CxVFk5sQ.js";var ie={class:`local-vm-settings`,"aria-label":`本地虚拟机设置`},ae={class:`row`},oe={class:`status`},se=[`disabled`],ce={key:1},le={key:2,role:`alert`},ue={key:3,role:`status`},de={key:0},fe=[`aria-expanded`],pe={key:1},me={key:2},S={class:`segmented`,role:`group`,"aria-label":`虚拟机隔离方式`},C=[`aria-pressed`,`disabled`],w=[`aria-pressed`,`disabled`],T={class:`row`},E=[`value`,`disabled`],D=[`value`],O={key:3},k={class:`row`},A=[`value`,`disabled`],j=[`value`],M=[`value`],N={key:0},P={key:1},F={key:4},I={key:0},L={class:`step`},R={class:`step`},z={class:`step`},B={class:`image-preparation`},he=[`disabled`],ge=[`value`],_e=[`disabled`],ve={key:5},ye={key:0},be={class:`row`},xe=[`disabled`,`onClick`],Se=[`disabled`,`onClick`],Ce=[`disabled`,`onClick`],we={class:`row`},Te=[`disabled`,`onClick`],Ee=[`disabled`,`onClick`],V=m(ee({__name:`LocalVmSettingsPanel`,setup(ee){let m=l(),V=l(``),H=l(!1),U=l(!1),W=l(!1),G=l(`standard`),K=l([]),q=!1,J;async function Y(){if(!(H.value||U.value||q)){H.value=!0;try{let e=await _(`/api/app/admin/local-vm`);if(q)return;m.value=e;let t=await _(`/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 _(`/api/app/agents/${e.id}/local-vm`)})));q||(K.value=i)}catch(e){q||(V.value=e instanceof Error?e.message:`无法读取本地虚拟机状态`)}finally{H.value=!1}}}async function X(e){U.value=!0,V.value=``;try{await e()}catch(e){V.value=e instanceof Error?e.message:`操作未完成`}finally{U.value=!1,await Y()}}function De(){X(()=>_(`/api/app/admin/local-vm/prepare`,{method:`POST`,body:{requestId:v(),imageKey:G.value}}))}function Z(e,t=m.value?.maxInstances??2){e===`shared`&&m.value?.mode!==`shared`&&!confirm(`当前账号的多个机器人将共用桌面、工作文件和浏览器登录。确认使用共享虚拟机?`)||X(()=>_(`/api/app/admin/local-vm/policy`,{method:`PUT`,body:{requestId:v(),mode:e,maxInstances:t}}))}function Q(e,t){t===`remove`&&!confirm(`移除“${e.name}”的虚拟机实例?工作文件、软件配置和登录资料将保留。`)||X(()=>_(`/api/app/agents/${e.id}/local-vm/${t}`,{method:`POST`,body:{}}))}async function $(){await Y(),q||(J=setTimeout($,m.value?.busy?2e3:1e4))}return a($),e(()=>{q=!0,clearTimeout(J)}),(e,a)=>(n(),d(`section`,ie,[c(`article`,null,[c(`h3`,null,t(m.value?.fixedCapacity?`Compose 共享桌面`:`本地虚拟机`),1),c(`p`,null,t(m.value?.fixedCapacity?`桌面容器随 Web 的 Compose 文件部署,工作文件与浏览器资料保存在各桌面的数据卷中。`:`在 Yaovi 所在电脑的隔离 Linux 桌面中运行机器人,工作文件与浏览器资料持续保存。`),1),c(`div`,ae,[c(`span`,oe,[h(g,{name:m.value?.image?`check`:`monitor`},null,8,[`name`]),o(t(H.value&&!m.value?`正在检查…`:m.value?.maintenance?.phase===`ready`?`虚拟机环境已就绪`:m.value?.image?`镜像已准备`:`尚未就绪`),1)]),c(`button`,{disabled:H.value||U.value,onClick:a[0]||=e=>{V.value=``,Y()}},[h(g,{name:`refresh`}),a[7]||=o(`重新检查`,-1)],8,se)]),m.value?.maintenance?(n(),u(re,{key:0,state:m.value.maintenance},null,8,[`state`])):p(``,!0),m.value?.problem?(n(),d(`p`,ce,t(m.value.problem),1)):p(``,!0),V.value?(n(),d(`p`,le,t(V.value),1)):p(``,!0),m.value?.job&&m.value.job.state!==`complete`?(n(),d(`p`,ue,t(m.value.job.message),1)):p(``,!0)]),m.value?.executionHost===`runner`?(n(),d(`article`,de,[a[8]||=c(`h3`,null,`Yaovi 执行连接`,-1),c(`p`,null,t(m.value.fixedCapacity?`执行连接负责 Yaovi 模型与工具调用,桌面容器完全由这套 Compose 管理。`:`虚拟桌面由运行 Yaovi 的执行节点提供,Docker 版 Web 通过这条连接使用桌面。`)+t(m.value.runnerName?`当前节点:${m.value.runnerName}。`:``),1),c(`button`,{"aria-expanded":W.value,onClick:a[1]||=e=>W.value=!W.value},t(W.value?`收起执行节点设置`:`打开执行节点设置`),9,fe),W.value?(n(),u(ne,{key:0,"computer-setup":``})):p(``,!0)])):p(``,!0),m.value?.fixedCapacity?(n(),d(`article`,pe,[a[9]||=c(`h3`,null,`Compose 共享桌面`,-1),c(`p`,null,`已配置 `+t(m.value.maxInstances)+` 台桌面,数量由 Compose 固定。请在机器人聊天的电脑面板选择已有桌面。`,1),(n(!0),d(s,null,i(m.value.desktops,e=>(n(),d(`div`,{key:e.id,class:`instance`},[c(`strong`,null,t(e.name)+` · `+t(e.imageKey===`cursor`?`Cursor Universal`:`标准桌面`),1),c(`span`,null,t(e.ready?`已就绪`:e.online?`正在启动`:`未连接`),1)]))),128))])):m.value?(n(),d(`article`,me,[a[11]||=c(`h3`,null,`隔离方式`,-1),a[12]||=c(`p`,null,`按机器人创建独立桌面,或让可信的机器人共用一个桌面。`,-1),c(`div`,S,[c(`button`,{"aria-pressed":m.value?.mode===`shared`,disabled:!m.value?.configured||!!m.value?.setupRequired||U.value||m.value.busy,onClick:a[2]||=e=>Z(`shared`)},`共享虚拟机`,8,C),c(`button`,{"aria-pressed":m.value?.mode===`per-bot`,disabled:!m.value?.configured||!!m.value?.setupRequired||U.value||m.value.busy,onClick:a[3]||=e=>Z(`per-bot`)},`每个机器人独立`,8,w)]),a[13]||=c(`p`,{class:`sharing-explanation`},`共享指多个机器人同时使用同一台虚拟机:命令与文件操作并行执行,桌面操作交替进行,共用桌面和工作文件。`,-1),c(`label`,T,[a[10]||=o(`虚拟机运行数量上限`,-1),c(`select`,{"aria-label":`虚拟机数量上限`,value:m.value?.maxInstances??2,disabled:!m.value?.configured||!!m.value?.setupRequired||U.value||m.value.busy,onChange:a[4]||=e=>Z(m.value.mode,Number(e.target.value))},[(n(),d(s,null,i([1,2,3,4],e=>c(`option`,{key:e,value:e},t(e),9,D)),64))],40,E)])])):p(``,!0),m.value&&!m.value.fixedCapacity?(n(),d(`article`,O,[a[15]||=c(`h3`,null,`空闲自动停止`,-1),a[16]||=c(`p`,null,`从任务结束或交还控制权后开始计时,适用于该执行节点上的所有本地虚拟机。`,-1),c(`label`,k,[a[14]||=o(`停止时间`,-1),c(`select`,{"aria-label":`虚拟机空闲停止时间`,value:m.value.idleStopMinutes??5,disabled:U.value||!m.value.configured||m.value.idleStopMinutes===void 0,onChange:a[5]||=e=>X(()=>f(_)(`/api/app/admin/local-vm/idle-policy`,{method:`PUT`,body:{requestId:f(v)(),idleStopMinutes:Number(e.target.value)}}))},[(n(!0),d(s,null,i(f(y),e=>(n(),d(`option`,{key:e,value:e},t(f(b)(e)),9,j))),128)),m.value.idleStopMinutes!==void 0&&!f(y).some(e=>e===m.value.idleStopMinutes)?(n(),d(`option`,{key:0,value:m.value.idleStopMinutes},t(f(b)(m.value.idleStopMinutes)),9,M)):p(``,!0)],40,A)]),m.value.idleStopMinutes===void 0?(n(),d(`p`,N,`连接并更新执行节点后可修改。`)):m.value.idleStopMinutes===0?(n(),d(`p`,P,`空闲时保持运行;仍可手动停止,执行节点关闭时也会停止。`)):p(``,!0)])):p(``,!0),m.value&&!m.value.fixedCapacity&&!m.value.setupRequired?(n(),d(`article`,F,[a[23]||=c(`h3`,null,`准备运行环境`,-1),m.value?.executionHost===`runner`?(n(),d(`p`,I,`以下检查和镜像准备均在执行节点所在电脑完成。`)):p(``,!0),c(`ol`,null,[c(`li`,null,[c(`span`,L,t(m.value?.runtime?`✓`:`1`),1),c(`div`,null,[a[17]||=c(`strong`,null,`安装 Docker 或 Podman`,-1),c(`p`,null,t(m.value?.runtime?`已检测到 ${m.value.runtime}`:`请先安装 Docker Desktop 或 Podman。`),1)])]),c(`li`,null,[c(`span`,R,t(m.value?.daemonUp?`✓`:`2`),1),c(`div`,null,[a[18]||=c(`strong`,null,`启动容器运行环境`,-1),c(`p`,null,t(m.value?.daemonUp?`运行环境已启动`:`打开 Docker / Podman,等待它完成启动后重新检查。`),1)])]),c(`li`,null,[c(`span`,z,t(m.value?.image?`✓`:`3`),1),c(`div`,B,[a[20]||=c(`strong`,null,`准备虚拟机镜像`,-1),a[21]||=c(`p`,null,`两个镜像可分别准备。独立桌面可各自选择;同一共享桌面的所有机器人使用一个镜像。`,-1),c(`label`,null,[a[19]||=o(`镜像`,-1),r(c(`select`,{"onUpdate:modelValue":a[6]||=e=>G.value=e,disabled:U.value||m.value.busy},[(n(!0),d(s,null,i(f(x),e=>(n(),d(`option`,{key:e.key,value:e.key},t(e.name)+` · `+t(m.value.images?.find(t=>t.key===e.key)?.ready?`已就绪`:`未准备`),9,ge))),128))],8,he),[[te,G.value]])]),c(`p`,null,t(f(x).find(e=>e.key===G.value)?.description)+t(G.value===`cursor`?`;Apple 芯片电脑需要运行环境支持 amd64 模拟。`:``),1),c(`button`,{class:`primary`,disabled:U.value||m.value?.busy||!m.value?.daemonUp,onClick:De},t(m.value?.busy?`正在准备…`:m.value.images?.find(e=>e.key===G.value)?.ready?`重新检查所选镜像`:`准备所选镜像`),9,_e)])]),c(`li`,null,[a[22]||=c(`span`,{class:`step`},`4`,-1),c(`div`,null,[c(`strong`,null,t(m.value.maintenance?`自动启动与维护`:`从机器人聊天中创建桌面`),1),c(`p`,null,t(m.value.maintenance?`运行环境就绪后会自动准备并启动桌面。可在聊天的“电脑”中查看和使用;手动停止后由新任务或明确启动唤醒。`:`打开聊天的“电脑”,选择“本地虚拟机”,然后创建该机器人的虚拟机。`),1)])])])])):p(``,!0),m.value&&!m.value.fixedCapacity?(n(),d(`article`,ve,[c(`h3`,null,t(m.value?.mode===`shared`?`共享虚拟机`:`机器人虚拟机`),1),K.value.length?p(``,!0):(n(),d(`p`,ye,`还没有机器人使用本地虚拟机。`)),(n(!0),d(s,null,i(K.value,e=>(n(),d(`div`,{key:e.agent.id,class:`instance`},[c(`div`,null,[c(`strong`,null,t(e.agent.computerEnvironmentName??e.agent.name),1),c(`p`,null,t(e.vm.inUse?`正在使用`:{running:`运行中`,stopped:`已停止`,missing:`尚未创建`}[e.vm.container]),1)]),c(`div`,be,[e.vm.container===`running`?(n(),d(`button`,{key:0,disabled:U.value||e.vm.inUse,onClick:t=>Q(e.agent,`stop`)},`停止`,8,xe)):(n(),d(`button`,{key:1,disabled:U.value||!m.value?.image,onClick:t=>Q(e.agent,e.vm.container===`missing`?`create`:`start`)},t(e.vm.container===`missing`?`创建`:`启动`),9,Se)),e.vm.container===`missing`?p(``,!0):(n(),d(`button`,{key:2,disabled:U.value||e.vm.inUse,onClick:t=>Q(e.agent,`remove`)},`移除实例`,8,Ce))])]))),128)),(n(!0),d(s,null,i(m.value?.instances?.filter(e=>e.orphaned),e=>(n(),d(`div`,{key:e.id,class:`instance`},[c(`div`,null,[c(`strong`,null,t(e.name),1),c(`p`,null,`暂无机器人使用 · `+t(e.status===`free`?`已停止`:`运行中或待核对`),1)]),c(`div`,we,[c(`button`,{disabled:U.value,onClick:t=>X(()=>f(_)(`/api/app/admin/local-vm/instances/${e.id}/stop`,{method:`POST`,body:{requestId:f(v)()}}))},`停止`,8,Te),c(`button`,{disabled:U.value,onClick:t=>X(()=>f(_)(`/api/app/admin/local-vm/instances/${e.id}/remove`,{method:`POST`,body:{requestId:f(v)()}}))},`移除实例`,8,Ee)])]))),128))])):p(``,!0)]))}}),[[`__scopeId`,`data-v-640d666b`]]);export{V as default};
|
|
2
|
+
//# sourceMappingURL=LocalVmSettingsPanel-Zs7CiXdf.js.map
|
package/dist/assets/{LocalVmSettingsPanel-DBkqouAU.js.map → LocalVmSettingsPanel-Zs7CiXdf.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalVmSettingsPanel-DBkqouAU.js","names":[],"sources":["../../src/client/components/app/LocalVmSettingsPanel.vue","../../src/client/components/app/LocalVmSettingsPanel.vue"],"sourcesContent":["<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-Zs7CiXdf.js","names":[],"sources":["../../src/client/components/app/LocalVmSettingsPanel.vue","../../src/client/components/app/LocalVmSettingsPanel.vue"],"sourcesContent":["<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{A as e,Dt as t,F as n,G as r,H as i,L as a,M as ee,Tt as o,V as s,W as c,_ as l,a as u,b as d,d as f,et as p,f as m,m as h,ot as g,p as _,t as v,u as y,v as b}from"./_plugin-vue_export-helper-BumXM028.js";import{G as x,J as S,M as C,w}from"./index-BPE9bBla.js";import{t as T}from"./StandaloneDialog-B9HCNEFb.js";var E=`/api/app/admin/hermes/network`,D=e=>e?`?bot=${encodeURIComponent(e)}`:``,O={get:e=>w(E+D(e)),save:(e,t,n,r)=>w(`${E}/${n?`connections/`+encodeURIComponent(n):`default`}${D(r)}`,{method:`PUT`,body:{revision:e,policy:t}}),proxy:(e,t)=>w(`/api/app/admin/hermes/network/proxies`,{method:`POST`,body:{revision:e,proxy:t}}),remove:(e,t)=>w(`/api/app/admin/hermes/network/proxies/`+encodeURIComponent(t),{method:`DELETE`,body:{revision:e}}),test:(e,t)=>w(`/api/app/admin/hermes/network/test`+D(t),{method:`POST`,body:e})};function k(e,t){return t.mode===`inherit`?`跟随默认 · `+k(e,e.default):t.mode===`environment`?e.environmentDetected?`服务器环境代理`:`服务器环境(未检测到代理)`:t.mode===`direct`?`直接连接`:e.proxies.find(e=>e.id===t.proxyId)?.name??`代理已不可用`}var A={key:0,class:`compact-choice`},j=[`value`,`disabled`],M=[`value`],N=[`disabled`],P=[`name`,`checked`,`value`,`onChange`],F={key:2,class:`proxy-select`},I={key:0},L=[`value`,`disabled`],R=[`value`],z={key:0},te={key:1},B={key:3,class:`summary`},ne={key:4,class:`network-diagnostics`},V={key:1},H=[`disabled`],U={key:5,role:`alert`},W=v(d({__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(e,{expose:r,emit:ee}){let c=e,d=ee,m=s(),v=p(!1),b=p(),x=p(``),S=y(()=>k(c.view,c.modelValue)),C=y(()=>c.view.proxies.find(e=>e.id===c.modelValue.proxyId)),w=y(()=>c.global?[{id:`environment`,name:`沿用服务器环境`,hint:c.view.environmentDetected?`已检测到环境代理,保留当前网络行为`:`未检测到环境代理`},{id:`direct`,name:`直接连接`,hint:`不使用代理`},{id:`proxy`,name:`指定代理`,hint:`通过已保存的代理连接`}]:[{id:`inherit`,name:`跟随服务器默认`,hint:k(c.view,c.view.default)},{id:`direct`,name:`直接连接`,hint:`此连接不使用代理`},{id:`proxy`,name:`指定代理`,hint:`仅此连接使用所选代理`}]);function T(e){d(`update:modelValue`,e===`proxy`?{mode:e,proxyId:c.modelValue.proxyId??c.view.proxies[0]?.id}:{mode:e})}i(()=>JSON.stringify([c.modelValue,c.view.revision]),()=>{b.value=void 0,x.value=``});async function E(){if(!v.value){v.value=!0,d(`busy`,!0),x.value=``,b.value=void 0;try{b.value=await O.test({provider:c.provider,policy:c.modelValue.mode===`environment`?void 0:c.modelValue},c.bot)}catch(e){x.value=e instanceof Error?e.message:`测试失败,请重试。`}finally{v.value=!1,d(`busy`,!1)}}}return r({summary:S,test:E}),(r,i)=>(n(),h(`div`,{class:o([`network-choice`,{"global-choice":e.global}])},[e.compact&&!e.summaryOnly?(n(),h(`label`,A,[l(t(e.global?`默认连接方式`:`连接网络`),1),f(`select`,{value:e.modelValue.mode,disabled:e.disabled||v.value,onChange:i[0]||=e=>T(e.target.value)},[(n(!0),h(u,null,a(w.value,e=>(n(),h(`option`,{key:e.id,value:e.id},t(e.name),9,M))),128))],40,j)])):e.summaryOnly?_(``,!0):(n(),h(`fieldset`,{key:1,disabled:e.disabled||v.value},[f(`legend`,null,t(e.global?`默认连接方式`:`连接方式`),1),(n(!0),h(u,null,a(w.value,r=>(n(),h(`label`,{key:r.id,class:o([`network-option`,{selected:e.modelValue.mode===r.id}])},[f(`input`,{type:`radio`,name:g(m),checked:e.modelValue.mode===r.id,value:r.id,onChange:e=>T(r.id)},null,40,P),f(`span`,null,[f(`strong`,null,t(r.name),1),f(`small`,null,t(r.hint),1)])],2))),128))],8,N)),e.modelValue.mode===`proxy`&&!e.summaryOnly?(n(),h(`div`,F,[f(`label`,null,[e.compact||e.global?(n(),h(`span`,I,`使用代理`)):_(``,!0),f(`select`,{"aria-label":`使用代理`,value:e.modelValue.proxyId??``,disabled:e.disabled||v.value,onChange:i[1]||=e=>d(`update:modelValue`,{mode:`proxy`,proxyId:e.target.value})},[i[2]||=f(`option`,{value:``,disabled:``},`请选择代理`,-1),(n(!0),h(u,null,a(e.view.proxies,e=>(n(),h(`option`,{key:e.id,value:e.id},t(e.name),9,R))),128))],40,L)]),C.value?(n(),h(`small`,z,t(C.value.protocol.toUpperCase())+` · `+t(C.value.host)+`:`+t(C.value.port),1)):_(``,!0),e.view.proxies.length?_(``,!0):(n(),h(`p`,te,`尚未添加代理。请先在「环境与权限 → 模型网络」添加。`))])):_(``,!0),e.compact&&!e.global&&!e.summaryOnly?(n(),h(`p`,B,t(S.value),1)):_(``,!0),!e.global&&!e.hideTest?(n(),h(`div`,ne,[b.value?(n(),h(`div`,{key:0,class:o([`test-result`,{failed:b.value.proxy===`failed`||b.value.target===`failed`}]),role:`status`},[f(`strong`,null,[l(`代理:`+t({passed:`端口可达`,failed:`连接失败`,skipped:`未检测`}[b.value.proxy]),1),e.provider?(n(),h(u,{key:0},[l(` · 目标服务:`+t({passed:`可达`,failed:`不可达`,skipped:`尚未检测`}[b.value.target]),1)],64)):_(``,!0)]),f(`small`,null,t(b.value.message),1)],2)):(n(),h(`div`,V,[...i[3]||=[f(`strong`,null,`连接测试`,-1),f(`small`,null,`由运行 Yaovi 的服务器发起`,-1)]])),f(`button`,{type:`button`,disabled:e.disabled||v.value||e.modelValue.mode===`proxy`&&!C.value,onClick:E},t(v.value?`正在测试…`:`测试当前选择`),9,H)])):_(``,!0),x.value?(n(),h(`p`,U,t(x.value),1)):_(``,!0)],2))}}),[[`__scopeId`,`data-v-a1f2e27a`]]),G=`/api/app/admin/hermes`,K=(e,t=``)=>{let n=new URLSearchParams(t);e&&n.set(`bot`,e);let r=n.toString();return r?`?${r}`:``},q={providers:e=>w(`${G}/providers${K(e)}`),config:e=>w(`${G}/config${K(e)}`),patchConfig:(e,t)=>w(`${G}/config${K(t)}`,{method:`PATCH`,body:{config:e}}),models:(e=!1,t)=>w(`${G}/models${K(t,e?`refresh=1`:``)}`),jev:()=>w(`${G}/jev`),botModels:()=>w(`${G}/bot-models`),saveBotModels:e=>w(`${G}/bot-models`,{method:`PUT`,body:e}),previewJev:e=>w(`${G}/jev/preview`,{method:`POST`,body:e}),saveJev:e=>w(`${G}/jev`,{method:`PUT`,body:e}),setModel:(e,t)=>w(`${G}/model${K(t)}`,{method:`POST`,body:e}),auxiliary:e=>w(`${G}/auxiliary${K(e)}`),oauth:e=>w(`${G}/oauth${K(e)}`),oauthStart:(e,t,n)=>w(`${G}/oauth/${encodeURIComponent(e)}/start${K(t)}`,{method:`POST`,body:n===void 0?{}:{networkRevision:n}}),oauthPoll:(e,t,n)=>w(`${G}/oauth/${encodeURIComponent(e)}/poll/${encodeURIComponent(t)}${K(n)}`),oauthCancel:(e,t)=>w(`${G}/oauth/sessions/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`}),oauthSubmit:(e,t,n)=>w(`${G}/oauth/${encodeURIComponent(e)}/token${K(n)}`,{method:`POST`,body:{token:t}}),oauthDisconnect:(e,t)=>w(`${G}/oauth/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`}),env:e=>w(`${G}/env${K(e)}`),setEnv:(e,t,n)=>w(`${G}/env${K(n)}`,{method:`PUT`,body:{key:e,value:t,provider_setup:!0}}),deleteEnv:(e,t)=>w(`${G}/env${K(t)}`,{method:`DELETE`,body:{key:e}}),validate:(e,t)=>w(`${G}/validate`,{method:`POST`,body:{key:e,value:t}}),endpoints:e=>w(`${G}/endpoints${K(e)}`),saveEndpoint:(e,t)=>w(`${G}/endpoints${K(t)}`,{method:`POST`,body:e}),deleteEndpoint:(e,t)=>w(`${G}/endpoints/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`}),pool:(e,t)=>w(`${G}/pool/${encodeURIComponent(e)}${K(t)}`),addPool:(e,t,n,r)=>w(`${G}/pool/${encodeURIComponent(e)}${K(r)}`,{method:`POST`,body:{apiKey:t,...n?{label:n}:{}}}),removePool:(e,t,n)=>w(`${G}/pool/${encodeURIComponent(e)}/${t}${K(n)}`,{method:`DELETE`})},J=e=>`${G}/media/${e}`,Y={config:e=>w(`${J(e)}/config`),models:(e,t)=>w(`${J(e)}/models${t?`?provider=${encodeURIComponent(t)}`:``}`),setProvider:(e,t)=>w(`${J(e)}/provider`,{method:`PUT`,body:{provider:t}}),setModel:(e,t,n)=>w(`${J(e)}/model`,{method:`PUT`,body:{model:t,provider:n}}),setEnv:(e,t)=>w(`${J(e)}/env`,{method:`PUT`,body:{env:t}})},X={config:()=>w(`${G}/web`),setProvider:(e,t)=>w(`${G}/web/provider`,{method:`PUT`,body:{capability:e,provider:t}}),setEnv:e=>w(`${G}/web/env`,{method:`PUT`,body:{env:e}}),test:(e,t)=>w(`${G}/web/test`,{method:`POST`,body:{capability:e,...e===`search`?{query:t}:{url:t}}})},Z=`${G}/mcp`,re={list:e=>w(`${Z}${K(e)}`),create:(e,t,n)=>w(`${Z}${K(n)}`,{method:`POST`,body:{name:e,...t}}),update:(e,t,n)=>w(`${Z}/${encodeURIComponent(e)}${K(n)}`,{method:`PUT`,body:t}),remove:(e,t)=>w(`${Z}/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`}),setEnabled:(e,t,n)=>w(`${Z}/${encodeURIComponent(e)}/enabled${K(n)}`,{method:`PATCH`,body:{enabled:t}}),test:(e,t)=>w(`${Z}/${encodeURIComponent(e)}/test${K(t)}`,{method:`POST`}),oauthStart:(e,t)=>w(`${Z}/${encodeURIComponent(e)}/oauth/start${K(t)}`,{method:`POST`}),oauthStatus:(e,t)=>w(`${Z}/oauth/${encodeURIComponent(e)}${K(t)}`),oauthCancel:(e,t)=>w(`${Z}/oauth/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`})},Q=`${G}/channels`,$=e=>`?bot=${encodeURIComponent(e)}`,ie={list:e=>w(`${Q}${$(e)}`),save:(e,t,n)=>w(`${Q}/${t}${$(e)}`,{method:`PUT`,body:n}),setEnabled:(e,t,n)=>w(`${Q}/${t}/enabled${$(e)}`,{method:`POST`,body:{enabled:n}}),remove:(e,t)=>w(`${Q}/${t}${$(e)}`,{method:`DELETE`}),test:(e,t)=>w(`${Q}/${t}/test${$(e)}`,{method:`POST`}),approve:(e,t,n)=>w(`${Q}/${t}/pairing${$(e)}`,{method:`POST`,body:{code:n}}),revoke:(e,t,n)=>w(`${Q}/${t}/pairing/${encodeURIComponent(n)}${$(e)}`,{method:`DELETE`})};function ae(e){let t=document.createElement(`textarea`),n=document.activeElement instanceof HTMLElement?document.activeElement:null;t.value=e,t.setAttribute(`readonly`,``),t.setAttribute(`aria-hidden`,`true`),t.style.cssText=`position:fixed;left:-9999px;top:0;width:1px;height:1px;opacity:0`,document.body.appendChild(t),t.focus({preventScroll:!0}),t.select(),t.setSelectionRange(0,e.length);let r=!1;try{r=document.execCommand(`copy`)}catch{r=!1}finally{t.remove(),n?.focus({preventScroll:!0})}return r}async function oe(e){try{if(window.isSecureContext&&navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}return ae(e)}var se={class:`provider-login`},ce={class:`scope`},le={class:`login-success`,role:`status`},ue={key:1,class:`network-summary`},de={class:`failure-heading`,role:`alert`},fe={key:0,role:`status`},pe=[`disabled`],me={key:0,class:`device-code`},he={key:0,role:`status`},ge=[`href`],_e={class:`hint`},ve=[`aria-label`,`disabled`],ye={key:0},be=[`disabled`],xe={key:6,class:`error`,role:`alert`},Se=[`disabled`],Ce={key:8,class:`hint`},we=[`disabled`],Te=v(d({__name:`ProviderLoginDialog`,props:{provider:{},bot:{}},emits:[`close`,`success`],setup(i,{emit:a}){let o=i,s=a,d=o.provider,v=o.bot,w=d.flow===`device_code`||[`device_code`,`process`].includes(d.web_flow??``),E=d.web_flow===`paste_token`,D=p(`ready`),A=p(),j=p(``),M=p(``),N=p(!1),P=p(``),F=p(),I=p({mode:`inherit`}),L=p(!1),R=y(()=>!!F.value&&JSON.stringify(I.value)!==JSON.stringify(F.value.connections[d.id]??{mode:`inherit`})),z=y(()=>!!F.value&&!L.value&&(I.value.mode!==`proxy`||!!I.value.proxyId)),te=y(()=>F.value?k(F.value,I.value):`正在读取网络…`);async function B(){try{let e=await O.get(v);H&&(F.value=e,I.value={...e.connections[d.id]??{mode:`inherit`}},j.value=``)}catch(e){H&&(j.value=e instanceof Error?e.message:`无法读取网络配置,请重试。`)}}async function ne(){R.value&&(F.value=await O.save(F.value.revision,I.value,d.id,v))}async function V(){if(!(A.value&&!window.confirm(`更改网络将结束本次授权,需要重新获取验证码。继续?`))){U++,clearTimeout(G);try{await Y(A.value),A.value=void 0,D.value=`ready`,j.value=``,await B()}catch(e){D.value=`failed`,j.value=e instanceof Error?e.message:`无法取消本次授权,请重试。`}}}let H=!0,U=0,G,K=y(()=>{try{let e=new URL(A.value?.verification_url??``);return[`https:`,`http:`].includes(e.protocol)&&!e.username&&!e.password?e.href:``}catch{return``}}),J=y(()=>D.value===`starting`?`正在准备登录…`:D.value===`approved`?`登录成功`:K.value?`等待你在浏览器中完成授权…`:`正在准备授权链接…`);async function Y(e){e&&await q.oauthCancel(e.session_id,v)}async function X(){if(D.value===`starting`||N.value||!z.value)return;let e=++U;clearTimeout(G),j.value=``,P.value=``,D.value=`starting`;try{if(await Y(A.value),A.value=void 0,await ne(),!H||e!==U)return;let t=await q.oauthStart(d.id,v,F.value.revision);if(!H||e!==U){await Y(t);return}A.value=t,D.value=`pending`,Z(e)}catch(t){if(!H||e!==U)return;j.value=t instanceof Error?t.message:`无法开始登录,请重试。`,D.value=`failed`}}function Z(e){let t=A.value;!t||!H||(G=setTimeout(async()=>{try{let n=await q.oauthPoll(d.id,t.session_id,v);if(!H||e!==U)return;if(n.user_code&&(t.user_code=n.user_code),n.verification_url&&(t.verification_url=n.verification_url),n.status===`pending`){Z(e);return}if(n.status===`approved`){A.value=void 0,D.value=`approved`,s(`success`);return}j.value=n.error_message||(n.status===`expired`?`授权已过期,请重新登录。`:n.status===`denied`?`账号已拒绝授权,可重新开始登录。`:`登录未完成,请重试。`),D.value=`failed`}catch(t){if(!H||e!==U)return;j.value=t instanceof Error?t.message:`无法获取授权结果,请重试。`,D.value=`failed`}},Math.max(1,t.poll_interval||2)*1e3))}async function re(){if(!(N.value||!M.value.trim()||!z.value)){N.value=!0,j.value=``;try{if(await ne(),await q.oauthSubmit(d.id,M.value.trim(),v),!H)return;M.value=``,D.value=`approved`,s(`success`)}catch(e){H&&(j.value=e instanceof Error?e.message:`凭据保存失败,请重试。`)}finally{N.value=!1}}}function Q(){return!N.value&&(!(M.value||R.value)||window.confirm(`还有未保存的输入,放弃本次更改?`))}function $(){Q()&&s(`close`)}async function ie(){P.value=await oe(A.value?.user_code??``)?`验证码已复制`:`无法自动复制,请选中验证码手动复制。`}return ee(B),e(()=>{H=!1,U++,clearTimeout(G);let e=A.value;A.value=void 0,M.value=``,Y(e).catch(()=>{})}),(e,i)=>(n(),m(T,{title:`登录 ${g(d).name}`,compact:``,wide:``,"before-close":Q,onClose:i[5]||=e=>s(`close`)},{default:c(()=>[f(`div`,se,[f(`p`,ce,t(g(v)?`仅对此 Bot 生效`:`服务器模型连接`),1),D.value===`approved`?(n(),h(u,{key:0},[f(`div`,le,[b(C,{name:`check`,size:24}),i[6]||=f(`strong`,null,`登录成功`,-1)]),f(`p`,null,`已完成 `+t(g(d).name)+` 授权。`,1),f(`button`,{class:`primary`,type:`button`,onClick:$},`完成`)],64)):(n(),h(u,{key:1},[D.value===`ready`?(n(),h(u,{key:0},[F.value?(n(),m(W,{key:0,modelValue:I.value,"onUpdate:modelValue":i[0]||=e=>I.value=e,view:F.value,provider:g(d).id,bot:g(v),compact:``,disabled:N.value,onBusy:i[1]||=e=>L.value=e},null,8,[`modelValue`,`view`,`provider`,`bot`,`disabled`])):(n(),h(`button`,{key:1,type:`button`,onClick:B},`重新读取网络配置`))],64)):(n(),h(`div`,ue,[f(`span`,null,`网络:`+t(te.value),1),D.value===`pending`||D.value===`failed`?(n(),h(`button`,{key:0,type:`button`,onClick:V},`更改`)):_(``,!0)])),D.value===`failed`?(n(),h(u,{key:2},[f(`div`,de,[b(C,{name:`alert`,size:24}),f(`strong`,null,t(j.value),1)]),F.value?(n(),m(W,{key:0,modelValue:I.value,"onUpdate:modelValue":i[2]||=e=>I.value=e,view:F.value,provider:g(d).id,bot:g(v),"summary-only":``,onBusy:i[3]||=e=>L.value=e},null,8,[`modelValue`,`view`,`provider`,`bot`])):_(``,!0),i[7]||=f(`p`,{class:`hint`},`不会自动切换为直接连接。`,-1),f(`button`,{type:`button`,class:`primary`,onClick:V},`检查网络设置`)],64)):_(``,!0),g(w)?(n(),h(u,{key:3},[D.value!==`failed`&&D.value!==`ready`?(n(),h(`p`,fe,t(J.value),1)):_(``,!0),D.value===`ready`?(n(),h(`button`,{key:1,type:`button`,class:`primary`,disabled:!z.value,onClick:X},t(R.value?`保存并开始登录`:`开始登录`),9,pe)):_(``,!0),D.value===`pending`?(n(),h(u,{key:2},[A.value?.user_code?(n(),h(`div`,me,[i[8]||=f(`span`,null,`在授权页面输入验证码`,-1),f(`strong`,null,t(A.value.user_code),1),f(`button`,{type:`button`,onClick:ie},`复制验证码`),P.value?(n(),h(`small`,he,t(P.value),1)):_(``,!0)])):_(``,!0),K.value?(n(),h(`a`,{key:1,class:`primary`,href:K.value,target:`_blank`,rel:`noopener noreferrer`},[b(C,{name:`external`,size:16}),i[9]||=l(`打开授权页面`,-1)],8,ge)):_(``,!0),f(`p`,_e,t(K.value?`在浏览器完成授权后,回到这里查看结果。`:`登录服务准备好后,授权入口会自动显示。`),1)],64)):_(``,!0)],64)):E?(n(),h(`form`,{key:4,onSubmit:S(re,[`prevent`])},[i[13]||=f(`p`,null,`粘贴已有的 API Key 或登录令牌完成连接。`,-1),f(`label`,null,[i[10]||=l(`登录凭据`,-1),r(f(`input`,{"onUpdate:modelValue":i[4]||=e=>M.value=e,"aria-label":`${g(d).name} 凭据`,type:`password`,autocomplete:`new-password`,disabled:N.value,placeholder:`粘贴登录凭据`},null,8,ve),[[x,M.value]])]),g(d).cli_command?(n(),h(`details`,ye,[i[11]||=f(`summary`,null,`如何获取登录令牌`,-1),i[12]||=f(`p`,null,`在服务器上按该服务的登录指引运行:`,-1),f(`code`,null,t(g(d).cli_command),1)])):_(``,!0),f(`button`,{type:`submit`,class:`primary`,disabled:N.value||!M.value.trim()||!z.value},t(N.value?`正在保存…`:R.value?`保存网络与凭据`:`保存凭据`),9,be)],32)):(n(),h(u,{key:5},[i[14]||=f(`p`,null,`此服务需要先在服务器上完成登录:`,-1),f(`code`,null,t(g(d).cli_command),1),i[15]||=f(`p`,{class:`hint`},`完成后关闭弹窗,并刷新连接状态。`,-1)],64)),j.value&&D.value!==`failed`?(n(),h(`p`,xe,t(j.value),1)):_(``,!0),g(w)&&D.value===`failed`?(n(),h(`button`,{key:7,type:`button`,disabled:!z.value,onClick:X},t(R.value?`保存并重新登录`:`重新登录`),9,Se)):_(``,!0),g(w)?(n(),h(`p`,Ce,`授权网页使用当前设备的浏览器网络。此代理仅作用于服务器发起的连接。`)):_(``,!0),f(`button`,{type:`button`,disabled:N.value,onClick:$},t(D.value===`pending`||D.value===`starting`?`取消登录`:`关闭`),9,we)],64))])]),_:1},8,[`title`]))}}),[[`__scopeId`,`data-v-2a052730`]]);export{re as a,W as c,ie as i,O as l,oe as n,Y as o,q as r,X as s,Te as t,k as u};
|
|
2
|
-
//# sourceMappingURL=ProviderLoginDialog-
|
|
1
|
+
import{A as e,Dt as t,F as n,G as r,H as i,L as a,M as ee,Tt as o,V as s,W as c,_ as l,a as u,b as d,d as f,et as p,f as m,m as h,ot as g,p as _,t as v,u as y,v as b}from"./_plugin-vue_export-helper-BumXM028.js";import{G as x,J as S,M as C,w}from"./index-D11cgXI9.js";import{t as T}from"./StandaloneDialog-BXCK22Dx.js";var E=`/api/app/admin/hermes/network`,D=e=>e?`?bot=${encodeURIComponent(e)}`:``,O={get:e=>w(E+D(e)),save:(e,t,n,r)=>w(`${E}/${n?`connections/`+encodeURIComponent(n):`default`}${D(r)}`,{method:`PUT`,body:{revision:e,policy:t}}),proxy:(e,t)=>w(`/api/app/admin/hermes/network/proxies`,{method:`POST`,body:{revision:e,proxy:t}}),remove:(e,t)=>w(`/api/app/admin/hermes/network/proxies/`+encodeURIComponent(t),{method:`DELETE`,body:{revision:e}}),test:(e,t)=>w(`/api/app/admin/hermes/network/test`+D(t),{method:`POST`,body:e})};function k(e,t){return t.mode===`inherit`?`跟随默认 · `+k(e,e.default):t.mode===`environment`?e.environmentDetected?`服务器环境代理`:`服务器环境(未检测到代理)`:t.mode===`direct`?`直接连接`:e.proxies.find(e=>e.id===t.proxyId)?.name??`代理已不可用`}var A={key:0,class:`compact-choice`},j=[`value`,`disabled`],M=[`value`],N=[`disabled`],P=[`name`,`checked`,`value`,`onChange`],F={key:2,class:`proxy-select`},I={key:0},L=[`value`,`disabled`],R=[`value`],z={key:0},te={key:1},B={key:3,class:`summary`},ne={key:4,class:`network-diagnostics`},V={key:1},H=[`disabled`],U={key:5,role:`alert`},W=v(d({__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(e,{expose:r,emit:ee}){let c=e,d=ee,m=s(),v=p(!1),b=p(),x=p(``),S=y(()=>k(c.view,c.modelValue)),C=y(()=>c.view.proxies.find(e=>e.id===c.modelValue.proxyId)),w=y(()=>c.global?[{id:`environment`,name:`沿用服务器环境`,hint:c.view.environmentDetected?`已检测到环境代理,保留当前网络行为`:`未检测到环境代理`},{id:`direct`,name:`直接连接`,hint:`不使用代理`},{id:`proxy`,name:`指定代理`,hint:`通过已保存的代理连接`}]:[{id:`inherit`,name:`跟随服务器默认`,hint:k(c.view,c.view.default)},{id:`direct`,name:`直接连接`,hint:`此连接不使用代理`},{id:`proxy`,name:`指定代理`,hint:`仅此连接使用所选代理`}]);function T(e){d(`update:modelValue`,e===`proxy`?{mode:e,proxyId:c.modelValue.proxyId??c.view.proxies[0]?.id}:{mode:e})}i(()=>JSON.stringify([c.modelValue,c.view.revision]),()=>{b.value=void 0,x.value=``});async function E(){if(!v.value){v.value=!0,d(`busy`,!0),x.value=``,b.value=void 0;try{b.value=await O.test({provider:c.provider,policy:c.modelValue.mode===`environment`?void 0:c.modelValue},c.bot)}catch(e){x.value=e instanceof Error?e.message:`测试失败,请重试。`}finally{v.value=!1,d(`busy`,!1)}}}return r({summary:S,test:E}),(r,i)=>(n(),h(`div`,{class:o([`network-choice`,{"global-choice":e.global}])},[e.compact&&!e.summaryOnly?(n(),h(`label`,A,[l(t(e.global?`默认连接方式`:`连接网络`),1),f(`select`,{value:e.modelValue.mode,disabled:e.disabled||v.value,onChange:i[0]||=e=>T(e.target.value)},[(n(!0),h(u,null,a(w.value,e=>(n(),h(`option`,{key:e.id,value:e.id},t(e.name),9,M))),128))],40,j)])):e.summaryOnly?_(``,!0):(n(),h(`fieldset`,{key:1,disabled:e.disabled||v.value},[f(`legend`,null,t(e.global?`默认连接方式`:`连接方式`),1),(n(!0),h(u,null,a(w.value,r=>(n(),h(`label`,{key:r.id,class:o([`network-option`,{selected:e.modelValue.mode===r.id}])},[f(`input`,{type:`radio`,name:g(m),checked:e.modelValue.mode===r.id,value:r.id,onChange:e=>T(r.id)},null,40,P),f(`span`,null,[f(`strong`,null,t(r.name),1),f(`small`,null,t(r.hint),1)])],2))),128))],8,N)),e.modelValue.mode===`proxy`&&!e.summaryOnly?(n(),h(`div`,F,[f(`label`,null,[e.compact||e.global?(n(),h(`span`,I,`使用代理`)):_(``,!0),f(`select`,{"aria-label":`使用代理`,value:e.modelValue.proxyId??``,disabled:e.disabled||v.value,onChange:i[1]||=e=>d(`update:modelValue`,{mode:`proxy`,proxyId:e.target.value})},[i[2]||=f(`option`,{value:``,disabled:``},`请选择代理`,-1),(n(!0),h(u,null,a(e.view.proxies,e=>(n(),h(`option`,{key:e.id,value:e.id},t(e.name),9,R))),128))],40,L)]),C.value?(n(),h(`small`,z,t(C.value.protocol.toUpperCase())+` · `+t(C.value.host)+`:`+t(C.value.port),1)):_(``,!0),e.view.proxies.length?_(``,!0):(n(),h(`p`,te,`尚未添加代理。请先在「环境与权限 → 模型网络」添加。`))])):_(``,!0),e.compact&&!e.global&&!e.summaryOnly?(n(),h(`p`,B,t(S.value),1)):_(``,!0),!e.global&&!e.hideTest?(n(),h(`div`,ne,[b.value?(n(),h(`div`,{key:0,class:o([`test-result`,{failed:b.value.proxy===`failed`||b.value.target===`failed`}]),role:`status`},[f(`strong`,null,[l(`代理:`+t({passed:`端口可达`,failed:`连接失败`,skipped:`未检测`}[b.value.proxy]),1),e.provider?(n(),h(u,{key:0},[l(` · 目标服务:`+t({passed:`可达`,failed:`不可达`,skipped:`尚未检测`}[b.value.target]),1)],64)):_(``,!0)]),f(`small`,null,t(b.value.message),1)],2)):(n(),h(`div`,V,[...i[3]||=[f(`strong`,null,`连接测试`,-1),f(`small`,null,`由运行 Yaovi 的服务器发起`,-1)]])),f(`button`,{type:`button`,disabled:e.disabled||v.value||e.modelValue.mode===`proxy`&&!C.value,onClick:E},t(v.value?`正在测试…`:`测试当前选择`),9,H)])):_(``,!0),x.value?(n(),h(`p`,U,t(x.value),1)):_(``,!0)],2))}}),[[`__scopeId`,`data-v-a1f2e27a`]]),G=`/api/app/admin/hermes`,K=(e,t=``)=>{let n=new URLSearchParams(t);e&&n.set(`bot`,e);let r=n.toString();return r?`?${r}`:``},q={providers:e=>w(`${G}/providers${K(e)}`),config:e=>w(`${G}/config${K(e)}`),patchConfig:(e,t)=>w(`${G}/config${K(t)}`,{method:`PATCH`,body:{config:e}}),models:(e=!1,t)=>w(`${G}/models${K(t,e?`refresh=1`:``)}`),jev:()=>w(`${G}/jev`),botModels:()=>w(`${G}/bot-models`),saveBotModels:e=>w(`${G}/bot-models`,{method:`PUT`,body:e}),previewJev:e=>w(`${G}/jev/preview`,{method:`POST`,body:e}),saveJev:e=>w(`${G}/jev`,{method:`PUT`,body:e}),setModel:(e,t)=>w(`${G}/model${K(t)}`,{method:`POST`,body:e}),auxiliary:e=>w(`${G}/auxiliary${K(e)}`),oauth:e=>w(`${G}/oauth${K(e)}`),oauthStart:(e,t,n)=>w(`${G}/oauth/${encodeURIComponent(e)}/start${K(t)}`,{method:`POST`,body:n===void 0?{}:{networkRevision:n}}),oauthPoll:(e,t,n)=>w(`${G}/oauth/${encodeURIComponent(e)}/poll/${encodeURIComponent(t)}${K(n)}`),oauthCancel:(e,t)=>w(`${G}/oauth/sessions/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`}),oauthSubmit:(e,t,n)=>w(`${G}/oauth/${encodeURIComponent(e)}/token${K(n)}`,{method:`POST`,body:{token:t}}),oauthDisconnect:(e,t)=>w(`${G}/oauth/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`}),env:e=>w(`${G}/env${K(e)}`),setEnv:(e,t,n)=>w(`${G}/env${K(n)}`,{method:`PUT`,body:{key:e,value:t,provider_setup:!0}}),deleteEnv:(e,t)=>w(`${G}/env${K(t)}`,{method:`DELETE`,body:{key:e}}),validate:(e,t)=>w(`${G}/validate`,{method:`POST`,body:{key:e,value:t}}),endpoints:e=>w(`${G}/endpoints${K(e)}`),saveEndpoint:(e,t)=>w(`${G}/endpoints${K(t)}`,{method:`POST`,body:e}),deleteEndpoint:(e,t)=>w(`${G}/endpoints/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`}),pool:(e,t)=>w(`${G}/pool/${encodeURIComponent(e)}${K(t)}`),addPool:(e,t,n,r)=>w(`${G}/pool/${encodeURIComponent(e)}${K(r)}`,{method:`POST`,body:{apiKey:t,...n?{label:n}:{}}}),removePool:(e,t,n)=>w(`${G}/pool/${encodeURIComponent(e)}/${t}${K(n)}`,{method:`DELETE`})},J=e=>`${G}/media/${e}`,Y={config:e=>w(`${J(e)}/config`),models:(e,t)=>w(`${J(e)}/models${t?`?provider=${encodeURIComponent(t)}`:``}`),setProvider:(e,t)=>w(`${J(e)}/provider`,{method:`PUT`,body:{provider:t}}),setModel:(e,t,n)=>w(`${J(e)}/model`,{method:`PUT`,body:{model:t,provider:n}}),setEnv:(e,t)=>w(`${J(e)}/env`,{method:`PUT`,body:{env:t}})},X={config:()=>w(`${G}/web`),setProvider:(e,t)=>w(`${G}/web/provider`,{method:`PUT`,body:{capability:e,provider:t}}),setEnv:e=>w(`${G}/web/env`,{method:`PUT`,body:{env:e}}),test:(e,t)=>w(`${G}/web/test`,{method:`POST`,body:{capability:e,...e===`search`?{query:t}:{url:t}}})},Z=`${G}/mcp`,re={list:e=>w(`${Z}${K(e)}`),create:(e,t,n)=>w(`${Z}${K(n)}`,{method:`POST`,body:{name:e,...t}}),update:(e,t,n)=>w(`${Z}/${encodeURIComponent(e)}${K(n)}`,{method:`PUT`,body:t}),remove:(e,t)=>w(`${Z}/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`}),setEnabled:(e,t,n)=>w(`${Z}/${encodeURIComponent(e)}/enabled${K(n)}`,{method:`PATCH`,body:{enabled:t}}),test:(e,t)=>w(`${Z}/${encodeURIComponent(e)}/test${K(t)}`,{method:`POST`}),oauthStart:(e,t)=>w(`${Z}/${encodeURIComponent(e)}/oauth/start${K(t)}`,{method:`POST`}),oauthStatus:(e,t)=>w(`${Z}/oauth/${encodeURIComponent(e)}${K(t)}`),oauthCancel:(e,t)=>w(`${Z}/oauth/${encodeURIComponent(e)}${K(t)}`,{method:`DELETE`})},Q=`${G}/channels`,$=e=>`?bot=${encodeURIComponent(e)}`,ie={list:e=>w(`${Q}${$(e)}`),save:(e,t,n)=>w(`${Q}/${t}${$(e)}`,{method:`PUT`,body:n}),setEnabled:(e,t,n)=>w(`${Q}/${t}/enabled${$(e)}`,{method:`POST`,body:{enabled:n}}),remove:(e,t)=>w(`${Q}/${t}${$(e)}`,{method:`DELETE`}),test:(e,t)=>w(`${Q}/${t}/test${$(e)}`,{method:`POST`}),approve:(e,t,n)=>w(`${Q}/${t}/pairing${$(e)}`,{method:`POST`,body:{code:n}}),revoke:(e,t,n)=>w(`${Q}/${t}/pairing/${encodeURIComponent(n)}${$(e)}`,{method:`DELETE`})};function ae(e){let t=document.createElement(`textarea`),n=document.activeElement instanceof HTMLElement?document.activeElement:null;t.value=e,t.setAttribute(`readonly`,``),t.setAttribute(`aria-hidden`,`true`),t.style.cssText=`position:fixed;left:-9999px;top:0;width:1px;height:1px;opacity:0`,document.body.appendChild(t),t.focus({preventScroll:!0}),t.select(),t.setSelectionRange(0,e.length);let r=!1;try{r=document.execCommand(`copy`)}catch{r=!1}finally{t.remove(),n?.focus({preventScroll:!0})}return r}async function oe(e){try{if(window.isSecureContext&&navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}return ae(e)}var se={class:`provider-login`},ce={class:`scope`},le={class:`login-success`,role:`status`},ue={key:1,class:`network-summary`},de={class:`failure-heading`,role:`alert`},fe={key:0,role:`status`},pe=[`disabled`],me={key:0,class:`device-code`},he={key:0,role:`status`},ge=[`href`],_e={class:`hint`},ve=[`aria-label`,`disabled`],ye={key:0},be=[`disabled`],xe={key:6,class:`error`,role:`alert`},Se=[`disabled`],Ce={key:8,class:`hint`},we=[`disabled`],Te=v(d({__name:`ProviderLoginDialog`,props:{provider:{},bot:{}},emits:[`close`,`success`],setup(i,{emit:a}){let o=i,s=a,d=o.provider,v=o.bot,w=d.flow===`device_code`||[`device_code`,`process`].includes(d.web_flow??``),E=d.web_flow===`paste_token`,D=p(`ready`),A=p(),j=p(``),M=p(``),N=p(!1),P=p(``),F=p(),I=p({mode:`inherit`}),L=p(!1),R=y(()=>!!F.value&&JSON.stringify(I.value)!==JSON.stringify(F.value.connections[d.id]??{mode:`inherit`})),z=y(()=>!!F.value&&!L.value&&(I.value.mode!==`proxy`||!!I.value.proxyId)),te=y(()=>F.value?k(F.value,I.value):`正在读取网络…`);async function B(){try{let e=await O.get(v);H&&(F.value=e,I.value={...e.connections[d.id]??{mode:`inherit`}},j.value=``)}catch(e){H&&(j.value=e instanceof Error?e.message:`无法读取网络配置,请重试。`)}}async function ne(){R.value&&(F.value=await O.save(F.value.revision,I.value,d.id,v))}async function V(){if(!(A.value&&!window.confirm(`更改网络将结束本次授权,需要重新获取验证码。继续?`))){U++,clearTimeout(G);try{await Y(A.value),A.value=void 0,D.value=`ready`,j.value=``,await B()}catch(e){D.value=`failed`,j.value=e instanceof Error?e.message:`无法取消本次授权,请重试。`}}}let H=!0,U=0,G,K=y(()=>{try{let e=new URL(A.value?.verification_url??``);return[`https:`,`http:`].includes(e.protocol)&&!e.username&&!e.password?e.href:``}catch{return``}}),J=y(()=>D.value===`starting`?`正在准备登录…`:D.value===`approved`?`登录成功`:K.value?`等待你在浏览器中完成授权…`:`正在准备授权链接…`);async function Y(e){e&&await q.oauthCancel(e.session_id,v)}async function X(){if(D.value===`starting`||N.value||!z.value)return;let e=++U;clearTimeout(G),j.value=``,P.value=``,D.value=`starting`;try{if(await Y(A.value),A.value=void 0,await ne(),!H||e!==U)return;let t=await q.oauthStart(d.id,v,F.value.revision);if(!H||e!==U){await Y(t);return}A.value=t,D.value=`pending`,Z(e)}catch(t){if(!H||e!==U)return;j.value=t instanceof Error?t.message:`无法开始登录,请重试。`,D.value=`failed`}}function Z(e){let t=A.value;!t||!H||(G=setTimeout(async()=>{try{let n=await q.oauthPoll(d.id,t.session_id,v);if(!H||e!==U)return;if(n.user_code&&(t.user_code=n.user_code),n.verification_url&&(t.verification_url=n.verification_url),n.status===`pending`){Z(e);return}if(n.status===`approved`){A.value=void 0,D.value=`approved`,s(`success`);return}j.value=n.error_message||(n.status===`expired`?`授权已过期,请重新登录。`:n.status===`denied`?`账号已拒绝授权,可重新开始登录。`:`登录未完成,请重试。`),D.value=`failed`}catch(t){if(!H||e!==U)return;j.value=t instanceof Error?t.message:`无法获取授权结果,请重试。`,D.value=`failed`}},Math.max(1,t.poll_interval||2)*1e3))}async function re(){if(!(N.value||!M.value.trim()||!z.value)){N.value=!0,j.value=``;try{if(await ne(),await q.oauthSubmit(d.id,M.value.trim(),v),!H)return;M.value=``,D.value=`approved`,s(`success`)}catch(e){H&&(j.value=e instanceof Error?e.message:`凭据保存失败,请重试。`)}finally{N.value=!1}}}function Q(){return!N.value&&(!(M.value||R.value)||window.confirm(`还有未保存的输入,放弃本次更改?`))}function $(){Q()&&s(`close`)}async function ie(){P.value=await oe(A.value?.user_code??``)?`验证码已复制`:`无法自动复制,请选中验证码手动复制。`}return ee(B),e(()=>{H=!1,U++,clearTimeout(G);let e=A.value;A.value=void 0,M.value=``,Y(e).catch(()=>{})}),(e,i)=>(n(),m(T,{title:`登录 ${g(d).name}`,compact:``,wide:``,"before-close":Q,onClose:i[5]||=e=>s(`close`)},{default:c(()=>[f(`div`,se,[f(`p`,ce,t(g(v)?`仅对此 Bot 生效`:`服务器模型连接`),1),D.value===`approved`?(n(),h(u,{key:0},[f(`div`,le,[b(C,{name:`check`,size:24}),i[6]||=f(`strong`,null,`登录成功`,-1)]),f(`p`,null,`已完成 `+t(g(d).name)+` 授权。`,1),f(`button`,{class:`primary`,type:`button`,onClick:$},`完成`)],64)):(n(),h(u,{key:1},[D.value===`ready`?(n(),h(u,{key:0},[F.value?(n(),m(W,{key:0,modelValue:I.value,"onUpdate:modelValue":i[0]||=e=>I.value=e,view:F.value,provider:g(d).id,bot:g(v),compact:``,disabled:N.value,onBusy:i[1]||=e=>L.value=e},null,8,[`modelValue`,`view`,`provider`,`bot`,`disabled`])):(n(),h(`button`,{key:1,type:`button`,onClick:B},`重新读取网络配置`))],64)):(n(),h(`div`,ue,[f(`span`,null,`网络:`+t(te.value),1),D.value===`pending`||D.value===`failed`?(n(),h(`button`,{key:0,type:`button`,onClick:V},`更改`)):_(``,!0)])),D.value===`failed`?(n(),h(u,{key:2},[f(`div`,de,[b(C,{name:`alert`,size:24}),f(`strong`,null,t(j.value),1)]),F.value?(n(),m(W,{key:0,modelValue:I.value,"onUpdate:modelValue":i[2]||=e=>I.value=e,view:F.value,provider:g(d).id,bot:g(v),"summary-only":``,onBusy:i[3]||=e=>L.value=e},null,8,[`modelValue`,`view`,`provider`,`bot`])):_(``,!0),i[7]||=f(`p`,{class:`hint`},`不会自动切换为直接连接。`,-1),f(`button`,{type:`button`,class:`primary`,onClick:V},`检查网络设置`)],64)):_(``,!0),g(w)?(n(),h(u,{key:3},[D.value!==`failed`&&D.value!==`ready`?(n(),h(`p`,fe,t(J.value),1)):_(``,!0),D.value===`ready`?(n(),h(`button`,{key:1,type:`button`,class:`primary`,disabled:!z.value,onClick:X},t(R.value?`保存并开始登录`:`开始登录`),9,pe)):_(``,!0),D.value===`pending`?(n(),h(u,{key:2},[A.value?.user_code?(n(),h(`div`,me,[i[8]||=f(`span`,null,`在授权页面输入验证码`,-1),f(`strong`,null,t(A.value.user_code),1),f(`button`,{type:`button`,onClick:ie},`复制验证码`),P.value?(n(),h(`small`,he,t(P.value),1)):_(``,!0)])):_(``,!0),K.value?(n(),h(`a`,{key:1,class:`primary`,href:K.value,target:`_blank`,rel:`noopener noreferrer`},[b(C,{name:`external`,size:16}),i[9]||=l(`打开授权页面`,-1)],8,ge)):_(``,!0),f(`p`,_e,t(K.value?`在浏览器完成授权后,回到这里查看结果。`:`登录服务准备好后,授权入口会自动显示。`),1)],64)):_(``,!0)],64)):E?(n(),h(`form`,{key:4,onSubmit:S(re,[`prevent`])},[i[13]||=f(`p`,null,`粘贴已有的 API Key 或登录令牌完成连接。`,-1),f(`label`,null,[i[10]||=l(`登录凭据`,-1),r(f(`input`,{"onUpdate:modelValue":i[4]||=e=>M.value=e,"aria-label":`${g(d).name} 凭据`,type:`password`,autocomplete:`new-password`,disabled:N.value,placeholder:`粘贴登录凭据`},null,8,ve),[[x,M.value]])]),g(d).cli_command?(n(),h(`details`,ye,[i[11]||=f(`summary`,null,`如何获取登录令牌`,-1),i[12]||=f(`p`,null,`在服务器上按该服务的登录指引运行:`,-1),f(`code`,null,t(g(d).cli_command),1)])):_(``,!0),f(`button`,{type:`submit`,class:`primary`,disabled:N.value||!M.value.trim()||!z.value},t(N.value?`正在保存…`:R.value?`保存网络与凭据`:`保存凭据`),9,be)],32)):(n(),h(u,{key:5},[i[14]||=f(`p`,null,`此服务需要先在服务器上完成登录:`,-1),f(`code`,null,t(g(d).cli_command),1),i[15]||=f(`p`,{class:`hint`},`完成后关闭弹窗,并刷新连接状态。`,-1)],64)),j.value&&D.value!==`failed`?(n(),h(`p`,xe,t(j.value),1)):_(``,!0),g(w)&&D.value===`failed`?(n(),h(`button`,{key:7,type:`button`,disabled:!z.value,onClick:X},t(R.value?`保存并重新登录`:`重新登录`),9,Se)):_(``,!0),g(w)?(n(),h(`p`,Ce,`授权网页使用当前设备的浏览器网络。此代理仅作用于服务器发起的连接。`)):_(``,!0),f(`button`,{type:`button`,disabled:N.value,onClick:$},t(D.value===`pending`||D.value===`starting`?`取消登录`:`关闭`),9,we)],64))])]),_:1},8,[`title`]))}}),[[`__scopeId`,`data-v-2a052730`]]);export{re as a,W as c,ie as i,O as l,oe as n,Y as o,q as r,X as s,Te as t,k as u};
|
|
2
|
+
//# sourceMappingURL=ProviderLoginDialog-D3mqdAlo.js.map
|