synabun 2.0.0 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +46 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-compact.mjs +11 -0
- package/hooks/claude-code/prompt-submit.mjs +60 -184
- package/hooks/claude-code/session-start.mjs +73 -16
- package/hooks/claude-code/shared.mjs +20 -0
- package/hooks/claude-code/stop.mjs +5 -5
- package/lib/start-launcher.js +88 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/neural-interface/i18n/en.json +1500 -153
- package/neural-interface/i18n/pt-BR.json +1498 -152
- package/neural-interface/lib/assistant-api.js +62 -6
- package/neural-interface/lib/assistant-brains/claude.js +4 -0
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +31 -2
- package/neural-interface/lib/assistant-dispatch.js +62 -13
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +6 -2
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-runtime.js +181 -12
- package/neural-interface/lib/assistant-task-prompt.js +13 -3
- package/neural-interface/lib/assistant-test-density.js +142 -0
- package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
- package/neural-interface/lib/assistant-user-context.js +908 -0
- package/neural-interface/lib/assistant-user-knowledge.js +328 -0
- package/neural-interface/lib/assistant-user-learn.js +404 -0
- package/neural-interface/lib/assistant-user-threads.js +226 -0
- package/neural-interface/lib/assistant-user-writer.js +181 -0
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-agent-bridge.js +33 -6
- package/neural-interface/lib/claude-model-catalog.js +30 -2
- package/neural-interface/lib/claude-temporary.js +46 -7
- package/neural-interface/lib/cloudflared/install.js +533 -0
- package/neural-interface/lib/cloudflared/manager.js +545 -0
- package/neural-interface/lib/cloudflared/pins.js +115 -0
- package/neural-interface/lib/cloudflared/pins.json +63 -0
- package/neural-interface/lib/cloudflared/tunnel.js +265 -0
- package/neural-interface/lib/editor-completion-endpoint.js +264 -0
- package/neural-interface/lib/editor-completion-opencode.js +436 -0
- package/neural-interface/lib/editor-completion.js +714 -0
- package/neural-interface/lib/editor-config.js +149 -0
- package/neural-interface/lib/editor-files-api.js +268 -0
- package/neural-interface/lib/editor-git-api.js +134 -0
- package/neural-interface/lib/file-explorer-api.js +1039 -0
- package/neural-interface/lib/file-explorer-system.js +355 -0
- package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
- package/neural-interface/lib/file-explorer-watch.js +239 -0
- package/neural-interface/lib/git-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +202 -0
- package/neural-interface/lib/greeting/api.js +89 -0
- package/neural-interface/lib/greeting/budgets.js +90 -0
- package/neural-interface/lib/greeting/engine.js +461 -0
- package/neural-interface/lib/greeting/facts.js +320 -0
- package/neural-interface/lib/greeting/sources.js +252 -0
- package/neural-interface/lib/greeting/writer.js +139 -0
- package/neural-interface/lib/http-guards.js +189 -13
- package/neural-interface/lib/machine-names.js +88 -0
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-loop-providers.js +10 -5
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/package-lock.json +701 -2
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/install-app.html +9 -6
- package/neural-interface/public/offline.html +3 -2
- package/neural-interface/public/onboarding.html +12 -1
- package/neural-interface/public/shared/agent-runtime-options.js +22 -6
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +18 -58
- package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
- package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
- package/neural-interface/public/shared/assistant/asst-render.js +162 -19
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +23 -329
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
- package/neural-interface/public/shared/assistant/asst-state.js +185 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +158 -4
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +15 -0
- package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
- package/neural-interface/public/shared/editor/editor-ai.js +619 -0
- package/neural-interface/public/shared/editor/editor-commands.js +212 -0
- package/neural-interface/public/shared/editor/editor-compare.js +103 -0
- package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
- package/neural-interface/public/shared/editor/editor-doc.js +257 -0
- package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
- package/neural-interface/public/shared/editor/editor-git.js +95 -0
- package/neural-interface/public/shared/editor/editor-languages.js +156 -0
- package/neural-interface/public/shared/editor/editor-lint.js +229 -0
- package/neural-interface/public/shared/editor/editor-loader.js +51 -0
- package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
- package/neural-interface/public/shared/editor/editor-menu.js +233 -0
- package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
- package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
- package/neural-interface/public/shared/editor/editor-palette.js +125 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
- package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
- package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
- package/neural-interface/public/shared/editor/editor-safety.js +500 -0
- package/neural-interface/public/shared/editor/editor-search.js +352 -0
- package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
- package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
- package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
- package/neural-interface/public/shared/editor/editor-theme.js +244 -0
- package/neural-interface/public/shared/editor/editor-view.js +332 -0
- package/neural-interface/public/shared/explorer/fx-api.js +216 -0
- package/neural-interface/public/shared/explorer/fx-links.js +204 -0
- package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
- package/neural-interface/public/shared/explorer/fx-main.js +865 -0
- package/neural-interface/public/shared/explorer/fx-names.js +87 -0
- package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
- package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
- package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
- package/neural-interface/public/shared/file-links.js +339 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
- package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
- package/neural-interface/public/shared/html-shell.js +160 -165
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +29 -1
- package/neural-interface/public/shared/pwa-install.css +3 -3
- package/neural-interface/public/shared/pwa-install.js +100 -1
- package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
- package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
- package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
- package/neural-interface/public/shared/settings/settings-ia.js +18 -4
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/start-bridge.js +238 -19
- package/neural-interface/public/shared/styles.css +1414 -721
- package/neural-interface/public/shared/synabun-mascot.js +20 -56
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +157 -16
- package/neural-interface/public/shared/ui-detail.js +15 -0
- package/neural-interface/public/shared/ui-explorer.js +22 -0
- package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
- package/neural-interface/public/shared/ui-file-links.js +103 -0
- package/neural-interface/public/shared/ui-help.js +26 -0
- package/neural-interface/public/shared/ui-invite.js +126 -116
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +159 -15
- package/neural-interface/public/shared/ui-menubar.js +12 -30
- package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
- package/neural-interface/public/shared/ui-navbar.js +45 -21
- package/neural-interface/public/shared/ui-settings.js +455 -488
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sync.js +16 -0
- package/neural-interface/public/shared/ui-terminal.js +23 -5
- package/neural-interface/public/shared/ui-tunnel.js +130 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
- package/neural-interface/public/shared/ui-whiteboard.js +12 -0
- package/neural-interface/public/shared/ui-worker-end.js +245 -0
- package/neural-interface/public/shared/ui-workspaces.js +41 -27
- package/neural-interface/public/shared/worker-end.js +251 -0
- package/neural-interface/public/sw.js +2 -2
- package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
- package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
- package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
- package/neural-interface/scripts/build-vendor.mjs +78 -2
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +873 -1028
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +2 -8
- package/setup.js +44 -7
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/neural-interface/lib/codex-capabilities.d.ts +0 -41
- package/neural-interface/public/category-logos/claude.svg +0 -1
- package/neural-interface/public/category-logos/gemini-color.svg +0 -1
- package/neural-interface/public/category-logos/openai.svg +0 -1
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude.svg +0 -1
- package/neural-interface/public/gallery.svg +0 -6
- package/neural-interface/public/leonardoai.svg +0 -1
- package/neural-interface/public/mcp.svg +0 -4
- package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
- package/neural-interface/public/pin-svgrepo-com.svg +0 -25
- package/neural-interface/public/sessionicon.svg +0 -8
- package/neural-interface/public/toolbar-album.svg +0 -8
- package/neural-interface/public/toolbar-question.svg +0 -6
- package/neural-interface/public/toolbar-sessions.svg +0 -6
- package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -28,21 +28,29 @@ import { getHiddenModels, HIDDEN_MODELS_EVENT, hiddenModelMatch } from '../ocp-h
|
|
|
28
28
|
import { getProviderMeta } from '../provider-icons.js';
|
|
29
29
|
import { on } from '../state.js';
|
|
30
30
|
import { fetchAssistantCatalog, fetchProjects } from './asst-api.js';
|
|
31
|
-
import { closeMenu, isMenuOpen, openMenu } from './asst-menu.js';
|
|
31
|
+
import { closeMenu, currentMenu, isMenuOpen, openMenu } from './asst-menu.js';
|
|
32
32
|
import {
|
|
33
33
|
ACCOUNT_PROVIDERS,
|
|
34
34
|
brainRequiresSwitch,
|
|
35
|
+
emptyProjectView,
|
|
35
36
|
modelShortName,
|
|
36
37
|
normalizeBrain,
|
|
37
38
|
PERMISSION_MODES,
|
|
38
39
|
PLAN_MODE_PROVIDERS,
|
|
39
40
|
permissionModeLabel,
|
|
40
41
|
PROVIDERS,
|
|
42
|
+
projectLabel,
|
|
41
43
|
resolveBrainDefaults,
|
|
44
|
+
TEST_DENSITIES,
|
|
45
|
+
normalizeTestDensity,
|
|
46
|
+
testDensityBars,
|
|
42
47
|
} from './asst-state.js';
|
|
43
48
|
|
|
44
49
|
let _projectsCache = null;
|
|
45
50
|
|
|
51
|
+
/** SynaBun's registered projects as the last load saw them ([{ path, label }]; [] before the first). */
|
|
52
|
+
export function cachedProjects() { return _projectsCache || []; }
|
|
53
|
+
|
|
46
54
|
const ICON_FOLDER = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>';
|
|
47
55
|
const ICON_SHIELD = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 5 6v5c0 4.5 3 8 7 10 4-2 7-5.5 7-10V6z"/></svg>';
|
|
48
56
|
const ICON_PLUG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3v5M15 3v5M6 8h12v3a6 6 0 0 1-12 0zM12 17v4"/></svg>';
|
|
@@ -53,6 +61,25 @@ export const ICON_ACCOUNT = '<svg viewBox="0 0 24 24" fill="none" stroke="curren
|
|
|
53
61
|
/** Dropdown caret shared by every toolbar dropdown (fields, route mode). */
|
|
54
62
|
export const ICON_CARET = '<svg viewBox="0 0 10 10" aria-hidden="true"><path d="M2.5 3.75 5 6.25l2.5-2.5"/></svg>';
|
|
55
63
|
|
|
64
|
+
// Test density: three bars centred in the 24 box (x 6–18, y 6–18; 5–19 with the stroke), `filled` of them at full strength.
|
|
65
|
+
function testsIcon(filled) {
|
|
66
|
+
const bar = (index, d) => `<path d="${d}"${index < filled ? '' : ' opacity=".3"'}/>`;
|
|
67
|
+
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${bar(0, 'M6 18v-4')}${bar(1, 'M12 18v-8')}${bar(2, 'M18 18V6')}</svg>`;
|
|
68
|
+
}
|
|
69
|
+
// English copy of the Test density menu (i18n: assistant.tests.levels.* / assistant.tests.desc.*).
|
|
70
|
+
const TEST_DENSITY_COPY = {
|
|
71
|
+
none: ['None', 'No tests. Only checks that the change loads.'],
|
|
72
|
+
light: ['Light', 'Runs the tests that cover the change. New tests only when they matter.'],
|
|
73
|
+
standard: ['Standard', 'Adds focused tests and runs the area\'s suite once.'],
|
|
74
|
+
thorough: ['Thorough', 'Full coverage, whole suite, live checks.'],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** "Light" — the level's name (a missing or unknown level is the default one). */
|
|
78
|
+
export function testDensityLabel(level, t) {
|
|
79
|
+
const id = normalizeTestDensity(level);
|
|
80
|
+
return tf(t)(`assistant.tests.levels.${id}`, TEST_DENSITY_COPY[id][0]);
|
|
81
|
+
}
|
|
82
|
+
|
|
56
83
|
function el(tag, className, text) {
|
|
57
84
|
const node = document.createElement(tag);
|
|
58
85
|
if (className) node.className = className;
|
|
@@ -92,6 +119,9 @@ let _catalog = null;
|
|
|
92
119
|
let _catalogAt = 0;
|
|
93
120
|
let _catalogPartial = false; // built without the server's catalog (no context variants)
|
|
94
121
|
let _catalogLoading = null;
|
|
122
|
+
let _catalogEpoch = 0; // bumped by every invalidation: a load that began before it is not kept
|
|
123
|
+
let _catalogForceNext = false; // the lists changed: the next load asks the server for a new build
|
|
124
|
+
const _catalogListeners = new Set();
|
|
95
125
|
const CATALOG_TTL_MS = 60_000;
|
|
96
126
|
|
|
97
127
|
function trimNumber(n) {
|
|
@@ -150,11 +180,16 @@ let _hidden = { 'claude-code': new Set(), codex: new Set(), opencode: new Set()
|
|
|
150
180
|
|
|
151
181
|
function hiddenSets(remote) {
|
|
152
182
|
const out = {};
|
|
183
|
+
// Entries with a pin record (an alias hidden as the model it named then): each hides its own row only.
|
|
184
|
+
const rowOnly = {};
|
|
153
185
|
for (const provider of PROVIDERS) {
|
|
154
186
|
const list = Array.isArray(remote?.hidden?.[provider]) ? remote.hidden[provider] : [];
|
|
155
187
|
out[provider] = new Set(list.map(id => String(id).toLowerCase()));
|
|
188
|
+
const pins = remote?.hiddenPins?.[provider];
|
|
189
|
+
rowOnly[provider] = new Set(pins && typeof pins === 'object' ? Object.keys(pins).map(id => id.toLowerCase()) : []);
|
|
156
190
|
}
|
|
157
191
|
out.opencode = new Set([...(out.opencode || []), ...hiddenOpencodeIds(remote)]);
|
|
192
|
+
out.rowOnly = rowOnly;
|
|
158
193
|
return out;
|
|
159
194
|
}
|
|
160
195
|
|
|
@@ -163,6 +198,9 @@ function hiddenSets(remote) {
|
|
|
163
198
|
* id, or the model it resolves to at the same context window ("opus[1m]" →
|
|
164
199
|
* "claude-opus-5-5[1m]"); "opus" and "opus[1m]" hide independently. OpenCode:
|
|
165
200
|
* full or bare id (this window's Settings → OpenCode mirror counts too).
|
|
201
|
+
* A hidden alias hides the model it named when it was hidden, and the server
|
|
202
|
+
* keeps the list true to that (an alias that moved to another model is taken
|
|
203
|
+
* off it); such an entry hides its own row only, never through `resolved`.
|
|
166
204
|
*/
|
|
167
205
|
export function hiddenModelFor(provider, id, { resolved = null, sets = _hidden } = {}) {
|
|
168
206
|
const want = String(id || '').trim().toLowerCase();
|
|
@@ -173,22 +211,87 @@ export function hiddenModelFor(provider, id, { resolved = null, sets = _hidden }
|
|
|
173
211
|
const suffix = (want.match(/\[(1m|extended)\]$/) || [''])[0];
|
|
174
212
|
const up = String(resolved || '').trim().toLowerCase().replace(/\[(1m|extended)\]$/, '');
|
|
175
213
|
const key = up ? `${up}${suffix}` : '';
|
|
176
|
-
return key && key !== want && list.has(key) ? key : null;
|
|
214
|
+
return key && key !== want && list.has(key) && !sets.rowOnly?.[provider]?.has(key) ? key : null;
|
|
177
215
|
}
|
|
178
216
|
|
|
179
217
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
218
|
+
* One provider's menu rows. Which models exist is the server catalog's word: it
|
|
219
|
+
* is built from the list the provider's own selector reads, with the models the
|
|
220
|
+
* user hid already left out. The window's runtime list (`local`) only adds what
|
|
221
|
+
* the catalog does not carry (the tagline, the default / top tag, a legacy
|
|
222
|
+
* selector). It stands in for the catalog only when the catalog has no row for
|
|
223
|
+
* the provider (request failed, older server, provider not answering there), or
|
|
224
|
+
* holds the provider's static stand-in while this window has a verified list.
|
|
225
|
+
*/
|
|
226
|
+
export function mergeProviderModels(provider, remoteList, localList, { sets = _hidden, remoteSource = null } = {}) {
|
|
227
|
+
const remote = (Array.isArray(remoteList) ? remoteList : []).filter(m => m?.id);
|
|
228
|
+
const local = (Array.isArray(localList) ? localList : []).filter(m => m?.id || m?.model);
|
|
229
|
+
const localVerified = local.length > 0 && !local.some(m => m.catalogSource === 'fallback');
|
|
230
|
+
const out = [];
|
|
231
|
+
const seen = new Set();
|
|
232
|
+
const push = (m) => { if (m && !seen.has(m.id)) { seen.add(m.id); out.push(m); } };
|
|
233
|
+
if (remote.length && !(remoteSource === 'fallback' && localVerified)) {
|
|
234
|
+
const own = new Map(local.map(m => [String(m.id || m.model), m]));
|
|
235
|
+
for (const r of remote) {
|
|
236
|
+
if (hiddenModelFor(provider, r.id, { resolved: r.upstream || null, sets })) continue;
|
|
237
|
+
const mine = own.get(String(r.id)) || null;
|
|
238
|
+
// The catalog row first. What it does not carry (an older server, a row with no
|
|
239
|
+
// label or effort list) is read from the window's row for the same id.
|
|
240
|
+
const row = normalizeCatalogModel(
|
|
241
|
+
{ ...r, desc: mine?.desc || r.desc || r.description || '', tier: mine?.tier || r.tier || null, upstream: r.upstream || mine?.resolvedModel || null },
|
|
242
|
+
mine ? {
|
|
243
|
+
label: mine.label || mine.displayName || '', efforts: modelEffortIds(mine) || undefined, contextWindow: mine.contextWindow || null,
|
|
244
|
+
defaultEffort: mine.defaultEffort || mine.defaultReasoningEffort || null, isDefault: mine.isDefault,
|
|
245
|
+
} : {},
|
|
246
|
+
);
|
|
247
|
+
if (row) row.selector = mine ? (modelSelectorValue(mine) || row.id) : row.id;
|
|
248
|
+
push(row);
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
const byId = new Map(remote.map(m => [String(m.id), m]));
|
|
253
|
+
for (const m of local) {
|
|
254
|
+
const extra = byId.get(String(m?.id)) || {};
|
|
255
|
+
if (hiddenModelFor(provider, m.id || m.model, { resolved: extra.upstream || m.resolvedModel || null, sets })) continue;
|
|
256
|
+
push(normalizeCatalogModel(m, extra));
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The catalog's empty list for `provider` is its answer, not a gap: the list was
|
|
263
|
+
* read (`sources`: live, or the last verified one) and the provider said it has
|
|
264
|
+
* no model (`known`: OpenCode with nothing connected) or listed some and the
|
|
265
|
+
* user hid every one (`listed`). Not an answer: no list or no `sources` (request
|
|
266
|
+
* failed, an older server), a provider that did not answer ('unavailable'), and
|
|
267
|
+
* the server's static stand-in while this window holds a verified list (the
|
|
268
|
+
* same precedence as mergeProviderModels).
|
|
269
|
+
*/
|
|
270
|
+
function catalogSaysNone(remote, provider, local) {
|
|
271
|
+
const rows = remote?.models?.[provider];
|
|
272
|
+
if (!Array.isArray(rows) || rows.length) return false;
|
|
273
|
+
const source = remote.sources?.[provider];
|
|
274
|
+
if (source !== 'live' && source !== 'stale' && source !== 'fallback') return false;
|
|
275
|
+
if (!(Number(remote.listed?.[provider]) > 0) && remote.known?.[provider] !== true) return false;
|
|
276
|
+
return !(source === 'fallback' && local.length > 0 && !local.some(m => m.catalogSource === 'fallback'));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* { 'claude-code': [...], codex: [...], opencode: [...] }: the models of every
|
|
281
|
+
* Assistant model menu and of the Routes editor, from GET /api/assistant/catalog
|
|
282
|
+
* (see mergeProviderModels). Models the user hid (Assistant → Models, and
|
|
283
|
+
* Settings → OpenCode for OpenCode) never appear. `force` asks the server for a
|
|
284
|
+
* new build instead of its cached one.
|
|
185
285
|
*/
|
|
186
286
|
export async function loadModelCatalog({ force = false } = {}) {
|
|
187
287
|
if (!force && _catalog && Date.now() - _catalogAt < CATALOG_TTL_MS) return _catalog;
|
|
188
288
|
if (_catalogLoading) return _catalogLoading;
|
|
289
|
+
const epoch = _catalogEpoch;
|
|
290
|
+
const serverForce = force || _catalogForceNext;
|
|
291
|
+
_catalogForceNext = false;
|
|
189
292
|
_catalogLoading = (async () => {
|
|
190
293
|
const [remote] = await Promise.all([
|
|
191
|
-
fetchAssistantCatalog().catch(() => null),
|
|
294
|
+
fetchAssistantCatalog({ force: serverForce }).catch(() => null),
|
|
192
295
|
...PROVIDERS.map(p => Promise.resolve(ensureModelsForProfile(p, force)).catch(() => [])),
|
|
193
296
|
]);
|
|
194
297
|
const out = {};
|
|
@@ -196,36 +299,16 @@ export async function loadModelCatalog({ force = false } = {}) {
|
|
|
196
299
|
const sets = remote ? hiddenSets(remote) : { ..._hidden, opencode: new Set([...(_hidden.opencode || []), ...hiddenOpencodeIds(null)]) };
|
|
197
300
|
_hidden = sets;
|
|
198
301
|
for (const provider of PROVIDERS) {
|
|
199
|
-
const remoteList = Array.isArray(remote?.models?.[provider]) ? remote.models[provider] : [];
|
|
200
|
-
const byId = new Map(remoteList.filter(m => m?.id).map(m => [String(m.id), m]));
|
|
201
302
|
const local = getModelsForProfile(provider) || [];
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const extra = byId.get(String(m.id)) || {};
|
|
207
|
-
if (hiddenModelFor(provider, m.id, { resolved: extra.upstream || m.resolvedModel || null, sets })) return false;
|
|
208
|
-
seen.add(m.id);
|
|
209
|
-
return true;
|
|
210
|
-
});
|
|
211
|
-
// Context variants ("opus[1m]", "gpt-x[extended]") the local list lacks: the
|
|
212
|
-
// catalog lists every one, each right after its base model.
|
|
213
|
-
if (provider !== 'opencode' && source !== remoteList) {
|
|
214
|
-
for (const r of remoteList) {
|
|
215
|
-
if (!/\[(1m|extended)\]$/i.test(String(r?.id || '')) || seen.has(String(r.id))) continue;
|
|
216
|
-
if (hiddenModelFor(provider, r.id, { resolved: r.upstream || null, sets })) continue;
|
|
217
|
-
const m = normalizeCatalogModel(r, r);
|
|
218
|
-
if (!m) continue;
|
|
219
|
-
seen.add(m.id);
|
|
220
|
-
const base = String(r.id).replace(/\[(1m|extended)\]$/i, '');
|
|
221
|
-
const at = out[provider].findIndex(x => x.id === base);
|
|
222
|
-
if (at >= 0) out[provider].splice(at + 1, 0, m); else out[provider].push(m);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
303
|
+
// An empty list the catalog vouches for is its answer: the window's own rows do not stand in for it.
|
|
304
|
+
out[provider] = catalogSaysNone(remote, provider, local)
|
|
305
|
+
? []
|
|
306
|
+
: mergeProviderModels(provider, remote?.models?.[provider], local, { sets, remoteSource: remote?.sources?.[provider] || null });
|
|
225
307
|
}
|
|
226
308
|
_catalog = out;
|
|
227
|
-
//
|
|
228
|
-
|
|
309
|
+
// Not kept when built without the server's catalog, or when the lists changed
|
|
310
|
+
// while it loaded: the next open asks again.
|
|
311
|
+
_catalogAt = remote && epoch === _catalogEpoch ? Date.now() : 0;
|
|
229
312
|
_catalogPartial = !remote;
|
|
230
313
|
return out;
|
|
231
314
|
})().finally(() => { _catalogLoading = null; });
|
|
@@ -235,6 +318,11 @@ export async function loadModelCatalog({ force = false } = {}) {
|
|
|
235
318
|
/** The last loadModelCatalog() result, synchronously (null before the first load). */
|
|
236
319
|
export function cachedModelCatalog() { return _catalog; }
|
|
237
320
|
|
|
321
|
+
/** UI metadata from the same catalog load; no second list or extra endpoint. */
|
|
322
|
+
export function modelCatalogStatus() {
|
|
323
|
+
return { partial: _catalogPartial, hiddenCounts: Object.fromEntries(PROVIDERS.map(provider => [provider, _hidden[provider]?.size || 0])) };
|
|
324
|
+
}
|
|
325
|
+
|
|
238
326
|
/** The catalog row for provider/model (covers "[1m]" / "[extended]" ids), else null. */
|
|
239
327
|
export function catalogModelRow(provider, model, catalog = _catalog) {
|
|
240
328
|
if (!provider || !model) return null;
|
|
@@ -246,6 +334,27 @@ export function invalidateModelCatalog() {
|
|
|
246
334
|
_catalog = null;
|
|
247
335
|
_catalogAt = 0;
|
|
248
336
|
_catalogPartial = false;
|
|
337
|
+
_catalogEpoch += 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Tells `listener` whenever the lists behind loadModelCatalog() changed: a model
|
|
342
|
+
* hidden or shown, a provider listing other models (the server's
|
|
343
|
+
* `assistant:catalog-changed`), an OpenCode provider connected or removed. The
|
|
344
|
+
* cache is already dropped when it is called. Returns the function that stops it.
|
|
345
|
+
*/
|
|
346
|
+
export function onModelCatalogChange(listener) {
|
|
347
|
+
if (typeof listener !== 'function') return () => {};
|
|
348
|
+
_catalogListeners.add(listener);
|
|
349
|
+
return () => { _catalogListeners.delete(listener); };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function modelListsChanged() {
|
|
353
|
+
invalidateModelCatalog();
|
|
354
|
+
_catalogForceNext = true;
|
|
355
|
+
for (const listener of [..._catalogListeners]) {
|
|
356
|
+
try { listener(); } catch { /* a listener's failure is its own */ }
|
|
357
|
+
}
|
|
249
358
|
}
|
|
250
359
|
|
|
251
360
|
/**
|
|
@@ -267,19 +376,25 @@ export function resolvableModels(provider, keep = '', catalog = _catalog) {
|
|
|
267
376
|
return hiddenModelFor(provider, keep) || unsure ? [...rows, { id: keep, selector: keep }] : rows;
|
|
268
377
|
}
|
|
269
378
|
|
|
270
|
-
// A Settings → OpenCode checkbox or an Assistant → Models toggle (this window, or another via
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
379
|
+
// A Settings → OpenCode checkbox or an Assistant → Models toggle (this window, or another via
|
|
380
|
+
// sync), an OpenCode provider connected or removed in Settings, a provider listing other models.
|
|
381
|
+
if (typeof document !== 'undefined') {
|
|
382
|
+
document.addEventListener(HIDDEN_MODELS_EVENT, modelListsChanged);
|
|
383
|
+
document.addEventListener('ocp-providers-changed', modelListsChanged);
|
|
384
|
+
}
|
|
385
|
+
on('sync:opencode:hidden-models-changed', modelListsChanged);
|
|
386
|
+
on('sync:assistant:hidden-models-changed', modelListsChanged);
|
|
387
|
+
on('sync:assistant:catalog-changed', modelListsChanged);
|
|
274
388
|
|
|
275
389
|
function sameModel(selected, m) {
|
|
276
390
|
if (!selected) return false;
|
|
277
391
|
return selected === m.id || selected === m.selector;
|
|
278
392
|
}
|
|
279
393
|
|
|
280
|
-
function modelDesc(m, tr) {
|
|
394
|
+
function modelDesc(m, tr, showUnknown = false) {
|
|
281
395
|
return [
|
|
282
|
-
m.ctxLabel
|
|
396
|
+
m.ctxLabel || (showUnknown ? m.contextWindow ? tr('assistant.routes.menu.contextTokens', '{count} tokens', { count: m.contextWindow.toLocaleString() }) : tr('assistant.routes.menu.contextUnknown', 'Context unknown') : ''), m.desc,
|
|
397
|
+
fmtModelPrice(m.price) || (showUnknown ? tr('assistant.routes.menu.priceUnknown', 'Price unknown') : ''), m.vision === true ? tr('assistant.brain.vision', 'vision') : '',
|
|
283
398
|
m.outputs?.image === true ? tr('assistant.brain.imageOut', 'makes images') : '',
|
|
284
399
|
m.outputs?.video === true ? tr('assistant.brain.videoOut', 'makes video') : '',
|
|
285
400
|
].filter(Boolean).join(' · ');
|
|
@@ -309,9 +424,19 @@ export async function openModelMenu(anchor, opts = {}) {
|
|
|
309
424
|
const tr = tf(t);
|
|
310
425
|
const b = normalizeBrain(brain);
|
|
311
426
|
anchor.setAttribute('aria-busy', 'true');
|
|
427
|
+
// Open immediately: a slow provider still has a visible, cancellable state.
|
|
428
|
+
const loading = opts.routingEditor ? openMenu(anchor, {
|
|
429
|
+
title, className: 'asst-model-menu asst-route-model-menu',
|
|
430
|
+
items: [{ kind: 'info', label: tr('assistant.routes.editorState.loadingModels', 'Loading models…') }],
|
|
431
|
+
width: 380, zIndex: opts.zIndex ?? null, repositionOnResize: true,
|
|
432
|
+
}) : null;
|
|
433
|
+
if (loading) loading.el.setAttribute('aria-busy', 'true');
|
|
434
|
+
if (loading) { loading.el.setAttribute('role', 'dialog'); anchor.setAttribute('aria-haspopup', 'dialog'); }
|
|
312
435
|
let catalog = {};
|
|
313
|
-
|
|
314
|
-
|
|
436
|
+
let failed = false;
|
|
437
|
+
try { catalog = await loadModelCatalog({ force: opts.forceReload === true }); } catch { failed = true; } finally { anchor.removeAttribute('aria-busy'); }
|
|
438
|
+
if (!anchor.isConnected || (loading && currentMenu() !== loading.el)) return null;
|
|
439
|
+
const metadata = modelCatalogStatus();
|
|
315
440
|
const items = [];
|
|
316
441
|
if (includeHere) {
|
|
317
442
|
items.push({ kind: 'radio', id: '__here', label: hereLabel || tr('assistant.routes.brainDecides', 'Let the brain decide'), selected: !current, onSelect: () => onSelect?.({ here: true }) });
|
|
@@ -320,26 +445,30 @@ export async function openModelMenu(anchor, opts = {}) {
|
|
|
320
445
|
let listed = 0;
|
|
321
446
|
for (const provider of list) {
|
|
322
447
|
const models = (catalog[provider] || []).filter(m => typeof accept !== 'function' || accept(provider, m));
|
|
323
|
-
if (!models.length) continue;
|
|
448
|
+
if (!models.length && !opts.routingEditor) continue;
|
|
324
449
|
listed += models.length;
|
|
325
450
|
const meta = getProviderMeta(provider);
|
|
326
|
-
const
|
|
451
|
+
const hiddenCount = metadata.hiddenCounts[provider] || 0;
|
|
452
|
+
const note = opts.routingEditor ? tr('assistant.routes.menu.hiddenCount', '{count} hidden in Models', { count: hiddenCount }) : warnSwitch && provider !== b.provider ? tr('assistant.brain.newConversation', 'starts a new conversation') : '';
|
|
327
453
|
items.push({ kind: 'header', label: meta.label, icon: meta.icon, color: meta.color, note });
|
|
328
454
|
for (const m of models) {
|
|
329
455
|
items.push({
|
|
330
456
|
kind: 'radio',
|
|
331
457
|
id: `${provider}:${m.id}`,
|
|
332
458
|
label: m.label,
|
|
333
|
-
desc: modelDesc(m, tr),
|
|
459
|
+
desc: modelDesc(m, tr, opts.routingEditor),
|
|
460
|
+
title: `${m.label || m.id} · ${m.id}`,
|
|
334
461
|
tag: tierTag(m, tr),
|
|
335
462
|
keywords: `${meta.label} ${m.id}`,
|
|
336
463
|
selected: !!current && current.provider === provider && sameModel(current.model, m),
|
|
337
464
|
onSelect: () => onSelect?.({ provider, model: m.id, label: m.label, tier: m.tier || null, vision: m.vision ?? null, outputs: m.outputs || null, efforts: m.efforts || null, price: m.price || null }),
|
|
338
465
|
});
|
|
339
466
|
}
|
|
467
|
+
if (opts.routingEditor && !models.length) items.push({ kind: 'info', label: typeof accept === 'function' ? tr('assistant.routes.menu.noCompatible', 'No compatible models shown for this provider.') : tr('assistant.routes.menu.providerEmpty', 'No models shown for this provider.') });
|
|
340
468
|
}
|
|
341
469
|
// Nothing passes the filter (e.g. no model makes video yet): say so instead of an empty list.
|
|
342
470
|
if (!listed && typeof accept === 'function' && emptyLabel) items.push({ kind: 'info', label: emptyLabel });
|
|
471
|
+
if (opts.routingEditor && !listed && typeof accept !== 'function') items.push({ kind: 'info', label: tr('assistant.routes.editorState.modelsEmpty', 'No models are available. Check Models or connect a provider, then reload.') });
|
|
343
472
|
if (allowCustom) {
|
|
344
473
|
items.push({ kind: 'separator' });
|
|
345
474
|
items.push({
|
|
@@ -356,29 +485,63 @@ export async function openModelMenu(anchor, opts = {}) {
|
|
|
356
485
|
},
|
|
357
486
|
});
|
|
358
487
|
}
|
|
359
|
-
if (typeof opts.onManageModels === 'function') {
|
|
488
|
+
if (!opts.routingEditor && typeof opts.onManageModels === 'function') {
|
|
360
489
|
items.push({ id: '__manage_models', label: tr('assistant.models.manage', 'Manage models…'), onSelect: () => opts.onManageModels() });
|
|
361
490
|
}
|
|
362
|
-
|
|
491
|
+
const menu = openMenu(anchor, {
|
|
363
492
|
title: title || tr('assistant.brain.model', 'Model'),
|
|
364
493
|
items,
|
|
365
494
|
filter: true,
|
|
366
495
|
filterPlaceholder: tr('assistant.brain.filterModels', 'Filter models…'),
|
|
367
496
|
placement,
|
|
368
|
-
width: 300,
|
|
497
|
+
width: opts.routingEditor ? 380 : 300,
|
|
369
498
|
role: 'menu',
|
|
370
|
-
className: 'asst-model-menu'
|
|
499
|
+
className: `asst-model-menu${opts.routingEditor ? ' asst-route-model-menu' : ''}`,
|
|
371
500
|
zIndex: opts.zIndex ?? null,
|
|
501
|
+
repositionOnResize: opts.routingEditor === true,
|
|
502
|
+
filterEmptyLabel: tr('assistant.routes.menu.noMatches', 'No matching models. Check Models for hidden choices.'),
|
|
372
503
|
});
|
|
504
|
+
if (opts.routingEditor && menu) {
|
|
505
|
+
// Search and a custom-id input belong to a dialog; choices are a radio group.
|
|
506
|
+
// A menu role cannot contain these form fields or recovery buttons.
|
|
507
|
+
menu.el.setAttribute('role', 'dialog');
|
|
508
|
+
anchor.setAttribute('aria-haspopup', 'dialog');
|
|
509
|
+
const choices = menu.list.querySelectorAll('[role="menuitemradio"]');
|
|
510
|
+
for (const choice of choices) choice.setAttribute('role', 'radio');
|
|
511
|
+
const groupRole = () => {
|
|
512
|
+
const visible = [...choices].filter(n => !n.hidden);
|
|
513
|
+
const focusChoice = visible.find(n => n.getAttribute('aria-checked') === 'true') || visible[0];
|
|
514
|
+
for (const choice of choices) choice.tabIndex = choice === focusChoice ? 0 : -1;
|
|
515
|
+
menu.list.setAttribute('role', visible.length ? 'radiogroup' : 'group');
|
|
516
|
+
menu.list.setAttribute('aria-labelledby', `${menu.el.id}-title`);
|
|
517
|
+
};
|
|
518
|
+
groupRole(); menu.el.querySelector('.asst-dd-filter')?.addEventListener('input', groupRole);
|
|
519
|
+
// Outside the filter's scroller: recovery stays visible, including zero results.
|
|
520
|
+
const footer = document.createElement('div'); footer.className = 'asst-route-menu-foot';
|
|
521
|
+
const note = document.createElement('div'); note.className = 'asst-dd-info';
|
|
522
|
+
note.textContent = failed || metadata.partial ? tr('assistant.routes.menu.failed', 'Model list could not be refreshed. Provider lists may be out of date.') : tr('assistant.routes.menu.hiddenHelp', 'Missing a model? Your Models list controls what is shown.');
|
|
523
|
+
note.setAttribute('role', 'status'); footer.append(note);
|
|
524
|
+
const customInput = menu.list.querySelector('.asst-dd-input');
|
|
525
|
+
if (customInput) footer.append(customInput);
|
|
526
|
+
for (const [label, action] of [
|
|
527
|
+
...(typeof opts.onManageModels === 'function' ? [[tr('assistant.models.manage', 'Manage models…'), () => { closeMenu(); opts.onManageModels(); }]] : []),
|
|
528
|
+
[tr('assistant.routes.reloadModels', 'Reload models'), () => { closeMenu(); openModelMenu(anchor, { ...opts, forceReload: true }); }],
|
|
529
|
+
]) {
|
|
530
|
+
const button = document.createElement('button'); button.type = 'button'; button.className = 'asst-dd-item'; button.tabIndex = -1; button.textContent = label; button.addEventListener('click', action); footer.append(button);
|
|
531
|
+
}
|
|
532
|
+
menu.el.append(footer); menu.reposition();
|
|
533
|
+
}
|
|
534
|
+
return menu;
|
|
373
535
|
}
|
|
374
536
|
|
|
375
537
|
// ── Picker ──────────────────────────────────────────────────────────────────
|
|
376
538
|
|
|
377
|
-
// Creation order is DOM order within a slot: model · effort · variant · mode · account in the composer.
|
|
539
|
+
// Creation order is DOM order within a slot: model · effort · variant · mode · tests · account in the composer.
|
|
378
540
|
// `effort` is the OpenCode agent for OpenCode; `variant` is OpenCode's effort (the model's variants).
|
|
379
|
-
|
|
541
|
+
// `tests` is the session's Test density, not a brain field: it shows only while the host supplies a level.
|
|
542
|
+
const FIELDS = ['model', 'effort', 'variant', 'mode', 'tests', 'account', 'project', 'mcp'];
|
|
380
543
|
// Slot of each field: the composer row (the brain and how it acts) · the toolbar (where it works).
|
|
381
|
-
const FIELD_GROUP = { model: 'brain', effort: 'brain', variant: 'brain', mode: 'brain', account: 'brain', project: 'context', mcp: 'context' };
|
|
544
|
+
const FIELD_GROUP = { model: 'brain', effort: 'brain', variant: 'brain', mode: 'brain', tests: 'brain', account: 'brain', project: 'context', mcp: 'context' };
|
|
382
545
|
|
|
383
546
|
/**
|
|
384
547
|
* createBrainPicker(host, hooks) — `host` is either one element (all fields in
|
|
@@ -386,7 +549,9 @@ const FIELD_GROUP = { model: 'brain', effort: 'brain', variant: 'brain', mode: '
|
|
|
386
549
|
* hooks: { t, getBrain() → brain, onChange(nextBrain, { requiresSwitch, field }),
|
|
387
550
|
* onAddAccount(provider), onManageAccounts(provider), onToast(text),
|
|
388
551
|
* getMenuAnchor() → fallback anchor (the toolbar "⋯"),
|
|
389
|
-
* menuPlacement: 'below' (default) | 'above' | 'auto'
|
|
552
|
+
* menuPlacement: 'below' (default) | 'above' | 'auto',
|
|
553
|
+
* getTestDensity() → level | null (null hides the field), onTestDensity(level),
|
|
554
|
+
* onProjects(projects) — the registered projects were (re)loaded }
|
|
390
555
|
*/
|
|
391
556
|
export function createBrainPicker(host, hooks = {}) {
|
|
392
557
|
const t = tf(hooks.t);
|
|
@@ -415,6 +580,8 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
415
580
|
|
|
416
581
|
let destroyed = false;
|
|
417
582
|
let loading = null;
|
|
583
|
+
// The empty state's "Choose a project" (mountEmptyProject): the project field's second button.
|
|
584
|
+
let heroProject = null;
|
|
418
585
|
|
|
419
586
|
function currentBrain() {
|
|
420
587
|
return normalizeBrain(hooks.getBrain?.());
|
|
@@ -479,13 +646,19 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
479
646
|
: (level ? level.label : (brain.effort || t('assistant.brain.effortOff', 'off'))),
|
|
480
647
|
variantSupported: brain.provider === 'opencode' && hasEfforts,
|
|
481
648
|
variant: level ? level.label : (brain.effort || t('assistant.brain.variantDefault', 'default variant')),
|
|
482
|
-
project: brain.project ?
|
|
649
|
+
project: brain.project ? projectLabel(brain.project, _projectsCache || []) : t('assistant.brain.projectHome', 'Home'),
|
|
483
650
|
mode: permissionModeLabel(brain, (id, fallback) => t(`assistant.brain.modes.${id}`, fallback)),
|
|
484
651
|
mcp: presets[brain.mcpProfile]?.label || brain.mcpProfile || 'full',
|
|
485
652
|
account: ACCOUNT_PROVIDERS.has(brain.provider) ? accountLabel(brain) : '',
|
|
486
653
|
};
|
|
487
654
|
}
|
|
488
655
|
|
|
656
|
+
/** The session's Test density, or null while the host has none to show (a server that does not know it). */
|
|
657
|
+
function testDensity() {
|
|
658
|
+
const level = typeof hooks.getTestDensity === 'function' ? hooks.getTestDensity() : null;
|
|
659
|
+
return level ? normalizeTestDensity(level) : null;
|
|
660
|
+
}
|
|
661
|
+
|
|
489
662
|
function setBtn(field, { label, icon, color, hidden, disabled, tooltip, aria }) {
|
|
490
663
|
const btn = buttons[field];
|
|
491
664
|
if (!btn) return;
|
|
@@ -520,7 +693,58 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
520
693
|
setBtn('account', { label: v.account, icon: ICON_ACCOUNT, hidden: accountChoices < 2, tooltip: `${t('assistant.brain.account', 'Account profile')}: ${v.account}` });
|
|
521
694
|
setBtn('project', { label: v.project, icon: ICON_FOLDER, tooltip: brain.project || t('assistant.brain.projectNoneTip', 'Working project: home directory'), aria: `${t('assistant.brain.project', 'Working project')}: ${brain.project || v.project}` });
|
|
522
695
|
setBtn('mode', { label: v.mode, icon: ICON_SHIELD, tooltip: `${t('assistant.brain.mode', 'Permission mode')}: ${v.mode}` });
|
|
696
|
+
const tests = testDensity();
|
|
697
|
+
const testsLabel = tests ? testDensityLabel(tests, hooks.t) : '';
|
|
698
|
+
setBtn('tests', { label: testsLabel, icon: testsIcon(testDensityBars(tests)), hidden: !tests, tooltip: `${t('assistant.tests.title', 'Test density')}: ${testsLabel}` });
|
|
699
|
+
if (tests) buttons.tests.dataset.level = tests; else delete buttons.tests.dataset.level;
|
|
523
700
|
setBtn('mcp', { label: v.mcp, icon: ICON_PLUG, tooltip: `${t('assistant.brain.mcp', 'MCP tool profile')}: ${v.mcp}` });
|
|
701
|
+
renderEmptyProject(brain);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function renderEmptyProject(brain = currentBrain()) {
|
|
705
|
+
const btn = heroProject;
|
|
706
|
+
if (!btn) return;
|
|
707
|
+
const view = emptyProjectView(brain.project, _projectsCache || [], t);
|
|
708
|
+
btn.dataset.chosen = view.chosen ? '1' : '0';
|
|
709
|
+
const key = btn.querySelector('.asst-empty-project-key');
|
|
710
|
+
key.textContent = view.key;
|
|
711
|
+
key.hidden = !view.key;
|
|
712
|
+
btn.querySelector('.asst-dd-label').textContent = view.label;
|
|
713
|
+
if (view.title) btn.setAttribute('data-tooltip', view.title); else btn.removeAttribute('data-tooltip');
|
|
714
|
+
btn.setAttribute('aria-label', view.aria);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* The empty state's project picker, in `slot` (asst-render.js): one more
|
|
719
|
+
* button for the project field, drawn by render() like the toolbar's and
|
|
720
|
+
* opening the same menu, so the two always show the same project. A pick
|
|
721
|
+
* sends focus back to it. A new slot replaces the last one; null removes it.
|
|
722
|
+
*/
|
|
723
|
+
function mountEmptyProject(slot) {
|
|
724
|
+
heroProject?.remove();
|
|
725
|
+
heroProject = null;
|
|
726
|
+
if (destroyed || !slot || typeof slot.appendChild !== 'function') return null;
|
|
727
|
+
const btn = el('button', 'asst-dd asst-empty-project-btn');
|
|
728
|
+
btn.type = 'button';
|
|
729
|
+
btn.dataset.field = 'project';
|
|
730
|
+
btn.setAttribute('aria-haspopup', 'menu');
|
|
731
|
+
btn.setAttribute('aria-expanded', 'false');
|
|
732
|
+
// glyph | seam | "Project" + name | caret: the field's capsule at the suggestions' md size.
|
|
733
|
+
const icon = el('span', 'asst-icon');
|
|
734
|
+
icon.innerHTML = ICON_FOLDER;
|
|
735
|
+
const caret = el('span', 'asst-dd-caret');
|
|
736
|
+
caret.innerHTML = ICON_CARET;
|
|
737
|
+
for (const part of [icon, caret]) part.setAttribute('aria-hidden', 'true');
|
|
738
|
+
btn.append(icon, el('span', 'asst-empty-project-key'), el('span', 'asst-dd-label'), caret);
|
|
739
|
+
btn.addEventListener('click', (e) => {
|
|
740
|
+
e.stopPropagation();
|
|
741
|
+
if (isMenuOpen(btn)) { closeMenu(); return; }
|
|
742
|
+
openField('project', btn, { restoreFocus: true });
|
|
743
|
+
});
|
|
744
|
+
slot.appendChild(btn);
|
|
745
|
+
heroProject = btn;
|
|
746
|
+
renderEmptyProject();
|
|
747
|
+
return btn;
|
|
524
748
|
}
|
|
525
749
|
|
|
526
750
|
/** Current values for the toolbar "⋯" menu (fields hidden inline stay reachable there). */
|
|
@@ -535,6 +759,7 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
535
759
|
{ field: 'variant', label: t('assistant.brain.variant', 'Model variant'), value: v.variant, icon: ICON_EFFORT, hidden: !v.variantSupported },
|
|
536
760
|
{ field: 'project', label: t('assistant.brain.project', 'Working project'), value: brain.project || v.project, icon: ICON_FOLDER, hidden: false },
|
|
537
761
|
{ field: 'mode', label: t('assistant.brain.mode', 'Permission mode'), value: v.mode, icon: ICON_SHIELD, hidden: false },
|
|
762
|
+
{ field: 'tests', label: t('assistant.tests.title', 'Test density'), value: testDensity() ? testDensityLabel(testDensity(), hooks.t) : '', icon: testsIcon(testDensityBars(testDensity())), hidden: !testDensity() },
|
|
538
763
|
{ field: 'mcp', label: t('assistant.brain.mcp', 'MCP tool profile'), value: v.mcp, icon: ICON_PLUG, hidden: false },
|
|
539
764
|
{ field: 'account', label: t('assistant.brain.account', 'Account profile'), value: v.account, icon: ICON_ACCOUNT, hidden: !ACCOUNT_PROVIDERS.has(brain.provider) },
|
|
540
765
|
];
|
|
@@ -555,7 +780,12 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
555
780
|
if (provider === 'codex') jobs.push(fetchCodexAccounts(force));
|
|
556
781
|
if (provider === 'claude-code') jobs.push(fetchClaudeAccounts(force));
|
|
557
782
|
if (provider === 'opencode') jobs.push(fetchOpencodeAgents(force));
|
|
558
|
-
if (!_projectsCache || force)
|
|
783
|
+
if (!_projectsCache || force) {
|
|
784
|
+
jobs.push(fetchProjects().then((p) => {
|
|
785
|
+
_projectsCache = p;
|
|
786
|
+
if (!destroyed) { try { hooks.onProjects?.(p); } catch { /* the host's own failure */ } }
|
|
787
|
+
}).catch(() => { _projectsCache = _projectsCache || []; }));
|
|
788
|
+
}
|
|
559
789
|
await Promise.allSettled(jobs);
|
|
560
790
|
}
|
|
561
791
|
|
|
@@ -580,8 +810,11 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
580
810
|
return fallback || btn || root;
|
|
581
811
|
}
|
|
582
812
|
|
|
583
|
-
/**
|
|
584
|
-
|
|
813
|
+
/**
|
|
814
|
+
* Open a field menu; `anchorOverride` (e.g. the toolbar "⋯") when the inline
|
|
815
|
+
* button is hidden. `restoreFocus`: a pick sends focus back to the anchor.
|
|
816
|
+
*/
|
|
817
|
+
async function openField(field, anchorOverride = null, { restoreFocus = false } = {}) {
|
|
585
818
|
const anchor = anchorFor(field, anchorOverride);
|
|
586
819
|
const brain = currentBrain();
|
|
587
820
|
if (field === 'model') {
|
|
@@ -601,6 +834,22 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
601
834
|
});
|
|
602
835
|
return;
|
|
603
836
|
}
|
|
837
|
+
// The session's own setting: nothing to load.
|
|
838
|
+
if (field === 'tests') {
|
|
839
|
+
const current = testDensity();
|
|
840
|
+
if (!current) return;
|
|
841
|
+
openMenu(anchor, {
|
|
842
|
+
title: t('assistant.tests.title', 'Test density'),
|
|
843
|
+
items: TEST_DENSITIES.map((id) => ({
|
|
844
|
+
kind: 'radio', id, label: testDensityLabel(id, hooks.t),
|
|
845
|
+
desc: t(`assistant.tests.desc.${id}`, TEST_DENSITY_COPY[id][1]),
|
|
846
|
+
selected: id === current,
|
|
847
|
+
onSelect: () => { hooks.onTestDensity?.(id); render(); },
|
|
848
|
+
})),
|
|
849
|
+
placement, role: 'menu', className: 'asst-tests-menu',
|
|
850
|
+
});
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
604
853
|
// Everything else needs the provider catalog — load it lazily on first open.
|
|
605
854
|
anchor.setAttribute('aria-busy', 'true');
|
|
606
855
|
try { await ensureLoaded(brain.provider); } finally { anchor.removeAttribute('aria-busy'); }
|
|
@@ -657,10 +906,10 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
657
906
|
|
|
658
907
|
if (field === 'project') {
|
|
659
908
|
const projects = _projectsCache || [];
|
|
660
|
-
const items = [{ kind: 'radio', id: '', label: t('assistant.brain.projectNone', 'Home directory'), desc: t('assistant.brain.projectNoneDesc', 'no project'), selected: !brain.project, onSelect: () => commit({ project: null }, 'project') }];
|
|
661
|
-
for (const p of projects) items.push({ kind: 'radio', id: p.path, label: p.label || basename(p.path), desc: p.path, selected: p.path === brain.project, onSelect: () => commit({ project: p.path }, 'project') });
|
|
909
|
+
const items = [{ kind: 'radio', id: '', label: t('assistant.brain.projectNone', 'Home directory'), desc: t('assistant.brain.projectNoneDesc', 'no project'), selected: !brain.project, restoreFocus, onSelect: () => commit({ project: null }, 'project') }];
|
|
910
|
+
for (const p of projects) items.push({ kind: 'radio', id: p.path, label: p.label || basename(p.path), desc: p.path, selected: p.path === brain.project, restoreFocus, onSelect: () => commit({ project: p.path }, 'project') });
|
|
662
911
|
items.push({ kind: 'separator' });
|
|
663
|
-
items.push({ kind: 'input', placeholder: t('assistant.brain.projectCustom', 'Type a directory path…'), value: brain.project && !projects.some(p => p.path === brain.project) ? brain.project : '', onSubmit: (v) => commit({ project: v }, 'project') });
|
|
912
|
+
items.push({ kind: 'input', placeholder: t('assistant.brain.projectCustom', 'Type a directory path…'), value: brain.project && !projects.some(p => p.path === brain.project) ? brain.project : '', restoreFocus, onSubmit: (v) => commit({ project: v }, 'project') });
|
|
664
913
|
menu(t('assistant.brain.project', 'Working project'), items, { width: 280, filter: projects.length > 8, filterPlaceholder: t('assistant.brain.filterProjects', 'Filter projects…') });
|
|
665
914
|
return;
|
|
666
915
|
}
|
|
@@ -737,8 +986,9 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
737
986
|
openField,
|
|
738
987
|
fieldSummary,
|
|
739
988
|
modelLabel: () => modelLabel(),
|
|
989
|
+
mountEmptyProject,
|
|
740
990
|
setBrain() { render(); },
|
|
741
991
|
close: closeMenu,
|
|
742
|
-
destroy() { destroyed = true; closeMenu(); Object.values(buttons).forEach((b) => b.remove()); root.remove(); },
|
|
992
|
+
destroy() { destroyed = true; closeMenu(); Object.values(buttons).forEach((b) => b.remove()); heroProject?.remove(); heroProject = null; root.remove(); },
|
|
743
993
|
};
|
|
744
994
|
}
|