ompchamber 2.0.2 → 3.0.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 +7 -6
- 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-prbmx3yj.js +5123 -0
- package/dist/client/static/js/index-ranztes9.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/provider-icon/glyphs.ts +118 -0
- package/src/client/components/common/provider-icon/index.tsx +85 -0
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
- package/src/client/components/layout/desktop-layout/index.tsx +2 -0
- package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
- 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/chat-timeline/AiMessageFooter.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.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/Tabs.tsx +2 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +111 -26
- package/src/client/components/workspace/editor/index.tsx +27 -9
- 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/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/use-file-editor.ts +24 -27
- 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/stubs/elkjs.ts +6 -1
- 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/{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/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/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/workspace/file-tab-id.ts +29 -0
- package/src/shared/types/settings/provider.ts +81 -0
- package/src/shared/types/settings/usage.ts +88 -14
- 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/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,89 @@
|
|
|
1
|
+
import { Check } from 'lucide-preact';
|
|
2
|
+
import { PROVIDER_THINKING_EFFORTS, type ProviderThinkingEffort } from '@/shared/lib/models/provider/dialect';
|
|
3
|
+
|
|
4
|
+
interface EffortLadderProps {
|
|
5
|
+
/** Selected levels, in any order; the component renders them in omp's order. */
|
|
6
|
+
selected: ProviderThinkingEffort[];
|
|
7
|
+
onToggle: (effort: ProviderThinkingEffort) => void;
|
|
8
|
+
onSelectAll: () => void;
|
|
9
|
+
onClear: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The reasoning ladder a model accepts, as checkboxes.
|
|
14
|
+
*
|
|
15
|
+
* omp reads this as `thinking.efforts` and refuses to start a level that is not
|
|
16
|
+
* on the list, so the control is a capability declaration rather than a
|
|
17
|
+
* preference: ticking `low` and `high` means the model may be asked for those
|
|
18
|
+
* two and nothing else. `off` is deliberately not offered — omp's `EffortSchema`
|
|
19
|
+
* rejects it, and turning thinking off is a per-turn choice the chat composer
|
|
20
|
+
* already owns.
|
|
21
|
+
*
|
|
22
|
+
* An EMPTY selection is a valid and distinct state: it means "do not declare a
|
|
23
|
+
* ladder", which leaves omp on its own default for the model. That is why the
|
|
24
|
+
* hint changes with the selection instead of always describing a ladder.
|
|
25
|
+
*/
|
|
26
|
+
export function EffortLadder({ selected, onToggle, onSelectAll, onClear }: EffortLadderProps) {
|
|
27
|
+
const isAll = selected.length === PROVIDER_THINKING_EFFORTS.length;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<div className="flex items-center justify-between mb-1.5">
|
|
32
|
+
<label className="block text-xs font-semibold text-ink">Reasoning levels</label>
|
|
33
|
+
<div className="flex items-center gap-2 text-[10px]">
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onClick={onSelectAll}
|
|
37
|
+
disabled={isAll}
|
|
38
|
+
className="text-ink/50 hover:text-ink transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
39
|
+
>
|
|
40
|
+
all
|
|
41
|
+
</button>
|
|
42
|
+
<span className="text-ink/20">·</span>
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={onClear}
|
|
46
|
+
disabled={selected.length === 0}
|
|
47
|
+
className="text-ink/50 hover:text-ink transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
48
|
+
>
|
|
49
|
+
none
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="grid grid-cols-3 gap-2">
|
|
55
|
+
{PROVIDER_THINKING_EFFORTS.map((effort) => {
|
|
56
|
+
const isOn = selected.includes(effort);
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
key={effort}
|
|
60
|
+
type="button"
|
|
61
|
+
onClick={() => onToggle(effort)}
|
|
62
|
+
aria-pressed={isOn}
|
|
63
|
+
className={`flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg border text-left text-[11px] transition-colors cursor-pointer ${
|
|
64
|
+
isOn
|
|
65
|
+
? 'border-ink/50 bg-ink/5 font-semibold text-ink'
|
|
66
|
+
: 'border-ink/15 hover:border-ink/30 text-ink/70'
|
|
67
|
+
}`}
|
|
68
|
+
>
|
|
69
|
+
<span
|
|
70
|
+
className={`w-3 h-3 rounded-[3px] border flex items-center justify-center shrink-0 ${
|
|
71
|
+
isOn ? 'border-ink bg-ink text-canvas' : 'border-ink/30'
|
|
72
|
+
}`}
|
|
73
|
+
>
|
|
74
|
+
{isOn && <Check size={9} strokeWidth={3} />}
|
|
75
|
+
</span>
|
|
76
|
+
<span className="truncate">{effort}</span>
|
|
77
|
+
</button>
|
|
78
|
+
);
|
|
79
|
+
})}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<p className="text-[10px] text-ink/40 mt-1">
|
|
83
|
+
{selected.length === 0
|
|
84
|
+
? 'No ladder declared — omp keeps its own default levels for this model.'
|
|
85
|
+
: `Only these levels will be offered: ${PROVIDER_THINKING_EFFORTS.filter((e) => selected.includes(e)).join(', ')}.`}
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProviderItem } from '@/shared/types';
|
|
2
|
-
import { ProviderIcon } from '@/client/components/
|
|
2
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
3
3
|
|
|
4
4
|
interface ProviderHeaderProps {
|
|
5
5
|
provider: ProviderItem;
|
|
@@ -9,7 +9,7 @@ export function ProviderHeader({ provider }: ProviderHeaderProps) {
|
|
|
9
9
|
return (
|
|
10
10
|
<div className="flex flex-col gap-1 pb-4">
|
|
11
11
|
<div className="flex items-center gap-2.5">
|
|
12
|
-
<ProviderIcon icon={provider.icon} size={22} />
|
|
12
|
+
<ProviderIcon icon={provider.icon} slug={provider.slug} name={provider.name} size={22} />
|
|
13
13
|
<h2 className="text-xl font-bold tracking-tight text-ink">
|
|
14
14
|
{provider.name}
|
|
15
15
|
</h2>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
2
2
|
import { AlertCircle, CheckCircle2, Globe, Key, Loader2 } from 'lucide-preact';
|
|
3
3
|
import type { ProviderItem } from '@/shared/types';
|
|
4
|
-
import { ProviderIcon } from '@/client/components/
|
|
4
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
5
5
|
import { Modal } from '@/client/components/common/Modal';
|
|
6
6
|
|
|
7
7
|
interface LoginModalProps {
|
|
@@ -132,7 +132,7 @@ export function LoginModal({ isOpen, provider, onClose, onAuthenticated }: Login
|
|
|
132
132
|
onClose={onClose}
|
|
133
133
|
header={
|
|
134
134
|
<div className="flex items-center gap-2.5">
|
|
135
|
-
<ProviderIcon icon={provider.icon} size={18} />
|
|
135
|
+
<ProviderIcon icon={provider.icon} slug={provider.slug} name={provider.name} size={18} />
|
|
136
136
|
<div>
|
|
137
137
|
<h3 className="text-sm font-semibold text-ink">Sign in to {provider.name}</h3>
|
|
138
138
|
<p className="text-[11px] text-ink/50 font-mono">{provider.slug}</p>
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { useState } from 'preact/hooks';
|
|
2
|
-
import { Eye, EyeOff, Image as ImageIcon, RefreshCw, Search, Settings2, SlidersHorizontal } from 'lucide-preact';
|
|
2
|
+
import { Eye, EyeOff, Image as ImageIcon, Plus, RefreshCw, Search, Settings2, SlidersHorizontal } from 'lucide-preact';
|
|
3
3
|
import type { ProviderModel } from '@/shared/types';
|
|
4
4
|
import { formatContextWindow } from '@/shared/lib/code/format';
|
|
5
5
|
import { formatPrice } from '@/shared/lib/format/number';
|
|
6
6
|
|
|
7
7
|
interface ProviderModelsListProps {
|
|
8
8
|
models: ProviderModel[];
|
|
9
|
+
/** Why the list is empty, when it legitimately is — never a generic excuse. */
|
|
10
|
+
emptyReason?: string;
|
|
9
11
|
onToggleModelVisibility: (modelId: string) => void;
|
|
10
12
|
onHideAll: () => void;
|
|
11
13
|
onShowAll: () => void;
|
|
12
14
|
onFetchModels: () => Promise<void>;
|
|
15
|
+
/** Opens the manual "Add Model" dialog. */
|
|
16
|
+
onAddModel: () => void;
|
|
13
17
|
canFetchModels: boolean;
|
|
14
18
|
isFetchingModels: boolean;
|
|
15
19
|
onOpenModelConfig: (model: ProviderModel) => void;
|
|
@@ -18,10 +22,12 @@ interface ProviderModelsListProps {
|
|
|
18
22
|
|
|
19
23
|
export function ProviderModelsList({
|
|
20
24
|
models,
|
|
25
|
+
emptyReason,
|
|
21
26
|
onToggleModelVisibility,
|
|
22
27
|
onHideAll,
|
|
23
28
|
onShowAll,
|
|
24
29
|
onFetchModels,
|
|
30
|
+
onAddModel,
|
|
25
31
|
canFetchModels,
|
|
26
32
|
isFetchingModels,
|
|
27
33
|
onOpenModelConfig,
|
|
@@ -52,6 +58,18 @@ export function ProviderModelsList({
|
|
|
52
58
|
<RefreshCw size={12} className={isFetchingModels ? 'animate-spin' : ''} />
|
|
53
59
|
<span>{isFetchingModels ? 'fetching...' : 'fetch models'}</span>
|
|
54
60
|
</button>
|
|
61
|
+
{/* Manual registration, for the ids no listing endpoint reports:
|
|
62
|
+
a gateway model its /models omits, an Azure deployment name, a
|
|
63
|
+
provider with no listing route at all (Bedrock, Vertex). */}
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
onClick={onAddModel}
|
|
67
|
+
title="Register a model by hand"
|
|
68
|
+
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded bg-ink/5 hover:bg-ink/10 text-[11px] font-medium text-ink/80 hover:text-ink transition-colors cursor-pointer border border-ink/10"
|
|
69
|
+
>
|
|
70
|
+
<Plus size={12} />
|
|
71
|
+
<span>add model</span>
|
|
72
|
+
</button>
|
|
55
73
|
<button
|
|
56
74
|
type="button"
|
|
57
75
|
onClick={onHideAll}
|
|
@@ -94,7 +112,9 @@ export function ProviderModelsList({
|
|
|
94
112
|
<div className="space-y-1 pt-1">
|
|
95
113
|
{filteredModels.length === 0 ? (
|
|
96
114
|
<div className="py-8 text-center text-xs text-ink/40">
|
|
97
|
-
|
|
115
|
+
{searchQuery
|
|
116
|
+
? `No models found matching "${searchQuery}"`
|
|
117
|
+
: emptyReason || 'No models registered for this provider yet.'}
|
|
98
118
|
</div>
|
|
99
119
|
) : (
|
|
100
120
|
filteredModels.map((model) => (
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useState } from 'preact/hooks';
|
|
2
|
+
import { Check, ChevronDown } from 'lucide-preact';
|
|
3
|
+
import type { PresetProviderOption } from '@/shared/types/settings/provider';
|
|
4
|
+
import { groupProviderPresets } from '@/shared/lib/models/provider/presets';
|
|
5
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
6
|
+
|
|
7
|
+
interface PresetPickerProps {
|
|
8
|
+
presets: PresetProviderOption[];
|
|
9
|
+
selectedPresetId: string;
|
|
10
|
+
onSelectPreset: (preset: PresetProviderOption) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The provider picker, grouped by kind.
|
|
15
|
+
*
|
|
16
|
+
* Groups matter because the presets are not interchangeable: a local engine is
|
|
17
|
+
* keyless and discovered by omp, a gateway is fetched and written into
|
|
18
|
+
* models.yml, and an "Other dialects" entry exists precisely because the
|
|
19
|
+
* endpoint is NOT OpenAI-shaped. Listing them in one flat grid made a keyless
|
|
20
|
+
* Ollama row look like a hosted API that would ask for a key.
|
|
21
|
+
*/
|
|
22
|
+
export function PresetPicker({ presets, selectedPresetId, onSelectPreset }: PresetPickerProps) {
|
|
23
|
+
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
|
|
24
|
+
const groups = groupProviderPresets(presets);
|
|
25
|
+
|
|
26
|
+
if (presets.length === 0) {
|
|
27
|
+
return (
|
|
28
|
+
<p className="px-2 py-3 text-xs text-ink/50">
|
|
29
|
+
Every known provider is already configured.
|
|
30
|
+
</p>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="max-h-52 scrollbar-overlay-container scrollbar-overlay-static pr-1 space-y-2">
|
|
36
|
+
{groups.map((entry) => {
|
|
37
|
+
const isCollapsed = collapsed[entry.group] === true;
|
|
38
|
+
return (
|
|
39
|
+
<div key={entry.group}>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
onClick={() => setCollapsed((prev) => ({ ...prev, [entry.group]: !isCollapsed }))}
|
|
43
|
+
className="w-full flex items-center gap-1.5 px-0.5 py-1 text-[10px] font-semibold tracking-wider text-ink/50 uppercase hover:text-ink/80 transition-colors cursor-pointer"
|
|
44
|
+
>
|
|
45
|
+
<ChevronDown
|
|
46
|
+
size={11}
|
|
47
|
+
className={`transition-transform ${isCollapsed ? '-rotate-90' : ''}`}
|
|
48
|
+
/>
|
|
49
|
+
<span>{entry.group}</span>
|
|
50
|
+
<span className="text-ink/30 normal-case tracking-normal">{entry.options.length}</span>
|
|
51
|
+
</button>
|
|
52
|
+
|
|
53
|
+
{!isCollapsed && (
|
|
54
|
+
<div className="grid grid-cols-2 gap-2 pt-1">
|
|
55
|
+
{entry.options.map((preset) => {
|
|
56
|
+
const isSelected = preset.id === selectedPresetId;
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
key={preset.id}
|
|
60
|
+
type="button"
|
|
61
|
+
onClick={() => onSelectPreset(preset)}
|
|
62
|
+
title={preset.defaultUrl}
|
|
63
|
+
className={`flex items-center gap-2 p-2 rounded-lg border text-left text-xs transition-colors cursor-pointer ${
|
|
64
|
+
isSelected
|
|
65
|
+
? 'border-ink/50 bg-ink/5 font-semibold text-ink shadow-2xs'
|
|
66
|
+
: 'border-ink/15 hover:border-ink/30 text-ink/70'
|
|
67
|
+
}`}
|
|
68
|
+
>
|
|
69
|
+
<ProviderIcon icon={preset.icon} slug={preset.slug} name={preset.name} size={14} />
|
|
70
|
+
<span className="truncate flex-1">{preset.name}</span>
|
|
71
|
+
{isSelected && <Check size={12} className="shrink-0 text-ink" />}
|
|
72
|
+
</button>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
})}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { DollarSign } from 'lucide-preact';
|
|
2
|
+
|
|
3
|
+
interface PriceFieldsProps {
|
|
4
|
+
costInput: string;
|
|
5
|
+
costOutput: string;
|
|
6
|
+
costCacheRead: string;
|
|
7
|
+
costCacheWrite: string;
|
|
8
|
+
onCostInputChange: (value: string) => void;
|
|
9
|
+
onCostOutputChange: (value: string) => void;
|
|
10
|
+
onCostCacheReadChange: (value: string) => void;
|
|
11
|
+
onCostCacheWriteChange: (value: string) => void;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** One labelled price box. `hint` distinguishes the four rates. */
|
|
16
|
+
function PriceField({
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
label,
|
|
20
|
+
hint,
|
|
21
|
+
}: {
|
|
22
|
+
value: string;
|
|
23
|
+
onChange: (value: string) => void;
|
|
24
|
+
label: string;
|
|
25
|
+
hint: string;
|
|
26
|
+
}) {
|
|
27
|
+
return (
|
|
28
|
+
<div>
|
|
29
|
+
<label className="block text-[11px] font-medium text-ink/70 mb-1" title={hint}>
|
|
30
|
+
{label}
|
|
31
|
+
</label>
|
|
32
|
+
<div className="relative flex items-center">
|
|
33
|
+
<DollarSign size={12} className="absolute left-2.5 text-ink/40 pointer-events-none" />
|
|
34
|
+
<input
|
|
35
|
+
type="text"
|
|
36
|
+
inputMode="decimal"
|
|
37
|
+
value={value}
|
|
38
|
+
onChange={(e) => onChange(e.currentTarget.value)}
|
|
39
|
+
placeholder="0"
|
|
40
|
+
className="w-full bg-paper border border-ink/20 rounded-md pl-7 pr-2 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The four `models.yml` cost rates, in USD per 1M tokens.
|
|
49
|
+
*
|
|
50
|
+
* omp requires all four fields together, so input/output are the pair that
|
|
51
|
+
* decides whether a price is written at all; the two cache rates then default
|
|
52
|
+
* to 0, which is the honest value for "no cached-token discount". Cache rates
|
|
53
|
+
* matter for accounting rather than routing: without them every cached turn is
|
|
54
|
+
* billed at the full input rate, which is why they are worth asking for
|
|
55
|
+
* separately instead of deriving from input.
|
|
56
|
+
*/
|
|
57
|
+
export function PriceFields({
|
|
58
|
+
costInput,
|
|
59
|
+
costOutput,
|
|
60
|
+
costCacheRead,
|
|
61
|
+
costCacheWrite,
|
|
62
|
+
onCostInputChange,
|
|
63
|
+
onCostOutputChange,
|
|
64
|
+
onCostCacheReadChange,
|
|
65
|
+
onCostCacheWriteChange,
|
|
66
|
+
error,
|
|
67
|
+
}: PriceFieldsProps) {
|
|
68
|
+
return (
|
|
69
|
+
<div>
|
|
70
|
+
<label className="block text-xs font-semibold text-ink mb-1.5">
|
|
71
|
+
Price <span className="font-normal text-ink/40">— USD per 1M tokens, optional</span>
|
|
72
|
+
</label>
|
|
73
|
+
<div className="grid grid-cols-2 gap-3">
|
|
74
|
+
<PriceField
|
|
75
|
+
value={costInput}
|
|
76
|
+
onChange={onCostInputChange}
|
|
77
|
+
label="Input"
|
|
78
|
+
hint="Uncached prompt tokens"
|
|
79
|
+
/>
|
|
80
|
+
<PriceField
|
|
81
|
+
value={costOutput}
|
|
82
|
+
onChange={onCostOutputChange}
|
|
83
|
+
label="Output"
|
|
84
|
+
hint="Generated tokens"
|
|
85
|
+
/>
|
|
86
|
+
<PriceField
|
|
87
|
+
value={costCacheRead}
|
|
88
|
+
onChange={onCostCacheReadChange}
|
|
89
|
+
label="Cache read"
|
|
90
|
+
hint="Prompt tokens served from the provider's cache — usually a fraction of the input rate"
|
|
91
|
+
/>
|
|
92
|
+
<PriceField
|
|
93
|
+
value={costCacheWrite}
|
|
94
|
+
onChange={onCostCacheWriteChange}
|
|
95
|
+
label="Cache write"
|
|
96
|
+
hint="Prompt tokens written into the provider's cache"
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
{error && <p className="text-[11px] text-error mt-1">{error}</p>}
|
|
100
|
+
<p className="text-[10px] text-ink/40 mt-1">
|
|
101
|
+
Used for omp's cost accounting. Input and output decide whether a price is
|
|
102
|
+
written at all; the cache rates fall back to 0, which bills cached turns at
|
|
103
|
+
the full input rate.
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useState } from 'preact/hooks';
|
|
2
2
|
import type { FormEvent } from 'preact/compat';
|
|
3
|
-
import { AlertCircle, CheckCircle2, DownloadCloud, Globe, Key, RefreshCw } from 'lucide-preact';
|
|
3
|
+
import { AlertCircle, CheckCircle2, DownloadCloud, Globe, Info, Key, RefreshCw } from 'lucide-preact';
|
|
4
4
|
import type { ProviderItem } from '@/shared/types';
|
|
5
|
-
import { fetchProviderModelsRemote } from '@/shared/lib/models/provider
|
|
6
|
-
import { ProviderIcon } from '@/client/components/
|
|
5
|
+
import { fetchProviderModelsRemote } from '@/shared/lib/models/provider/models';
|
|
6
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
7
7
|
import { Modal } from '@/client/components/common/Modal';
|
|
8
8
|
|
|
9
9
|
interface ReconnectModalProps {
|
|
@@ -23,6 +23,7 @@ export function ReconnectModal({
|
|
|
23
23
|
onReconnect,
|
|
24
24
|
onFetchModels,
|
|
25
25
|
}: ReconnectModalProps) {
|
|
26
|
+
const isKeyless = provider.auth === 'none';
|
|
26
27
|
const [apiKey, setApiKey] = useState(provider.apiKey || 'sk-••••••••••••••••••••••••');
|
|
27
28
|
const [baseUrl, setBaseUrl] = useState(provider.baseUrl || 'https://api.openai.com/v1');
|
|
28
29
|
const [isTesting, setIsTesting] = useState(false);
|
|
@@ -39,7 +40,11 @@ export function ReconnectModal({
|
|
|
39
40
|
const handleTestConnection = async () => {
|
|
40
41
|
setIsTesting(true);
|
|
41
42
|
setTestResult(null);
|
|
42
|
-
const result = await fetchProviderModelsRemote(
|
|
43
|
+
const result = await fetchProviderModelsRemote({
|
|
44
|
+
baseUrl,
|
|
45
|
+
...(draftCredentials.apiKey ? { apiKey: draftCredentials.apiKey } : {}),
|
|
46
|
+
...(provider.api ? { api: provider.api } : {}),
|
|
47
|
+
});
|
|
43
48
|
setIsTesting(false);
|
|
44
49
|
setTestResult(result.ok ? 'success' : 'failed');
|
|
45
50
|
};
|
|
@@ -47,7 +52,10 @@ export function ReconnectModal({
|
|
|
47
52
|
const handleSave = async (e: FormEvent) => {
|
|
48
53
|
e.preventDefault();
|
|
49
54
|
onReconnect({
|
|
50
|
-
|
|
55
|
+
// A keyless provider is configured by `auth: none`; storing the masked
|
|
56
|
+
// placeholder as its credential would show a key that authenticates
|
|
57
|
+
// nothing.
|
|
58
|
+
...(isKeyless ? {} : { apiKey }),
|
|
51
59
|
baseUrl,
|
|
52
60
|
status: 'connected',
|
|
53
61
|
});
|
|
@@ -60,7 +68,7 @@ export function ReconnectModal({
|
|
|
60
68
|
onClose={onClose}
|
|
61
69
|
header={
|
|
62
70
|
<div className="flex items-center gap-2.5">
|
|
63
|
-
<ProviderIcon icon={provider.icon} size={18} />
|
|
71
|
+
<ProviderIcon icon={provider.icon} slug={provider.slug} name={provider.name} size={18} />
|
|
64
72
|
<div>
|
|
65
73
|
<h3 className="text-sm font-semibold text-ink">
|
|
66
74
|
Authentication & Credentials
|
|
@@ -108,21 +116,33 @@ export function ReconnectModal({
|
|
|
108
116
|
</div>
|
|
109
117
|
</div>
|
|
110
118
|
|
|
111
|
-
{/* API Key
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
onChange={(e) => setApiKey(e.currentTarget.value)}
|
|
122
|
-
className="w-full bg-paper border border-ink/20 rounded-md pl-8 pr-3 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
|
|
123
|
-
/>
|
|
119
|
+
{/* API Key — omitted for a keyless provider, which omp configures by
|
|
120
|
+
`auth: none` and which has no credential to store. */}
|
|
121
|
+
{isKeyless ? (
|
|
122
|
+
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-ink/5 border border-ink/10 text-[11px] text-ink/60">
|
|
123
|
+
<Info size={13} className="mt-0.5 flex-shrink-0 text-ink/50" />
|
|
124
|
+
<span>
|
|
125
|
+
This provider is registered with <span className="font-mono">auth: none</span>, so omp
|
|
126
|
+
sends no credential. Set Auth to “API key” in the provider’s own entry if the endpoint
|
|
127
|
+
starts requiring one.
|
|
128
|
+
</span>
|
|
124
129
|
</div>
|
|
125
|
-
|
|
130
|
+
) : (
|
|
131
|
+
<div>
|
|
132
|
+
<label className="block text-xs font-semibold text-ink mb-1">
|
|
133
|
+
API Key / Auth Token
|
|
134
|
+
</label>
|
|
135
|
+
<div className="relative flex items-center">
|
|
136
|
+
<Key size={13} className="absolute left-2.5 text-ink/40 pointer-events-none" />
|
|
137
|
+
<input
|
|
138
|
+
type="text"
|
|
139
|
+
value={apiKey}
|
|
140
|
+
onChange={(e) => setApiKey(e.currentTarget.value)}
|
|
141
|
+
className="w-full bg-paper border border-ink/20 rounded-md pl-8 pr-3 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
126
146
|
|
|
127
147
|
{/* Test connection indicator */}
|
|
128
148
|
<div className="flex items-center justify-between pt-1">
|
|
@@ -154,8 +174,9 @@ export function ReconnectModal({
|
|
|
154
174
|
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-ink/5 border border-ink/10 text-[11px] text-ink/60">
|
|
155
175
|
<DownloadCloud size={13} className="mt-0.5 flex-shrink-0 text-ink/50" />
|
|
156
176
|
<span>
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
{isKeyless
|
|
178
|
+
? 'On save, models are fetched from the endpoint and merged into the list below — a keyless server needs none to answer.'
|
|
179
|
+
: 'On save, models are fetched from the endpoint and merged into the list below — existing models are kept, only new ones are added.'}
|
|
159
180
|
</span>
|
|
160
181
|
</div>
|
|
161
182
|
</Modal>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useRef, useState } from 'preact/hooks';
|
|
3
3
|
import { Check, ChevronDown, Folder, Plus } from 'lucide-preact';
|
|
4
4
|
import type { ProviderItem } from '@/shared/types';
|
|
5
|
-
import { ProviderIcon } from '@/client/components/
|
|
5
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
6
6
|
import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
|
|
7
7
|
|
|
8
8
|
interface ProviderSidebarListProps {
|
|
@@ -115,7 +115,7 @@ export function ProviderSidebarList({
|
|
|
115
115
|
}`}
|
|
116
116
|
>
|
|
117
117
|
<div className="flex items-center gap-2.5 min-w-0">
|
|
118
|
-
<ProviderIcon icon={item.icon} size={15} className="flex-shrink-0" />
|
|
118
|
+
<ProviderIcon icon={item.icon} slug={item.slug} name={item.name} size={15} className="flex-shrink-0" />
|
|
119
119
|
<span className="text-[12px] truncate">{item.name}</span>
|
|
120
120
|
</div>
|
|
121
121
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { UsageLimitWindow } from '@/shared/types';
|
|
2
|
+
import { formatDateTime, formatFractionPercent, formatUsageAmount } from '@/client/components/settings/categories/usage-settings/format';
|
|
3
|
+
|
|
4
|
+
interface LimitRowProps {
|
|
5
|
+
limit: UsageLimitWindow;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** One provider-reported quota window: label, bar, used/remaining, reset. */
|
|
9
|
+
function LimitRow({ limit }: LimitRowProps) {
|
|
10
|
+
const percent = Math.min(100, Math.max(0, (limit.usedFraction ?? 0) * 100));
|
|
11
|
+
const scope = limit.modelId ?? limit.accountId;
|
|
12
|
+
const usedText = limit.used !== undefined ? formatUsageAmount(limit.used, limit.unit) : undefined;
|
|
13
|
+
const limitText = limit.limit !== undefined ? formatUsageAmount(limit.limit, limit.unit) : undefined;
|
|
14
|
+
const remainingText = limit.remaining !== undefined
|
|
15
|
+
? formatUsageAmount(limit.remaining, limit.unit)
|
|
16
|
+
: undefined;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="bg-paper border border-ink/15 rounded-lg p-3 shadow-xs space-y-2">
|
|
20
|
+
<div className="flex items-start justify-between gap-2">
|
|
21
|
+
<div className="min-w-0">
|
|
22
|
+
<div className="text-[11px] font-semibold text-ink truncate">{limit.label}</div>
|
|
23
|
+
{scope && <div className="text-[10px] text-ink/50 truncate">{scope}</div>}
|
|
24
|
+
</div>
|
|
25
|
+
<span className="text-[10px] text-ink/50 flex-shrink-0 whitespace-nowrap">
|
|
26
|
+
{limit.resetsAt !== undefined ? `Resets ${formatDateTime(new Date(limit.resetsAt).toISOString())}` : ''}
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div className="h-1.5 w-full bg-ink/10 rounded-full overflow-hidden">
|
|
31
|
+
<div
|
|
32
|
+
className={`h-full rounded-full ${limit.status === 'exhausted' ? 'bg-error/70' : 'bg-ink/80'}`}
|
|
33
|
+
style={{ width: `${percent}%` }}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-0.5 text-[10px] text-ink/60">
|
|
38
|
+
<span>
|
|
39
|
+
Used{' '}
|
|
40
|
+
<span className="font-semibold text-ink">
|
|
41
|
+
{limit.unit === 'percent' || !usedText ? formatFractionPercent(limit.usedFraction ?? 0) : usedText}
|
|
42
|
+
</span>
|
|
43
|
+
{/* A percent unit is already a share of 100, so naming the limit
|
|
44
|
+
again would print "42% of 100%". */}
|
|
45
|
+
{limit.unit !== 'percent' && usedText && limitText ? ` of ${limitText}` : ''}
|
|
46
|
+
</span>
|
|
47
|
+
<span className="flex items-center gap-2">
|
|
48
|
+
{limit.status !== 'ok' && limit.status !== 'unknown' && (
|
|
49
|
+
<span className="text-[10px] font-mono uppercase px-1.5 py-0.5 rounded bg-ink/10 text-ink/70">
|
|
50
|
+
{limit.status}
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
{remainingText && (
|
|
54
|
+
<span>
|
|
55
|
+
Remaining <span className="font-semibold text-ink">{remainingText}</span>
|
|
56
|
+
</span>
|
|
57
|
+
)}
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{limit.notes && limit.notes.length > 0 && (
|
|
62
|
+
<ul className="text-[10px] text-ink/50 leading-relaxed space-y-0.5">
|
|
63
|
+
{limit.notes.map((note) => (
|
|
64
|
+
<li key={note}>{note}</li>
|
|
65
|
+
))}
|
|
66
|
+
</ul>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface LimitsSectionProps {
|
|
73
|
+
limits: UsageLimitWindow[];
|
|
74
|
+
/** Whether omp ships a usage adapter for this provider. */
|
|
75
|
+
tracked: boolean;
|
|
76
|
+
/** omp tracks this provider but its endpoint returned nothing. */
|
|
77
|
+
unavailable?: boolean;
|
|
78
|
+
/** Provider-wide caveats shown above the windows. */
|
|
79
|
+
notes?: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Generic quota windows for any provider omp reports usage for. */
|
|
83
|
+
export function LimitsSection({ limits, tracked, unavailable, notes }: LimitsSectionProps) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="space-y-2">
|
|
86
|
+
<h4 className="text-[10px] font-mono font-semibold uppercase tracking-wider text-ink/40">
|
|
87
|
+
Quota windows
|
|
88
|
+
</h4>
|
|
89
|
+
|
|
90
|
+
{notes && notes.length > 0 && (
|
|
91
|
+
<ul className="text-[10px] text-ink/50 leading-relaxed space-y-0.5">
|
|
92
|
+
{notes.map((note) => (
|
|
93
|
+
<li key={note}>{note}</li>
|
|
94
|
+
))}
|
|
95
|
+
</ul>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{limits.length === 0 ? (
|
|
99
|
+
<p className="text-[11px] text-ink/50">
|
|
100
|
+
{!tracked
|
|
101
|
+
? 'No quota source for this provider: omp ships no usage adapter for it and it exposes no balance endpoint the chamber knows.'
|
|
102
|
+
: unavailable
|
|
103
|
+
? 'omp tracks this provider but its usage endpoint returned no data — an invalid or expired credential, an unreachable endpoint, or a plan without quota windows.'
|
|
104
|
+
: 'No quota windows were reported for this provider.'}
|
|
105
|
+
</p>
|
|
106
|
+
) : (
|
|
107
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2.5">
|
|
108
|
+
{limits.map((limit) => (
|
|
109
|
+
<LimitRow key={limit.id} limit={limit} />
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|