@leterhong/multichat 1.6.0 → 1.7.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 (83) 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-DIOlVr1C.js +18 -0
  44. package/frontend/dist/assets/assets-D2_TVEqD.js +1 -0
  45. package/frontend/dist/assets/compare-BYqbOF36.js +1 -0
  46. package/frontend/dist/assets/conversations-DSJtvviE.js +1 -0
  47. package/frontend/dist/assets/data-D6TAf2h9.js +1 -0
  48. package/frontend/dist/assets/{extensionImport-CnTWKtWm.js → extensionImport-C2cHyfeC.js} +7 -7
  49. package/frontend/dist/assets/importExport-BWpKt1dW.js +69 -0
  50. package/frontend/dist/assets/index-BaUGyQ3C.js +46 -0
  51. package/frontend/dist/assets/index-DXwClNo-.css +1 -0
  52. package/frontend/dist/assets/init-DUZJcBWS.js +1 -0
  53. package/frontend/dist/assets/modal-CO4he94s.js +2 -0
  54. package/frontend/dist/assets/modelPicker-Ddloc_nz.js +14 -0
  55. package/frontend/dist/assets/pluginsUI-28Rxtlgo.js +16 -0
  56. package/frontend/dist/assets/react-CDEwA66Z.js +1 -0
  57. package/frontend/dist/assets/{render-DgAg1K8A.js → render-BVmIwN3C.js} +1 -1
  58. package/frontend/dist/assets/send-yu3ud0kM.js +14 -0
  59. package/frontend/dist/assets/settings-IUzleKUb.js +2 -0
  60. package/frontend/dist/assets/{shell-DFV2khQl.js → shell-BLqMMR-L.js} +1 -1
  61. package/frontend/dist/assets/workbench-ChZGUESv.js +40 -0
  62. package/frontend/dist/boot-fallback.js +16 -0
  63. package/frontend/dist/icon-maskable.svg +6 -3
  64. package/frontend/dist/icon.svg +6 -3
  65. package/frontend/dist/index.html +11 -4
  66. package/frontend/dist/manifest.webmanifest +2 -2
  67. package/frontend/dist/theme-seed.js +11 -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/importExport-B3mB-9zs.js +0 -70
  74. package/frontend/dist/assets/index-CASVCOUO.js +0 -34
  75. package/frontend/dist/assets/index-DSvbjNLc.css +0 -1
  76. package/frontend/dist/assets/init-ZPK483vB.js +0 -1
  77. package/frontend/dist/assets/jsx-runtime-ZSsbr0oW.js +0 -9
  78. package/frontend/dist/assets/modal-CgHocv3n.js +0 -2
  79. package/frontend/dist/assets/modelPicker-EUmmVnbj.js +0 -14
  80. package/frontend/dist/assets/pluginsUI-B6jGUQkx.js +0 -16
  81. package/frontend/dist/assets/send-BJhZ_5YA.js +0 -14
  82. package/frontend/dist/assets/settings-DdnYwZl-.js +0 -2
  83. package/frontend/dist/assets/workbench-BY2J4vWC.js +0 -41
@@ -1,4 +1,4 @@
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=>`
1
+ import{S as e,c as t,w as n,x as r}from"./index-BaUGyQ3C.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:`支持以 SKILL.md 为入口的 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
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
3
  ${(t.fileTree||[]).length?`<details class="extension-file-tree"><summary>查看包内文件</summary><pre>${n(t.fileTree.join(`
4
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">
@@ -12,17 +12,17 @@ import{a as e,f as t,h as n,p as r}from"./index-CASVCOUO.js";var i=`<svg viewBox
12
12
  ${i===`plugin`?`<div class="extension-import-note">插件以完整资源包安装并独立启停,包契约使用 Codex plugin。MultiChat 当前会加载其中的 Skills 与 MCP;其他资源会保留,但不会把上传的 JS、hooks 直接注入网站主进程。</div>`:``}
13
13
  ${l.length&&!f?`<label class="extension-import-confirm"><input type="checkbox" id="replaceExtension" /> 我已确认,用这个包覆盖当前项目中的同名项</label>`:``}
14
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>
15
+ <div class="row"><button type="button" class="btn-ghost" id="importBack">重新选择</button><button type="button" class="btn-primary btn-auto" id="importCommit" ${f?`disabled`:``}>确认导入</button></div>`,onMount:n=>{e(`#importBack`,n).onclick=()=>m(i),e(`#importCommit`,n).onclick=async()=>{let o=l.length>0&&!!e(`#replaceExtension`,n)?.checked;if(l.length&&!o){e(`#extensionImportErr`,n).textContent=`请先确认覆盖同名项。`;return}let u=e(`#importCommit`,n);u.disabled=!0,u.textContent=`正在导入…`;try{await t(`/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(),r(i===`plugin`?`插件包已导入,当前保持停用`:i===`mcp`?`MCP 配置已导入,当前保持停用`:`Skill 已导入当前项目`)}catch(t){e(`#extensionImportErr`,n).textContent=t.message,u.disabled=!1,u.textContent=`确认导入`}}}})}async function p(n,r,i){let o=e(`#extensionPickStatus`,i);o.classList.remove(`error`),o.textContent=`正在读取并预检…`;let s=e(`#extensionPickRetry`,i);s&&(s.hidden=!0,s.disabled=!0);try{let e=await c(r);f(n,e,await t(`/api/extensions/import/${n}/inspect`,{method:`POST`,body:JSON.stringify(e)}))}catch(e){a={kind:n,files:r},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(t){let r=o[t];showModal({title:r.title,body:`<p class="lead lead-tight">${n(r.help)}</p>
16
16
  <div class="extension-dropzone" id="extensionDropzone" tabindex="0">
17
17
  <div class="extension-drop-icon">${i}</div><strong>拖入文件进行预检</strong><span>不会在预检阶段执行任何脚本或 MCP 命令</span>
18
18
  </div>
19
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>`:``}
20
+ <button type="button" class="btn-primary btn-auto" id="pickExtensionFile">${n(r.fileLabel)}</button>
21
+ ${r.directory?`<button type="button" class="btn-ghost" id="pickExtensionDirectory">选择完整目录</button>`:``}
22
22
  </div>
23
- <input type="file" id="extensionFileInput" accept="${n(t.accept)}" hidden />
24
- ${t.directory?`<input type="file" id="extensionDirectoryInput" hidden multiple />`:``}
23
+ <input type="file" id="extensionFileInput" accept="${n(r.accept)}" hidden />
24
+ ${r.directory?`<input type="file" id="extensionDirectoryInput" hidden multiple />`:``}
25
25
  <div id="extensionPickStatus" class="extension-pick-status" role="status"></div>
26
26
  <button type="button" class="btn-ghost" id="extensionPickRetry" hidden>用刚才的文件重新预检</button>
27
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};
28
+ <div class="row"><button type="button" class="btn-ghost" id="extensionImportCancel">取消</button></div>`,onMount:n=>{let r=e(`#extensionFileInput`,n),i=e(`#extensionDirectoryInput`,n);i&&i.setAttribute(`webkitdirectory`,``),e(`#pickExtensionFile`,n).onclick=()=>r.click(),i&&(e(`#pickExtensionDirectory`,n).onclick=()=>i.click()),e(`#extensionImportCancel`,n).onclick=closeModal,r.onchange=()=>r.files?.length&&p(t,r.files,n),i&&(i.onchange=()=>i.files?.length&&p(t,i.files,n));let a=e(`#extensionDropzone`,n);a.ondragover=e=>{e.preventDefault(),a.classList.add(`dragging`)},a.ondragleave=()=>a.classList.remove(`dragging`),a.ondrop=e=>{e.preventDefault(),a.classList.remove(`dragging`),e.dataTransfer?.files?.length&&p(t,e.dataTransfer.files,n)}}})}export{m as showExtensionImport};
@@ -0,0 +1,69 @@
1
+ import{S as e,c as t,g as n,w as r,x as i}from"./index-BaUGyQ3C.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(e,r){try{let a=await t(`/api/import`,{method:`POST`,body:JSON.stringify(e)});return await loadAgents(),renderSettings(n.currentTab||`agents`,!0),renderTopbar(),i(`已导入 ${a.agents} 个运行配置(来源:${r})`),a}catch(e){throw i(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 t=e(`#settingsBody`),n=t.querySelector(`#importFile`),r=t.querySelector(`#importFileInput`);n&&r&&(n.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){i(`解析失败:`+e.message,`error`)}r.value=``}})}function l(e,t){let n=new Blob([JSON.stringify(e,null,2)],{type:`application/json`}),r=document.createElement(`a`);r.href=URL.createObjectURL(n),r.download=(e.id||t)+`.json`,document.body.appendChild(r),r.click(),r.remove(),setTimeout(()=>URL.revokeObjectURL(r.href),1e3),i(`已导出:`+(e.name||e.id))}function u(a){let o=!!a,s=o?n.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>系统提示词 <button type="button" class="btn-ghost tpl-save" id="savePromptTpl" style="float:right;font-size:11px;">存为模板</button></label>
10
+ <select id="promptTplPick" class="form-control" style="margin-bottom:6px;"><option value="">从模板填充…</option></select>
11
+ <textarea name="systemPrompt" rows="4" placeholder="你是一个...">${s?r(s.systemPrompt||``):``}</textarea></div>
12
+ <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>
13
+ <div class="field"><label>Agent Skills(先提供描述,匹配任务时再加载完整工作流)</label>
14
+ <div class="extension-checks">
15
+ ${n.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>
16
+ <input type="checkbox" name="skill" value="${r(t)}" ${i} /> ${r(e.name)} <span class="pmeta">${r(sourceLabel(e.source))}${e.enabled?``:` · 已停用`}</span>
17
+ </label>`}).join(``)||`<span class="pmeta">没有已启用的 Skill</span>`}
18
+ </div>
19
+ </div>
20
+ <div class="field"><label>内置工具(函数调用)</label>
21
+ <div class="extension-checks">
22
+ ${(n.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>`}
23
+ </div>
24
+ </div>
25
+ <div class="field"><label>MCP servers(工具由 tools/list 实时发现)</label>
26
+ <div class="extension-checks">
27
+ ${(n.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>`}
28
+ </div>
29
+ </div>
30
+ <div id="agentErr" class="auth-error"></div>
31
+ <div class="row">
32
+ <button type="button" class="btn-ghost" id="agentCancel">取消</button>
33
+ <button type="submit" class="btn-primary btn-auto">${o?`保存`:`创建`}</button>
34
+ </div>
35
+ </form>`,onMount:r=>{e(`#agentCancel`,r).onclick=closeModal;let c=e(`#promptTplPick`,r),l=r.querySelector(`[name="systemPrompt"]`);(async()=>{try{let e=await t(`/api/prompt-templates`);for(let t of e){let e=document.createElement(`option`);e.value=t.id,e.textContent=t.name,c.appendChild(e)}}catch{}})(),c.onchange=async()=>{if(!c.value)return;let e=(await t(`/api/prompt-templates`)).find(e=>e.id===c.value);e&&(l.value=e.content)},e(`#savePromptTpl`,r).onclick=async()=>{let e=l.value.trim();if(!e){i(`提示词为空,无法存为模板`,`error`);return}let n=await showPrompt({title:`保存为提示词模板`,label:`模板名称`,placeholder:`例如:代码审查员`,maxLength:80});if(n)try{await t(`/api/prompt-templates`,{method:`POST`,body:JSON.stringify({name:n,content:e})});let r=document.createElement(`option`);r.value=(await t(`/api/prompt-templates`)).find(e=>e.name===n)?.id||``,r.textContent=n,r.selected=!0,c.appendChild(r),i(`已保存模板:`+n)}catch(e){i(e.message,`error`)}},e(`#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(n.skills.flatMap(e=>[e.key||e.id,e.id])),f=new Set((n.tools||[]).map(e=>e.id)),p=new Set((n.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 t(`/api/agents/`+a,{method:`PUT`,body:JSON.stringify(u)}),i(`已保存`)):(await t(`/api/agents`,{method:`POST`,body:JSON.stringify(u)}),i(`已创建`)),await loadAgents(),renderSettings(n.currentTab||`agents`,!0),renderTopbar(),closeModal()}catch(t){e(`#agentErr`,r).textContent=t.message}}}})}function d(a){let o=!!a,s=o?n.skills.find(e=>(e.key||e.id)===a):null;showModal({title:o?`编辑 Agent Skill`:`新建 Agent Skill`,body:`<form id="skillForm">
36
+ <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`:``} />
37
+ <div class="pmeta">1–64 位小写字母、数字或连字符;写入 SKILL.md frontmatter。</div></div>
38
+ <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>
39
+ <div class="field"><label>Description</label><textarea name="description" rows="3" placeholder="说明何时应使用此 Skill,以及它能完成什么。" required>${s?r(s.description||``):``}</textarea></div>
40
+ <div class="field"><label>Instructions(SKILL.md 正文)</label><textarea name="instructions" rows="9" placeholder="# Workflow&#10;&#10;1. ..." required>${s?r(s.instructions||``):``}</textarea>
41
+ <div class="pmeta">需要脚本、参考资料或模板时,可在对应 Skill 目录添加 scripts、references、assets。</div></div>
42
+ <div id="skillErr" class="auth-error"></div>
43
+ <div class="row">
44
+ <button type="button" class="btn-ghost" id="skillCancel">取消</button>
45
+ <button type="submit" class="btn-primary btn-auto">${o?`保存`:`创建`}</button>
46
+ </div>
47
+ </form>`,onMount:r=>{e(`#skillCancel`,r).onclick=closeModal,e(`#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 e={...l};delete e.id,await t(`/api/skills/`+encodeURIComponent(a),{method:`PUT`,body:JSON.stringify(e)}),i(`已保存`)}else await t(`/api/skills`,{method:`POST`,body:JSON.stringify(l)}),i(`已创建`);await loadSkills(),renderSettings(n.currentTab||`skills`,!0),closeModal()}catch(t){e(`#skillErr`,r).textContent=t.message}}}})}var f=[{id:`mock`,name:`本地体验(无需密钥)`,apiType:`openai`,baseUrl:`http://127.0.0.1:3099`,models:[`echo`],allowPrivate:!0},{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 class="provider-template-grid">
48
+ ${f.map(e=>`<button class="add-tile" data-id="${e.id}"><div class="tpl-name">${e.name}</div><div class="tpl-meta">${e.apiType} · ${e.baseUrl}</div></button>`).join(``)}
49
+ </div>`,onMount:e=>{e.querySelectorAll(`[data-id]`).forEach(e=>e.onclick=async()=>{let n=f.find(t=>t.id===e.dataset.id);try{await t(`/api/providers`,{method:`POST`,body:JSON.stringify(n)}),await loadProviders(),renderSettings(`providers`,!0),closeModal(),i(`已添加:`+n.name)}catch(e){i(e.message,`error`)}})}})}function m(){showModal({title:`自定义提供方`,body:`<form id="customForm">
50
+ <div class="field"><label>Provider ID(留空自动生成)</label><input name="id" placeholder="留空自动生成,例如 acme-gateway" />
51
+ <div class="field-hint">小写字母、数字、下划线或连字符,唯一标识该提供方;不确定就留空。</div></div>
52
+ <div class="field"><label>模型列表</label><textarea name="models" rows="2" placeholder="deepseek-chat, deepseek-reasoner"></textarea>
53
+ <div class="field-hint">逗号或换行分隔;如不确定可留空,稍后在模型卡片中补充。</div></div>
54
+ <div class="field"><label>显示名称</label><input name="name" placeholder="显示名称" /></div>
55
+ <div class="field"><label>API 地址</label><input name="baseUrl" placeholder="https://gateway.example/v1" required /></div>
56
+ <div class="field"><label>API 协议</label><select name="apiType">
57
+ <option value="openai">openai-completions</option>
58
+ <option value="anthropic">anthropic-messages</option>
59
+ <option value="ollama">ollama</option>
60
+ <option value="lmstudio">lmstudio</option>
61
+ </select></div>
62
+ <div class="field"><label>API 密钥</label><input name="apiKey" type="password" placeholder="输入 API 密钥" /></div>
63
+ <label class="provider-private"><input name="allowPrivate" type="checkbox" /> 允许该提供方访问本机或内网地址(仅本地网关需要)</label>
64
+ <div id="customErr" class="auth-error"></div>
65
+ <div class="row">
66
+ <button type="button" class="btn-ghost" id="customCancel">取消</button>
67
+ <button type="submit" class="btn-primary btn-auto">保存</button>
68
+ </div>
69
+ </form>`,onMount:n=>{e(`#customCancel`,n).onclick=closeModal,e(`#customForm`,n).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 t(`/api/providers`,{method:`POST`,body:JSON.stringify(o)}),await loadProviders(),renderSettings(`providers`,!0),closeModal(),i(`已添加`)}catch(t){e(`#customErr`,n).textContent=t.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};