@lsamien/yaovi 0.1.0 → 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.
Files changed (64) hide show
  1. package/build-info.json +44 -3
  2. package/desktop/main.mjs +18 -9
  3. package/dist/assets/{AboutDialog-BW25cB-W.js → AboutDialog-CJ9tCjSs.js} +2 -2
  4. package/dist/assets/AboutDialog-CJ9tCjSs.js.map +1 -0
  5. package/dist/assets/{BotAutomationsView-BoK2R918.js → BotAutomationsView-CsoI3js3.js} +2 -2
  6. package/dist/assets/{BotAutomationsView-BoK2R918.js.map → BotAutomationsView-CsoI3js3.js.map} +1 -1
  7. package/dist/assets/{BotControlSettingsPanel-BbhD2eC0.js → BotControlSettingsPanel-DIuBZlLu.js} +2 -2
  8. package/dist/assets/{BotControlSettingsPanel-BbhD2eC0.js.map → BotControlSettingsPanel-DIuBZlLu.js.map} +1 -1
  9. package/dist/assets/{BotPluginsDialog-DjwFw-lt.js → BotPluginsDialog-BsGszDMH.js} +2 -2
  10. package/dist/assets/{BotPluginsDialog-DjwFw-lt.js.map → BotPluginsDialog-BsGszDMH.js.map} +1 -1
  11. package/dist/assets/{BotSettingsPanel-DLmnL6pH.js → BotSettingsPanel-j3ThYgt2.js} +3 -3
  12. package/dist/assets/{BotSettingsPanel-DLmnL6pH.js.map → BotSettingsPanel-j3ThYgt2.js.map} +1 -1
  13. package/dist/assets/{ComputerPanel-CRIF1YEd.js → ComputerPanel-DdlgOm-B.js} +2 -2
  14. package/dist/assets/{ComputerPanel-CRIF1YEd.js.map → ComputerPanel-DdlgOm-B.js.map} +1 -1
  15. package/dist/assets/{ComputerViewerView-CizYWH2R.js → ComputerViewerView-I3adPI6O.js} +2 -2
  16. package/dist/assets/{ComputerViewerView-CizYWH2R.js.map → ComputerViewerView-I3adPI6O.js.map} +1 -1
  17. package/dist/assets/ConversationsView-CBhzg-9w.css +1 -0
  18. package/dist/assets/{ConversationsView-1igFR9VY.js → ConversationsView-CxVFk5sQ.js} +73 -73
  19. package/dist/assets/ConversationsView-CxVFk5sQ.js.map +1 -0
  20. package/dist/assets/{DevicePairingDialog-orOS4xqu.js → DevicePairingDialog-C307ARt-.js} +2 -2
  21. package/dist/assets/{DevicePairingDialog-orOS4xqu.js.map → DevicePairingDialog-C307ARt-.js.map} +1 -1
  22. package/dist/assets/{LocalVmSettingsPanel-DzM--Lpg.js → LocalVmSettingsPanel-Zs7CiXdf.js} +2 -2
  23. package/dist/assets/{LocalVmSettingsPanel-DzM--Lpg.js.map → LocalVmSettingsPanel-Zs7CiXdf.js.map} +1 -1
  24. package/dist/assets/{ProviderLoginDialog-Bw5KVMfi.js → ProviderLoginDialog-D3mqdAlo.js} +2 -2
  25. package/dist/assets/{ProviderLoginDialog-Bw5KVMfi.js.map → ProviderLoginDialog-D3mqdAlo.js.map} +1 -1
  26. package/dist/assets/{RunnerSettingsPanel-B-fwuBEs.js → RunnerSettingsPanel-DjMGnXGP.js} +2 -2
  27. package/dist/assets/{RunnerSettingsPanel-B-fwuBEs.js.map → RunnerSettingsPanel-DjMGnXGP.js.map} +1 -1
  28. package/dist/assets/{StandaloneDialog-7GkhtRlT.js → StandaloneDialog-BXCK22Dx.js} +2 -2
  29. package/dist/assets/{StandaloneDialog-7GkhtRlT.js.map → StandaloneDialog-BXCK22Dx.js.map} +1 -1
  30. package/dist/assets/{UpdateCheckDialog-DYiM4xBv.js → UpdateCheckDialog-DAY0VEQm.js} +2 -2
  31. package/dist/assets/{UpdateCheckDialog-DYiM4xBv.js.map → UpdateCheckDialog-DAY0VEQm.js.map} +1 -1
  32. package/dist/assets/{UsageView-D1cjS1hy.js → UsageView-DNwG8LAx.js} +2 -2
  33. package/dist/assets/{UsageView-D1cjS1hy.js.map → UsageView-DNwG8LAx.js.map} +1 -1
  34. package/dist/assets/{WorkspaceKnowledgePanel-DcGdLRS1.js → WorkspaceKnowledgePanel-hy_IkIJI.js} +2 -2
  35. package/dist/assets/{WorkspaceKnowledgePanel-DcGdLRS1.js.map → WorkspaceKnowledgePanel-hy_IkIJI.js.map} +1 -1
  36. package/dist/assets/{WorkspaceRoutinesPanel-DLntimKs.js → WorkspaceRoutinesPanel-B7PkBP7x.js} +2 -2
  37. package/dist/assets/{WorkspaceRoutinesPanel-DLntimKs.js.map → WorkspaceRoutinesPanel-B7PkBP7x.js.map} +1 -1
  38. package/dist/assets/{WorkstreamsView-DMoeV8Eu.js → WorkstreamsView-BeE2TRXK.js} +2 -2
  39. package/dist/assets/{WorkstreamsView-DMoeV8Eu.js.map → WorkstreamsView-BeE2TRXK.js.map} +1 -1
  40. package/dist/assets/{index-D0pMYnOi.js → index-D11cgXI9.js} +3 -3
  41. package/dist/assets/{index-D0pMYnOi.js.map → index-D11cgXI9.js.map} +1 -1
  42. package/dist/assets/{pdf-BCisgslg.js → pdf-BjTcV6As.js} +2 -2
  43. package/dist/assets/{pdf-BCisgslg.js.map → pdf-BjTcV6As.js.map} +1 -1
  44. package/dist/index.html +1 -1
  45. package/dist-server/server/botControl.js +13 -1
  46. package/dist-server/server/desktopEnvironments.js +10 -9
  47. package/dist-server/server/desktopHostProtocol.js +1 -1
  48. package/dist-server/server/harness/agentRuntime.js +22 -8
  49. package/dist-server/server/workspaceDialoguePolicy.js +3 -1
  50. package/dist-server/server/workspaceKnowledge.js +25 -16
  51. package/dist-server/server/workspaceMemoryContext.js +75 -19
  52. package/dist-server/server/workspacePrompt.js +11 -4
  53. package/dist-server/server/workspaceRuntime.js +35 -13
  54. package/dist-server/shared/botEnvironment.js +10 -4
  55. package/dist-server/shared/fileTransferEndpoint.mjs +5 -5
  56. package/dist-server/shared/hostPaths.mjs +14 -4
  57. package/package.json +4 -3
  58. package/release.json +3 -3
  59. package/runtime/hermes/hermes_state_messages.py +17 -0
  60. package/runtime/worker.py +34 -1
  61. package/dist/assets/AboutDialog-BW25cB-W.js.map +0 -1
  62. package/dist/assets/ConversationsView-1igFR9VY.js.map +0 -1
  63. package/dist/assets/ConversationsView-h94_iPtv.css +0 -1
  64. package/dist-server/server/harness/context.js +0 -88
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderLoginDialog-Bw5KVMfi.js","names":[],"sources":["../../src/client/api/modelNetwork.ts","../../src/client/components/app/ModelNetworkChoice.vue","../../src/client/components/app/ModelNetworkChoice.vue","../../src/client/api/hermes.ts","../../src/client/utils/clipboard.ts","../../src/client/components/app/ProviderLoginDialog.vue","../../src/client/components/app/ProviderLoginDialog.vue"],"sourcesContent":["import { apiRequest } from './client'\nimport type { ModelNetworkView, NetworkPolicy, NetworkTest, ModelProxy } from '@shared/modelNetwork'\nexport type ProxyDraft = Omit<ModelProxy, 'id' | 'hasPassword'> & { id?: string; password?: string; clearPassword?: boolean }\nconst base = '/api/app/admin/hermes/network'\nconst q = (bot?: string) => bot ? `?bot=${encodeURIComponent(bot)}` : ''\nexport const modelNetworkApi = {\n get: (bot?: string) => apiRequest<ModelNetworkView>(base + q(bot)),\n save: (revision: number, policy: NetworkPolicy, provider?: string, bot?: string) => apiRequest<ModelNetworkView>(`${base}/${provider ? 'connections/' + encodeURIComponent(provider) : 'default'}${q(bot)}`, { method: 'PUT', body: { revision, policy } }),\n proxy: (revision: number, proxy: ProxyDraft) => apiRequest<ModelNetworkView>(base + '/proxies', { method: 'POST', body: { revision, proxy } }),\n remove: (revision: number, id: string) => apiRequest<ModelNetworkView>(base + '/proxies/' + encodeURIComponent(id), { method: 'DELETE', body: { revision } }),\n test: (input: { provider?: string; policy?: NetworkPolicy; draft?: ProxyDraft }, bot?: string) => apiRequest<NetworkTest>(base + '/test' + q(bot), { method: 'POST', body: input }),\n}\nexport function networkLabel(view: ModelNetworkView, policy: NetworkPolicy): string {\n if (policy.mode === 'inherit') return '跟随默认 · ' + networkLabel(view, view.default)\n if (policy.mode === 'environment') return view.environmentDetected ? '服务器环境代理' : '服务器环境(未检测到代理)'\n if (policy.mode === 'direct') return '直接连接'\n return view.proxies.find(p => p.id === policy.proxyId)?.name ?? '代理已不可用'\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch, useId } from 'vue'\nimport { modelNetworkApi, networkLabel } from '@/api/modelNetwork'\nimport type { ModelNetworkView, NetworkPolicy, NetworkTest } from '@shared/modelNetwork'\nconst props = defineProps<{ view: ModelNetworkView; modelValue: NetworkPolicy; provider?: string; bot?: string; disabled?: boolean; compact?: boolean; global?: boolean; summaryOnly?: boolean; hideTest?:boolean }>()\nconst emit = defineEmits<{ 'update:modelValue': [value: NetworkPolicy]; manage: []; busy: [value: boolean] }>()\nconst radioName=useId()\nconst testing = ref(false), result = ref<NetworkTest>(), error = ref('')\nconst summary = computed(() => networkLabel(props.view, props.modelValue))\nconst selected = computed(() => props.view.proxies.find(p => p.id === props.modelValue.proxyId))\nconst modes = computed(() => props.global ? [{ id: 'environment', name: '沿用服务器环境', hint: props.view.environmentDetected ? '已检测到环境代理,保留当前网络行为' : '未检测到环境代理' }, { id: 'direct', name: '直接连接', hint: '不使用代理' }, { id: 'proxy', name: '指定代理', hint: '通过已保存的代理连接' }] : [{ id: 'inherit', name: '跟随服务器默认', hint: networkLabel(props.view, props.view.default) }, { id: 'direct', name: '直接连接', hint: '此连接不使用代理' }, { id: 'proxy', name: '指定代理', hint: '仅此连接使用所选代理' }])\nfunction change(mode: string) { emit('update:modelValue', mode === 'proxy' ? { mode, proxyId: props.modelValue.proxyId ?? props.view.proxies[0]?.id } : { mode } as NetworkPolicy) }\nwatch(() => JSON.stringify([props.modelValue, props.view.revision]), () => { result.value = undefined; error.value = '' })\nasync function test() {\n if (testing.value) return\n testing.value = true; emit('busy', true); error.value = ''; result.value = undefined\n try { result.value = await modelNetworkApi.test({ provider: props.provider, policy: props.modelValue.mode === 'environment' ? undefined : props.modelValue }, props.bot) }\n catch (cause) { error.value = cause instanceof Error ? cause.message : '测试失败,请重试。' }\n finally { testing.value = false; emit('busy', false) }\n}\ndefineExpose({ summary, test })\n</script>\n<template>\n <div class=\"network-choice\" :class=\"{'global-choice':global}\">\n <label v-if=\"compact && !summaryOnly\" class=\"compact-choice\">{{ global?'默认连接方式':'连接网络' }}<select :value=\"modelValue.mode\" :disabled=\"disabled || testing\" @change=\"change(($event.target as HTMLSelectElement).value)\"><option v-for=\"mode in modes\" :key=\"mode.id\" :value=\"mode.id\">{{ mode.name }}</option></select></label>\n <fieldset v-else-if=\"!summaryOnly\" :disabled=\"disabled || testing\"><legend>{{ global ? '默认连接方式' : '连接方式' }}</legend><label v-for=\"mode in modes\" :key=\"mode.id\" class=\"network-option\" :class=\"{ selected: modelValue.mode === mode.id }\"><input type=\"radio\" :name=\"radioName\" :checked=\"modelValue.mode === mode.id\" :value=\"mode.id\" @change=\"change(mode.id)\" /><span><strong>{{ mode.name }}</strong><small>{{ mode.hint }}</small></span></label></fieldset>\n <div v-if=\"modelValue.mode === 'proxy' && !summaryOnly\" class=\"proxy-select\"><label><span v-if=\"compact || global\">使用代理</span><select aria-label=\"使用代理\" :value=\"modelValue.proxyId ?? ''\" :disabled=\"disabled || testing\" @change=\"emit('update:modelValue', {mode:'proxy', proxyId:($event.target as HTMLSelectElement).value})\"><option value=\"\" disabled>请选择代理</option><option v-for=\"p in view.proxies\" :key=\"p.id\" :value=\"p.id\">{{ p.name }}</option></select></label><small v-if=\"selected\">{{ selected.protocol.toUpperCase() }} · {{ selected.host }}:{{ selected.port }}</small><p v-if=\"!view.proxies.length\">尚未添加代理。请先在「环境与权限 → 模型网络」添加。</p></div>\n <p v-if=\"compact && !global && !summaryOnly\" class=\"summary\">{{ summary }}</p>\n <div v-if=\"!global && !hideTest\" class=\"network-diagnostics\"><div v-if=\"result\" class=\"test-result\" :class=\"{failed:result.proxy==='failed'||result.target==='failed'}\" role=\"status\"><strong>代理:{{ {passed:'端口可达',failed:'连接失败',skipped:'未检测'}[result.proxy] }}<template v-if=\"provider\"> · 目标服务:{{ {passed:'可达',failed:'不可达',skipped:'尚未检测'}[result.target] }}</template></strong><small>{{ result.message }}</small></div><div v-else><strong>连接测试</strong><small>由运行 Yaovi 的服务器发起</small></div><button type=\"button\" :disabled=\"disabled || testing || (modelValue.mode === 'proxy' && !selected)\" @click=\"test\">{{ testing ? '正在测试…' : '测试当前选择' }}</button></div>\n <p v-if=\"error\" role=\"alert\">{{ error }}</p>\n </div>\n</template>\n<style scoped>\n.network-choice:has(fieldset) .proxy-select{margin-left:36px;max-width:530px}\n.global-choice .compact-choice,.global-choice .proxy-select>label{display:grid;grid-template-columns:minmax(0,1fr) minmax(200px,460px);align-items:center;gap:24px}.global-choice .proxy-select>small{margin-left:auto}@media(max-width:600px){.global-choice .compact-choice,.global-choice .proxy-select>label{grid-template-columns:1fr;gap:8px}}\n.network-choice{display:grid;gap:8px;min-width:0;font-size:14px}.network-choice fieldset{border:0;padding:0;margin:0;display:grid;gap:0;min-width:0}.network-choice legend{font-weight:600;margin-bottom:12px}.network-option{display:flex;align-items:center;gap:12px;border:0;border-bottom:1px solid var(--line);border-radius:0;padding:8px 0;cursor:pointer}.network-option.selected{background:transparent}.network-option input{width:16px;height:16px;accent-color:var(--accent)}.network-option span,.network-diagnostics>div{display:grid;gap:4px}.network-option strong{font-weight:550}.network-choice small,.summary{color:var(--text-secondary);line-height:1.6}.network-choice p{margin:0}.proxy-select,.proxy-select label,.compact-choice{display:grid;gap:8px}.network-choice select,.network-choice button{font:inherit;min-height:44px;border:1px solid var(--line);border-radius:8px;padding:10px 12px;background:var(--surface);color:var(--text-primary);max-width:100%;box-sizing:border-box}.network-choice button{cursor:pointer}.network-diagnostics{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--line);padding-top:16px}.test-result{display:grid;gap:5px}.test-result strong{font-size:13px;color:var(--success)}.test-result.failed strong{color:var(--danger)}.network-choice [role=alert]{color:var(--danger)}.network-choice :focus-visible{outline:2px solid var(--accent);outline-offset:3px}.network-choice :disabled{opacity:.55}@media(max-width:420px){.network-diagnostics{align-items:flex-start;flex-direction:column}}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch, useId } from 'vue'\nimport { modelNetworkApi, networkLabel } from '@/api/modelNetwork'\nimport type { ModelNetworkView, NetworkPolicy, NetworkTest } from '@shared/modelNetwork'\nconst props = defineProps<{ view: ModelNetworkView; modelValue: NetworkPolicy; provider?: string; bot?: string; disabled?: boolean; compact?: boolean; global?: boolean; summaryOnly?: boolean; hideTest?:boolean }>()\nconst emit = defineEmits<{ 'update:modelValue': [value: NetworkPolicy]; manage: []; busy: [value: boolean] }>()\nconst radioName=useId()\nconst testing = ref(false), result = ref<NetworkTest>(), error = ref('')\nconst summary = computed(() => networkLabel(props.view, props.modelValue))\nconst selected = computed(() => props.view.proxies.find(p => p.id === props.modelValue.proxyId))\nconst modes = computed(() => props.global ? [{ id: 'environment', name: '沿用服务器环境', hint: props.view.environmentDetected ? '已检测到环境代理,保留当前网络行为' : '未检测到环境代理' }, { id: 'direct', name: '直接连接', hint: '不使用代理' }, { id: 'proxy', name: '指定代理', hint: '通过已保存的代理连接' }] : [{ id: 'inherit', name: '跟随服务器默认', hint: networkLabel(props.view, props.view.default) }, { id: 'direct', name: '直接连接', hint: '此连接不使用代理' }, { id: 'proxy', name: '指定代理', hint: '仅此连接使用所选代理' }])\nfunction change(mode: string) { emit('update:modelValue', mode === 'proxy' ? { mode, proxyId: props.modelValue.proxyId ?? props.view.proxies[0]?.id } : { mode } as NetworkPolicy) }\nwatch(() => JSON.stringify([props.modelValue, props.view.revision]), () => { result.value = undefined; error.value = '' })\nasync function test() {\n if (testing.value) return\n testing.value = true; emit('busy', true); error.value = ''; result.value = undefined\n try { result.value = await modelNetworkApi.test({ provider: props.provider, policy: props.modelValue.mode === 'environment' ? undefined : props.modelValue }, props.bot) }\n catch (cause) { error.value = cause instanceof Error ? cause.message : '测试失败,请重试。' }\n finally { testing.value = false; emit('busy', false) }\n}\ndefineExpose({ summary, test })\n</script>\n<template>\n <div class=\"network-choice\" :class=\"{'global-choice':global}\">\n <label v-if=\"compact && !summaryOnly\" class=\"compact-choice\">{{ global?'默认连接方式':'连接网络' }}<select :value=\"modelValue.mode\" :disabled=\"disabled || testing\" @change=\"change(($event.target as HTMLSelectElement).value)\"><option v-for=\"mode in modes\" :key=\"mode.id\" :value=\"mode.id\">{{ mode.name }}</option></select></label>\n <fieldset v-else-if=\"!summaryOnly\" :disabled=\"disabled || testing\"><legend>{{ global ? '默认连接方式' : '连接方式' }}</legend><label v-for=\"mode in modes\" :key=\"mode.id\" class=\"network-option\" :class=\"{ selected: modelValue.mode === mode.id }\"><input type=\"radio\" :name=\"radioName\" :checked=\"modelValue.mode === mode.id\" :value=\"mode.id\" @change=\"change(mode.id)\" /><span><strong>{{ mode.name }}</strong><small>{{ mode.hint }}</small></span></label></fieldset>\n <div v-if=\"modelValue.mode === 'proxy' && !summaryOnly\" class=\"proxy-select\"><label><span v-if=\"compact || global\">使用代理</span><select aria-label=\"使用代理\" :value=\"modelValue.proxyId ?? ''\" :disabled=\"disabled || testing\" @change=\"emit('update:modelValue', {mode:'proxy', proxyId:($event.target as HTMLSelectElement).value})\"><option value=\"\" disabled>请选择代理</option><option v-for=\"p in view.proxies\" :key=\"p.id\" :value=\"p.id\">{{ p.name }}</option></select></label><small v-if=\"selected\">{{ selected.protocol.toUpperCase() }} · {{ selected.host }}:{{ selected.port }}</small><p v-if=\"!view.proxies.length\">尚未添加代理。请先在「环境与权限 → 模型网络」添加。</p></div>\n <p v-if=\"compact && !global && !summaryOnly\" class=\"summary\">{{ summary }}</p>\n <div v-if=\"!global && !hideTest\" class=\"network-diagnostics\"><div v-if=\"result\" class=\"test-result\" :class=\"{failed:result.proxy==='failed'||result.target==='failed'}\" role=\"status\"><strong>代理:{{ {passed:'端口可达',failed:'连接失败',skipped:'未检测'}[result.proxy] }}<template v-if=\"provider\"> · 目标服务:{{ {passed:'可达',failed:'不可达',skipped:'尚未检测'}[result.target] }}</template></strong><small>{{ result.message }}</small></div><div v-else><strong>连接测试</strong><small>由运行 Yaovi 的服务器发起</small></div><button type=\"button\" :disabled=\"disabled || testing || (modelValue.mode === 'proxy' && !selected)\" @click=\"test\">{{ testing ? '正在测试…' : '测试当前选择' }}</button></div>\n <p v-if=\"error\" role=\"alert\">{{ error }}</p>\n </div>\n</template>\n<style scoped>\n.network-choice:has(fieldset) .proxy-select{margin-left:36px;max-width:530px}\n.global-choice .compact-choice,.global-choice .proxy-select>label{display:grid;grid-template-columns:minmax(0,1fr) minmax(200px,460px);align-items:center;gap:24px}.global-choice .proxy-select>small{margin-left:auto}@media(max-width:600px){.global-choice .compact-choice,.global-choice .proxy-select>label{grid-template-columns:1fr;gap:8px}}\n.network-choice{display:grid;gap:8px;min-width:0;font-size:14px}.network-choice fieldset{border:0;padding:0;margin:0;display:grid;gap:0;min-width:0}.network-choice legend{font-weight:600;margin-bottom:12px}.network-option{display:flex;align-items:center;gap:12px;border:0;border-bottom:1px solid var(--line);border-radius:0;padding:8px 0;cursor:pointer}.network-option.selected{background:transparent}.network-option input{width:16px;height:16px;accent-color:var(--accent)}.network-option span,.network-diagnostics>div{display:grid;gap:4px}.network-option strong{font-weight:550}.network-choice small,.summary{color:var(--text-secondary);line-height:1.6}.network-choice p{margin:0}.proxy-select,.proxy-select label,.compact-choice{display:grid;gap:8px}.network-choice select,.network-choice button{font:inherit;min-height:44px;border:1px solid var(--line);border-radius:8px;padding:10px 12px;background:var(--surface);color:var(--text-primary);max-width:100%;box-sizing:border-box}.network-choice button{cursor:pointer}.network-diagnostics{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--line);padding-top:16px}.test-result{display:grid;gap:5px}.test-result strong{font-size:13px;color:var(--success)}.test-result.failed strong{color:var(--danger)}.network-choice [role=alert]{color:var(--danger)}.network-choice :focus-visible{outline:2px solid var(--accent);outline-offset:3px}.network-choice :disabled{opacity:.55}@media(max-width:420px){.network-diagnostics{align-items:flex-start;flex-direction:column}}\n</style>\n","import type { MessagingChannelPlatform } from '@shared/channelPlatforms'\nimport { apiRequest } from './client'\nimport type { BotAutoPool, BotModelOption, BotModelVisibilityView } from '@shared/botModelSettings'\nimport type { AutoRouteDecision } from '@shared/autoRouting'\n\n/** Client for /api/app/admin/hermes: Yaovi's front-end over Hermes's own provider management. */\nconst base = '/api/app/admin/hermes'\nconst q = (bot?: string, extra = '') => {\n const params = new URLSearchParams(extra)\n if (bot) params.set('bot', bot)\n const s = params.toString()\n return s ? `?${s}` : ''\n}\nexport type HermesProvider = { id: string; label: string; authType: string; transport: string; aggregator: boolean; signupUrl: string; envVars: string[]; aliases: string[] }\nexport type HermesOAuthProvider = { id: string; name: string; flow: 'device_code' | 'external' | string; web_flow?: 'device_code' | 'process' | 'paste_token'; cli_command: string; docs_url: string; disconnectable: boolean; disconnect_hint: string | null; status: { logged_in: boolean; token_preview?: string; source_label?: string; expires_at?: string | null; error?: string } }\nexport type HermesDeviceStart = { session_id: string; flow: string; user_code: string; verification_url: string; expires_in: number; poll_interval: number }\nexport type HermesEnvVar = { is_set: boolean; redacted_value: string | null; description: string; category: string; is_password: boolean; provider: string; provider_label: string; advanced?: boolean; custom?: boolean; channel_managed?: boolean; readonly?: boolean }\nexport type HermesPoolEntry = { index: number; id: string; label: string; authType: string; source: string; priority: number; baseUrl: string | null; lastStatus: string | null; lastError: string | null; tokenPreview: string }\nexport type HermesModelOptions = { providers: Array<{ slug: string; name: string; models: string[]; authenticated?: boolean; is_current?: boolean; is_user_defined?: boolean }>; model?: string; provider?: string }\nexport type HermesAuxRow = { task: string; provider: string; model: string; base_url: string; reasoning_effort: string | null; label?: string; hint?: string }\nexport type HermesConfigSections = { model?: { provider?: string; default?: string; base_url?: string }; fallback_model?: Array<{ provider: string; model: string }> | { provider: string; model: string }; credential_pool_strategies?: Record<string, string>; auxiliary?: Record<string, Record<string, unknown>> }\nexport type HermesEndpoint = { id: string; name: string; base_url: string; model: string; api_mode?: string; has_api_key?: boolean; is_default?: boolean }\n\nexport type DecisionBackend = 'jev' | 'jeff'\nexport type JevAutoView = BotAutoPool & {\n revision: number\n enabled: boolean\n hasApiKey: boolean\n decisionReady: boolean\n backend: DecisionBackend\n jeffBaseUrl: string\n jeffModel: string\n modelOptions?: BotModelOption[]\n}\n\nexport const hermesApi = {\n providers: (bot?: string) => apiRequest<{ providers: HermesProvider[] }>(`${base}/providers${q(bot)}`),\n config: (bot?: string) => apiRequest<{ config: HermesConfigSections }>(`${base}/config${q(bot)}`),\n patchConfig: (config: Record<string, unknown>, bot?: string) => apiRequest<{ config: HermesConfigSections }>(`${base}/config${q(bot)}`, { method: 'PATCH', body: { config } as any }),\n models: (refresh = false, bot?: string) => apiRequest<HermesModelOptions>(`${base}/models${q(bot, refresh ? 'refresh=1' : '')}`),\n jev: () => apiRequest<JevAutoView>(`${base}/jev`),\n botModels: () => apiRequest<BotModelVisibilityView>(`${base}/bot-models`),\n saveBotModels: (body: BotModelVisibilityView) => apiRequest<BotModelVisibilityView>(`${base}/bot-models`, { method: 'PUT', body: body as any }),\n previewJev: (body: { pool: BotAutoPool; enabled: boolean; userText: string; apiKey?: string; backend?: DecisionBackend; jeffBaseUrl?: string; jeffModel?: string }) => apiRequest<AutoRouteDecision>(`${base}/jev/preview`, { method: 'POST', body: body as any }),\n saveJev: (body: BotAutoPool & { revision: number; enabled: boolean; apiKey?: string; clearApiKey?: boolean; backend?: DecisionBackend; jeffBaseUrl?: string; jeffModel?: string }) =>\n apiRequest<JevAutoView>(`${base}/jev`, { method: 'PUT', body: body as any }),\n setModel: (body: { scope: 'main' | 'auxiliary'; provider: string; model: string; task?: string; reasoning_effort?: string | null; base_url?: string; api_key?: string; confirm_expensive_model?: boolean }, bot?: string) =>\n apiRequest<{ ok?: boolean; confirm_required?: boolean; confirm_message?: string }>(`${base}/model${q(bot)}`, { method: 'POST', body: body as any }),\n auxiliary: (bot?: string) => apiRequest<{ tasks: HermesAuxRow[]; main: { provider: string; model: string } }>(`${base}/auxiliary${q(bot)}`),\n oauth: (bot?: string) => apiRequest<{ providers: HermesOAuthProvider[] }>(`${base}/oauth${q(bot)}`),\n oauthStart: (provider: string, bot?: string, networkRevision?: number) => apiRequest<HermesDeviceStart>(`${base}/oauth/${encodeURIComponent(provider)}/start${q(bot)}`, { method: 'POST', body: networkRevision === undefined ? {} : { networkRevision } }),\n oauthPoll: (provider: string, session: string, bot?: string) => apiRequest<{ status: string; error_message?: string | null; user_code?: string; verification_url?: string }>(`${base}/oauth/${encodeURIComponent(provider)}/poll/${encodeURIComponent(session)}${q(bot)}`),\n oauthCancel: (session: string, bot?: string) => apiRequest(`${base}/oauth/sessions/${encodeURIComponent(session)}${q(bot)}`, { method: 'DELETE' }),\n oauthSubmit: (provider: string, token: string, bot?: string) => apiRequest<{ ok: boolean; env_var: string }>(`${base}/oauth/${encodeURIComponent(provider)}/token${q(bot)}`, { method: 'POST', body: { token } }),\n oauthDisconnect: (provider: string, bot?: string) => apiRequest(`${base}/oauth/${encodeURIComponent(provider)}${q(bot)}`, { method: 'DELETE' }),\n env: (bot?: string) => apiRequest<Record<string, HermesEnvVar>>(`${base}/env${q(bot)}`),\n setEnv: (key: string, value: string, bot?: string) => apiRequest(`${base}/env${q(bot)}`, { method: 'PUT', body: { key, value, provider_setup: true } }),\n deleteEnv: (key: string, bot?: string) => apiRequest(`${base}/env${q(bot)}`, { method: 'DELETE', body: { key } }),\n validate: (key: string, value: string) => apiRequest<{ ok: boolean; reachable: boolean; message: string }>(`${base}/validate`, { method: 'POST', body: { key, value } }),\n endpoints: (bot?: string) => apiRequest<{ endpoints?: HermesEndpoint[] } & Record<string, unknown>>(`${base}/endpoints${q(bot)}`),\n saveEndpoint: (body: Record<string, unknown>, bot?: string) => apiRequest(`${base}/endpoints${q(bot)}`, { method: 'POST', body: body as any }),\n deleteEndpoint: (id: string, bot?: string) => apiRequest(`${base}/endpoints/${encodeURIComponent(id)}${q(bot)}`, { method: 'DELETE' }),\n pool: (provider: string, bot?: string) => apiRequest<{ provider: string; strategy: string; entries: HermesPoolEntry[] }>(`${base}/pool/${encodeURIComponent(provider)}${q(bot)}`),\n addPool: (provider: string, apiKey: string, label: string, bot?: string) => apiRequest(`${base}/pool/${encodeURIComponent(provider)}${q(bot)}`, { method: 'POST', body: { apiKey, ...(label ? { label } : {}) } }),\n removePool: (provider: string, index: number, bot?: string) => apiRequest(`${base}/pool/${encodeURIComponent(provider)}/${index}${q(bot)}`, { method: 'DELETE' }),\n}\n\nexport type HermesMediaToolset = 'image_gen' | 'video_gen'\nexport type HermesMediaProvider = {\n name: string; badge: string; tag: string; is_active: boolean; status: string; requires_nous_auth: boolean\n env_vars: Array<{ key: string; prompt: string; url?: string; default?: string; is_set: boolean }>\n}\nexport type HermesMediaConfig = { name: string; has_category: boolean; providers: HermesMediaProvider[]; active_provider: string | null }\nexport type HermesMediaModels = {\n name: string; has_models: boolean; provider?: string; plugin?: string; current: string | null; default: string | null\n models: Array<{ id: string; display: string; speed?: string; strengths?: string; price?: string }>\n}\nconst media = (name: HermesMediaToolset) => `${base}/media/${name}`\nexport const hermesMediaApi = {\n config: (name: HermesMediaToolset) => apiRequest<HermesMediaConfig>(`${media(name)}/config`),\n models: (name: HermesMediaToolset, provider?: string) => apiRequest<HermesMediaModels>(`${media(name)}/models${provider ? `?provider=${encodeURIComponent(provider)}` : ''}`),\n setProvider: (name: HermesMediaToolset, provider: string) => apiRequest<{ ok: boolean; needs_nous_auth?: boolean }>(`${media(name)}/provider`, { method: 'PUT', body: { provider } }),\n setModel: (name: HermesMediaToolset, model: string, provider: string) => apiRequest(`${media(name)}/model`, { method: 'PUT', body: { model, provider } }),\n setEnv: (name: HermesMediaToolset, env: Record<string, string>) => apiRequest(`${media(name)}/env`, { method: 'PUT', body: { env } }),\n}\n\nexport type HermesWebCapability = 'search' | 'extract'\nexport type HermesWebConfig = {\n providers: Array<HermesMediaProvider & { web_backend?: string; managed_nous_feature?: string; capabilities?: HermesWebCapability[] }>\n selection: Record<HermesWebCapability, string | null>\n active_search_backend: string | null; active_extract_backend: string | null\n}\nexport const hermesWebApi = {\n config: () => apiRequest<HermesWebConfig>(`${base}/web`),\n setProvider: (capability: HermesWebCapability, provider: string | null) => apiRequest<HermesWebConfig>(`${base}/web/provider`, { method: 'PUT', body: { capability, provider } }),\n setEnv: (env: Record<string, string>) => apiRequest<HermesWebConfig>(`${base}/web/env`, { method: 'PUT', body: { env } }),\n test: (capability: HermesWebCapability, value: string) => apiRequest<{ ok: boolean; provider: string | null; result: Record<string, any> }>(`${base}/web/test`, { method: 'POST', body: { capability, ...(capability === 'search' ? { query: value } : { url: value }) } }),\n}\n\n/** Hermes `mcp_servers` (system layer, or a Bot's override layer with `bot`). Secrets come back as key names only. */\nexport type HermesMcpServer = {\n name: string; transport: 'stdio' | 'http' | 'unknown'; url: string | null; command: string | null; args: string[]\n auth: string | null; enabled: boolean; env_keys: string[]; header_keys: string[]; yaovi_location: 'server' | 'virtual'\n include: string[] | null; exclude: string[] | null\n}\nexport type HermesMcpInput = {\n transport: 'stdio' | 'http'; command?: string; args?: string[]; url?: string; env?: Record<string, string | true>; headers?: Record<string, string | true>\n auth?: 'none' | 'oauth'; yaovi_location?: 'server' | 'virtual'; include?: string[] | null; exclude?: string[] | null; enabled?: boolean\n}\nexport type HermesMcpProbe = { ok: boolean; error?: string; tools: Array<{ name: string; description: string }> }\nexport type HermesMcpFlow = { flow_id: string; server_name: string; status: string; authorization_url: string | null; error: string | null }\nconst mcp = `${base}/mcp`\nexport const hermesMcpApi = {\n list: (bot?: string) => apiRequest<{ servers: HermesMcpServer[] }>(`${mcp}${q(bot)}`),\n create: (name: string, body: HermesMcpInput, bot?: string) => apiRequest<HermesMcpServer>(`${mcp}${q(bot)}`, { method: 'POST', body: { name, ...body } as any }),\n update: (name: string, body: HermesMcpInput, bot?: string) => apiRequest<HermesMcpServer>(`${mcp}/${encodeURIComponent(name)}${q(bot)}`, { method: 'PUT', body: body as any }),\n remove: (name: string, bot?: string) => apiRequest(`${mcp}/${encodeURIComponent(name)}${q(bot)}`, { method: 'DELETE' }),\n setEnabled: (name: string, enabled: boolean, bot?: string) => apiRequest(`${mcp}/${encodeURIComponent(name)}/enabled${q(bot)}`, { method: 'PATCH', body: { enabled } }),\n test: (name: string, bot?: string) => apiRequest<HermesMcpProbe>(`${mcp}/${encodeURIComponent(name)}/test${q(bot)}`, { method: 'POST' }),\n oauthStart: (name: string, bot?: string) => apiRequest<HermesMcpFlow>(`${mcp}/${encodeURIComponent(name)}/oauth/start${q(bot)}`, { method: 'POST' }),\n oauthStatus: (flow: string, bot?: string) => apiRequest<HermesMcpFlow & { tools?: unknown }>(`${mcp}/oauth/${encodeURIComponent(flow)}${q(bot)}`),\n oauthCancel: (flow: string, bot?: string) => apiRequest(`${mcp}/oauth/${encodeURIComponent(flow)}${q(bot)}`, { method: 'DELETE' }),\n}\n\nexport type HermesChannelField = { key: string; label: string; secret: boolean; required: boolean; configured: boolean; value: string | null }\nexport type HermesChannel = {\n platform: MessagingChannelPlatform; preparation?:{ready:boolean;missing:string[]}; label: string; configured: boolean; enabled: boolean; ready: boolean\n fields: HermesChannelField[]; home_channel: string; home_channel_name: string\n pending: Array<{ code: string; user_id: string; user_name: string; age_minutes?: number }>\n approved: Array<{ user_id: string; user_name: string }>\n}\nexport type HermesChannelRuntime = { running: boolean; platforms: string[]; status: Record<string, { state: string; error?: string; at: number }> }\nexport type HermesChannelInput = { enabled?: boolean; credentials?: Record<string, string | true>; home_channel?: string; home_channel_name?: string }\nconst channels = `${base}/channels`\nconst bq = (bot: string) => `?bot=${encodeURIComponent(bot)}`\nexport const hermesChannelsApi = {\n list: (bot: string) => apiRequest<{ platforms: HermesChannel[]; runtime: HermesChannelRuntime }>(`${channels}${bq(bot)}`),\n save: (bot: string, platform: string, body: HermesChannelInput) => apiRequest<HermesChannel>(`${channels}/${platform}${bq(bot)}`, { method: 'PUT', body: body as any }),\n setEnabled: (bot: string, platform: string, enabled: boolean) => apiRequest<HermesChannel>(`${channels}/${platform}/enabled${bq(bot)}`, { method: 'POST', body: { enabled } }),\n remove: (bot: string, platform: string) => apiRequest(`${channels}/${platform}${bq(bot)}`, { method: 'DELETE' }),\n test: (bot: string, platform: string) => apiRequest<{ ok: boolean; error?: string | null }>(`${channels}/${platform}/test${bq(bot)}`, { method: 'POST' }),\n approve: (bot: string, platform: string, code: string) => apiRequest<{ user_id: string; user_name: string }>(`${channels}/${platform}/pairing${bq(bot)}`, { method: 'POST', body: { code } }),\n revoke: (bot: string, platform: string, user: string) => apiRequest(`${channels}/${platform}/pairing/${encodeURIComponent(user)}${bq(bot)}`, { method: 'DELETE' }),\n}\n","function legacyCopyText(text: string): boolean {\n const textarea = document.createElement('textarea')\n const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null\n textarea.value = text\n textarea.setAttribute('readonly', '')\n textarea.setAttribute('aria-hidden', 'true')\n textarea.style.cssText = 'position:fixed;left:-9999px;top:0;width:1px;height:1px;opacity:0'\n document.body.appendChild(textarea)\n textarea.focus({ preventScroll: true })\n textarea.select()\n textarea.setSelectionRange(0, text.length)\n\n let copied = false\n try {\n copied = document.execCommand('copy')\n } catch {\n copied = false\n } finally {\n textarea.remove()\n activeElement?.focus({ preventScroll: true })\n }\n return copied\n}\n\n/** Copy text in secure browsers and in embedded/HTTP browsers with a legacy fallback. */\nexport async function copyTextToClipboard(text: string): Promise<boolean> {\n try {\n if (window.isSecureContext && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text)\n return true\n }\n } catch {\n // Permission-restricted embedded browsers can expose the API but reject it.\n }\n return legacyCopyText(text)\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport { hermesApi, type HermesDeviceStart, type HermesOAuthProvider } from '@/api/hermes'\nimport { copyTextToClipboard } from '@/utils/clipboard'\nimport ModelNetworkChoice from './ModelNetworkChoice.vue'\nimport { modelNetworkApi, networkLabel } from '@/api/modelNetwork'\nimport type { ModelNetworkView, NetworkPolicy } from '@shared/modelNetwork'\n\nconst props = defineProps<{ provider: HermesOAuthProvider; bot?: string }>()\nconst emit = defineEmits<{ close: []; success: [] }>()\n// Capture the scope for the lifetime of this login, including late cancellation.\nconst provider = props.provider, bot = props.bot\nconst webLogin = provider.flow === 'device_code' || ['device_code', 'process'].includes(provider.web_flow ?? '')\nconst tokenLogin = provider.web_flow === 'paste_token'\nconst state = ref<'starting' | 'pending' | 'failed' | 'approved' | 'ready'>('ready')\nconst session = ref<HermesDeviceStart>(), error = ref(''), token = ref(''), saving = ref(false), copyNotice = ref('')\nconst network = ref<ModelNetworkView>(), policy = ref<NetworkPolicy>({mode:'inherit'}), networkBusy = ref(false)\nconst networkDirty = computed(() => !!network.value && JSON.stringify(policy.value) !== JSON.stringify(network.value.connections[provider.id] ?? {mode:'inherit'}))\nconst canStart = computed(() => !!network.value && !networkBusy.value && (policy.value.mode !== 'proxy' || !!policy.value.proxyId))\nconst networkSummary = computed(() => network.value ? networkLabel(network.value, policy.value) : '正在读取网络…')\nasync function loadNetwork() {\n try { const value=await modelNetworkApi.get(bot);if(alive){network.value=value;policy.value={...value.connections[provider.id] ?? {mode:'inherit'}};error.value=''} }\n catch(cause) { if(alive)error.value=cause instanceof Error?cause.message:'无法读取网络配置,请重试。' }\n}\nasync function saveNetwork() {\n if(networkDirty.value){network.value=await modelNetworkApi.save(network.value!.revision,policy.value,provider.id,bot)}\n}\nasync function changeNetwork() {\n if(session.value && !window.confirm('更改网络将结束本次授权,需要重新获取验证码。继续?'))return\n generation++;clearTimeout(timer)\n try{await release(session.value);session.value=undefined;state.value='ready';error.value='';await loadNetwork()}\n catch(cause){state.value='failed';error.value=cause instanceof Error?cause.message:'无法取消本次授权,请重试。'}\n}\nlet alive = true, generation = 0, timer: ReturnType<typeof setTimeout> | undefined\nconst verificationUrl = computed(() => {\n try { const url = new URL(session.value?.verification_url ?? ''); return ['https:', 'http:'].includes(url.protocol) && !url.username && !url.password ? url.href : '' } catch { return '' }\n})\nconst message = computed(() => state.value === 'starting' ? '正在准备登录…' : state.value === 'approved' ? '登录成功' : verificationUrl.value ? '等待你在浏览器中完成授权…' : '正在准备授权链接…')\n\nasync function release(current?: HermesDeviceStart) {\n if (current) await hermesApi.oauthCancel(current.session_id, bot)\n}\nasync function start() {\n if (state.value === 'starting' || saving.value || !canStart.value) return\n const currentGeneration = ++generation\n clearTimeout(timer); error.value = ''; copyNotice.value = ''; state.value = 'starting'\n try {\n await release(session.value); session.value = undefined\n await saveNetwork()\n if (!alive || currentGeneration !== generation) return\n const next = await hermesApi.oauthStart(provider.id, bot, network.value!.revision)\n if (!alive || currentGeneration !== generation) { await release(next); return }\n session.value = next; state.value = 'pending'; poll(currentGeneration)\n } catch (cause) {\n if (!alive || currentGeneration !== generation) return\n error.value = cause instanceof Error ? cause.message : '无法开始登录,请重试。'; state.value = 'failed'\n }\n}\nfunction poll(currentGeneration: number) {\n const current = session.value\n if (!current || !alive) return\n timer = setTimeout(async () => {\n try {\n const result = await hermesApi.oauthPoll(provider.id, current.session_id, bot)\n if (!alive || currentGeneration !== generation) return\n if (result.user_code) current.user_code = result.user_code\n if (result.verification_url) current.verification_url = result.verification_url\n if (result.status === 'pending') { poll(currentGeneration); return }\n if (result.status === 'approved') { session.value = undefined; state.value = 'approved'; emit('success'); return }\n error.value = result.error_message || (result.status === 'expired' ? '授权已过期,请重新登录。' : result.status === 'denied' ? '账号已拒绝授权,可重新开始登录。' : '登录未完成,请重试。')\n state.value = 'failed'\n } catch (cause) {\n if (!alive || currentGeneration !== generation) return\n error.value = cause instanceof Error ? cause.message : '无法获取授权结果,请重试。'; state.value = 'failed'\n }\n }, Math.max(1, current.poll_interval || 2) * 1000)\n}\nasync function submitToken() {\n if (saving.value || !token.value.trim() || !canStart.value) return\n saving.value = true; error.value = ''\n try {\n await saveNetwork()\n await hermesApi.oauthSubmit(provider.id, token.value.trim(), bot)\n if (!alive) return\n token.value = ''; state.value = 'approved'; emit('success')\n } catch (cause) { if (alive) error.value = cause instanceof Error ? cause.message : '凭据保存失败,请重试。' }\n finally { saving.value = false }\n}\nfunction beforeClose() {\n return !saving.value && (!(token.value || networkDirty.value) || window.confirm('还有未保存的输入,放弃本次更改?'))\n}\nfunction close() { if (beforeClose()) emit('close') }\nasync function copyCode() {\n copyNotice.value = await copyTextToClipboard(session.value?.user_code ?? '') ? '验证码已复制' : '无法自动复制,请选中验证码手动复制。'\n}\nonMounted(loadNetwork)\nonBeforeUnmount(() => {\n alive = false; generation++; clearTimeout(timer)\n const current = session.value; session.value = undefined; token.value = ''\n void release(current).catch(() => { /* The server also expires abandoned sessions. */ })\n})\n</script>\n\n<template>\n <StandaloneDialog :title=\"`登录 ${provider.name}`\" compact wide :before-close=\"beforeClose\" @close=\"emit('close')\">\n <div class=\"provider-login\">\n <p class=\"scope\">{{ bot ? '仅对此 Bot 生效' : '服务器模型连接' }}</p>\n <template v-if=\"state === 'approved'\">\n <div class=\"login-success\" role=\"status\"><AppIcon name=\"check\" :size=\"24\" /><strong>登录成功</strong></div>\n <p>已完成 {{ provider.name }} 授权。</p>\n <button class=\"primary\" type=\"button\" @click=\"close\">完成</button>\n </template>\n <template v-else>\n <template v-if=\"state==='ready'\">\n <ModelNetworkChoice v-if=\"network\" v-model=\"policy\" :view=\"network\" :provider=\"provider.id\" :bot=\"bot\" compact :disabled=\"saving\" @busy=\"networkBusy=$event\" />\n <button v-else type=\"button\" @click=\"loadNetwork\">重新读取网络配置</button>\n </template>\n <div v-else class=\"network-summary\"><span>网络:{{ networkSummary }}</span><button v-if=\"state==='pending' || state==='failed'\" type=\"button\" @click=\"changeNetwork\">更改</button></div>\n <template v-if=\"state==='failed'\">\n <div class=\"failure-heading\" role=\"alert\"><AppIcon name=\"alert\" :size=\"24\" /><strong>{{ error }}</strong></div>\n <ModelNetworkChoice v-if=\"network\" v-model=\"policy\" :view=\"network\" :provider=\"provider.id\" :bot=\"bot\" summary-only @busy=\"networkBusy=$event\" />\n <p class=\"hint\">不会自动切换为直接连接。</p>\n <button type=\"button\" class=\"primary\" @click=\"changeNetwork\">检查网络设置</button>\n </template>\n <template v-if=\"webLogin\">\n <p v-if=\"state !== 'failed' && state !== 'ready'\" role=\"status\">{{ message }}</p>\n <button v-if=\"state==='ready'\" type=\"button\" class=\"primary\" :disabled=\"!canStart\" @click=\"start\">{{ networkDirty?'保存并开始登录':'开始登录' }}</button>\n <template v-if=\"state === 'pending'\">\n <div v-if=\"session?.user_code\" class=\"device-code\"><span>在授权页面输入验证码</span><strong>{{ session.user_code }}</strong><button type=\"button\" @click=\"copyCode\">复制验证码</button><small v-if=\"copyNotice\" role=\"status\">{{ copyNotice }}</small></div>\n <a v-if=\"verificationUrl\" class=\"primary\" :href=\"verificationUrl\" target=\"_blank\" rel=\"noopener noreferrer\"><AppIcon name=\"external\" :size=\"16\" />打开授权页面</a>\n <p class=\"hint\">{{ verificationUrl ? '在浏览器完成授权后,回到这里查看结果。' : '登录服务准备好后,授权入口会自动显示。' }}</p>\n </template>\n </template>\n <form v-else-if=\"tokenLogin\" @submit.prevent=\"submitToken\">\n <p>粘贴已有的 API Key 或登录令牌完成连接。</p>\n <label>登录凭据<input v-model=\"token\" :aria-label=\"`${provider.name} 凭据`\" type=\"password\" autocomplete=\"new-password\" :disabled=\"saving\" placeholder=\"粘贴登录凭据\" /></label>\n <details v-if=\"provider.cli_command\"><summary>如何获取登录令牌</summary><p>在服务器上按该服务的登录指引运行:</p><code>{{ provider.cli_command }}</code></details>\n <button type=\"submit\" class=\"primary\" :disabled=\"saving || !token.trim() || !canStart\">{{ saving ? '正在保存…' : networkDirty?'保存网络与凭据':'保存凭据' }}</button>\n </form>\n <template v-else><p>此服务需要先在服务器上完成登录:</p><code>{{ provider.cli_command }}</code><p class=\"hint\">完成后关闭弹窗,并刷新连接状态。</p></template>\n <p v-if=\"error && state!=='failed'\" class=\"error\" role=\"alert\">{{ error }}</p>\n <button v-if=\"webLogin && state === 'failed'\" type=\"button\" :disabled=\"!canStart\" @click=\"start\">{{ networkDirty?'保存并重新登录':'重新登录' }}</button>\n <p v-if=\"webLogin\" class=\"hint\">授权网页使用当前设备的浏览器网络。此代理仅作用于服务器发起的连接。</p>\n <button type=\"button\" :disabled=\"saving\" @click=\"close\">{{ state === 'pending' || state === 'starting' ? '取消登录' : '关闭' }}</button>\n </template>\n </div>\n </StandaloneDialog>\n</template>\n\n<style scoped>\n.failure-heading{display:flex;align-items:flex-start;gap:12px;color:var(--danger);font-size:18px}.failure-heading .app-icon{flex-shrink:0;margin-top:2px}\n.network-summary{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;color:var(--text-secondary)}\n.provider-login,.provider-login form{display:grid;gap:16px;min-width:0;color:var(--text-primary);font-size:14px;line-height:1.6}.provider-login p{margin:0;overflow-wrap:anywhere}.scope,.hint,.device-code>span{color:var(--text-secondary);font-size:13px}.device-code{display:grid;gap:12px;padding:20px 12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-soft);text-align:center}.device-code strong{font:650 26px/1.4 var(--font-ui);letter-spacing:2px;overflow-wrap:anywhere;user-select:all}.provider-login button,.provider-login a{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;color:var(--text-primary);background:var(--surface);font:inherit;cursor:pointer;text-decoration:none;box-sizing:border-box}.provider-login .primary{background:var(--accent);color:var(--text-on-solid);border-color:var(--accent)}.provider-login button:disabled{opacity:.5;cursor:default}.provider-login button:hover:not(:disabled){filter:brightness(.95)}.provider-login label{display:grid;gap:8px}.provider-login input{width:100%;min-width:0;box-sizing:border-box;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);font:inherit}.provider-login code{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px}.provider-login summary{cursor:pointer;min-height:44px;align-content:center}.provider-login :focus-visible{outline:2px solid var(--accent);outline-offset:3px}.error{color:var(--danger)}.login-success{display:flex;align-items:center;gap:10px;color:var(--success)}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport { hermesApi, type HermesDeviceStart, type HermesOAuthProvider } from '@/api/hermes'\nimport { copyTextToClipboard } from '@/utils/clipboard'\nimport ModelNetworkChoice from './ModelNetworkChoice.vue'\nimport { modelNetworkApi, networkLabel } from '@/api/modelNetwork'\nimport type { ModelNetworkView, NetworkPolicy } from '@shared/modelNetwork'\n\nconst props = defineProps<{ provider: HermesOAuthProvider; bot?: string }>()\nconst emit = defineEmits<{ close: []; success: [] }>()\n// Capture the scope for the lifetime of this login, including late cancellation.\nconst provider = props.provider, bot = props.bot\nconst webLogin = provider.flow === 'device_code' || ['device_code', 'process'].includes(provider.web_flow ?? '')\nconst tokenLogin = provider.web_flow === 'paste_token'\nconst state = ref<'starting' | 'pending' | 'failed' | 'approved' | 'ready'>('ready')\nconst session = ref<HermesDeviceStart>(), error = ref(''), token = ref(''), saving = ref(false), copyNotice = ref('')\nconst network = ref<ModelNetworkView>(), policy = ref<NetworkPolicy>({mode:'inherit'}), networkBusy = ref(false)\nconst networkDirty = computed(() => !!network.value && JSON.stringify(policy.value) !== JSON.stringify(network.value.connections[provider.id] ?? {mode:'inherit'}))\nconst canStart = computed(() => !!network.value && !networkBusy.value && (policy.value.mode !== 'proxy' || !!policy.value.proxyId))\nconst networkSummary = computed(() => network.value ? networkLabel(network.value, policy.value) : '正在读取网络…')\nasync function loadNetwork() {\n try { const value=await modelNetworkApi.get(bot);if(alive){network.value=value;policy.value={...value.connections[provider.id] ?? {mode:'inherit'}};error.value=''} }\n catch(cause) { if(alive)error.value=cause instanceof Error?cause.message:'无法读取网络配置,请重试。' }\n}\nasync function saveNetwork() {\n if(networkDirty.value){network.value=await modelNetworkApi.save(network.value!.revision,policy.value,provider.id,bot)}\n}\nasync function changeNetwork() {\n if(session.value && !window.confirm('更改网络将结束本次授权,需要重新获取验证码。继续?'))return\n generation++;clearTimeout(timer)\n try{await release(session.value);session.value=undefined;state.value='ready';error.value='';await loadNetwork()}\n catch(cause){state.value='failed';error.value=cause instanceof Error?cause.message:'无法取消本次授权,请重试。'}\n}\nlet alive = true, generation = 0, timer: ReturnType<typeof setTimeout> | undefined\nconst verificationUrl = computed(() => {\n try { const url = new URL(session.value?.verification_url ?? ''); return ['https:', 'http:'].includes(url.protocol) && !url.username && !url.password ? url.href : '' } catch { return '' }\n})\nconst message = computed(() => state.value === 'starting' ? '正在准备登录…' : state.value === 'approved' ? '登录成功' : verificationUrl.value ? '等待你在浏览器中完成授权…' : '正在准备授权链接…')\n\nasync function release(current?: HermesDeviceStart) {\n if (current) await hermesApi.oauthCancel(current.session_id, bot)\n}\nasync function start() {\n if (state.value === 'starting' || saving.value || !canStart.value) return\n const currentGeneration = ++generation\n clearTimeout(timer); error.value = ''; copyNotice.value = ''; state.value = 'starting'\n try {\n await release(session.value); session.value = undefined\n await saveNetwork()\n if (!alive || currentGeneration !== generation) return\n const next = await hermesApi.oauthStart(provider.id, bot, network.value!.revision)\n if (!alive || currentGeneration !== generation) { await release(next); return }\n session.value = next; state.value = 'pending'; poll(currentGeneration)\n } catch (cause) {\n if (!alive || currentGeneration !== generation) return\n error.value = cause instanceof Error ? cause.message : '无法开始登录,请重试。'; state.value = 'failed'\n }\n}\nfunction poll(currentGeneration: number) {\n const current = session.value\n if (!current || !alive) return\n timer = setTimeout(async () => {\n try {\n const result = await hermesApi.oauthPoll(provider.id, current.session_id, bot)\n if (!alive || currentGeneration !== generation) return\n if (result.user_code) current.user_code = result.user_code\n if (result.verification_url) current.verification_url = result.verification_url\n if (result.status === 'pending') { poll(currentGeneration); return }\n if (result.status === 'approved') { session.value = undefined; state.value = 'approved'; emit('success'); return }\n error.value = result.error_message || (result.status === 'expired' ? '授权已过期,请重新登录。' : result.status === 'denied' ? '账号已拒绝授权,可重新开始登录。' : '登录未完成,请重试。')\n state.value = 'failed'\n } catch (cause) {\n if (!alive || currentGeneration !== generation) return\n error.value = cause instanceof Error ? cause.message : '无法获取授权结果,请重试。'; state.value = 'failed'\n }\n }, Math.max(1, current.poll_interval || 2) * 1000)\n}\nasync function submitToken() {\n if (saving.value || !token.value.trim() || !canStart.value) return\n saving.value = true; error.value = ''\n try {\n await saveNetwork()\n await hermesApi.oauthSubmit(provider.id, token.value.trim(), bot)\n if (!alive) return\n token.value = ''; state.value = 'approved'; emit('success')\n } catch (cause) { if (alive) error.value = cause instanceof Error ? cause.message : '凭据保存失败,请重试。' }\n finally { saving.value = false }\n}\nfunction beforeClose() {\n return !saving.value && (!(token.value || networkDirty.value) || window.confirm('还有未保存的输入,放弃本次更改?'))\n}\nfunction close() { if (beforeClose()) emit('close') }\nasync function copyCode() {\n copyNotice.value = await copyTextToClipboard(session.value?.user_code ?? '') ? '验证码已复制' : '无法自动复制,请选中验证码手动复制。'\n}\nonMounted(loadNetwork)\nonBeforeUnmount(() => {\n alive = false; generation++; clearTimeout(timer)\n const current = session.value; session.value = undefined; token.value = ''\n void release(current).catch(() => { /* The server also expires abandoned sessions. */ })\n})\n</script>\n\n<template>\n <StandaloneDialog :title=\"`登录 ${provider.name}`\" compact wide :before-close=\"beforeClose\" @close=\"emit('close')\">\n <div class=\"provider-login\">\n <p class=\"scope\">{{ bot ? '仅对此 Bot 生效' : '服务器模型连接' }}</p>\n <template v-if=\"state === 'approved'\">\n <div class=\"login-success\" role=\"status\"><AppIcon name=\"check\" :size=\"24\" /><strong>登录成功</strong></div>\n <p>已完成 {{ provider.name }} 授权。</p>\n <button class=\"primary\" type=\"button\" @click=\"close\">完成</button>\n </template>\n <template v-else>\n <template v-if=\"state==='ready'\">\n <ModelNetworkChoice v-if=\"network\" v-model=\"policy\" :view=\"network\" :provider=\"provider.id\" :bot=\"bot\" compact :disabled=\"saving\" @busy=\"networkBusy=$event\" />\n <button v-else type=\"button\" @click=\"loadNetwork\">重新读取网络配置</button>\n </template>\n <div v-else class=\"network-summary\"><span>网络:{{ networkSummary }}</span><button v-if=\"state==='pending' || state==='failed'\" type=\"button\" @click=\"changeNetwork\">更改</button></div>\n <template v-if=\"state==='failed'\">\n <div class=\"failure-heading\" role=\"alert\"><AppIcon name=\"alert\" :size=\"24\" /><strong>{{ error }}</strong></div>\n <ModelNetworkChoice v-if=\"network\" v-model=\"policy\" :view=\"network\" :provider=\"provider.id\" :bot=\"bot\" summary-only @busy=\"networkBusy=$event\" />\n <p class=\"hint\">不会自动切换为直接连接。</p>\n <button type=\"button\" class=\"primary\" @click=\"changeNetwork\">检查网络设置</button>\n </template>\n <template v-if=\"webLogin\">\n <p v-if=\"state !== 'failed' && state !== 'ready'\" role=\"status\">{{ message }}</p>\n <button v-if=\"state==='ready'\" type=\"button\" class=\"primary\" :disabled=\"!canStart\" @click=\"start\">{{ networkDirty?'保存并开始登录':'开始登录' }}</button>\n <template v-if=\"state === 'pending'\">\n <div v-if=\"session?.user_code\" class=\"device-code\"><span>在授权页面输入验证码</span><strong>{{ session.user_code }}</strong><button type=\"button\" @click=\"copyCode\">复制验证码</button><small v-if=\"copyNotice\" role=\"status\">{{ copyNotice }}</small></div>\n <a v-if=\"verificationUrl\" class=\"primary\" :href=\"verificationUrl\" target=\"_blank\" rel=\"noopener noreferrer\"><AppIcon name=\"external\" :size=\"16\" />打开授权页面</a>\n <p class=\"hint\">{{ verificationUrl ? '在浏览器完成授权后,回到这里查看结果。' : '登录服务准备好后,授权入口会自动显示。' }}</p>\n </template>\n </template>\n <form v-else-if=\"tokenLogin\" @submit.prevent=\"submitToken\">\n <p>粘贴已有的 API Key 或登录令牌完成连接。</p>\n <label>登录凭据<input v-model=\"token\" :aria-label=\"`${provider.name} 凭据`\" type=\"password\" autocomplete=\"new-password\" :disabled=\"saving\" placeholder=\"粘贴登录凭据\" /></label>\n <details v-if=\"provider.cli_command\"><summary>如何获取登录令牌</summary><p>在服务器上按该服务的登录指引运行:</p><code>{{ provider.cli_command }}</code></details>\n <button type=\"submit\" class=\"primary\" :disabled=\"saving || !token.trim() || !canStart\">{{ saving ? '正在保存…' : networkDirty?'保存网络与凭据':'保存凭据' }}</button>\n </form>\n <template v-else><p>此服务需要先在服务器上完成登录:</p><code>{{ provider.cli_command }}</code><p class=\"hint\">完成后关闭弹窗,并刷新连接状态。</p></template>\n <p v-if=\"error && state!=='failed'\" class=\"error\" role=\"alert\">{{ error }}</p>\n <button v-if=\"webLogin && state === 'failed'\" type=\"button\" :disabled=\"!canStart\" @click=\"start\">{{ networkDirty?'保存并重新登录':'重新登录' }}</button>\n <p v-if=\"webLogin\" class=\"hint\">授权网页使用当前设备的浏览器网络。此代理仅作用于服务器发起的连接。</p>\n <button type=\"button\" :disabled=\"saving\" @click=\"close\">{{ state === 'pending' || state === 'starting' ? '取消登录' : '关闭' }}</button>\n </template>\n </div>\n </StandaloneDialog>\n</template>\n\n<style scoped>\n.failure-heading{display:flex;align-items:flex-start;gap:12px;color:var(--danger);font-size:18px}.failure-heading .app-icon{flex-shrink:0;margin-top:2px}\n.network-summary{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;color:var(--text-secondary)}\n.provider-login,.provider-login form{display:grid;gap:16px;min-width:0;color:var(--text-primary);font-size:14px;line-height:1.6}.provider-login p{margin:0;overflow-wrap:anywhere}.scope,.hint,.device-code>span{color:var(--text-secondary);font-size:13px}.device-code{display:grid;gap:12px;padding:20px 12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-soft);text-align:center}.device-code strong{font:650 26px/1.4 var(--font-ui);letter-spacing:2px;overflow-wrap:anywhere;user-select:all}.provider-login button,.provider-login a{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;color:var(--text-primary);background:var(--surface);font:inherit;cursor:pointer;text-decoration:none;box-sizing:border-box}.provider-login .primary{background:var(--accent);color:var(--text-on-solid);border-color:var(--accent)}.provider-login button:disabled{opacity:.5;cursor:default}.provider-login button:hover:not(:disabled){filter:brightness(.95)}.provider-login label{display:grid;gap:8px}.provider-login input{width:100%;min-width:0;box-sizing:border-box;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);font:inherit}.provider-login code{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px}.provider-login summary{cursor:pointer;min-height:44px;align-content:center}.provider-login :focus-visible{outline:2px solid var(--accent);outline-offset:3px}.error{color:var(--danger)}.login-success{display:flex;align-items:center;gap:10px;color:var(--success)}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProviderLoginDialog-D3mqdAlo.js","names":[],"sources":["../../src/client/api/modelNetwork.ts","../../src/client/components/app/ModelNetworkChoice.vue","../../src/client/components/app/ModelNetworkChoice.vue","../../src/client/api/hermes.ts","../../src/client/utils/clipboard.ts","../../src/client/components/app/ProviderLoginDialog.vue","../../src/client/components/app/ProviderLoginDialog.vue"],"sourcesContent":["import { apiRequest } from './client'\nimport type { ModelNetworkView, NetworkPolicy, NetworkTest, ModelProxy } from '@shared/modelNetwork'\nexport type ProxyDraft = Omit<ModelProxy, 'id' | 'hasPassword'> & { id?: string; password?: string; clearPassword?: boolean }\nconst base = '/api/app/admin/hermes/network'\nconst q = (bot?: string) => bot ? `?bot=${encodeURIComponent(bot)}` : ''\nexport const modelNetworkApi = {\n get: (bot?: string) => apiRequest<ModelNetworkView>(base + q(bot)),\n save: (revision: number, policy: NetworkPolicy, provider?: string, bot?: string) => apiRequest<ModelNetworkView>(`${base}/${provider ? 'connections/' + encodeURIComponent(provider) : 'default'}${q(bot)}`, { method: 'PUT', body: { revision, policy } }),\n proxy: (revision: number, proxy: ProxyDraft) => apiRequest<ModelNetworkView>(base + '/proxies', { method: 'POST', body: { revision, proxy } }),\n remove: (revision: number, id: string) => apiRequest<ModelNetworkView>(base + '/proxies/' + encodeURIComponent(id), { method: 'DELETE', body: { revision } }),\n test: (input: { provider?: string; policy?: NetworkPolicy; draft?: ProxyDraft }, bot?: string) => apiRequest<NetworkTest>(base + '/test' + q(bot), { method: 'POST', body: input }),\n}\nexport function networkLabel(view: ModelNetworkView, policy: NetworkPolicy): string {\n if (policy.mode === 'inherit') return '跟随默认 · ' + networkLabel(view, view.default)\n if (policy.mode === 'environment') return view.environmentDetected ? '服务器环境代理' : '服务器环境(未检测到代理)'\n if (policy.mode === 'direct') return '直接连接'\n return view.proxies.find(p => p.id === policy.proxyId)?.name ?? '代理已不可用'\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch, useId } from 'vue'\nimport { modelNetworkApi, networkLabel } from '@/api/modelNetwork'\nimport type { ModelNetworkView, NetworkPolicy, NetworkTest } from '@shared/modelNetwork'\nconst props = defineProps<{ view: ModelNetworkView; modelValue: NetworkPolicy; provider?: string; bot?: string; disabled?: boolean; compact?: boolean; global?: boolean; summaryOnly?: boolean; hideTest?:boolean }>()\nconst emit = defineEmits<{ 'update:modelValue': [value: NetworkPolicy]; manage: []; busy: [value: boolean] }>()\nconst radioName=useId()\nconst testing = ref(false), result = ref<NetworkTest>(), error = ref('')\nconst summary = computed(() => networkLabel(props.view, props.modelValue))\nconst selected = computed(() => props.view.proxies.find(p => p.id === props.modelValue.proxyId))\nconst modes = computed(() => props.global ? [{ id: 'environment', name: '沿用服务器环境', hint: props.view.environmentDetected ? '已检测到环境代理,保留当前网络行为' : '未检测到环境代理' }, { id: 'direct', name: '直接连接', hint: '不使用代理' }, { id: 'proxy', name: '指定代理', hint: '通过已保存的代理连接' }] : [{ id: 'inherit', name: '跟随服务器默认', hint: networkLabel(props.view, props.view.default) }, { id: 'direct', name: '直接连接', hint: '此连接不使用代理' }, { id: 'proxy', name: '指定代理', hint: '仅此连接使用所选代理' }])\nfunction change(mode: string) { emit('update:modelValue', mode === 'proxy' ? { mode, proxyId: props.modelValue.proxyId ?? props.view.proxies[0]?.id } : { mode } as NetworkPolicy) }\nwatch(() => JSON.stringify([props.modelValue, props.view.revision]), () => { result.value = undefined; error.value = '' })\nasync function test() {\n if (testing.value) return\n testing.value = true; emit('busy', true); error.value = ''; result.value = undefined\n try { result.value = await modelNetworkApi.test({ provider: props.provider, policy: props.modelValue.mode === 'environment' ? undefined : props.modelValue }, props.bot) }\n catch (cause) { error.value = cause instanceof Error ? cause.message : '测试失败,请重试。' }\n finally { testing.value = false; emit('busy', false) }\n}\ndefineExpose({ summary, test })\n</script>\n<template>\n <div class=\"network-choice\" :class=\"{'global-choice':global}\">\n <label v-if=\"compact && !summaryOnly\" class=\"compact-choice\">{{ global?'默认连接方式':'连接网络' }}<select :value=\"modelValue.mode\" :disabled=\"disabled || testing\" @change=\"change(($event.target as HTMLSelectElement).value)\"><option v-for=\"mode in modes\" :key=\"mode.id\" :value=\"mode.id\">{{ mode.name }}</option></select></label>\n <fieldset v-else-if=\"!summaryOnly\" :disabled=\"disabled || testing\"><legend>{{ global ? '默认连接方式' : '连接方式' }}</legend><label v-for=\"mode in modes\" :key=\"mode.id\" class=\"network-option\" :class=\"{ selected: modelValue.mode === mode.id }\"><input type=\"radio\" :name=\"radioName\" :checked=\"modelValue.mode === mode.id\" :value=\"mode.id\" @change=\"change(mode.id)\" /><span><strong>{{ mode.name }}</strong><small>{{ mode.hint }}</small></span></label></fieldset>\n <div v-if=\"modelValue.mode === 'proxy' && !summaryOnly\" class=\"proxy-select\"><label><span v-if=\"compact || global\">使用代理</span><select aria-label=\"使用代理\" :value=\"modelValue.proxyId ?? ''\" :disabled=\"disabled || testing\" @change=\"emit('update:modelValue', {mode:'proxy', proxyId:($event.target as HTMLSelectElement).value})\"><option value=\"\" disabled>请选择代理</option><option v-for=\"p in view.proxies\" :key=\"p.id\" :value=\"p.id\">{{ p.name }}</option></select></label><small v-if=\"selected\">{{ selected.protocol.toUpperCase() }} · {{ selected.host }}:{{ selected.port }}</small><p v-if=\"!view.proxies.length\">尚未添加代理。请先在「环境与权限 → 模型网络」添加。</p></div>\n <p v-if=\"compact && !global && !summaryOnly\" class=\"summary\">{{ summary }}</p>\n <div v-if=\"!global && !hideTest\" class=\"network-diagnostics\"><div v-if=\"result\" class=\"test-result\" :class=\"{failed:result.proxy==='failed'||result.target==='failed'}\" role=\"status\"><strong>代理:{{ {passed:'端口可达',failed:'连接失败',skipped:'未检测'}[result.proxy] }}<template v-if=\"provider\"> · 目标服务:{{ {passed:'可达',failed:'不可达',skipped:'尚未检测'}[result.target] }}</template></strong><small>{{ result.message }}</small></div><div v-else><strong>连接测试</strong><small>由运行 Yaovi 的服务器发起</small></div><button type=\"button\" :disabled=\"disabled || testing || (modelValue.mode === 'proxy' && !selected)\" @click=\"test\">{{ testing ? '正在测试…' : '测试当前选择' }}</button></div>\n <p v-if=\"error\" role=\"alert\">{{ error }}</p>\n </div>\n</template>\n<style scoped>\n.network-choice:has(fieldset) .proxy-select{margin-left:36px;max-width:530px}\n.global-choice .compact-choice,.global-choice .proxy-select>label{display:grid;grid-template-columns:minmax(0,1fr) minmax(200px,460px);align-items:center;gap:24px}.global-choice .proxy-select>small{margin-left:auto}@media(max-width:600px){.global-choice .compact-choice,.global-choice .proxy-select>label{grid-template-columns:1fr;gap:8px}}\n.network-choice{display:grid;gap:8px;min-width:0;font-size:14px}.network-choice fieldset{border:0;padding:0;margin:0;display:grid;gap:0;min-width:0}.network-choice legend{font-weight:600;margin-bottom:12px}.network-option{display:flex;align-items:center;gap:12px;border:0;border-bottom:1px solid var(--line);border-radius:0;padding:8px 0;cursor:pointer}.network-option.selected{background:transparent}.network-option input{width:16px;height:16px;accent-color:var(--accent)}.network-option span,.network-diagnostics>div{display:grid;gap:4px}.network-option strong{font-weight:550}.network-choice small,.summary{color:var(--text-secondary);line-height:1.6}.network-choice p{margin:0}.proxy-select,.proxy-select label,.compact-choice{display:grid;gap:8px}.network-choice select,.network-choice button{font:inherit;min-height:44px;border:1px solid var(--line);border-radius:8px;padding:10px 12px;background:var(--surface);color:var(--text-primary);max-width:100%;box-sizing:border-box}.network-choice button{cursor:pointer}.network-diagnostics{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--line);padding-top:16px}.test-result{display:grid;gap:5px}.test-result strong{font-size:13px;color:var(--success)}.test-result.failed strong{color:var(--danger)}.network-choice [role=alert]{color:var(--danger)}.network-choice :focus-visible{outline:2px solid var(--accent);outline-offset:3px}.network-choice :disabled{opacity:.55}@media(max-width:420px){.network-diagnostics{align-items:flex-start;flex-direction:column}}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch, useId } from 'vue'\nimport { modelNetworkApi, networkLabel } from '@/api/modelNetwork'\nimport type { ModelNetworkView, NetworkPolicy, NetworkTest } from '@shared/modelNetwork'\nconst props = defineProps<{ view: ModelNetworkView; modelValue: NetworkPolicy; provider?: string; bot?: string; disabled?: boolean; compact?: boolean; global?: boolean; summaryOnly?: boolean; hideTest?:boolean }>()\nconst emit = defineEmits<{ 'update:modelValue': [value: NetworkPolicy]; manage: []; busy: [value: boolean] }>()\nconst radioName=useId()\nconst testing = ref(false), result = ref<NetworkTest>(), error = ref('')\nconst summary = computed(() => networkLabel(props.view, props.modelValue))\nconst selected = computed(() => props.view.proxies.find(p => p.id === props.modelValue.proxyId))\nconst modes = computed(() => props.global ? [{ id: 'environment', name: '沿用服务器环境', hint: props.view.environmentDetected ? '已检测到环境代理,保留当前网络行为' : '未检测到环境代理' }, { id: 'direct', name: '直接连接', hint: '不使用代理' }, { id: 'proxy', name: '指定代理', hint: '通过已保存的代理连接' }] : [{ id: 'inherit', name: '跟随服务器默认', hint: networkLabel(props.view, props.view.default) }, { id: 'direct', name: '直接连接', hint: '此连接不使用代理' }, { id: 'proxy', name: '指定代理', hint: '仅此连接使用所选代理' }])\nfunction change(mode: string) { emit('update:modelValue', mode === 'proxy' ? { mode, proxyId: props.modelValue.proxyId ?? props.view.proxies[0]?.id } : { mode } as NetworkPolicy) }\nwatch(() => JSON.stringify([props.modelValue, props.view.revision]), () => { result.value = undefined; error.value = '' })\nasync function test() {\n if (testing.value) return\n testing.value = true; emit('busy', true); error.value = ''; result.value = undefined\n try { result.value = await modelNetworkApi.test({ provider: props.provider, policy: props.modelValue.mode === 'environment' ? undefined : props.modelValue }, props.bot) }\n catch (cause) { error.value = cause instanceof Error ? cause.message : '测试失败,请重试。' }\n finally { testing.value = false; emit('busy', false) }\n}\ndefineExpose({ summary, test })\n</script>\n<template>\n <div class=\"network-choice\" :class=\"{'global-choice':global}\">\n <label v-if=\"compact && !summaryOnly\" class=\"compact-choice\">{{ global?'默认连接方式':'连接网络' }}<select :value=\"modelValue.mode\" :disabled=\"disabled || testing\" @change=\"change(($event.target as HTMLSelectElement).value)\"><option v-for=\"mode in modes\" :key=\"mode.id\" :value=\"mode.id\">{{ mode.name }}</option></select></label>\n <fieldset v-else-if=\"!summaryOnly\" :disabled=\"disabled || testing\"><legend>{{ global ? '默认连接方式' : '连接方式' }}</legend><label v-for=\"mode in modes\" :key=\"mode.id\" class=\"network-option\" :class=\"{ selected: modelValue.mode === mode.id }\"><input type=\"radio\" :name=\"radioName\" :checked=\"modelValue.mode === mode.id\" :value=\"mode.id\" @change=\"change(mode.id)\" /><span><strong>{{ mode.name }}</strong><small>{{ mode.hint }}</small></span></label></fieldset>\n <div v-if=\"modelValue.mode === 'proxy' && !summaryOnly\" class=\"proxy-select\"><label><span v-if=\"compact || global\">使用代理</span><select aria-label=\"使用代理\" :value=\"modelValue.proxyId ?? ''\" :disabled=\"disabled || testing\" @change=\"emit('update:modelValue', {mode:'proxy', proxyId:($event.target as HTMLSelectElement).value})\"><option value=\"\" disabled>请选择代理</option><option v-for=\"p in view.proxies\" :key=\"p.id\" :value=\"p.id\">{{ p.name }}</option></select></label><small v-if=\"selected\">{{ selected.protocol.toUpperCase() }} · {{ selected.host }}:{{ selected.port }}</small><p v-if=\"!view.proxies.length\">尚未添加代理。请先在「环境与权限 → 模型网络」添加。</p></div>\n <p v-if=\"compact && !global && !summaryOnly\" class=\"summary\">{{ summary }}</p>\n <div v-if=\"!global && !hideTest\" class=\"network-diagnostics\"><div v-if=\"result\" class=\"test-result\" :class=\"{failed:result.proxy==='failed'||result.target==='failed'}\" role=\"status\"><strong>代理:{{ {passed:'端口可达',failed:'连接失败',skipped:'未检测'}[result.proxy] }}<template v-if=\"provider\"> · 目标服务:{{ {passed:'可达',failed:'不可达',skipped:'尚未检测'}[result.target] }}</template></strong><small>{{ result.message }}</small></div><div v-else><strong>连接测试</strong><small>由运行 Yaovi 的服务器发起</small></div><button type=\"button\" :disabled=\"disabled || testing || (modelValue.mode === 'proxy' && !selected)\" @click=\"test\">{{ testing ? '正在测试…' : '测试当前选择' }}</button></div>\n <p v-if=\"error\" role=\"alert\">{{ error }}</p>\n </div>\n</template>\n<style scoped>\n.network-choice:has(fieldset) .proxy-select{margin-left:36px;max-width:530px}\n.global-choice .compact-choice,.global-choice .proxy-select>label{display:grid;grid-template-columns:minmax(0,1fr) minmax(200px,460px);align-items:center;gap:24px}.global-choice .proxy-select>small{margin-left:auto}@media(max-width:600px){.global-choice .compact-choice,.global-choice .proxy-select>label{grid-template-columns:1fr;gap:8px}}\n.network-choice{display:grid;gap:8px;min-width:0;font-size:14px}.network-choice fieldset{border:0;padding:0;margin:0;display:grid;gap:0;min-width:0}.network-choice legend{font-weight:600;margin-bottom:12px}.network-option{display:flex;align-items:center;gap:12px;border:0;border-bottom:1px solid var(--line);border-radius:0;padding:8px 0;cursor:pointer}.network-option.selected{background:transparent}.network-option input{width:16px;height:16px;accent-color:var(--accent)}.network-option span,.network-diagnostics>div{display:grid;gap:4px}.network-option strong{font-weight:550}.network-choice small,.summary{color:var(--text-secondary);line-height:1.6}.network-choice p{margin:0}.proxy-select,.proxy-select label,.compact-choice{display:grid;gap:8px}.network-choice select,.network-choice button{font:inherit;min-height:44px;border:1px solid var(--line);border-radius:8px;padding:10px 12px;background:var(--surface);color:var(--text-primary);max-width:100%;box-sizing:border-box}.network-choice button{cursor:pointer}.network-diagnostics{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--line);padding-top:16px}.test-result{display:grid;gap:5px}.test-result strong{font-size:13px;color:var(--success)}.test-result.failed strong{color:var(--danger)}.network-choice [role=alert]{color:var(--danger)}.network-choice :focus-visible{outline:2px solid var(--accent);outline-offset:3px}.network-choice :disabled{opacity:.55}@media(max-width:420px){.network-diagnostics{align-items:flex-start;flex-direction:column}}\n</style>\n","import type { MessagingChannelPlatform } from '@shared/channelPlatforms'\nimport { apiRequest } from './client'\nimport type { BotAutoPool, BotModelOption, BotModelVisibilityView } from '@shared/botModelSettings'\nimport type { AutoRouteDecision } from '@shared/autoRouting'\n\n/** Client for /api/app/admin/hermes: Yaovi's front-end over Hermes's own provider management. */\nconst base = '/api/app/admin/hermes'\nconst q = (bot?: string, extra = '') => {\n const params = new URLSearchParams(extra)\n if (bot) params.set('bot', bot)\n const s = params.toString()\n return s ? `?${s}` : ''\n}\nexport type HermesProvider = { id: string; label: string; authType: string; transport: string; aggregator: boolean; signupUrl: string; envVars: string[]; aliases: string[] }\nexport type HermesOAuthProvider = { id: string; name: string; flow: 'device_code' | 'external' | string; web_flow?: 'device_code' | 'process' | 'paste_token'; cli_command: string; docs_url: string; disconnectable: boolean; disconnect_hint: string | null; status: { logged_in: boolean; token_preview?: string; source_label?: string; expires_at?: string | null; error?: string } }\nexport type HermesDeviceStart = { session_id: string; flow: string; user_code: string; verification_url: string; expires_in: number; poll_interval: number }\nexport type HermesEnvVar = { is_set: boolean; redacted_value: string | null; description: string; category: string; is_password: boolean; provider: string; provider_label: string; advanced?: boolean; custom?: boolean; channel_managed?: boolean; readonly?: boolean }\nexport type HermesPoolEntry = { index: number; id: string; label: string; authType: string; source: string; priority: number; baseUrl: string | null; lastStatus: string | null; lastError: string | null; tokenPreview: string }\nexport type HermesModelOptions = { providers: Array<{ slug: string; name: string; models: string[]; authenticated?: boolean; is_current?: boolean; is_user_defined?: boolean }>; model?: string; provider?: string }\nexport type HermesAuxRow = { task: string; provider: string; model: string; base_url: string; reasoning_effort: string | null; label?: string; hint?: string }\nexport type HermesConfigSections = { model?: { provider?: string; default?: string; base_url?: string }; fallback_model?: Array<{ provider: string; model: string }> | { provider: string; model: string }; credential_pool_strategies?: Record<string, string>; auxiliary?: Record<string, Record<string, unknown>> }\nexport type HermesEndpoint = { id: string; name: string; base_url: string; model: string; api_mode?: string; has_api_key?: boolean; is_default?: boolean }\n\nexport type DecisionBackend = 'jev' | 'jeff'\nexport type JevAutoView = BotAutoPool & {\n revision: number\n enabled: boolean\n hasApiKey: boolean\n decisionReady: boolean\n backend: DecisionBackend\n jeffBaseUrl: string\n jeffModel: string\n modelOptions?: BotModelOption[]\n}\n\nexport const hermesApi = {\n providers: (bot?: string) => apiRequest<{ providers: HermesProvider[] }>(`${base}/providers${q(bot)}`),\n config: (bot?: string) => apiRequest<{ config: HermesConfigSections }>(`${base}/config${q(bot)}`),\n patchConfig: (config: Record<string, unknown>, bot?: string) => apiRequest<{ config: HermesConfigSections }>(`${base}/config${q(bot)}`, { method: 'PATCH', body: { config } as any }),\n models: (refresh = false, bot?: string) => apiRequest<HermesModelOptions>(`${base}/models${q(bot, refresh ? 'refresh=1' : '')}`),\n jev: () => apiRequest<JevAutoView>(`${base}/jev`),\n botModels: () => apiRequest<BotModelVisibilityView>(`${base}/bot-models`),\n saveBotModels: (body: BotModelVisibilityView) => apiRequest<BotModelVisibilityView>(`${base}/bot-models`, { method: 'PUT', body: body as any }),\n previewJev: (body: { pool: BotAutoPool; enabled: boolean; userText: string; apiKey?: string; backend?: DecisionBackend; jeffBaseUrl?: string; jeffModel?: string }) => apiRequest<AutoRouteDecision>(`${base}/jev/preview`, { method: 'POST', body: body as any }),\n saveJev: (body: BotAutoPool & { revision: number; enabled: boolean; apiKey?: string; clearApiKey?: boolean; backend?: DecisionBackend; jeffBaseUrl?: string; jeffModel?: string }) =>\n apiRequest<JevAutoView>(`${base}/jev`, { method: 'PUT', body: body as any }),\n setModel: (body: { scope: 'main' | 'auxiliary'; provider: string; model: string; task?: string; reasoning_effort?: string | null; base_url?: string; api_key?: string; confirm_expensive_model?: boolean }, bot?: string) =>\n apiRequest<{ ok?: boolean; confirm_required?: boolean; confirm_message?: string }>(`${base}/model${q(bot)}`, { method: 'POST', body: body as any }),\n auxiliary: (bot?: string) => apiRequest<{ tasks: HermesAuxRow[]; main: { provider: string; model: string } }>(`${base}/auxiliary${q(bot)}`),\n oauth: (bot?: string) => apiRequest<{ providers: HermesOAuthProvider[] }>(`${base}/oauth${q(bot)}`),\n oauthStart: (provider: string, bot?: string, networkRevision?: number) => apiRequest<HermesDeviceStart>(`${base}/oauth/${encodeURIComponent(provider)}/start${q(bot)}`, { method: 'POST', body: networkRevision === undefined ? {} : { networkRevision } }),\n oauthPoll: (provider: string, session: string, bot?: string) => apiRequest<{ status: string; error_message?: string | null; user_code?: string; verification_url?: string }>(`${base}/oauth/${encodeURIComponent(provider)}/poll/${encodeURIComponent(session)}${q(bot)}`),\n oauthCancel: (session: string, bot?: string) => apiRequest(`${base}/oauth/sessions/${encodeURIComponent(session)}${q(bot)}`, { method: 'DELETE' }),\n oauthSubmit: (provider: string, token: string, bot?: string) => apiRequest<{ ok: boolean; env_var: string }>(`${base}/oauth/${encodeURIComponent(provider)}/token${q(bot)}`, { method: 'POST', body: { token } }),\n oauthDisconnect: (provider: string, bot?: string) => apiRequest(`${base}/oauth/${encodeURIComponent(provider)}${q(bot)}`, { method: 'DELETE' }),\n env: (bot?: string) => apiRequest<Record<string, HermesEnvVar>>(`${base}/env${q(bot)}`),\n setEnv: (key: string, value: string, bot?: string) => apiRequest(`${base}/env${q(bot)}`, { method: 'PUT', body: { key, value, provider_setup: true } }),\n deleteEnv: (key: string, bot?: string) => apiRequest(`${base}/env${q(bot)}`, { method: 'DELETE', body: { key } }),\n validate: (key: string, value: string) => apiRequest<{ ok: boolean; reachable: boolean; message: string }>(`${base}/validate`, { method: 'POST', body: { key, value } }),\n endpoints: (bot?: string) => apiRequest<{ endpoints?: HermesEndpoint[] } & Record<string, unknown>>(`${base}/endpoints${q(bot)}`),\n saveEndpoint: (body: Record<string, unknown>, bot?: string) => apiRequest(`${base}/endpoints${q(bot)}`, { method: 'POST', body: body as any }),\n deleteEndpoint: (id: string, bot?: string) => apiRequest(`${base}/endpoints/${encodeURIComponent(id)}${q(bot)}`, { method: 'DELETE' }),\n pool: (provider: string, bot?: string) => apiRequest<{ provider: string; strategy: string; entries: HermesPoolEntry[] }>(`${base}/pool/${encodeURIComponent(provider)}${q(bot)}`),\n addPool: (provider: string, apiKey: string, label: string, bot?: string) => apiRequest(`${base}/pool/${encodeURIComponent(provider)}${q(bot)}`, { method: 'POST', body: { apiKey, ...(label ? { label } : {}) } }),\n removePool: (provider: string, index: number, bot?: string) => apiRequest(`${base}/pool/${encodeURIComponent(provider)}/${index}${q(bot)}`, { method: 'DELETE' }),\n}\n\nexport type HermesMediaToolset = 'image_gen' | 'video_gen'\nexport type HermesMediaProvider = {\n name: string; badge: string; tag: string; is_active: boolean; status: string; requires_nous_auth: boolean\n env_vars: Array<{ key: string; prompt: string; url?: string; default?: string; is_set: boolean }>\n}\nexport type HermesMediaConfig = { name: string; has_category: boolean; providers: HermesMediaProvider[]; active_provider: string | null }\nexport type HermesMediaModels = {\n name: string; has_models: boolean; provider?: string; plugin?: string; current: string | null; default: string | null\n models: Array<{ id: string; display: string; speed?: string; strengths?: string; price?: string }>\n}\nconst media = (name: HermesMediaToolset) => `${base}/media/${name}`\nexport const hermesMediaApi = {\n config: (name: HermesMediaToolset) => apiRequest<HermesMediaConfig>(`${media(name)}/config`),\n models: (name: HermesMediaToolset, provider?: string) => apiRequest<HermesMediaModels>(`${media(name)}/models${provider ? `?provider=${encodeURIComponent(provider)}` : ''}`),\n setProvider: (name: HermesMediaToolset, provider: string) => apiRequest<{ ok: boolean; needs_nous_auth?: boolean }>(`${media(name)}/provider`, { method: 'PUT', body: { provider } }),\n setModel: (name: HermesMediaToolset, model: string, provider: string) => apiRequest(`${media(name)}/model`, { method: 'PUT', body: { model, provider } }),\n setEnv: (name: HermesMediaToolset, env: Record<string, string>) => apiRequest(`${media(name)}/env`, { method: 'PUT', body: { env } }),\n}\n\nexport type HermesWebCapability = 'search' | 'extract'\nexport type HermesWebConfig = {\n providers: Array<HermesMediaProvider & { web_backend?: string; managed_nous_feature?: string; capabilities?: HermesWebCapability[] }>\n selection: Record<HermesWebCapability, string | null>\n active_search_backend: string | null; active_extract_backend: string | null\n}\nexport const hermesWebApi = {\n config: () => apiRequest<HermesWebConfig>(`${base}/web`),\n setProvider: (capability: HermesWebCapability, provider: string | null) => apiRequest<HermesWebConfig>(`${base}/web/provider`, { method: 'PUT', body: { capability, provider } }),\n setEnv: (env: Record<string, string>) => apiRequest<HermesWebConfig>(`${base}/web/env`, { method: 'PUT', body: { env } }),\n test: (capability: HermesWebCapability, value: string) => apiRequest<{ ok: boolean; provider: string | null; result: Record<string, any> }>(`${base}/web/test`, { method: 'POST', body: { capability, ...(capability === 'search' ? { query: value } : { url: value }) } }),\n}\n\n/** Hermes `mcp_servers` (system layer, or a Bot's override layer with `bot`). Secrets come back as key names only. */\nexport type HermesMcpServer = {\n name: string; transport: 'stdio' | 'http' | 'unknown'; url: string | null; command: string | null; args: string[]\n auth: string | null; enabled: boolean; env_keys: string[]; header_keys: string[]; yaovi_location: 'server' | 'virtual'\n include: string[] | null; exclude: string[] | null\n}\nexport type HermesMcpInput = {\n transport: 'stdio' | 'http'; command?: string; args?: string[]; url?: string; env?: Record<string, string | true>; headers?: Record<string, string | true>\n auth?: 'none' | 'oauth'; yaovi_location?: 'server' | 'virtual'; include?: string[] | null; exclude?: string[] | null; enabled?: boolean\n}\nexport type HermesMcpProbe = { ok: boolean; error?: string; tools: Array<{ name: string; description: string }> }\nexport type HermesMcpFlow = { flow_id: string; server_name: string; status: string; authorization_url: string | null; error: string | null }\nconst mcp = `${base}/mcp`\nexport const hermesMcpApi = {\n list: (bot?: string) => apiRequest<{ servers: HermesMcpServer[] }>(`${mcp}${q(bot)}`),\n create: (name: string, body: HermesMcpInput, bot?: string) => apiRequest<HermesMcpServer>(`${mcp}${q(bot)}`, { method: 'POST', body: { name, ...body } as any }),\n update: (name: string, body: HermesMcpInput, bot?: string) => apiRequest<HermesMcpServer>(`${mcp}/${encodeURIComponent(name)}${q(bot)}`, { method: 'PUT', body: body as any }),\n remove: (name: string, bot?: string) => apiRequest(`${mcp}/${encodeURIComponent(name)}${q(bot)}`, { method: 'DELETE' }),\n setEnabled: (name: string, enabled: boolean, bot?: string) => apiRequest(`${mcp}/${encodeURIComponent(name)}/enabled${q(bot)}`, { method: 'PATCH', body: { enabled } }),\n test: (name: string, bot?: string) => apiRequest<HermesMcpProbe>(`${mcp}/${encodeURIComponent(name)}/test${q(bot)}`, { method: 'POST' }),\n oauthStart: (name: string, bot?: string) => apiRequest<HermesMcpFlow>(`${mcp}/${encodeURIComponent(name)}/oauth/start${q(bot)}`, { method: 'POST' }),\n oauthStatus: (flow: string, bot?: string) => apiRequest<HermesMcpFlow & { tools?: unknown }>(`${mcp}/oauth/${encodeURIComponent(flow)}${q(bot)}`),\n oauthCancel: (flow: string, bot?: string) => apiRequest(`${mcp}/oauth/${encodeURIComponent(flow)}${q(bot)}`, { method: 'DELETE' }),\n}\n\nexport type HermesChannelField = { key: string; label: string; secret: boolean; required: boolean; configured: boolean; value: string | null }\nexport type HermesChannel = {\n platform: MessagingChannelPlatform; preparation?:{ready:boolean;missing:string[]}; label: string; configured: boolean; enabled: boolean; ready: boolean\n fields: HermesChannelField[]; home_channel: string; home_channel_name: string\n pending: Array<{ code: string; user_id: string; user_name: string; age_minutes?: number }>\n approved: Array<{ user_id: string; user_name: string }>\n}\nexport type HermesChannelRuntime = { running: boolean; platforms: string[]; status: Record<string, { state: string; error?: string; at: number }> }\nexport type HermesChannelInput = { enabled?: boolean; credentials?: Record<string, string | true>; home_channel?: string; home_channel_name?: string }\nconst channels = `${base}/channels`\nconst bq = (bot: string) => `?bot=${encodeURIComponent(bot)}`\nexport const hermesChannelsApi = {\n list: (bot: string) => apiRequest<{ platforms: HermesChannel[]; runtime: HermesChannelRuntime }>(`${channels}${bq(bot)}`),\n save: (bot: string, platform: string, body: HermesChannelInput) => apiRequest<HermesChannel>(`${channels}/${platform}${bq(bot)}`, { method: 'PUT', body: body as any }),\n setEnabled: (bot: string, platform: string, enabled: boolean) => apiRequest<HermesChannel>(`${channels}/${platform}/enabled${bq(bot)}`, { method: 'POST', body: { enabled } }),\n remove: (bot: string, platform: string) => apiRequest(`${channels}/${platform}${bq(bot)}`, { method: 'DELETE' }),\n test: (bot: string, platform: string) => apiRequest<{ ok: boolean; error?: string | null }>(`${channels}/${platform}/test${bq(bot)}`, { method: 'POST' }),\n approve: (bot: string, platform: string, code: string) => apiRequest<{ user_id: string; user_name: string }>(`${channels}/${platform}/pairing${bq(bot)}`, { method: 'POST', body: { code } }),\n revoke: (bot: string, platform: string, user: string) => apiRequest(`${channels}/${platform}/pairing/${encodeURIComponent(user)}${bq(bot)}`, { method: 'DELETE' }),\n}\n","function legacyCopyText(text: string): boolean {\n const textarea = document.createElement('textarea')\n const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null\n textarea.value = text\n textarea.setAttribute('readonly', '')\n textarea.setAttribute('aria-hidden', 'true')\n textarea.style.cssText = 'position:fixed;left:-9999px;top:0;width:1px;height:1px;opacity:0'\n document.body.appendChild(textarea)\n textarea.focus({ preventScroll: true })\n textarea.select()\n textarea.setSelectionRange(0, text.length)\n\n let copied = false\n try {\n copied = document.execCommand('copy')\n } catch {\n copied = false\n } finally {\n textarea.remove()\n activeElement?.focus({ preventScroll: true })\n }\n return copied\n}\n\n/** Copy text in secure browsers and in embedded/HTTP browsers with a legacy fallback. */\nexport async function copyTextToClipboard(text: string): Promise<boolean> {\n try {\n if (window.isSecureContext && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text)\n return true\n }\n } catch {\n // Permission-restricted embedded browsers can expose the API but reject it.\n }\n return legacyCopyText(text)\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport { hermesApi, type HermesDeviceStart, type HermesOAuthProvider } from '@/api/hermes'\nimport { copyTextToClipboard } from '@/utils/clipboard'\nimport ModelNetworkChoice from './ModelNetworkChoice.vue'\nimport { modelNetworkApi, networkLabel } from '@/api/modelNetwork'\nimport type { ModelNetworkView, NetworkPolicy } from '@shared/modelNetwork'\n\nconst props = defineProps<{ provider: HermesOAuthProvider; bot?: string }>()\nconst emit = defineEmits<{ close: []; success: [] }>()\n// Capture the scope for the lifetime of this login, including late cancellation.\nconst provider = props.provider, bot = props.bot\nconst webLogin = provider.flow === 'device_code' || ['device_code', 'process'].includes(provider.web_flow ?? '')\nconst tokenLogin = provider.web_flow === 'paste_token'\nconst state = ref<'starting' | 'pending' | 'failed' | 'approved' | 'ready'>('ready')\nconst session = ref<HermesDeviceStart>(), error = ref(''), token = ref(''), saving = ref(false), copyNotice = ref('')\nconst network = ref<ModelNetworkView>(), policy = ref<NetworkPolicy>({mode:'inherit'}), networkBusy = ref(false)\nconst networkDirty = computed(() => !!network.value && JSON.stringify(policy.value) !== JSON.stringify(network.value.connections[provider.id] ?? {mode:'inherit'}))\nconst canStart = computed(() => !!network.value && !networkBusy.value && (policy.value.mode !== 'proxy' || !!policy.value.proxyId))\nconst networkSummary = computed(() => network.value ? networkLabel(network.value, policy.value) : '正在读取网络…')\nasync function loadNetwork() {\n try { const value=await modelNetworkApi.get(bot);if(alive){network.value=value;policy.value={...value.connections[provider.id] ?? {mode:'inherit'}};error.value=''} }\n catch(cause) { if(alive)error.value=cause instanceof Error?cause.message:'无法读取网络配置,请重试。' }\n}\nasync function saveNetwork() {\n if(networkDirty.value){network.value=await modelNetworkApi.save(network.value!.revision,policy.value,provider.id,bot)}\n}\nasync function changeNetwork() {\n if(session.value && !window.confirm('更改网络将结束本次授权,需要重新获取验证码。继续?'))return\n generation++;clearTimeout(timer)\n try{await release(session.value);session.value=undefined;state.value='ready';error.value='';await loadNetwork()}\n catch(cause){state.value='failed';error.value=cause instanceof Error?cause.message:'无法取消本次授权,请重试。'}\n}\nlet alive = true, generation = 0, timer: ReturnType<typeof setTimeout> | undefined\nconst verificationUrl = computed(() => {\n try { const url = new URL(session.value?.verification_url ?? ''); return ['https:', 'http:'].includes(url.protocol) && !url.username && !url.password ? url.href : '' } catch { return '' }\n})\nconst message = computed(() => state.value === 'starting' ? '正在准备登录…' : state.value === 'approved' ? '登录成功' : verificationUrl.value ? '等待你在浏览器中完成授权…' : '正在准备授权链接…')\n\nasync function release(current?: HermesDeviceStart) {\n if (current) await hermesApi.oauthCancel(current.session_id, bot)\n}\nasync function start() {\n if (state.value === 'starting' || saving.value || !canStart.value) return\n const currentGeneration = ++generation\n clearTimeout(timer); error.value = ''; copyNotice.value = ''; state.value = 'starting'\n try {\n await release(session.value); session.value = undefined\n await saveNetwork()\n if (!alive || currentGeneration !== generation) return\n const next = await hermesApi.oauthStart(provider.id, bot, network.value!.revision)\n if (!alive || currentGeneration !== generation) { await release(next); return }\n session.value = next; state.value = 'pending'; poll(currentGeneration)\n } catch (cause) {\n if (!alive || currentGeneration !== generation) return\n error.value = cause instanceof Error ? cause.message : '无法开始登录,请重试。'; state.value = 'failed'\n }\n}\nfunction poll(currentGeneration: number) {\n const current = session.value\n if (!current || !alive) return\n timer = setTimeout(async () => {\n try {\n const result = await hermesApi.oauthPoll(provider.id, current.session_id, bot)\n if (!alive || currentGeneration !== generation) return\n if (result.user_code) current.user_code = result.user_code\n if (result.verification_url) current.verification_url = result.verification_url\n if (result.status === 'pending') { poll(currentGeneration); return }\n if (result.status === 'approved') { session.value = undefined; state.value = 'approved'; emit('success'); return }\n error.value = result.error_message || (result.status === 'expired' ? '授权已过期,请重新登录。' : result.status === 'denied' ? '账号已拒绝授权,可重新开始登录。' : '登录未完成,请重试。')\n state.value = 'failed'\n } catch (cause) {\n if (!alive || currentGeneration !== generation) return\n error.value = cause instanceof Error ? cause.message : '无法获取授权结果,请重试。'; state.value = 'failed'\n }\n }, Math.max(1, current.poll_interval || 2) * 1000)\n}\nasync function submitToken() {\n if (saving.value || !token.value.trim() || !canStart.value) return\n saving.value = true; error.value = ''\n try {\n await saveNetwork()\n await hermesApi.oauthSubmit(provider.id, token.value.trim(), bot)\n if (!alive) return\n token.value = ''; state.value = 'approved'; emit('success')\n } catch (cause) { if (alive) error.value = cause instanceof Error ? cause.message : '凭据保存失败,请重试。' }\n finally { saving.value = false }\n}\nfunction beforeClose() {\n return !saving.value && (!(token.value || networkDirty.value) || window.confirm('还有未保存的输入,放弃本次更改?'))\n}\nfunction close() { if (beforeClose()) emit('close') }\nasync function copyCode() {\n copyNotice.value = await copyTextToClipboard(session.value?.user_code ?? '') ? '验证码已复制' : '无法自动复制,请选中验证码手动复制。'\n}\nonMounted(loadNetwork)\nonBeforeUnmount(() => {\n alive = false; generation++; clearTimeout(timer)\n const current = session.value; session.value = undefined; token.value = ''\n void release(current).catch(() => { /* The server also expires abandoned sessions. */ })\n})\n</script>\n\n<template>\n <StandaloneDialog :title=\"`登录 ${provider.name}`\" compact wide :before-close=\"beforeClose\" @close=\"emit('close')\">\n <div class=\"provider-login\">\n <p class=\"scope\">{{ bot ? '仅对此 Bot 生效' : '服务器模型连接' }}</p>\n <template v-if=\"state === 'approved'\">\n <div class=\"login-success\" role=\"status\"><AppIcon name=\"check\" :size=\"24\" /><strong>登录成功</strong></div>\n <p>已完成 {{ provider.name }} 授权。</p>\n <button class=\"primary\" type=\"button\" @click=\"close\">完成</button>\n </template>\n <template v-else>\n <template v-if=\"state==='ready'\">\n <ModelNetworkChoice v-if=\"network\" v-model=\"policy\" :view=\"network\" :provider=\"provider.id\" :bot=\"bot\" compact :disabled=\"saving\" @busy=\"networkBusy=$event\" />\n <button v-else type=\"button\" @click=\"loadNetwork\">重新读取网络配置</button>\n </template>\n <div v-else class=\"network-summary\"><span>网络:{{ networkSummary }}</span><button v-if=\"state==='pending' || state==='failed'\" type=\"button\" @click=\"changeNetwork\">更改</button></div>\n <template v-if=\"state==='failed'\">\n <div class=\"failure-heading\" role=\"alert\"><AppIcon name=\"alert\" :size=\"24\" /><strong>{{ error }}</strong></div>\n <ModelNetworkChoice v-if=\"network\" v-model=\"policy\" :view=\"network\" :provider=\"provider.id\" :bot=\"bot\" summary-only @busy=\"networkBusy=$event\" />\n <p class=\"hint\">不会自动切换为直接连接。</p>\n <button type=\"button\" class=\"primary\" @click=\"changeNetwork\">检查网络设置</button>\n </template>\n <template v-if=\"webLogin\">\n <p v-if=\"state !== 'failed' && state !== 'ready'\" role=\"status\">{{ message }}</p>\n <button v-if=\"state==='ready'\" type=\"button\" class=\"primary\" :disabled=\"!canStart\" @click=\"start\">{{ networkDirty?'保存并开始登录':'开始登录' }}</button>\n <template v-if=\"state === 'pending'\">\n <div v-if=\"session?.user_code\" class=\"device-code\"><span>在授权页面输入验证码</span><strong>{{ session.user_code }}</strong><button type=\"button\" @click=\"copyCode\">复制验证码</button><small v-if=\"copyNotice\" role=\"status\">{{ copyNotice }}</small></div>\n <a v-if=\"verificationUrl\" class=\"primary\" :href=\"verificationUrl\" target=\"_blank\" rel=\"noopener noreferrer\"><AppIcon name=\"external\" :size=\"16\" />打开授权页面</a>\n <p class=\"hint\">{{ verificationUrl ? '在浏览器完成授权后,回到这里查看结果。' : '登录服务准备好后,授权入口会自动显示。' }}</p>\n </template>\n </template>\n <form v-else-if=\"tokenLogin\" @submit.prevent=\"submitToken\">\n <p>粘贴已有的 API Key 或登录令牌完成连接。</p>\n <label>登录凭据<input v-model=\"token\" :aria-label=\"`${provider.name} 凭据`\" type=\"password\" autocomplete=\"new-password\" :disabled=\"saving\" placeholder=\"粘贴登录凭据\" /></label>\n <details v-if=\"provider.cli_command\"><summary>如何获取登录令牌</summary><p>在服务器上按该服务的登录指引运行:</p><code>{{ provider.cli_command }}</code></details>\n <button type=\"submit\" class=\"primary\" :disabled=\"saving || !token.trim() || !canStart\">{{ saving ? '正在保存…' : networkDirty?'保存网络与凭据':'保存凭据' }}</button>\n </form>\n <template v-else><p>此服务需要先在服务器上完成登录:</p><code>{{ provider.cli_command }}</code><p class=\"hint\">完成后关闭弹窗,并刷新连接状态。</p></template>\n <p v-if=\"error && state!=='failed'\" class=\"error\" role=\"alert\">{{ error }}</p>\n <button v-if=\"webLogin && state === 'failed'\" type=\"button\" :disabled=\"!canStart\" @click=\"start\">{{ networkDirty?'保存并重新登录':'重新登录' }}</button>\n <p v-if=\"webLogin\" class=\"hint\">授权网页使用当前设备的浏览器网络。此代理仅作用于服务器发起的连接。</p>\n <button type=\"button\" :disabled=\"saving\" @click=\"close\">{{ state === 'pending' || state === 'starting' ? '取消登录' : '关闭' }}</button>\n </template>\n </div>\n </StandaloneDialog>\n</template>\n\n<style scoped>\n.failure-heading{display:flex;align-items:flex-start;gap:12px;color:var(--danger);font-size:18px}.failure-heading .app-icon{flex-shrink:0;margin-top:2px}\n.network-summary{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;color:var(--text-secondary)}\n.provider-login,.provider-login form{display:grid;gap:16px;min-width:0;color:var(--text-primary);font-size:14px;line-height:1.6}.provider-login p{margin:0;overflow-wrap:anywhere}.scope,.hint,.device-code>span{color:var(--text-secondary);font-size:13px}.device-code{display:grid;gap:12px;padding:20px 12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-soft);text-align:center}.device-code strong{font:650 26px/1.4 var(--font-ui);letter-spacing:2px;overflow-wrap:anywhere;user-select:all}.provider-login button,.provider-login a{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;color:var(--text-primary);background:var(--surface);font:inherit;cursor:pointer;text-decoration:none;box-sizing:border-box}.provider-login .primary{background:var(--accent);color:var(--text-on-solid);border-color:var(--accent)}.provider-login button:disabled{opacity:.5;cursor:default}.provider-login button:hover:not(:disabled){filter:brightness(.95)}.provider-login label{display:grid;gap:8px}.provider-login input{width:100%;min-width:0;box-sizing:border-box;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);font:inherit}.provider-login code{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px}.provider-login summary{cursor:pointer;min-height:44px;align-content:center}.provider-login :focus-visible{outline:2px solid var(--accent);outline-offset:3px}.error{color:var(--danger)}.login-success{display:flex;align-items:center;gap:10px;color:var(--success)}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport StandaloneDialog from '@/components/common/StandaloneDialog.vue'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport { hermesApi, type HermesDeviceStart, type HermesOAuthProvider } from '@/api/hermes'\nimport { copyTextToClipboard } from '@/utils/clipboard'\nimport ModelNetworkChoice from './ModelNetworkChoice.vue'\nimport { modelNetworkApi, networkLabel } from '@/api/modelNetwork'\nimport type { ModelNetworkView, NetworkPolicy } from '@shared/modelNetwork'\n\nconst props = defineProps<{ provider: HermesOAuthProvider; bot?: string }>()\nconst emit = defineEmits<{ close: []; success: [] }>()\n// Capture the scope for the lifetime of this login, including late cancellation.\nconst provider = props.provider, bot = props.bot\nconst webLogin = provider.flow === 'device_code' || ['device_code', 'process'].includes(provider.web_flow ?? '')\nconst tokenLogin = provider.web_flow === 'paste_token'\nconst state = ref<'starting' | 'pending' | 'failed' | 'approved' | 'ready'>('ready')\nconst session = ref<HermesDeviceStart>(), error = ref(''), token = ref(''), saving = ref(false), copyNotice = ref('')\nconst network = ref<ModelNetworkView>(), policy = ref<NetworkPolicy>({mode:'inherit'}), networkBusy = ref(false)\nconst networkDirty = computed(() => !!network.value && JSON.stringify(policy.value) !== JSON.stringify(network.value.connections[provider.id] ?? {mode:'inherit'}))\nconst canStart = computed(() => !!network.value && !networkBusy.value && (policy.value.mode !== 'proxy' || !!policy.value.proxyId))\nconst networkSummary = computed(() => network.value ? networkLabel(network.value, policy.value) : '正在读取网络…')\nasync function loadNetwork() {\n try { const value=await modelNetworkApi.get(bot);if(alive){network.value=value;policy.value={...value.connections[provider.id] ?? {mode:'inherit'}};error.value=''} }\n catch(cause) { if(alive)error.value=cause instanceof Error?cause.message:'无法读取网络配置,请重试。' }\n}\nasync function saveNetwork() {\n if(networkDirty.value){network.value=await modelNetworkApi.save(network.value!.revision,policy.value,provider.id,bot)}\n}\nasync function changeNetwork() {\n if(session.value && !window.confirm('更改网络将结束本次授权,需要重新获取验证码。继续?'))return\n generation++;clearTimeout(timer)\n try{await release(session.value);session.value=undefined;state.value='ready';error.value='';await loadNetwork()}\n catch(cause){state.value='failed';error.value=cause instanceof Error?cause.message:'无法取消本次授权,请重试。'}\n}\nlet alive = true, generation = 0, timer: ReturnType<typeof setTimeout> | undefined\nconst verificationUrl = computed(() => {\n try { const url = new URL(session.value?.verification_url ?? ''); return ['https:', 'http:'].includes(url.protocol) && !url.username && !url.password ? url.href : '' } catch { return '' }\n})\nconst message = computed(() => state.value === 'starting' ? '正在准备登录…' : state.value === 'approved' ? '登录成功' : verificationUrl.value ? '等待你在浏览器中完成授权…' : '正在准备授权链接…')\n\nasync function release(current?: HermesDeviceStart) {\n if (current) await hermesApi.oauthCancel(current.session_id, bot)\n}\nasync function start() {\n if (state.value === 'starting' || saving.value || !canStart.value) return\n const currentGeneration = ++generation\n clearTimeout(timer); error.value = ''; copyNotice.value = ''; state.value = 'starting'\n try {\n await release(session.value); session.value = undefined\n await saveNetwork()\n if (!alive || currentGeneration !== generation) return\n const next = await hermesApi.oauthStart(provider.id, bot, network.value!.revision)\n if (!alive || currentGeneration !== generation) { await release(next); return }\n session.value = next; state.value = 'pending'; poll(currentGeneration)\n } catch (cause) {\n if (!alive || currentGeneration !== generation) return\n error.value = cause instanceof Error ? cause.message : '无法开始登录,请重试。'; state.value = 'failed'\n }\n}\nfunction poll(currentGeneration: number) {\n const current = session.value\n if (!current || !alive) return\n timer = setTimeout(async () => {\n try {\n const result = await hermesApi.oauthPoll(provider.id, current.session_id, bot)\n if (!alive || currentGeneration !== generation) return\n if (result.user_code) current.user_code = result.user_code\n if (result.verification_url) current.verification_url = result.verification_url\n if (result.status === 'pending') { poll(currentGeneration); return }\n if (result.status === 'approved') { session.value = undefined; state.value = 'approved'; emit('success'); return }\n error.value = result.error_message || (result.status === 'expired' ? '授权已过期,请重新登录。' : result.status === 'denied' ? '账号已拒绝授权,可重新开始登录。' : '登录未完成,请重试。')\n state.value = 'failed'\n } catch (cause) {\n if (!alive || currentGeneration !== generation) return\n error.value = cause instanceof Error ? cause.message : '无法获取授权结果,请重试。'; state.value = 'failed'\n }\n }, Math.max(1, current.poll_interval || 2) * 1000)\n}\nasync function submitToken() {\n if (saving.value || !token.value.trim() || !canStart.value) return\n saving.value = true; error.value = ''\n try {\n await saveNetwork()\n await hermesApi.oauthSubmit(provider.id, token.value.trim(), bot)\n if (!alive) return\n token.value = ''; state.value = 'approved'; emit('success')\n } catch (cause) { if (alive) error.value = cause instanceof Error ? cause.message : '凭据保存失败,请重试。' }\n finally { saving.value = false }\n}\nfunction beforeClose() {\n return !saving.value && (!(token.value || networkDirty.value) || window.confirm('还有未保存的输入,放弃本次更改?'))\n}\nfunction close() { if (beforeClose()) emit('close') }\nasync function copyCode() {\n copyNotice.value = await copyTextToClipboard(session.value?.user_code ?? '') ? '验证码已复制' : '无法自动复制,请选中验证码手动复制。'\n}\nonMounted(loadNetwork)\nonBeforeUnmount(() => {\n alive = false; generation++; clearTimeout(timer)\n const current = session.value; session.value = undefined; token.value = ''\n void release(current).catch(() => { /* The server also expires abandoned sessions. */ })\n})\n</script>\n\n<template>\n <StandaloneDialog :title=\"`登录 ${provider.name}`\" compact wide :before-close=\"beforeClose\" @close=\"emit('close')\">\n <div class=\"provider-login\">\n <p class=\"scope\">{{ bot ? '仅对此 Bot 生效' : '服务器模型连接' }}</p>\n <template v-if=\"state === 'approved'\">\n <div class=\"login-success\" role=\"status\"><AppIcon name=\"check\" :size=\"24\" /><strong>登录成功</strong></div>\n <p>已完成 {{ provider.name }} 授权。</p>\n <button class=\"primary\" type=\"button\" @click=\"close\">完成</button>\n </template>\n <template v-else>\n <template v-if=\"state==='ready'\">\n <ModelNetworkChoice v-if=\"network\" v-model=\"policy\" :view=\"network\" :provider=\"provider.id\" :bot=\"bot\" compact :disabled=\"saving\" @busy=\"networkBusy=$event\" />\n <button v-else type=\"button\" @click=\"loadNetwork\">重新读取网络配置</button>\n </template>\n <div v-else class=\"network-summary\"><span>网络:{{ networkSummary }}</span><button v-if=\"state==='pending' || state==='failed'\" type=\"button\" @click=\"changeNetwork\">更改</button></div>\n <template v-if=\"state==='failed'\">\n <div class=\"failure-heading\" role=\"alert\"><AppIcon name=\"alert\" :size=\"24\" /><strong>{{ error }}</strong></div>\n <ModelNetworkChoice v-if=\"network\" v-model=\"policy\" :view=\"network\" :provider=\"provider.id\" :bot=\"bot\" summary-only @busy=\"networkBusy=$event\" />\n <p class=\"hint\">不会自动切换为直接连接。</p>\n <button type=\"button\" class=\"primary\" @click=\"changeNetwork\">检查网络设置</button>\n </template>\n <template v-if=\"webLogin\">\n <p v-if=\"state !== 'failed' && state !== 'ready'\" role=\"status\">{{ message }}</p>\n <button v-if=\"state==='ready'\" type=\"button\" class=\"primary\" :disabled=\"!canStart\" @click=\"start\">{{ networkDirty?'保存并开始登录':'开始登录' }}</button>\n <template v-if=\"state === 'pending'\">\n <div v-if=\"session?.user_code\" class=\"device-code\"><span>在授权页面输入验证码</span><strong>{{ session.user_code }}</strong><button type=\"button\" @click=\"copyCode\">复制验证码</button><small v-if=\"copyNotice\" role=\"status\">{{ copyNotice }}</small></div>\n <a v-if=\"verificationUrl\" class=\"primary\" :href=\"verificationUrl\" target=\"_blank\" rel=\"noopener noreferrer\"><AppIcon name=\"external\" :size=\"16\" />打开授权页面</a>\n <p class=\"hint\">{{ verificationUrl ? '在浏览器完成授权后,回到这里查看结果。' : '登录服务准备好后,授权入口会自动显示。' }}</p>\n </template>\n </template>\n <form v-else-if=\"tokenLogin\" @submit.prevent=\"submitToken\">\n <p>粘贴已有的 API Key 或登录令牌完成连接。</p>\n <label>登录凭据<input v-model=\"token\" :aria-label=\"`${provider.name} 凭据`\" type=\"password\" autocomplete=\"new-password\" :disabled=\"saving\" placeholder=\"粘贴登录凭据\" /></label>\n <details v-if=\"provider.cli_command\"><summary>如何获取登录令牌</summary><p>在服务器上按该服务的登录指引运行:</p><code>{{ provider.cli_command }}</code></details>\n <button type=\"submit\" class=\"primary\" :disabled=\"saving || !token.trim() || !canStart\">{{ saving ? '正在保存…' : networkDirty?'保存网络与凭据':'保存凭据' }}</button>\n </form>\n <template v-else><p>此服务需要先在服务器上完成登录:</p><code>{{ provider.cli_command }}</code><p class=\"hint\">完成后关闭弹窗,并刷新连接状态。</p></template>\n <p v-if=\"error && state!=='failed'\" class=\"error\" role=\"alert\">{{ error }}</p>\n <button v-if=\"webLogin && state === 'failed'\" type=\"button\" :disabled=\"!canStart\" @click=\"start\">{{ networkDirty?'保存并重新登录':'重新登录' }}</button>\n <p v-if=\"webLogin\" class=\"hint\">授权网页使用当前设备的浏览器网络。此代理仅作用于服务器发起的连接。</p>\n <button type=\"button\" :disabled=\"saving\" @click=\"close\">{{ state === 'pending' || state === 'starting' ? '取消登录' : '关闭' }}</button>\n </template>\n </div>\n </StandaloneDialog>\n</template>\n\n<style scoped>\n.failure-heading{display:flex;align-items:flex-start;gap:12px;color:var(--danger);font-size:18px}.failure-heading .app-icon{flex-shrink:0;margin-top:2px}\n.network-summary{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;color:var(--text-secondary)}\n.provider-login,.provider-login form{display:grid;gap:16px;min-width:0;color:var(--text-primary);font-size:14px;line-height:1.6}.provider-login p{margin:0;overflow-wrap:anywhere}.scope,.hint,.device-code>span{color:var(--text-secondary);font-size:13px}.device-code{display:grid;gap:12px;padding:20px 12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-soft);text-align:center}.device-code strong{font:650 26px/1.4 var(--font-ui);letter-spacing:2px;overflow-wrap:anywhere;user-select:all}.provider-login button,.provider-login a{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;color:var(--text-primary);background:var(--surface);font:inherit;cursor:pointer;text-decoration:none;box-sizing:border-box}.provider-login .primary{background:var(--accent);color:var(--text-on-solid);border-color:var(--accent)}.provider-login button:disabled{opacity:.5;cursor:default}.provider-login button:hover:not(:disabled){filter:brightness(.95)}.provider-login label{display:grid;gap:8px}.provider-login input{width:100%;min-width:0;box-sizing:border-box;min-height:44px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);font:inherit}.provider-login code{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px}.provider-login summary{cursor:pointer;min-height:44px;align-content:center}.provider-login :focus-visible{outline:2px solid var(--accent);outline-offset:3px}.error{color:var(--danger)}.login-success{display:flex;align-items:center;gap:10px;color:var(--success)}\n</style>\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{Dt as e,F as t,G as n,L as r,M as i,_ as a,a as o,b as s,d as c,et as l,m as u,p as d,t as f}from"./_plugin-vue_export-helper-BumXM028.js";import{G as p,J as m,V as h,W as g,w as _}from"./index-D0pMYnOi.js";var ee=`sha256:`+`0`.repeat(64),te={class:`runner-panel`,"aria-label":`执行节点设置`},ne={key:0},re={class:`runner-toolbar`},ie=[`disabled`],v={key:1,class:`runner-list`},y=[`disabled`,`aria-label`,`onClick`],ae={key:2},b=[`disabled`],x=[`disabled`],S=[`disabled`],oe={class:`runner-check`},se=[`disabled`],ce={class:`runner-check`},le=[`disabled`],ue={class:`runner-check`},C=[`disabled`],w={key:0,class:`runner-computer`},T={key:0},E={key:1},D={key:2},O={key:3},k={key:4},A=[`disabled`],j={key:4,class:`runner-setup`},M={key:0},N={key:1},P={key:5,class:`runner-error`,role:`alert`},F={key:6,role:`status`},I=f(s({__name:`RunnerSettingsPanel`,props:{computerSetup:{type:Boolean}},setup(s){let f=s,I=l(!1),L=l(!1),R=l([]),z=l(!1),B=l(``),V=l(``),H=l(``),U=l(window.location.origin),W=l(`public-proxy`),G=l(!0),K=l(!!f.computerSetup),q=l(`docker`),J=l(``),Y=l(``),X=l(!1),Z=l();async function Q(){let e=await _(`/api/app/admin/runners`);R.value=e.runners,I.value=!!e.bundleAvailable,L.value=!!e.fixedDesktops}async function $(e){if(!z.value){B.value=``,V.value=``,z.value=!0;try{await e()}catch(e){B.value=e instanceof Error?e.message:`执行节点操作失败`}finally{z.value=!1}}}async function de(){await $(async()=>{let e=new URL(U.value);if(![`http:`,`https:`].includes(e.protocol)||e.username||e.password||e.search||e.hash||e.pathname!==`/`)throw Error(`请填写不带路径的Yaovi服务地址`);if(e.protocol===`http:`&&![`localhost`,`127.0.0.1`,`[::1]`].includes(e.hostname)&&!X.value)throw Error(`远程节点请使用 HTTPS,或勾选可信局域网 HTTP`);if(K.value&&J.value&&!/^sha256:[a-f0-9]{64}$/.test(J.value))throw Error(`自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。`);let t=await _(`/api/app/admin/runners`,{method:`POST`,body:{name:H.value.trim()}});Z.value={protocol:1,serverURL:e.origin,runnerId:t.runner.id,token:t.token,artifactRoots:Y.value.split(`
1
+ import{Dt as e,F as t,G as n,L as r,M as i,_ as a,a as o,b as s,d as c,et as l,m as u,p as d,t as f}from"./_plugin-vue_export-helper-BumXM028.js";import{G as p,J as m,V as h,W as g,w as _}from"./index-D11cgXI9.js";var ee=`sha256:`+`0`.repeat(64),te={class:`runner-panel`,"aria-label":`执行节点设置`},ne={key:0},re={class:`runner-toolbar`},ie=[`disabled`],v={key:1,class:`runner-list`},y=[`disabled`,`aria-label`,`onClick`],ae={key:2},b=[`disabled`],x=[`disabled`],S=[`disabled`],oe={class:`runner-check`},se=[`disabled`],ce={class:`runner-check`},le=[`disabled`],ue={class:`runner-check`},C=[`disabled`],w={key:0,class:`runner-computer`},T={key:0},E={key:1},D={key:2},O={key:3},k={key:4},A=[`disabled`],j={key:4,class:`runner-setup`},M={key:0},N={key:1},P={key:5,class:`runner-error`,role:`alert`},F={key:6,role:`status`},I=f(s({__name:`RunnerSettingsPanel`,props:{computerSetup:{type:Boolean}},setup(s){let f=s,I=l(!1),L=l(!1),R=l([]),z=l(!1),B=l(``),V=l(``),H=l(``),U=l(window.location.origin),W=l(`public-proxy`),G=l(!0),K=l(!!f.computerSetup),q=l(`docker`),J=l(``),Y=l(``),X=l(!1),Z=l();async function Q(){let e=await _(`/api/app/admin/runners`);R.value=e.runners,I.value=!!e.bundleAvailable,L.value=!!e.fixedDesktops}async function $(e){if(!z.value){B.value=``,V.value=``,z.value=!0;try{await e()}catch(e){B.value=e instanceof Error?e.message:`执行节点操作失败`}finally{z.value=!1}}}async function de(){await $(async()=>{let e=new URL(U.value);if(![`http:`,`https:`].includes(e.protocol)||e.username||e.password||e.search||e.hash||e.pathname!==`/`)throw Error(`请填写不带路径的Yaovi服务地址`);if(e.protocol===`http:`&&![`localhost`,`127.0.0.1`,`[::1]`].includes(e.hostname)&&!X.value)throw Error(`远程节点请使用 HTTPS,或勾选可信局域网 HTTP`);if(K.value&&J.value&&!/^sha256:[a-f0-9]{64}$/.test(J.value))throw Error(`自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。`);let t=await _(`/api/app/admin/runners`,{method:`POST`,body:{name:H.value.trim()}});Z.value={protocol:1,serverURL:e.origin,runnerId:t.runner.id,token:t.token,artifactRoots:Y.value.split(`
2
2
  `).map(e=>e.trim()).filter(Boolean),...X.value?{allowInsecureLan:!0}:{},...K.value?{computers:{...L.value?{managedBy:`compose`}:{},network:L.value?`none`:W.value,runtime:q.value,imageId:J.value||ee}}:{}},Z.value.browser={enabled:G.value},V.value=`节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。`,await Q()})}function fe(){if(!Z.value)return;let e=URL.createObjectURL(new Blob([JSON.stringify(Z.value,null,2)+`
3
3
  `],{type:`application/json`})),t=document.createElement(`a`);t.href=e,t.download=`runner.json`,t.click(),setTimeout(()=>URL.revokeObjectURL(e),1e3)}function pe(e){$(async()=>{await _(`/api/app/admin/runners/${e.id}`,{method:`DELETE`,body:{}}),Z.value?.runnerId===e.id&&(Z.value=void 0),await Q(),V.value=`已停用 ${e.name}`})}return i(()=>{$(Q)}),(i,s)=>(t(),u(`section`,te,[s[30]||=c(`p`,null,`执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。`,-1),I.value?(t(),u(`p`,ne,[...s[10]||=[c(`a`,{href:`/api/app/admin/runners/bundle`,download:``},`下载配套执行节点程序`,-1),a(`(包含桌面资源,需要 Node.js 24 或更高版本)。`,-1)]])):d(``,!0),c(`div`,re,[s[11]||=c(`strong`,null,`已注册节点`,-1),c(`button`,{type:`button`,disabled:z.value,onClick:s[0]||=e=>$(Q)},`刷新状态`,8,ie)]),R.value.length?(t(),u(`ul`,v,[(t(!0),u(o,null,r(R.value,n=>(t(),u(`li`,{key:n.id},[c(`div`,null,[c(`strong`,null,e(n.name),1),c(`span`,null,e(n.enabled?n.online?`已连接`:`待连接`:`已停用`),1)]),n.enabled?(t(),u(`button`,{key:0,type:`button`,disabled:z.value,"aria-label":`停用 ${n.name}`,onClick:e=>pe(n)},`停用`,8,y)):d(``,!0)]))),128))])):(t(),u(`p`,ae,`尚未注册执行节点。`)),Z.value?(t(),u(`div`,j,[c(`button`,{class:`solid-button`,type:`button`,onClick:fe},`下载 Runner 配置`),s[29]||=c(`p`,null,`配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:`,-1),I.value?(t(),u(`code`,M,[...s[27]||=[a(`mkdir -p yaovi-runner`,-1),c(`br`,null,null,-1),a(`tar -xzf yaovi-runner.tar.gz -C yaovi-runner`,-1),c(`br`,null,null,-1),a(`chmod 600 runner.json`,-1),c(`br`,null,null,-1),a(`node yaovi-runner/runner.mjs --config /完整路径/runner.json`,-1)]])):(t(),u(`code`,N,[...s[28]||=[a(`chmod 600 runner.json`,-1),c(`br`,null,null,-1),a(`npm run runner -- --config /完整路径/runner.json`,-1)]])),c(`small`,null,e(I.value?Z.value.computers?`在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。`:`在执行电脑上启动节点。`:`在 yaovi 项目中先运行 npm run build。`)+`配置丢失后可停用节点并重新注册。`,1)])):(t(),u(`form`,{key:3,onSubmit:m(de,[`prevent`])},[c(`label`,null,[s[12]||=a(`节点名称`,-1),n(c(`input`,{"onUpdate:modelValue":s[1]||=e=>H.value=e,required:``,maxlength:`100`,autocomplete:`off`,placeholder:`我的 Mac`,disabled:z.value},null,8,b),[[p,H.value]])]),c(`label`,null,[s[13]||=a(`Yaovi服务地址`,-1),n(c(`input`,{"onUpdate:modelValue":s[2]||=e=>U.value=e,type:`url`,required:``,disabled:z.value},null,8,x),[[p,U.value]]),s[14]||=c(`small`,null,`必须能从 Runner 所在电脑访问。`,-1)]),c(`label`,null,[s[15]||=a(`允许导出的产物目录`,-1),n(c(`textarea`,{"onUpdate:modelValue":s[3]||=e=>Y.value=e,rows:`2`,disabled:z.value,placeholder:`每行一个绝对路径,留空时禁止导出文件`},null,8,S),[[p,Y.value]])]),c(`label`,oe,[n(c(`input`,{"onUpdate:modelValue":s[4]||=e=>X.value=e,type:`checkbox`,disabled:z.value},null,8,se),[[h,X.value]]),s[16]||=a(`使用可信局域网 HTTP`,-1)]),c(`label`,ce,[n(c(`input`,{"onUpdate:modelValue":s[5]||=e=>G.value=e,type:`checkbox`,disabled:z.value},null,8,le),[[h,G.value]]),s[17]||=a(`托管浏览器`,-1)]),s[26]||=c(`small`,null,`默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。`,-1),c(`label`,ue,[n(c(`input`,{"onUpdate:modelValue":s[6]||=e=>K.value=e,type:`checkbox`,disabled:z.value},null,8,C),[[h,K.value]]),s[18]||=a(`启用隔离电脑 Worker`,-1)]),K.value?(t(),u(`fieldset`,w,[s[24]||=c(`legend`,null,`执行电脑配置`,-1),L.value?(t(),u(`p`,T,`桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。`)):d(``,!0),L.value?d(``,!0):(t(),u(`label`,E,[s[20]||=a(`容器运行时`,-1),n(c(`select`,{"onUpdate:modelValue":s[7]||=e=>q.value=e},[...s[19]||=[c(`option`,{value:`docker`},`Docker`,-1),c(`option`,{value:`podman`},`Podman`,-1)]],512),[[g,q.value]])])),L.value?d(``,!0):(t(),u(`label`,D,[s[22]||=a(`联网范围`,-1),n(c(`select`,{"onUpdate:modelValue":s[8]||=e=>W.value=e},[...s[21]||=[c(`option`,{value:`public-proxy`},`公网网页(HTTP / HTTPS)`,-1),c(`option`,{value:`none`},`不联网`,-1)]],512),[[g,W.value]])])),L.value?d(``,!0):(t(),u(`small`,O,`公网模式通过受控代理访问,不开放宿主和内网地址。`)),L.value?d(``,!0):(t(),u(`label`,k,[s[23]||=a(`兼容镜像 ID`,-1),n(c(`input`,{"onUpdate:modelValue":s[9]||=e=>J.value=e,placeholder:`可在本地虚拟机设置中准备`},null,512),[[p,J.value,void 0,{trim:!0}]])])),s[25]||=c(`small`,null,`这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。`,-1)])):d(``,!0),c(`button`,{class:`solid-button`,disabled:z.value||!H.value.trim()},`注册节点`,8,A)],32)),B.value?(t(),u(`p`,P,e(B.value),1)):d(``,!0),V.value?(t(),u(`p`,F,e(V.value),1)):d(``,!0)]))}}),[[`__scopeId`,`data-v-da99aca0`]]);export{I as t};
4
- //# sourceMappingURL=RunnerSettingsPanel-B-fwuBEs.js.map
4
+ //# sourceMappingURL=RunnerSettingsPanel-DjMGnXGP.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RunnerSettingsPanel-B-fwuBEs.js","names":[],"sources":["../../src/shared/runner.ts","../../src/client/components/app/RunnerSettingsPanel.vue","../../src/client/components/app/RunnerSettingsPanel.vue"],"sourcesContent":["export const RUNNER_PROTOCOL = 1\nexport const VM_NETWORK_FEATURE = 'vm-network-policy-v2'\nexport const VM_MAINTENANCE_FEATURE = 'vm-maintenance-v1'\nexport interface RunnerRecord {\n id: string\n name: string\n sourceNodeId: string\n sourceOwner: string\n tokenHash: string\n enabled: boolean\n createdAt: number\n}\nexport interface RunnerCommand {\n id: string\n kind: 'computer.cleanup' | 'network.configure' | 'network.test' | 'browser.invoke' | 'local-vm.manage' | 'computer.control' | 'computer.retire' | 'http' | 'gateway.open' | 'gateway.rpc' | 'gateway.close' | 'lease.create' | 'lease.bind' | 'lease.close'\n payload: Record<string, unknown>\n expiresAt: number\n}\nexport interface RunnerConfiguration {\n protocol: 1\n serverURL: string\n runnerId: string\n token: string\n artifactRoots: string[]\n allowInsecureLan?: boolean\n /** Opt-in, independent of the full Linux desktop. */\n browser?: { enabled: boolean; maxSessions?: number; idleTimeoutMs?: number }\n computers?: {managedBy?:'compose';network?:'none'|'public-proxy';runtime:'docker'|'podman';imageId:string;maxConcurrent?:number}\n}\nexport const UNCONFIGURED_COMPUTER_IMAGE='sha256:'+'0'.repeat(64)\n","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { apiRequest } from '@/api/client'\nimport { UNCONFIGURED_COMPUTER_IMAGE } from '@shared/runner'\nimport type { RunnerConfiguration, RunnerRecord } from '@shared/runner'\n\ntype Runner = Omit<RunnerRecord, 'tokenHash' | 'sourceOwner'> & {online: boolean}\nconst props=defineProps<{computerSetup?:boolean}>()\nconst bundleAvailable=ref(false),fixedDesktops=ref(false)\nconst runners = ref<Runner[]>([])\nconst busy = ref(false), error = ref(''), notice = ref('')\nconst name = ref('')\nconst serverURL = ref(window.location.origin)\nconst computerNetwork=ref<'none'|'public-proxy'>('public-proxy')\nconst managedBrowser=ref(true)\nconst computers=ref(!!props.computerSetup),computerRuntime=ref<'docker'|'podman'>('docker'),computerImage=ref('')\nconst artifactRoots = ref(''), insecure = ref(false), configuration = ref<RunnerConfiguration>()\n\nasync function refresh() {\n const data = await apiRequest<{runners:Runner[];bundleAvailable?:boolean;fixedDesktops?:boolean;modelProvider?:string}>('/api/app/admin/runners')\n runners.value = data.runners;bundleAvailable.value=!!data.bundleAvailable;fixedDesktops.value=!!data.fixedDesktops\n}\nasync function action(work:()=>Promise<void>) {\n if(busy.value) return\n error.value = ''; notice.value = ''; busy.value = true\n try { await work() } catch(cause) { error.value = cause instanceof Error ? cause.message : '执行节点操作失败' }\n finally { busy.value = false }\n}\nasync function register() {\n await action(async()=>{\n const web = new URL(serverURL.value)\n if(!['http:','https:'].includes(web.protocol)||web.username||web.password||web.search||web.hash||web.pathname!=='/')throw new Error('请填写不带路径的Yaovi服务地址')\n if(web.protocol==='http:'&&!['localhost','127.0.0.1','[::1]'].includes(web.hostname)&&!insecure.value)throw new Error('远程节点请使用 HTTPS,或勾选可信局域网 HTTP')\n if(computers.value&&((!!computerImage.value&&!/^sha256:[a-f0-9]{64}$/.test(computerImage.value))))throw new Error('自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。')\n const result = await apiRequest<{runner:Runner;token:string}>('/api/app/admin/runners', {method:'POST',body:{name:name.value.trim()}})\n configuration.value = {protocol:1,serverURL:web.origin,runnerId:result.runner.id,token:result.token,artifactRoots:artifactRoots.value.split('\\n').map(p=>p.trim()).filter(Boolean),...(insecure.value?{allowInsecureLan:true}:{}),...(computers.value?{computers:{...(fixedDesktops.value?{managedBy:'compose' as const}:{}),network:fixedDesktops.value?'none':computerNetwork.value,runtime:computerRuntime.value,imageId:computerImage.value||UNCONFIGURED_COMPUTER_IMAGE}}:{})}\n configuration.value.browser={enabled:managedBrowser.value}\n notice.value = '节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。'\n await refresh()\n })\n}\nfunction download() {\n if(!configuration.value) return\n const url = URL.createObjectURL(new Blob([JSON.stringify(configuration.value,null,2)+'\\n'],{type:'application/json'}))\n const anchor = document.createElement('a'); anchor.href = url; anchor.download = 'runner.json'; anchor.click()\n setTimeout(()=>URL.revokeObjectURL(url),1000)\n}\nfunction disable(runner:Runner) {\n void action(async()=>{\n await apiRequest(`/api/app/admin/runners/${runner.id}`,{method:'DELETE',body:{}})\n if(configuration.value?.runnerId===runner.id)configuration.value=undefined\n await refresh(); notice.value = `已停用 ${runner.name}`\n })\n}\nonMounted(()=>{void action(refresh)})\n</script>\n\n<template>\n <section class=\"runner-panel\" aria-label=\"执行节点设置\">\n <p>执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。</p>\n <p v-if=\"bundleAvailable\"><a href=\"/api/app/admin/runners/bundle\" download>下载配套执行节点程序</a>(包含桌面资源,需要 Node.js 24 或更高版本)。</p>\n <div class=\"runner-toolbar\"><strong>已注册节点</strong><button type=\"button\" :disabled=\"busy\" @click=\"action(refresh)\">刷新状态</button></div>\n <ul v-if=\"runners.length\" class=\"runner-list\">\n <li v-for=\"runner in runners\" :key=\"runner.id\">\n <div><strong>{{ runner.name }}</strong><span>{{ !runner.enabled ? '已停用' : runner.online ? '已连接' : '待连接' }}</span></div>\n <button v-if=\"runner.enabled\" type=\"button\" :disabled=\"busy\" :aria-label=\"`停用 ${runner.name}`\" @click=\"disable(runner)\">停用</button>\n </li>\n </ul>\n <p v-else>尚未注册执行节点。</p>\n <form v-if=\"!configuration\" @submit.prevent=\"register\">\n <label>节点名称<input v-model=\"name\" required maxlength=\"100\" autocomplete=\"off\" placeholder=\"我的 Mac\" :disabled=\"busy\"></label>\n <label>Yaovi服务地址<input v-model=\"serverURL\" type=\"url\" required :disabled=\"busy\"><small>必须能从 Runner 所在电脑访问。</small></label>\n <label>允许导出的产物目录<textarea v-model=\"artifactRoots\" rows=\"2\" :disabled=\"busy\" placeholder=\"每行一个绝对路径,留空时禁止导出文件\"></textarea></label>\n <label class=\"runner-check\"><input v-model=\"insecure\" type=\"checkbox\" :disabled=\"busy\">使用可信局域网 HTTP</label>\n <label class=\"runner-check\"><input v-model=\"managedBrowser\" type=\"checkbox\" :disabled=\"busy\">托管浏览器</label>\n <small>默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。</small>\n <label class=\"runner-check\"><input v-model=\"computers\" type=\"checkbox\" :disabled=\"busy\">启用隔离电脑 Worker</label>\n <fieldset v-if=\"computers\" class=\"runner-computer\">\n <legend>执行电脑配置</legend><p v-if=\"fixedDesktops\">桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。</p>\n <label v-if=\"!fixedDesktops\">容器运行时<select v-model=\"computerRuntime\"><option value=\"docker\">Docker</option><option value=\"podman\">Podman</option></select></label>\n <label v-if=\"!fixedDesktops\">联网范围<select v-model=\"computerNetwork\"><option value=\"public-proxy\">公网网页(HTTP / HTTPS)</option><option value=\"none\">不联网</option></select></label>\n <small v-if=\"!fixedDesktops\">公网模式通过受控代理访问,不开放宿主和内网地址。</small>\n <label v-if=\"!fixedDesktops\">兼容镜像 ID<input v-model.trim=\"computerImage\" placeholder=\"可在本地虚拟机设置中准备\"></label>\n <small>这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。</small>\n </fieldset>\n <button class=\"solid-button\" :disabled=\"busy || !name.trim()\">注册节点</button>\n </form>\n <div v-else class=\"runner-setup\">\n <button class=\"solid-button\" type=\"button\" @click=\"download\">下载 Runner 配置</button>\n <p>配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:</p>\n <code v-if=\"bundleAvailable\">mkdir -p yaovi-runner<br>tar -xzf yaovi-runner.tar.gz -C yaovi-runner<br>chmod 600 runner.json<br>node yaovi-runner/runner.mjs --config /完整路径/runner.json</code>\n <code v-else>chmod 600 runner.json<br>npm run runner -- --config /完整路径/runner.json</code>\n <small>{{bundleAvailable?(configuration.computers?'在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。':'在执行电脑上启动节点。'):'在 yaovi 项目中先运行 npm run build。'}}配置丢失后可停用节点并重新注册。</small>\n </div>\n <p v-if=\"error\" class=\"runner-error\" role=\"alert\">{{ error }}</p>\n <p v-if=\"notice\" role=\"status\">{{ notice }}</p>\n </section>\n</template>\n\n<style scoped>\n.runner-panel { display: grid; gap: 14px; min-width: 0; font-size: 13px; }\n.runner-panel p { margin: 0; line-height: 1.6; color: var(--text-secondary); }\n.runner-toolbar,.runner-list li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.runner-panel button { min-height: 36px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); cursor: pointer; }\n.runner-panel button:disabled { opacity: .5; cursor: default; }\n.runner-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }\n.runner-list li { padding: 12px; border: 1px solid var(--line); border-radius: 10px; }\n.runner-list li div { display: grid; gap: 5px; overflow-wrap: anywhere; }\n.runner-list span,.runner-panel small { color: var(--text-secondary); font-size: 12px; line-height: 1.5; }\n.runner-panel form,.runner-panel label,.runner-setup { display: grid; gap: 8px; min-width: 0; }\n.runner-panel form { gap: 14px; }\n.runner-computer { display: grid; gap: 12px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; min-width: 0; }\n.runner-panel input:not([type=checkbox]),.runner-panel select,.runner-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); font: inherit; }\n.runner-panel .runner-check { display: flex; align-items: center; }\n.runner-panel code { overflow-wrap: anywhere; line-height: 1.7; }\n.runner-panel .runner-error { color: var(--danger); }\n.runner-panel :is(input,select,textarea,button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n</style>","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { apiRequest } from '@/api/client'\nimport { UNCONFIGURED_COMPUTER_IMAGE } from '@shared/runner'\nimport type { RunnerConfiguration, RunnerRecord } from '@shared/runner'\n\ntype Runner = Omit<RunnerRecord, 'tokenHash' | 'sourceOwner'> & {online: boolean}\nconst props=defineProps<{computerSetup?:boolean}>()\nconst bundleAvailable=ref(false),fixedDesktops=ref(false)\nconst runners = ref<Runner[]>([])\nconst busy = ref(false), error = ref(''), notice = ref('')\nconst name = ref('')\nconst serverURL = ref(window.location.origin)\nconst computerNetwork=ref<'none'|'public-proxy'>('public-proxy')\nconst managedBrowser=ref(true)\nconst computers=ref(!!props.computerSetup),computerRuntime=ref<'docker'|'podman'>('docker'),computerImage=ref('')\nconst artifactRoots = ref(''), insecure = ref(false), configuration = ref<RunnerConfiguration>()\n\nasync function refresh() {\n const data = await apiRequest<{runners:Runner[];bundleAvailable?:boolean;fixedDesktops?:boolean;modelProvider?:string}>('/api/app/admin/runners')\n runners.value = data.runners;bundleAvailable.value=!!data.bundleAvailable;fixedDesktops.value=!!data.fixedDesktops\n}\nasync function action(work:()=>Promise<void>) {\n if(busy.value) return\n error.value = ''; notice.value = ''; busy.value = true\n try { await work() } catch(cause) { error.value = cause instanceof Error ? cause.message : '执行节点操作失败' }\n finally { busy.value = false }\n}\nasync function register() {\n await action(async()=>{\n const web = new URL(serverURL.value)\n if(!['http:','https:'].includes(web.protocol)||web.username||web.password||web.search||web.hash||web.pathname!=='/')throw new Error('请填写不带路径的Yaovi服务地址')\n if(web.protocol==='http:'&&!['localhost','127.0.0.1','[::1]'].includes(web.hostname)&&!insecure.value)throw new Error('远程节点请使用 HTTPS,或勾选可信局域网 HTTP')\n if(computers.value&&((!!computerImage.value&&!/^sha256:[a-f0-9]{64}$/.test(computerImage.value))))throw new Error('自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。')\n const result = await apiRequest<{runner:Runner;token:string}>('/api/app/admin/runners', {method:'POST',body:{name:name.value.trim()}})\n configuration.value = {protocol:1,serverURL:web.origin,runnerId:result.runner.id,token:result.token,artifactRoots:artifactRoots.value.split('\\n').map(p=>p.trim()).filter(Boolean),...(insecure.value?{allowInsecureLan:true}:{}),...(computers.value?{computers:{...(fixedDesktops.value?{managedBy:'compose' as const}:{}),network:fixedDesktops.value?'none':computerNetwork.value,runtime:computerRuntime.value,imageId:computerImage.value||UNCONFIGURED_COMPUTER_IMAGE}}:{})}\n configuration.value.browser={enabled:managedBrowser.value}\n notice.value = '节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。'\n await refresh()\n })\n}\nfunction download() {\n if(!configuration.value) return\n const url = URL.createObjectURL(new Blob([JSON.stringify(configuration.value,null,2)+'\\n'],{type:'application/json'}))\n const anchor = document.createElement('a'); anchor.href = url; anchor.download = 'runner.json'; anchor.click()\n setTimeout(()=>URL.revokeObjectURL(url),1000)\n}\nfunction disable(runner:Runner) {\n void action(async()=>{\n await apiRequest(`/api/app/admin/runners/${runner.id}`,{method:'DELETE',body:{}})\n if(configuration.value?.runnerId===runner.id)configuration.value=undefined\n await refresh(); notice.value = `已停用 ${runner.name}`\n })\n}\nonMounted(()=>{void action(refresh)})\n</script>\n\n<template>\n <section class=\"runner-panel\" aria-label=\"执行节点设置\">\n <p>执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。</p>\n <p v-if=\"bundleAvailable\"><a href=\"/api/app/admin/runners/bundle\" download>下载配套执行节点程序</a>(包含桌面资源,需要 Node.js 24 或更高版本)。</p>\n <div class=\"runner-toolbar\"><strong>已注册节点</strong><button type=\"button\" :disabled=\"busy\" @click=\"action(refresh)\">刷新状态</button></div>\n <ul v-if=\"runners.length\" class=\"runner-list\">\n <li v-for=\"runner in runners\" :key=\"runner.id\">\n <div><strong>{{ runner.name }}</strong><span>{{ !runner.enabled ? '已停用' : runner.online ? '已连接' : '待连接' }}</span></div>\n <button v-if=\"runner.enabled\" type=\"button\" :disabled=\"busy\" :aria-label=\"`停用 ${runner.name}`\" @click=\"disable(runner)\">停用</button>\n </li>\n </ul>\n <p v-else>尚未注册执行节点。</p>\n <form v-if=\"!configuration\" @submit.prevent=\"register\">\n <label>节点名称<input v-model=\"name\" required maxlength=\"100\" autocomplete=\"off\" placeholder=\"我的 Mac\" :disabled=\"busy\"></label>\n <label>Yaovi服务地址<input v-model=\"serverURL\" type=\"url\" required :disabled=\"busy\"><small>必须能从 Runner 所在电脑访问。</small></label>\n <label>允许导出的产物目录<textarea v-model=\"artifactRoots\" rows=\"2\" :disabled=\"busy\" placeholder=\"每行一个绝对路径,留空时禁止导出文件\"></textarea></label>\n <label class=\"runner-check\"><input v-model=\"insecure\" type=\"checkbox\" :disabled=\"busy\">使用可信局域网 HTTP</label>\n <label class=\"runner-check\"><input v-model=\"managedBrowser\" type=\"checkbox\" :disabled=\"busy\">托管浏览器</label>\n <small>默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。</small>\n <label class=\"runner-check\"><input v-model=\"computers\" type=\"checkbox\" :disabled=\"busy\">启用隔离电脑 Worker</label>\n <fieldset v-if=\"computers\" class=\"runner-computer\">\n <legend>执行电脑配置</legend><p v-if=\"fixedDesktops\">桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。</p>\n <label v-if=\"!fixedDesktops\">容器运行时<select v-model=\"computerRuntime\"><option value=\"docker\">Docker</option><option value=\"podman\">Podman</option></select></label>\n <label v-if=\"!fixedDesktops\">联网范围<select v-model=\"computerNetwork\"><option value=\"public-proxy\">公网网页(HTTP / HTTPS)</option><option value=\"none\">不联网</option></select></label>\n <small v-if=\"!fixedDesktops\">公网模式通过受控代理访问,不开放宿主和内网地址。</small>\n <label v-if=\"!fixedDesktops\">兼容镜像 ID<input v-model.trim=\"computerImage\" placeholder=\"可在本地虚拟机设置中准备\"></label>\n <small>这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。</small>\n </fieldset>\n <button class=\"solid-button\" :disabled=\"busy || !name.trim()\">注册节点</button>\n </form>\n <div v-else class=\"runner-setup\">\n <button class=\"solid-button\" type=\"button\" @click=\"download\">下载 Runner 配置</button>\n <p>配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:</p>\n <code v-if=\"bundleAvailable\">mkdir -p yaovi-runner<br>tar -xzf yaovi-runner.tar.gz -C yaovi-runner<br>chmod 600 runner.json<br>node yaovi-runner/runner.mjs --config /完整路径/runner.json</code>\n <code v-else>chmod 600 runner.json<br>npm run runner -- --config /完整路径/runner.json</code>\n <small>{{bundleAvailable?(configuration.computers?'在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。':'在执行电脑上启动节点。'):'在 yaovi 项目中先运行 npm run build。'}}配置丢失后可停用节点并重新注册。</small>\n </div>\n <p v-if=\"error\" class=\"runner-error\" role=\"alert\">{{ error }}</p>\n <p v-if=\"notice\" role=\"status\">{{ notice }}</p>\n </section>\n</template>\n\n<style scoped>\n.runner-panel { display: grid; gap: 14px; min-width: 0; font-size: 13px; }\n.runner-panel p { margin: 0; line-height: 1.6; color: var(--text-secondary); }\n.runner-toolbar,.runner-list li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.runner-panel button { min-height: 36px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); cursor: pointer; }\n.runner-panel button:disabled { opacity: .5; cursor: default; }\n.runner-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }\n.runner-list li { padding: 12px; border: 1px solid var(--line); border-radius: 10px; }\n.runner-list li div { display: grid; gap: 5px; overflow-wrap: anywhere; }\n.runner-list span,.runner-panel small { color: var(--text-secondary); font-size: 12px; line-height: 1.5; }\n.runner-panel form,.runner-panel label,.runner-setup { display: grid; gap: 8px; min-width: 0; }\n.runner-panel form { gap: 14px; }\n.runner-computer { display: grid; gap: 12px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; min-width: 0; }\n.runner-panel input:not([type=checkbox]),.runner-panel select,.runner-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); font: inherit; }\n.runner-panel .runner-check { display: flex; align-items: center; }\n.runner-panel code { overflow-wrap: anywhere; line-height: 1.7; }\n.runner-panel .runner-error { color: var(--danger); }\n.runner-panel :is(input,select,textarea,button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n</style>"],"mappings":"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"}
1
+ {"version":3,"file":"RunnerSettingsPanel-DjMGnXGP.js","names":[],"sources":["../../src/shared/runner.ts","../../src/client/components/app/RunnerSettingsPanel.vue","../../src/client/components/app/RunnerSettingsPanel.vue"],"sourcesContent":["export const RUNNER_PROTOCOL = 1\nexport const VM_NETWORK_FEATURE = 'vm-network-policy-v2'\nexport const VM_MAINTENANCE_FEATURE = 'vm-maintenance-v1'\nexport interface RunnerRecord {\n id: string\n name: string\n sourceNodeId: string\n sourceOwner: string\n tokenHash: string\n enabled: boolean\n createdAt: number\n}\nexport interface RunnerCommand {\n id: string\n kind: 'computer.cleanup' | 'network.configure' | 'network.test' | 'browser.invoke' | 'local-vm.manage' | 'computer.control' | 'computer.retire' | 'http' | 'gateway.open' | 'gateway.rpc' | 'gateway.close' | 'lease.create' | 'lease.bind' | 'lease.close'\n payload: Record<string, unknown>\n expiresAt: number\n}\nexport interface RunnerConfiguration {\n protocol: 1\n serverURL: string\n runnerId: string\n token: string\n artifactRoots: string[]\n allowInsecureLan?: boolean\n /** Opt-in, independent of the full Linux desktop. */\n browser?: { enabled: boolean; maxSessions?: number; idleTimeoutMs?: number }\n computers?: {managedBy?:'compose';network?:'none'|'public-proxy';runtime:'docker'|'podman';imageId:string;maxConcurrent?:number}\n}\nexport const UNCONFIGURED_COMPUTER_IMAGE='sha256:'+'0'.repeat(64)\n","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { apiRequest } from '@/api/client'\nimport { UNCONFIGURED_COMPUTER_IMAGE } from '@shared/runner'\nimport type { RunnerConfiguration, RunnerRecord } from '@shared/runner'\n\ntype Runner = Omit<RunnerRecord, 'tokenHash' | 'sourceOwner'> & {online: boolean}\nconst props=defineProps<{computerSetup?:boolean}>()\nconst bundleAvailable=ref(false),fixedDesktops=ref(false)\nconst runners = ref<Runner[]>([])\nconst busy = ref(false), error = ref(''), notice = ref('')\nconst name = ref('')\nconst serverURL = ref(window.location.origin)\nconst computerNetwork=ref<'none'|'public-proxy'>('public-proxy')\nconst managedBrowser=ref(true)\nconst computers=ref(!!props.computerSetup),computerRuntime=ref<'docker'|'podman'>('docker'),computerImage=ref('')\nconst artifactRoots = ref(''), insecure = ref(false), configuration = ref<RunnerConfiguration>()\n\nasync function refresh() {\n const data = await apiRequest<{runners:Runner[];bundleAvailable?:boolean;fixedDesktops?:boolean;modelProvider?:string}>('/api/app/admin/runners')\n runners.value = data.runners;bundleAvailable.value=!!data.bundleAvailable;fixedDesktops.value=!!data.fixedDesktops\n}\nasync function action(work:()=>Promise<void>) {\n if(busy.value) return\n error.value = ''; notice.value = ''; busy.value = true\n try { await work() } catch(cause) { error.value = cause instanceof Error ? cause.message : '执行节点操作失败' }\n finally { busy.value = false }\n}\nasync function register() {\n await action(async()=>{\n const web = new URL(serverURL.value)\n if(!['http:','https:'].includes(web.protocol)||web.username||web.password||web.search||web.hash||web.pathname!=='/')throw new Error('请填写不带路径的Yaovi服务地址')\n if(web.protocol==='http:'&&!['localhost','127.0.0.1','[::1]'].includes(web.hostname)&&!insecure.value)throw new Error('远程节点请使用 HTTPS,或勾选可信局域网 HTTP')\n if(computers.value&&((!!computerImage.value&&!/^sha256:[a-f0-9]{64}$/.test(computerImage.value))))throw new Error('自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。')\n const result = await apiRequest<{runner:Runner;token:string}>('/api/app/admin/runners', {method:'POST',body:{name:name.value.trim()}})\n configuration.value = {protocol:1,serverURL:web.origin,runnerId:result.runner.id,token:result.token,artifactRoots:artifactRoots.value.split('\\n').map(p=>p.trim()).filter(Boolean),...(insecure.value?{allowInsecureLan:true}:{}),...(computers.value?{computers:{...(fixedDesktops.value?{managedBy:'compose' as const}:{}),network:fixedDesktops.value?'none':computerNetwork.value,runtime:computerRuntime.value,imageId:computerImage.value||UNCONFIGURED_COMPUTER_IMAGE}}:{})}\n configuration.value.browser={enabled:managedBrowser.value}\n notice.value = '节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。'\n await refresh()\n })\n}\nfunction download() {\n if(!configuration.value) return\n const url = URL.createObjectURL(new Blob([JSON.stringify(configuration.value,null,2)+'\\n'],{type:'application/json'}))\n const anchor = document.createElement('a'); anchor.href = url; anchor.download = 'runner.json'; anchor.click()\n setTimeout(()=>URL.revokeObjectURL(url),1000)\n}\nfunction disable(runner:Runner) {\n void action(async()=>{\n await apiRequest(`/api/app/admin/runners/${runner.id}`,{method:'DELETE',body:{}})\n if(configuration.value?.runnerId===runner.id)configuration.value=undefined\n await refresh(); notice.value = `已停用 ${runner.name}`\n })\n}\nonMounted(()=>{void action(refresh)})\n</script>\n\n<template>\n <section class=\"runner-panel\" aria-label=\"执行节点设置\">\n <p>执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。</p>\n <p v-if=\"bundleAvailable\"><a href=\"/api/app/admin/runners/bundle\" download>下载配套执行节点程序</a>(包含桌面资源,需要 Node.js 24 或更高版本)。</p>\n <div class=\"runner-toolbar\"><strong>已注册节点</strong><button type=\"button\" :disabled=\"busy\" @click=\"action(refresh)\">刷新状态</button></div>\n <ul v-if=\"runners.length\" class=\"runner-list\">\n <li v-for=\"runner in runners\" :key=\"runner.id\">\n <div><strong>{{ runner.name }}</strong><span>{{ !runner.enabled ? '已停用' : runner.online ? '已连接' : '待连接' }}</span></div>\n <button v-if=\"runner.enabled\" type=\"button\" :disabled=\"busy\" :aria-label=\"`停用 ${runner.name}`\" @click=\"disable(runner)\">停用</button>\n </li>\n </ul>\n <p v-else>尚未注册执行节点。</p>\n <form v-if=\"!configuration\" @submit.prevent=\"register\">\n <label>节点名称<input v-model=\"name\" required maxlength=\"100\" autocomplete=\"off\" placeholder=\"我的 Mac\" :disabled=\"busy\"></label>\n <label>Yaovi服务地址<input v-model=\"serverURL\" type=\"url\" required :disabled=\"busy\"><small>必须能从 Runner 所在电脑访问。</small></label>\n <label>允许导出的产物目录<textarea v-model=\"artifactRoots\" rows=\"2\" :disabled=\"busy\" placeholder=\"每行一个绝对路径,留空时禁止导出文件\"></textarea></label>\n <label class=\"runner-check\"><input v-model=\"insecure\" type=\"checkbox\" :disabled=\"busy\">使用可信局域网 HTTP</label>\n <label class=\"runner-check\"><input v-model=\"managedBrowser\" type=\"checkbox\" :disabled=\"busy\">托管浏览器</label>\n <small>默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。</small>\n <label class=\"runner-check\"><input v-model=\"computers\" type=\"checkbox\" :disabled=\"busy\">启用隔离电脑 Worker</label>\n <fieldset v-if=\"computers\" class=\"runner-computer\">\n <legend>执行电脑配置</legend><p v-if=\"fixedDesktops\">桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。</p>\n <label v-if=\"!fixedDesktops\">容器运行时<select v-model=\"computerRuntime\"><option value=\"docker\">Docker</option><option value=\"podman\">Podman</option></select></label>\n <label v-if=\"!fixedDesktops\">联网范围<select v-model=\"computerNetwork\"><option value=\"public-proxy\">公网网页(HTTP / HTTPS)</option><option value=\"none\">不联网</option></select></label>\n <small v-if=\"!fixedDesktops\">公网模式通过受控代理访问,不开放宿主和内网地址。</small>\n <label v-if=\"!fixedDesktops\">兼容镜像 ID<input v-model.trim=\"computerImage\" placeholder=\"可在本地虚拟机设置中准备\"></label>\n <small>这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。</small>\n </fieldset>\n <button class=\"solid-button\" :disabled=\"busy || !name.trim()\">注册节点</button>\n </form>\n <div v-else class=\"runner-setup\">\n <button class=\"solid-button\" type=\"button\" @click=\"download\">下载 Runner 配置</button>\n <p>配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:</p>\n <code v-if=\"bundleAvailable\">mkdir -p yaovi-runner<br>tar -xzf yaovi-runner.tar.gz -C yaovi-runner<br>chmod 600 runner.json<br>node yaovi-runner/runner.mjs --config /完整路径/runner.json</code>\n <code v-else>chmod 600 runner.json<br>npm run runner -- --config /完整路径/runner.json</code>\n <small>{{bundleAvailable?(configuration.computers?'在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。':'在执行电脑上启动节点。'):'在 yaovi 项目中先运行 npm run build。'}}配置丢失后可停用节点并重新注册。</small>\n </div>\n <p v-if=\"error\" class=\"runner-error\" role=\"alert\">{{ error }}</p>\n <p v-if=\"notice\" role=\"status\">{{ notice }}</p>\n </section>\n</template>\n\n<style scoped>\n.runner-panel { display: grid; gap: 14px; min-width: 0; font-size: 13px; }\n.runner-panel p { margin: 0; line-height: 1.6; color: var(--text-secondary); }\n.runner-toolbar,.runner-list li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.runner-panel button { min-height: 36px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); cursor: pointer; }\n.runner-panel button:disabled { opacity: .5; cursor: default; }\n.runner-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }\n.runner-list li { padding: 12px; border: 1px solid var(--line); border-radius: 10px; }\n.runner-list li div { display: grid; gap: 5px; overflow-wrap: anywhere; }\n.runner-list span,.runner-panel small { color: var(--text-secondary); font-size: 12px; line-height: 1.5; }\n.runner-panel form,.runner-panel label,.runner-setup { display: grid; gap: 8px; min-width: 0; }\n.runner-panel form { gap: 14px; }\n.runner-computer { display: grid; gap: 12px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; min-width: 0; }\n.runner-panel input:not([type=checkbox]),.runner-panel select,.runner-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); font: inherit; }\n.runner-panel .runner-check { display: flex; align-items: center; }\n.runner-panel code { overflow-wrap: anywhere; line-height: 1.7; }\n.runner-panel .runner-error { color: var(--danger); }\n.runner-panel :is(input,select,textarea,button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n</style>","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { apiRequest } from '@/api/client'\nimport { UNCONFIGURED_COMPUTER_IMAGE } from '@shared/runner'\nimport type { RunnerConfiguration, RunnerRecord } from '@shared/runner'\n\ntype Runner = Omit<RunnerRecord, 'tokenHash' | 'sourceOwner'> & {online: boolean}\nconst props=defineProps<{computerSetup?:boolean}>()\nconst bundleAvailable=ref(false),fixedDesktops=ref(false)\nconst runners = ref<Runner[]>([])\nconst busy = ref(false), error = ref(''), notice = ref('')\nconst name = ref('')\nconst serverURL = ref(window.location.origin)\nconst computerNetwork=ref<'none'|'public-proxy'>('public-proxy')\nconst managedBrowser=ref(true)\nconst computers=ref(!!props.computerSetup),computerRuntime=ref<'docker'|'podman'>('docker'),computerImage=ref('')\nconst artifactRoots = ref(''), insecure = ref(false), configuration = ref<RunnerConfiguration>()\n\nasync function refresh() {\n const data = await apiRequest<{runners:Runner[];bundleAvailable?:boolean;fixedDesktops?:boolean;modelProvider?:string}>('/api/app/admin/runners')\n runners.value = data.runners;bundleAvailable.value=!!data.bundleAvailable;fixedDesktops.value=!!data.fixedDesktops\n}\nasync function action(work:()=>Promise<void>) {\n if(busy.value) return\n error.value = ''; notice.value = ''; busy.value = true\n try { await work() } catch(cause) { error.value = cause instanceof Error ? cause.message : '执行节点操作失败' }\n finally { busy.value = false }\n}\nasync function register() {\n await action(async()=>{\n const web = new URL(serverURL.value)\n if(!['http:','https:'].includes(web.protocol)||web.username||web.password||web.search||web.hash||web.pathname!=='/')throw new Error('请填写不带路径的Yaovi服务地址')\n if(web.protocol==='http:'&&!['localhost','127.0.0.1','[::1]'].includes(web.hostname)&&!insecure.value)throw new Error('远程节点请使用 HTTPS,或勾选可信局域网 HTTP')\n if(computers.value&&((!!computerImage.value&&!/^sha256:[a-f0-9]{64}$/.test(computerImage.value))))throw new Error('自定义路径必须为执行电脑上的绝对路径;默认安装可留空。镜像 ID 可留空或填写完整 sha256 ID。')\n const result = await apiRequest<{runner:Runner;token:string}>('/api/app/admin/runners', {method:'POST',body:{name:name.value.trim()}})\n configuration.value = {protocol:1,serverURL:web.origin,runnerId:result.runner.id,token:result.token,artifactRoots:artifactRoots.value.split('\\n').map(p=>p.trim()).filter(Boolean),...(insecure.value?{allowInsecureLan:true}:{}),...(computers.value?{computers:{...(fixedDesktops.value?{managedBy:'compose' as const}:{}),network:fixedDesktops.value?'none':computerNetwork.value,runtime:computerRuntime.value,imageId:computerImage.value||UNCONFIGURED_COMPUTER_IMAGE}}:{})}\n configuration.value.browser={enabled:managedBrowser.value}\n notice.value = '节点已注册,请下载配置并在提供电脑工具的主机启动 Runner。'\n await refresh()\n })\n}\nfunction download() {\n if(!configuration.value) return\n const url = URL.createObjectURL(new Blob([JSON.stringify(configuration.value,null,2)+'\\n'],{type:'application/json'}))\n const anchor = document.createElement('a'); anchor.href = url; anchor.download = 'runner.json'; anchor.click()\n setTimeout(()=>URL.revokeObjectURL(url),1000)\n}\nfunction disable(runner:Runner) {\n void action(async()=>{\n await apiRequest(`/api/app/admin/runners/${runner.id}`,{method:'DELETE',body:{}})\n if(configuration.value?.runnerId===runner.id)configuration.value=undefined\n await refresh(); notice.value = `已停用 ${runner.name}`\n })\n}\nonMounted(()=>{void action(refresh)})\n</script>\n\n<template>\n <section class=\"runner-panel\" aria-label=\"执行节点设置\">\n <p>执行节点提供虚拟环境和托管浏览器能力。模型由 Yaovi 服务端连接 OpenAI。</p>\n <p v-if=\"bundleAvailable\"><a href=\"/api/app/admin/runners/bundle\" download>下载配套执行节点程序</a>(包含桌面资源,需要 Node.js 24 或更高版本)。</p>\n <div class=\"runner-toolbar\"><strong>已注册节点</strong><button type=\"button\" :disabled=\"busy\" @click=\"action(refresh)\">刷新状态</button></div>\n <ul v-if=\"runners.length\" class=\"runner-list\">\n <li v-for=\"runner in runners\" :key=\"runner.id\">\n <div><strong>{{ runner.name }}</strong><span>{{ !runner.enabled ? '已停用' : runner.online ? '已连接' : '待连接' }}</span></div>\n <button v-if=\"runner.enabled\" type=\"button\" :disabled=\"busy\" :aria-label=\"`停用 ${runner.name}`\" @click=\"disable(runner)\">停用</button>\n </li>\n </ul>\n <p v-else>尚未注册执行节点。</p>\n <form v-if=\"!configuration\" @submit.prevent=\"register\">\n <label>节点名称<input v-model=\"name\" required maxlength=\"100\" autocomplete=\"off\" placeholder=\"我的 Mac\" :disabled=\"busy\"></label>\n <label>Yaovi服务地址<input v-model=\"serverURL\" type=\"url\" required :disabled=\"busy\"><small>必须能从 Runner 所在电脑访问。</small></label>\n <label>允许导出的产物目录<textarea v-model=\"artifactRoots\" rows=\"2\" :disabled=\"busy\" placeholder=\"每行一个绝对路径,留空时禁止导出文件\"></textarea></label>\n <label class=\"runner-check\"><input v-model=\"insecure\" type=\"checkbox\" :disabled=\"busy\">使用可信局域网 HTTP</label>\n <label class=\"runner-check\"><input v-model=\"managedBrowser\" type=\"checkbox\" :disabled=\"busy\">托管浏览器</label>\n <small>默认自动检测浏览器,首次使用时自动准备。是否允许 Bot 使用仍由 「设置 → 环境与权限 → 运行环境」中的全局托管浏览器开关控制。</small>\n <label class=\"runner-check\"><input v-model=\"computers\" type=\"checkbox\" :disabled=\"busy\">启用隔离电脑 Worker</label>\n <fieldset v-if=\"computers\" class=\"runner-computer\">\n <legend>执行电脑配置</legend><p v-if=\"fixedDesktops\">桌面已由 Compose 创建。Runner 只转发执行和控制,不安装桌面镜像、不创建容器;联网方式由 Compose 指定。</p>\n <label v-if=\"!fixedDesktops\">容器运行时<select v-model=\"computerRuntime\"><option value=\"docker\">Docker</option><option value=\"podman\">Podman</option></select></label>\n <label v-if=\"!fixedDesktops\">联网范围<select v-model=\"computerNetwork\"><option value=\"public-proxy\">公网网页(HTTP / HTTPS)</option><option value=\"none\">不联网</option></select></label>\n <small v-if=\"!fixedDesktops\">公网模式通过受控代理访问,不开放宿主和内网地址。</small>\n <label v-if=\"!fixedDesktops\">兼容镜像 ID<input v-model.trim=\"computerImage\" placeholder=\"可在本地虚拟机设置中准备\"></label>\n <small>这是外部执行节点的高级配置。本机虚拟机可直接在应用设置中准备,并从机器人聊天的电脑面板启用。</small>\n </fieldset>\n <button class=\"solid-button\" :disabled=\"busy || !name.trim()\">注册节点</button>\n </form>\n <div v-else class=\"runner-setup\">\n <button class=\"solid-button\" type=\"button\" @click=\"download\">下载 Runner 配置</button>\n <p>配置包含本节点的连接凭据,仅本次可下载。将文件放在执行电脑,设置为仅自己可读:</p>\n <code v-if=\"bundleAvailable\">mkdir -p yaovi-runner<br>tar -xzf yaovi-runner.tar.gz -C yaovi-runner<br>chmod 600 runner.json<br>node yaovi-runner/runner.mjs --config /完整路径/runner.json</code>\n <code v-else>chmod 600 runner.json<br>npm run runner -- --config /完整路径/runner.json</code>\n <small>{{bundleAvailable?(configuration.computers?'在运行 Docker/Podman 的电脑上执行,节点连接后返回本地虚拟机设置准备桌面。':'在执行电脑上启动节点。'):'在 yaovi 项目中先运行 npm run build。'}}配置丢失后可停用节点并重新注册。</small>\n </div>\n <p v-if=\"error\" class=\"runner-error\" role=\"alert\">{{ error }}</p>\n <p v-if=\"notice\" role=\"status\">{{ notice }}</p>\n </section>\n</template>\n\n<style scoped>\n.runner-panel { display: grid; gap: 14px; min-width: 0; font-size: 13px; }\n.runner-panel p { margin: 0; line-height: 1.6; color: var(--text-secondary); }\n.runner-toolbar,.runner-list li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.runner-panel button { min-height: 36px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); cursor: pointer; }\n.runner-panel button:disabled { opacity: .5; cursor: default; }\n.runner-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }\n.runner-list li { padding: 12px; border: 1px solid var(--line); border-radius: 10px; }\n.runner-list li div { display: grid; gap: 5px; overflow-wrap: anywhere; }\n.runner-list span,.runner-panel small { color: var(--text-secondary); font-size: 12px; line-height: 1.5; }\n.runner-panel form,.runner-panel label,.runner-setup { display: grid; gap: 8px; min-width: 0; }\n.runner-panel form { gap: 14px; }\n.runner-computer { display: grid; gap: 12px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; min-width: 0; }\n.runner-panel input:not([type=checkbox]),.runner-panel select,.runner-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); color: var(--text-primary); font: inherit; }\n.runner-panel .runner-check { display: flex; align-items: center; }\n.runner-panel code { overflow-wrap: anywhere; line-height: 1.7; }\n.runner-panel .runner-error { color: var(--danger); }\n.runner-panel :is(input,select,textarea,button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n</style>"],"mappings":"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"}