feat(desktop): add model change notices (#43288)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
This commit is contained in:
usrnk1
2026-08-24 02:17:35 +02:00
committed by GitHub
parent f9bc948912
commit d3e5d6d268
13 changed files with 178 additions and 133 deletions
-36
View File
@@ -3218,12 +3218,6 @@
"@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="],
"@vercel/cli-config": ["@vercel/cli-config@0.2.3", "", { "dependencies": { "xdg-app-paths": "5", "zod": "4.1.11" } }, "sha512-Ggh0Wmi92TUkUexmSUPkkDtvJmbjUr7IvF5T3FkSsWrXXs3GFzOujfxFpECdJZpux1JG4SWDv9BT4w++TDgD6A=="],
"@vercel/cli-exec": ["@vercel/cli-exec@1.0.1", "", { "dependencies": { "execa": "5.1.1" } }, "sha512-g9XerViJ/paZujufXYcu5XYI2vU2rtB4sgdpjUHde5RnOkdmpu0ngH46LCFGHoPXO/C+qDPSczIHIRN+8Q2YKQ=="],
"@vercel/functions": ["@vercel/functions@3.9.3", "", { "dependencies": { "@vercel/oidc": "3.8.4" }, "peerDependencies": { "@aws-sdk/credential-provider-web-identity": "*", "ws": ">=8" }, "optionalPeers": ["@aws-sdk/credential-provider-web-identity", "ws"] }, "sha512-cbzTdASCZDnufrABc8oO00e/FqlqFFSdld+iGZPhrWBDHP4Pu8ESKKYIzASqYvbYYUIWujBvEa5LLCcZzm7WEw=="],
"@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
@@ -3982,8 +3976,6 @@
"eventsource-parser": ["eventsource-parser@3.1.1", "", {}, "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ=="],
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
"exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
"expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="],
@@ -4258,8 +4250,6 @@
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
@@ -4624,8 +4614,6 @@
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"mermaid": ["mermaid@11.16.1", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.20", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g=="],
@@ -4710,8 +4698,6 @@
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
@@ -4828,8 +4814,6 @@
"npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="],
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@@ -4854,8 +4838,6 @@
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="],
"oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="],
@@ -4870,8 +4852,6 @@
"opentui-spinner": ["opentui-spinner@0.0.7", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.3.4", "@opentui/react": "^0.3.4", "@opentui/solid": "^0.3.4", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-nPzwAvJG+y9rVEwwHLHqbsMzLnIk2zw+F9LqwA7aYJvpM5gsrKC2rrGi36A+tZpA+1RnWxXeWEgVZMchnaH18Q=="],
"os-paths": ["os-paths@4.4.0", "", {}, "sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg=="],
"own-keys": ["own-keys@1.0.2", "", { "dependencies": { "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg=="],
"oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="],
@@ -5454,8 +5434,6 @@
"strip-comments": ["strip-comments@2.0.1", "", {}, "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw=="],
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
"stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="],
@@ -5848,10 +5826,6 @@
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
"xdg-app-paths": ["xdg-app-paths@5.5.1", "", { "dependencies": { "os-paths": "^4.0.1", "xdg-portable": "^7.2.0" } }, "sha512-hI3flOB4PLZIy5prbtTpirobtPE2ZtZ52szO+2mM9Efp6ErM398La+C1lIpNWDfNoQk+6Lsi6nMcCwVB7pxeMQ=="],
"xdg-portable": ["xdg-portable@7.3.0", "", { "dependencies": { "os-paths": "^4.0.1" } }, "sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw=="],
"xml-naming": ["xml-naming@0.3.0", "", {}, "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ=="],
"xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
@@ -6424,10 +6398,6 @@
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
"@vercel/cli-config/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@vercel/functions/@vercel/oidc": ["@vercel/oidc@3.8.4", "", { "dependencies": { "@vercel/cli-config": "0.2.3", "@vercel/cli-exec": "1.0.1", "jose": "^5.9.6" } }, "sha512-FGNvVZ5pgX9FaBqkPt6VkYFZ6bWAMDzYi7nxW+1Xt+Z4fn5PuTULVwsxjKc+0uKhysyWBQmvsmM50Oh6C2/oMA=="],
"@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@vitejs/plugin-react/vite": ["vite@7.1.11", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg=="],
@@ -6566,10 +6536,6 @@
"esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
"execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
@@ -7326,8 +7292,6 @@
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"@vercel/functions/@vercel/oidc/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
@@ -899,8 +899,26 @@ function provider() {
name: "OpenCode",
models: { "claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6", limit: { context: 200_000 } } },
},
{
id: "company-gateway",
name: "Company Gateway",
models: {
"fast-nano": {
id: "fast-nano",
api: { id: "openai/gpt-5.4-nano" },
name: "GPT-5.4 nano",
limit: { context: 128_000 },
},
"long-context": {
id: "long-context",
api: { id: "company/long-context" },
name: "Company Gateway Extra Long Context Model for Narrow Timeline Layouts",
limit: { context: 128_000 },
},
},
},
],
connected: ["opencode"],
connected: ["opencode", "company-gateway"],
default: { providerID: "opencode", modelID: "claude-opus-4-6" },
}
}
@@ -250,6 +250,43 @@ test.describe("session timeline projection", () => {
expect(rows).toEqual(["AssistantPart", "TurnDivider", "AssistantPart"])
})
test("renders aliased and long custom model notices", async ({ page }) => {
const shortName = "GPT-5.4 nano"
const longName = "Company Gateway Extra Long Context Model for Narrow Timeline Layouts"
await setupTimeline(page, {
viewport: { width: 420, height: 700 },
sessionMessages: [
{
id: "msg_model_fast_nano",
type: "model-switched",
time: { created: 1700000000000 },
model: { providerID: "company-gateway", id: "fast-nano", variant: "xhigh" },
},
{
id: "msg_model_long_context",
type: "model-switched",
time: { created: 1700000001000 },
model: { providerID: "company-gateway", id: "long-context" },
},
userMessage(),
assistantMessage(),
],
})
const shortNotice = page.locator('[data-slot="session-timeline-notice"]').filter({ hasText: shortName })
const longNotice = page.locator('[data-slot="session-timeline-notice"]').filter({ hasText: longName })
await expect(shortNotice).toBeVisible()
await expect(shortNotice.getByText(`Switched to ${shortName}`, { exact: true })).toBeVisible()
await expect(shortNotice.locator('[data-slot="session-timeline-notice-variant"]')).toHaveText("xhigh")
await expect(page.getByText("fast-nano", { exact: true })).toHaveCount(0)
await expect(shortNotice.locator('[data-component="provider-icon"]')).toBeVisible()
await expect(longNotice).toBeVisible()
await expect(longNotice.locator('[data-component="provider-icon"]')).toBeVisible()
await expect(longNotice.locator('[data-slot="session-timeline-notice-variant"]')).toHaveCount(0)
await expect(longNotice.locator("[title]")).toHaveAttribute("title", `Switched to ${longName}`)
await expect.poll(() => longNotice.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true)
})
test("renders user image, file attachment, file reference, and agent reference", async ({ page }) => {
const text = "Use @explore with @src/a.ts and inspect the attachments"
const parts: PartSeed<"user">[] = [
+1 -1
View File
@@ -501,7 +501,7 @@ function currentModels(value: unknown) {
return [
{
id: model.id,
modelID: model.id,
modelID: record(model.api) && typeof model.api.id === "string" ? model.api.id : model.id,
providerID: provider.id,
name: model.name,
capabilities: { tools: true, input: ["text"], output: ["text"] },
@@ -1,6 +1,7 @@
import { DataProvider } from "@opencode-ai/session-ui/context"
import { useNavigate, useParams } from "@solidjs/router"
import { createMemo, type ParentProps } from "solid-js"
import { useProviders } from "@/providers/catalog/providers"
import { LocalProvider } from "@/providers/models/selection"
import type { ServerConnection } from "@/runtime/server/registry"
import { sessionHref } from "@/shell/routes/session"
@@ -30,7 +31,11 @@ export function SessionUIProvider(
await data.session.sync(sessionID).catch(() => undefined)
navigate(href(sessionID))
}
const providers = useProviders(directory)
const sessionUIData = createMemo(() => ({
provider: providers.ready()
? { all: providers.all(), default: providers.default(), connected: providers.connected().map((item) => item.id) }
: undefined,
session: data.session.list(),
session_status: Object.fromEntries(
data.session
@@ -263,33 +263,6 @@
}
}
[data-component="compaction-part"] {
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
[data-slot="compaction-part-divider"] {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 0;
width: 100%;
}
[data-slot="compaction-part-line"] {
flex: 1 1 auto;
height: 1px;
background: var(--v2-border-border-base);
}
[data-slot="compaction-part-label"] {
flex: 0 0 auto;
white-space: nowrap;
text-align: center;
}
}
[data-component="reasoning-part"] {
width: 100%;
color: var(--v2-text-text-muted);
@@ -1,7 +1,2 @@
export {
AssistantReasoningContent,
AssistantTextContent,
CurrentUserMessageDisplay,
MessageDivider,
} from "../message/message-content"
export { AssistantReasoningContent, AssistantTextContent, CurrentUserMessageDisplay } from "../message/message-content"
export { CurrentContextToolGroup, SessionShellMessage, ToolDisplay } from "../tools/tool-renderer"
@@ -0,0 +1,33 @@
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Show } from "solid-js"
export function TimelineSeparator(props: { label: string; providerID?: string; variant?: string }) {
return (
<div class="flex h-8 w-full items-center gap-3 text-v2-text-text-faint">
<span class="h-px min-w-0 flex-1 bg-v2-border-border-strong" />
<span class="flex min-w-0 items-center gap-1 text-[13px] font-[440] leading-text-compact tracking-[-0.04px]">
<Show when={props.providerID}>
{(providerID) => <ProviderIcon id={providerID()} class="text-v2-icon-icon-faint" aria-hidden="true" />}
</Show>
<span class="flex min-w-0 items-center gap-1.5">
<bdi dir="auto" class="truncate" title={props.label}>
{props.label}
</bdi>
<Show when={props.variant && props.variant !== "default" ? props.variant : undefined}>
{(variant) => (
<>
<span class="flex size-1.5 shrink-0 items-center justify-center" aria-hidden="true">
<span class="size-[2.25px] rounded-full bg-current" />
</span>
<span data-slot="session-timeline-notice-variant" class="shrink-0 capitalize">
{variant()}
</span>
</>
)}
</Show>
</span>
</span>
<span class="h-px min-w-0 flex-1 bg-v2-border-border-strong" />
</div>
)
}
@@ -10,7 +10,6 @@ import { CurrentContextToolGroup, CurrentFileToolGroup, ToolDisplay } from "../t
import { currentToolError, currentToolInput, currentToolMetadata, currentToolOutput } from "./current-tool-state"
export type { SessionUserActions, SessionUserComment } from "../actions"
export { MessageDivider } from "./message-content"
export { SessionShellMessage } from "../tools/tool-renderer"
export { currentContentDefaultOpen } from "./current-tool-state"
@@ -8,6 +8,7 @@ import { ImagePreview } from "@opencode-ai/ui/image-preview"
import { getFilename } from "@opencode-ai/util/path"
import { AttachmentCard } from "./attachment-card"
import { CommentCard } from "./comment-card"
import { TimelineSeparator } from "../components/timeline-separator"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Icon } from "@opencode-ai/ui/icon"
@@ -371,20 +372,6 @@ function CurrentHighlightedText(props: {
type HighlightSegment = { text: string; type?: "file" | "agent" }
export function MessageDivider(props: { label: string }) {
return (
<div data-component="compaction-part">
<div data-slot="compaction-part-divider">
<span data-slot="compaction-part-line" />
<span data-slot="compaction-part-label" class="text-12-regular text-text-weak">
{props.label}
</span>
<span data-slot="compaction-part-line" />
</div>
</div>
)
}
export function SessionCompactionMessage(props: { message: SessionMessageCompaction; error: string }) {
const i18n = useI18n()
const summary = () => (props.message.status === "failed" ? "" : props.message.summary)
@@ -395,7 +382,9 @@ export function SessionCompactionMessage(props: { message: SessionMessageCompact
return (
<div data-component="session-compaction-message">
<MessageDivider label={i18n.t("ui.messagePart.compaction")} />
<div class="py-2">
<TimelineSeparator label={i18n.t("ui.messagePart.compaction")} />
</div>
<Show when={summary().trim()}>
<div data-component="text-part" data-timeline-part-id={props.message.id}>
<div data-slot="text-part-body">
@@ -12,8 +12,9 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
import { For, Show, createMemo, type Accessor, type JSX } from "solid-js"
import type { SessionUserActions, SessionUserComment } from "../actions"
import { BasicTool } from "../components/basic-tool"
import { useData } from "../context"
import { TimelineSeparator } from "../components/timeline-separator"
import {
MessageDivider,
SessionAssistantContent,
SessionContextToolGroup,
SessionFileToolGroup,
@@ -52,6 +53,7 @@ export function createSessionTimelineRowRenderer(input: {
anchor?: (messageID: string) => string | undefined
}) {
const i18n = useI18n()
const data = useData()
const workingTurn = (messageID: string) =>
input.status().type !== "idle" && input.projection.activeMessageID() === messageID
const duration = (messageID: string) => {
@@ -183,11 +185,7 @@ export function createSessionTimelineRowRenderer(input: {
label: i18n.t("ui.tool.agent.default"),
data: message.previous ? `${message.previous}${message.agent}` : message.agent,
}
if (message.type === "model-switched")
return {
label: i18n.t("ui.sessionTimeline.notice.model"),
data: `${message.model.providerID}/${message.model.id}`,
}
if (message.type === "model-switched") return undefined
if (message.type === "skill") return { label: i18n.t("ui.tool.skill"), data: message.name }
if (message.type === "system") {
const prefix = "Instructions updated: "
@@ -328,6 +326,18 @@ export function createSessionTimelineRowRenderer(input: {
const value = message()
return value?.type === "location-switched" ? value : undefined
})
const model = createMemo(() => {
const value = message()
if (value?.type !== "model-switched") return undefined
const match = data.store.provider?.all?.get(value.model.providerID)
return {
providerID: value.model.providerID,
variant: value.model.variant,
label: i18n.t("ui.sessionTimeline.notice.modelSwitched", {
model: match?.models?.[value.model.id]?.name ?? value.model.id,
}),
}
})
const content = createMemo(() => {
const value = message()
return value ? notice(value) : undefined
@@ -346,50 +356,69 @@ export function createSessionTimelineRowRenderer(input: {
<Show
when={moved()}
fallback={
<Show when={content()}>
{(content) => (
<Show
when={content().items?.length}
fallback={
<div
data-slot="session-timeline-notice"
class={`w-full pt-3 pb-1 text-13-regular text-text-weak ${padding()}`}
<Show
when={model()}
fallback={
<Show when={content()}>
{(content) => (
<Show
when={content().items?.length}
fallback={
<div
data-slot="session-timeline-notice"
class={`w-full pt-3 pb-1 text-13-regular text-text-weak ${padding()}`}
>
<bdi dir="auto" class="text-13-medium">
{content().label}
</bdi>
<Show when={content().data}>
{(data) => (
<span>
{" "}
· <bdi dir="auto">{data()}</bdi>
</span>
)}
</Show>
</div>
}
>
<bdi dir="auto" class="text-13-medium">
{content().label}
</bdi>
<Show when={content().data}>
{(data) => (
<span>
{" "}
· <bdi dir="auto">{data()}</bdi>
</span>
)}
</Show>
</div>
}
>
<div data-slot="session-timeline-notice" class={`w-full py-1 ${padding()}`}>
<div class="flex min-h-5 min-w-0 items-center gap-2 overflow-hidden">
<bdi
dir="auto"
class="shrink-0 text-[13px] font-[530] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint"
>
{content().label}
</bdi>
<For each={content().items}>
{(item) => (
<div data-slot="session-timeline-notice" class={`w-full py-1 ${padding()}`}>
<div class="flex min-h-5 min-w-0 items-center gap-2 overflow-hidden">
<bdi
dir="auto"
class="min-w-0 truncate text-[13px] font-[440] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint"
class="shrink-0 text-[13px] font-[530] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint"
>
{item}
{content().label}
</bdi>
)}
</For>
</div>
</div>
<For each={content().items}>
{(item) => (
<bdi
dir="auto"
class="min-w-0 truncate text-[13px] font-[440] leading-text-compact tracking-[-0.04px] text-v2-text-text-faint"
>
{item}
</bdi>
)}
</For>
</div>
</div>
</Show>
)}
</Show>
}
>
{(model) => (
<div
data-slot="session-timeline-notice"
data-type="model-switched"
class={`w-full py-2 ${padding()}`}
>
<TimelineSeparator
label={model().label}
providerID={model().providerID}
variant={model().variant}
/>
</div>
)}
</Show>
}
@@ -430,7 +459,9 @@ export function createSessionTimelineRowRenderer(input: {
<Frame row={current()}>
<div data-slot="session-turn-message-container" class={`w-full ${padding()}`}>
<div data-slot="session-turn-compaction">
<MessageDivider label={i18n.t("ui.message.interrupted")} />
<div class="py-2">
<TimelineSeparator label={i18n.t("ui.message.interrupted")} />
</div>
</div>
</div>
</Frame>
+1 -1
View File
@@ -9,7 +9,7 @@ export type ProviderIconProps = JSX.SVGElementTags["svg"] & {
export const ProviderIcon: Component<ProviderIconProps> = (props) => {
const [local, rest] = splitProps(props, ["id", "class", "classList"])
const resolved = createMemo(() => (iconNames.includes(local.id as IconName) ? local.id : "session.synthetic"))
const resolved = createMemo(() => (iconNames.includes(local.id as IconName) ? local.id : "synthetic"))
return (
<svg
data-component="provider-icon"
+1
View File
@@ -211,6 +211,7 @@ const source = {
"ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
"ui.message.interrupted": "Interrupted",
"ui.sessionTimeline.notice.model": "Model",
"ui.sessionTimeline.notice.modelSwitched": "Switched to {{model}}",
"ui.sessionTimeline.notice.movedTo": "Moved to",
"ui.sessionTimeline.notice.movedTooltip": "Session working directory changed",
"ui.sessionTimeline.notice.failed": "{{actor}} failed",