diff --git a/bun.lock b/bun.lock index fef52c5a..8e557472 100644 --- a/bun.lock +++ b/bun.lock @@ -77,8 +77,7 @@ "diff": "^7.0.0", "react": "^19.2.3", "react-dom": "^19.2.3", - "react-markdown": "^10.1.0", - "react-syntax-highlighter": "^16.1.0", + "react-shiki": "^0.9.1", "react-textarea-autosize": "^8.5.9", "remark-gfm": "^4.0.1", "socket.io-client": "^4.8.1", @@ -87,7 +86,6 @@ "devDependencies": { "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", - "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", @@ -444,6 +442,20 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.4", "", { "os": "win32", "cpu": "x64" }, "sha512-Sbx45u/Lbb5RyptSbX7/3deP+/lzEmZ0BTSHxwxN/IMOZDZf8S0AGo0hJD5n/LQssxb5Z3B4og4P2X6Dd8acCA=="], + "@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ=="], + + "@shikijs/langs": ["@shikijs/langs@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA=="], + + "@shikijs/themes": ["@shikijs/themes@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ=="], + + "@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@socket.io/bun-engine": ["@socket.io/bun-engine@0.1.0", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-B1z6GuAxZlfvjgaa3BHZBOfqHJNfnpebTw15p+Un1HuBL4YM7wUxO9sJa7K4NDTe7XbUBeqLIwTDA5tOOjffog=="], "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], @@ -494,16 +506,12 @@ "@types/parse-path": ["@types/parse-path@7.1.0", "", { "dependencies": { "parse-path": "*" } }, "sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q=="], - "@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="], - "@types/ps-list": ["@types/ps-list@6.2.1", "", { "dependencies": { "ps-list": "*" } }, "sha512-YGrVUmLaAu89QffaehCcCp5kphlbCf72+vDxnRyP1soC9y7xaatnoB0Fgo/tEGHk8teglcLwY6b4iHcyd/UWFA=="], "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - "@types/react-syntax-highlighter": ["@types/react-syntax-highlighter@15.5.13", "", { "dependencies": { "@types/react": "*" } }, "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA=="], - "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], "@types/tmp": ["@types/tmp@0.2.6", "", {}, "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA=="], @@ -868,8 +876,6 @@ "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - "fault": ["fault@1.0.4", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], @@ -890,8 +896,6 @@ "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], - "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], @@ -950,22 +954,18 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], - "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], - - "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], - - "highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], - "hono": ["hono@4.11.1", "", {}, "sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg=="], "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], @@ -1102,8 +1102,6 @@ "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], - "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], - "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], "macos-release": ["macos-release@3.4.0", "", {}, "sha512-wpGPwyg/xrSp4H4Db4xYSeAr6+cFQGHfspHzDUdYxswDnUW0L5Ov63UuJiSr8NMSpyaChO4u1n0MXUvVPtrN6A=="], @@ -1272,6 +1270,10 @@ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], @@ -1352,8 +1354,6 @@ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], - "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], @@ -1396,9 +1396,9 @@ "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + "react-shiki": ["react-shiki@0.9.1", "", { "dependencies": { "clsx": "^2.1.1", "dequal": "^2.0.3", "hast-util-to-jsx-runtime": "^2.3.6", "shiki": "^3.11.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@types/react": ">=16.8.0", "@types/react-dom": ">=16.8.0", "react": ">= 16.8.0", "react-dom": ">= 16.8.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ln1PnISi7WaSlheSBRdxVruVbU1zMUkCmxe+vmbIvZSsHdfvOF5NBOgf1h4cCr6OjdR0dLAxmPKcx3tobdyxVA=="], - "react-syntax-highlighter": ["react-syntax-highlighter@16.1.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.30.0", "refractor": "^5.0.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg=="], + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], "react-textarea-autosize": ["react-textarea-autosize@8.5.9", "", { "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A=="], @@ -1410,7 +1410,11 @@ "rechoir": ["rechoir@0.6.2", "", { "dependencies": { "resolve": "^1.1.6" } }, "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw=="], - "refractor": ["refractor@5.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^9.0.0", "parse-entities": "^4.0.0" } }, "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw=="], + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], "release-it": ["release-it@19.1.0", "", { "dependencies": { "@nodeutils/defaults-deep": "1.1.0", "@octokit/rest": "22.0.0", "@phun-ky/typeof": "2.0.3", "async-retry": "1.3.3", "c12": "3.3.1", "ci-info": "^4.3.0", "eta": "4.0.1", "git-url-parse": "16.1.0", "inquirer": "12.9.6", "issue-parser": "7.0.1", "lodash.merge": "4.6.2", "mime-types": "3.0.1", "new-github-release-url": "2.0.0", "open": "10.2.0", "ora": "9.0.0", "os-name": "6.1.0", "proxy-agent": "6.5.0", "semver": "7.7.2", "tinyglobby": "0.2.15", "undici": "6.21.3", "url-join": "5.0.0", "wildcard-match": "5.1.4", "yargs-parser": "21.1.1" }, "bin": { "release-it": "bin/release-it.js" } }, "sha512-XgAIZVKgHgOBUUoLFrUfXHG4cUDiXPWFW0AHIAgk17Tk9JbIjz9YyAU8MyQer7dWkfG5IpNU9QH8tViQZGfhaw=="], @@ -1480,6 +1484,8 @@ "shelljs": ["shelljs@0.8.5", "", { "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", "rechoir": "^0.6.2" }, "bin": { "shjs": "bin/shjs" } }, "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow=="], + "shiki": ["shiki@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/engine-javascript": "3.20.0", "@shikijs/engine-oniguruma": "3.20.0", "@shikijs/langs": "3.20.0", "@shikijs/themes": "3.20.0", "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg=="], + "shx": ["shx@0.3.4", "", { "dependencies": { "minimist": "^1.2.3", "shelljs": "^0.8.5" }, "bin": { "shx": "lib/cli.js" } }, "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g=="], "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], diff --git a/web/package.json b/web/package.json index 742cb057..31b45042 100644 --- a/web/package.json +++ b/web/package.json @@ -19,8 +19,7 @@ "diff": "^7.0.0", "react": "^19.2.3", "react-dom": "^19.2.3", - "react-markdown": "^10.1.0", - "react-syntax-highlighter": "^16.1.0", + "react-shiki": "^0.9.1", "react-textarea-autosize": "^8.5.9", "remark-gfm": "^4.0.1", "socket.io-client": "^4.8.1", @@ -29,7 +28,6 @@ "devDependencies": { "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", - "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", diff --git a/web/src/components/AssistantChat/messages/AssistantMessage.tsx b/web/src/components/AssistantChat/messages/AssistantMessage.tsx index 028f10c4..9c91d7f5 100644 --- a/web/src/components/AssistantChat/messages/AssistantMessage.tsx +++ b/web/src/components/AssistantChat/messages/AssistantMessage.tsx @@ -1,35 +1,7 @@ -import type { SyntaxHighlighterProps } from '@assistant-ui/react-markdown' -import { MarkdownTextPrimitive } from '@assistant-ui/react-markdown' import { MessagePrimitive, useAssistantState } from '@assistant-ui/react' -import remarkGfm from 'remark-gfm' -import { CodeBlock } from '@/components/CodeBlock' +import { MarkdownText } from '@/components/assistant-ui/markdown-text' import { HappyToolMessage } from '@/components/AssistantChat/messages/ToolMessage' -function MarkdownSyntaxHighlighter(props: SyntaxHighlighterProps) { - return ( - - ) -} - -const MARKDOWN_PLUGINS = [remarkGfm] -const MARKDOWN_COMPONENTS = { - SyntaxHighlighter: MarkdownSyntaxHighlighter -} as const - -function MarkdownText() { - return ( - - ) -} - const TOOL_COMPONENTS = { Fallback: HappyToolMessage } as const diff --git a/web/src/components/CodeBlock.tsx b/web/src/components/CodeBlock.tsx index 224c9203..11181949 100644 --- a/web/src/components/CodeBlock.tsx +++ b/web/src/components/CodeBlock.tsx @@ -1,41 +1,20 @@ +import type { Themes } from 'react-shiki/web' import { useMemo, useState } from 'react' -import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-async-light' -import oneDark from 'react-syntax-highlighter/dist/esm/styles/prism/one-dark' -import oneLight from 'react-syntax-highlighter/dist/esm/styles/prism/one-light' +import { useShikiHighlighter } from 'react-shiki/web' import { getTelegramWebApp } from '@/hooks/useTelegram' -import { useTheme } from '@/hooks/useTheme' -type SyntaxHighlighterWithSupportedLanguages = typeof SyntaxHighlighter & { - supportedLanguages?: string[] -} - -const SUPPORTED_LANGUAGE_LOOKUP = new Map( - ((SyntaxHighlighter as SyntaxHighlighterWithSupportedLanguages).supportedLanguages ?? []).map( - (lang: string): [string, string] => [canonicalizeLanguage(lang), lang] - ) -) - -function canonicalizeLanguage(value: string): string { - return value - .trim() - .replace(/^language-/, '') - .toLowerCase() - .replace(/#/g, 'sharp') - .replace(/\+/g, 'p') - .replace(/[^a-z0-9]/g, '') +const SHIKI_THEMES: Themes = { + light: 'github-light', + dark: 'github-dark', } function normalizeLanguage(language?: string): string { const raw = language?.trim() if (!raw) return 'text' - - const canonical = canonicalizeLanguage(raw) - if (!canonical || canonical === 'text' || canonical === 'plaintext' || canonical === 'txt') return 'text' - - const supported = SUPPORTED_LANGUAGE_LOOKUP.get(canonical) - if (supported) return supported - - return raw.startsWith('language-') ? raw.slice('language-'.length) : raw + const cleaned = raw.startsWith('language-') ? raw.slice('language-'.length) : raw + const canonical = cleaned.toLowerCase() + if (canonical === 'text' || canonical === 'plaintext' || canonical === 'txt') return 'text' + return cleaned } function safeCopyToClipboard(text: string): Promise { @@ -91,10 +70,15 @@ export function CodeBlock(props: { }) { const showCopyButton = props.showCopyButton ?? true const normalizedLanguage = useMemo(() => normalizeLanguage(props.language), [props.language]) - const { isDark } = useTheme() const [copied, setCopied] = useState(false) + const highlighted = useShikiHighlighter(props.code, normalizedLanguage, SHIKI_THEMES, { + delay: 75, + outputFormat: 'react', + structure: 'inline', + }) + const handleCopy = async () => { try { await safeCopyToClipboard(props.code) @@ -106,8 +90,6 @@ export function CodeBlock(props: { } } - const syntaxTheme = isDark ? oneDark : oneLight - return (
{showCopyButton ? ( @@ -121,16 +103,18 @@ export function CodeBlock(props: { ) : null} -
- - {props.code} - -
+
+                {typeof highlighted === 'string' ? (
+                    
+                ) : (
+                    
+                        {highlighted ?? props.code}
+                    
+                )}
+            
) } diff --git a/web/src/components/MarkdownRenderer.tsx b/web/src/components/MarkdownRenderer.tsx index 99f9a310..106a5ce5 100644 --- a/web/src/components/MarkdownRenderer.tsx +++ b/web/src/components/MarkdownRenderer.tsx @@ -1,50 +1,30 @@ -import ReactMarkdown, { Components } from 'react-markdown' -import remarkGfm from 'remark-gfm' -import { CodeBlock } from '@/components/CodeBlock' - -function getLanguageFromClassName(className?: string): string | null { - if (!className) return null - for (const token of className.split(/\s+/g)) { - if (token.startsWith('language-')) { - return token.slice('language-'.length) || null - } - } - return null -} - -const defaultComponents: Components = { - code({ className, children, ...props }) { - const language = getLanguageFromClassName(className) || 'text' - const code = String(children).replace(/\n$/, '') - - if (language !== 'text' || code.includes('\n')) { - return ( - - ) - } - - return {children} - }, -} +import type { MarkdownTextPrimitiveProps } from '@assistant-ui/react-markdown' +import { MarkdownTextPrimitive } from '@assistant-ui/react-markdown' +import { TextMessagePartProvider } from '@assistant-ui/react' +import { MARKDOWN_PLUGINS, defaultComponents } from '@/components/assistant-ui/markdown-text' +import { cn } from '@/lib/utils' interface MarkdownRendererProps { content: string - components?: Components + components?: MarkdownTextPrimitiveProps['components'] } -export function MarkdownRenderer({ content, components }: MarkdownRendererProps) { +function MarkdownContent(props: MarkdownRendererProps) { + const mergedComponents = props.components + ? { ...defaultComponents, ...props.components } + : defaultComponents + return ( -
- - {content} - -
+ + + ) } + +export function MarkdownRenderer(props: MarkdownRendererProps) { + return +} diff --git a/web/src/components/assistant-ui/markdown-text.tsx b/web/src/components/assistant-ui/markdown-text.tsx new file mode 100644 index 00000000..2f52761a --- /dev/null +++ b/web/src/components/assistant-ui/markdown-text.tsx @@ -0,0 +1,280 @@ +import '@assistant-ui/react-markdown/styles/dot.css' +import type { ComponentPropsWithoutRef } from 'react' +import { useState } from 'react' +import { + MarkdownTextPrimitive, + unstable_memoizeMarkdownComponents as memoizeMarkdownComponents, + useIsMarkdownCodeBlock, + type CodeHeaderProps, +} from '@assistant-ui/react-markdown' +import remarkGfm from 'remark-gfm' +import { getTelegramWebApp } from '@/hooks/useTelegram' +import { cn } from '@/lib/utils' +import { SyntaxHighlighter } from '@/components/assistant-ui/shiki-highlighter' + +export const MARKDOWN_PLUGINS = [remarkGfm] + +function safeCopyToClipboard(text: string): Promise { + if (navigator.clipboard?.writeText) { + return navigator.clipboard.writeText(text) + } + return Promise.reject(new Error('Clipboard API not available')) +} + +function CopyIcon(props: { className?: string }) { + return ( + + + + + ) +} + +function CheckIcon(props: { className?: string }) { + return ( + + + + ) +} + +function CodeHeader(props: CodeHeaderProps) { + const [copied, setCopied] = useState(false) + + const language = props.language && props.language !== 'unknown' ? props.language : '' + + const handleCopy = async () => { + try { + await safeCopyToClipboard(props.code) + getTelegramWebApp()?.HapticFeedback?.notificationOccurred('success') + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } catch { + getTelegramWebApp()?.HapticFeedback?.notificationOccurred('error') + } + } + + return ( +
+
+ {language} +
+ +
+ ) +} + +function Pre(props: ComponentPropsWithoutRef<'pre'>) { + return ( +
+    )
+}
+
+function Code(props: ComponentPropsWithoutRef<'code'>) {
+    const isCodeBlock = useIsMarkdownCodeBlock()
+
+    if (isCodeBlock) {
+        return (
+            
+        )
+    }
+
+    return (
+        
+    )
+}
+
+function A(props: ComponentPropsWithoutRef<'a'>) {
+    const rel = props.target === '_blank' ? (props.rel ?? 'noreferrer') : props.rel
+
+    return (
+        
+    )
+}
+
+function Paragraph(props: ComponentPropsWithoutRef<'p'>) {
+    return 

+} + +function Blockquote(props: ComponentPropsWithoutRef<'blockquote'>) { + return ( +

+ ) +} + +function UnorderedList(props: ComponentPropsWithoutRef<'ul'>) { + return
    +} + +function OrderedList(props: ComponentPropsWithoutRef<'ol'>) { + return
      +} + +function ListItem(props: ComponentPropsWithoutRef<'li'>) { + return
    1. +} + +function Hr(props: ComponentPropsWithoutRef<'hr'>) { + return
      +} + +function Table(props: ComponentPropsWithoutRef<'table'>) { + return +} + +function Thead(props: ComponentPropsWithoutRef<'thead'>) { + return +} + +function Tbody(props: ComponentPropsWithoutRef<'tbody'>) { + return +} + +function Tr(props: ComponentPropsWithoutRef<'tr'>) { + return +} + +function Th(props: ComponentPropsWithoutRef<'th'>) { + return ( +
      + ) +} + +function Td(props: ComponentPropsWithoutRef<'td'>) { + return +} + +function H1(props: ComponentPropsWithoutRef<'h1'>) { + return

      +} + +function H2(props: ComponentPropsWithoutRef<'h2'>) { + return

      +} + +function H3(props: ComponentPropsWithoutRef<'h3'>) { + return

      +} + +function H4(props: ComponentPropsWithoutRef<'h4'>) { + return

      +} + +function H5(props: ComponentPropsWithoutRef<'h5'>) { + return

      +} + +function H6(props: ComponentPropsWithoutRef<'h6'>) { + return
      +} + +function Strong(props: ComponentPropsWithoutRef<'strong'>) { + return +} + +function Em(props: ComponentPropsWithoutRef<'em'>) { + return +} + +function Image(props: ComponentPropsWithoutRef<'img'>) { + return +} + +export const defaultComponents = memoizeMarkdownComponents({ + SyntaxHighlighter, + CodeHeader, + pre: Pre, + code: Code, + h1: H1, + h2: H2, + h3: H3, + h4: H4, + h5: H5, + h6: H6, + a: A, + p: Paragraph, + strong: Strong, + em: Em, + blockquote: Blockquote, + ul: UnorderedList, + ol: OrderedList, + li: ListItem, + hr: Hr, + table: Table, + thead: Thead, + tbody: Tbody, + tr: Tr, + th: Th, + td: Td, + img: Image, +} as const) + +export function MarkdownText() { + return ( + + ) +} diff --git a/web/src/components/assistant-ui/shiki-highlighter.tsx b/web/src/components/assistant-ui/shiki-highlighter.tsx new file mode 100644 index 00000000..82f9b4dc --- /dev/null +++ b/web/src/components/assistant-ui/shiki-highlighter.tsx @@ -0,0 +1,24 @@ +import type { SyntaxHighlighterProps } from '@assistant-ui/react-markdown' +import ShikiHighlighter from 'react-shiki/web' + +const SHIKI_THEMES = { + light: 'github-light', + dark: 'github-dark', +} as const + +export function SyntaxHighlighter(props: SyntaxHighlighterProps) { + return ( +
      + + {props.code} + +
      + ) +} diff --git a/web/src/index.css b/web/src/index.css index 79c53362..ef0fc8a0 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -92,6 +92,33 @@ body { .markdown-content table { border-collapse: collapse; width: 100%; } .markdown-content th, .markdown-content td { border: 1px solid var(--app-border); padding: 0.25rem 0.5rem; } +/* assistant-ui markdown + shiki */ +.aui-md-codeblock pre.shiki { + margin: 0; + padding: 0.5rem; + overflow: auto; + background-color: transparent !important; + font-size: 0.75rem; +} + +.aui-md-codeblock pre.shiki code { + display: block; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +.aui-md-codeblock .shiki, +.aui-md-codeblock .shiki span { + background-color: transparent !important; +} + +html[data-theme="dark"] .shiki, +html[data-theme="dark"] .shiki span { + color: var(--shiki-dark) !important; + font-style: var(--shiki-dark-font-style) !important; + font-weight: var(--shiki-dark-font-weight) !important; + text-decoration: var(--shiki-dark-text-decoration) !important; +} + /* Ultrathink rainbow sparkle effect - per-letter wave animation */ @keyframes rainbow-wave { 0% { color: #ff5555; } /* red */