@leterhong/multichat 1.6.0 → 1.8.0

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 (84) hide show
  1. package/README.md +9 -22
  2. package/backend/dist/routes/agents.js +4 -2
  3. package/backend/dist/routes/agents.js.map +1 -1
  4. package/backend/dist/routes/assistants.js +4 -2
  5. package/backend/dist/routes/assistants.js.map +1 -1
  6. package/backend/dist/routes/chat.js +45 -13
  7. package/backend/dist/routes/chat.js.map +1 -1
  8. package/backend/dist/routes/control-plane.js +25 -18
  9. package/backend/dist/routes/control-plane.js.map +1 -1
  10. package/backend/dist/routes/conversations.js +116 -30
  11. package/backend/dist/routes/conversations.js.map +1 -1
  12. package/backend/dist/routes/import.js +5 -4
  13. package/backend/dist/routes/import.js.map +1 -1
  14. package/backend/dist/routes/mock.js +3 -0
  15. package/backend/dist/routes/mock.js.map +1 -1
  16. package/backend/dist/routes/prompt-templates.js +50 -0
  17. package/backend/dist/routes/prompt-templates.js.map +1 -0
  18. package/backend/dist/routes/providers.js +15 -4
  19. package/backend/dist/routes/providers.js.map +1 -1
  20. package/backend/dist/routes/runs.js +35 -16
  21. package/backend/dist/routes/runs.js.map +1 -1
  22. package/backend/dist/routes/tools.js +15 -13
  23. package/backend/dist/routes/tools.js.map +1 -1
  24. package/backend/dist/runtime/auto-snapshot.js +73 -0
  25. package/backend/dist/runtime/auto-snapshot.js.map +1 -0
  26. package/backend/dist/server.js +3 -0
  27. package/backend/dist/server.js.map +1 -1
  28. package/backend/package.json +5 -2
  29. package/backend/routes/agents.ts +6 -4
  30. package/backend/routes/assistants.ts +4 -2
  31. package/backend/routes/chat.ts +43 -13
  32. package/backend/routes/control-plane.ts +22 -14
  33. package/backend/routes/conversations.ts +105 -29
  34. package/backend/routes/import.ts +11 -10
  35. package/backend/routes/mock.ts +4 -0
  36. package/backend/routes/prompt-templates.ts +38 -0
  37. package/backend/routes/providers.ts +14 -4
  38. package/backend/routes/runs.ts +42 -20
  39. package/backend/routes/tools.ts +14 -12
  40. package/backend/runtime/auto-snapshot.ts +60 -0
  41. package/backend/server.ts +3 -0
  42. package/bin/multichat.mjs +18 -0
  43. package/frontend/dist/assets/agentPicker-Jan8fWb3.js +18 -0
  44. package/frontend/dist/assets/assets-ChkvQqhp.js +1 -0
  45. package/frontend/dist/assets/compare-CkyqXEbN.js +1 -0
  46. package/frontend/dist/assets/conversations-DoyXfY4T.js +1 -0
  47. package/frontend/dist/assets/data-BN7t_AoO.js +1 -0
  48. package/frontend/dist/assets/extensionImport-DPZhdnxE.js +28 -0
  49. package/frontend/dist/assets/importExport-Bb6-L-4y.js +69 -0
  50. package/frontend/dist/assets/index-CeU_ksnE.js +49 -0
  51. package/frontend/dist/assets/index-DDdMiim6.css +1 -0
  52. package/frontend/dist/assets/init-yIIG9GSH.js +1 -0
  53. package/frontend/dist/assets/modal-BYrNUOVj.js +2 -0
  54. package/frontend/dist/assets/modelPicker-B612Gojw.js +14 -0
  55. package/frontend/dist/assets/pluginsUI-KCpFagXv.js +16 -0
  56. package/frontend/dist/assets/react-sySUN5R7.js +1 -0
  57. package/frontend/dist/assets/{render-DgAg1K8A.js → render-BBfAbC8M.js} +1 -1
  58. package/frontend/dist/assets/send-BAyqvnlM.js +14 -0
  59. package/frontend/dist/assets/settings-DkUaMAOG.js +2 -0
  60. package/frontend/dist/assets/{shell-DFV2khQl.js → shell-Cny595HR.js} +1 -1
  61. package/frontend/dist/assets/workbench-cTwE7423.js +40 -0
  62. package/frontend/dist/boot-fallback.js +18 -0
  63. package/frontend/dist/icon-maskable.svg +18 -3
  64. package/frontend/dist/icon.svg +13 -3
  65. package/frontend/dist/index.html +11 -4
  66. package/frontend/dist/manifest.webmanifest +2 -2
  67. package/frontend/dist/theme-seed.js +21 -0
  68. package/package.json +4 -1
  69. package/frontend/dist/assets/agentPicker-C39VzmYa.js +0 -13
  70. package/frontend/dist/assets/compare-BpaGD32J.js +0 -1
  71. package/frontend/dist/assets/conversations-COuLmg99.js +0 -7
  72. package/frontend/dist/assets/data-ZH7Dux1K.js +0 -1
  73. package/frontend/dist/assets/extensionImport-CnTWKtWm.js +0 -28
  74. package/frontend/dist/assets/importExport-B3mB-9zs.js +0 -70
  75. package/frontend/dist/assets/index-CASVCOUO.js +0 -34
  76. package/frontend/dist/assets/index-DSvbjNLc.css +0 -1
  77. package/frontend/dist/assets/init-ZPK483vB.js +0 -1
  78. package/frontend/dist/assets/jsx-runtime-ZSsbr0oW.js +0 -9
  79. package/frontend/dist/assets/modal-CgHocv3n.js +0 -2
  80. package/frontend/dist/assets/modelPicker-EUmmVnbj.js +0 -14
  81. package/frontend/dist/assets/pluginsUI-B6jGUQkx.js +0 -16
  82. package/frontend/dist/assets/send-BJhZ_5YA.js +0 -14
  83. package/frontend/dist/assets/settings-DdnYwZl-.js +0 -2
  84. package/frontend/dist/assets/workbench-BY2J4vWC.js +0 -41
@@ -0,0 +1,40 @@
1
+ import{D as e,E as t,O as n,b as r,c as i,d as a,r as o,t as s}from"./index-CeU_ksnE.js";var c=null;function l(){let e=r.selectedProvider;return!e||!r.selectedModel?!1:!!e.baseUrl&&([`ollama`,`lmstudio`,`mock`].includes(String(e.apiType||``).toLowerCase())||e.apiKeyMasked||e.apiKey)}function u(e){let t=e?.usage||{};return t.totalTokens??t.total_tokens??t.total??0}function d(e,t,n){let r=e?.[t]||e?.[n]||[];return Array.isArray(r)?r.length:0}function f(){let e=r.selectedAgent,t=!!(r.selectedProvider&&r.selectedModel),n=l(),i=!!(r.selectedProject&&r.selectedProject.id!==`pr_inbox`&&r.selectedProject.name!==`收件箱`);return[{label:`模型选择`,ok:t,detail:t?`${r.selectedProvider.name||r.selectedProvider.id} · ${r.selectedModel}`:`尚未选择模型`,tab:`providers`},{label:`连接配置`,ok:n,detail:n?`凭据与地址已配置`:`需要补充 API 地址或凭据`,tab:`providers`},{label:`项目上下文`,ok:i,detail:i?r.selectedProject.name:`尚未打开项目文件夹`,tab:`workspace`},{label:`运行配置`,ok:!0,detail:e?e.name:`直接对话(无工具注入)`,tab:`agents`}]}function p(e){return{running:`运行中`,completed:`已完成`,error:`失败`,cancelled:`已取消`,rejected:`已拒绝`}[e]||e||`暂无记录`}function m(){let i=t(`#inspectorBody`);if(!i)return;let a=r.selectedAgent,o=r.runs?.[0],s=f(),c=s.every(e=>e.ok),l=r.selectedAssetIds?.size||0,m=(r.assets||[]).filter(e=>r.selectedAssetIds?.has(e.id)).reduce((e,t)=>e+Number(t.size||t.bytes||0),0),h=Math.ceil(m/4),_=(r.memories||[]).filter(e=>e.enabled!==!1).length,v=d(a,`skillRefs`,`skillIds`),y=d(a,`toolIds`,`toolIds`),b=d(a,`mcpServerIds`,`mcpServerIds`),x=Math.round(s.filter(e=>e.ok).length/s.length*100);i.innerHTML=`
2
+ <section class="inspector-status ${c?`ready`:`needs-attention`}">
3
+ <div class="inspector-score"><strong>${x}</strong><span>%</span></div>
4
+ <div><strong>${c?`可以开始运行`:`需要完成配置`}</strong><span>${c?`必要条件已经就绪`:`补齐下面的配置项后再运行`}</span></div>
5
+ </section>
6
+ <section class="inspector-section">
7
+ <div class="inspector-section-head"><strong>会话配置</strong><button type="button" data-inspector-settings="agents">编辑</button></div>
8
+ <dl class="inspector-facts">
9
+ <div><dt>模型</dt><dd>${n(r.selectedModel||`未选择`)}</dd></div>
10
+ <div><dt>运行配置</dt><dd>${n(a?.name||`直接对话`)}</dd></div>
11
+ <div><dt>项目</dt><dd>${n(r.selectedProject?.id===`pr_inbox`?`临时对话`:r.selectedProject?.name||`未选择`)}</dd></div>
12
+ </dl>
13
+ </section>
14
+ <section class="inspector-section">
15
+ <div class="inspector-section-head"><strong>上下文装配</strong><button type="button" data-inspector-settings="workspace">管理</button></div>
16
+ <div class="inspector-metrics">
17
+ <div><strong>${l}</strong><span>文件</span></div>
18
+ <div><strong>${_}</strong><span>记忆</span></div>
19
+ <div><strong>${v}</strong><span>Skills</span></div>
20
+ <div><strong>${y+b}</strong><span>工具</span></div>
21
+ </div>
22
+ <div class="context-budget"><div><span>文件上下文估算</span><strong>${h.toLocaleString(`zh-CN`)} Token</strong></div><div class="context-budget-track"><span style="width:${Math.min(100,h/320)}%"></span></div><small>仅估算已选文件;模型系统提示和对话历史未计入。</small></div>
23
+ ${a?`<p class="inspector-note">${v} Skills · ${y} 内置工具 · ${b} MCP 服务</p>`:`<p class="inspector-note">直接对话不会注入工具或运行配置提示词。</p>`}
24
+ </section>
25
+ <section class="inspector-section">
26
+ <div class="inspector-section-head"><strong>运行前检查</strong><button type="button" data-inspector-settings="capabilities">能力清单</button></div>
27
+ <div class="inspector-checks">
28
+ ${s.map(e=>`<button type="button" data-inspector-settings="${e.tab}" class="inspector-check ${e.ok?`ok`:`warn`}"><span aria-hidden="true">${e.ok?`✓`:`!`}</span><div><strong>${n(e.label)}</strong><small>${n(e.detail)}</small></div></button>`).join(``)}
29
+ </div>
30
+ </section>
31
+ <section class="inspector-section">
32
+ <div class="inspector-section-head"><strong>最近运行</strong><button type="button" data-inspector-settings="runs">查看日志</button></div>
33
+ ${o?`<div class="inspector-run"><div><span class="run-state ${n(o.status)}">${n(p(o.status))}</span><strong>${n(o.agentName||o.agentId||`直接对话`)}</strong></div><dl><div><dt>步骤</dt><dd>${n(o.steps||0)}</dd></div><div><dt>工具调用</dt><dd>${n(o.toolCalls||0)}</dd></div><div><dt>Token</dt><dd>${n(u(o).toLocaleString(`zh-CN`))}</dd></div></dl></div>`:`<div class="inspector-empty">完成一次运行后,这里会显示步骤、工具调用和 Token。</div>`}
34
+ </section>
35
+ `,e(`[data-inspector-settings]`,i).forEach(e=>{e.onclick=()=>{g(),openSettings(e.dataset.inspectorSettings||`general`)}})}function h(){s(`context`)}function g(){let e=t(`#sessionInspector`),n=t(`#mainWorkspace`),r=t(`#inspectorBtn`);e&&n&&(e.contains(document.activeElement)&&(r||document.body).focus(),n.classList.remove(`inspector-open`),e.setAttribute(`aria-hidden`,`true`),e.inert=!0,r?.setAttribute(`aria-expanded`,`false`))}function _(){s(`activity`)}function v(){let e=(r.conversations||[]).slice(0,8).map(e=>({id:`conversation:${e.id}`,label:e.title||`未命名对话`,description:`打开最近对话`,group:`对话`,run:()=>openConversation(e.id)})),n=(r.providers||[]).flatMap(e=>(e.models||[]).slice(0,5).map(t=>({id:`model:${e.id}:${t}`,label:t,description:`切换到 ${e.name||e.id}`,group:`模型`,run:()=>selectModel(e.id,t)}))).slice(0,10),s=(r.agents||[]).slice(0,6).map(e=>({id:`agent:${e.id}`,label:e.name||e.id,description:`在智能体页面查看和编辑运行配置`,group:`智能体`,run:()=>openSettings(`agents`)}));return[{id:`new`,label:`新任务`,description:`使用当前模型开始一个任务`,group:`任务`,shortcut:`Ctrl N`,run:()=>{o(`chat`),newConversation()}},{id:`run-agent`,label:`运行智能体`,description:`选择智能体并开始一个可使用工具的任务`,group:`任务`,run:()=>{o(`agent`),newConversation().then(()=>t(`#agentPicker`)?.click())}},{id:`inspect`,label:`打开 AI 活动中心`,description:`检查模型活动、上下文和真实运行指标`,group:`对话`,shortcut:`Ctrl I`,run:_},...e,...n,...s,{id:`compare`,label:`模型对比`,description:`使用相同任务和上下文并行比较 2–4 个模型`,group:`任务`,shortcut:`Ctrl M`,run:()=>{o(`compare`),openSettings(`experiment`)}},{id:`switch-project`,label:`切换项目`,description:`选择项目或打开本地项目文件夹`,group:`工作区`,run:()=>t(`#workspacePicker`)?.click()},{id:`settings`,label:`打开设置`,description:`配置外观、模型、能力与运行记录`,group:`工作区`,run:()=>openSettings(`general`)},{id:`providers`,label:`模型连接`,description:`添加模型提供方、地址和凭据`,group:`设置`,run:()=>openSettings(`providers`)},{id:`agents`,label:`运行配置`,description:`组合提示词、Skills、工具与 MCP`,group:`设置`,run:()=>openSettings(`agents`)},{id:`workspace`,label:`项目与文件`,description:`管理代码文件、记忆、快照和项目默认值`,group:`设置`,run:()=>openSettings(`workspace`)},{id:`skills`,label:`Skills`,description:`浏览、导入和管理工作流能力`,group:`能力`,run:()=>openSettings(`skills`)},{id:`mcp`,label:`MCP 服务`,description:`管理外部工具连接与信任状态`,group:`能力`,run:()=>openSettings(`mcp`)},{id:`plugins`,label:`插件`,description:`导入、审查和启用扩展包`,group:`能力`,run:()=>openSettings(`plugins`)},{id:`runs`,label:`运行日志`,description:`查看 Turn、Step、工具调用和审批记录`,group:`观察`,run:()=>openSettings(`runs`)},{id:`usage`,label:`Token 用量`,description:`查看每日消耗、模型分布和缓存命中`,group:`观察`,run:()=>openSettings(`usage`)},{id:`theme`,label:i()===`dark`?`切换为浅色界面`:`切换为深色界面`,description:`更改当前设备上的显示主题`,group:`界面`,run:()=>a(i()===`dark`?`light`:`dark`)}]}function y(r=``){let i=t(`#commandList`);if(!i)return;let a=r.trim().toLowerCase(),o=v().filter(e=>`${e.label} ${e.description} ${e.group}`.toLowerCase().includes(a));i.innerHTML=o.length?o.map(e=>`
36
+ <button class="command-item" type="button" data-command="${e.id}">
37
+ <span class="command-item-group">${n(e.group)}</span>
38
+ <span class="command-item-copy"><strong>${n(e.label)}</strong><small>${n(e.description)}</small></span>
39
+ ${e.shortcut?`<kbd>${n(e.shortcut)}</kbd>`:`<span class="command-arrow" aria-hidden="true">›</span>`}
40
+ </button>`).join(``):`<div class="command-empty">没有匹配的操作</div>`,e(`[data-command]`,i).forEach(e=>{e.onclick=()=>{let t=v().find(t=>t.id===e.dataset.command);x(!1),t?.run()}})}function b(){let e=t(`#commandPalette`),n=t(`#commandSearch`);e&&n&&(c=document.activeElement,n.value=``,y(),e.inert=!1,e.setAttribute(`aria-hidden`,`false`),e.classList.add(`open`),t(`#app`).inert=!0,window.setTimeout(()=>n.focus(),0))}function x(e=!0){let n=t(`#commandPalette`);n&&(t(`#app`).inert=!1,n.contains(document.activeElement)&&(c||t(`#commandBtn`)||document.body).focus(),n.classList.remove(`open`),n.setAttribute(`aria-hidden`,`true`),n.inert=!0,e&&c&&document.contains(c)&&c.focus())}function S(){let n={general:`通用与外观`,workspace:`项目、文件与记忆`,providers:`模型`,experiment:`模型对比`,agents:`智能体`,capabilities:`能力审计`,usage:`Token 用量`,runs:`运行记录`};e(`.settings-tab[data-tab]`).forEach(e=>{let t=e.querySelector(`span:last-child`);t&&n[e.dataset.tab||``]&&(t.textContent=n[e.dataset.tab||``])}),t(`#commandBtn`).onclick=b;let r=t(`#commandSearch`);r.oninput=()=>y(r.value),r.onkeydown=t=>{let n=Array.from(e(`.command-item`));t.key===`ArrowDown`&&n.length&&(t.preventDefault(),n[0].focus()),t.key===`Enter`&&n.length&&(t.preventDefault(),n[0].click())},t(`#commandList`).addEventListener(`keydown`,t=>{let n=Array.from(e(`.command-item`)),i=n.indexOf(document.activeElement);t.key===`ArrowDown`&&i>=0&&(t.preventDefault(),n[(i+1)%n.length]?.focus()),t.key===`ArrowUp`&&i>=0&&(t.preventDefault(),(i===0?r:n[i-1])?.focus())}),e(`[data-open-settings]`).forEach(e=>{e.onclick=()=>openSettings(e.dataset.openSettings||`general`)}),t(`#commandPalette`).addEventListener(`mousedown`,e=>{e.target===t(`#commandPalette`)&&x()}),document.addEventListener(`keydown`,e=>{(t(`#settings`)?.classList.contains(`open`)||t(`#modal`)?.classList.contains(`open`))&&(e.ctrlKey||e.metaKey)&&[`k`,`i`,`m`,`n`].includes(e.key.toLowerCase())||((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()===`k`?(e.preventDefault(),t(`#commandPalette`)?.classList.contains(`open`)?x():b()):(e.ctrlKey||e.metaKey)&&e.key.toLowerCase()===`i`?(e.preventDefault(),t(`#commandPalette`)?.classList.contains(`open`)&&x(!1),_()):(e.ctrlKey||e.metaKey)&&e.key.toLowerCase()===`m`?(e.preventDefault(),t(`#commandPalette`)?.classList.contains(`open`)&&x(!1),openSettings(`experiment`)):(e.ctrlKey||e.metaKey)&&e.key.toLowerCase()===`n`?(e.preventDefault(),t(`#commandPalette`)?.classList.contains(`open`)&&x(!1),newConversation()):e.key===`Escape`&&(t(`#commandPalette`)?.classList.contains(`open`)?x():t(`#mainWorkspace`)?.classList.contains(`inspector-open`)&&g()))}),m()}export{x as closeCommandPalette,g as closeInspector,b as openCommandPalette,h as openInspector,m as renderInspector,S as setupWorkbench,_ as toggleInspector};
@@ -0,0 +1,18 @@
1
+ // 主模块加载失败(网络中断/旧缓存指向已删除的 chunk 等)时,#root 会保持空白
2
+ // 且主应用的错误兜底来不及挂载。这个独立脚本在 load 后检查一次:4 秒内
3
+ // #root 仍是空的就显示重试入口;应用一旦挂载立即隐藏。
4
+ (function () {
5
+ function hide() { var fb = document.getElementById('bootFallback'); if (fb) fb.style.display = 'none'; }
6
+ window.addEventListener('load', function () {
7
+ setTimeout(function () {
8
+ var root = document.getElementById('root');
9
+ var fb = document.getElementById('bootFallback');
10
+ if (!root || !fb) return;
11
+ if (root.firstChild) { hide(); return; }
12
+ fb.style.display = 'flex';
13
+ }, 4000);
14
+ });
15
+ var retry = document.getElementById('bootReloadBtn');
16
+ if (retry) retry.addEventListener('click', function () { location.reload(); });
17
+ new MutationObserver(hide).observe(document.documentElement, { childList: true, subtree: true });
18
+ })();
@@ -1,4 +1,19 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
- <rect width="32" height="32" fill="#171915"/>
3
- <path d="M9.5 20.5v-9h2.6l3.9 4.7 3.9-4.7h2.6v9h-2.4v-5.4l-4.1 4.9-4.1-4.9v5.4z" fill="#f5f5ef"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
2
+ <title>MultiChat</title>
3
+ <style>
4
+ .bg { fill: #f7f8fa; }
5
+ .arc { fill: none; stroke: #3b6cf6; stroke-width: 3.1; stroke-linecap: round; }
6
+ .core { fill: #3b6cf6; }
7
+ @media (prefers-color-scheme: dark) {
8
+ .bg { fill: #1e1e2e; }
9
+ .arc { stroke: #89b4fa; }
10
+ .core { fill: #89b4fa; }
11
+ }
12
+ </style>
13
+ <rect class="bg" width="36" height="36"/>
14
+ <g transform="translate(5.04 5.04) scale(0.72)">
15
+ <path class="arc" d="M7.5 15.5a11 11 0 0 1 21 0"/>
16
+ <path class="arc" d="M28.5 20.5a11 11 0 0 1-21 0"/>
17
+ <circle class="core" cx="18" cy="18" r="4.3"/>
18
+ </g>
4
19
  </svg>
@@ -1,4 +1,14 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
- <rect width="32" height="32" rx="7" fill="#171915"/>
3
- <path d="M8 21V10h3l5 6 5-6h3v11h-3v-7l-5 6-5-6v7z" fill="#f5f5ef"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
2
+ <title>MultiChat</title>
3
+ <style>
4
+ .arc { fill: none; stroke: #3b6cf6; stroke-width: 3.1; stroke-linecap: round; }
5
+ .core { fill: #3b6cf6; }
6
+ @media (prefers-color-scheme: dark) {
7
+ .arc { stroke: #89b4fa; }
8
+ .core { fill: #89b4fa; }
9
+ }
10
+ </style>
11
+ <path class="arc" d="M7.5 15.5a11 11 0 0 1 21 0"/>
12
+ <path class="arc" d="M28.5 20.5a11 11 0 0 1-21 0"/>
13
+ <circle class="core" cx="18" cy="18" r="4.3"/>
4
14
  </svg>
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
- <meta name="theme-color" content="#171915" />
6
+ <meta name="theme-color" content="#f7f8fa" id="mcThemeColor" />
7
7
  <meta name="color-scheme" content="light dark" />
8
8
  <title>MultiChat · 本地模型工作台</title>
9
9
  <link rel="manifest" href="/manifest.webmanifest" />
@@ -11,11 +11,18 @@
11
11
  <meta name="apple-mobile-web-app-capable" content="yes" />
12
12
  <meta name="apple-mobile-web-app-title" content="MultiChat" />
13
13
  <meta name="mobile-web-app-capable" content="yes" />
14
- <script type="module" crossorigin src="/assets/index-CASVCOUO.js"></script>
15
- <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-ZSsbr0oW.js">
16
- <link rel="stylesheet" crossorigin href="/assets/index-DSvbjNLc.css">
14
+ <script src="/theme-seed.js"></script>
15
+ <script type="module" crossorigin src="/assets/index-CeU_ksnE.js"></script>
16
+ <link rel="modulepreload" crossorigin href="/assets/react-sySUN5R7.js">
17
+ <link rel="stylesheet" crossorigin href="/assets/index-DDdMiim6.css">
17
18
  </head>
18
19
  <body>
19
20
  <div id="root"></div>
21
+ <div id="bootFallback" role="alert" style="display:none;position:fixed;inset:0;z-index:999;flex-direction:column;align-items:center;justify-content:center;gap:14px;background:var(--mc-surface,#ffffff);color:var(--mc-text,#1f2329);font-family:system-ui,sans-serif;">
22
+ <strong style="font-size:16px;">MultiChat 加载失败</strong>
23
+ <span style="font-size:13px;opacity:.7;">页面资源未能在 4 秒内就绪,可能是服务刚重启或网络缓存过期。</span>
24
+ <button type="button" id="bootReloadBtn" style="padding:9px 18px;border:0;border-radius:8px;background:var(--mc-accent,#3b6cf6);color:#fff;font-size:13px;cursor:pointer;">重新加载</button>
25
+ </div>
26
+ <script src="/boot-fallback.js"></script>
20
27
  </body>
21
28
  </html>
@@ -6,8 +6,8 @@
6
6
  "scope": "/",
7
7
  "display": "standalone",
8
8
  "orientation": "any",
9
- "background_color": "#101522",
10
- "theme_color": "#171915",
9
+ "background_color": "#f7f8fa",
10
+ "theme_color": "#f7f8fa",
11
11
  "lang": "zh-CN",
12
12
  "icons": [
13
13
  {
@@ -0,0 +1,21 @@
1
+ // Deep-link theme override: /?theme=light or #/?theme=dark.
2
+ // Also seeds the primitive-layer switches (accent skin, spacing density)
3
+ // before first paint so the saved appearance never flashes.
4
+ // Loaded synchronously from <head> (CSP allows 'self'), applied pre-paint and
5
+ // persisted so the hash-router's history rewrites cannot lose it.
6
+ (function () {
7
+ try {
8
+ var m =
9
+ location.search.match(/[?&]theme=(light|dark|system)\b/) ||
10
+ location.hash.match(/[?&]theme=(light|dark|system)\b/);
11
+ if (m) localStorage.setItem('multichat_theme', m[1]);
12
+ } catch (e) {}
13
+
14
+ try {
15
+ var root = document.documentElement;
16
+ var skin = localStorage.getItem('multichat_skin');
17
+ var density = localStorage.getItem('multichat_density');
18
+ root.dataset.skin = skin === 'graphite' || skin === 'teal' ? skin : 'blue';
19
+ root.dataset.density = density === 'compact' ? density : 'comfortable';
20
+ } catch (e) {}
21
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leterhong/multichat",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Local-first multi-model agent workspace with Skills, MCP servers, and plugins",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -54,5 +54,8 @@
54
54
  "adm-zip": "^0.6.0",
55
55
  "cors": "^2.8.5",
56
56
  "express": "^4.19.2"
57
+ },
58
+ "overrides": {
59
+ "qs": "^6.16.0"
57
60
  }
58
61
  }
@@ -1,13 +0,0 @@
1
- import{d as e,f as t,h as n,p as r,u as i}from"./index-CASVCOUO.js";r(`#workspacePicker`).onclick=()=>{let t=e.workspaces.map(e=>({id:e.id,name:e.name,description:e.description}));if(!t.length){openSettings(`workspace`);return}showModal({title:`选择工作区`,body:`<div class="picker-dialog-intro"><span>切换对话与项目所在的工作区</span><span>${t.length} 个工作区</span></div>
2
- <div class="model-picker-list">${t.map(t=>{let r=e.selectedWorkspace?.id===t.id;return`<button type="button" class="picker-option${r?` active`:``}" data-wid="${n(t.id)}" aria-pressed="${r}">
3
- <span class="picker-provider-mark" aria-hidden="true">${n(t.name.trim().slice(0,1).toUpperCase()||`W`)}</span>
4
- <span class="picker-option-copy"><strong>${n(t.name)}</strong><small>${n(t.description||`本地工作区`)}</small></span>
5
- <span class="picker-option-state" aria-hidden="true">${r?`✓`:`›`}</span>
6
- </button>`}).join(``)}</div>`,onMount:t=>t.querySelectorAll(`[data-wid]`).forEach(t=>t.onclick=async()=>{e.selectedWorkspace=e.workspaces.find(e=>e.id===t.dataset.wid)||null,await loadProjects(),e.currentConvId=null,e.messages=[],renderTopbar(),renderConvList(),renderContent(),closeModal()})})},r(`#agentPicker`).onclick=()=>{if(!e.agents.length){openSettings(`agents`),t(`请先创建运行配置`);return}let r=[{id:``,name:`直接对话`,description:`不注入系统提示词,也不启用工具`}].concat(e.agents.map(e=>({id:e.id,name:e.name,description:e.description})));showModal({title:`选择运行配置`,body:`<div class="picker-dialog-intro"><span>选择本轮使用的提示词与能力组合</span><span>${e.agents.length} 个配置</span></div>
7
- <div class="model-picker-list">
8
- ${r.map(t=>{let r=(e.selectedAgent?.id||``)===t.id;return`<button type="button" class="picker-option${r?` active`:``}" data-aid="${n(t.id)}" aria-pressed="${r}">
9
- <span class="picker-provider-mark agent" aria-hidden="true">${t.id?n(t.name.trim().slice(0,1).toUpperCase()||`A`):`—`}</span>
10
- <span class="picker-option-copy"><strong>${n(t.name)}</strong><small>${n(t.description||`自定义运行配置`)}</small></span>
11
- <span class="picker-option-state" aria-hidden="true">${r?`✓`:`›`}</span>
12
- </button>`}).join(``)}
13
- </div>`,onMount:t=>{t.querySelectorAll(`[data-aid]`).forEach(t=>{t.onclick=()=>{let n=t.dataset.aid;e.selectedAgent=n?e.agents.find(e=>e.id===n):null,i(),renderTopbar(),closeModal(),e.messages.length||renderContent()}})}})};
@@ -1 +0,0 @@
1
- import{i as e,n as t,s as n,t as r}from"./jsx-runtime-ZSsbr0oW.js";import{d as i,f as a,m as o,o as s,p as c,t as l}from"./index-CASVCOUO.js";var u=t(),d=n(e(),1),f=r(),p=`multichat_compare_history_v1`;function m(){try{let e=JSON.parse(localStorage.getItem(p)||`[]`);return Array.isArray(e)?e.filter(e=>e&&typeof e.id==`string`&&typeof e.prompt==`string`&&Array.isArray(e.results)).map(e=>({id:e.id,prompt:e.prompt,createdAt:typeof e.createdAt==`string`?e.createdAt:``,projectName:typeof e.projectName==`string`?e.projectName:`未选择项目`,results:e.results.filter(e=>e&&typeof e.model==`string`&&typeof e.providerId==`string`)})).slice(0,12):[]}catch{return[]}}function h(e,t,n){let r=[{id:`experiment_${Date.now().toString(36)}`,prompt:e,createdAt:new Date().toISOString(),projectName:t,results:n},...m()].slice(0,12);try{localStorage.setItem(p,JSON.stringify(r))}catch{a(`实验已完成,但本地历史无法保存`,`error`)}return r}function g({result:e,prompt:t,adopt:n}){let r=e.usage?.total_tokens??e.usage?.total??0,i=e.status===`running`?`运行中`:e.status===`error`?`失败`:e.status===`success`?`完成`:`等待`;return(0,f.jsxs)(`article`,{className:`compare-result`,"data-state":e.status,children:[(0,f.jsxs)(`header`,{children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`strong`,{children:e.model}),(0,f.jsx)(`span`,{children:e.providerName})]}),(0,f.jsx)(`span`,{className:`compare-state`,children:i})]}),(0,f.jsxs)(`div`,{className:`compare-result-body`,children:[e.status===`running`&&(0,f.jsxs)(`div`,{className:`compare-loading`,"aria-label":`模型正在运行`,children:[(0,f.jsx)(`span`,{}),(0,f.jsx)(`span`,{}),(0,f.jsx)(`span`,{})]}),e.status===`error`&&(0,f.jsx)(`div`,{className:`compare-error`,children:e.error||`请求失败`}),e.status===`success`&&(0,f.jsx)(`div`,{className:`compare-answer`,children:(0,f.jsx)(l,{children:e.text||``})}),e.status===`idle`&&(0,f.jsx)(`div`,{className:`compare-empty`,children:`等待开始`})]}),e.status===`success`&&(0,f.jsxs)(`footer`,{children:[(0,f.jsxs)(`span`,{children:[((e.elapsedMs||0)/1e3).toFixed(1),` 秒`]}),(0,f.jsxs)(`span`,{children:[Number(r).toLocaleString(`zh-CN`),` Token`]}),(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`button`,{type:`button`,onClick:async()=>{try{await navigator.clipboard.writeText(e.text||``),a(`结果已复制`)}catch{a(`复制失败`,`error`)}},children:`复制`}),(0,f.jsx)(`button`,{type:`button`,className:`compare-adopt`,onClick:()=>void n(e,t),children:`采用到对话`})]})]})]})}function _({targets:e,defaultTargetIds:t,initialPrompt:n,projectName:r,fileCount:i,memoryCount:o,execute:s,adopt:c}){let[l,u]=(0,d.useState)(()=>new Set(t)),[p,_]=(0,d.useState)(n),[v,y]=(0,d.useState)([]),[b,x]=(0,d.useState)(m),[S,C]=(0,d.useState)(!1),w=(0,d.useMemo)(()=>e.filter(e=>l.has(e.id)),[e,l]),T=(e,t)=>{let n=new Set(l);if(t){if(n.size>=4){a(`一次最多实验 4 个模型`,`error`);return}n.add(e)}else n.delete(e);u(n)},E=async()=>{let e=p.trim();if(!e){a(`请输入实验任务`,`error`);return}if(w.length<2){a(`至少选择两个模型`,`error`);return}C(!0),y(w.map(e=>({...e,status:`running`})));try{let t=await Promise.all(w.map(async t=>{let n=await s(t,e);return y(e=>e.map(e=>e.id===t.id?n:e)),n}));x(h(e,r,t))}finally{C(!1)}};return(0,f.jsxs)(`div`,{className:`compare-lab`,children:[(0,f.jsxs)(`div`,{className:`compare-intro`,children:[(0,f.jsx)(`div`,{children:(0,f.jsx)(`p`,{children:`同一任务、同一上下文,并行观察不同模型的答案。`})}),(0,f.jsxs)(`span`,{className:`compare-context`,children:[i,` 个文件 · `,o,` 条记忆`]})]}),(0,f.jsxs)(`div`,{className:`compare-config`,children:[(0,f.jsxs)(`fieldset`,{className:`compare-targets`,children:[(0,f.jsx)(`legend`,{children:`参与模型`}),e.map(e=>(0,f.jsxs)(`label`,{children:[(0,f.jsx)(`input`,{type:`checkbox`,checked:l.has(e.id),onChange:t=>T(e.id,t.target.checked)}),(0,f.jsxs)(`span`,{children:[(0,f.jsx)(`strong`,{children:e.model}),(0,f.jsx)(`small`,{children:e.providerName})]})]},e.id))]}),(0,f.jsx)(`label`,{className:`compare-prompt-label`,htmlFor:`comparePrompt`,children:`实验任务`}),(0,f.jsx)(`textarea`,{id:`comparePrompt`,className:`compare-prompt`,rows:3,placeholder:`输入要交给多个模型的相同任务…`,value:p,onChange:e=>_(e.target.value)}),(0,f.jsxs)(`div`,{className:`compare-actions`,children:[(0,f.jsxs)(`span`,{children:[`已选择 `,w.length,` 个模型`]}),(0,f.jsx)(`button`,{className:`btn-primary`,type:`button`,disabled:S,onClick:()=>void E(),children:S?`实验进行中…`:v.length?`再次实验`:`开始实验`})]})]}),(0,f.jsx)(`div`,{className:`compare-results`,children:v.map(e=>(0,f.jsx)(g,{result:e,prompt:p.trim(),adopt:c},e.id))}),(0,f.jsxs)(`details`,{className:`compare-history`,open:!b.length,children:[(0,f.jsxs)(`summary`,{children:[(0,f.jsx)(`span`,{children:`历史实验`}),(0,f.jsxs)(`small`,{children:[b.length,` 条,保存在当前设备`]})]}),(0,f.jsx)(`div`,{children:b.length?b.map(e=>{let t=new Date(e.createdAt),n=Number.isNaN(t.getTime())?``:t.toLocaleString(`zh-CN`,{month:`numeric`,day:`numeric`,hour:`2-digit`,minute:`2-digit`}),r=e.results.filter(e=>e.status===`success`).length;return(0,f.jsxs)(`button`,{type:`button`,className:`compare-history-item`,onClick:()=>{_(e.prompt),y(e.results)},children:[(0,f.jsxs)(`span`,{children:[(0,f.jsx)(`strong`,{children:e.prompt}),(0,f.jsxs)(`small`,{children:[e.projectName,` · `,r,` 个结果`]})]}),(0,f.jsx)(`time`,{children:n})]},e.id)}):(0,f.jsx)(`div`,{className:`compare-history-empty`,children:`完成一次实验后,结果会保存在当前设备。`})})]})]})}function v(){return(i.providers||[]).flatMap(e=>(e.models||[]).map(t=>({id:`${e.id}:${t}`,providerId:e.id,providerName:e.name||e.id,model:t})))}async function y(e,t){let n=performance.now();try{let r=await fetch(i.apiBase+`/v1/chat/completions`,{method:`POST`,headers:{"Content-Type":`application/json`,...s()},body:JSON.stringify({providerId:e.providerId,model:e.model,messages:[{role:`user`,content:t}],stream:!1,temperature:i.params.temperature,max_tokens:i.params.max_tokens,top_p:i.params.top_p,workspaceId:i.selectedWorkspace?.id||null,projectId:i.selectedProject?.id||null,assetIds:[...i.selectedAssetIds],interactionId:`compare_${Date.now().toString(36)}_${e.providerId}`})}),a=await r.json();if(!r.ok){let e=typeof a?.error==`string`?a.error:a?.error?.message;throw Error(e||`HTTP ${r.status}`)}return{...e,status:`success`,text:a?.choices?.[0]?.message?.content||``,usage:a?.usage||{},elapsedMs:Math.round(performance.now()-n)}}catch(t){return{...e,status:`error`,error:t instanceof Error?t.message:String(t),elapsedMs:Math.round(performance.now()-n)}}}async function b(e,t){if(!e.text)return;let n=(i.providers||[]).find(t=>t.id===e.providerId);n&&(i.selectedProvider=n,i.selectedModel=e.model),i.messages=[...i.messages,{role:`user`,content:t},{role:`assistant`,content:e.text,model:e.model,providerName:e.providerName,usage:e.usage,elapsedMs:e.elapsedMs,importedFromCompare:!0}];try{await ensureConversation(),await saveCurrentMessages()}catch{}x=i.currentConvId?`#/chat/${encodeURIComponent(i.currentConvId)}`:`#/new`,renderTopbar(),renderContent(),renderConvList(),closeModal(),window.requestAnimationFrame(()=>c(`#input`)?.focus()),a(`已采用 ${e.model} 的结果`)}var x=``,S=null;function C(){let e=v();if(e.length<2){a(`模型实验至少需要配置两个模型`,`error`),openSettings(`providers`);return}if(c(`#modal`)?.classList.contains(`open`)&&c(`#modalCard`)?.classList.contains(`compare-modal-card`))return;location.hash===`#/compare`?x||=i.currentConvId?`#/chat/${encodeURIComponent(i.currentConvId)}`:`#/new`:(x=location.hash||(i.currentConvId?`#/chat/${encodeURIComponent(i.currentConvId)}`:`#/new`),history.pushState(null,``,`#/compare`));let t=[i.selectedProvider&&i.selectedModel?`${i.selectedProvider.id}:${i.selectedModel}`:``,...e.map(e=>e.id)].filter(Boolean).filter((e,t,n)=>n.indexOf(e)===t).slice(0,2),n=[...i.messages||[]].reverse().find(e=>e.role===`user`)?.content||``;showModal({title:`模型实验`,body:`<div id="compareReactRoot"></div>`,onMount:r=>{r.classList.add(`compare-modal-card`);let a=r.querySelector(`#compareReactRoot`);a&&(S=(0,u.createRoot)(a),S.render((0,d.createElement)(_,{targets:e,defaultTargetIds:t,initialPrompt:n,projectName:i.selectedProject?.name||`未选择项目`,fileCount:i.selectedAssetIds?.size||0,memoryCount:(i.memories||[]).filter(e=>e.enabled!==!1).length,execute:y,adopt:b})))},onClose:()=>{S?.unmount(),S=null,location.hash===`#/compare`&&history.replaceState(null,``,x||`#/new`),x=``}})}function w(){let e=c(`#compareBtn`);e&&(e.onclick=C),o(`[data-open-compare]`).forEach(e=>e.onclick=C)}export{C as openCompare,w as setupCompare};
@@ -1,7 +0,0 @@
1
- import{a as e,d as t,f as n,h as r,p as i,u as a}from"./index-CASVCOUO.js";async function o(){try{t.conversations=await e(`/api/conversations`)}catch{t.conversations=[]}s()}function s(){let n=i(`#convList`),a=(t.convSearch||``).trim().toLowerCase(),o=a?t.conversations.filter(e=>(e.title||`新对话`).toLowerCase().includes(a)):t.conversations;if(!o.length){n.innerHTML=`<div style="padding:18px 12px; color:var(--label-caption); font-size:12.5px;">`+(t.conversations.length?`没有匹配的对话`:`还没有对话`)+`</div>`;return}n.innerHTML=o.map(e=>`
2
- <div class="conv-item ${e.id===t.currentConvId?`active`:``}" data-id="${r(e.id)}">
3
- <span class="conv-dot"></span>
4
- <span style="overflow:hidden;text-overflow:ellipsis;">${r(e.title||`新对话`)}</span>
5
- <button class="conv-del" data-del="${r(e.id)}" title="删除">×</button>
6
- </div>
7
- `).join(``),n.querySelectorAll(`.conv-item`).forEach(e=>{e.onclick=t=>{t.target.dataset.del||l(e.dataset.id)}}),n.querySelectorAll(`.conv-del`).forEach(n=>{n.onclick=async r=>{r.stopPropagation();let a=n.dataset.del;if(await showConfirm({title:`删除对话`,message:`删除该对话?`,confirmLabel:`删除`,danger:!0})){try{await e(`/api/conversations/`+a,{method:`DELETE`})}catch{}t.conversations=t.conversations.filter(e=>e.id!==a),t.currentConvId===a&&(t.currentConvId=null,t.messages=[],renderContent(),i(`#topbarTitle`).textContent=`新对话`),s()}}})}async function c(){t.currentConvId=null,t.messages=[],i(`#topbarTitle`).textContent=`新对话`,renderContent(),s(),renderInspector(),i(`#heroInput`)?.focus(),location.hash!==`#/new`&&history.pushState(null,``,`#/new`)}i(`#newChatBtn`).onclick=c;async function l(r){try{let n=await e(`/api/conversations/`+r);(n.workspaceId&&(!t.selectedWorkspace||t.selectedWorkspace.id!==n.workspaceId)||n.projectId&&(!t.selectedProject||t.selectedProject.id!==n.projectId))&&(t.selectedWorkspace=t.workspaces.find(e=>e.id===n.workspaceId)||t.selectedWorkspace,t.selectedWorkspace&&await loadProjects(),n.projectId&&(t.selectedProject=t.projects.find(e=>e.id===n.projectId)||t.selectedProject,t.selectedProject&&(localStorage.setItem(`multichat_project`,t.selectedProject.id),t.assets=await e(`/api/assets?projectId=`+encodeURIComponent(t.selectedProject.id)),t.selectedAssetIds=new Set(t.assets.map(e=>e.id))))),t.currentConvId=n.id,t.messages=(n.messages||[]).map(e=>({role:e.role,content:e.content,model:e.model})),i(`#topbarTitle`).textContent=n.title||`对话`,renderTopbar(),renderContent(),s(),d();let a=`#/chat/${encodeURIComponent(n.id)}`;location.hash!==a&&history.pushState(null,``,a)}catch(e){n(`打开对话失败:`+e.message,`error`)}}function u(e){try{return e<1024?e+` B`:e<1048576?(e/1024).toFixed(1)+` KB`:(e/1024/1024).toFixed(1)+` MB`}catch{return``}}function d(){renderContent(),renderInspector()}function f(){let e=t.selectedProject;if(e){if(e.defaultAgentId){let n=t.agents.find(t=>t.id===e.defaultAgentId);n&&(t.selectedAgent=n,a())}if(e.defaultProviderId&&e.defaultModel){let n=t.providers.find(t=>t.id===e.defaultProviderId);n&&(t.selectedProvider=n,t.selectedModel=e.defaultModel,localStorage.setItem(`multichat_lastModel`,n.id+`:`+e.defaultModel))}renderTopbar()}}export{f as applyProjectDefaults,u as fmtSize,o as loadConversations,c as newConversation,l as openConversation,s as renderConvList,d as renderFileContext};
@@ -1 +0,0 @@
1
- import{a as e,d as t}from"./index-CASVCOUO.js";async function n(){try{t.providers=await e(`/api/providers`)}catch{t.providers=[]}}async function r(){try{t.runtime=await e(`/api/runtime`)}catch{t.runtime=null}}async function i(){try{t.runs=await e(`/api/runs?limit=40`)}catch{t.runs=[]}}async function a(n=t.usageRange){t.usageRange=String(n||`30`),t.usageLoading=!0;try{let n=new Date().getTimezoneOffset();t.usage=await e(`/api/usage?range=${encodeURIComponent(t.usageRange)}&offset=${n}`)}catch{t.usage=null}finally{t.usageLoading=!1}}async function o(){try{t.capabilities=await e(`/api/capabilities`)}catch{t.capabilities=null}}async function s(){if(!t.selectedProject){t.memories=[],t.snapshots=[];return}try{[t.memories,t.snapshots]=await Promise.all([e(`/api/memories?projectId=`+encodeURIComponent(t.selectedProject.id)),e(`/api/snapshots?projectId=`+encodeURIComponent(t.selectedProject.id))])}catch{t.memories=[],t.snapshots=[]}}async function c(){try{t.workspaces=await e(`/api/workspaces`);let n=localStorage.getItem(`multichat_workspace`);t.selectedWorkspace=t.workspaces.find(e=>e.id===n)||t.workspaces[0]||null,await l()}catch{t.workspaces=[],t.projects=[]}}async function l(){if(!t.selectedWorkspace){t.projects=[],t.selectedProject=null;return}try{t.projects=await e(`/api/projects?workspaceId=`+encodeURIComponent(t.selectedWorkspace.id));let n=localStorage.getItem(`multichat_project`);t.selectedProject=t.projects.find(e=>e.id===n)||t.projects.find(e=>e.id===t.selectedWorkspace.defaultProjectId)||t.projects[0]||null,localStorage.setItem(`multichat_workspace`,t.selectedWorkspace.id),t.selectedProject&&localStorage.setItem(`multichat_project`,t.selectedProject.id),t.selectedProject&&(t.assets=await e(`/api/assets?projectId=`+encodeURIComponent(t.selectedProject.id)),t.selectedAssetIds=new Set(t.assets.map(e=>e.id)),applyProjectDefaults(),await s())}catch{t.projects=[],t.selectedProject=null,t.assets=[],t.selectedAssetIds=new Set}}async function u(){try{t.skills=await e(`/api/skills`)}catch{t.skills=[]}}async function d(){try{t.tools=await e(`/api/tools`)}catch{t.tools=[]}}async function f(){try{t.mcpServers=await e(`/api/mcp-servers`)}catch{t.mcpServers=[]}}async function p(){let n=t.selectedAgent?.id;try{t.agents=await e(`/api/agents`),n&&(t.selectedAgent=t.agents.find(e=>e.id===n)||null)}catch{t.agents=[],n&&(t.selectedAgent=null)}}async function m(){try{t.plugins=await e(`/api/plugins`)}catch{t.plugins=[]}}export{p as loadAgents,o as loadCapabilities,f as loadMcpServers,m as loadPlugins,s as loadProjectControlData,l as loadProjects,n as loadProviders,i as loadRuns,r as loadRuntime,u as loadSkills,d as loadTools,a as loadUsage,c as loadWorkspaces};
@@ -1,28 +0,0 @@
1
- import{a as e,f as t,h as n,p as r}from"./index-CASVCOUO.js";var i=`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 15V4M7.5 8.5 12 4l4.5 4.5"/><path d="M4 15v3.5A1.5 1.5 0 0 0 5.5 20h13a1.5 1.5 0 0 0 1.5-1.5V15"/></svg>`,a=null,o={skill:{title:`导入 Agent Skill`,tab:`skills`,accept:`.zip,.md`,fileLabel:`选择 ZIP / SKILL.md`,directory:!0,help:`支持 WorkBuddy 风格的 Skill ZIP、单个 SKILL.md,或直接选择完整目录。目录中的 scripts、references、assets 和 templates 会原样保留。`},mcp:{title:`导入 MCP 配置`,tab:`mcp`,accept:`.json,.mcp.json`,fileLabel:`选择 MCP JSON`,directory:!1,help:`支持 mcpServers、mcp_servers、servers 和直接 server map。导入项默认停用且未信任,不会在导入时启动命令或连接网络。`},plugin:{title:`安装插件包`,tab:`plugins`,accept:`.zip`,fileLabel:`选择插件 ZIP`,directory:!0,help:`插件以 .codex-plugin/plugin.json 为入口,整包注册到当前项目市场,Skill、MCP 与资源目录不会被扁平化。安装与启用分离。`}};function s(e){return new Promise((t,n)=>{let r=new FileReader;r.onerror=()=>n(Error(`无法读取 ${e.name}`)),r.onload=()=>{let i=String(r.result||``),a=i.indexOf(`,`);a<0?n(Error(`无法编码 ${e.name}`)):t(i.slice(a+1))},r.readAsDataURL(e)})}async function c(e){let t=Array.from(e||[]);if(!t.length)throw Error(`请先选择文件`);if(t.length>600)throw Error(`一次最多导入 600 个文件`);if(t.reduce((e,t)=>e+t.size,0)>20971520)throw Error(`导入文件总大小不能超过 20 MB`);if(!(t.length>1||t.some(e=>!!e.webkitRelativePath))){let e=t[0];return{fileName:e.name,contentBase64:await s(e)}}return{files:await Promise.all(t.map(async e=>({path:e.webkitRelativePath||e.name,contentBase64:await s(e)})))}}function l(e){return Number.isFinite(e)?e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KB`:`${(e/1024/1024).toFixed(1)} MB`:``}function u(e,t){if(e===`mcp`)return`<div class="extension-import-list">${(t.servers||[]).map(e=>`
2
- <div class="extension-import-row"><div><strong>${n(e.name)}</strong><span>${n(e.id)}</span></div><div class="mc-tags"><span class="mc-tag">${n(String(e.transport||``).toUpperCase())}</span>${(e.targets||[]).map(e=>`<span class="mc-tag">${n(e)}</span>`).join(``)}</div></div>`).join(``)}</div>`;let r=t.components||{};return`<div class="mc-tags extension-import-tags">${[t.fileCount===void 0?``:`${t.fileCount} 个文件`,t.totalBytes===void 0?``:l(t.totalBytes),...t.resources||[],r.skills===void 0?``:`${r.skills} Skills`,r.mcpServers===void 0?``:`${r.mcpServers} MCP`].filter(Boolean).map(e=>`<span class="mc-tag">${n(e)}</span>`).join(``)}</div>
3
- ${(t.fileTree||[]).length?`<details class="extension-file-tree"><summary>查看包内文件</summary><pre>${n(t.fileTree.join(`
4
- `))}</pre></details>`:``}`}async function d(e){e===`skill`&&await Promise.all([loadSkills(),loadAgents(),loadRuntime()]),e===`mcp`&&await Promise.all([loadMcpServers(),loadAgents(),loadRuntime()]),e===`plugin`&&await Promise.all([loadPlugins(),loadSkills(),loadMcpServers(),loadAgents(),loadRuntime()])}function f(i,a,s){let c=o[i],l=s.conflicts||[],f=l.some(e=>e.replaceable===!1),p=s.warnings||[];showModal({title:`预检通过 · ${s.name}`,body:`<div class="extension-import-summary">
5
- <div class="extension-import-kind">${i===`skill`?`SKILL`:i===`mcp`?`MCP`:`PLUGIN`}</div>
6
- <div><div class="extension-import-name">${n(s.name)}</div><div class="pmeta">${n(s.description||``)}${s.version?` · v${n(s.version)}`:``}</div></div>
7
- </div>
8
- <div class="field"><label>安装范围</label><div class="extension-import-scope">当前项目 · ${i===`mcp`?`.codex/config.toml(启用且选择 Codex 后同步)`:i===`skill`?`.agents/skills`:`.agents/plugins/marketplace.json`}</div></div>
9
- ${u(i,s)}
10
- ${p.length?`<div class="extension-import-warnings"><strong>启用前请确认</strong>${p.map(e=>`<div>• ${n(e)}</div>`).join(``)}</div>`:``}
11
- ${l.length?`<div class="extension-import-conflicts"><strong>发现同名项</strong>${l.map(e=>`<div>${n(e.id)}${e.path?` · ${n(e.path)}`:``}${e.replaceable===!1?` · 其他来源,不可覆盖`:``}</div>`).join(``)}</div>`:``}
12
- ${i===`plugin`?`<div class="extension-import-note">插件以完整资源包安装并独立启停,包契约使用 Codex plugin。MultiChat 当前会加载其中的 Skills 与 MCP;其他资源会保留,但不会把上传的 JS、hooks 直接注入网站主进程。</div>`:``}
13
- ${l.length&&!f?`<label class="extension-import-confirm"><input type="checkbox" id="replaceExtension" /> 我已确认,用这个包覆盖当前项目中的同名项</label>`:``}
14
- <div id="extensionImportErr" class="auth-error">${f?`同名项来自插件或其他受保护来源,本次导入不能覆盖。`:``}</div>
15
- <div class="row"><button type="button" class="btn-ghost" id="importBack">重新选择</button><button type="button" class="btn-primary" id="importCommit" style="width:auto;padding:9px 18px;" ${f?`disabled`:``}>确认导入</button></div>`,onMount:n=>{r(`#importBack`,n).onclick=()=>m(i),r(`#importCommit`,n).onclick=async()=>{let o=l.length>0&&!!r(`#replaceExtension`,n)?.checked;if(l.length&&!o){r(`#extensionImportErr`,n).textContent=`请先确认覆盖同名项。`;return}let u=r(`#importCommit`,n);u.disabled=!0,u.textContent=`正在导入…`;try{await e(`/api/extensions/import/${i}/install`,{method:`POST`,body:JSON.stringify({...a,expectedFingerprint:s.fingerprint,conflictPolicy:o?`replace`:`reject`})}),await d(i),renderSettings(c.tab,!0),closeModal(),t(i===`plugin`?`插件包已导入,当前保持停用`:i===`mcp`?`MCP 配置已导入,当前保持停用`:`Skill 已导入当前项目`)}catch(e){r(`#extensionImportErr`,n).textContent=e.message,u.disabled=!1,u.textContent=`确认导入`}}}})}async function p(t,n,i){let o=r(`#extensionPickStatus`,i);o.classList.remove(`error`),o.textContent=`正在读取并预检…`;let s=r(`#extensionPickRetry`,i);s&&(s.hidden=!0,s.disabled=!0);try{let r=await c(n);f(t,r,await e(`/api/extensions/import/${t}/inspect`,{method:`POST`,body:JSON.stringify(r)}))}catch(e){a={kind:t,files:n},o.textContent=e.message,o.classList.add(`error`),s&&(s.hidden=!1,s.disabled=!1,s.onclick=()=>a&&p(a.kind,a.files,i))}}function m(e){let t=o[e];showModal({title:t.title,body:`<p class="lead" style="margin-bottom:14px;">${n(t.help)}</p>
16
- <div class="extension-dropzone" id="extensionDropzone" tabindex="0">
17
- <div class="extension-drop-icon">${i}</div><strong>拖入文件进行预检</strong><span>不会在预检阶段执行任何脚本或 MCP 命令</span>
18
- </div>
19
- <div class="extension-picker-actions">
20
- <button type="button" class="btn-primary" id="pickExtensionFile" style="width:auto;padding:9px 16px;">${n(t.fileLabel)}</button>
21
- ${t.directory?`<button type="button" class="btn-ghost" id="pickExtensionDirectory">选择完整目录</button>`:``}
22
- </div>
23
- <input type="file" id="extensionFileInput" accept="${n(t.accept)}" hidden />
24
- ${t.directory?`<input type="file" id="extensionDirectoryInput" hidden multiple />`:``}
25
- <div id="extensionPickStatus" class="extension-pick-status" role="status"></div>
26
- <button type="button" class="btn-ghost" id="extensionPickRetry" hidden>用刚才的文件重新预检</button>
27
- <div class="extension-import-note">默认冲突策略是拒绝覆盖;只有预检发现冲突且你再次勾选确认后,才会替换旧版本。</div>
28
- <div class="row"><button type="button" class="btn-ghost" id="extensionImportCancel">取消</button></div>`,onMount:t=>{let n=r(`#extensionFileInput`,t),i=r(`#extensionDirectoryInput`,t);i&&i.setAttribute(`webkitdirectory`,``),r(`#pickExtensionFile`,t).onclick=()=>n.click(),i&&(r(`#pickExtensionDirectory`,t).onclick=()=>i.click()),r(`#extensionImportCancel`,t).onclick=closeModal,n.onchange=()=>n.files?.length&&p(e,n.files,t),i&&(i.onchange=()=>i.files?.length&&p(e,i.files,t));let a=r(`#extensionDropzone`,t);a.ondragover=e=>{e.preventDefault(),a.classList.add(`dragging`)},a.ondragleave=()=>a.classList.remove(`dragging`),a.ondrop=n=>{n.preventDefault(),a.classList.remove(`dragging`),n.dataTransfer?.files?.length&&p(e,n.dataTransfer.files,t)}}})}export{m as showExtensionImport};
@@ -1,70 +0,0 @@
1
- import{a as e,d as t,f as n,h as r,p as i}from"./index-CASVCOUO.js";function a(){return`
2
- <div class="import-bar">
3
- <span class="import-bar-label">运行配置备份</span>
4
- <button class="mini-btn" id="importFile">导入配置 JSON</button>
5
- <input type="file" id="importFileInput" accept=".json,application/json" style="display:none" />
6
- </div>`}async function o(r,i){try{let a=await e(`/api/import`,{method:`POST`,body:JSON.stringify(r)});return await loadAgents(),renderSettings(t.currentTab||`agents`,!0),renderTopbar(),n(`已导入 ${a.agents} 个运行配置(来源:${i})`),a}catch(e){throw n(e.message,`error`),e}}function s(e){if(Array.isArray(e))return{agents:e};if(e&&e.agents)return{agents:e.agents};if(e&&typeof e==`object`&&e.id)return{agents:[e]};throw Error(`文件不是 Agent JSON 备份`)}function c(){let e=i(`#settingsBody`),t=e.querySelector(`#importFile`),r=e.querySelector(`#importFileInput`);t&&r&&(t.onclick=()=>r.click(),r.onchange=async()=>{let e=r.files&&r.files[0];if(e){try{let t=await e.text();await o(s(JSON.parse(t)),`文件 `+e.name)}catch(e){n(`解析失败:`+e.message,`error`)}r.value=``}})}function l(e,t){let r=new Blob([JSON.stringify(e,null,2)],{type:`application/json`}),i=document.createElement(`a`);i.href=URL.createObjectURL(r),i.download=(e.id||t)+`.json`,document.body.appendChild(i),i.click(),i.remove(),setTimeout(()=>URL.revokeObjectURL(i.href),1e3),n(`已导出:`+(e.name||e.id))}function u(a){let o=!!a,s=o?t.agents.find(e=>e.id===a):null;showModal({title:o?`编辑运行配置`:`新建运行配置`,body:`<form id="agentForm">
7
- <div class="field"><label>名称</label><input name="name" value="${s?r(s.name):``}" placeholder="中英翻译" required /></div>
8
- <div class="field"><label>描述</label><input name="description" value="${s?r(s.description||``):``}" placeholder="一句话说明这套配置适合什么任务" /></div>
9
- <div class="field"><label>系统提示词</label><textarea name="systemPrompt" rows="4" placeholder="你是一个...">${s?r(s.systemPrompt||``):``}</textarea></div>
10
- <div class="field"><label>单次最大执行轮数</label><input name="maxIterations" type="number" min="1" max="30" step="1" value="${r(s?.maxIterations||12)}" /><div class="pmeta">达到上限后保存检查点,可在对话中继续运行。</div></div>
11
- <div class="field"><label>Agent Skills(先提供描述,匹配任务时再加载完整工作流)</label>
12
- <div class="extension-checks">
13
- ${t.skills.map(e=>{let t=e.key||e.id,n=s&&(s.skillRefs||s.skillIds)||[],i=n.includes(t)||n.includes(e.id)?`checked`:``;return`<label>
14
- <input type="checkbox" name="skill" value="${r(t)}" ${i} /> ${r(e.name)} <span class="pmeta">${r(sourceLabel(e.source))}${e.enabled?``:` · 已停用`}</span>
15
- </label>`}).join(``)||`<span class="pmeta">没有已启用的 Skill</span>`}
16
- </div>
17
- </div>
18
- <div class="field"><label>内置工具(函数调用)</label>
19
- <div class="extension-checks">
20
- ${(t.tools||[]).map(e=>{let t=s&&(s.toolIds||[]).includes(e.id)?`checked`:``;return`<label><input type="checkbox" name="tool" value="${r(e.id)}" ${t} /> ${r(e.name)}${e.enabled?``:`(已停用)`}</label>`}).join(``)||`<span class="pmeta">没有已启用的内置工具</span>`}
21
- </div>
22
- </div>
23
- <div class="field"><label>MCP servers(工具由 tools/list 实时发现)</label>
24
- <div class="extension-checks">
25
- ${(t.mcpServers||[]).filter(e=>(e.targets||[`multichat`]).includes(`multichat`)).map(e=>{let t=s&&(s.mcpServerIds||[]).includes(e.id)?`checked`:``;return`<label><input type="checkbox" name="mcpServer" value="${r(e.id)}" ${t} /> ${r(e.name)} <span class="pmeta">${(e.tools||[]).length} tools${e.enabled?``:` · 已停用`}</span></label>`}).join(``)||`<span class="pmeta">没有已启用的 MCP server</span>`}
26
- </div>
27
- </div>
28
- <div id="agentErr" class="auth-error"></div>
29
- <div class="row">
30
- <button type="button" class="btn-ghost" id="agentCancel">取消</button>
31
- <button type="submit" class="btn-primary" style="width:auto;padding:9px 18px;">${o?`保存`:`创建`}</button>
32
- </div>
33
- </form>`,onMount:r=>{i(`#agentCancel`,r).onclick=closeModal,i(`#agentForm`,r).onsubmit=async c=>{c.preventDefault();let l=new FormData(c.target),u=Object.fromEntries(l.entries());u.name&&=u.name.trim(),u.description&&=u.description.trim(),u.maxIterations=Math.max(1,Math.min(30,Number.parseInt(u.maxIterations||`12`,10)||12));let d=new Set(t.skills.flatMap(e=>[e.key||e.id,e.id])),f=new Set((t.tools||[]).map(e=>e.id)),p=new Set((t.mcpServers||[]).filter(e=>(e.targets||[`multichat`]).includes(`multichat`)).map(e=>e.id)),m=o?(s?.skillRefs||s?.skillIds||[]).filter(e=>!d.has(e)):[],h=o?(s?.toolIds||[]).filter(e=>!f.has(e)):[],g=o?(s?.mcpServerIds||[]).filter(e=>!p.has(e)):[];u.skillRefs=[...Array.from(c.target.querySelectorAll(`input[name="skill"]:checked`)).map(e=>e.value),...m],u.toolIds=[...Array.from(c.target.querySelectorAll(`input[name="tool"]:checked`)).map(e=>e.value),...h],u.mcpServerIds=[...Array.from(c.target.querySelectorAll(`input[name="mcpServer"]:checked`)).map(e=>e.value),...g];try{o?(await e(`/api/agents/`+a,{method:`PUT`,body:JSON.stringify(u)}),n(`已保存`)):(await e(`/api/agents`,{method:`POST`,body:JSON.stringify(u)}),n(`已创建`)),await loadAgents(),renderSettings(t.currentTab||`agents`,!0),renderTopbar(),closeModal()}catch(e){i(`#agentErr`,r).textContent=e.message}}}})}function d(a){let o=!!a,s=o?t.skills.find(e=>(e.key||e.id)===a):null;showModal({title:o?`编辑 Agent Skill`:`新建 Agent Skill`,body:`<form id="skillForm">
34
- <div class="field"><label>Skill ID</label><input name="id" value="${s?r(s.id):``}" placeholder="release-notes" required pattern="[a-z0-9-]+" ${o?`readonly`:``} />
35
- <div class="pmeta">1–64 位小写字母、数字或连字符;写入 SKILL.md frontmatter。</div></div>
36
- <div class="field"><label>Skill name</label><input name="name" value="${s?r(s.name):``}" placeholder="release-notes" required pattern="[a-z0-9-]+" ${o?`readonly`:``} /></div>
37
- <div class="field"><label>Description</label><textarea name="description" rows="3" placeholder="说明何时应使用此 Skill,以及它能完成什么。" required>${s?r(s.description||``):``}</textarea></div>
38
- <div class="field"><label>Instructions(SKILL.md 正文)</label><textarea name="instructions" rows="9" placeholder="# Workflow&#10;&#10;1. ..." required>${s?r(s.instructions||``):``}</textarea>
39
- <div class="pmeta">需要脚本、参考资料或模板时,可在对应 Skill 目录添加 scripts、references、assets。</div></div>
40
- <div id="skillErr" class="auth-error"></div>
41
- <div class="row">
42
- <button type="button" class="btn-ghost" id="skillCancel">取消</button>
43
- <button type="submit" class="btn-primary" style="width:auto;padding:9px 18px;">${o?`保存`:`创建`}</button>
44
- </div>
45
- </form>`,onMount:r=>{i(`#skillCancel`,r).onclick=closeModal,i(`#skillForm`,r).onsubmit=async s=>{s.preventDefault();let c=new FormData(s.target),l=Object.fromEntries(c.entries());l.id&&=l.id.trim(),l.name&&=l.name.trim(),l.description&&=l.description.trim(),l.instructions&&=l.instructions.trim();try{if(o){let t={...l};delete t.id,await e(`/api/skills/`+encodeURIComponent(a),{method:`PUT`,body:JSON.stringify(t)}),n(`已保存`)}else await e(`/api/skills`,{method:`POST`,body:JSON.stringify(l)}),n(`已创建`);await loadSkills(),renderSettings(t.currentTab||`skills`,!0),closeModal()}catch(e){i(`#skillErr`,r).textContent=e.message}}}})}var f=[{id:`openai`,name:`OpenAI`,apiType:`openai`,baseUrl:`https://api.openai.com/v1`,models:[`gpt-4o`,`gpt-4o-mini`,`gpt-4-turbo`]},{id:`deepseek`,name:`DeepSeek`,apiType:`openai`,baseUrl:`https://api.deepseek.com/v1`,models:[`deepseek-chat`,`deepseek-reasoner`]},{id:`anthropic`,name:`Anthropic`,apiType:`anthropic`,baseUrl:`https://api.anthropic.com/v1`,models:[`claude-3-5-sonnet-latest`,`claude-3-opus-latest`,`claude-3-haiku-latest`]},{id:`gemini`,name:`Google Gemini`,apiType:`openai`,baseUrl:`https://generativelanguage.googleapis.com/v1beta/openai`,models:[`gemini-1.5-pro`,`gemini-1.5-flash`,`gemini-2.0-flash`]},{id:`moonshot`,name:`月之暗面 Kimi`,apiType:`openai`,baseUrl:`https://api.moonshot.cn/v1`,models:[`moonshot-v1-8k`,`moonshot-v1-32k`,`moonshot-v1-128k`]},{id:`zhipu`,name:`智谱 GLM`,apiType:`zhipu`,baseUrl:`https://open.bigmodel.cn/api/paas/v4`,models:[`glm-4-plus`,`glm-4`,`glm-4-air`]},{id:`dashscope`,name:`阿里云 DashScope`,apiType:`openai`,baseUrl:`https://dashscope.aliyuncs.com/compatible-mode/v1`,models:[`qwen-max`,`qwen-plus`,`qwen-turbo`]},{id:`ollama`,name:`Ollama (本地)`,apiType:`ollama`,baseUrl:`http://localhost:11434/v1`,models:[]},{id:`lmstudio`,name:`LM Studio (本地)`,apiType:`lmstudio`,baseUrl:`http://localhost:1234/v1`,models:[]},{id:`siliconflow`,name:`硅基流动 SiliconFlow (注册送额度)`,apiType:`openai`,baseUrl:`https://api.siliconflow.cn/v1`,models:[`Qwen/Qwen2.5-72B-Instruct`,`deepseek-ai/DeepSeek-V3`,`Qwen/Qwen2.5-7B-Instruct`]}];function p(){showModal({title:`添加提供方`,body:`<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
46
- ${f.map(e=>`<button class="add-tile" data-id="${e.id}" style="text-align:left;padding:14px;">
47
- <div style="font-weight:600;color:var(--label-primary);">${e.name}</div>
48
- <div style="font-size:11.5px;color:var(--label-caption);margin-top:2px;">${e.apiType} · ${e.baseUrl}</div>
49
- </button>`).join(``)}
50
- </div>`,onMount:t=>{t.querySelectorAll(`[data-id]`).forEach(t=>t.onclick=async()=>{let r=f.find(e=>e.id===t.dataset.id);try{await e(`/api/providers`,{method:`POST`,body:JSON.stringify(r)}),await loadProviders(),renderSettings(`providers`,!0),closeModal(),n(`已添加:`+r.name)}catch(e){n(e.message,`error`)}})}})}function m(){showModal({title:`自定义提供方`,body:`<form id="customForm">
51
- <div class="field"><label>Provider ID</label><input name="id" placeholder="acme-gateway" required pattern="[-a-zA-Z0-9_]+" />
52
- <div style="font-size:11.5px;color:var(--label-caption);margin-top:4px;">小写字母、数字、下划线或连字符,唯一标识该提供方。</div></div>
53
- <div class="field"><label>模型列表</label><textarea name="models" rows="2" placeholder="deepseek-chat, deepseek-reasoner"></textarea>
54
- <div style="font-size:11.5px;color:var(--label-caption);margin-top:4px;">逗号或换行分隔;如不确定可留空,稍后在模型卡片中补充。</div></div>
55
- <div class="field"><label>显示名称</label><input name="name" placeholder="显示名称" /></div>
56
- <div class="field"><label>API 地址</label><input name="baseUrl" placeholder="https://gateway.example/v1" required /></div>
57
- <div class="field"><label>API 协议</label><select name="apiType">
58
- <option value="openai">openai-completions</option>
59
- <option value="anthropic">anthropic-messages</option>
60
- <option value="ollama">ollama</option>
61
- <option value="lmstudio">lmstudio</option>
62
- </select></div>
63
- <div class="field"><label>API 密钥</label><input name="apiKey" type="password" placeholder="输入 API 密钥" /></div>
64
- <label class="provider-private"><input name="allowPrivate" type="checkbox" /> 允许该提供方访问本机或内网地址(仅本地网关需要)</label>
65
- <div id="customErr" class="auth-error"></div>
66
- <div class="row">
67
- <button type="button" class="btn-ghost" id="customCancel">取消</button>
68
- <button type="submit" class="btn-primary" style="width:auto;padding:9px 18px;">保存</button>
69
- </div>
70
- </form>`,onMount:t=>{i(`#customCancel`,t).onclick=closeModal,i(`#customForm`,t).onsubmit=async r=>{r.preventDefault();let a=new FormData(r.target),o=Object.fromEntries(a.entries());o.models=(o.models||``).toString().split(/[,\n]/).map(e=>e.trim()).filter(Boolean),o.allowPrivate=a.get(`allowPrivate`)===`on`,o.id&&=o.id.trim(),o.name&&=o.name.trim();try{await e(`/api/providers`,{method:`POST`,body:JSON.stringify(o)}),await loadProviders(),renderSettings(`providers`,!0),closeModal(),n(`已添加`)}catch(e){i(`#customErr`,t).textContent=e.message}}}})}export{f as BUILTIN_PROVIDERS,o as doImport,l as exportEntity,a as importBarHTML,s as normalizeImport,p as showAddBuiltin,m as showAddCustom,u as showAgentModal,d as showSkillModal,c as wireImportBar};