mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
wip: migrate session-timeline component
This commit is contained in:
parent
bfab6a2e1a
commit
d5eb2b0d68
29 changed files with 1094 additions and 885 deletions
38
bun.lock
38
bun.lock
|
|
@ -410,8 +410,8 @@
|
|||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.4.4",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.3",
|
||||
"@solidjs/start": "1.1.0",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "1.2.0",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
|
|
@ -425,14 +425,14 @@
|
|||
"hono-openapi": "1.1.1",
|
||||
"luxon": "3.6.1",
|
||||
"remeda": "2.26.0",
|
||||
"solid-js": "1.9.9",
|
||||
"solid-js": "1.9.10",
|
||||
"solid-list": "0.3.0",
|
||||
"tailwindcss": "4.1.11",
|
||||
"typescript": "5.8.2",
|
||||
"ulid": "3.0.1",
|
||||
"virtua": "0.42.3",
|
||||
"vite": "7.1.4",
|
||||
"vite-plugin-solid": "2.11.8",
|
||||
"vite-plugin-solid": "2.11.10",
|
||||
"zod": "4.1.8",
|
||||
},
|
||||
"packages": {
|
||||
|
|
@ -1418,9 +1418,9 @@
|
|||
|
||||
"@solidjs/meta": ["@solidjs/meta@0.29.4", "", { "peerDependencies": { "solid-js": ">=1.8.4" } }, "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g=="],
|
||||
|
||||
"@solidjs/router": ["@solidjs/router@0.15.3", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw=="],
|
||||
"@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="],
|
||||
|
||||
"@solidjs/start": ["@solidjs/start@1.1.0", "", { "dependencies": { "@tanstack/server-functions-plugin": "^1.99.5", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.3" } }, "sha512-7MNhNVt8uF7tdvLkvJhj4357vg3Ha+yqJP8XhQ6IbSZbsyk/xMkYmfc1h6w4GWiWZ5tn1DvS1uqGXjLFbKRy6g=="],
|
||||
"@solidjs/start": ["@solidjs/start@1.2.0", "", { "dependencies": { "@tanstack/server-functions-plugin": "1.121.21", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "cookie-es": "^2.0.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.7" } }, "sha512-SRv1g3R+4sxZnxCBPK1IedtLKsPhPJ7W/Yv4xEHjM4jJGPWi3ed35/yd0D5zhRK0C7zJIkZKbhnR/S3g8JUD5w=="],
|
||||
|
||||
"@speed-highlight/core": ["@speed-highlight/core@1.2.12", "", {}, "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA=="],
|
||||
|
||||
|
|
@ -1462,11 +1462,11 @@
|
|||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
|
||||
|
||||
"@tanstack/directive-functions-plugin": ["@tanstack/directive-functions-plugin@1.139.0", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.27.7", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@tanstack/router-utils": "1.139.0", "babel-dead-code-elimination": "^1.0.10", "pathe": "^2.0.3", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "vite": ">=6.0.0 || >=7.0.0" } }, "sha512-qLGxldnWa0pp/siZEFEYDU+eB/j40bd1V3IuTzP0sFnrYi11Ldx1yVkOruDKUbO1WM0o+OlPhp22Q1h+LMdDMA=="],
|
||||
"@tanstack/directive-functions-plugin": ["@tanstack/directive-functions-plugin@1.121.21", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/router-utils": "^1.121.21", "babel-dead-code-elimination": "^1.0.10", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "vite": ">=6.0.0" } }, "sha512-B9z/HbF7gJBaRHieyX7f2uQ4LpLLAVAEutBZipH6w+CYD6RHRJvSVPzECGHF7icFhNWTiJQL2QR6K07s59yzEw=="],
|
||||
|
||||
"@tanstack/router-utils": ["@tanstack/router-utils@1.139.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.5", "@babel/preset-typescript": "^7.27.1", "ansis": "^4.1.0", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-jT7D6NimWqoFSkid4vCno8gvTyfL1+NHpgm3es0B2UNhKKRV3LngOGilm1m6v8Qvk/gy6Fh/tvB+s+hBl6GhOg=="],
|
||||
|
||||
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.139.0", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.27.7", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@tanstack/directive-functions-plugin": "1.139.0", "babel-dead-code-elimination": "^1.0.9", "tiny-invariant": "^1.3.3" } }, "sha512-IpNFiCoy2YU6gY/4lCKIVlFyU67ltlcUMGcdnrevqOgq20AbMyeLbbBVo9tAA3TkHK9F+9Hd7DqGXsup2pmBLg=="],
|
||||
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.121.21", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/directive-functions-plugin": "1.121.21", "babel-dead-code-elimination": "^1.0.9", "tiny-invariant": "^1.3.3" } }, "sha512-a05fzK+jBGacsSAc1vE8an7lpBh4H0PyIEcivtEyHLomgSeElAJxm9E2It/0nYRZ5Lh23m0okbhzJNaYWZpAOg=="],
|
||||
|
||||
"@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="],
|
||||
|
||||
|
|
@ -1552,7 +1552,7 @@
|
|||
|
||||
"@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
|
||||
|
||||
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
|
||||
"@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
||||
|
||||
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
||||
|
||||
|
|
@ -1898,7 +1898,7 @@
|
|||
|
||||
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||
|
||||
"cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
|
||||
"cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
|
||||
|
||||
|
|
@ -3274,7 +3274,7 @@
|
|||
|
||||
"smol-toml": ["smol-toml@1.5.2", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="],
|
||||
|
||||
"solid-js": ["solid-js@1.9.9", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA=="],
|
||||
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
|
||||
|
||||
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
|
||||
|
||||
|
|
@ -3590,7 +3590,7 @@
|
|||
|
||||
"vite-plugin-icons-spritesheet": ["vite-plugin-icons-spritesheet@3.0.1", "", { "dependencies": { "chalk": "^5.4.1", "glob": "^11.0.1", "node-html-parser": "^7.0.1", "tinyexec": "^0.3.2" }, "peerDependencies": { "vite": ">=5.2.0" } }, "sha512-Cr0+Z6wRMwSwKisWW9PHeTjqmQFv0jwRQQMc3YgAhAgZEe03j21el0P/CA31KN/L5eiL1LhR14VTXl96LetonA=="],
|
||||
|
||||
"vite-plugin-solid": ["vite-plugin-solid@2.11.8", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-hFrCxBfv3B1BmFqnJF4JOCYpjrmi/zwyeKjcomQ0khh8HFyQ8SbuBWQ7zGojfrz6HUOBFrJBNySDi/JgAHytWg=="],
|
||||
"vite-plugin-solid": ["vite-plugin-solid@2.11.10", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||
|
||||
|
|
@ -3924,11 +3924,11 @@
|
|||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tanstack/directive-functions-plugin/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
"@tanstack/directive-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
|
||||
"@tanstack/router-utils/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
||||
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
|
||||
"@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
|
|
@ -4048,6 +4048,8 @@
|
|||
|
||||
"gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
|
||||
|
||||
"h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
|
||||
|
||||
"handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
|
||||
|
|
@ -4092,8 +4094,6 @@
|
|||
|
||||
"nitropack/c12": ["c12@3.3.2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="],
|
||||
|
||||
"nitropack/cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
|
||||
|
||||
"nitropack/globby": ["globby@15.0.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", "ignore": "^7.0.5", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw=="],
|
||||
|
||||
"nitropack/h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="],
|
||||
|
|
@ -4256,8 +4256,6 @@
|
|||
|
||||
"yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||
|
||||
"youch/cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
|
||||
|
||||
"zod-to-json-schema/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
|
@ -4664,6 +4662,8 @@
|
|||
|
||||
"listhen/@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
|
||||
|
||||
"listhen/h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
|
||||
|
||||
"miniflare/sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||
|
|
@ -4722,6 +4722,8 @@
|
|||
|
||||
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"unstorage/h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
|
||||
|
||||
"wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
||||
|
||||
"wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -29,9 +29,6 @@
|
|||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.4.4",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.3",
|
||||
"@solidjs/start": "1.1.0",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"ai": "5.0.97",
|
||||
|
|
@ -43,12 +40,15 @@
|
|||
"@typescript/native-preview": "7.0.0-dev.20251014.1",
|
||||
"zod": "4.1.8",
|
||||
"remeda": "2.26.0",
|
||||
"solid-js": "1.9.9",
|
||||
"solid-list": "0.3.0",
|
||||
"tailwindcss": "4.1.11",
|
||||
"virtua": "0.42.3",
|
||||
"vite": "7.1.4",
|
||||
"vite-plugin-solid": "2.11.8"
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "1.2.0",
|
||||
"solid-js": "1.9.10",
|
||||
"vite-plugin-solid": "2.11.10"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useLocal, type LocalFile } from "@/context/local"
|
||||
import { FileIcon, Tooltip } from "@opencode-ai/ui"
|
||||
import { Collapsible } from "@/ui"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { For, Match, Switch, Show, type ComponentProps, type ParentProps } from "solid-js"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { Button, FileIcon, Icon, IconButton, Select, SelectDialog, Tooltip } from "@opencode-ai/ui"
|
||||
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
||||
import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
|
|
@ -10,6 +9,13 @@ import { ContentPart, DEFAULT_PROMPT, isPromptEqual, Prompt, useSession } from "
|
|||
import { useSDK } from "@/context/sdk"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { SelectDialog } from "@opencode-ai/ui/select-dialog"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Select } from "@opencode-ai/ui/select"
|
||||
|
||||
interface PromptInputProps {
|
||||
class?: string
|
||||
|
|
@ -183,8 +189,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
const range = selection.getRangeAt(0)
|
||||
|
||||
if (atMatch) {
|
||||
let node: Node | null = range.startContainer
|
||||
let offset = range.startOffset
|
||||
// let node: Node | null = range.startContainer
|
||||
// let offset = range.startOffset
|
||||
let runningLength = 0
|
||||
|
||||
const walker = document.createTreeWalker(editorRef, NodeFilter.SHOW_TEXT, null)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import "@/index.css"
|
|||
import { render } from "solid-js/web"
|
||||
import { Router, Route, Navigate } from "@solidjs/router"
|
||||
import { MetaProvider } from "@solidjs/meta"
|
||||
import { Fonts, MarkedProvider } from "@opencode-ai/ui"
|
||||
import { Fonts } from "@opencode-ai/ui/fonts"
|
||||
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||
import { GlobalSyncProvider, useGlobalSync } from "./context/global-sync"
|
||||
import Layout from "@/pages/layout"
|
||||
import DirectoryLayout from "@/pages/directory-layout"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useGlobalSync } from "@/context/global-sync"
|
|||
import { base64Encode, getFilename } from "@/utils"
|
||||
import { For } from "solid-js"
|
||||
import { A } from "@solidjs/router"
|
||||
import { Button } from "@opencode-ai/ui"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
|
||||
export default function Home() {
|
||||
const sync = useGlobalSync()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
import { Button, Tooltip, DiffChanges, IconButton, Mark, Icon, Collapsible } from "@opencode-ai/ui"
|
||||
import { createMemo, For, ParentProps, Show } from "solid-js"
|
||||
import { DateTime } from "luxon"
|
||||
import { A, useParams } from "@solidjs/router"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { base64Encode, getFilename } from "@/utils"
|
||||
import { Mark } from "@opencode-ai/ui/logo"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { Collapsible } from "@opencode-ai/ui/collapsible"
|
||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const params = useParams()
|
||||
|
|
|
|||
|
|
@ -1,39 +1,20 @@
|
|||
import {
|
||||
SelectDialog,
|
||||
IconButton,
|
||||
Tabs,
|
||||
Icon,
|
||||
Accordion,
|
||||
Diff,
|
||||
Collapsible,
|
||||
DiffChanges,
|
||||
Message,
|
||||
Typewriter,
|
||||
Card,
|
||||
Code,
|
||||
Tooltip,
|
||||
ProgressCircle,
|
||||
FileIcon,
|
||||
SessionReview,
|
||||
MessageProgress,
|
||||
} from "@opencode-ai/ui"
|
||||
import {
|
||||
For,
|
||||
onCleanup,
|
||||
onMount,
|
||||
Show,
|
||||
Match,
|
||||
Switch,
|
||||
createSignal,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createResource,
|
||||
} from "solid-js"
|
||||
import { For, onCleanup, onMount, Show, Match, Switch, createResource } from "solid-js"
|
||||
import { useLocal, type LocalFile } from "@/context/local"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { getDirectory, getFilename } from "@/utils"
|
||||
import { PromptInput } from "@/components/prompt-input"
|
||||
import { DateTime } from "luxon"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { Code } from "@opencode-ai/ui/code"
|
||||
import { SessionTimeline } from "@opencode-ai/ui/session-timeline"
|
||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||
import { SelectDialog } from "@opencode-ai/ui/select-dialog"
|
||||
import {
|
||||
DragDropProvider,
|
||||
DragDropSensors,
|
||||
|
|
@ -46,11 +27,8 @@ import {
|
|||
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
|
||||
import type { JSX } from "solid-js"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
|
||||
import { Markdown, Spinner, StickyAccordionHeader } from "@opencode-ai/ui"
|
||||
import { useSession } from "@/context/session"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { createSessionSeen } from "@/hooks/create-session-seen"
|
||||
|
||||
export default function Page() {
|
||||
const layout = useLayout()
|
||||
|
|
@ -63,7 +41,6 @@ export default function Page() {
|
|||
activeDraggable: undefined as string | undefined,
|
||||
})
|
||||
let inputRef!: HTMLDivElement
|
||||
let messageScrollElement!: HTMLDivElement
|
||||
|
||||
const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control"
|
||||
|
||||
|
|
@ -356,284 +333,10 @@ export default function Page() {
|
|||
<div class="relative shrink-0 px-6 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-xl mx-auto">
|
||||
<Switch>
|
||||
<Match when={session.id}>
|
||||
<div
|
||||
classList={{
|
||||
"flex-1 min-h-0 pb-20": true,
|
||||
"flex items-start justify-start": layout.review.state() === "pane",
|
||||
}}
|
||||
>
|
||||
<Show when={session.messages.user().length > 1}>
|
||||
{(_) => {
|
||||
const expanded = createMemo(() => layout.review.state() === "tab" || !session.diffs().length)
|
||||
|
||||
return (
|
||||
<ul
|
||||
role="list"
|
||||
classList={{
|
||||
"mr-8 shrink-0 flex flex-col items-start": true,
|
||||
"absolute right-full w-60 mt-3 @7xl:gap-2 @7xl:mt-1": expanded(),
|
||||
"mt-3": !expanded(),
|
||||
}}
|
||||
>
|
||||
<For each={session.messages.user()}>
|
||||
{(message) => {
|
||||
const working = createMemo(
|
||||
() => message.id === session.messages.last()?.id && session.working(),
|
||||
)
|
||||
const handleClick = () => session.messages.setActive(message.id)
|
||||
|
||||
return (
|
||||
<li
|
||||
classList={{
|
||||
"group/li flex items-center self-stretch justify-end": true,
|
||||
"@7xl:justify-start": expanded(),
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
placement="right"
|
||||
gutter={8}
|
||||
value={
|
||||
<div class="flex items-center gap-2">
|
||||
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
||||
{message.summary?.title}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button
|
||||
data-active={session.messages.active()?.id === message.id}
|
||||
onClick={handleClick}
|
||||
classList={{
|
||||
"group/tick flex items-center justify-start h-2 w-8 -mr-3": true,
|
||||
"data-[active=true]:[&>div]:bg-icon-strong-base data-[active=true]:[&>div]:w-full": true,
|
||||
"@7xl:hidden": expanded(),
|
||||
}}
|
||||
>
|
||||
<div class="h-px w-5 bg-icon-base group-hover/tick:w-full group-hover/tick:bg-icon-strong-base" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button
|
||||
classList={{
|
||||
"hidden items-center self-stretch w-full gap-x-2 cursor-default": true,
|
||||
"@7xl:flex": expanded(),
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={working()}>
|
||||
<Spinner class="text-text-base shrink-0 w-[18px] aspect-square" />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
||||
</Match>
|
||||
</Switch>
|
||||
<div
|
||||
data-active={session.messages.active()?.id === message.id}
|
||||
classList={{
|
||||
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
|
||||
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
|
||||
}}
|
||||
>
|
||||
<Show when={message.summary?.title} fallback="New message">
|
||||
{message.summary?.title}
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</ul>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
<div ref={messageScrollElement} class="grow size-full min-w-0 overflow-y-auto no-scrollbar">
|
||||
<For each={session.messages.user()}>
|
||||
{(message) => {
|
||||
const isActive = createMemo(() => session.messages.active()?.id === message.id)
|
||||
const titleSeen = createSessionSeen(`message-title-${message.id}`)
|
||||
const contentSeen = createSessionSeen(`message-content-${message.id}`)
|
||||
const [titled, setTitled] = createSignal(titleSeen())
|
||||
const assistantMessages = createMemo(() => {
|
||||
if (!session.id) return []
|
||||
return sync.data.message[session.id]?.filter(
|
||||
(m) => m.role === "assistant" && m.parentID == message.id,
|
||||
) as AssistantMessageType[]
|
||||
})
|
||||
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
|
||||
const [detailsExpanded, setDetailsExpanded] = createSignal(false)
|
||||
const parts = createMemo(() => sync.data.part[message.id])
|
||||
const hasToolPart = createMemo(() =>
|
||||
assistantMessages()
|
||||
?.flatMap((m) => sync.data.part[m.id])
|
||||
.some((p) => p?.type === "tool"),
|
||||
)
|
||||
const working = createMemo(
|
||||
() => message.id === session.messages.last()?.id && session.working(),
|
||||
)
|
||||
const initialCompleted = !(message.id === session.messages.last()?.id && session.working())
|
||||
const [completed, setCompleted] = createSignal(initialCompleted)
|
||||
|
||||
// allowing time for the animations to finish
|
||||
createEffect(() => {
|
||||
if (titleSeen()) return
|
||||
const title = message.summary?.title
|
||||
if (title) setTimeout(() => setTitled(true), 10_000)
|
||||
})
|
||||
createEffect(() => {
|
||||
const completed = !working()
|
||||
setTimeout(() => setCompleted(completed), 1200)
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={isActive()}>
|
||||
<div
|
||||
data-message={message.id}
|
||||
class="flex flex-col items-start self-stretch gap-8 pb-20"
|
||||
>
|
||||
{/* Title */}
|
||||
<div class="flex items-center gap-2 self-stretch sticky top-0 bg-background-stronger z-20 h-8">
|
||||
<div class="w-full text-14-medium text-text-strong">
|
||||
<Show
|
||||
when={titled()}
|
||||
fallback={
|
||||
<Typewriter
|
||||
as="h1"
|
||||
text={message.summary?.title}
|
||||
class="overflow-hidden text-ellipsis min-w-0 text-nowrap"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<h1 class="overflow-hidden text-ellipsis min-w-0 text-nowrap">
|
||||
{message.summary?.title}
|
||||
</h1>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Message message={message} parts={parts()} />
|
||||
{/* Summary */}
|
||||
<Show when={completed()}>
|
||||
<div class="w-full flex flex-col gap-6 items-start self-stretch">
|
||||
<div class="flex flex-col items-start gap-1 self-stretch">
|
||||
<h2 class="text-12-medium text-text-weak">
|
||||
<Switch>
|
||||
<Match when={message.summary?.diffs?.length}>Summary</Match>
|
||||
<Match when={true}>Response</Match>
|
||||
</Switch>
|
||||
</h2>
|
||||
<Show when={message.summary?.body}>
|
||||
{(summary) => (
|
||||
<Markdown
|
||||
classList={{
|
||||
"text-14-regular": !!message.summary?.diffs?.length,
|
||||
"[&>*]:fade-up-text": !message.summary?.diffs?.length && !contentSeen(),
|
||||
}}
|
||||
text={summary()}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<Accordion class="w-full" multiple>
|
||||
<For each={message.summary?.diffs ?? []}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file}>
|
||||
<StickyAccordionHeader class="top-10 data-expanded:before:-top-10">
|
||||
<Accordion.Trigger>
|
||||
<div class="flex items-center justify-between w-full gap-5">
|
||||
<div class="grow flex items-center gap-5 min-w-0">
|
||||
<FileIcon
|
||||
node={{ path: diff.file, type: "file" }}
|
||||
class="shrink-0 size-4"
|
||||
/>
|
||||
<div class="flex grow min-w-0">
|
||||
<Show when={diff.file.includes("/")}>
|
||||
<span class="text-text-base truncate-start">
|
||||
{getDirectory(diff.file)}‎
|
||||
</span>
|
||||
</Show>
|
||||
<span class="text-text-strong shrink-0">
|
||||
{getFilename(diff.file)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0 flex gap-4 items-center justify-end">
|
||||
<DiffChanges changes={diff} />
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content class="max-h-60 overflow-y-auto no-scrollbar">
|
||||
<Diff
|
||||
before={{
|
||||
name: diff.file!,
|
||||
contents: diff.before!,
|
||||
}}
|
||||
after={{
|
||||
name: diff.file!,
|
||||
contents: diff.after!,
|
||||
}}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={error() && !detailsExpanded()}>
|
||||
<Card variant="error" class="text-text-on-critical-base">
|
||||
{error()?.data?.message as string}
|
||||
</Card>
|
||||
</Show>
|
||||
{/* Response */}
|
||||
<div class="w-full">
|
||||
<Switch>
|
||||
<Match when={!completed()}>
|
||||
<MessageProgress assistantMessages={assistantMessages} done={!working()} />
|
||||
</Match>
|
||||
<Match when={completed() && hasToolPart()}>
|
||||
<Collapsible
|
||||
variant="ghost"
|
||||
open={detailsExpanded()}
|
||||
onOpenChange={setDetailsExpanded}
|
||||
>
|
||||
<Collapsible.Trigger class="text-text-weak hover:text-text-strong">
|
||||
<div class="flex items-center gap-1 self-stretch">
|
||||
<div class="text-12-medium">
|
||||
<Switch>
|
||||
<Match when={detailsExpanded()}>Hide details</Match>
|
||||
<Match when={!detailsExpanded()}>Show details</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<Collapsible.Arrow />
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<div class="w-full flex flex-col items-start self-stretch gap-3">
|
||||
<For each={assistantMessages()}>
|
||||
{(assistantMessage) => {
|
||||
const parts = createMemo(() => sync.data.part[assistantMessage.id])
|
||||
return <Message message={assistantMessage} parts={parts()} />
|
||||
}}
|
||||
</For>
|
||||
<Show when={error()}>
|
||||
<Card variant="error" class="text-text-on-critical-base">
|
||||
{error()?.data?.message as string}
|
||||
</Card>
|
||||
</Show>
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
<SessionTimeline
|
||||
sessionID={session.id}
|
||||
expanded={layout.review.state() === "tab" || !session.diffs().length}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Collapsible as KobalteCollapsible } from "@kobalte/core/collapsible"
|
||||
import { Icon, IconProps } from "@opencode-ai/ui/icon"
|
||||
import { splitProps } from "solid-js"
|
||||
import type { ComponentProps, ParentProps } from "solid-js"
|
||||
import { Icon, type IconProps } from "@opencode-ai/ui"
|
||||
|
||||
export interface CollapsibleProps extends ComponentProps<typeof KobalteCollapsible> {}
|
||||
export interface CollapsibleTriggerProps extends ComponentProps<typeof KobalteCollapsible.Trigger> {}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default defineConfig({
|
|||
plugins: [tailwindcss(), solidPlugin()],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
allowedHosts: true,
|
||||
port: 3000,
|
||||
},
|
||||
build: {
|
||||
|
|
|
|||
|
|
@ -4,5 +4,9 @@ import tailwindcss from "@tailwindcss/vite"
|
|||
export default defineConfig({
|
||||
vite: {
|
||||
plugins: [tailwindcss() as any],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
allowedHosts: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { FileRoutes } from "@solidjs/start/router"
|
|||
import { Suspense } from "solid-js"
|
||||
import { Fonts } from "@opencode-ai/ui/fonts"
|
||||
import { MetaProvider } from "@solidjs/meta"
|
||||
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||
import "./app.css"
|
||||
|
||||
export default function App() {
|
||||
|
|
@ -11,10 +12,12 @@ export default function App() {
|
|||
root={(props) => (
|
||||
<>
|
||||
<Suspense>
|
||||
<MetaProvider>
|
||||
<Fonts />
|
||||
{props.children}
|
||||
</MetaProvider>
|
||||
<MarkedProvider>
|
||||
<MetaProvider>
|
||||
<Fonts />
|
||||
{props.children}
|
||||
</MetaProvider>
|
||||
</MarkedProvider>
|
||||
</Suspense>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { FileDiff, Message, Part, Session } from "@opencode-ai/sdk"
|
||||
import { FileDiff, Message, Part, Session, SessionStatus } from "@opencode-ai/sdk"
|
||||
import { fn } from "@opencode-ai/util/fn"
|
||||
import { iife } from "@opencode-ai/util/iife"
|
||||
import z from "zod"
|
||||
|
|
@ -28,6 +28,10 @@ export namespace Share {
|
|||
type: z.literal("session_diff"),
|
||||
data: z.custom<FileDiff[]>(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("session_status"),
|
||||
data: z.custom<SessionStatus>(),
|
||||
}),
|
||||
])
|
||||
export type Data = z.infer<typeof Data>
|
||||
|
||||
|
|
@ -92,6 +96,9 @@ export namespace Share {
|
|||
case "session_diff":
|
||||
await Storage.write(["share_data", input.share.id, "session_diff"], item.data)
|
||||
break
|
||||
case "session_status":
|
||||
await Storage.write(["share_data", input.share.id, "session_status"], item.data)
|
||||
break
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { FileDiff, Message, Part, Session } from "@opencode-ai/sdk"
|
||||
import { FileDiff, Message, Part, Session, SessionStatus } from "@opencode-ai/sdk"
|
||||
import { SessionTimeline } from "@opencode-ai/ui/session-timeline"
|
||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||
import { DataProvider } from "@opencode-ai/ui/context"
|
||||
import { createAsync, query, RouteDefinition, useParams } from "@solidjs/router"
|
||||
import { Show } from "solid-js"
|
||||
import { Share } from "~/core/share"
|
||||
|
||||
const getData = query(async (sessionID) => {
|
||||
|
|
@ -9,6 +13,9 @@ const getData = query(async (sessionID) => {
|
|||
session_diff: {
|
||||
[sessionID: string]: FileDiff[]
|
||||
}
|
||||
session_status: {
|
||||
[sessionID: string]: SessionStatus
|
||||
}
|
||||
message: {
|
||||
[sessionID: string]: Message[]
|
||||
}
|
||||
|
|
@ -20,6 +27,11 @@ const getData = query(async (sessionID) => {
|
|||
session_diff: {
|
||||
[sessionID]: [],
|
||||
},
|
||||
session_status: {
|
||||
[sessionID]: {
|
||||
type: "idle",
|
||||
},
|
||||
},
|
||||
message: {},
|
||||
part: {},
|
||||
}
|
||||
|
|
@ -32,6 +44,9 @@ const getData = query(async (sessionID) => {
|
|||
case "session_diff":
|
||||
result.session_diff[sessionID] = item.data
|
||||
break
|
||||
case "session_status":
|
||||
result.session_status[sessionID] = item.data
|
||||
break
|
||||
case "message":
|
||||
result.message[item.data.sessionID] = result.message[item.data.sessionID] ?? []
|
||||
result.message[item.data.sessionID].push(item.data)
|
||||
|
|
@ -55,5 +70,26 @@ export default function () {
|
|||
if (!params.sessionID) return
|
||||
return getData(params.sessionID)
|
||||
})
|
||||
return <pre>{JSON.stringify(data(), null, 2)}</pre>
|
||||
return (
|
||||
<Show when={data()}>
|
||||
{(data) => (
|
||||
<DataProvider data={data()}>
|
||||
<div class="relative bg-background-base size-full overflow-x-hidden flex flex-col">
|
||||
<div class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
|
||||
<div class="w-full flex-1 min-h-0 flex">
|
||||
<div class="relative shrink-0 px-6 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-xl mx-auto">
|
||||
<SessionTimeline sessionID={params.sessionID!} expanded />
|
||||
</div>
|
||||
<Show when={data().session_diff[params.sessionID!]?.length}>
|
||||
<div class="relative grow px-6 py-3 flex-1 min-h-0 border-l border-border-weak-base">
|
||||
<SessionReview diffs={data().session_diff[params.sessionID!]} />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DataProvider>
|
||||
)}
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@
|
|||
"version": "1.0.85",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/components/index.ts",
|
||||
"./*": "./src/components/*.tsx",
|
||||
"./hooks": "./src/hooks/index.ts",
|
||||
"./context": "./src/context/index.ts",
|
||||
"./context/*": "./src/context/*",
|
||||
"./context/*": "./src/context/*.tsx",
|
||||
"./styles": "./src/styles/index.css",
|
||||
"./styles/tailwind": "./src/styles/tailwind/index.css",
|
||||
"./fonts/*": "./src/assets/fonts/*"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import {
|
|||
type DiffLineAnnotation,
|
||||
type HunkData,
|
||||
FileDiffOptions,
|
||||
registerCustomTheme,
|
||||
ThemeRegistrationResolved,
|
||||
} from "@pierre/precision-diffs"
|
||||
import { ComponentProps, createEffect, splitProps } from "solid-js"
|
||||
|
||||
|
|
@ -173,373 +171,3 @@ export function Diff<T>(props: DiffProps<T>) {
|
|||
/>
|
||||
)
|
||||
}
|
||||
|
||||
registerCustomTheme("OpenCode", () => {
|
||||
return Promise.resolve({
|
||||
name: "OpenCode",
|
||||
colors: {
|
||||
"editor.background": "transparent",
|
||||
"editor.foreground": "var(--text-base)",
|
||||
"gitDecoration.addedResourceForeground": "var(--syntax-diff-add)",
|
||||
"gitDecoration.deletedResourceForeground": "var(--syntax-diff-delete)",
|
||||
// "gitDecoration.conflictingResourceForeground": "#ffca00",
|
||||
// "gitDecoration.modifiedResourceForeground": "#1a76d4",
|
||||
// "gitDecoration.untrackedResourceForeground": "#00cab1",
|
||||
// "gitDecoration.ignoredResourceForeground": "#84848A",
|
||||
// "terminal.titleForeground": "#adadb1",
|
||||
// "terminal.titleInactiveForeground": "#84848A",
|
||||
// "terminal.background": "#141415",
|
||||
// "terminal.foreground": "#adadb1",
|
||||
// "terminal.ansiBlack": "#141415",
|
||||
// "terminal.ansiRed": "#ff2e3f",
|
||||
// "terminal.ansiGreen": "#0dbe4e",
|
||||
// "terminal.ansiYellow": "#ffca00",
|
||||
// "terminal.ansiBlue": "#008cff",
|
||||
// "terminal.ansiMagenta": "#c635e4",
|
||||
// "terminal.ansiCyan": "#08c0ef",
|
||||
// "terminal.ansiWhite": "#c6c6c8",
|
||||
// "terminal.ansiBrightBlack": "#141415",
|
||||
// "terminal.ansiBrightRed": "#ff2e3f",
|
||||
// "terminal.ansiBrightGreen": "#0dbe4e",
|
||||
// "terminal.ansiBrightYellow": "#ffca00",
|
||||
// "terminal.ansiBrightBlue": "#008cff",
|
||||
// "terminal.ansiBrightMagenta": "#c635e4",
|
||||
// "terminal.ansiBrightCyan": "#08c0ef",
|
||||
// "terminal.ansiBrightWhite": "#c6c6c8",
|
||||
},
|
||||
tokenColors: [
|
||||
{
|
||||
scope: ["comment", "punctuation.definition.comment", "string.comment"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-comment)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["entity.other.attribute-name"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-property)", // maybe attribute
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["constant", "entity.name.constant", "variable.other.constant", "variable.language", "entity"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-constant)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["entity.name", "meta.export.default", "meta.definition.variable"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-type)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["meta.object.member"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"variable.parameter.function",
|
||||
"meta.jsx.children",
|
||||
"meta.block",
|
||||
"meta.tag.attributes",
|
||||
"entity.name.constant",
|
||||
"meta.embedded.expression",
|
||||
"meta.template.expression",
|
||||
"string.other.begin.yaml",
|
||||
"string.other.end.yaml",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-punctuation)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["entity.name.function", "support.type.primitive"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["support.class.component"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-type)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "keyword",
|
||||
settings: {
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"keyword.operator",
|
||||
"storage.type.function.arrow",
|
||||
"punctuation.separator.key-value.css",
|
||||
"entity.name.tag.yaml",
|
||||
"punctuation.separator.key-value.mapping.yaml",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["storage", "storage.type"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["storage.modifier.package", "storage.modifier.import", "storage.type.java"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"string",
|
||||
"punctuation.definition.string",
|
||||
"string punctuation.section.embedded source",
|
||||
"entity.name.tag",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-string)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "support",
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["support.type.object.module", "variable.other.object", "support.type.property-name.css"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-object)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.property-name",
|
||||
settings: {
|
||||
foreground: "var(--syntax-property)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "variable",
|
||||
settings: {
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "variable.other",
|
||||
settings: {
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"invalid.broken",
|
||||
"invalid.illegal",
|
||||
"invalid.unimplemented",
|
||||
"invalid.deprecated",
|
||||
"message.error",
|
||||
"markup.deleted",
|
||||
"meta.diff.header.from-file",
|
||||
"punctuation.definition.deleted",
|
||||
"brackethighlighter.unmatched",
|
||||
"token.error-token",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-critical)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "carriage-return",
|
||||
settings: {
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "string source",
|
||||
settings: {
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "string variable",
|
||||
settings: {
|
||||
foreground: "var(--syntax-constant)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"source.regexp",
|
||||
"string.regexp",
|
||||
"string.regexp.character-class",
|
||||
"string.regexp constant.character.escape",
|
||||
"string.regexp source.ruby.embedded",
|
||||
"string.regexp string.regexp.arbitrary-repitition",
|
||||
"string.regexp constant.character.escape",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-regexp)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "support.constant",
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "support.variable",
|
||||
settings: {
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.module-reference",
|
||||
settings: {
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "punctuation.definition.list.begin.markdown",
|
||||
settings: {
|
||||
foreground: "var(--syntax-punctuation)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["markup.heading", "markup.heading entity.name"],
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.quote",
|
||||
settings: {
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.italic",
|
||||
settings: {
|
||||
fontStyle: "italic",
|
||||
// foreground: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.bold",
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--text-strong)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"markup.raw",
|
||||
"markup.inserted",
|
||||
"meta.diff.header.to-file",
|
||||
"punctuation.definition.inserted",
|
||||
"markup.changed",
|
||||
"punctuation.definition.changed",
|
||||
"markup.ignored",
|
||||
"markup.untracked",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--text-base)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.diff.range",
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.diff.header",
|
||||
settings: {
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.separator",
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.output",
|
||||
settings: {
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.export.default",
|
||||
settings: {
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"brackethighlighter.tag",
|
||||
"brackethighlighter.curly",
|
||||
"brackethighlighter.round",
|
||||
"brackethighlighter.square",
|
||||
"brackethighlighter.angle",
|
||||
"brackethighlighter.quote",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["constant.other.reference.link", "string.other.link"],
|
||||
settings: {
|
||||
fontStyle: "underline",
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "token.info-token",
|
||||
settings: {
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "token.warn-token",
|
||||
settings: {
|
||||
foreground: "var(--syntax-warning)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "token.debug-token",
|
||||
settings: {
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
],
|
||||
semanticTokenColors: {
|
||||
comment: "var(--syntax-comment)",
|
||||
string: "var(--syntax-string)",
|
||||
number: "var(--syntax-constant)",
|
||||
regexp: "var(--syntax-regexp)",
|
||||
keyword: "var(--syntax-keyword)",
|
||||
variable: "var(--syntax-variable)",
|
||||
parameter: "var(--syntax-variable)",
|
||||
property: "var(--syntax-property)",
|
||||
function: "var(--syntax-primitive)",
|
||||
method: "var(--syntax-primitive)",
|
||||
type: "var(--syntax-type)",
|
||||
class: "var(--syntax-type)",
|
||||
namespace: "var(--syntax-type)",
|
||||
enumMember: "var(--syntax-primitive)",
|
||||
"variable.constant": "var(--syntax-constant)",
|
||||
"variable.defaultLibrary": "var(--syntax-unknown)",
|
||||
},
|
||||
} as unknown as ThemeRegistrationResolved)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
export * from "./accordion"
|
||||
export * from "./button"
|
||||
export * from "./card"
|
||||
export * from "./checkbox"
|
||||
export * from "./code"
|
||||
export * from "./collapsible"
|
||||
export * from "./dialog"
|
||||
export * from "./diff"
|
||||
export * from "./diff-changes"
|
||||
export * from "./icon"
|
||||
export * from "./icon-button"
|
||||
export * from "./input"
|
||||
export * from "./favicon"
|
||||
export * from "./fonts"
|
||||
export * from "./list"
|
||||
export * from "./logo"
|
||||
export * from "./markdown"
|
||||
export * from "./message-part"
|
||||
export * from "./message-progress"
|
||||
export * from "./progress-circle"
|
||||
export * from "./select"
|
||||
export * from "./select-dialog"
|
||||
export * from "./spinner"
|
||||
export * from "./sticky-accordion-header"
|
||||
export * from "./tabs"
|
||||
export * from "./basic-tool"
|
||||
export * from "./file-icon"
|
||||
export * from "./tooltip"
|
||||
export * from "./typewriter"
|
||||
export * from "./session-review"
|
||||
|
||||
export * from "../context/helper"
|
||||
export * from "../context/marked"
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
[data-component="list"] {
|
||||
/* overflow-y: auto; */
|
||||
|
||||
/* Hide scrollbar */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
||||
[data-slot="item"] {
|
||||
width: 100%;
|
||||
margin-bottom: 6px;
|
||||
padding: 4px 12px;
|
||||
text-align: left;
|
||||
|
||||
border-radius: var(--radius-md);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
&[data-active="true"] {
|
||||
background-color: var(--surface-raised-base-hover);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--surface-raised-base-hover);
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import { ComponentProps, createEffect, createSignal, type JSX } from "solid-js"
|
||||
import { VirtualizerHandle, VList } from "virtua/solid"
|
||||
import { createList } from "solid-list"
|
||||
import { createStore } from "solid-js/store"
|
||||
|
||||
export interface ListProps<T> {
|
||||
data: T[]
|
||||
children: (x: T) => JSX.Element
|
||||
key: (x: T) => string
|
||||
current?: T
|
||||
onSelect?: (value: T | undefined) => void
|
||||
onHover?: (value: T | undefined) => void
|
||||
class?: ComponentProps<"div">["class"]
|
||||
}
|
||||
|
||||
export function List<T>(props: ListProps<T>) {
|
||||
const [virtualizer, setVirtualizer] = createSignal<VirtualizerHandle | undefined>(undefined)
|
||||
const [store, setStore] = createStore({
|
||||
mouseActive: false,
|
||||
})
|
||||
const list = createList({
|
||||
items: () => props.data.map(props.key),
|
||||
initialActive: props.current ? props.key(props.current) : undefined,
|
||||
loop: true,
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (props.current) list.setActive(props.key(props.current))
|
||||
})
|
||||
// const resetSelection = () => {
|
||||
// if (props.data.length === 0) return
|
||||
// list.setActive(props.key(props.data[0]))
|
||||
// }
|
||||
const handleSelect = (item: T) => {
|
||||
props.onSelect?.(item)
|
||||
list.setActive(props.key(item))
|
||||
}
|
||||
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
setStore("mouseActive", false)
|
||||
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
const selected = props.data.find((x) => props.key(x) === list.active())
|
||||
if (selected) handleSelect(selected)
|
||||
} else {
|
||||
list.onKeyDown(e)
|
||||
}
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (store.mouseActive || props.data.length === 0) return
|
||||
const index = props.data.findIndex((x) => props.key(x) === list.active())
|
||||
props.onHover?.(props.data[index])
|
||||
if (index === 0) {
|
||||
virtualizer()?.scrollTo(0)
|
||||
return
|
||||
}
|
||||
// virtualizer()?.scrollTo(list.active())
|
||||
// const element = virtualizer()?.querySelector(`[data-key="${list.active()}"]`)
|
||||
// element?.scrollIntoView({ block: "nearest", behavior: "smooth" })
|
||||
})
|
||||
|
||||
return (
|
||||
<VList data-component="list" ref={setVirtualizer} data={props.data} onKeyDown={handleKey} class={props.class}>
|
||||
{(item) => (
|
||||
<button
|
||||
data-slot="item"
|
||||
data-key={props.key(item)}
|
||||
data-active={props.key(item) === list.active()}
|
||||
onClick={() => handleSelect(item)}
|
||||
onMouseMove={() => {
|
||||
// e.currentTarget.focus()
|
||||
setStore("mouseActive", true)
|
||||
// list.setActive(props.key(item))
|
||||
}}
|
||||
>
|
||||
{props.children(item)}
|
||||
</button>
|
||||
)}
|
||||
</VList>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
import { createEffect, Show, For, type JSX, splitProps } from "solid-js"
|
||||
import { Dialog, DialogProps, Icon, IconButton, Input } from "@opencode-ai/ui"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { FilteredListProps, useFilteredList } from "@opencode-ai/ui/hooks"
|
||||
import { Dialog, DialogProps } from "./dialog"
|
||||
import { Icon } from "./icon"
|
||||
import { Input } from "./input"
|
||||
import { IconButton } from "./icon-button"
|
||||
|
||||
interface SelectDialogProps<T>
|
||||
extends FilteredListProps<T>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { Select as Kobalte } from "@kobalte/core/select"
|
||||
import { createMemo, type ComponentProps } from "solid-js"
|
||||
import { Icon, Button, type ButtonProps } from "@opencode-ai/ui"
|
||||
import { pipe, groupBy, entries, map } from "remeda"
|
||||
import { Button, ButtonProps } from "./button"
|
||||
import { Icon } from "./icon"
|
||||
|
||||
export interface SelectProps<T> {
|
||||
placeholder?: string
|
||||
|
|
|
|||
325
packages/ui/src/components/session-timeline.css
Normal file
325
packages/ui/src/components/session-timeline.css
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
[data-component="session-timeline"] {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding-bottom: 80px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
|
||||
[data-slot="timeline-list"] {
|
||||
margin-right: 32px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-top: 12px;
|
||||
|
||||
&[data-expanded="true"] {
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
width: 240px;
|
||||
margin-top: 12px;
|
||||
|
||||
@media (min-width: 80rem) {
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="timeline-item"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
justify-content: flex-end;
|
||||
|
||||
&[data-expanded="true"] {
|
||||
@media (min-width: 80rem) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="tick-button"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: 8px;
|
||||
width: 32px;
|
||||
margin-right: -12px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
|
||||
&[data-active="true"] [data-slot="tick-line"] {
|
||||
background-color: var(--icon-strong-base);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&[data-expanded="true"] {
|
||||
@media (min-width: 80rem) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="tick-line"] {
|
||||
height: 1px;
|
||||
width: 20px;
|
||||
background-color: var(--icon-base);
|
||||
transition:
|
||||
width 0.2s,
|
||||
background-color 0.2s;
|
||||
}
|
||||
|
||||
[data-slot="tick-button"]:hover [data-slot="tick-line"] {
|
||||
width: 100%;
|
||||
background-color: var(--icon-strong-base);
|
||||
}
|
||||
|
||||
[data-slot="message-button"] {
|
||||
display: none;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
column-gap: 8px;
|
||||
cursor: default;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
|
||||
&[data-expanded="true"] {
|
||||
@media (min-width: 80rem) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
color: var(--text-base);
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
[data-slot="message-title-preview"] {
|
||||
font-size: 14px; /* text-14-regular */
|
||||
color: var(--text-weak);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
|
||||
&[data-active="true"] {
|
||||
color: var(--text-strong);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="timeline-item"]:hover [data-slot="message-title-preview"] {
|
||||
color: var(--text-base);
|
||||
}
|
||||
|
||||
[data-slot="content"] {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
[data-slot="content"]::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-slot="message-container"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
gap: 32px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
[data-slot="message-header"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--background-stronger);
|
||||
z-index: 20;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
[data-slot="message-title"] {
|
||||
width: 100%;
|
||||
font-size: 14px; /* text-14-medium */
|
||||
font-weight: 500;
|
||||
color: var(--text-strong);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-slot="message-title"] h1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
[data-slot="summary-section"] {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
[data-slot="summary-header"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
[data-slot="summary-title"] {
|
||||
font-size: 12px; /* text-12-medium */
|
||||
font-weight: 500;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="markdown"] {
|
||||
&[data-diffs="true"] {
|
||||
font-size: 14px; /* text-14-regular */
|
||||
}
|
||||
|
||||
&[data-fade="true"] > * {
|
||||
animation: fade-up-text 0.3s ease-out forwards;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="accordion"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-slot="sticky-header"] {
|
||||
top: 40px; /* top-10 */
|
||||
|
||||
&::before {
|
||||
top: -40px; /* -top-10 */
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="accordion-trigger-content"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
[data-slot="file-info"] {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-slot="file-icon"] {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
[data-slot="file-path"] {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-slot="directory"] {
|
||||
color: var(--text-base);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
direction: rtl;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
[data-slot="filename"] {
|
||||
color: var(--text-strong);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-slot="accordion-actions"] {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
[data-slot="accordion-content"] {
|
||||
max-height: 240px; /* max-h-60 */
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
[data-slot="accordion-content"]::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-slot="response-section"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-slot="collapsible-trigger"] {
|
||||
color: var(--text-weak);
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-strong);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="collapsible-trigger-content"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
[data-slot="details-text"] {
|
||||
font-size: 12px; /* text-12-medium */
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
color: var(--text-on-critical-base);
|
||||
}
|
||||
|
||||
[data-slot="collapsible-content-inner"] {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
265
packages/ui/src/components/session-timeline.tsx
Normal file
265
packages/ui/src/components/session-timeline.tsx
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
import { createSeen } from "../hooks/create-seen"
|
||||
import { AssistantMessage } from "@opencode-ai/sdk"
|
||||
import { useData } from "../context"
|
||||
import { Binary } from "@opencode-ai/util/binary"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||
import { createEffect, createMemo, createSignal, For, Match, Show, Switch } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { DiffChanges } from "./diff-changes"
|
||||
import { Spinner } from "./spinner"
|
||||
import { Typewriter } from "./typewriter"
|
||||
import { Message } from "./message-part"
|
||||
import { Markdown } from "./markdown"
|
||||
import { Accordion } from "./accordion"
|
||||
import { StickyAccordionHeader } from "./sticky-accordion-header"
|
||||
import { FileIcon } from "./file-icon"
|
||||
import { Icon } from "./icon"
|
||||
import { Diff } from "./diff"
|
||||
import { Card } from "./card"
|
||||
import { MessageProgress } from "./message-progress"
|
||||
import { Collapsible } from "./collapsible"
|
||||
|
||||
export function SessionTimeline(props: { sessionID: string; expanded?: boolean }) {
|
||||
const data = useData()
|
||||
const [store, setStore] = createStore({
|
||||
messageId: undefined as string | undefined,
|
||||
})
|
||||
const match = Binary.search(data.session, props.sessionID, (s) => s.id)
|
||||
if (!match.found) throw new Error(`Session ${props.sessionID} not found`)
|
||||
|
||||
// const info = createMemo(() => data.session[match.index])
|
||||
const messages = createMemo(() => (props.sessionID ? (data.message[props.sessionID] ?? []) : []))
|
||||
const userMessages = createMemo(() =>
|
||||
messages()
|
||||
.filter((m) => m.role === "user")
|
||||
.sort((a, b) => b.id.localeCompare(a.id)),
|
||||
)
|
||||
const lastUserMessage = createMemo(() => {
|
||||
return userMessages()?.at(0)
|
||||
})
|
||||
const activeMessage = createMemo(() => {
|
||||
if (!store.messageId) return lastUserMessage()
|
||||
return userMessages()?.find((m) => m.id === store.messageId)
|
||||
})
|
||||
const status = createMemo(
|
||||
() =>
|
||||
data.session_status[props.sessionID] ?? {
|
||||
type: "idle",
|
||||
},
|
||||
)
|
||||
const working = createMemo(() => status()?.type !== "idle")
|
||||
|
||||
return (
|
||||
<div data-component="session-timeline">
|
||||
<Show when={userMessages().length > 1}>
|
||||
<ul role="list" data-slot="timeline-list" data-expanded={props.expanded}>
|
||||
<For each={userMessages()}>
|
||||
{(message) => {
|
||||
const messageWorking = createMemo(() => message.id === lastUserMessage()?.id && working())
|
||||
const handleClick = () => setStore("messageId", message.id)
|
||||
|
||||
return (
|
||||
<li data-slot="timeline-item" data-expanded={props.expanded}>
|
||||
<button
|
||||
data-slot="tick-button"
|
||||
data-active={activeMessage()?.id === message.id}
|
||||
data-expanded={props.expanded}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div data-slot="tick-line" />
|
||||
</button>
|
||||
<button data-slot="message-button" data-expanded={props.expanded} onClick={handleClick}>
|
||||
<Switch>
|
||||
<Match when={messageWorking()}>
|
||||
<Spinner class="spinner" />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
||||
</Match>
|
||||
</Switch>
|
||||
<div data-slot="message-title-preview" data-active={activeMessage()?.id === message.id}>
|
||||
<Show when={message.summary?.title} fallback="New message">
|
||||
{message.summary?.title}
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</ul>
|
||||
</Show>
|
||||
<div data-slot="content">
|
||||
<For each={userMessages()}>
|
||||
{(message) => {
|
||||
const isActive = createMemo(() => activeMessage()?.id === message.id)
|
||||
const titleSeen = createMemo(() => true)
|
||||
const contentSeen = createMemo(() => true)
|
||||
{
|
||||
/* const titleSeen = createSeen(`message-title-${message.id}`) */
|
||||
}
|
||||
{
|
||||
/* const contentSeen = createSeen(`message-content-${message.id}`) */
|
||||
}
|
||||
const [titled, setTitled] = createSignal(titleSeen())
|
||||
const assistantMessages = createMemo(() => {
|
||||
return messages()?.filter((m) => m.role === "assistant" && m.parentID == message.id) as AssistantMessage[]
|
||||
})
|
||||
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
|
||||
const [detailsExpanded, setDetailsExpanded] = createSignal(false)
|
||||
const parts = createMemo(() => data.part[message.id])
|
||||
const hasToolPart = createMemo(() =>
|
||||
assistantMessages()
|
||||
?.flatMap((m) => data.part[m.id])
|
||||
.some((p) => p?.type === "tool"),
|
||||
)
|
||||
const messageWorking = createMemo(() => message.id === lastUserMessage()?.id && working())
|
||||
const initialCompleted = !(message.id === lastUserMessage()?.id && working())
|
||||
const [completed, setCompleted] = createSignal(initialCompleted)
|
||||
|
||||
// allowing time for the animations to finish
|
||||
createEffect(() => {
|
||||
if (titleSeen()) return
|
||||
const title = message.summary?.title
|
||||
if (title) setTimeout(() => setTitled(true), 10_000)
|
||||
})
|
||||
createEffect(() => {
|
||||
const completed = !messageWorking()
|
||||
setTimeout(() => setCompleted(completed), 1200)
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={isActive()}>
|
||||
<div data-message={message.id} data-slot="message-container">
|
||||
{/* Title */}
|
||||
<div data-slot="message-header">
|
||||
<div data-slot="message-title">
|
||||
<Show
|
||||
when={titled()}
|
||||
fallback={
|
||||
<Typewriter
|
||||
as="h1"
|
||||
text={message.summary?.title}
|
||||
class="overflow-hidden text-ellipsis min-w-0 text-nowrap"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<h1>{message.summary?.title}</h1>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Message message={message} parts={parts()} />
|
||||
{/* Summary */}
|
||||
<Show when={completed()}>
|
||||
<div data-slot="summary-section">
|
||||
<div data-slot="summary-header">
|
||||
<h2 data-slot="summary-title">
|
||||
<Switch>
|
||||
<Match when={message.summary?.diffs?.length}>Summary</Match>
|
||||
<Match when={true}>Response</Match>
|
||||
</Switch>
|
||||
</h2>
|
||||
<Show when={message.summary?.body}>
|
||||
{(summary) => (
|
||||
<Markdown
|
||||
data-slot="markdown"
|
||||
data-diffs={!!message.summary?.diffs?.length}
|
||||
data-fade={!message.summary?.diffs?.length && !contentSeen()}
|
||||
text={summary()}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<Accordion data-slot="accordion" multiple>
|
||||
<For each={message.summary?.diffs ?? []}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file}>
|
||||
<StickyAccordionHeader data-slot="sticky-header">
|
||||
<Accordion.Trigger>
|
||||
<div data-slot="accordion-trigger-content">
|
||||
<div data-slot="file-info">
|
||||
<FileIcon node={{ path: diff.file, type: "file" }} data-slot="file-icon" />
|
||||
<div data-slot="file-path">
|
||||
<Show when={diff.file.includes("/")}>
|
||||
<span data-slot="directory">{getDirectory(diff.file)}‎</span>
|
||||
</Show>
|
||||
<span data-slot="filename">{getFilename(diff.file)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-slot="accordion-actions">
|
||||
<DiffChanges changes={diff} />
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content data-slot="accordion-content">
|
||||
<Diff
|
||||
before={{
|
||||
name: diff.file!,
|
||||
contents: diff.before!,
|
||||
}}
|
||||
after={{
|
||||
name: diff.file!,
|
||||
contents: diff.after!,
|
||||
}}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={error() && !detailsExpanded()}>
|
||||
<Card variant="error" class="error-card">
|
||||
{error()?.data?.message as string}
|
||||
</Card>
|
||||
</Show>
|
||||
{/* Response */}
|
||||
<div data-slot="response-section">
|
||||
<Switch>
|
||||
<Match when={!completed()}>
|
||||
<MessageProgress assistantMessages={assistantMessages} done={!messageWorking()} />
|
||||
</Match>
|
||||
<Match when={completed() && hasToolPart()}>
|
||||
<Collapsible variant="ghost" open={detailsExpanded()} onOpenChange={setDetailsExpanded}>
|
||||
<Collapsible.Trigger data-slot="collapsible-trigger">
|
||||
<div data-slot="collapsible-trigger-content">
|
||||
<div data-slot="details-text">
|
||||
<Switch>
|
||||
<Match when={detailsExpanded()}>Hide details</Match>
|
||||
<Match when={!detailsExpanded()}>Show details</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<Collapsible.Arrow />
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<div data-slot="collapsible-content-inner">
|
||||
<For each={assistantMessages()}>
|
||||
{(assistantMessage) => {
|
||||
const parts = createMemo(() => data.part[assistantMessage.id])
|
||||
return <Message message={assistantMessage} parts={parts()} />
|
||||
}}
|
||||
</For>
|
||||
<Show when={error()}>
|
||||
<Card variant="error" class="error-card">
|
||||
{error()?.data?.message as string}
|
||||
</Card>
|
||||
</Show>
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Tooltip as KobalteTooltip } from "@kobalte/core/tooltip"
|
||||
import { children, createEffect, createSignal, Match, splitProps, Switch, type JSX } from "solid-js"
|
||||
import { children, createSignal, Match, onMount, splitProps, Switch, type JSX } from "solid-js"
|
||||
import type { ComponentProps } from "solid-js"
|
||||
|
||||
export interface TooltipProps extends ComponentProps<typeof KobalteTooltip> {
|
||||
|
|
@ -14,7 +14,7 @@ export function Tooltip(props: TooltipProps) {
|
|||
|
||||
const c = children(() => local.children)
|
||||
|
||||
createEffect(() => {
|
||||
onMount(() => {
|
||||
const childElements = c()
|
||||
if (childElements instanceof HTMLElement) {
|
||||
childElements.addEventListener("focus", () => setOpen(true))
|
||||
|
|
|
|||
|
|
@ -2,9 +2,377 @@ import { marked } from "marked"
|
|||
import markedShiki from "marked-shiki"
|
||||
import { bundledLanguages, type BundledLanguage } from "shiki"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { getSharedHighlighter } from "@pierre/precision-diffs"
|
||||
import { getSharedHighlighter, registerCustomTheme, ThemeRegistrationResolved } from "@pierre/precision-diffs"
|
||||
|
||||
const highlighter = await getSharedHighlighter({ themes: ["OpenCode"], langs: [] })
|
||||
registerCustomTheme("OpenCode", () => {
|
||||
return Promise.resolve({
|
||||
name: "OpenCode",
|
||||
colors: {
|
||||
"editor.background": "transparent",
|
||||
"editor.foreground": "var(--text-base)",
|
||||
"gitDecoration.addedResourceForeground": "var(--syntax-diff-add)",
|
||||
"gitDecoration.deletedResourceForeground": "var(--syntax-diff-delete)",
|
||||
// "gitDecoration.conflictingResourceForeground": "#ffca00",
|
||||
// "gitDecoration.modifiedResourceForeground": "#1a76d4",
|
||||
// "gitDecoration.untrackedResourceForeground": "#00cab1",
|
||||
// "gitDecoration.ignoredResourceForeground": "#84848A",
|
||||
// "terminal.titleForeground": "#adadb1",
|
||||
// "terminal.titleInactiveForeground": "#84848A",
|
||||
// "terminal.background": "#141415",
|
||||
// "terminal.foreground": "#adadb1",
|
||||
// "terminal.ansiBlack": "#141415",
|
||||
// "terminal.ansiRed": "#ff2e3f",
|
||||
// "terminal.ansiGreen": "#0dbe4e",
|
||||
// "terminal.ansiYellow": "#ffca00",
|
||||
// "terminal.ansiBlue": "#008cff",
|
||||
// "terminal.ansiMagenta": "#c635e4",
|
||||
// "terminal.ansiCyan": "#08c0ef",
|
||||
// "terminal.ansiWhite": "#c6c6c8",
|
||||
// "terminal.ansiBrightBlack": "#141415",
|
||||
// "terminal.ansiBrightRed": "#ff2e3f",
|
||||
// "terminal.ansiBrightGreen": "#0dbe4e",
|
||||
// "terminal.ansiBrightYellow": "#ffca00",
|
||||
// "terminal.ansiBrightBlue": "#008cff",
|
||||
// "terminal.ansiBrightMagenta": "#c635e4",
|
||||
// "terminal.ansiBrightCyan": "#08c0ef",
|
||||
// "terminal.ansiBrightWhite": "#c6c6c8",
|
||||
},
|
||||
tokenColors: [
|
||||
{
|
||||
scope: ["comment", "punctuation.definition.comment", "string.comment"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-comment)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["entity.other.attribute-name"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-property)", // maybe attribute
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["constant", "entity.name.constant", "variable.other.constant", "variable.language", "entity"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-constant)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["entity.name", "meta.export.default", "meta.definition.variable"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-type)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["meta.object.member"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"variable.parameter.function",
|
||||
"meta.jsx.children",
|
||||
"meta.block",
|
||||
"meta.tag.attributes",
|
||||
"entity.name.constant",
|
||||
"meta.embedded.expression",
|
||||
"meta.template.expression",
|
||||
"string.other.begin.yaml",
|
||||
"string.other.end.yaml",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-punctuation)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["entity.name.function", "support.type.primitive"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["support.class.component"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-type)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "keyword",
|
||||
settings: {
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"keyword.operator",
|
||||
"storage.type.function.arrow",
|
||||
"punctuation.separator.key-value.css",
|
||||
"entity.name.tag.yaml",
|
||||
"punctuation.separator.key-value.mapping.yaml",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["storage", "storage.type"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["storage.modifier.package", "storage.modifier.import", "storage.type.java"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"string",
|
||||
"punctuation.definition.string",
|
||||
"string punctuation.section.embedded source",
|
||||
"entity.name.tag",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-string)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "support",
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["support.type.object.module", "variable.other.object", "support.type.property-name.css"],
|
||||
settings: {
|
||||
foreground: "var(--syntax-object)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.property-name",
|
||||
settings: {
|
||||
foreground: "var(--syntax-property)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "variable",
|
||||
settings: {
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "variable.other",
|
||||
settings: {
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"invalid.broken",
|
||||
"invalid.illegal",
|
||||
"invalid.unimplemented",
|
||||
"invalid.deprecated",
|
||||
"message.error",
|
||||
"markup.deleted",
|
||||
"meta.diff.header.from-file",
|
||||
"punctuation.definition.deleted",
|
||||
"brackethighlighter.unmatched",
|
||||
"token.error-token",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-critical)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "carriage-return",
|
||||
settings: {
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "string source",
|
||||
settings: {
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "string variable",
|
||||
settings: {
|
||||
foreground: "var(--syntax-constant)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"source.regexp",
|
||||
"string.regexp",
|
||||
"string.regexp.character-class",
|
||||
"string.regexp constant.character.escape",
|
||||
"string.regexp source.ruby.embedded",
|
||||
"string.regexp string.regexp.arbitrary-repitition",
|
||||
"string.regexp constant.character.escape",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-regexp)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "support.constant",
|
||||
settings: {
|
||||
foreground: "var(--syntax-primitive)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "support.variable",
|
||||
settings: {
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.module-reference",
|
||||
settings: {
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "punctuation.definition.list.begin.markdown",
|
||||
settings: {
|
||||
foreground: "var(--syntax-punctuation)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["markup.heading", "markup.heading entity.name"],
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.quote",
|
||||
settings: {
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.italic",
|
||||
settings: {
|
||||
fontStyle: "italic",
|
||||
// foreground: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.bold",
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--text-strong)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"markup.raw",
|
||||
"markup.inserted",
|
||||
"meta.diff.header.to-file",
|
||||
"punctuation.definition.inserted",
|
||||
"markup.changed",
|
||||
"punctuation.definition.changed",
|
||||
"markup.ignored",
|
||||
"markup.untracked",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--text-base)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.diff.range",
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.diff.header",
|
||||
settings: {
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.separator",
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.output",
|
||||
settings: {
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.export.default",
|
||||
settings: {
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: [
|
||||
"brackethighlighter.tag",
|
||||
"brackethighlighter.curly",
|
||||
"brackethighlighter.round",
|
||||
"brackethighlighter.square",
|
||||
"brackethighlighter.angle",
|
||||
"brackethighlighter.quote",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["constant.other.reference.link", "string.other.link"],
|
||||
settings: {
|
||||
fontStyle: "underline",
|
||||
foreground: "var(--syntax-unknown)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "token.info-token",
|
||||
settings: {
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "token.warn-token",
|
||||
settings: {
|
||||
foreground: "var(--syntax-warning)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "token.debug-token",
|
||||
settings: {
|
||||
foreground: "var(--syntax-info)",
|
||||
},
|
||||
},
|
||||
],
|
||||
semanticTokenColors: {
|
||||
comment: "var(--syntax-comment)",
|
||||
string: "var(--syntax-string)",
|
||||
number: "var(--syntax-constant)",
|
||||
regexp: "var(--syntax-regexp)",
|
||||
keyword: "var(--syntax-keyword)",
|
||||
variable: "var(--syntax-variable)",
|
||||
parameter: "var(--syntax-variable)",
|
||||
property: "var(--syntax-property)",
|
||||
function: "var(--syntax-primitive)",
|
||||
method: "var(--syntax-primitive)",
|
||||
type: "var(--syntax-type)",
|
||||
class: "var(--syntax-type)",
|
||||
namespace: "var(--syntax-type)",
|
||||
enumMember: "var(--syntax-primitive)",
|
||||
"variable.constant": "var(--syntax-constant)",
|
||||
"variable.defaultLibrary": "var(--syntax-unknown)",
|
||||
},
|
||||
} as unknown as ThemeRegistrationResolved)
|
||||
})
|
||||
|
||||
export const { use: useMarked, provider: MarkedProvider } = createSimpleContext({
|
||||
name: "Marked",
|
||||
|
|
@ -12,6 +380,7 @@ export const { use: useMarked, provider: MarkedProvider } = createSimpleContext(
|
|||
return marked.use(
|
||||
markedShiki({
|
||||
async highlight(code, lang) {
|
||||
const highlighter = await getSharedHighlighter({ themes: ["OpenCode"], langs: [] })
|
||||
if (!(lang in bundledLanguages)) {
|
||||
lang = "text"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
Tabs,
|
||||
Tooltip,
|
||||
Fonts,
|
||||
List,
|
||||
Dialog,
|
||||
Icon,
|
||||
IconButton,
|
||||
|
|
@ -131,11 +130,6 @@ const Demo: Component = () => {
|
|||
</Tooltip>
|
||||
</section>
|
||||
<h3>List</h3>
|
||||
<section style={{ height: "300px" }}>
|
||||
<List data={["Item 1", "Item 2", "Item 3"]} key={(x) => x}>
|
||||
{(x) => <div>{x}</div>}
|
||||
</List>
|
||||
</section>
|
||||
<h3>Input</h3>
|
||||
<section>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createSignal, onCleanup, onMount } from "solid-js"
|
||||
import { isServer } from "solid-js/web"
|
||||
|
||||
export function createSessionSeen(key: string, delay = 1000) {
|
||||
export function createSeen(key: string, delay = 1000) {
|
||||
// 1. Initialize state based on storage (default to true on server to avoid flash)
|
||||
const storageKey = `app:seen:${key}`
|
||||
const [hasSeen] = createSignal(!isServer && sessionStorage.getItem(storageKey) === "true")
|
||||
|
|
@ -1 +1,2 @@
|
|||
export * from "./use-filtered-list"
|
||||
export * from "./create-seen"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
@import "../components/icon.css" layer(components);
|
||||
@import "../components/icon-button.css" layer(components);
|
||||
@import "../components/input.css" layer(components);
|
||||
@import "../components/list.css" layer(components);
|
||||
@import "../components/logo.css" layer(components);
|
||||
@import "../components/markdown.css" layer(components);
|
||||
@import "../components/message-part.css" layer(components);
|
||||
|
|
@ -27,6 +26,7 @@
|
|||
@import "../components/select-dialog.css" layer(components);
|
||||
@import "../components/spinner.css" layer(components);
|
||||
@import "../components/session-review.css" layer(components);
|
||||
@import "../components/session-timeline.css" layer(components);
|
||||
@import "../components/sticky-accordion-header.css" layer(components);
|
||||
@import "../components/tabs.css" layer(components);
|
||||
@import "../components/tooltip.css" layer(components);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue