ompchamber 2.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/bunfig.toml +11 -0
- package/dist/client/index.html +16 -5
- package/dist/client/index.js +1393 -0
- package/dist/client/static/js/index-8t70jhyj.js +5149 -0
- package/dist/client/static/js/index-n4wkqj7t.css +1 -0
- package/index.html +54 -0
- package/package.json +9 -10
- package/public/apple-touch-icon.png +0 -0
- package/public/icon-192x192.png +0 -0
- package/public/icon.svg +60 -0
- package/public/manifest.webmanifest +38 -0
- package/src/cli/lib/commands/serve.js +9 -5
- package/src/cli/lib/runtime.js +18 -7
- package/src/client/components/common/code-editor/handler.test.ts +274 -0
- package/src/client/components/common/code-editor/handler.ts +175 -0
- package/src/client/components/common/code-editor/index.tsx +185 -220
- package/src/client/components/common/code-surface/index.tsx +109 -12
- package/src/client/components/common/lazy-panels.tsx +5 -1
- package/src/client/components/common/provider-icon/glyphs.ts +118 -0
- package/src/client/components/common/provider-icon/index.tsx +85 -0
- package/src/client/components/common/viewport.ts +5 -3
- package/src/client/components/layout/RightActivityBar.tsx +2 -1
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +21 -4
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
- package/src/client/components/layout/desktop-layout/index.tsx +26 -4
- package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
- package/src/client/components/mobile/RightSidebar.tsx +11 -4
- package/src/client/components/mobile/SessionSidebar.tsx +10 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +0 -1
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -1
- package/src/client/components/mobile/mobile-session-sidebar/List.tsx +3 -0
- package/src/client/components/settings/categories/ProviderSettings.tsx +45 -4
- package/src/client/components/settings/categories/UsageSettings.tsx +22 -7
- package/src/client/components/settings/categories/provider-settings/AddModelModal.tsx +322 -0
- package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +152 -56
- package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +52 -1
- package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +133 -0
- package/src/client/components/settings/categories/provider-settings/DialectFields.tsx +136 -0
- package/src/client/components/settings/categories/provider-settings/EffortLadder.tsx +89 -0
- package/src/client/components/settings/categories/provider-settings/Header.tsx +2 -2
- package/src/client/components/settings/categories/provider-settings/LoginModal.tsx +2 -2
- package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +22 -2
- package/src/client/components/settings/categories/provider-settings/PresetPicker.tsx +82 -0
- package/src/client/components/settings/categories/provider-settings/PriceFields.tsx +107 -0
- package/src/client/components/settings/categories/provider-settings/ReconnectModal.tsx +43 -22
- package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +2 -2
- package/src/client/components/settings/categories/usage-settings/LimitsSection.tsx +115 -0
- package/src/client/components/settings/categories/usage-settings/ProviderCard.tsx +19 -26
- package/src/client/components/settings/categories/usage-settings/ProviderDetail.tsx +29 -22
- package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +60 -48
- package/src/client/components/settings/categories/usage-settings/format.ts +36 -0
- package/src/client/components/settings/categories/usage-settings/providers.ts +52 -28
- package/src/client/components/workspace/browser-panel/AddressBar.tsx +1 -10
- package/src/client/components/workspace/browser-panel/Screencast.tsx +4 -5
- package/src/client/components/workspace/browser-panel/index.tsx +6 -6
- package/src/client/components/workspace/browser-toolbar/index.tsx +22 -12
- package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Todo.tsx +1 -1
- package/src/client/components/workspace/diff-panel/Toolbar.tsx +71 -38
- package/src/client/components/workspace/diff-panel/index.tsx +139 -53
- package/src/client/components/workspace/editor/CommandPalette.test.ts +242 -0
- package/src/client/components/workspace/editor/CommandPalette.tsx +138 -0
- package/src/client/components/workspace/editor/FindWidget.test.ts +261 -0
- package/src/client/components/workspace/editor/FindWidget.tsx +219 -0
- package/src/client/components/workspace/editor/Tabs.tsx +2 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +129 -26
- package/src/client/components/workspace/editor/index.tsx +180 -62
- package/src/client/components/workspace/file-explorer/Modals.tsx +79 -2
- package/src/client/components/workspace/file-explorer/TreeItem.tsx +43 -113
- package/src/client/components/workspace/model-dropdown/Item.tsx +13 -23
- package/src/client/components/workspace/model-dropdown/Panel.tsx +1 -1
- package/src/client/components/workspace/model-dropdown/Section.tsx +13 -4
- package/src/client/components/workspace/model-dropdown/index.tsx +7 -3
- package/src/client/components/workspace/todo-panel/PhaseGroup.tsx +76 -0
- package/src/client/components/workspace/todo-panel/Row.tsx +69 -0
- package/src/client/components/workspace/todo-panel/index.tsx +161 -0
- package/src/client/components/workspace/usage-panel/ProviderSelect.tsx +10 -5
- package/src/client/components/workspace/usage-panel/index.tsx +36 -18
- package/src/client/data/samples/dialogue-session.ts +1 -1
- package/src/client/data/samples/sample-tools/other.ts +1 -1
- package/src/client/data/settings/provider.ts +141 -9
- package/src/client/hooks/editor/find-harness.test-util.ts +170 -0
- package/src/client/hooks/editor/scroll-container.test.ts +102 -0
- package/src/client/hooks/editor/scroll-container.ts +26 -0
- package/src/client/hooks/editor/use-code-window.ts +2 -20
- package/src/client/hooks/editor/use-editor-find-write.test.ts +170 -0
- package/src/client/hooks/editor/use-editor-find.test.ts +118 -0
- package/src/client/hooks/editor/use-editor-find.ts +321 -0
- package/src/client/hooks/editor/use-editor-history.test.ts +216 -0
- package/src/client/hooks/editor/use-editor-history.ts +134 -0
- package/src/client/hooks/editor/use-file-editor.ts +24 -27
- package/src/client/hooks/editor/use-occurrences.test.ts +236 -0
- package/src/client/hooks/editor/use-occurrences.ts +98 -0
- package/src/client/hooks/models/use-provider-names.ts +1 -1
- package/src/client/hooks/settings/providers/actions.ts +337 -0
- package/src/client/hooks/settings/providers/api.ts +69 -0
- package/src/client/hooks/settings/providers/index.ts +138 -0
- package/src/client/hooks/settings/providers/messages.ts +77 -0
- package/src/client/hooks/settings/useUsageReport.ts +8 -3
- package/src/client/hooks/ui/theme.ts +3 -3
- package/src/client/hooks/workspace/file-tabs.ts +62 -11
- package/src/client/hooks/workspace/file-tree-actions.test.ts +348 -0
- package/src/client/hooks/workspace/file-tree-actions.ts +250 -0
- package/src/client/hooks/workspace/session-todos.ts +102 -0
- package/src/client/stubs/elkjs.ts +6 -1
- package/src/client/tailwind.css +30 -0
- package/src/server/index.ts +20 -2
- package/src/server/lib/assets/font-css.server.ts +86 -0
- package/src/server/lib/assets/fonts.server.ts +117 -0
- package/src/server/lib/bundler/css.ts +116 -0
- package/src/server/lib/lifecycle/shell-route.ts +17 -0
- package/src/server/lib/models/provider-listing.server.ts +257 -0
- package/src/server/lib/models/provider-registry.server.test.ts +122 -0
- package/src/server/lib/models/provider-registry.server.ts +76 -25
- package/src/server/lib/omp/config/models-config.ts +11 -0
- package/src/server/lib/omp/config/models-validation.test.ts +137 -0
- package/src/server/lib/omp/config/models-validation.ts +111 -2
- package/src/server/lib/omp/config/provider-seeds.test.ts +167 -0
- package/src/server/lib/omp/config/provider-seeds.ts +176 -0
- package/src/server/lib/omp/config/providers.ts +94 -100
- package/src/server/lib/omp/core/auth-credentials.ts +45 -0
- package/src/server/lib/omp/session/todos.ts +100 -0
- package/src/server/lib/omp/session/{usage.ts → usage/aggregate.ts} +1 -118
- package/src/server/lib/omp/session/usage/shaping.ts +130 -0
- package/src/server/lib/usage/deepseek.ts +4 -5
- package/src/server/lib/usage/kenari.ts +7 -6
- package/src/server/lib/usage/omp-usage.server.test.ts +126 -0
- package/src/server/lib/usage/omp-usage.server.ts +194 -0
- package/src/server/lib/usage/provider-key.ts +81 -3
- package/src/server/lib/usage/providers.server.test.ts +47 -0
- package/src/server/lib/usage/providers.server.ts +208 -0
- package/src/server/lib/usage/vendor-balance.server.test.ts +104 -0
- package/src/server/lib/usage/vendor-balance.server.ts +284 -0
- package/src/server/plugins/shell.server.ts +56 -0
- package/src/server/plugins/ssr.ts +24 -36
- package/src/server/plugins/static.test.ts +26 -29
- package/src/server/plugins/static.ts +40 -19
- package/src/server/routes/fs/action-create.test.ts +90 -0
- package/src/server/routes/fs/action.ts +23 -0
- package/src/server/routes/omp/index.ts +2 -0
- package/src/server/routes/omp/session-todos.ts +80 -0
- package/src/server/routes/settings/index.ts +2 -0
- package/src/server/routes/settings/provider-model.ts +107 -0
- package/src/server/routes/settings/provider-models.ts +65 -202
- package/src/server/routes/settings/providers.ts +22 -7
- package/src/server/routes/settings/usage.ts +125 -45
- package/src/server/routes/telemetry/tokens.ts +2 -1
- package/src/shared/lib/chat/{todo-parser.ts → todo/parser.ts} +17 -0
- package/src/shared/lib/chat/todo/progress.ts +97 -0
- package/src/shared/lib/chat/todo/snapshot.test.ts +156 -0
- package/src/shared/lib/chat/todo/snapshot.ts +215 -0
- package/src/shared/lib/code/editor/commands.test.ts +155 -0
- package/src/shared/lib/code/editor/commands.ts +260 -0
- package/src/shared/lib/code/editor/edits.test.ts +100 -0
- package/src/shared/lib/code/editor/edits.ts +122 -0
- package/src/shared/lib/code/editor/find-status.test.ts +50 -0
- package/src/shared/lib/code/editor/find-status.ts +36 -0
- package/src/shared/lib/code/editor/find.test.ts +203 -0
- package/src/shared/lib/code/editor/find.ts +224 -0
- package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.test.ts +1 -1
- package/src/shared/lib/code/editor/keymap.test.ts +109 -0
- package/src/shared/lib/code/editor/keymap.ts +151 -0
- package/src/shared/lib/code/editor/layers.test.ts +77 -0
- package/src/shared/lib/code/editor/layers.ts +67 -0
- package/src/shared/lib/code/editor/line-edits.test.ts +131 -0
- package/src/shared/lib/code/editor/line-edits.ts +159 -0
- package/src/shared/lib/code/editor/lines.test.ts +81 -0
- package/src/shared/lib/code/editor/lines.ts +83 -0
- package/src/shared/lib/code/editor/marks.test.ts +86 -0
- package/src/shared/lib/code/editor/marks.ts +106 -0
- package/src/shared/lib/code/editor/occurrences.test.ts +117 -0
- package/src/shared/lib/code/editor/occurrences.ts +111 -0
- package/src/shared/lib/code/editor/palette.test.ts +91 -0
- package/src/shared/lib/code/editor/palette.ts +92 -0
- package/src/shared/lib/code/editor/reset-css.test.ts +35 -0
- package/src/shared/lib/code/editor/reset-css.ts +32 -0
- package/src/shared/lib/code/editor/text-commands.test.ts +178 -0
- package/src/shared/lib/code/editor/text-commands.ts +154 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +75 -0
- package/src/shared/lib/code/syntax-highlight.ts +29 -17
- package/src/shared/lib/code/windowed-highlight.ts +52 -1
- package/src/shared/lib/fs/diff-parser.test.ts +116 -0
- package/src/shared/lib/fs/diff-parser.ts +86 -33
- package/src/shared/lib/models/{provider-connection.ts → provider/connection.ts} +37 -0
- package/src/shared/lib/models/provider/dialect.ts +179 -0
- package/src/shared/lib/models/provider/glyph.test.ts +116 -0
- package/src/shared/lib/models/provider/glyph.ts +200 -0
- package/src/shared/lib/models/{provider-models.ts → provider/models.ts} +91 -6
- package/src/shared/lib/models/provider/presets.test.ts +172 -0
- package/src/shared/lib/models/provider/presets.ts +83 -0
- package/src/shared/lib/ui/key-binding.test.ts +115 -0
- package/src/shared/lib/ui/key-binding.ts +143 -0
- package/src/shared/lib/util/platform.test.ts +87 -0
- package/src/shared/lib/util/platform.ts +30 -0
- package/src/shared/lib/workspace/file-tab-id.ts +29 -0
- package/src/shared/lib/workspace/keymap.test.ts +82 -0
- package/src/shared/lib/workspace/keymap.ts +30 -0
- package/src/shared/lib/workspace/refresh-cadence.ts +9 -0
- package/src/shared/lib/workspace/right-panels.ts +8 -0
- package/src/shared/lib/workspace/session-state/store.ts +0 -1
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/settings/provider.ts +81 -0
- package/src/shared/types/settings/usage.ts +88 -14
- package/src/shared/types/todo.ts +108 -0
- package/tsconfig.json +3 -1
- package/dist/client/static/css/index.5f630421b7.css +0 -2
- package/dist/client/static/font/KaTeX_AMS-Regular.a79f1c3119.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Bold.ec17d13264.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Regular.55fac25845.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Bold.d42a5579b0.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Regular.d3c882a649.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Bold.c3fb5ac22f.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-BoldItalic.6f2bb1dff2.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Italic.8916142bec.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Regular.0462f03bdf.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Math-BoldItalic.572d331f69.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Math-Italic.f28c23acad.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Bold.8c5b5494b6.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Italic.3b1e59b3ba.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Regular.ba21ed5f84.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Script-Regular.03e9641d6f.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size1-Regular.eae34984b3.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size2-Regular.5916a24fa3.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size4-Regular.10d95fd3a2.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Typewriter-Regular.a8709e3622.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-400-normal.2655b10c55.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-500-normal.92a991f204.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-600-normal.43788eb7f1.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-700-normal.2667215d33.woff2 +0 -0
- package/dist/client/static/js/77.0d81c74cf0.js +0 -139
- package/dist/client/static/js/77.0d81c74cf0.js.LICENSE.txt +0 -8
- package/dist/client/static/js/async/0.8e59f2eb5b.js +0 -1
- package/dist/client/static/js/async/0k.5852e6b4fa.js +0 -1
- package/dist/client/static/js/async/0w.9ed36d5860.js +0 -1
- package/dist/client/static/js/async/1.f387c12b63.js +0 -1
- package/dist/client/static/js/async/12.d091f91c99.js +0 -1
- package/dist/client/static/js/async/13.8f810a8a6c.js +0 -1
- package/dist/client/static/js/async/19.6fe909d55a.js +0 -160
- package/dist/client/static/js/async/1b.23f1141a38.js +0 -1
- package/dist/client/static/js/async/1k.973e8e21fc.js +0 -2
- package/dist/client/static/js/async/1k.973e8e21fc.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/1p.0d5cddda96.js +0 -1
- package/dist/client/static/js/async/2.b589f1733b.js +0 -1
- package/dist/client/static/js/async/2v.96165101de.js +0 -1
- package/dist/client/static/js/async/3.0cacc08423.js +0 -1
- package/dist/client/static/js/async/37.b28b2f61cd.js +0 -1
- package/dist/client/static/js/async/3d.5085fb913f.js +0 -1
- package/dist/client/static/js/async/3j.357c80ff38.js +0 -2
- package/dist/client/static/js/async/3j.357c80ff38.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/3p.bcf32a0983.js +0 -1
- package/dist/client/static/js/async/3r.3a69e98ee0.js +0 -64
- package/dist/client/static/js/async/3u.1228460e2a.js +0 -1
- package/dist/client/static/js/async/3y.3530b824ad.js +0 -198
- package/dist/client/static/js/async/3y.3530b824ad.js.LICENSE.txt +0 -5
- package/dist/client/static/js/async/4.9442eeb472.js +0 -1
- package/dist/client/static/js/async/4e.678285ca55.js +0 -1
- package/dist/client/static/js/async/4k.33a0c7eeb4.js +0 -1
- package/dist/client/static/js/async/4m.928a1dde85.js +0 -31
- package/dist/client/static/js/async/4m.928a1dde85.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/4q.aad20dab25.js +0 -1
- package/dist/client/static/js/async/5.ebaf8b89cc.js +0 -36
- package/dist/client/static/js/async/51.95bcae98af.js +0 -2
- package/dist/client/static/js/async/51.95bcae98af.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
- package/dist/client/static/js/async/5j.d6407c4248.js +0 -287
- package/dist/client/static/js/async/5o.88316d4460.js +0 -62
- package/dist/client/static/js/async/6.f034c9b528.js +0 -1
- package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
- package/dist/client/static/js/async/6o.5b1a06a382.js +0 -1
- package/dist/client/static/js/async/7.e4a9288dd0.js +0 -1
- package/dist/client/static/js/async/73.308dff4da8.js +0 -1
- package/dist/client/static/js/async/7e.a1cc9bfd88.js +0 -1
- package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
- package/dist/client/static/js/async/7l.fadbbc0583.js +0 -1
- package/dist/client/static/js/async/8.5067439d8c.js +0 -1
- package/dist/client/static/js/async/8j.ea110663fb.js +0 -1
- package/dist/client/static/js/async/a.bce4542392.js +0 -1
- package/dist/client/static/js/async/aa.65d3852576.js +0 -1
- package/dist/client/static/js/async/ar.9303e5f2e8.js +0 -2
- package/dist/client/static/js/async/ar.9303e5f2e8.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/az.d656062b0f.js +0 -1
- package/dist/client/static/js/async/b.b4f6f06515.js +0 -1
- package/dist/client/static/js/async/bi.09688ba75c.js +0 -1
- package/dist/client/static/js/async/bq.586bc738d1.js +0 -1
- package/dist/client/static/js/async/c.797d933882.js +0 -1
- package/dist/client/static/js/async/c0.0af1e640d3.js +0 -24
- package/dist/client/static/js/async/ca.8a52cfd447.js +0 -1
- package/dist/client/static/js/async/cf.d34fff5d67.js +0 -1
- package/dist/client/static/js/async/ct.b74928f7a3.js +0 -1
- package/dist/client/static/js/async/cx.3cb0242a3a.js +0 -226
- package/dist/client/static/js/async/d.a5fc8fe1b3.js +0 -1
- package/dist/client/static/js/async/da.f62c70f6c8.js +0 -1
- package/dist/client/static/js/async/dp.1725d6432c.js +0 -1
- package/dist/client/static/js/async/e.93214d622b.js +0 -79
- package/dist/client/static/js/async/f.ab50467719.js +0 -19
- package/dist/client/static/js/async/f.ab50467719.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/f7.46971e9d6d.js +0 -2
- package/dist/client/static/js/async/f7.46971e9d6d.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/f8.bbf0dbae70.js +0 -1
- package/dist/client/static/js/async/g.cb81980498.js +0 -2
- package/dist/client/static/js/async/g.cb81980498.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/h.4e790471e4.js +0 -27
- package/dist/client/static/js/async/h1.393f066d09.js +0 -1
- package/dist/client/static/js/async/h3.216e9798ab.js +0 -148
- package/dist/client/static/js/async/hd.0da931b622.js +0 -1
- package/dist/client/static/js/async/hk.ba7fb04d41.js +0 -1
- package/dist/client/static/js/async/hn.b645041e00.js +0 -1
- package/dist/client/static/js/async/ho.475a5e1ae4.js +0 -1
- package/dist/client/static/js/async/hp.e9fe2e395a.js +0 -2
- package/dist/client/static/js/async/hp.e9fe2e395a.js.LICENSE.txt +0 -17
- package/dist/client/static/js/async/hs.e7c59d8090.js +0 -41
- package/dist/client/static/js/async/i.7c148e8440.js +0 -1
- package/dist/client/static/js/async/id.573df20f29.js +0 -1
- package/dist/client/static/js/async/j.ab925882d8.js +0 -1
- package/dist/client/static/js/async/jd.63886a1f09.js +0 -1
- package/dist/client/static/js/async/jg.d86ebd4013.js +0 -86
- package/dist/client/static/js/async/jl.874d352278.js +0 -1
- package/dist/client/static/js/async/jll.11313b455a.js +0 -1
- package/dist/client/static/js/async/js.67182759ef.js +0 -1
- package/dist/client/static/js/async/jsc.23b70d6f40.js +0 -1
- package/dist/client/static/js/async/jse.334588ff5d.js +0 -1
- package/dist/client/static/js/async/jz.72eb929106.js +0 -77
- package/dist/client/static/js/async/k.3210286401.js +0 -217
- package/dist/client/static/js/async/ka.c1990ecaa3.js +0 -115
- package/dist/client/static/js/async/katex-vendor.de6a87c308.js +0 -1
- package/dist/client/static/js/async/ku.29c8a59d0c.js +0 -1
- package/dist/client/static/js/async/ky.8dab1dade0.js +0 -1
- package/dist/client/static/js/async/l.12dd34e1be.js +0 -1
- package/dist/client/static/js/async/l9.949710adaf.js +0 -83
- package/dist/client/static/js/async/l9.949710adaf.js.LICENSE.txt +0 -13
- package/dist/client/static/js/async/ls.9d9a01026c.js +0 -104
- package/dist/client/static/js/async/m.eb2e6752e2.js +0 -28
- package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/m3.d2b05f9604.js +0 -2
- package/dist/client/static/js/async/m3.d2b05f9604.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/ma.2c261b04dd.js +0 -2
- package/dist/client/static/js/async/ma.2c261b04dd.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/me.8ace414f80.js +0 -23
- package/dist/client/static/js/async/ms.98960af4eb.js +0 -67
- package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
- package/dist/client/static/js/async/n.a9f1c51701.js +0 -1
- package/dist/client/static/js/async/na.a36cd532d6.js +0 -1
- package/dist/client/static/js/async/nk.fc7e58325e.js +0 -1
- package/dist/client/static/js/async/o.36353dfb88.js +0 -1
- package/dist/client/static/js/async/oc.b73042e8bd.js +0 -8
- package/dist/client/static/js/async/og.8eb577893a.js +0 -193
- package/dist/client/static/js/async/p.48ecfcd14e.js +0 -1
- package/dist/client/static/js/async/px.fc6877924b.js +0 -1
- package/dist/client/static/js/async/q.22588845a9.js +0 -21
- package/dist/client/static/js/async/q.22588845a9.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/q5.97dc1481b6.js +0 -286
- package/dist/client/static/js/async/qs.2a9ddb3c72.js +0 -1
- package/dist/client/static/js/async/r.51010ad87c.js +0 -1
- package/dist/client/static/js/async/rd.cb6bf495bb.js +0 -2
- package/dist/client/static/js/async/rd.cb6bf495bb.js.LICENSE.txt +0 -4
- package/dist/client/static/js/async/s.0f395cdda5.js +0 -94
- package/dist/client/static/js/async/t.4dca0e2ba8.js +0 -1
- package/dist/client/static/js/async/t9.d44f57d518.js +0 -1
- package/dist/client/static/js/async/ta.973534bbec.js +0 -1
- package/dist/client/static/js/async/tr.e878658140.js +0 -309
- package/dist/client/static/js/async/u.0871ac5b22.js +0 -1
- package/dist/client/static/js/async/ud.3065bfee31.js +0 -2
- package/dist/client/static/js/async/ud.3065bfee31.js.LICENSE.txt +0 -4
- package/dist/client/static/js/async/up.ea990da0ff.js +0 -32
- package/dist/client/static/js/async/uw.8890abbabc.js +0 -1
- package/dist/client/static/js/async/v.7d28d3c241.js +0 -34
- package/dist/client/static/js/async/vp.9b13bf8610.js +0 -1
- package/dist/client/static/js/async/vt.42416b707d.js +0 -1
- package/dist/client/static/js/async/w.08ac210f60.js +0 -1
- package/dist/client/static/js/async/wt.ad92393a90.js +0 -1
- package/dist/client/static/js/async/x.563f72ca6b.js +0 -1
- package/dist/client/static/js/async/x0.475516418f.js +0 -1
- package/dist/client/static/js/async/x1.da8687159b.js +0 -90
- package/dist/client/static/js/async/xe.da8dcc2015.js +0 -2
- package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/xl.a3c562a58c.js +0 -1
- package/dist/client/static/js/async/xn.6510c0abe2.js +0 -2
- package/dist/client/static/js/async/xn.6510c0abe2.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/xn1.83a7784fe1.js +0 -10
- package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/xnl.feb0021e50.js +0 -2
- package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/y.1a9ed7a5d8.js +0 -1
- package/dist/client/static/js/async/y6.43c57defe4.js +0 -1
- package/dist/client/static/js/async/yb.8c1342401d.js +0 -1
- package/dist/client/static/js/async/ybq.4491f2f2dc.js +0 -1
- package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
- package/dist/client/static/js/async/yq.917ddd7ba2.js +0 -1
- package/dist/client/static/js/async/ys.00687cf82d.js +0 -38
- package/dist/client/static/js/async/yv.d99d829136.js +0 -1
- package/dist/client/static/js/async/z.56f61a4374.js +0 -1
- package/dist/client/static/js/async/zi.5fdb07b86a.js +0 -24
- package/dist/client/static/js/async/zj.654f37e8ff.js +0 -1
- package/dist/client/static/js/async/zn.966d6f1cc6.js +0 -1
- package/dist/client/static/js/async/zo.26213b40c0.js +0 -1
- package/dist/client/static/js/async/zx.e9f4105e00.js +0 -1
- package/dist/client/static/js/index.e6a257402a.js +0 -38
- package/dist/client/static/js/index.e6a257402a.js.LICENSE.txt +0 -4
- package/dist/client/static/js/markdown-vendor.71e43278b3.js +0 -144
- package/dist/client/static/js/shiki-vendor.cb8452ddcd.js +0 -1
- package/src/client/components/settings/categories/provider-settings/Icons.tsx +0 -35
- package/src/client/hooks/settings/providers.ts +0 -326
- package/src/server/plugins/dev-assets.ts +0 -49
- package/src/shared/lib/code/editor-utils.ts +0 -16
- package/src/shared/lib/models/provider-presets.ts +0 -29
- /package/dist/client/{apple-touch-icon.png → static/apple-touch-icon-dskx5sr3.png} +0 -0
- /package/dist/client/{icon-192x192.png → static/icon-192x192-6gn5cr2f.png} +0 -0
- /package/dist/client/{icon.svg → static/icon-d07z9dtz.svg} +0 -0
- /package/dist/client/{manifest.webmanifest → static/manifest-bnyy75kt.webmanifest} +0 -0
- /package/{dist/client → public}/icon-512x512.png +0 -0
- /package/{dist/client → public}/icon-maskable-512x512.png +0 -0
- /package/{dist/client → public}/sw.js +0 -0
- /package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.ts +0 -0
- /package/src/shared/lib/models/{provider-cleanup.ts → provider/cleanup.ts} +0 -0
- /package/src/shared/lib/models/{provider-label.ts → provider/label.ts} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The hook that owns the editor's extra selection ranges.
|
|
8
|
+
*
|
|
9
|
+
* It is a thin shell over `shared/lib/code/editor/occurrences`: the rules live
|
|
10
|
+
* there as pure functions, and this holds the ranges in a ref (the keydown and
|
|
11
|
+
* `input` handlers read them synchronously, inside one event) and tells the
|
|
12
|
+
* caller through `onChange` so the surface can paint them.
|
|
13
|
+
*
|
|
14
|
+
* The split is deliberate: a hook that decides where a range goes cannot be
|
|
15
|
+
* tested without a DOM, and every rule here is one a user notices when it is
|
|
16
|
+
* wrong.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { useRef } from 'preact/hooks';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
growToNextOccurrence,
|
|
23
|
+
offsetBelow,
|
|
24
|
+
replicateAtRanges,
|
|
25
|
+
selectAllOccurrences,
|
|
26
|
+
} from '@/shared/lib/code/editor/occurrences';
|
|
27
|
+
import type { TextRange } from '@/shared/lib/code/editor/commands';
|
|
28
|
+
|
|
29
|
+
export interface UseOccurrencesOptions {
|
|
30
|
+
/** Called with the extra ranges whenever they change, so the surface repaints. */
|
|
31
|
+
onChange: (ranges: readonly TextRange[]) => void;
|
|
32
|
+
/** The caller's copy of the ranges — clearing them there must clear them here. */
|
|
33
|
+
controlled?: readonly TextRange[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface OccurrencesApi {
|
|
37
|
+
/** The extra ranges, excluding the textarea's own (primary) selection. */
|
|
38
|
+
readonly ranges: readonly TextRange[];
|
|
39
|
+
/** Replace the ranges and notify the surface. */
|
|
40
|
+
set: (ranges: readonly TextRange[]) => void;
|
|
41
|
+
/** Drop every extra range. */
|
|
42
|
+
clear: () => void;
|
|
43
|
+
/** ⌘D — grow the primary selection to the next occurrence of what it covers. */
|
|
44
|
+
selectNext: (input: HTMLTextAreaElement, select: (start: number, end: number) => void) => void;
|
|
45
|
+
/** ⌘⇧L — select every occurrence of the selection/word at the caret. */
|
|
46
|
+
selectAll: (input: HTMLTextAreaElement, select: (start: number, end: number) => void) => void;
|
|
47
|
+
/** ⌘⇧D — add an empty caret on the line below. */
|
|
48
|
+
insertCursorBelow: (input: HTMLTextAreaElement) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Replay the browser's edit at every extra range. Returns the buffer and
|
|
51
|
+
* caret the caller should commit, or null when there is nothing to replicate.
|
|
52
|
+
*/
|
|
53
|
+
replicate: (before: string, after: string, primaryBefore: TextRange) => { value: string; caret: number; rest: TextRange[] } | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useOccurrences({ onChange, controlled }: UseOccurrencesOptions): OccurrencesApi {
|
|
57
|
+
const ref = useRef<readonly TextRange[]>([]);
|
|
58
|
+
// The caller may clear the ranges without the editor asking (a file switch, a
|
|
59
|
+
// save), so its copy is mirrored into the ref the handlers read.
|
|
60
|
+
if (controlled && controlled !== ref.current) ref.current = controlled;
|
|
61
|
+
|
|
62
|
+
const set = (ranges: readonly TextRange[]) => {
|
|
63
|
+
ref.current = ranges;
|
|
64
|
+
onChange(ranges);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
get ranges() {
|
|
69
|
+
return ref.current;
|
|
70
|
+
},
|
|
71
|
+
set,
|
|
72
|
+
clear: () => set([]),
|
|
73
|
+
|
|
74
|
+
selectNext: (input, select) => {
|
|
75
|
+
const current: TextRange = { start: input.selectionStart, end: input.selectionEnd };
|
|
76
|
+
const next = growToNextOccurrence(input.value, current, ref.current);
|
|
77
|
+
if (!next) return;
|
|
78
|
+
select(next.primary.start, next.primary.end);
|
|
79
|
+
set(next.extras);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
selectAll: (input, select) => {
|
|
83
|
+
const current: TextRange = { start: input.selectionStart, end: input.selectionEnd };
|
|
84
|
+
const all = selectAllOccurrences(input.value, current);
|
|
85
|
+
if (!all) return;
|
|
86
|
+
select(all.primary.start, all.primary.end);
|
|
87
|
+
set(all.extras);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
insertCursorBelow: (input) => {
|
|
91
|
+
const target = offsetBelow(input.value, input.selectionStart);
|
|
92
|
+
if (target === null) return;
|
|
93
|
+
set([...ref.current, { start: target, end: target }]);
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
replicate: (before, after, primaryBefore) => replicateAtRanges(before, after, primaryBefore, ref.current),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelsResponse } from '@/shared/lib/models/client';
|
|
2
|
-
import { providerNamesFromConnected } from '@/shared/lib/models/provider
|
|
2
|
+
import { providerNamesFromConnected } from '@/shared/lib/models/provider/label';
|
|
3
3
|
import { useModelsCatalog } from '@/client/hooks/models/use-models-catalog';
|
|
4
4
|
|
|
5
5
|
/** slug → display-name map from `connectedProviders` ({} when none connected). */
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The provider mutation actions behind Settings → Providers.
|
|
8
|
+
*
|
|
9
|
+
* Split from the hook (`providers.ts`) so that file holds state wiring only:
|
|
10
|
+
* every action here is a write whose failure has to be reported, and keeping
|
|
11
|
+
* them together means the "what does each mutation actually change" contract
|
|
12
|
+
* has one home. `deps` is a single record rather than a positional list, so a
|
|
13
|
+
* caller cannot silently pass the wrong setter.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ProviderItem, ProviderModel } from '@/shared/types';
|
|
17
|
+
import { notifyModelsUpdated } from '@/shared/lib/models/client';
|
|
18
|
+
import {
|
|
19
|
+
deleteProvider,
|
|
20
|
+
saveProviderOverlay,
|
|
21
|
+
setProviderEnabled,
|
|
22
|
+
} from '@/shared/lib/models/provider/connection';
|
|
23
|
+
import {
|
|
24
|
+
fetchProviderModelsRemote,
|
|
25
|
+
mergeProviderModels,
|
|
26
|
+
syncProviderModelsToCatalog,
|
|
27
|
+
} from '@/shared/lib/models/provider/models';
|
|
28
|
+
import { removeLegacyKenariModels } from '@/shared/lib/models/provider/cleanup';
|
|
29
|
+
import { addProviderMessage, fetchModelsNote } from '@/client/hooks/settings/providers/messages';
|
|
30
|
+
import { saveModelOverrideRemote } from '@/client/hooks/settings/providers/api';
|
|
31
|
+
|
|
32
|
+
export interface ProviderActionsDeps {
|
|
33
|
+
providers: ProviderItem[];
|
|
34
|
+
selectedProvider: ProviderItem | undefined;
|
|
35
|
+
connectedProviders: ProviderItem[];
|
|
36
|
+
isFetchingModels: boolean;
|
|
37
|
+
isDeletingProvider: boolean;
|
|
38
|
+
pushToast: (message: string, tone: 'success' | 'error') => void;
|
|
39
|
+
setProviders: (providers: ProviderItem[]) => void;
|
|
40
|
+
setSelectedProviderId: (id: string) => void;
|
|
41
|
+
setIsAddModalOpen: (open: boolean) => void;
|
|
42
|
+
setIsDeleteModalOpen: (open: boolean) => void;
|
|
43
|
+
setIsDeletingProvider: (deleting: boolean) => void;
|
|
44
|
+
setIsFetchingModels: (fetching: boolean) => void;
|
|
45
|
+
onAddModalClose?: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createProviderActions(deps: ProviderActionsDeps) {
|
|
49
|
+
const {
|
|
50
|
+
providers,
|
|
51
|
+
selectedProvider,
|
|
52
|
+
connectedProviders,
|
|
53
|
+
isFetchingModels,
|
|
54
|
+
isDeletingProvider,
|
|
55
|
+
pushToast,
|
|
56
|
+
setProviders,
|
|
57
|
+
setSelectedProviderId,
|
|
58
|
+
setIsAddModalOpen,
|
|
59
|
+
setIsDeleteModalOpen,
|
|
60
|
+
setIsDeletingProvider,
|
|
61
|
+
setIsFetchingModels,
|
|
62
|
+
onAddModalClose,
|
|
63
|
+
} = deps;
|
|
64
|
+
|
|
65
|
+
/** Publish an overlay edit and adopt whatever the server merged back. */
|
|
66
|
+
const persistProviders = async (updated: ProviderItem[]) => {
|
|
67
|
+
setProviders(updated);
|
|
68
|
+
try {
|
|
69
|
+
const merged = await saveProviderOverlay(updated);
|
|
70
|
+
if (merged) setProviders(merged);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Failed to save providers via API:', error);
|
|
73
|
+
pushToast('Failed to save provider settings.', 'error');
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const toggleProviderEnabled = async (
|
|
78
|
+
slug: string,
|
|
79
|
+
enabled: boolean,
|
|
80
|
+
): Promise<ProviderItem[] | null> => {
|
|
81
|
+
try {
|
|
82
|
+
const merged = await setProviderEnabled(slug, enabled);
|
|
83
|
+
if (merged) setProviders(merged);
|
|
84
|
+
return merged;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Failed to update provider connection:', error);
|
|
87
|
+
pushToast('Failed to update the provider connection.', 'error');
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleAddProvider = async (
|
|
93
|
+
newProvider: ProviderItem,
|
|
94
|
+
options: { fetchedCount: number; ompNote?: string },
|
|
95
|
+
) => {
|
|
96
|
+
// Replace by slug rather than append: an endpoint override reuses the slug
|
|
97
|
+
// of a provider omp already ships, and two overlay rows for one slug would
|
|
98
|
+
// let the stale one win the next merge.
|
|
99
|
+
const slug = newProvider.slug.trim().toLowerCase();
|
|
100
|
+
const withoutExisting = providers.filter((p) => p.slug.trim().toLowerCase() !== slug);
|
|
101
|
+
await persistProviders([...withoutExisting, newProvider]);
|
|
102
|
+
const merged = await toggleProviderEnabled(newProvider.slug, true);
|
|
103
|
+
const registered = merged?.find((provider) => provider.slug.trim().toLowerCase() === slug);
|
|
104
|
+
setSelectedProviderId(registered?.id || newProvider.id);
|
|
105
|
+
setIsAddModalOpen(false);
|
|
106
|
+
onAddModalClose?.();
|
|
107
|
+
|
|
108
|
+
const message = addProviderMessage(newProvider, options);
|
|
109
|
+
pushToast(message.text, message.tone);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleReconnect = async (updates: Partial<ProviderItem>) => {
|
|
113
|
+
if (!selectedProvider) return;
|
|
114
|
+
const target = selectedProvider;
|
|
115
|
+
// A keyless provider stores no credential, so a masked placeholder echoed
|
|
116
|
+
// back from the dialog must never be persisted as one — it would show a key
|
|
117
|
+
// that authenticates nothing.
|
|
118
|
+
const patch: Partial<ProviderItem> = target.auth === 'none'
|
|
119
|
+
? Object.fromEntries(Object.entries(updates).filter(([key]) => key !== 'apiKey'))
|
|
120
|
+
: updates;
|
|
121
|
+
await persistProviders(providers.map((p) => (
|
|
122
|
+
p.id === target.id ? { ...p, ...patch, status: 'connected' as const } : p
|
|
123
|
+
)));
|
|
124
|
+
if (target.status !== 'connected') {
|
|
125
|
+
await toggleProviderEnabled(target.slug, true);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const handleFetchModels = async (credentials: { apiKey?: string; baseUrl?: string }) => {
|
|
130
|
+
if (!selectedProvider || isFetchingModels) return;
|
|
131
|
+
const baseUrl = credentials.baseUrl || selectedProvider.baseUrl;
|
|
132
|
+
if (!baseUrl) {
|
|
133
|
+
pushToast('Base URL is required to fetch models.', 'error');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setIsFetchingModels(true);
|
|
137
|
+
try {
|
|
138
|
+
const result = await fetchModelsFor(selectedProvider, baseUrl, credentials.apiKey);
|
|
139
|
+
if (!result) return;
|
|
140
|
+
if (result.omp?.written) notifyModelsUpdated();
|
|
141
|
+
} finally {
|
|
142
|
+
setIsFetchingModels(false);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The listing probe plus its reporting. Kept as one unit because the three
|
|
148
|
+
* outcomes — models added, legacy models pruned, nothing new — each have their
|
|
149
|
+
* own message, and a partial report reads as a failed fetch.
|
|
150
|
+
*/
|
|
151
|
+
const fetchModelsFor = async (
|
|
152
|
+
provider: ProviderItem,
|
|
153
|
+
baseUrl: string,
|
|
154
|
+
credential: string | undefined,
|
|
155
|
+
): Promise<{ omp?: { written: boolean; reason?: string } } | null> => {
|
|
156
|
+
const result = await fetchProviderModelsRemote({
|
|
157
|
+
baseUrl,
|
|
158
|
+
apiKey: credential || provider.apiKey,
|
|
159
|
+
providerSlug: provider.slug,
|
|
160
|
+
persistToOmp: true,
|
|
161
|
+
// The provider's own dialect decides the auth header the probe sends;
|
|
162
|
+
// falling back to the URL classifier would send a Bearer header to an
|
|
163
|
+
// Anthropic-shaped proxy that only accepts x-api-key.
|
|
164
|
+
...(provider.api ? { api: provider.api } : {}),
|
|
165
|
+
...(provider.auth ? { auth: provider.auth } : {}),
|
|
166
|
+
});
|
|
167
|
+
if (!result.ok || !result.models) {
|
|
168
|
+
pushToast(result.error || 'Failed to fetch models from the provider.', 'error');
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const cleaned = removeLegacyKenariModels(provider, provider.models);
|
|
172
|
+
const removedCount = provider.models.length - cleaned.length;
|
|
173
|
+
const { merged, addedCount } = mergeProviderModels(cleaned, result.models);
|
|
174
|
+
if (addedCount > 0 || removedCount > 0) {
|
|
175
|
+
await handleReconnect({ models: merged });
|
|
176
|
+
}
|
|
177
|
+
if (addedCount > 0) {
|
|
178
|
+
pushToast(`Fetched ${addedCount} new model${addedCount === 1 ? '' : 's'} from the provider.`, 'success');
|
|
179
|
+
} else if (removedCount > 0) {
|
|
180
|
+
pushToast('Removed legacy fallback models from the provider.', 'success');
|
|
181
|
+
} else {
|
|
182
|
+
pushToast('No new models found — all fetched models already exist.', 'success');
|
|
183
|
+
}
|
|
184
|
+
const note = fetchModelsNote(result);
|
|
185
|
+
if (note) pushToast(note.text, note.tone);
|
|
186
|
+
if (addedCount > 0) void syncProviderModelsToCatalog(provider.name, result.models);
|
|
187
|
+
return { omp: result.omp };
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const handleFetchModelsFromList = async () => {
|
|
191
|
+
if (!selectedProvider) return;
|
|
192
|
+
await handleFetchModels({
|
|
193
|
+
apiKey: selectedProvider.apiKey,
|
|
194
|
+
baseUrl: selectedProvider.baseUrl,
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const handleOmpAuthSuccess = () => {
|
|
199
|
+
if (!selectedProvider) return;
|
|
200
|
+
const target = selectedProvider;
|
|
201
|
+
void persistProviders(providers.map((p) => (
|
|
202
|
+
p.id === target.id ? { ...p, status: 'connected' as const } : p
|
|
203
|
+
)));
|
|
204
|
+
void toggleProviderEnabled(target.slug, true);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleToggleDisconnect = async () => {
|
|
208
|
+
if (!selectedProvider) return;
|
|
209
|
+
const target = selectedProvider;
|
|
210
|
+
const disconnecting = target.status === 'connected';
|
|
211
|
+
if (disconnecting) {
|
|
212
|
+
setSelectedProviderId(connectedProviders.find((provider) => provider.id !== target.id)?.id || '');
|
|
213
|
+
}
|
|
214
|
+
const merged = await toggleProviderEnabled(target.slug, !disconnecting);
|
|
215
|
+
if (!merged) return;
|
|
216
|
+
pushToast(
|
|
217
|
+
disconnecting
|
|
218
|
+
? `${target.name} disconnected — its models are no longer offered in the chat picker.`
|
|
219
|
+
: `${target.name} reconnected.`,
|
|
220
|
+
'success',
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const updateSelectedModels = (transform: (models: ProviderModel[]) => ProviderModel[]) => {
|
|
225
|
+
if (!selectedProvider) return;
|
|
226
|
+
void persistProviders(providers.map((p) => (
|
|
227
|
+
p.id === selectedProvider.id ? { ...p, models: transform(p.models) } : p
|
|
228
|
+
)));
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Disable/enable from omp's own config.yml `disabledProviders`. This is the
|
|
233
|
+
* coarse switch that removes the provider — and every model it serves — from
|
|
234
|
+
* the chat picker; the per-model eye toggle is the fine-grained one.
|
|
235
|
+
*/
|
|
236
|
+
const handleToggleProviderDisabled = async () => {
|
|
237
|
+
if (!selectedProvider) return;
|
|
238
|
+
const target = selectedProvider;
|
|
239
|
+
const disabling = target.disabled !== true;
|
|
240
|
+
const merged = await toggleProviderEnabled(target.slug, !disabling);
|
|
241
|
+
if (!merged) return;
|
|
242
|
+
pushToast(
|
|
243
|
+
disabling
|
|
244
|
+
? `${target.name} disabled — it no longer appears in the model list.`
|
|
245
|
+
: `${target.name} enabled — its models are offered again.`,
|
|
246
|
+
'success',
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Delete a provider's `models.yml` entry (and the chamber's overlay row).
|
|
252
|
+
*
|
|
253
|
+
* Only offered for a provider the server reports as `inModelsYml`, because
|
|
254
|
+
* the action is a file edit: a login provider's credential is untouched, and
|
|
255
|
+
* a provider with no file entry has nothing to delete. The selection moves off
|
|
256
|
+
* the deleted provider BEFORE the list is republished — leaving it selected
|
|
257
|
+
* would render a detail pane for a provider that no longer exists.
|
|
258
|
+
*/
|
|
259
|
+
const handleDeleteProvider = async () => {
|
|
260
|
+
if (!selectedProvider || isDeletingProvider) return;
|
|
261
|
+
const target = selectedProvider;
|
|
262
|
+
setIsDeletingProvider(true);
|
|
263
|
+
try {
|
|
264
|
+
const result = await deleteProvider(target.id);
|
|
265
|
+
if (!result.ok) {
|
|
266
|
+
pushToast(result.error || 'Failed to delete the provider.', 'error');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
setIsDeleteModalOpen(false);
|
|
270
|
+
const remaining = result.providers ?? [];
|
|
271
|
+
setSelectedProviderId(remaining.find((provider) => provider.status === 'connected')?.id || '');
|
|
272
|
+
setProviders(remaining);
|
|
273
|
+
// The overlay row is gone even when the file cleanup failed, so the
|
|
274
|
+
// warning is reported rather than presented as a clean delete.
|
|
275
|
+
if (result.warning) {
|
|
276
|
+
pushToast(`${target.name} removed from the chamber, but models.yml cleanup failed: ${result.warning}`, 'error');
|
|
277
|
+
} else if (result.removedFromOmp) {
|
|
278
|
+
const count = result.modelsRemoved ?? 0;
|
|
279
|
+
pushToast(
|
|
280
|
+
`${target.name} deleted from models.yml${count > 0 ? ` with its ${count} model${count === 1 ? '' : 's'}` : ''}.`,
|
|
281
|
+
'success',
|
|
282
|
+
);
|
|
283
|
+
} else {
|
|
284
|
+
pushToast(`${target.name} removed from the chamber list.`, 'success');
|
|
285
|
+
}
|
|
286
|
+
} finally {
|
|
287
|
+
setIsDeletingProvider(false);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const handleHideAll = () => updateSelectedModels(
|
|
292
|
+
(models) => models.map((m) => ({ ...m, isVisible: false })),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const handleShowAll = () => updateSelectedModels(
|
|
296
|
+
(models) => models.map((m) => ({ ...m, isVisible: true })),
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const handleToggleModelVisibility = (modelId: string) => updateSelectedModels(
|
|
300
|
+
(models) => models.map((m) => (m.id === modelId ? { ...m, isVisible: !m.isVisible } : m)),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Save the per-model knobs omp honours (`models.yml` modelOverrides). The
|
|
305
|
+
* overlay still stores the display value so the dialog reopens where the user
|
|
306
|
+
* left it, but omp only reads the override — writing just the overlay was the
|
|
307
|
+
* bug that made these controls inert.
|
|
308
|
+
*/
|
|
309
|
+
const handleSaveModelConfig = async (modelId: string, updates: Partial<ProviderModel>) => {
|
|
310
|
+
if (!selectedProvider) return;
|
|
311
|
+
const provider = selectedProvider;
|
|
312
|
+
updateSelectedModels((models) => models.map((m) => (m.id === modelId ? { ...m, ...updates } : m)));
|
|
313
|
+
|
|
314
|
+
const result = await saveModelOverrideRemote(provider.slug, modelId, updates);
|
|
315
|
+
if (!result.ok) {
|
|
316
|
+
pushToast(result.error || 'Failed to save the model configuration.', 'error');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (result.written) notifyModelsUpdated();
|
|
320
|
+
else if (result.reason) pushToast(result.reason, 'error');
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
handleAddProvider,
|
|
325
|
+
handleReconnect,
|
|
326
|
+
handleFetchModels,
|
|
327
|
+
handleFetchModelsFromList,
|
|
328
|
+
handleOmpAuthSuccess,
|
|
329
|
+
handleToggleDisconnect,
|
|
330
|
+
handleToggleProviderDisabled,
|
|
331
|
+
handleDeleteProvider,
|
|
332
|
+
handleHideAll,
|
|
333
|
+
handleShowAll,
|
|
334
|
+
handleToggleModelVisibility,
|
|
335
|
+
handleSaveModelConfig,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The settings API calls behind Settings → Providers.
|
|
8
|
+
*
|
|
9
|
+
* Split from the hook so the hook holds state transitions only — each of these
|
|
10
|
+
* is a request whose failure has to be reported to the user, and keeping them
|
|
11
|
+
* here means the "what does the server answer" contract has one home.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { PresetProviderOption, ProviderItem, ProviderModel } from '@/shared/types';
|
|
15
|
+
|
|
16
|
+
export interface ProviderSettingsPayload {
|
|
17
|
+
providers: ProviderItem[];
|
|
18
|
+
presetProviders?: PresetProviderOption[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Load the merged provider registry (omp's sources + the chamber overlay). */
|
|
22
|
+
export async function loadProvidersFromApi(): Promise<ProviderSettingsPayload | null> {
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch('/api/settings/providers');
|
|
25
|
+
const data = await response.json() as Partial<ProviderSettingsPayload>;
|
|
26
|
+
if (!Array.isArray(data?.providers)) return null;
|
|
27
|
+
return { providers: data.providers, ...(data.presetProviders ? { presetProviders: data.presetProviders } : {}) };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Failed to load providers from API:', error);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ModelOverrideResult {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
written?: boolean;
|
|
37
|
+
reason?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Write the per-model knobs omp honours (`maxTokens`, reasoning effort) into
|
|
43
|
+
* models.yml `modelOverrides`. Writing only the chamber overlay left the
|
|
44
|
+
* controls inert — omp never read them.
|
|
45
|
+
*/
|
|
46
|
+
export async function saveModelOverrideRemote(
|
|
47
|
+
providerSlug: string,
|
|
48
|
+
modelId: string,
|
|
49
|
+
updates: Partial<ProviderModel>,
|
|
50
|
+
): Promise<ModelOverrideResult> {
|
|
51
|
+
const body: Record<string, unknown> = { provider: providerSlug, modelId };
|
|
52
|
+
if ('maxTokens' in updates) body.maxTokens = updates.maxTokens ?? null;
|
|
53
|
+
if ('reasoningEffort' in updates) body.reasoningEffort = updates.reasoningEffort ?? null;
|
|
54
|
+
// Nothing omp would read — the overlay update alone is the whole change.
|
|
55
|
+
if (Object.keys(body).length === 2) return { ok: true, written: false };
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch('/api/settings/model-override', {
|
|
58
|
+
method: 'PUT',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
});
|
|
62
|
+
const data = await response.json() as { success?: boolean; written?: boolean; reason?: string; error?: string };
|
|
63
|
+
if (!response.ok) return { ok: false, error: data.error || 'Failed to save the model configuration.' };
|
|
64
|
+
return { ok: true, written: data.written, reason: data.reason };
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Failed to save model override:', error);
|
|
67
|
+
return { ok: false, error: 'Failed to save the model configuration.' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'preact/hooks';
|
|
2
|
+
import type { PresetProviderOption, ProviderItem, ProviderModel } from '@/shared/types';
|
|
3
|
+
import { PRESET_NEW_PROVIDERS } from '@/client/data/settings/provider';
|
|
4
|
+
import { buildAvailableProviderPresets } from '@/shared/lib/models/provider/presets';
|
|
5
|
+
import { useToasts } from '@/client/hooks/ui/toasts';
|
|
6
|
+
import { loadProvidersFromApi } from '@/client/hooks/settings/providers/api';
|
|
7
|
+
import { createProviderActions } from '@/client/hooks/settings/providers/actions';
|
|
8
|
+
|
|
9
|
+
interface UseProviderSettingsOptions {
|
|
10
|
+
autoOpenAdd?: boolean;
|
|
11
|
+
onAddModalClose?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* State wiring behind Settings → Providers.
|
|
16
|
+
*
|
|
17
|
+
* The mutations themselves live in `providers/actions.ts`, which receives this
|
|
18
|
+
* hook's setters as one `deps` record — the split keeps both files under the
|
|
19
|
+
* repo's size ceiling without changing closure semantics, since every action
|
|
20
|
+
* still closes over the same state.
|
|
21
|
+
*
|
|
22
|
+
* Every mutation lands in one of two stores: the chamber-local overlay (per-model
|
|
23
|
+
* visibility and sampling config) or omp's own config.yml `disabledProviders`
|
|
24
|
+
* (connect / disconnect). Disconnect has to use the latter — the overlay is
|
|
25
|
+
* re-merged away on load, which is why a disconnected provider used to come back
|
|
26
|
+
* as connected.
|
|
27
|
+
*/
|
|
28
|
+
export function useProviderSettings({
|
|
29
|
+
autoOpenAdd = false,
|
|
30
|
+
onAddModalClose,
|
|
31
|
+
}: UseProviderSettingsOptions = {}) {
|
|
32
|
+
const [providers, setProviders] = useState<ProviderItem[]>([]);
|
|
33
|
+
const [presetProviders, setPresetProviders] = useState<PresetProviderOption[]>(PRESET_NEW_PROVIDERS);
|
|
34
|
+
const [selectedProviderId, setSelectedProviderId] = useState<string>('provider-deepseek');
|
|
35
|
+
const [currentProject, setCurrentProject] = useState('ompchamber');
|
|
36
|
+
const [isFetchingModels, setIsFetchingModels] = useState(false);
|
|
37
|
+
const { toasts, pushToast, dismissToast } = useToasts();
|
|
38
|
+
|
|
39
|
+
const [isAddModalOpen, setIsAddModalOpen] = useState(autoOpenAdd);
|
|
40
|
+
const [isAddModelModalOpen, setIsAddModelModalOpen] = useState(false);
|
|
41
|
+
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
42
|
+
const [isDeletingProvider, setIsDeletingProvider] = useState(false);
|
|
43
|
+
const [isReconnectModalOpen, setIsReconnectModalOpen] = useState(false);
|
|
44
|
+
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
|
|
45
|
+
const [configModel, setConfigModel] = useState<ProviderModel | null>(null);
|
|
46
|
+
const [capabilitiesModel, setCapabilitiesModel] = useState<ProviderModel | null>(null);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (autoOpenAdd) setIsAddModalOpen(true);
|
|
50
|
+
}, [autoOpenAdd]);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
let active = true;
|
|
54
|
+
void loadProvidersFromApi().then((data) => {
|
|
55
|
+
if (!active || !data) return;
|
|
56
|
+
setProviders(data.providers);
|
|
57
|
+
const connected = data.providers.filter((provider) => provider.status === 'connected');
|
|
58
|
+
setSelectedProviderId(connected.length > 0 ? connected[0].id : '');
|
|
59
|
+
if (data.presetProviders) setPresetProviders(data.presetProviders);
|
|
60
|
+
});
|
|
61
|
+
return () => { active = false; };
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
// The sidebar lists connected providers plus every provider the user disabled:
|
|
65
|
+
// a disabled provider reports `disconnected`, so filtering on status alone
|
|
66
|
+
// would make the switch that hid it unreachable.
|
|
67
|
+
const connectedProviders = useMemo(
|
|
68
|
+
() => providers.filter(
|
|
69
|
+
(provider) => provider.status === 'connected' || provider.disabled === true,
|
|
70
|
+
),
|
|
71
|
+
[providers],
|
|
72
|
+
);
|
|
73
|
+
const availablePresetProviders = useMemo(
|
|
74
|
+
() => buildAvailableProviderPresets(providers, presetProviders),
|
|
75
|
+
[providers, presetProviders],
|
|
76
|
+
);
|
|
77
|
+
const selectedProvider = connectedProviders.find((p) => p.id === selectedProviderId) || connectedProviders[0];
|
|
78
|
+
|
|
79
|
+
const actions = createProviderActions({
|
|
80
|
+
providers,
|
|
81
|
+
selectedProvider,
|
|
82
|
+
connectedProviders,
|
|
83
|
+
isFetchingModels,
|
|
84
|
+
isDeletingProvider,
|
|
85
|
+
pushToast,
|
|
86
|
+
setProviders,
|
|
87
|
+
setSelectedProviderId,
|
|
88
|
+
setIsAddModalOpen,
|
|
89
|
+
setIsDeleteModalOpen,
|
|
90
|
+
setIsDeletingProvider,
|
|
91
|
+
setIsFetchingModels,
|
|
92
|
+
...(onAddModalClose ? { onAddModalClose } : {}),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* After a hand-registered model lands in models.yml, the registry has to be
|
|
97
|
+
* re-read: the new row exists only in omp's file, so nothing in the chamber's
|
|
98
|
+
* own overlay knows about it yet. Re-fetching the MERGED list (rather than
|
|
99
|
+
* re-saving the overlay, whose response is the overlay alone) is what puts
|
|
100
|
+
* the model in the sidebar count and the chat picker.
|
|
101
|
+
*/
|
|
102
|
+
const handleModelAdded = async (modelId: string) => {
|
|
103
|
+
pushToast(`${modelId} registered in models.yml.`, 'success');
|
|
104
|
+
const data = await loadProvidersFromApi();
|
|
105
|
+
if (data) setProviders(data.providers);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
providers,
|
|
110
|
+
connectedProviders,
|
|
111
|
+
availablePresetProviders,
|
|
112
|
+
selectedProvider,
|
|
113
|
+
currentProject,
|
|
114
|
+
setCurrentProject,
|
|
115
|
+
isFetchingModels,
|
|
116
|
+
toasts,
|
|
117
|
+
pushToast,
|
|
118
|
+
dismissToast,
|
|
119
|
+
isAddModelModalOpen,
|
|
120
|
+
setIsAddModelModalOpen,
|
|
121
|
+
handleModelAdded,
|
|
122
|
+
isDeleteModalOpen,
|
|
123
|
+
setIsDeleteModalOpen,
|
|
124
|
+
isDeletingProvider,
|
|
125
|
+
isAddModalOpen,
|
|
126
|
+
setIsAddModalOpen,
|
|
127
|
+
isReconnectModalOpen,
|
|
128
|
+
setIsReconnectModalOpen,
|
|
129
|
+
isLoginModalOpen,
|
|
130
|
+
setIsLoginModalOpen,
|
|
131
|
+
configModel,
|
|
132
|
+
setConfigModel,
|
|
133
|
+
capabilitiesModel,
|
|
134
|
+
setCapabilitiesModel,
|
|
135
|
+
setSelectedProviderId,
|
|
136
|
+
...actions,
|
|
137
|
+
};
|
|
138
|
+
}
|