synabun 2.0.0-beta → 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 +65 -0
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +51 -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/launcher.mjs +40 -0
- package/lib/claude-hooks.js +61 -8
- package/lib/client-config-repair.js +4 -3
- package/lib/data-home-migration.js +4 -0
- package/lib/desktop-pwa.js +206 -0
- package/lib/external-tools.js +221 -0
- package/lib/first-launch-protection.js +5 -0
- package/lib/packaged-runtime.js +110 -0
- package/lib/start-launcher.js +1396 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/neural-interface/i18n/en.json +1540 -154
- package/neural-interface/i18n/pt-BR.json +1538 -153
- 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-envelope.js +9 -6
- 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/augmented-path.js +23 -3
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-language.js +83 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-agent-bridge.js +87 -153
- package/neural-interface/lib/claude-executable.js +241 -0
- package/neural-interface/lib/claude-model-catalog.js +82 -93
- 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-binary-runtime.js +69 -337
- package/neural-interface/lib/native-loop-providers.js +40 -49
- package/neural-interface/lib/offline-page.js +54 -0
- 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/session-title-generator.js +5 -0
- package/neural-interface/lib/synabun-update-plan.js +19 -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 -253
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/install-app.html +34 -0
- package/neural-interface/public/manifest.json +2 -0
- package/neural-interface/public/offline.html +402 -66
- package/neural-interface/public/onboarding.html +264 -147
- 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 +26 -64
- 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-rack-styles.js +0 -3
- package/neural-interface/public/shared/assistant/asst-render.js +219 -67
- 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 +162 -6
- 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/command-runner-model.js +30 -0
- 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 +169 -170
- 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 +62 -0
- package/neural-interface/public/shared/pwa-install.css +12 -0
- package/neural-interface/public/shared/pwa-install.js +127 -0
- 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-rules.js +160 -0
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/start-bridge.js +802 -0
- package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
- package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
- package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
- package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
- package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
- package/neural-interface/public/shared/styles.css +2219 -1117
- package/neural-interface/public/shared/synabun-mascot.js +20 -56
- package/neural-interface/public/shared/term-ready.js +307 -0
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +160 -19
- package/neural-interface/public/shared/ui-command-runner.js +251 -468
- 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-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +367 -12
- package/neural-interface/public/shared/ui-menubar.js +21 -55
- 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 +491 -547
- 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 +153 -149
- 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 +3 -1
- package/neural-interface/public/variant/2d/main.js +2 -1
- package/neural-interface/public/variant/3d/main.js +2 -1
- 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/rebuild-pty.js +15 -12
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +1092 -1212
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +13 -7
- package/postinstall.js +11 -81
- package/setup.js +111 -6
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/docs/browser-v2.md +0 -109
- package/docs/hook-integrity.md +0 -89
- package/docs/judgments.md +0 -182
- package/docs/style-guide.md +0 -246
- 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/scripts/audit-claude-hook-usage.mjs +0 -12
- package/scripts/audit-memory-duplicates.mjs +0 -44
- package/scripts/measure-claude-hook-overhead.mjs +0 -77
- package/scripts/recover-user-config.mjs +0 -254
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Code editor: AI autocomplete
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// Grey text after the caret that Tab accepts: the suggestion as a CodeMirror
|
|
6
|
+
// state field and widget, the request that fetches it (asked a moment after
|
|
7
|
+
// the last change, aborted by the next one), the keys, and the status-bar
|
|
8
|
+
// chip with its popover. What needs no page is decided in editor-ai-logic.js;
|
|
9
|
+
// the server side is lib/editor-completion.js.
|
|
10
|
+
//
|
|
11
|
+
// It ships off. Nothing is requested while `ai.enabled` is off or while the
|
|
12
|
+
// server has no `complete` capability, and without that capability there is no
|
|
13
|
+
// chip either: against a server that was not restarted the editor is exactly
|
|
14
|
+
// as it was. Takes the vendored bundle, the strings and the preferences as
|
|
15
|
+
// arguments: it imports nothing of the app.
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
cacheKey, chipState, completionWindow, createSuggestionCache, describeFailure, failureKey, formatMs,
|
|
19
|
+
hasPrivateKey, matchesExclude, modelsReadFailed, nextWord, requestBody, shouldRequest, suggestionsExpected, typeThrough, usageLine,
|
|
20
|
+
} from './editor-ai-logic.js';
|
|
21
|
+
import { EDITOR_COMMANDS, formatKey } from './editor-commands.js';
|
|
22
|
+
|
|
23
|
+
const IS_MAC = typeof navigator !== 'undefined' && /Mac|iP(hone|[oa]d)/.test(navigator.platform || '');
|
|
24
|
+
const COMPLETE_URL = '/api/editor/complete';
|
|
25
|
+
const FEEDBACK_DELAY_MS = 3000;
|
|
26
|
+
const MAX_BACKOFF_MS = 60_000;
|
|
27
|
+
|
|
28
|
+
function el(tag, props = {}, ...children) {
|
|
29
|
+
const node = document.createElement(tag);
|
|
30
|
+
for (const [name, value] of Object.entries(props)) {
|
|
31
|
+
if (value == null || value === false) continue;
|
|
32
|
+
if (name === 'class') node.className = value;
|
|
33
|
+
else if (name === 'text') node.textContent = value;
|
|
34
|
+
else node.setAttribute(name, value === true ? '' : value);
|
|
35
|
+
}
|
|
36
|
+
node.append(...children);
|
|
37
|
+
return node;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {object} cm the vendored CodeMirror bundle (editor-loader.js)
|
|
42
|
+
* @param {object} o
|
|
43
|
+
* @param {(key: string, params?: object) => string} o.t
|
|
44
|
+
* @param {{ get: () => object, set: (partial: object) => Promise<boolean>, subscribe: (fn: Function) => Function }} o.prefs
|
|
45
|
+
* the editor's preferences (editor-prefs.js)
|
|
46
|
+
* @param {() => ({ path: string, language: string, indentUnit: string } | null)} o.getDocument the open document
|
|
47
|
+
* @param {() => { path: string, text: string }[]} [o.getNeighbors] snippets of the other open tabs that go with a
|
|
48
|
+
* request (pickNeighbors() of editor-ai-logic.js); asked for only when a request is made
|
|
49
|
+
* @param {typeof fetch} [o.fetchImpl]
|
|
50
|
+
* @param {() => void} [o.openSettings] opens Settings on the AI autocomplete section
|
|
51
|
+
*/
|
|
52
|
+
export function editorAi(cm, { t, prefs, getDocument, getNeighbors = () => [], fetchImpl = (...args) => fetch(...args), openSettings = () => {} }) {
|
|
53
|
+
const { Decoration, EditorView, StateEffect, StateField, ViewPlugin, WidgetType } = cm;
|
|
54
|
+
|
|
55
|
+
let view = null; // the editor, while a document is in it
|
|
56
|
+
let capable = false; // the server has the `complete` capability
|
|
57
|
+
let timer = 0; // the wait after the last change
|
|
58
|
+
let request = null; // the request under way: { id, controller }
|
|
59
|
+
let sequence = 0;
|
|
60
|
+
let problem = null; // the last failure (describeFailure), until a request succeeds
|
|
61
|
+
let failures = 0; // failures in a row
|
|
62
|
+
let pausedUntil = 0; // no automatic request before this time (rate limit, repeated failures)
|
|
63
|
+
let pauseTimer = 0;
|
|
64
|
+
let refusedPath = null; // a file the server said it never reads
|
|
65
|
+
let shownKey = null; // the cache key of the suggestion on screen
|
|
66
|
+
let shownSeq = 0; // moves on with every suggestion put in the document...
|
|
67
|
+
let countedSeq = 0; // ...and this one when it was seen: one that stayed under the completion list is not counted
|
|
68
|
+
const cache = createSuggestionCache();
|
|
69
|
+
const counted = { shown: 0, accepted: 0 };
|
|
70
|
+
let feedbackTimer = 0;
|
|
71
|
+
let chip = null; // { wrap, button, popover, sep } once mounted
|
|
72
|
+
let listedModels = null; // the models the popover last listed
|
|
73
|
+
|
|
74
|
+
const ai = () => prefs.get().ai;
|
|
75
|
+
const usable = () => capable && ai().enabled;
|
|
76
|
+
|
|
77
|
+
// ── The suggestion ──
|
|
78
|
+
|
|
79
|
+
class GhostText extends WidgetType {
|
|
80
|
+
constructor(text) { super(); this.text = text; }
|
|
81
|
+
eq(other) { return other.text === this.text; }
|
|
82
|
+
toDOM() { return el('span', { class: 'cm-ghost', 'aria-hidden': 'true', text: this.text }); }
|
|
83
|
+
ignoreEvent() { return false; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Replaces the suggestion: `{ from, text }`, or null for none. */
|
|
87
|
+
const setSuggestion = StateEffect.define();
|
|
88
|
+
|
|
89
|
+
// `{ from, text, hidden }`: `text` would be inserted at `from`. It is not drawn while the completion list is open:
|
|
90
|
+
// it may arrive under the list (a request does not wait for the list to close) and shows when the list is gone.
|
|
91
|
+
const suggestionField = StateField.define({
|
|
92
|
+
create: () => null,
|
|
93
|
+
update(value, tr) {
|
|
94
|
+
for (const effect of tr.effects) if (effect.is(setSuggestion)) value = effect.value ? { ...effect.value, hidden: false } : null;
|
|
95
|
+
const set = tr.effects.some((effect) => effect.is(setSuggestion));
|
|
96
|
+
if (!value) return null;
|
|
97
|
+
if (!set) {
|
|
98
|
+
const selection = tr.newSelection;
|
|
99
|
+
const oneCaret = selection.ranges.length === 1 && selection.main.empty;
|
|
100
|
+
if (tr.docChanged) {
|
|
101
|
+
// Typing what the suggestion begins with leaves the rest of it; any other change drops it.
|
|
102
|
+
let change = null;
|
|
103
|
+
let changes = 0;
|
|
104
|
+
tr.changes.iterChanges((from, to, _fromB, _toB, inserted) => { changes++; change = { from, to, insert: inserted.toString() }; });
|
|
105
|
+
const left = changes === 1 && oneCaret ? typeThrough(value, { ...change, head: selection.main.head }).suggestion : null;
|
|
106
|
+
if (!left) return null;
|
|
107
|
+
value = { ...left, hidden: value.hidden };
|
|
108
|
+
} else if (tr.selection && !(oneCaret && selection.main.head === value.from)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const hidden = cm.completionStatus(tr.state) === 'active';
|
|
113
|
+
return hidden === value.hidden ? value : { ...value, hidden };
|
|
114
|
+
},
|
|
115
|
+
provide: (field) => EditorView.decorations.from(field, (value) => (value && !value.hidden
|
|
116
|
+
? Decoration.set([Decoration.widget({ widget: new GhostText(value.text), side: 1 }).range(value.from)])
|
|
117
|
+
: Decoration.none)),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const visibleSuggestion = (state) => {
|
|
121
|
+
const value = state.field(suggestionField, false);
|
|
122
|
+
return value && !value.hidden ? value : null;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// ── Requests ──
|
|
126
|
+
|
|
127
|
+
function abortRequest() {
|
|
128
|
+
if (!request) return;
|
|
129
|
+
request.controller.abort();
|
|
130
|
+
request = null;
|
|
131
|
+
renderChip();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cancelWait() {
|
|
135
|
+
if (timer) { clearTimeout(timer); timer = 0; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function schedule(trigger = 'auto') {
|
|
139
|
+
cancelWait();
|
|
140
|
+
if (!usable()) return;
|
|
141
|
+
timer = setTimeout(() => { timer = 0; ask(trigger); }, trigger === 'manual' ? 0 : ai().debounceMs);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function pauseFor(ms) {
|
|
145
|
+
pausedUntil = Date.now() + ms;
|
|
146
|
+
clearTimeout(pauseTimer);
|
|
147
|
+
// The chip goes back to "ready" when the wait is over.
|
|
148
|
+
pauseTimer = setTimeout(() => { if (problem?.kind === 'rate') problem = null; renderChip(); }, ms + 50);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function failed(failure) {
|
|
152
|
+
if (failure.kind === 'disabled') {
|
|
153
|
+
// The server's preferences say off: another window switched it off, or this page never heard.
|
|
154
|
+
prefs.load?.();
|
|
155
|
+
problem = null;
|
|
156
|
+
} else if (failure.kind === 'excluded') {
|
|
157
|
+
refusedPath = getDocument()?.path || null;
|
|
158
|
+
problem = null;
|
|
159
|
+
} else {
|
|
160
|
+
problem = failure;
|
|
161
|
+
failures++;
|
|
162
|
+
if (failure.kind === 'rate') pauseFor(failure.retryAfterMs);
|
|
163
|
+
else if (failure.kind !== 'no-model' && failure.kind !== 'no-endpoint') pauseFor(Math.min(MAX_BACKOFF_MS, 5000 * failures));
|
|
164
|
+
}
|
|
165
|
+
renderChip();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Whether a request may be made for the caret as it is, and for which document. */
|
|
169
|
+
function situation() {
|
|
170
|
+
const doc = getDocument();
|
|
171
|
+
if (!view || !doc) return { check: { ok: false, reason: 'off' }, doc: null };
|
|
172
|
+
const { state } = view;
|
|
173
|
+
const selection = state.selection;
|
|
174
|
+
const head = selection.main.head;
|
|
175
|
+
const line = state.doc.lineAt(head);
|
|
176
|
+
const check = shouldRequest({
|
|
177
|
+
enabled: ai().enabled, capable, readOnly: state.readOnly, focused: view.hasFocus,
|
|
178
|
+
visible: document.visibilityState === 'visible',
|
|
179
|
+
carets: selection.ranges.length, hasSelection: selection.ranges.some((range) => !range.empty),
|
|
180
|
+
composing: view.composing,
|
|
181
|
+
lineAfter: state.sliceDoc(head, line.to), path: doc.path, exclude: ai().exclude,
|
|
182
|
+
});
|
|
183
|
+
return { check, doc, head };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function ask(trigger) {
|
|
187
|
+
const { check, doc, head } = situation();
|
|
188
|
+
if (!check.ok || refusedPath === doc.path) return;
|
|
189
|
+
const manual = trigger === 'manual';
|
|
190
|
+
if (!manual && Date.now() < pausedUntil) return;
|
|
191
|
+
// Nothing to choose from yet: asking again would only fail again, until the preferences change.
|
|
192
|
+
if (!manual && (problem?.kind === 'no-model' || problem?.kind === 'no-endpoint')) return;
|
|
193
|
+
const { state } = view;
|
|
194
|
+
const window = completionWindow({ slice: (from, to) => state.sliceDoc(from, to), length: state.doc.length, pos: head });
|
|
195
|
+
if (hasPrivateKey(window.prefix, window.suffix, window.header)) return;
|
|
196
|
+
const key = cacheKey({ path: doc.path, prefix: window.prefix, suffix: window.suffix });
|
|
197
|
+
if (!manual) {
|
|
198
|
+
const kept = cache.get(key);
|
|
199
|
+
if (kept !== undefined) { show(kept, head, key); return; }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
abortRequest();
|
|
203
|
+
// What the other open tabs of this language hold, unless the user switched that off.
|
|
204
|
+
let neighbors = [];
|
|
205
|
+
if (ai().neighbors !== false) { try { neighbors = getNeighbors() || []; } catch { neighbors = []; } }
|
|
206
|
+
const mine = { id: ++sequence, controller: new AbortController() };
|
|
207
|
+
request = mine;
|
|
208
|
+
renderChip();
|
|
209
|
+
let failure = null;
|
|
210
|
+
let completion = null;
|
|
211
|
+
try {
|
|
212
|
+
const res = await fetchImpl(COMPLETE_URL, {
|
|
213
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, signal: mine.controller.signal,
|
|
214
|
+
body: JSON.stringify(requestBody({ path: doc.path, language: doc.language, indentUnit: doc.indentUnit, window, neighbors, trigger })),
|
|
215
|
+
});
|
|
216
|
+
const body = await res.json().catch(() => null);
|
|
217
|
+
if (res.ok && body?.ok === true) completion = typeof body.completion === 'string' ? body.completion : '';
|
|
218
|
+
else failure = describeFailure({ status: res.status, body });
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (mine.controller.signal.aborted) return;
|
|
221
|
+
failure = describeFailure({ error: error?.message });
|
|
222
|
+
}
|
|
223
|
+
if (request !== mine) return; // given up on while the answer was being read
|
|
224
|
+
request = null;
|
|
225
|
+
if (failure) { failed(failure); return; }
|
|
226
|
+
problem = null;
|
|
227
|
+
failures = 0;
|
|
228
|
+
pausedUntil = 0;
|
|
229
|
+
cache.set(key, completion);
|
|
230
|
+
renderChip();
|
|
231
|
+
show(completion, head, key);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Put a suggestion in the document, when the caret is still where it was asked for. Under an open completion
|
|
236
|
+
* list it is there and not drawn (the field hides it): it shows when the list closes.
|
|
237
|
+
*/
|
|
238
|
+
function show(text, pos, key) {
|
|
239
|
+
if (!text || !view) return;
|
|
240
|
+
const { state } = view;
|
|
241
|
+
const selection = state.selection;
|
|
242
|
+
if (selection.ranges.length !== 1 || !selection.main.empty || selection.main.head !== pos) return;
|
|
243
|
+
if (!view.hasFocus || state.readOnly) return;
|
|
244
|
+
shownKey = key;
|
|
245
|
+
shownSeq++;
|
|
246
|
+
view.dispatch({ effects: setSuggestion.of({ from: pos, text }) });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── What the user did with suggestions ──
|
|
250
|
+
|
|
251
|
+
function sendFeedback({ leaving = false } = {}) {
|
|
252
|
+
clearTimeout(feedbackTimer);
|
|
253
|
+
feedbackTimer = 0;
|
|
254
|
+
if (!counted.shown && !counted.accepted) return;
|
|
255
|
+
const body = JSON.stringify(counted);
|
|
256
|
+
counted.shown = 0;
|
|
257
|
+
counted.accepted = 0;
|
|
258
|
+
Promise.resolve().then(() => fetchImpl(`${COMPLETE_URL}/feedback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body, keepalive: leaving })).catch(() => {});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function count(what) {
|
|
262
|
+
counted[what]++;
|
|
263
|
+
if (!feedbackTimer) feedbackTimer = setTimeout(() => sendFeedback(), FEEDBACK_DELAY_MS);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ── Commands (editor-commands.js binds the keys) ──
|
|
267
|
+
|
|
268
|
+
/** Insert `text` at `at` and leave `remainder` (the rest of the suggestion, or nothing) after it. */
|
|
269
|
+
function insert(target, at, text, remainder = '') {
|
|
270
|
+
const end = at + text.length;
|
|
271
|
+
target.dispatch({
|
|
272
|
+
changes: { from: at, insert: text },
|
|
273
|
+
selection: { anchor: end },
|
|
274
|
+
effects: setSuggestion.of(remainder ? { from: end, text: remainder } : null),
|
|
275
|
+
// One step of its own: an undo takes the suggestion back whole, and never the typing around it.
|
|
276
|
+
annotations: cm.isolateHistory.of('full'),
|
|
277
|
+
userEvent: 'input.complete',
|
|
278
|
+
scrollIntoView: true,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Tab: take the suggestion. False when there is none (the key then indents, as always). */
|
|
283
|
+
function accept(target) {
|
|
284
|
+
const suggestion = visibleSuggestion(target.state);
|
|
285
|
+
if (!suggestion || target.state.readOnly) return false;
|
|
286
|
+
insert(target, suggestion.from, suggestion.text);
|
|
287
|
+
count('accepted');
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Mod-→: take the next word of the suggestion and keep the rest. */
|
|
292
|
+
function acceptWord(target) {
|
|
293
|
+
const suggestion = visibleSuggestion(target.state);
|
|
294
|
+
if (!suggestion || target.state.readOnly) return false;
|
|
295
|
+
const word = nextWord(suggestion.text);
|
|
296
|
+
const rest = suggestion.text.slice(word.length);
|
|
297
|
+
insert(target, suggestion.from, word, rest);
|
|
298
|
+
if (!rest) count('accepted');
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Esc: drop the suggestion. A request that has not answered yet is given up on, and the key goes on to whatever else it closes. */
|
|
303
|
+
function dismiss(target) {
|
|
304
|
+
const suggestion = visibleSuggestion(target.state);
|
|
305
|
+
// With the completion list open the key is the list's: what was asked for the caret stays on its way, and a
|
|
306
|
+
// suggestion that is waiting under the list shows when it is gone. The next Esc is the suggestion's.
|
|
307
|
+
if (!suggestion && cm.completionStatus(target.state) !== null) return false;
|
|
308
|
+
cancelWait();
|
|
309
|
+
abortRequest();
|
|
310
|
+
if (!suggestion) return false;
|
|
311
|
+
if (shownKey) cache.delete(shownKey); // dismissed: not offered again from memory
|
|
312
|
+
target.dispatch({ effects: setSuggestion.of(null) });
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Alt-\: ask now, without the wait. False while the feature is off: the key is then typed as usual. */
|
|
317
|
+
function trigger() {
|
|
318
|
+
if (!usable() || !view) return false;
|
|
319
|
+
cancelWait();
|
|
320
|
+
ask('manual');
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ── The editor's side ──
|
|
325
|
+
|
|
326
|
+
const isTyping = (update) => update.transactions.some((tr) => tr.docChanged && (tr.isUserEvent('input') || tr.isUserEvent('delete')));
|
|
327
|
+
|
|
328
|
+
const watcher = ViewPlugin.fromClass(class {
|
|
329
|
+
constructor(editor) {
|
|
330
|
+
view = editor;
|
|
331
|
+
this.listOpen = false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
update(update) {
|
|
335
|
+
const listOpen = cm.completionStatus(update.state) !== null;
|
|
336
|
+
const listClosed = this.listOpen && !listOpen;
|
|
337
|
+
this.listOpen = listOpen;
|
|
338
|
+
const suggestion = update.state.field(suggestionField, false);
|
|
339
|
+
if (update.docChanged || update.selectionSet) {
|
|
340
|
+
// Any change to the document or the selection: the answer on its way is for a caret that moved.
|
|
341
|
+
abortRequest();
|
|
342
|
+
cancelWait();
|
|
343
|
+
// A remainder that was typed into needs no new request.
|
|
344
|
+
if (!suggestion && update.docChanged && isTyping(update)) schedule();
|
|
345
|
+
} else if (listClosed && !suggestion && !request && !timer) {
|
|
346
|
+
schedule(); // the completion list went away and nothing is there or on its way for this caret
|
|
347
|
+
}
|
|
348
|
+
// Counted as shown when it is seen, which for one that arrived under the completion list is when the list closes.
|
|
349
|
+
if (suggestion && !suggestion.hidden && countedSeq !== shownSeq) {
|
|
350
|
+
countedSeq = shownSeq;
|
|
351
|
+
count('shown');
|
|
352
|
+
}
|
|
353
|
+
if (update.focusChanged && !update.view.hasFocus) {
|
|
354
|
+
cancelWait();
|
|
355
|
+
abortRequest();
|
|
356
|
+
if (suggestion) queueMicrotask(() => { if (view === update.view && view.state.field(suggestionField, false)) view.dispatch({ effects: setSuggestion.of(null) }); });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
destroy() {
|
|
361
|
+
cancelWait();
|
|
362
|
+
abortRequest();
|
|
363
|
+
view = null;
|
|
364
|
+
}
|
|
365
|
+
}, {
|
|
366
|
+
eventHandlers: {
|
|
367
|
+
// A composition (an IME) is typing of its own kind: the suggestion steps aside for it.
|
|
368
|
+
compositionstart(_event, editor) {
|
|
369
|
+
cancelWait();
|
|
370
|
+
abortRequest();
|
|
371
|
+
if (editor.state.field(suggestionField, false)) editor.dispatch({ effects: setSuggestion.of(null) });
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// ── The chip and its popover ──
|
|
377
|
+
|
|
378
|
+
const stateSentence = (state) => {
|
|
379
|
+
if (state === 'off') {
|
|
380
|
+
const doc = getDocument();
|
|
381
|
+
const excluded = ai().enabled && doc && (refusedPath === doc.path || matchesExclude(doc.path, ai().exclude));
|
|
382
|
+
return t(excluded ? 'editor.ai.state.excluded' : 'editor.ai.state.off');
|
|
383
|
+
}
|
|
384
|
+
if (state === 'thinking') return t('editor.ai.state.thinking');
|
|
385
|
+
if (state === 'problem') return problemSentence();
|
|
386
|
+
return t('editor.ai.state.ready');
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const problemSentence = () => t(failureKey(problem), { message: problem?.message || '', seconds: Math.max(1, Math.ceil((pausedUntil - Date.now()) / 1000)) });
|
|
390
|
+
|
|
391
|
+
function currentState() {
|
|
392
|
+
const doc = getDocument();
|
|
393
|
+
const excluded = !!doc && (refusedPath === doc.path || matchesExclude(doc.path, ai().exclude));
|
|
394
|
+
return chipState({ capable, enabled: ai().enabled, excluded, busy: !!request, problem });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function renderChip() {
|
|
398
|
+
if (!chip) return;
|
|
399
|
+
const state = currentState();
|
|
400
|
+
const sentence = state === 'hidden' ? '' : stateSentence(state);
|
|
401
|
+
chip.wrap.hidden = state === 'hidden';
|
|
402
|
+
chip.sep.hidden = state === 'hidden';
|
|
403
|
+
chip.button.dataset.state = state;
|
|
404
|
+
chip.button.dataset.tooltip = sentence;
|
|
405
|
+
chip.button.setAttribute('aria-label', sentence);
|
|
406
|
+
if (!chip.popover.hidden) renderPopover();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const openPopover = () => chip && !chip.popover.hidden;
|
|
410
|
+
const POPOVER_KEYS = ['keydown', 'keyup', 'keypress'];
|
|
411
|
+
|
|
412
|
+
// While the popover is open, the keys pressed in it are its own. Taken at the window in the capture phase: a
|
|
413
|
+
// listener at the document (the whiteboard takes Escape there whenever the keyboard is not in a text field, and
|
|
414
|
+
// the popover's switch is a button) would otherwise have the key before the popover does.
|
|
415
|
+
function onPopoverKey(event) {
|
|
416
|
+
if (!openPopover() || !chip.wrap.contains(event.target)) return;
|
|
417
|
+
event.stopPropagation();
|
|
418
|
+
if (event.type === 'keydown' && event.key === 'Escape') { event.preventDefault(); closePopover({ refocus: true }); }
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function closePopover({ refocus = false } = {}) {
|
|
422
|
+
if (!chip || chip.popover.hidden) return;
|
|
423
|
+
chip.popover.hidden = true;
|
|
424
|
+
chip.button.setAttribute('aria-expanded', 'false');
|
|
425
|
+
for (const type of POPOVER_KEYS) window.removeEventListener(type, onPopoverKey, true);
|
|
426
|
+
if (refocus) chip.button.focus();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function renderPopover() {
|
|
430
|
+
const q = (id) => chip.popover.querySelector(`#fe-editor-ai-${id}`);
|
|
431
|
+
const config = ai();
|
|
432
|
+
const state = currentState();
|
|
433
|
+
const toggle = q('switch');
|
|
434
|
+
toggle.setAttribute('aria-checked', String(config.enabled));
|
|
435
|
+
const status = q('status');
|
|
436
|
+
status.textContent = stateSentence(state);
|
|
437
|
+
status.dataset.state = state;
|
|
438
|
+
const endpoint = config.engine === 'endpoint';
|
|
439
|
+
q('model-row').hidden = endpoint;
|
|
440
|
+
q('fastest-row').hidden = endpoint;
|
|
441
|
+
const where = q('endpoint');
|
|
442
|
+
where.hidden = !endpoint;
|
|
443
|
+
where.textContent = endpoint ? t(config.endpoint.model ? 'editor.ai.endpointModel' : 'editor.ai.endpointNone', { model: config.endpoint.model }) : '';
|
|
444
|
+
const select = q('model');
|
|
445
|
+
if (!endpoint && listedModels && select.value !== (config.model || '')) fillModels(listedModels);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function fillModels(models) {
|
|
449
|
+
const select = chip.popover.querySelector('#fe-editor-ai-model');
|
|
450
|
+
const selected = ai().model || '';
|
|
451
|
+
listedModels = models;
|
|
452
|
+
select.textContent = '';
|
|
453
|
+
if (!selected) select.append(el('option', { value: '', text: t('editor.ai.modelNone') }));
|
|
454
|
+
for (const model of models) {
|
|
455
|
+
const price = model.free ? t('editor.ai.priceFree') : model.price ? t('editor.ai.price', { input: model.price.input, output: model.price.output }) : '';
|
|
456
|
+
select.append(el('option', { value: model.id, text: price ? `${model.label} · ${price}` : model.label }));
|
|
457
|
+
}
|
|
458
|
+
// A model that is selected but not listed (hidden since, or its provider signed out) is still named.
|
|
459
|
+
if (selected && !models.some((model) => model.id === selected)) select.append(el('option', { value: selected, text: selected }));
|
|
460
|
+
select.value = selected;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function loadPopoverData() {
|
|
464
|
+
const result = chip.popover.querySelector('#fe-editor-ai-result');
|
|
465
|
+
const usage = chip.popover.querySelector('#fe-editor-ai-usage');
|
|
466
|
+
const select = chip.popover.querySelector('#fe-editor-ai-model');
|
|
467
|
+
if (!listedModels) {
|
|
468
|
+
select.textContent = '';
|
|
469
|
+
select.append(el('option', { value: ai().model || '', text: t('editor.ai.modelsLoading') }));
|
|
470
|
+
}
|
|
471
|
+
const read = async (path) => {
|
|
472
|
+
try { const res = await fetchImpl(`${COMPLETE_URL}${path}`); return res.ok ? await res.json() : null; } catch { return null; }
|
|
473
|
+
};
|
|
474
|
+
const [models, today] = await Promise.all([ai().engine === 'endpoint' ? null : read('/models'), read('/usage')]);
|
|
475
|
+
if (!openPopover()) return;
|
|
476
|
+
if (models?.ok) {
|
|
477
|
+
fillModels(Array.isArray(models.models) ? models.models : []);
|
|
478
|
+
if (models.available === false && !result.textContent) result.textContent = t('editor.ai.modelsUnavailable');
|
|
479
|
+
} else if (modelsReadFailed({ engine: ai().engine, models })) {
|
|
480
|
+
// No answer about the models: the list goes back to what it was (the model in use, at least) and says so,
|
|
481
|
+
// instead of "Loading…" for as long as the popover is open.
|
|
482
|
+
fillModels(listedModels || []);
|
|
483
|
+
result.textContent = t('editor.ai.modelsFailed');
|
|
484
|
+
}
|
|
485
|
+
// "No requests today" only for an answer of zero: a read that failed is said to have failed.
|
|
486
|
+
const line = usageLine(today);
|
|
487
|
+
usage.textContent = t(line.key, line.params);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function findFastest() {
|
|
491
|
+
const button = chip.popover.querySelector('#fe-editor-ai-fastest');
|
|
492
|
+
const result = chip.popover.querySelector('#fe-editor-ai-result');
|
|
493
|
+
button.disabled = true;
|
|
494
|
+
result.textContent = t('editor.ai.finding');
|
|
495
|
+
let body = null;
|
|
496
|
+
try {
|
|
497
|
+
const res = await fetchImpl(`${COMPLETE_URL}/test`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fastest: true }) });
|
|
498
|
+
body = await res.json().catch(() => null);
|
|
499
|
+
} catch { body = null; }
|
|
500
|
+
button.disabled = false;
|
|
501
|
+
const best = body?.results?.find((row) => row.model === body.fastest);
|
|
502
|
+
if (best) {
|
|
503
|
+
await prefs.set({ ai: { model: best.model } });
|
|
504
|
+
result.textContent = t('editor.ai.fastest', { model: best.model, time: formatMs(best.ms) });
|
|
505
|
+
loadPopoverData();
|
|
506
|
+
} else {
|
|
507
|
+
result.textContent = body?.message || t('editor.ai.fastestNone');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function buildPopover() {
|
|
512
|
+
const mac = IS_MAC;
|
|
513
|
+
const keys = el('div', { class: 'fe-editor-ai-keys' });
|
|
514
|
+
for (const command of EDITOR_COMMANDS.filter((entry) => entry.feature === 'ai')) {
|
|
515
|
+
keys.append(el('div', { class: 'fe-editor-kb' }, el('kbd', { text: formatKey(command.keys[0], mac) }), ' ', el('span', { text: t(command.titleKey) })));
|
|
516
|
+
}
|
|
517
|
+
const popover = el('div', { class: 'fe-editor-ai-popover', id: 'fe-editor-ai-popover', role: 'dialog', 'aria-labelledby': 'fe-editor-ai-title', hidden: true },
|
|
518
|
+
el('div', { class: 'fe-editor-ai-head' },
|
|
519
|
+
el('span', { class: 'fe-editor-ai-title', id: 'fe-editor-ai-title', text: t('editor.ai.title') }),
|
|
520
|
+
el('button', { type: 'button', class: 'fe-editor-ai-switch', id: 'fe-editor-ai-switch', role: 'switch', 'aria-checked': 'false', 'aria-labelledby': 'fe-editor-ai-title', 'aria-describedby': 'fe-editor-ai-consent' })),
|
|
521
|
+
el('p', { class: 'fe-editor-ai-note', id: 'fe-editor-ai-consent', text: t('editor.ai.consent') }),
|
|
522
|
+
el('p', { class: 'fe-editor-ai-status', id: 'fe-editor-ai-status', role: 'status' }),
|
|
523
|
+
el('p', { class: 'fe-editor-ai-note', id: 'fe-editor-ai-endpoint', hidden: true }),
|
|
524
|
+
el('div', { class: 'fe-editor-ai-row', id: 'fe-editor-ai-model-row' },
|
|
525
|
+
el('label', { for: 'fe-editor-ai-model', text: t('editor.ai.model') }),
|
|
526
|
+
el('select', { class: 'fe-editor-ai-select', id: 'fe-editor-ai-model' })),
|
|
527
|
+
el('div', { class: 'fe-editor-ai-row', id: 'fe-editor-ai-fastest-row' },
|
|
528
|
+
el('button', { type: 'button', class: 'fe-editor-btn', id: 'fe-editor-ai-fastest', text: t('editor.ai.findFastest') }),
|
|
529
|
+
el('span', { class: 'fe-editor-ai-result', id: 'fe-editor-ai-result', role: 'status' })),
|
|
530
|
+
el('p', { class: 'fe-editor-ai-note', id: 'fe-editor-ai-usage' }),
|
|
531
|
+
keys,
|
|
532
|
+
el('button', { type: 'button', class: 'fe-editor-ai-link', id: 'fe-editor-ai-settings', text: t('editor.ai.settings') }));
|
|
533
|
+
|
|
534
|
+
popover.querySelector('#fe-editor-ai-switch').addEventListener('click', () => { prefs.set({ ai: { enabled: !ai().enabled } }); });
|
|
535
|
+
popover.querySelector('#fe-editor-ai-model').addEventListener('change', (event) => { if (event.target.value) prefs.set({ ai: { model: event.target.value } }); });
|
|
536
|
+
popover.querySelector('#fe-editor-ai-fastest').addEventListener('click', findFastest);
|
|
537
|
+
popover.querySelector('#fe-editor-ai-settings').addEventListener('click', () => { closePopover(); openSettings(); });
|
|
538
|
+
return popover;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Put the chip in the status bar, in front of the caret's position. Called once the server is known to have
|
|
543
|
+
* the `complete` capability: without it nothing is added to the page.
|
|
544
|
+
* @param {HTMLElement} statusbar #fe-editor-statusbar
|
|
545
|
+
*/
|
|
546
|
+
function mountChip(statusbar) {
|
|
547
|
+
if (chip || !statusbar) return;
|
|
548
|
+
const button = el('button', { type: 'button', class: 'fe-editor-ai-chip', id: 'fe-editor-ai', 'aria-haspopup': 'dialog', 'aria-expanded': 'false', 'aria-controls': 'fe-editor-ai-popover' },
|
|
549
|
+
el('span', { class: 'fe-editor-ai-label', text: t('editor.ai.chip') }), el('span', { class: 'fe-editor-ai-dot', 'aria-hidden': 'true' }));
|
|
550
|
+
const popover = buildPopover();
|
|
551
|
+
const wrap = el('span', { class: 'fe-editor-ai', id: 'fe-editor-ai-wrap' }, button, popover);
|
|
552
|
+
const sep = el('span', { class: 'fe-editor-sep', id: 'fe-editor-ai-sep' });
|
|
553
|
+
const before = statusbar.querySelector('#fe-editor-cursor');
|
|
554
|
+
if (before) before.before(wrap, sep); else statusbar.append(sep, wrap);
|
|
555
|
+
chip = { wrap, button, popover, sep };
|
|
556
|
+
|
|
557
|
+
button.addEventListener('click', (event) => {
|
|
558
|
+
event.stopPropagation();
|
|
559
|
+
if (openPopover()) { closePopover(); return; }
|
|
560
|
+
popover.hidden = false;
|
|
561
|
+
button.setAttribute('aria-expanded', 'true');
|
|
562
|
+
for (const type of POPOVER_KEYS) window.addEventListener(type, onPopoverKey, true);
|
|
563
|
+
popover.querySelector('#fe-editor-ai-result').textContent = '';
|
|
564
|
+
renderPopover();
|
|
565
|
+
loadPopoverData();
|
|
566
|
+
popover.querySelector('#fe-editor-ai-switch').focus();
|
|
567
|
+
});
|
|
568
|
+
popover.addEventListener('click', (event) => event.stopPropagation());
|
|
569
|
+
document.addEventListener('click', () => closePopover());
|
|
570
|
+
// The chip is outside the editor's own element: its keys are stopped here, so they reach no shortcut of the app
|
|
571
|
+
// (the open popover's are taken earlier still: onPopoverKey).
|
|
572
|
+
for (const type of POPOVER_KEYS) wrap.addEventListener(type, (event) => event.stopPropagation());
|
|
573
|
+
renderChip();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
prefs.subscribe(() => {
|
|
577
|
+
// Another model, another list of files, on or off: what was kept and what went wrong belong to the old settings.
|
|
578
|
+
cache.clear();
|
|
579
|
+
problem = null;
|
|
580
|
+
failures = 0;
|
|
581
|
+
pausedUntil = 0;
|
|
582
|
+
refusedPath = null;
|
|
583
|
+
if (!ai().enabled) { cancelWait(); abortRequest(); if (view?.state.field(suggestionField, false)) view.dispatch({ effects: setSuggestion.of(null) }); }
|
|
584
|
+
renderChip();
|
|
585
|
+
});
|
|
586
|
+
if (typeof window !== 'undefined') window.addEventListener('pagehide', () => sendFeedback({ leaving: true }));
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
extension: [suggestionField, watcher],
|
|
590
|
+
accept,
|
|
591
|
+
acceptWord,
|
|
592
|
+
dismiss,
|
|
593
|
+
trigger,
|
|
594
|
+
mountChip,
|
|
595
|
+
/** The server's answer about what it offers: with `complete`, the feature exists on this page. */
|
|
596
|
+
setCapable(on) { capable = !!on; renderChip(); },
|
|
597
|
+
/** Another document was opened, or the panel closed: what was under way belonged to the old one. */
|
|
598
|
+
documentChanged() {
|
|
599
|
+
cancelWait();
|
|
600
|
+
abortRequest();
|
|
601
|
+
problem = problem?.kind === 'no-model' || problem?.kind === 'no-endpoint' ? problem : null;
|
|
602
|
+
refusedPath = null;
|
|
603
|
+
closePopover();
|
|
604
|
+
renderChip();
|
|
605
|
+
},
|
|
606
|
+
/**
|
|
607
|
+
* Whether suggestions are expected for the open document (on, a file that is read, a model to ask). The
|
|
608
|
+
* completion list then keeps the document's words for when it is asked (editor-completion-list.js).
|
|
609
|
+
*/
|
|
610
|
+
expectsSuggestions() {
|
|
611
|
+
const doc = getDocument();
|
|
612
|
+
if (!doc) return false;
|
|
613
|
+
return suggestionsExpected({ capable, enabled: ai().enabled, excluded: refusedPath === doc.path || matchesExclude(doc.path, ai().exclude), problem });
|
|
614
|
+
},
|
|
615
|
+
/** The suggestion on screen, or null (tests, and the panel's own checks). */
|
|
616
|
+
suggestion: () => (view ? visibleSuggestion(view.state) : null),
|
|
617
|
+
state: () => currentState(),
|
|
618
|
+
};
|
|
619
|
+
}
|