sync with main

This commit is contained in:
Pavel committed 2026-09-26 14:03:40 +04:00
commit 6bc5467c08
9 files changed
+1399 -181

No files matched your search

+1152 -74
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -8,7 +8,7 @@
"license": "MIT",
"dependencies": {
"@vercel/analytics": "^2.0.1",
"docsgpt-react": "^0.7.1",
"docsgpt-react": "^0.8.0",
"next": "^16.3.5",
"next-themes": "^0.4.6",
"nextra": "^4.6.1",
+3 -94
View File
@@ -1,12 +1,12 @@
{
"name": "docsgpt",
"version": "0.7.1",
"version": "0.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "docsgpt",
"version": "0.7.1",
"version": "0.8.0",
"license": "MIT",
"dependencies": {
"@babel/plugin-transform-flow-strip-types": "^8.0.1",
@@ -69,7 +69,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.5.tgz",
"integrity": "sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -79,7 +78,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.5.tgz",
"integrity": "sha512-2/oWkgTbBYoqioCWAE4XJobOrzwxTDa5/XjDP3tJ1BhDr/owcd9qnXBp4xc3/2G5X4bvXM04nrxbRJxFcXAxFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^8.0.0",
@@ -111,7 +109,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz",
"integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^8.0.0",
@@ -125,7 +122,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
"integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -135,7 +131,6 @@
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
"integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -145,7 +140,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.5.tgz",
"integrity": "sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^8.0.0",
@@ -159,14 +153,12 @@
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@babel/generator": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.5.tgz",
"integrity": "sha512-f/TuhuMAxJqhwxEGNsJrswuG9VHmh0oNFoQoo6TbpgtFAz9wYZXcTAcWZMHfp7ljesr0RG04bp3Aos9GI59L7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^8.0.5",
@@ -184,7 +176,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
"integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -194,7 +185,6 @@
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
"integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -204,7 +194,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.5.tgz",
"integrity": "sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^8.0.0",
@@ -265,7 +254,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.5.tgz",
"integrity": "sha512-Qk8ahMGooH5mz6uuhoDvfZGkUf/Mf3RTBucVVl4MKx4LKMTv872TeW8O92h15iVtlN8wAROBIpI1aV6x1z0LCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^8.0.5",
@@ -340,7 +328,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz",
"integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -635,7 +622,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz",
"integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -694,7 +680,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.5.tgz",
"integrity": "sha512-fQtPOXjYOYv85PIdwotp2TJGVYOycX0PQq+l844fFAxOULtBy8BVF35GyeueX0r4KvDthqPH5xAI1clQPk/2uA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^8.0.0",
@@ -708,7 +693,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
"integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -718,7 +702,6 @@
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
"integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -728,7 +711,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.5.tgz",
"integrity": "sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^8.0.0",
@@ -742,7 +724,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.5.tgz",
"integrity": "sha512-51RXvQNFakaS0bTpYiGkxNbUVwkPO4kONv6EVLorZABxsx+KZ6Z7uSYvi/wmKS/+X+rfj9RvOw0/ZNh+cmI0Rw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^8.0.5"
@@ -758,7 +739,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
"integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -768,7 +748,6 @@
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
"integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -778,7 +757,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.5.tgz",
"integrity": "sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^8.0.0",
@@ -2054,7 +2032,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz",
"integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^8.0.0",
@@ -2069,7 +2046,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz",
"integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^8.0.0",
@@ -2083,7 +2059,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
"integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -2093,7 +2068,6 @@
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
"integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -2103,7 +2077,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.5.tgz",
"integrity": "sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^8.0.0",
@@ -2117,14 +2090,12 @@
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@babel/traverse": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.5.tgz",
"integrity": "sha512-XFfnuvapSc/vJOcUO7kwORSvpBIvraofKEZ2dhT0PjiF21BRCD7YbAFC8UEeDJNeLoQz82/gVqzgX5hCzkCbdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^8.0.0",
@@ -2143,7 +2114,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz",
"integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^8.0.0",
@@ -2157,7 +2127,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
"integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -2167,7 +2136,6 @@
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
"integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
@@ -2177,7 +2145,6 @@
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.5.tgz",
"integrity": "sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^8.0.0",
@@ -2191,7 +2158,6 @@
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@babel/types": {
@@ -2508,7 +2474,6 @@
"version": "0.4.0-beta.0",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz",
"integrity": "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.6.0-beta.0",
@@ -2582,7 +2547,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2596,7 +2560,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2610,7 +2573,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2624,7 +2586,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2638,7 +2599,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2652,7 +2612,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2680,7 +2639,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2694,7 +2652,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2708,7 +2665,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2722,7 +2678,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2736,7 +2691,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2750,7 +2704,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2784,7 +2737,6 @@
"version": "2.16.4",
"resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.16.4.tgz",
"integrity": "sha512-+uCyeElSga2MBbmbXpIj/WVKH7TByCrKaxtHbelfKKIJpYMgEHVjO4cuc7GUfTrUAmRUS8ZGvnX7Etgq6/jQhw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@parcel/fs": "2.16.4",
@@ -2889,7 +2841,6 @@
"version": "2.16.4",
"resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.16.4.tgz",
"integrity": "sha512-a0CgrW5A5kwuSu5J1RFRoMQaMs9yagvfH2jJMYVw56+/7NRI4KOtu612SG9Y1ERWfY55ZwzyFxtLWvD6LO+Anw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mischnic/json-sourcemap": "^0.1.1",
@@ -2930,7 +2881,6 @@
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -3024,7 +2974,6 @@
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.6.4.tgz",
"integrity": "sha512-Cj9yV+/k88kFhE+D+gz0YuNRpvNOCVDskO9pFqkcQhGbsGq6kg2XpZ9V7HlYraih31xf8Vb589bZOwjKIiHixQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@parcel/feature-flags": "2.16.4",
@@ -3241,7 +3190,6 @@
"version": "2.16.4",
"resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.16.4.tgz",
"integrity": "sha512-obWv9gZgdnkT3Kd+fBkKjhdNEY7zfOP5gVaox5i4nQstVCaVnDlMv5FwLEXwehL+WbwEcGyEGGxOHHkAFKk7Cg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@parcel/diagnostic": "2.16.4",
@@ -3269,7 +3217,6 @@
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -5259,7 +5206,6 @@
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.16.2.tgz",
"integrity": "sha512-95I4kiSMeveI/Mhi+tE4fiWcWLUMfzfKrk0jtr8LRMqHgOgq+xHS+zExkDqoO4b5OeeuXHMWVdD5MeP3X6sULw==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5303,7 +5249,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5320,7 +5265,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5337,7 +5281,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -5354,7 +5297,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5371,7 +5313,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5388,7 +5329,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5405,7 +5345,6 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5422,7 +5361,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5439,7 +5377,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5456,7 +5393,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5473,7 +5409,6 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5490,7 +5425,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -5504,14 +5438,13 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@swc/helpers": {
"version": "0.5.23",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
@@ -5521,7 +5454,6 @@
"version": "0.1.28",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz",
"integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3"
@@ -5548,14 +5480,12 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz",
"integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/jsesc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz",
"integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
@@ -6145,7 +6075,6 @@
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
"integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "^5.0.1"
@@ -6357,7 +6286,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8"
@@ -6798,7 +6726,6 @@
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -6811,7 +6738,6 @@
"version": "11.0.7",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
"integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"dotenv": "^16.4.5"
@@ -6848,7 +6774,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz",
"integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -7903,7 +7828,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
"integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@@ -8788,7 +8712,6 @@
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.8.5.tgz",
"integrity": "sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -8814,7 +8737,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
"dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
@@ -8873,7 +8795,6 @@
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
"integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
@@ -8983,7 +8904,6 @@
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.12.1.tgz",
"integrity": "sha512-4EUH9tQHnMmEgzW/MdAP0KIfa1T9AF+htl0ffe2n5vb2EKn9y2co8ccpgWko6S52Jy1PQZKwRnx5/KkYjtd9MQ==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"msgpackr-extract": "^3.0.2"
@@ -8993,7 +8913,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz",
"integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -9016,7 +8935,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
@@ -9027,7 +8945,6 @@
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -9085,7 +9002,6 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz",
"integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.1"
@@ -9100,7 +9016,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -9246,7 +9161,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz",
"integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==",
"dev": true,
"funding": [
"https://github.com/sponsors/sxzz",
"https://opencollective.com/debug"
@@ -9278,7 +9192,6 @@
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz",
"integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==",
"dev": true,
"license": "MIT"
},
"node_modules/own-keys": {
@@ -9726,7 +9639,6 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
@@ -10382,7 +10294,6 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -10514,7 +10425,6 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/verkit/-/verkit-0.3.2.tgz",
"integrity": "sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.12.0"
@@ -10527,7 +10437,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz",
"integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==",
"dev": true,
"license": "MIT"
},
"node_modules/which": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "docsgpt",
"version": "0.7.1",
"version": "0.8.0",
"private": false,
"description": "DocsGPT 🦖 is an innovative open-source tool designed to simplify the retrieval of information from project documentation using advanced GPT models 🤖.",
"source": "./src/index.html",
@@ -24,7 +24,10 @@ import {
normalizeExtensions,
useAttachments,
} from '../hooks/useAttachments';
import { useBackDismiss } from '../hooks/useBackDismiss';
import { useDictation } from '../hooks/useDictation';
import { useVisualViewportBounds } from '../hooks/useVisualViewportBounds';
import { isTouchPrimary } from '../utils/helper';
import {
AttachButton,
AttachmentChips,
@@ -304,7 +307,8 @@ const WidgetContainer = styled.div<{ $modal?: boolean }>`
@media only screen and (max-width: 768px) {
right: 0;
bottom: 0;
/* Keyboard inset; see useVisualViewportBounds. */
bottom: var(--dgpt-vv-bottom, 0px);
&.modal {
transform: none;
}
@@ -325,6 +329,9 @@ const StyledContainer = styled.div<{ $isOpen: boolean }>`
background-color: ${(props) => props.theme.primary.bg};
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
/* all: initial re-enables Safari's text auto-inflation. */
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
display: flex;
padding: 0;
overflow: hidden;
@@ -368,10 +375,13 @@ const StyledContainer = styled.div<{ $isOpen: boolean }>`
@media only screen and (max-width: 768px) {
width: 100vw;
height: 100dvh;
/* 100dvh unless a keyboard is covering part of it. */
height: var(--dgpt-vv-height, 100dvh);
max-width: 100vw;
max-height: 100dvh;
max-height: var(--dgpt-vv-height, 100dvh);
border-radius: 0;
/* Expanding is desktop-only; easing after the keyboard reads as lag. */
transition: none;
}
`;
@@ -847,7 +857,7 @@ const StatusLine = styled.div`
min-width: 0;
max-width: 100%;
font-size: 12px;
font-family: sans-serif;
font-family: inherit;
color: ${(props) => props.theme.secondary.text};
`;
const StatusDot = styled.span`
@@ -892,7 +902,7 @@ const Thought = styled.div`
padding-left: 10px;
border-left: 2px solid ${(props) => props.theme.hairline};
font-size: 12px;
font-family: sans-serif;
font-family: inherit;
font-style: italic;
line-height: 1.5;
white-space: pre-wrap;
@@ -1034,6 +1044,11 @@ const StyledTextarea = styled.textarea<{ $hidden?: boolean }>`
text-align: left;
color: ${(props) => props.theme.secondary.text};
}
/* iOS Safari zooms the page in on any field under 16px. */
@media (pointer: coarse) {
font-size: 16px;
}
`;
const StyledButton = styled.button`
display: flex;
@@ -1323,6 +1338,7 @@ export const WidgetCore = ({
const [isDraggingFiles, setIsDraggingFiles] = React.useState(false);
const abortRef = useRef<AbortController | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const conversationRef = useRef<HTMLDivElement | null>(null);
const endMessageRef = React.useRef<HTMLDivElement | null>(null);
const promptRef = React.useRef<HTMLTextAreaElement | null>(null);
@@ -1330,6 +1346,20 @@ export const WidgetCore = ({
// dragenter/dragleave fire per child crossed, hence a depth count.
const dragDepthRef = React.useRef(0);
// On a phone the composer waits to be tapped: autofocus costs half the
// display to a keyboard nobody asked for.
const [touchPrimary] = React.useState(isTouchPrimary);
// Typed as a click handler, but no close path reads the event.
const closePanel = () => {
(handleClose as ((event?: React.MouseEvent) => void) | undefined)?.();
};
// The node exists only once mounted.
useVisualViewportBounds(isOpen && mounted, containerRef);
// Touch only: nothing fills the screen on desktop.
useBackDismiss(isOpen && touchPrimary, closePanel);
// An empty list disables attachments.
const acceptedExtensions = React.useMemo(
() => normalizeExtensions(allowedFileExtensions),
@@ -1440,6 +1470,20 @@ export const WidgetCore = ({
else scrollToLatest();
}, [queries.length, queries[queries.length - 1]?.response, status]);
// Shortening the panel leaves scrollTop where it was, dropping the tail of
// the latest answer below the fold. A frame later, once the height lands.
React.useEffect(() => {
const viewport = window.visualViewport;
if (!isOpen || !viewport) return;
const repin = () =>
window.requestAnimationFrame(() => {
const el = conversationRef.current;
if (el && isPinnedToLatest) el.scrollTop = el.scrollHeight;
});
viewport.addEventListener('resize', repin);
return () => viewport.removeEventListener('resize', repin);
}, [isOpen, isPinnedToLatest]);
const setFeedbackAt = (index: number, value?: FEEDBACK) =>
setQueries((prev: Query[]) =>
prev.map((q, i) => {
@@ -1847,6 +1891,7 @@ export const WidgetCore = ({
{isOpen && size === 'large' && <Overlay onClick={handleClose} />}
{
<WidgetContainer
ref={containerRef}
className={`${size !== 'large' ? (isOpen ? 'open' : 'close') : 'modal'}`}
$modal={size === 'large'}
>
@@ -2114,7 +2159,7 @@ export const WidgetCore = ({
$hidden={isDictating}
id="chatInput"
ref={promptRef}
autoFocus
autoFocus={!touchPrimary}
onInput={handleUserInput}
value={prompt}
onChange={handlePromptChange}
@@ -4,11 +4,13 @@ import { WidgetCore } from './DocsGPTWidget';
import { DEFAULT_AVATAR } from './defaultAvatar';
import { radii, themes } from './tokens';
import { MicButton, VoiceWaveform } from './ComposerControls';
import { useBackDismiss } from '../hooks/useBackDismiss';
import { useDictation } from '../hooks/useDictation';
import { useVisualViewportBounds } from '../hooks/useVisualViewportBounds';
import { SearchBarProps } from '@/types';
import { getSearchResults } from '../requests/searchAPI';
import { Result } from '@/types';
import { getOS, processMarkdownString } from '../utils/helper';
import { getOS, isTouchPrimary, processMarkdownString } from '../utils/helper';
import DOMPurify from 'dompurify';
import {
CodeIcon,
@@ -29,6 +31,9 @@ const Main = styled.div`
all: initial;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
/* all: initial re-enables Safari's text auto-inflation. */
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
`;
const SearchButton = styled.button<{ $inputWidth: string }>`
@@ -78,7 +83,8 @@ const SearchOverlay = styled.div`
const SearchResults = styled.div`
position: fixed;
top: 50%;
/* Centred on the strip left above an open keyboard, else the viewport. */
top: calc(var(--dgpt-vv-top, 0px) + var(--dgpt-vv-height, 100%) / 2);
left: 50%;
z-index: 100;
transform: translate(-50%, -50%);
@@ -101,6 +107,9 @@ const SearchResults = styled.div`
@media only screen and (max-width: 768px) {
width: 90vw;
height: 80vh;
/* Into the strip above an open keyboard, less a gutter at each end. With
no keyboard the fallback resolves against the viewport and never bites. */
max-height: calc(var(--dgpt-vv-height, 100%) - 32px);
}
`;
@@ -336,6 +345,7 @@ export const SearchBar = ({
const [isWidgetOpen, setIsWidgetOpen] = React.useState<boolean>(false);
const inputRef = React.useRef<HTMLInputElement>(null);
const containerRef = React.useRef<HTMLInputElement>(null);
const resultsRef = React.useRef<HTMLDivElement | null>(null);
const [isResultVisible, setIsResultVisible] = React.useState<boolean>(false);
const [results, setResults] = React.useState<Result[]>([]);
const debounceTimeout = React.useRef<ReturnType<typeof setTimeout> | null>(
@@ -358,7 +368,13 @@ export const SearchBar = ({
onEnd: focusSearchInput,
});
const browserOS = getOS();
const isTouch = 'ontouchstart' in window;
// A touchscreen laptop still has Ctrl+K; this asks if touch is the only
// way in.
const [isTouch] = React.useState(isTouchPrimary);
useVisualViewportBounds(isResultVisible, resultsRef);
// Touch only: nothing fills the screen on desktop.
useBackDismiss(isResultVisible && isTouch, () => setIsResultVisible(false));
const getKeyboardInstruction = () => {
if (isResultVisible) return 'Enter';
@@ -463,7 +479,7 @@ export const SearchBar = ({
{isResultVisible && (
<>
<SearchOverlay onClick={() => setIsResultVisible(false)} />
<SearchResults>
<SearchResults ref={resultsRef}>
<SearchHeader>
{dictation.isDictating && (
<VoiceWaveform
@@ -486,7 +502,8 @@ export const SearchBar = ({
}}
onKeyDown={(e) => handleKeyDown(e)}
placeholder={placeholder}
autoFocus
/* A keyboard would cover the results. */
autoFocus={!isTouch}
/>
{dictation.available && (
<MicButton
@@ -0,0 +1,84 @@
import React from 'react';
/** Marks our own history entry, so only an entry we pushed is popped. */
const HISTORY_MARKER = 'dgptOverlay';
const layers: Array<() => void> = [];
let entryPushed = false;
let releaseTimer: ReturnType<typeof setTimeout> | 0 = 0;
let releasing = false;
const ours = () =>
(window.history.state as Record<string, unknown> | null)?.[HISTORY_MARKER] ===
true;
const arm = () => {
if (entryPushed) return;
entryPushed = true;
if (!ours()) window.history.pushState({ [HISTORY_MARKER]: true }, '');
window.addEventListener('popstate', handlePopState);
};
const handlePopState = () => {
if (releasing) {
releasing = false;
entryPushed = ours();
if (layers.length > 0) arm();
else if (!entryPushed)
window.removeEventListener('popstate', handlePopState);
return;
}
// A host entry stacked above ours pops first, leaving ours in place.
entryPushed = ours();
const remaining = layers.length > 1;
layers[layers.length - 1]?.();
if (remaining) arm();
};
const register = (dismiss: () => void) => {
layers.push(dismiss);
if (releaseTimer) {
clearTimeout(releaseTimer);
releaseTimer = 0;
}
arm();
};
const unregister = (dismiss: () => void) => {
const index = layers.lastIndexOf(dismiss);
if (index !== -1) layers.splice(index, 1);
if (layers.length > 0 || releaseTimer) return;
// The palette hands over to the chat panel in one commit, every cleanup
// before any setup, so release only once nothing reclaims it a task later.
releaseTimer = setTimeout(() => {
releaseTimer = 0;
if (layers.length > 0) return;
entryPushed = false;
// Closing by button or overlay would otherwise leave the entry behind.
if (ours()) {
releasing = true;
window.history.back();
return;
}
window.removeEventListener('popstate', handlePopState);
});
};
/**
* Closes an open layer on the hardware back button instead of leaving the page.
* All layers share one history entry, and `onDismiss` is read through a ref.
*/
export function useBackDismiss(active: boolean, onDismiss: () => void): void {
const dismissRef = React.useRef(onDismiss);
React.useEffect(() => {
dismissRef.current = onDismiss;
});
React.useEffect(() => {
if (!active || typeof window === 'undefined' || !window.history) return;
const dismiss = () => dismissRef.current();
register(dismiss);
return () => unregister(dismiss);
}, [active]);
}
@@ -0,0 +1,76 @@
import React from 'react';
/** Below this the gap is the browser's own chrome, not a keyboard. */
const KEYBOARD_MIN_INSET_PX = 120;
/**
* Publishes the visual viewport as `--dgpt-vv-top`, `--dgpt-vv-bottom` and
* `--dgpt-vv-height` on the node while a keyboard covers it. Neither
* `position: fixed` nor `100dvh` notices one, so a full-screen panel keeps its
* height and slides its header off the display, close button and all. Written
* straight to the node, since these events fire every frame while the keyboard
* animates.
*/
export function useVisualViewportBounds(
active: boolean,
ref: React.RefObject<HTMLElement | null>,
): void {
React.useEffect(() => {
const viewport =
typeof window === 'undefined' ? null : window.visualViewport;
const node = ref.current;
if (!active || !viewport || !node) return;
let frame = 0;
let written = '';
const clear = () => {
if (!written) return;
written = '';
node.style.removeProperty('--dgpt-vv-top');
node.style.removeProperty('--dgpt-vv-height');
node.style.removeProperty('--dgpt-vv-bottom');
};
const apply = () => {
frame = 0;
// Pinch-zoom shrinks the viewport the same way but covers nothing.
if (viewport.scale > 1.01) {
clear();
return;
}
// A quirks-mode host reports its content height from `clientHeight`.
const layoutHeight = Math.min(
document.documentElement.clientHeight,
window.innerHeight,
);
const top = viewport.offsetTop;
const bottom = Math.max(0, layoutHeight - top - viewport.height);
if (top + bottom < KEYBOARD_MIN_INSET_PX) {
clear();
return;
}
const next = `${top}|${viewport.height}|${bottom}`;
if (next === written) return;
written = next;
node.style.setProperty('--dgpt-vv-top', `${top}px`);
node.style.setProperty('--dgpt-vv-height', `${viewport.height}px`);
node.style.setProperty('--dgpt-vv-bottom', `${bottom}px`);
};
const schedule = () => {
if (!frame) frame = window.requestAnimationFrame(apply);
};
apply();
viewport.addEventListener('resize', schedule);
viewport.addEventListener('scroll', schedule);
return () => {
if (frame) window.cancelAnimationFrame(frame);
viewport.removeEventListener('resize', schedule);
viewport.removeEventListener('scroll', schedule);
clear();
};
}, [active, ref]);
}
@@ -1,3 +1,12 @@
/**
* Whether touch is the only pointer: a phone or tablet, not a laptop with a
* touchscreen, which `'ontouchstart' in window` cannot tell apart.
*/
export const isTouchPrimary = (): boolean => {
if (typeof window === 'undefined' || !window.matchMedia) return false;
return window.matchMedia('(hover: none) and (pointer: coarse)').matches;
};
export const getOS = () => {
const platform = window.navigator.platform;
const userAgent = window.navigator.userAgent || window.navigator.vendor;