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
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { Check, Copy, Download, Eye, EyeOff, Maximize, Save, WrapText, ZoomIn, ZoomOut } from 'lucide-preact';
|
|
1
|
+
import { AlertCircle, Check, Copy, Download, Eye, EyeOff, Maximize, Minimize, Save, WrapText, ZoomIn, ZoomOut } from 'lucide-preact';
|
|
2
|
+
|
|
3
|
+
export type EditorSaveStatus = 'idle' | 'saving' | 'saved' | 'error';
|
|
2
4
|
|
|
3
5
|
interface EditorToolbarProps {
|
|
4
6
|
path: string;
|
|
5
|
-
saveStatus:
|
|
7
|
+
saveStatus: EditorSaveStatus;
|
|
6
8
|
wordWrap: boolean;
|
|
7
9
|
isMd: boolean;
|
|
8
10
|
isPreview: boolean;
|
|
11
|
+
/** True for the 1.5s after a copy landed, so the button can confirm it. */
|
|
12
|
+
copied: boolean;
|
|
9
13
|
/** Raster image: no text to save or wrap, and zoom belongs to the picture. */
|
|
10
14
|
isImage?: boolean;
|
|
15
|
+
/** A read failure left no trustworthy buffer, so there is nothing to save. */
|
|
16
|
+
saveDisabled?: boolean;
|
|
17
|
+
isMaximized?: boolean;
|
|
11
18
|
onSave: () => void;
|
|
12
19
|
onTogglePreview: () => void;
|
|
13
20
|
onToggleWordWrap: () => void;
|
|
@@ -18,13 +25,25 @@ interface EditorToolbarProps {
|
|
|
18
25
|
onToggleMaximize: () => void;
|
|
19
26
|
}
|
|
20
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Shared icon-button shell. Every action here is icon-only, so each one carries
|
|
30
|
+
* a `title` (the tooltip) and an `aria-label` (the accessible name) — a bare
|
|
31
|
+
* `<svg onClick>` is unreachable by keyboard and unlabeled to a screen reader.
|
|
32
|
+
* `p-1.5` also gives the 14px glyph a ~26px hit target.
|
|
33
|
+
*/
|
|
34
|
+
const ACTION_CLASS =
|
|
35
|
+
'flex items-center justify-center p-1.5 rounded transition-colors cursor-pointer disabled:cursor-default disabled:opacity-50';
|
|
36
|
+
|
|
21
37
|
export function EditorToolbar({
|
|
22
38
|
path,
|
|
23
39
|
saveStatus,
|
|
24
40
|
wordWrap,
|
|
25
41
|
isMd,
|
|
26
42
|
isPreview,
|
|
43
|
+
copied,
|
|
27
44
|
isImage = false,
|
|
45
|
+
saveDisabled = false,
|
|
46
|
+
isMaximized = false,
|
|
28
47
|
onSave,
|
|
29
48
|
onTogglePreview,
|
|
30
49
|
onToggleWordWrap,
|
|
@@ -34,65 +53,131 @@ export function EditorToolbar({
|
|
|
34
53
|
onDownload,
|
|
35
54
|
onToggleMaximize,
|
|
36
55
|
}: EditorToolbarProps) {
|
|
56
|
+
const saveTitle =
|
|
57
|
+
saveDisabled
|
|
58
|
+
? 'Nothing to save — this file could not be read'
|
|
59
|
+
: saveStatus === 'saving'
|
|
60
|
+
? 'Saving…'
|
|
61
|
+
: saveStatus === 'error'
|
|
62
|
+
? 'Save failed — the file on disk was not changed'
|
|
63
|
+
: 'Save file (Ctrl+S)';
|
|
64
|
+
|
|
37
65
|
return (
|
|
38
|
-
<div className="flex items-center justify-between px-3 py-2 border-b border-ink/10 bg-paper">
|
|
66
|
+
<div className="flex items-center justify-between px-3 py-2 border-b border-ink/10 bg-paper flex-shrink-0">
|
|
39
67
|
{/* Left status */}
|
|
40
|
-
<div className="flex items-center space-x-2 text-xs font-mono text-ink/40">
|
|
68
|
+
<div className="flex items-center space-x-2 text-xs font-mono text-ink/40 min-w-0">
|
|
41
69
|
<span className="truncate max-w-[300px]" title={path}>{path}</span>
|
|
42
70
|
</div>
|
|
43
71
|
|
|
44
72
|
{/* Right actions */}
|
|
45
|
-
<div className="flex items-center space-x-
|
|
73
|
+
<div className="flex items-center space-x-1 text-ink/40 flex-shrink-0">
|
|
46
74
|
{!isImage && (
|
|
47
|
-
<div className="flex items-center pr-
|
|
75
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
48
76
|
<button
|
|
49
77
|
type="button"
|
|
50
78
|
onClick={onSave}
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
disabled={saveDisabled || saveStatus === 'saving'}
|
|
80
|
+
className={`${ACTION_CLASS} hover:bg-ink/5`}
|
|
81
|
+
title={saveTitle}
|
|
82
|
+
aria-label={saveTitle}
|
|
53
83
|
>
|
|
54
84
|
{saveStatus === 'saving' ? (
|
|
55
85
|
<Save size={14} className="text-amber-500 animate-pulse" />
|
|
56
86
|
) : saveStatus === 'saved' ? (
|
|
57
87
|
<Check size={14} className="text-success" />
|
|
88
|
+
) : saveStatus === 'error' ? (
|
|
89
|
+
<AlertCircle size={14} className="text-error" />
|
|
58
90
|
) : (
|
|
59
|
-
<Save size={14} className="hover:text-ink
|
|
91
|
+
<Save size={14} className="hover:text-ink" />
|
|
60
92
|
)}
|
|
61
93
|
</button>
|
|
62
94
|
</div>
|
|
63
95
|
)}
|
|
64
96
|
|
|
65
97
|
{isMd && (
|
|
66
|
-
<div
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
98
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={onTogglePreview}
|
|
102
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 ${isPreview ? 'text-ink' : 'hover:text-ink'}`}
|
|
103
|
+
title={isPreview ? 'Show markdown source' : 'Preview markdown'}
|
|
104
|
+
aria-label={isPreview ? 'Show markdown source' : 'Preview markdown'}
|
|
105
|
+
aria-pressed={isPreview}
|
|
106
|
+
>
|
|
107
|
+
{isPreview ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
108
|
+
</button>
|
|
72
109
|
</div>
|
|
73
110
|
)}
|
|
74
111
|
|
|
75
112
|
{!isImage && (
|
|
76
113
|
<>
|
|
77
|
-
<div className="flex items-center
|
|
78
|
-
<
|
|
114
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
onClick={onToggleWordWrap}
|
|
118
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 ${wordWrap ? 'text-ink' : 'hover:text-ink'}`}
|
|
119
|
+
title={wordWrap ? 'Word wrap on' : 'Word wrap off'}
|
|
120
|
+
aria-label={wordWrap ? 'Word wrap on' : 'Word wrap off'}
|
|
121
|
+
aria-pressed={wordWrap}
|
|
122
|
+
>
|
|
123
|
+
<WrapText size={14} />
|
|
124
|
+
</button>
|
|
79
125
|
</div>
|
|
80
126
|
|
|
81
|
-
<div className="flex items-center
|
|
82
|
-
<
|
|
83
|
-
|
|
127
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
onClick={onZoomOut}
|
|
131
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink`}
|
|
132
|
+
title="Decrease font size"
|
|
133
|
+
aria-label="Decrease font size"
|
|
134
|
+
>
|
|
135
|
+
<ZoomOut size={14} />
|
|
136
|
+
</button>
|
|
137
|
+
<button
|
|
138
|
+
type="button"
|
|
139
|
+
onClick={onZoomIn}
|
|
140
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink`}
|
|
141
|
+
title="Increase font size"
|
|
142
|
+
aria-label="Increase font size"
|
|
143
|
+
>
|
|
144
|
+
<ZoomIn size={14} />
|
|
145
|
+
</button>
|
|
84
146
|
</div>
|
|
85
147
|
</>
|
|
86
148
|
)}
|
|
87
149
|
|
|
88
|
-
<div className="flex items-center
|
|
89
|
-
<
|
|
90
|
-
|
|
150
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
onClick={onCopy}
|
|
154
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink`}
|
|
155
|
+
title={copied ? 'Copied' : isImage ? 'Copy image' : 'Copy content'}
|
|
156
|
+
aria-label={copied ? 'Copied' : isImage ? 'Copy image' : 'Copy content'}
|
|
157
|
+
>
|
|
158
|
+
{copied ? <Check size={14} className="text-success" /> : <Copy size={14} />}
|
|
159
|
+
</button>
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
onClick={onDownload}
|
|
163
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink`}
|
|
164
|
+
title="Download file"
|
|
165
|
+
aria-label="Download file"
|
|
166
|
+
>
|
|
167
|
+
<Download size={14} />
|
|
168
|
+
</button>
|
|
91
169
|
</div>
|
|
92
170
|
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
onClick={onToggleMaximize}
|
|
174
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink ${isMaximized ? 'text-ink' : ''}`}
|
|
175
|
+
title={isMaximized ? 'Exit full screen' : 'Full screen'}
|
|
176
|
+
aria-label={isMaximized ? 'Exit full screen' : 'Full screen'}
|
|
177
|
+
aria-pressed={isMaximized}
|
|
178
|
+
>
|
|
179
|
+
{isMaximized ? <Minimize size={14} /> : <Maximize size={14} />}
|
|
180
|
+
</button>
|
|
96
181
|
</div>
|
|
97
182
|
</div>
|
|
98
183
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'preact/hooks';
|
|
2
|
+
import { AlertTriangle, Loader2 } from 'lucide-preact';
|
|
2
3
|
import { FileIcon } from '@/client/components/common/file-icon';
|
|
3
4
|
import { CodeSurface } from '@/client/components/common/code-surface';
|
|
4
5
|
import { MarkdownRenderer } from '@/client/components/common/MarkdownRenderer';
|
|
@@ -6,7 +7,6 @@ import { useScrollbarFade, scrollbarFadeClass } from '@/client/hooks/ui/scrollba
|
|
|
6
7
|
import { EditorTabs } from '@/client/components/workspace/editor/Tabs';
|
|
7
8
|
import { EditorToolbar } from '@/client/components/workspace/editor/Toolbar';
|
|
8
9
|
import { ImageViewer } from '@/client/components/common/image-viewer';
|
|
9
|
-
import { getDefaultContent } from '@/shared/lib/code/editor-utils';
|
|
10
10
|
import { DiffPanel } from '@/client/components/workspace/diff-panel';
|
|
11
11
|
import { useSessionState } from '@/client/hooks/workspace/session-state';
|
|
12
12
|
import { useSessionStateContext } from '@/client/hooks/workspace/session-state/context';
|
|
@@ -19,6 +19,8 @@ interface EditorProps {
|
|
|
19
19
|
activeFileId: number | string | null;
|
|
20
20
|
onSelectFile: (id: number | string) => void;
|
|
21
21
|
onCloseFile: (id: number | string) => void;
|
|
22
|
+
/** Replace a diff tab with the plain editor tab for the same file. */
|
|
23
|
+
onConvertDiffToEditor: (id: number | string) => void;
|
|
22
24
|
refreshKey?: number;
|
|
23
25
|
onFileSaved?: () => void;
|
|
24
26
|
}
|
|
@@ -29,6 +31,7 @@ export function Editor({
|
|
|
29
31
|
activeFileId,
|
|
30
32
|
onSelectFile,
|
|
31
33
|
onCloseFile,
|
|
34
|
+
onConvertDiffToEditor,
|
|
32
35
|
refreshKey = 0,
|
|
33
36
|
onFileSaved
|
|
34
37
|
}: EditorProps) {
|
|
@@ -42,7 +45,10 @@ export function Editor({
|
|
|
42
45
|
const { isScrolling, handleScroll } = useScrollbarFade();
|
|
43
46
|
|
|
44
47
|
const editor = useFileEditor(activeFile ?? null, {
|
|
45
|
-
|
|
48
|
+
// A file that could not be read must report the failure, not substitute
|
|
49
|
+
// placeholder text — the mock sample used to be written straight over the
|
|
50
|
+
// real file by the next save.
|
|
51
|
+
reportSaveError: true,
|
|
46
52
|
onFileSaved,
|
|
47
53
|
});
|
|
48
54
|
|
|
@@ -96,7 +102,6 @@ export function Editor({
|
|
|
96
102
|
const currentContent = editor.content;
|
|
97
103
|
const isMd = activeFile?.name.endsWith('.md');
|
|
98
104
|
const isPreview = activeFile ? previewMode[activeFile.id] : false;
|
|
99
|
-
const toolbarSaveStatus = editor.saveStatus === 'error' ? 'idle' : editor.saveStatus;
|
|
100
105
|
|
|
101
106
|
const editorContainerClass = isMaximized
|
|
102
107
|
? 'fixed inset-0 z-50 flex flex-col bg-canvas'
|
|
@@ -119,11 +124,7 @@ export function Editor({
|
|
|
119
124
|
isStaged={Boolean(activeFile.diffStaged)}
|
|
120
125
|
root={activeFile.root}
|
|
121
126
|
repo={activeFile.repo || '.'}
|
|
122
|
-
onOpenInEditor={() =>
|
|
123
|
-
// Convert diff tab to regular editor tab
|
|
124
|
-
activeFile.isDiff = false;
|
|
125
|
-
onSelectFile(activeFile.id);
|
|
126
|
-
}}
|
|
127
|
+
onOpenInEditor={() => onConvertDiffToEditor(activeFile.id)}
|
|
127
128
|
onFileSaved={onFileSaved}
|
|
128
129
|
className="flex-1"
|
|
129
130
|
/>
|
|
@@ -131,11 +132,14 @@ export function Editor({
|
|
|
131
132
|
<>
|
|
132
133
|
<EditorToolbar
|
|
133
134
|
path={activeFile.path}
|
|
134
|
-
saveStatus={
|
|
135
|
+
saveStatus={editor.saveStatus}
|
|
135
136
|
wordWrap={wordWrap}
|
|
136
137
|
isMd={isMd}
|
|
137
138
|
isPreview={isPreview}
|
|
139
|
+
copied={editor.copied}
|
|
138
140
|
isImage={editor.isImage}
|
|
141
|
+
saveDisabled={Boolean(editor.loadError)}
|
|
142
|
+
isMaximized={isMaximized}
|
|
139
143
|
onSave={editor.saveNow}
|
|
140
144
|
onTogglePreview={togglePreview}
|
|
141
145
|
onToggleWordWrap={() => setWordWrap(!wordWrap)}
|
|
@@ -151,6 +155,20 @@ export function Editor({
|
|
|
151
155
|
// the scrolling text container — a wheel there would scroll the
|
|
152
156
|
// pane instead of zooming the image.
|
|
153
157
|
<ImageViewer src={editor.imageUrl} name={activeFile.name} className="flex-1 min-h-0" />
|
|
158
|
+
) : editor.isLoading ? (
|
|
159
|
+
<div className="flex-1 flex items-center justify-center gap-2 text-ink/40 text-xs font-mono">
|
|
160
|
+
<Loader2 size={14} className="animate-spin" />
|
|
161
|
+
<span>Loading file content…</span>
|
|
162
|
+
</div>
|
|
163
|
+
) : editor.loadError ? (
|
|
164
|
+
// A read that failed must say so. Without this the pane rendered
|
|
165
|
+
// an empty (or substituted) buffer, and the next save wrote it
|
|
166
|
+
// over the real file.
|
|
167
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-2 px-6 text-center">
|
|
168
|
+
<AlertTriangle size={20} className="text-error" />
|
|
169
|
+
<span className="text-xs text-error font-sans">Could not read {activeFile.name}</span>
|
|
170
|
+
<span className="text-[11px] text-ink/50 font-mono break-all">{editor.loadError}</span>
|
|
171
|
+
</div>
|
|
154
172
|
) : (
|
|
155
173
|
<div onScroll={handleScroll} className={`flex-1 overflow-auto bg-paper flex ${scrollbarFadeClass(isScrolling)}`}>
|
|
156
174
|
{(isMd && isPreview) ? (
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { TargetedMouseEvent } from 'preact';
|
|
2
|
-
import {
|
|
2
|
+
import { Brain, Check, Command, GripVertical, Star } from 'lucide-preact';
|
|
3
3
|
import type { AIModelOption } from '@/shared/types';
|
|
4
4
|
import { formatContextWindow } from '@/shared/lib/code/format';
|
|
5
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
5
6
|
|
|
6
7
|
interface ModelDropdownItemProps {
|
|
7
8
|
model: AIModelOption;
|
|
@@ -35,28 +36,17 @@ export function ModelDropdownItem({
|
|
|
35
36
|
);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return <Boxes size={13} className="text-emerald-500 flex-shrink-0" />;
|
|
50
|
-
case 'google':
|
|
51
|
-
case 'gemini':
|
|
52
|
-
return <Sparkles size={13} className="text-blue-400 flex-shrink-0" />;
|
|
53
|
-
case 'opencode':
|
|
54
|
-
return <Zap size={13} className="text-violet-400 flex-shrink-0" />;
|
|
55
|
-
case 'mistral':
|
|
56
|
-
return <Globe size={13} className="text-rose-400 flex-shrink-0" />;
|
|
57
|
-
default:
|
|
58
|
-
return <Sparkles size={13} className="text-ink/60 flex-shrink-0" />;
|
|
59
|
-
}
|
|
39
|
+
// The provider's own mark, keyed on the catalog's `providerIcon` first so a
|
|
40
|
+
// legacy key still resolves; unknown providers fall back to their initials.
|
|
41
|
+
return (
|
|
42
|
+
<ProviderIcon
|
|
43
|
+
icon={model.providerIcon}
|
|
44
|
+
slug={model.provider}
|
|
45
|
+
name={model.provider}
|
|
46
|
+
size={13}
|
|
47
|
+
className="text-ink/70"
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
60
50
|
};
|
|
61
51
|
|
|
62
52
|
return (
|
|
@@ -115,7 +115,7 @@ export function ModelDropdownPanel({
|
|
|
115
115
|
key={provider}
|
|
116
116
|
id={provider}
|
|
117
117
|
title={provider}
|
|
118
|
-
iconType=
|
|
118
|
+
iconType="provider"
|
|
119
119
|
models={pModels}
|
|
120
120
|
isCollapsed={!!collapsedSections[provider]}
|
|
121
121
|
onToggleCollapse={onToggleCollapse}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { TargetedMouseEvent } from 'preact';
|
|
2
|
-
import { ChevronDown, ChevronUp,
|
|
2
|
+
import { ChevronDown, ChevronUp, History, Star } from 'lucide-preact';
|
|
3
3
|
import type { AIModelOption } from '@/shared/types';
|
|
4
4
|
import { ModelDropdownItem } from '@/client/components/workspace/model-dropdown/Item';
|
|
5
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
5
6
|
import { modelKey } from '@/shared/lib/models/identity';
|
|
6
7
|
|
|
7
8
|
interface ModelDropdownSectionProps {
|
|
8
9
|
id: string;
|
|
9
10
|
title: string;
|
|
10
|
-
iconType?: 'star' | 'recent' | 'provider'
|
|
11
|
+
iconType?: 'star' | 'recent' | 'provider';
|
|
11
12
|
models: AIModelOption[];
|
|
12
13
|
isCollapsed: boolean;
|
|
13
14
|
onToggleCollapse: (id: string) => void;
|
|
@@ -49,8 +50,16 @@ export function ModelDropdownSection({
|
|
|
49
50
|
<div className="flex items-center space-x-1.5">
|
|
50
51
|
{iconType === 'star' && <Star size={12} className="fill-sky-400 text-sky-400" />}
|
|
51
52
|
{iconType === 'recent' && <History size={12} className="text-ink/60" />}
|
|
52
|
-
{iconType === '
|
|
53
|
-
|
|
53
|
+
{iconType === 'provider' && (
|
|
54
|
+
// The section's own provider, taken from a row rather than the
|
|
55
|
+
// uppercased group key, so the mark resolves off the real slug.
|
|
56
|
+
<ProviderIcon
|
|
57
|
+
icon={models[0]?.providerIcon}
|
|
58
|
+
slug={models[0]?.provider ?? id}
|
|
59
|
+
name={models[0]?.provider ?? id}
|
|
60
|
+
size={12}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
54
63
|
<span>{title}</span>
|
|
55
64
|
</div>
|
|
56
65
|
{isCollapsed ? (
|
|
@@ -5,6 +5,7 @@ import type { AIModelOption, ModelPreferences } from '@/shared/types';
|
|
|
5
5
|
import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
|
|
6
6
|
import { invalidateModelsCache } from '@/shared/lib/models/client';
|
|
7
7
|
import { ModelDropdownPanel } from '@/client/components/workspace/model-dropdown/Panel';
|
|
8
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
8
9
|
import { useModelCatalog } from '@/client/components/workspace/model-dropdown/use-catalog';
|
|
9
10
|
import { buildPickerGroups } from '@/client/components/workspace/model-dropdown/groups';
|
|
10
11
|
import { modelKey } from '@/shared/lib/models/identity';
|
|
@@ -229,10 +230,13 @@ export function ModelDropdown({
|
|
|
229
230
|
<span className="text-ink/60 flex-shrink-0">
|
|
230
231
|
{selectedModel.isCmdAgent ? (
|
|
231
232
|
<span className="font-mono text-[10px] font-bold text-ink/70">⌘</span>
|
|
232
|
-
) : selectedModel.provider.toLowerCase() === 'deepseek' ? (
|
|
233
|
-
<span>🐋</span>
|
|
234
233
|
) : (
|
|
235
|
-
<
|
|
234
|
+
<ProviderIcon
|
|
235
|
+
icon={selectedModel.providerIcon}
|
|
236
|
+
slug={selectedModel.provider}
|
|
237
|
+
name={selectedModel.provider}
|
|
238
|
+
size={12}
|
|
239
|
+
/>
|
|
236
240
|
)}
|
|
237
241
|
</span>
|
|
238
242
|
<span className="flex items-center space-x-1 min-w-0 max-w-[280px]">
|
|
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
|
2
2
|
import type { KeyboardEvent as ReactKeyboardEvent } from 'preact/compat';
|
|
3
3
|
import { Check, ChevronDown, Search } from 'lucide-preact';
|
|
4
4
|
import { statusLabel, type ProviderEntry, type UsageProviderId } from '@/client/components/settings/categories/usage-settings/providers';
|
|
5
|
+
import { ProviderIcon } from '@/client/components/common/provider-icon';
|
|
5
6
|
import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
|
|
6
7
|
|
|
7
8
|
interface ProviderSelectProps {
|
|
@@ -120,7 +121,8 @@ export function ProviderSelect({ providers, value, onChange }: ProviderSelectPro
|
|
|
120
121
|
title="Select usage provider"
|
|
121
122
|
className="flex w-full min-w-0 items-center justify-between gap-2 bg-paper border border-ink/15 rounded-lg px-3 py-1.5 text-xs text-ink hover:bg-ink/5 transition-colors"
|
|
122
123
|
>
|
|
123
|
-
<span className="flex min-w-0 items-
|
|
124
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
125
|
+
{selected && <ProviderIcon slug={selected.id} name={selected.name} size={13} className="text-ink/60" />}
|
|
124
126
|
<span className="truncate font-medium">{selected?.name ?? 'Select provider'}</span>
|
|
125
127
|
{selected && (
|
|
126
128
|
<span className="truncate text-[10px] text-ink/50">{statusLabel(selected)}</span>
|
|
@@ -190,10 +192,13 @@ export function ProviderSelect({ providers, value, onChange }: ProviderSelectPro
|
|
|
190
192
|
index === activeIndex ? 'bg-ink/5' : 'hover:bg-ink/5'
|
|
191
193
|
}`}
|
|
192
194
|
>
|
|
193
|
-
<span className="min-w-0">
|
|
194
|
-
<
|
|
195
|
-
<span className="
|
|
196
|
-
|
|
195
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
196
|
+
<ProviderIcon slug={provider.id} name={provider.name} size={14} className="text-ink/60" />
|
|
197
|
+
<span className="min-w-0">
|
|
198
|
+
<span className="block truncate font-medium text-ink">{provider.name}</span>
|
|
199
|
+
<span className="block truncate text-[10px] text-ink/50">
|
|
200
|
+
{statusLabel(provider)} · {provider.hint}
|
|
201
|
+
</span>
|
|
197
202
|
</span>
|
|
198
203
|
</span>
|
|
199
204
|
{provider.id === value && <Check size={12} className="flex-shrink-0 text-ink" />}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useEffect } from 'preact/hooks';
|
|
1
2
|
import { AlertCircle, RefreshCw } from 'lucide-preact';
|
|
2
3
|
import { ProviderDetail } from '@/client/components/settings/categories/usage-settings/ProviderDetail';
|
|
3
4
|
import { buildProviders, type UsageProviderId } from '@/client/components/settings/categories/usage-settings/providers';
|
|
@@ -14,19 +15,30 @@ interface UsagePanelProps {
|
|
|
14
15
|
active?: boolean;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
/** Right-panel Usage: the selected provider's
|
|
18
|
+
/** Right-panel Usage: the selected provider's quota and balance via a dropdown. */
|
|
18
19
|
export function UsagePanel({ className = '', active = true }: UsagePanelProps) {
|
|
19
20
|
const { report, isLoading, error, reload } = useUsageReport();
|
|
20
21
|
const [selectedProviderId, setSelectedProviderId] = useSessionState<UsageProviderId>(
|
|
21
22
|
'usage.selectedProviderId',
|
|
22
|
-
'
|
|
23
|
+
'',
|
|
23
24
|
);
|
|
24
25
|
|
|
25
|
-
//
|
|
26
|
-
// the
|
|
27
|
-
|
|
26
|
+
// Derived before every early return: a hook must not sit behind a conditional
|
|
27
|
+
// return, or the hook count changes between the loading and loaded renders.
|
|
28
|
+
const providers = report ? buildProviders(report) : [];
|
|
29
|
+
const selected = providers.find((provider) => provider.id === selectedProviderId) ?? providers[0];
|
|
30
|
+
|
|
31
|
+
// Auto refresh: keep quota current without a manual reload. Silent — the
|
|
32
|
+
// spinner stays reserved for the user's own refresh button. Polls only while
|
|
33
|
+
// the panel is actually visible.
|
|
28
34
|
usePanelRefresh(() => reload({ silent: true }), active);
|
|
29
35
|
|
|
36
|
+
// A provider can disappear from the report (its key was removed) or a new one
|
|
37
|
+
// can take the first slot; keep the persisted selection valid.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (selected && selected.id !== selectedProviderId) setSelectedProviderId(selected.id);
|
|
40
|
+
}, [selected, selectedProviderId, setSelectedProviderId]);
|
|
41
|
+
|
|
30
42
|
if (error && !report) {
|
|
31
43
|
return (
|
|
32
44
|
<div className={`flex h-full w-full flex-col items-center justify-center gap-2 p-8 text-center text-xs ${className}`}>
|
|
@@ -51,8 +63,6 @@ export function UsagePanel({ className = '', active = true }: UsagePanelProps) {
|
|
|
51
63
|
);
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
const providers = buildProviders(report);
|
|
55
|
-
|
|
56
66
|
return (
|
|
57
67
|
<div className={`flex h-full w-full min-w-0 min-h-0 flex-col overflow-hidden bg-paper text-xs text-ink ${className}`}>
|
|
58
68
|
<div className="px-3.5 py-2.5 border-b border-ink/10 flex items-center justify-between gap-3 flex-shrink-0 min-w-0">
|
|
@@ -69,7 +79,7 @@ export function UsagePanel({ className = '', active = true }: UsagePanelProps) {
|
|
|
69
79
|
</div>
|
|
70
80
|
<button
|
|
71
81
|
type="button"
|
|
72
|
-
onClick={() => reload()}
|
|
82
|
+
onClick={() => reload({ force: true })}
|
|
73
83
|
disabled={isLoading}
|
|
74
84
|
className="p-1.5 bg-paper border border-ink/15 rounded-md hover:bg-ink/5 text-ink/80 transition-colors disabled:opacity-50 flex-shrink-0"
|
|
75
85
|
title="Refresh usage"
|
|
@@ -78,20 +88,28 @@ export function UsagePanel({ className = '', active = true }: UsagePanelProps) {
|
|
|
78
88
|
</button>
|
|
79
89
|
</div>
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
<div className="
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
{providers.length > 0 && (
|
|
92
|
+
<div className="px-3.5 py-2.5 border-b border-ink/10 flex-shrink-0 min-w-0">
|
|
93
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
94
|
+
<span className="text-[11px] font-semibold text-ink/60 flex-shrink-0">Provider</span>
|
|
95
|
+
<ProviderSelect
|
|
96
|
+
providers={providers}
|
|
97
|
+
value={selected?.id ?? ''}
|
|
98
|
+
onChange={setSelectedProviderId}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
89
101
|
</div>
|
|
90
|
-
|
|
102
|
+
)}
|
|
91
103
|
|
|
92
104
|
<div className="flex-1 min-w-0 min-h-0 w-full overflow-y-auto scrollbar-overlay-container scrollbar-overlay-static p-3.5 space-y-4">
|
|
93
105
|
{error && <p className="text-error text-xs">{error}</p>}
|
|
94
|
-
|
|
106
|
+
{selected ? (
|
|
107
|
+
<ProviderDetail summary={selected.summary} />
|
|
108
|
+
) : (
|
|
109
|
+
<p className="text-[11px] text-ink/50 leading-relaxed">
|
|
110
|
+
No provider credentials detected. Add an API key in Settings → Providers and it will appear here.
|
|
111
|
+
</p>
|
|
112
|
+
)}
|
|
95
113
|
</div>
|
|
96
114
|
</div>
|
|
97
115
|
);
|
|
@@ -42,7 +42,7 @@ export function getSampleDialogueSession(): {
|
|
|
42
42
|
id: '1a6cb095',
|
|
43
43
|
role: 'ai',
|
|
44
44
|
content:
|
|
45
|
-
'Elysia + Bun app (OMPChamber). Top-level contents:\n\n- **App source** — `src/` (client, server, shared)\n- **Config** — `package.json`, `tsconfig.json`, `
|
|
45
|
+
'Elysia + Bun app (OMPChamber). Top-level contents:\n\n- **App source** — `src/` (client, server, shared)\n- **Config** — `package.json`, `tsconfig.json`, `bunfig.toml`, `bun.lock`, `AGENTS.md`, `DESIGN.md`\n- **Static/PWA** — `public/` (icons, manifest, service worker)\n- **DB** — `workspace.db` (112 KB)\n- **Misc** — `notes/` (2 planning docs)\n\nRoot has no stray source files; project is cleanly under `src/`.',
|
|
46
46
|
date: 'Sep 9, 04:33 AM',
|
|
47
47
|
thinking: {
|
|
48
48
|
thought: 'Brief listing.',
|