Spaces:
Running
Running
Add P5WithEditor component and improve UI/UX
Browse files- package-lock.json +104 -15
- package.json +2 -0
- public/index.html +85 -5
- src/App.scss +92 -4
- src/App.tsx +18 -3
- src/components/p5/P5Sketch.tsx +15 -6
- src/components/p5/P5WithEditor.tsx +617 -0
- src/components/p5/p5-with-editor.scss +203 -0
- src/components/side-panel/SidePanel.tsx +10 -4
package-lock.json
CHANGED
@@ -8,6 +8,7 @@
|
|
8 |
"name": "multimodal-live-api-web-console",
|
9 |
"version": "0.1.0",
|
10 |
"dependencies": {
|
|
|
11 |
"classnames": "^2.5.1",
|
12 |
"dotenv": "^16.4.1",
|
13 |
"dotenv-flow": "^4.1.0",
|
@@ -15,6 +16,7 @@
|
|
15 |
"express": "^4.18.2",
|
16 |
"http-proxy-middleware": "^3.0.3",
|
17 |
"lodash": "^4.17.21",
|
|
|
18 |
"react": "^18.3.1",
|
19 |
"react-dom": "^18.3.1",
|
20 |
"react-icons": "^5.3.0",
|
@@ -1993,7 +1995,7 @@
|
|
1993 |
"version": "0.8.1",
|
1994 |
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
1995 |
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
1996 |
-
"
|
1997 |
"dependencies": {
|
1998 |
"@jridgewell/trace-mapping": "0.3.9"
|
1999 |
},
|
@@ -2005,7 +2007,7 @@
|
|
2005 |
"version": "0.3.9",
|
2006 |
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
2007 |
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
2008 |
-
"
|
2009 |
"dependencies": {
|
2010 |
"@jridgewell/resolve-uri": "^3.0.3",
|
2011 |
"@jridgewell/sourcemap-codec": "^1.4.10"
|
@@ -2997,6 +2999,29 @@
|
|
2997 |
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
|
2998 |
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="
|
2999 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3000 |
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
3001 |
"version": "5.1.1-v1",
|
3002 |
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
@@ -3730,6 +3755,38 @@
|
|
3730 |
"url": "https://github.com/sponsors/gregberge"
|
3731 |
}
|
3732 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3733 |
"node_modules/@testing-library/jest-dom": {
|
3734 |
"version": "5.17.0",
|
3735 |
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
|
@@ -3847,25 +3904,25 @@
|
|
3847 |
"version": "1.0.11",
|
3848 |
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
3849 |
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
3850 |
-
"
|
3851 |
},
|
3852 |
"node_modules/@tsconfig/node12": {
|
3853 |
"version": "1.0.11",
|
3854 |
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
3855 |
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
3856 |
-
"
|
3857 |
},
|
3858 |
"node_modules/@tsconfig/node14": {
|
3859 |
"version": "1.0.3",
|
3860 |
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
3861 |
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
3862 |
-
"
|
3863 |
},
|
3864 |
"node_modules/@tsconfig/node16": {
|
3865 |
"version": "1.0.4",
|
3866 |
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
3867 |
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
3868 |
-
"
|
3869 |
},
|
3870 |
"node_modules/@types/aria-query": {
|
3871 |
"version": "5.0.4",
|
@@ -6141,7 +6198,7 @@
|
|
6141 |
"version": "1.1.1",
|
6142 |
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
6143 |
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
6144 |
-
"
|
6145 |
},
|
6146 |
"node_modules/cross-spawn": {
|
6147 |
"version": "7.0.5",
|
@@ -6978,6 +7035,17 @@
|
|
6978 |
"node": ">= 0.8"
|
6979 |
}
|
6980 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6981 |
"node_modules/destroy": {
|
6982 |
"version": "1.2.0",
|
6983 |
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
@@ -7050,7 +7118,7 @@
|
|
7050 |
"version": "4.0.2",
|
7051 |
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
7052 |
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
7053 |
-
"
|
7054 |
"engines": {
|
7055 |
"node": ">=0.3.1"
|
7056 |
}
|
@@ -11545,6 +11613,15 @@
|
|
11545 |
"yallist": "^3.0.2"
|
11546 |
}
|
11547 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11548 |
"node_modules/lz-string": {
|
11549 |
"version": "1.5.0",
|
11550 |
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
@@ -11588,7 +11665,7 @@
|
|
11588 |
"version": "1.3.6",
|
11589 |
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
11590 |
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
11591 |
-
"
|
11592 |
},
|
11593 |
"node_modules/makeerror": {
|
11594 |
"version": "1.0.12",
|
@@ -11777,6 +11854,13 @@
|
|
11777 |
"mkdirp": "bin/cmd.js"
|
11778 |
}
|
11779 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11780 |
"node_modules/ms": {
|
11781 |
"version": "2.1.3",
|
11782 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
@@ -15322,6 +15406,12 @@
|
|
15322 |
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
|
15323 |
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
|
15324 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
15325 |
"node_modules/static-eval": {
|
15326 |
"version": "2.0.2",
|
15327 |
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz",
|
@@ -16269,7 +16359,7 @@
|
|
16269 |
"version": "10.9.2",
|
16270 |
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
16271 |
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
16272 |
-
"
|
16273 |
"dependencies": {
|
16274 |
"@cspotcode/source-map-support": "^0.8.0",
|
16275 |
"@tsconfig/node10": "^1.0.7",
|
@@ -16312,7 +16402,7 @@
|
|
16312 |
"version": "8.3.4",
|
16313 |
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
16314 |
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
16315 |
-
"
|
16316 |
"dependencies": {
|
16317 |
"acorn": "^8.11.0"
|
16318 |
},
|
@@ -16324,7 +16414,7 @@
|
|
16324 |
"version": "4.1.3",
|
16325 |
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
16326 |
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
16327 |
-
"
|
16328 |
},
|
16329 |
"node_modules/tsconfig-paths": {
|
16330 |
"version": "3.15.0",
|
@@ -16509,7 +16599,6 @@
|
|
16509 |
"version": "5.6.3",
|
16510 |
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
16511 |
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
16512 |
-
"dev": true,
|
16513 |
"bin": {
|
16514 |
"tsc": "bin/tsc",
|
16515 |
"tsserver": "bin/tsserver"
|
@@ -16730,7 +16819,7 @@
|
|
16730 |
"version": "3.0.1",
|
16731 |
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
16732 |
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
16733 |
-
"
|
16734 |
},
|
16735 |
"node_modules/v8-to-istanbul": {
|
16736 |
"version": "8.1.1",
|
@@ -18139,7 +18228,7 @@
|
|
18139 |
"version": "3.1.1",
|
18140 |
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
18141 |
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
18142 |
-
"
|
18143 |
"engines": {
|
18144 |
"node": ">=6"
|
18145 |
}
|
|
|
8 |
"name": "multimodal-live-api-web-console",
|
9 |
"version": "0.1.0",
|
10 |
"dependencies": {
|
11 |
+
"@monaco-editor/react": "^4.7.0",
|
12 |
"classnames": "^2.5.1",
|
13 |
"dotenv": "^16.4.1",
|
14 |
"dotenv-flow": "^4.1.0",
|
|
|
16 |
"express": "^4.18.2",
|
17 |
"http-proxy-middleware": "^3.0.3",
|
18 |
"lodash": "^4.17.21",
|
19 |
+
"lucide-react": "^0.477.0",
|
20 |
"react": "^18.3.1",
|
21 |
"react-dom": "^18.3.1",
|
22 |
"react-icons": "^5.3.0",
|
|
|
1995 |
"version": "0.8.1",
|
1996 |
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
1997 |
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
1998 |
+
"devOptional": true,
|
1999 |
"dependencies": {
|
2000 |
"@jridgewell/trace-mapping": "0.3.9"
|
2001 |
},
|
|
|
2007 |
"version": "0.3.9",
|
2008 |
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
2009 |
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
2010 |
+
"devOptional": true,
|
2011 |
"dependencies": {
|
2012 |
"@jridgewell/resolve-uri": "^3.0.3",
|
2013 |
"@jridgewell/sourcemap-codec": "^1.4.10"
|
|
|
2999 |
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
|
3000 |
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="
|
3001 |
},
|
3002 |
+
"node_modules/@monaco-editor/loader": {
|
3003 |
+
"version": "1.5.0",
|
3004 |
+
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
|
3005 |
+
"integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==",
|
3006 |
+
"license": "MIT",
|
3007 |
+
"dependencies": {
|
3008 |
+
"state-local": "^1.0.6"
|
3009 |
+
}
|
3010 |
+
},
|
3011 |
+
"node_modules/@monaco-editor/react": {
|
3012 |
+
"version": "4.7.0",
|
3013 |
+
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
|
3014 |
+
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
|
3015 |
+
"license": "MIT",
|
3016 |
+
"dependencies": {
|
3017 |
+
"@monaco-editor/loader": "^1.5.0"
|
3018 |
+
},
|
3019 |
+
"peerDependencies": {
|
3020 |
+
"monaco-editor": ">= 0.25.0 < 1",
|
3021 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
3022 |
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
3023 |
+
}
|
3024 |
+
},
|
3025 |
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
3026 |
"version": "5.1.1-v1",
|
3027 |
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
|
|
3755 |
"url": "https://github.com/sponsors/gregberge"
|
3756 |
}
|
3757 |
},
|
3758 |
+
"node_modules/@testing-library/dom": {
|
3759 |
+
"version": "10.4.0",
|
3760 |
+
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
3761 |
+
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
|
3762 |
+
"dev": true,
|
3763 |
+
"license": "MIT",
|
3764 |
+
"peer": true,
|
3765 |
+
"dependencies": {
|
3766 |
+
"@babel/code-frame": "^7.10.4",
|
3767 |
+
"@babel/runtime": "^7.12.5",
|
3768 |
+
"@types/aria-query": "^5.0.1",
|
3769 |
+
"aria-query": "5.3.0",
|
3770 |
+
"chalk": "^4.1.0",
|
3771 |
+
"dom-accessibility-api": "^0.5.9",
|
3772 |
+
"lz-string": "^1.5.0",
|
3773 |
+
"pretty-format": "^27.0.2"
|
3774 |
+
},
|
3775 |
+
"engines": {
|
3776 |
+
"node": ">=18"
|
3777 |
+
}
|
3778 |
+
},
|
3779 |
+
"node_modules/@testing-library/dom/node_modules/aria-query": {
|
3780 |
+
"version": "5.3.0",
|
3781 |
+
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
3782 |
+
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
3783 |
+
"dev": true,
|
3784 |
+
"license": "Apache-2.0",
|
3785 |
+
"peer": true,
|
3786 |
+
"dependencies": {
|
3787 |
+
"dequal": "^2.0.3"
|
3788 |
+
}
|
3789 |
+
},
|
3790 |
"node_modules/@testing-library/jest-dom": {
|
3791 |
"version": "5.17.0",
|
3792 |
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
|
|
|
3904 |
"version": "1.0.11",
|
3905 |
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
3906 |
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
3907 |
+
"devOptional": true
|
3908 |
},
|
3909 |
"node_modules/@tsconfig/node12": {
|
3910 |
"version": "1.0.11",
|
3911 |
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
3912 |
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
3913 |
+
"devOptional": true
|
3914 |
},
|
3915 |
"node_modules/@tsconfig/node14": {
|
3916 |
"version": "1.0.3",
|
3917 |
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
3918 |
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
3919 |
+
"devOptional": true
|
3920 |
},
|
3921 |
"node_modules/@tsconfig/node16": {
|
3922 |
"version": "1.0.4",
|
3923 |
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
3924 |
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
3925 |
+
"devOptional": true
|
3926 |
},
|
3927 |
"node_modules/@types/aria-query": {
|
3928 |
"version": "5.0.4",
|
|
|
6198 |
"version": "1.1.1",
|
6199 |
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
6200 |
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
6201 |
+
"devOptional": true
|
6202 |
},
|
6203 |
"node_modules/cross-spawn": {
|
6204 |
"version": "7.0.5",
|
|
|
7035 |
"node": ">= 0.8"
|
7036 |
}
|
7037 |
},
|
7038 |
+
"node_modules/dequal": {
|
7039 |
+
"version": "2.0.3",
|
7040 |
+
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
7041 |
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
7042 |
+
"dev": true,
|
7043 |
+
"license": "MIT",
|
7044 |
+
"peer": true,
|
7045 |
+
"engines": {
|
7046 |
+
"node": ">=6"
|
7047 |
+
}
|
7048 |
+
},
|
7049 |
"node_modules/destroy": {
|
7050 |
"version": "1.2.0",
|
7051 |
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
|
|
7118 |
"version": "4.0.2",
|
7119 |
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
7120 |
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
7121 |
+
"devOptional": true,
|
7122 |
"engines": {
|
7123 |
"node": ">=0.3.1"
|
7124 |
}
|
|
|
11613 |
"yallist": "^3.0.2"
|
11614 |
}
|
11615 |
},
|
11616 |
+
"node_modules/lucide-react": {
|
11617 |
+
"version": "0.477.0",
|
11618 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.477.0.tgz",
|
11619 |
+
"integrity": "sha512-yCf7aYxerFZAbd8jHJxjwe1j7jEMPptjnaOqdYeirFnEy85cNR3/L+o0I875CYFYya+eEVzZSbNuRk8BZPDpVw==",
|
11620 |
+
"license": "ISC",
|
11621 |
+
"peerDependencies": {
|
11622 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
11623 |
+
}
|
11624 |
+
},
|
11625 |
"node_modules/lz-string": {
|
11626 |
"version": "1.5.0",
|
11627 |
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
|
|
11665 |
"version": "1.3.6",
|
11666 |
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
11667 |
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
11668 |
+
"devOptional": true
|
11669 |
},
|
11670 |
"node_modules/makeerror": {
|
11671 |
"version": "1.0.12",
|
|
|
11854 |
"mkdirp": "bin/cmd.js"
|
11855 |
}
|
11856 |
},
|
11857 |
+
"node_modules/monaco-editor": {
|
11858 |
+
"version": "0.52.2",
|
11859 |
+
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
|
11860 |
+
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
|
11861 |
+
"license": "MIT",
|
11862 |
+
"peer": true
|
11863 |
+
},
|
11864 |
"node_modules/ms": {
|
11865 |
"version": "2.1.3",
|
11866 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
|
|
15406 |
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
|
15407 |
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
|
15408 |
},
|
15409 |
+
"node_modules/state-local": {
|
15410 |
+
"version": "1.0.7",
|
15411 |
+
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
|
15412 |
+
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
15413 |
+
"license": "MIT"
|
15414 |
+
},
|
15415 |
"node_modules/static-eval": {
|
15416 |
"version": "2.0.2",
|
15417 |
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz",
|
|
|
16359 |
"version": "10.9.2",
|
16360 |
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
16361 |
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
16362 |
+
"devOptional": true,
|
16363 |
"dependencies": {
|
16364 |
"@cspotcode/source-map-support": "^0.8.0",
|
16365 |
"@tsconfig/node10": "^1.0.7",
|
|
|
16402 |
"version": "8.3.4",
|
16403 |
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
16404 |
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
16405 |
+
"devOptional": true,
|
16406 |
"dependencies": {
|
16407 |
"acorn": "^8.11.0"
|
16408 |
},
|
|
|
16414 |
"version": "4.1.3",
|
16415 |
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
16416 |
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
16417 |
+
"devOptional": true
|
16418 |
},
|
16419 |
"node_modules/tsconfig-paths": {
|
16420 |
"version": "3.15.0",
|
|
|
16599 |
"version": "5.6.3",
|
16600 |
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
16601 |
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
|
|
16602 |
"bin": {
|
16603 |
"tsc": "bin/tsc",
|
16604 |
"tsserver": "bin/tsserver"
|
|
|
16819 |
"version": "3.0.1",
|
16820 |
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
16821 |
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
16822 |
+
"devOptional": true
|
16823 |
},
|
16824 |
"node_modules/v8-to-istanbul": {
|
16825 |
"version": "8.1.1",
|
|
|
18228 |
"version": "3.1.1",
|
18229 |
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
18230 |
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
18231 |
+
"devOptional": true,
|
18232 |
"engines": {
|
18233 |
"node": ">=6"
|
18234 |
}
|
package.json
CHANGED
@@ -2,6 +2,7 @@
|
|
2 |
"name": "multimodal-live-api-web-console",
|
3 |
"version": "0.1.0",
|
4 |
"dependencies": {
|
|
|
5 |
"classnames": "^2.5.1",
|
6 |
"dotenv": "^16.4.1",
|
7 |
"dotenv-flow": "^4.1.0",
|
@@ -9,6 +10,7 @@
|
|
9 |
"express": "^4.18.2",
|
10 |
"http-proxy-middleware": "^3.0.3",
|
11 |
"lodash": "^4.17.21",
|
|
|
12 |
"react": "^18.3.1",
|
13 |
"react-dom": "^18.3.1",
|
14 |
"react-icons": "^5.3.0",
|
|
|
2 |
"name": "multimodal-live-api-web-console",
|
3 |
"version": "0.1.0",
|
4 |
"dependencies": {
|
5 |
+
"@monaco-editor/react": "^4.7.0",
|
6 |
"classnames": "^2.5.1",
|
7 |
"dotenv": "^16.4.1",
|
8 |
"dotenv-flow": "^4.1.0",
|
|
|
10 |
"express": "^4.18.2",
|
11 |
"http-proxy-middleware": "^3.0.3",
|
12 |
"lodash": "^4.17.21",
|
13 |
+
"lucide-react": "^0.477.0",
|
14 |
"react": "^18.3.1",
|
15 |
"react-dom": "^18.3.1",
|
16 |
"react-icons": "^5.3.0",
|
public/index.html
CHANGED
@@ -18,7 +18,7 @@
|
|
18 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/addons/p5.sound.min.js"></script>
|
19 |
<script src="https://cdn.jsdelivr.net/gh/molleindustria/p5.play/lib/p5.play.js"></script>
|
20 |
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
21 |
-
<title>
|
22 |
</head>
|
23 |
|
24 |
<body>
|
@@ -30,6 +30,9 @@
|
|
30 |
// Function to remove existing sketch instance
|
31 |
window.removeSketch = function() {
|
32 |
if (mySketch) {
|
|
|
|
|
|
|
33 |
mySketch.remove();
|
34 |
mySketch = null;
|
35 |
}
|
@@ -45,6 +48,58 @@
|
|
45 |
|
46 |
// Create new sketch instance
|
47 |
mySketch = new p5((p) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
// Add the sketch code to p5 instance scope
|
49 |
const sketchFunction = new Function('p', `
|
50 |
with (p) {
|
@@ -53,7 +108,7 @@
|
|
53 |
// If setup/draw weren't defined, provide defaults
|
54 |
if (typeof setup !== 'function') {
|
55 |
setup = function() {
|
56 |
-
createCanvas(
|
57 |
}
|
58 |
}
|
59 |
|
@@ -66,7 +121,7 @@
|
|
66 |
// Always ensure we have window resize handling
|
67 |
if (typeof windowResized !== 'function') {
|
68 |
windowResized = function() {
|
69 |
-
resizeCanvas(
|
70 |
}
|
71 |
}
|
72 |
|
@@ -84,12 +139,37 @@
|
|
84 |
if (container) {
|
85 |
container.innerHTML = "";
|
86 |
container.appendChild(p._renderer.canvas);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
}
|
88 |
};
|
89 |
|
90 |
p.draw = functions.draw.bind(p);
|
91 |
p.windowResized = functions.windowResized.bind(p);
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
|
94 |
console.log("Successfully created new sketch");
|
95 |
return true;
|
@@ -108,7 +188,7 @@
|
|
108 |
// Create initial empty sketch
|
109 |
window.updateSketch(`
|
110 |
function setup() {
|
111 |
-
createCanvas(
|
112 |
}
|
113 |
|
114 |
function draw() {
|
|
|
18 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/addons/p5.sound.min.js"></script>
|
19 |
<script src="https://cdn.jsdelivr.net/gh/molleindustria/p5.play/lib/p5.play.js"></script>
|
20 |
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
21 |
+
<title>Gemini Live API + p5.js</title>
|
22 |
</head>
|
23 |
|
24 |
<body>
|
|
|
30 |
// Function to remove existing sketch instance
|
31 |
window.removeSketch = function() {
|
32 |
if (mySketch) {
|
33 |
+
if (mySketch._cleanup) {
|
34 |
+
mySketch._cleanup();
|
35 |
+
}
|
36 |
mySketch.remove();
|
37 |
mySketch = null;
|
38 |
}
|
|
|
48 |
|
49 |
// Create new sketch instance
|
50 |
mySketch = new p5((p) => {
|
51 |
+
// Store container reference in p5 instance
|
52 |
+
p.containerWidth = container.clientWidth;
|
53 |
+
p.containerHeight = container.clientHeight;
|
54 |
+
|
55 |
+
let resizeTimeout = null;
|
56 |
+
let resizeObserver = null;
|
57 |
+
|
58 |
+
// Update container dimensions when needed
|
59 |
+
const updateContainerSize = () => {
|
60 |
+
const rect = container.getBoundingClientRect();
|
61 |
+
const newWidth = rect.width;
|
62 |
+
const newHeight = rect.height;
|
63 |
+
|
64 |
+
// Only update if dimensions actually changed
|
65 |
+
if (p.containerWidth !== newWidth || p.containerHeight !== newHeight) {
|
66 |
+
p.containerWidth = newWidth;
|
67 |
+
p.containerHeight = newHeight;
|
68 |
+
if (p.windowResized) {
|
69 |
+
p.windowResized();
|
70 |
+
}
|
71 |
+
}
|
72 |
+
};
|
73 |
+
|
74 |
+
// Debounced resize handler
|
75 |
+
const debouncedResize = () => {
|
76 |
+
if (resizeTimeout) {
|
77 |
+
clearTimeout(resizeTimeout);
|
78 |
+
}
|
79 |
+
resizeTimeout = setTimeout(() => {
|
80 |
+
updateContainerSize();
|
81 |
+
resizeTimeout = null;
|
82 |
+
}, 100); // Debounce for 100ms
|
83 |
+
};
|
84 |
+
|
85 |
+
// Setup resize observer
|
86 |
+
const setupResizeObserver = () => {
|
87 |
+
if (resizeObserver) {
|
88 |
+
resizeObserver.disconnect();
|
89 |
+
}
|
90 |
+
|
91 |
+
resizeObserver = new ResizeObserver((entries) => {
|
92 |
+
// Avoid processing if container is hidden or detached
|
93 |
+
if (container.offsetParent !== null) {
|
94 |
+
window.requestAnimationFrame(() => {
|
95 |
+
debouncedResize();
|
96 |
+
});
|
97 |
+
}
|
98 |
+
});
|
99 |
+
|
100 |
+
resizeObserver.observe(container);
|
101 |
+
};
|
102 |
+
|
103 |
// Add the sketch code to p5 instance scope
|
104 |
const sketchFunction = new Function('p', `
|
105 |
with (p) {
|
|
|
108 |
// If setup/draw weren't defined, provide defaults
|
109 |
if (typeof setup !== 'function') {
|
110 |
setup = function() {
|
111 |
+
createCanvas(containerWidth, containerHeight);
|
112 |
}
|
113 |
}
|
114 |
|
|
|
121 |
// Always ensure we have window resize handling
|
122 |
if (typeof windowResized !== 'function') {
|
123 |
windowResized = function() {
|
124 |
+
resizeCanvas(containerWidth, containerHeight, true);
|
125 |
}
|
126 |
}
|
127 |
|
|
|
139 |
if (container) {
|
140 |
container.innerHTML = "";
|
141 |
container.appendChild(p._renderer.canvas);
|
142 |
+
|
143 |
+
// Fix accessibility tooltip issue
|
144 |
+
if (p._renderer.canvas) {
|
145 |
+
// Remove the default accessibility attributes that cause tooltips
|
146 |
+
p._renderer.canvas.removeAttribute('aria-label');
|
147 |
+
p._renderer.canvas.removeAttribute('role');
|
148 |
+
|
149 |
+
// Add a custom class to help with styling
|
150 |
+
p._renderer.canvas.classList.add('p5-managed-canvas');
|
151 |
+
}
|
152 |
+
|
153 |
+
// Setup resize observer after canvas is added
|
154 |
+
setupResizeObserver();
|
155 |
}
|
156 |
};
|
157 |
|
158 |
p.draw = functions.draw.bind(p);
|
159 |
p.windowResized = functions.windowResized.bind(p);
|
160 |
+
|
161 |
+
// Cleanup when sketch is removed
|
162 |
+
p._cleanup = () => {
|
163 |
+
if (resizeObserver) {
|
164 |
+
resizeObserver.disconnect();
|
165 |
+
resizeObserver = null;
|
166 |
+
}
|
167 |
+
if (resizeTimeout) {
|
168 |
+
clearTimeout(resizeTimeout);
|
169 |
+
resizeTimeout = null;
|
170 |
+
}
|
171 |
+
};
|
172 |
+
}, container);
|
173 |
|
174 |
console.log("Successfully created new sketch");
|
175 |
return true;
|
|
|
188 |
// Create initial empty sketch
|
189 |
window.updateSketch(`
|
190 |
function setup() {
|
191 |
+
createCanvas(containerWidth, containerHeight);
|
192 |
}
|
193 |
|
194 |
function draw() {
|
src/App.scss
CHANGED
@@ -138,19 +138,92 @@ body {
|
|
138 |
position: relative;
|
139 |
display: flex;
|
140 |
flex-direction: column;
|
141 |
-
align-items:
|
142 |
-
justify-content:
|
143 |
flex-grow: 1;
|
144 |
gap: 1rem;
|
145 |
max-width: 100%;
|
146 |
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
}
|
148 |
|
149 |
.main-app-area {
|
150 |
display: flex;
|
151 |
flex: 1;
|
152 |
-
align-items:
|
153 |
-
justify-content:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
}
|
155 |
|
156 |
.function-call {
|
@@ -180,3 +253,18 @@ body {
|
|
180 |
max-width: 320px;
|
181 |
}
|
182 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
position: relative;
|
139 |
display: flex;
|
140 |
flex-direction: column;
|
141 |
+
align-items: flex-start;
|
142 |
+
justify-content: flex-start;
|
143 |
flex-grow: 1;
|
144 |
gap: 1rem;
|
145 |
max-width: 100%;
|
146 |
overflow: hidden;
|
147 |
+
padding: 20px 40px;
|
148 |
+
}
|
149 |
+
|
150 |
+
.app-title {
|
151 |
+
font-family: "Space Mono", monospace;
|
152 |
+
font-size: 28px;
|
153 |
+
font-weight: 500;
|
154 |
+
color: white;
|
155 |
+
margin-bottom: 0px;
|
156 |
+
text-align: left;
|
157 |
+
display: flex;
|
158 |
+
align-items: baseline;
|
159 |
+
gap: 15px;
|
160 |
+
|
161 |
+
.subtitle {
|
162 |
+
font-size: 18px;
|
163 |
+
color: #888;
|
164 |
+
font-weight: normal;
|
165 |
+
opacity: 0.8;
|
166 |
+
|
167 |
+
a {
|
168 |
+
color: inherit;
|
169 |
+
text-decoration: underline;
|
170 |
+
text-underline-offset: 3px;
|
171 |
+
|
172 |
+
&:hover {
|
173 |
+
opacity: 0.9;
|
174 |
+
}
|
175 |
+
}
|
176 |
+
}
|
177 |
+
|
178 |
+
@media (max-width: 768px) {
|
179 |
+
font-size: 32px;
|
180 |
+
flex-direction: column;
|
181 |
+
align-items: flex-start;
|
182 |
+
gap: 5px;
|
183 |
+
|
184 |
+
.subtitle {
|
185 |
+
font-size: 14px;
|
186 |
+
}
|
187 |
+
}
|
188 |
}
|
189 |
|
190 |
.main-app-area {
|
191 |
display: flex;
|
192 |
flex: 1;
|
193 |
+
align-items: flex-start;
|
194 |
+
justify-content: flex-start;
|
195 |
+
width: 100%;
|
196 |
+
padding: 0;
|
197 |
+
}
|
198 |
+
|
199 |
+
.p5-editor-container {
|
200 |
+
width: 100%;
|
201 |
+
height: calc(100% - 100px);
|
202 |
+
border-radius: 12px;
|
203 |
+
border: 2px solid var(--gray-600);
|
204 |
+
overflow: hidden;
|
205 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
206 |
+
background-color: var(--Neutral-10);
|
207 |
+
|
208 |
+
@media (max-width: 1024px) {
|
209 |
+
width: 100%;
|
210 |
+
}
|
211 |
+
|
212 |
+
@media (max-width: 768px) {
|
213 |
+
width: 100%;
|
214 |
+
height: calc(100vh - 160px);
|
215 |
+
margin-bottom: 20px;
|
216 |
+
display: flex;
|
217 |
+
align-items: center;
|
218 |
+
justify-content: center;
|
219 |
+
|
220 |
+
// Ensure P5Sketch component fills the container on mobile
|
221 |
+
> div {
|
222 |
+
width: 100% !important;
|
223 |
+
height: 100% !important;
|
224 |
+
border-radius: inherit;
|
225 |
+
}
|
226 |
+
}
|
227 |
}
|
228 |
|
229 |
.function-call {
|
|
|
253 |
max-width: 320px;
|
254 |
}
|
255 |
}
|
256 |
+
|
257 |
+
/* Fix for p5.js canvas accessibility tooltips */
|
258 |
+
canvas#defaultCanvas0,
|
259 |
+
[id^="defaultCanvas"],
|
260 |
+
.p5Canvas,
|
261 |
+
.p5-managed-canvas {
|
262 |
+
&::before,
|
263 |
+
&::after {
|
264 |
+
display: none !important;
|
265 |
+
content: none !important;
|
266 |
+
opacity: 0 !important;
|
267 |
+
visibility: hidden !important;
|
268 |
+
pointer-events: none !important;
|
269 |
+
}
|
270 |
+
}
|
src/App.tsx
CHANGED
@@ -18,6 +18,7 @@ import { useRef, useState, useEffect } from "react";
|
|
18 |
import "./App.scss";
|
19 |
import { LiveAPIProvider } from "./contexts/LiveAPIContext";
|
20 |
import SidePanel from "./components/side-panel/SidePanel";
|
|
|
21 |
import { P5Sketch } from "./components/p5/P5Sketch";
|
22 |
import ControlTray from "./components/control-tray/ControlTray";
|
23 |
import { IOSModal } from "./components/ios-modal/IOSModal";
|
@@ -31,23 +32,37 @@ function App() {
|
|
31 |
// either the screen capture, the video or null, if null we hide it
|
32 |
const [videoStream, setVideoStream] = useState<MediaStream | null>(null);
|
33 |
const [showIOSModal, setShowIOSModal] = useState(false);
|
|
|
34 |
|
35 |
useEffect(() => {
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
if (isIOS()) {
|
38 |
setShowIOSModal(true);
|
39 |
}
|
|
|
|
|
40 |
}, []);
|
41 |
|
42 |
return (
|
43 |
<div className="App">
|
44 |
<LiveAPIProvider>
|
45 |
<div className="streaming-console">
|
46 |
-
<SidePanel />
|
47 |
<main>
|
|
|
|
|
|
|
48 |
<div className="main-app-area">
|
49 |
{/* APP goes here */}
|
50 |
-
<
|
|
|
|
|
51 |
</div>
|
52 |
|
53 |
<video
|
|
|
18 |
import "./App.scss";
|
19 |
import { LiveAPIProvider } from "./contexts/LiveAPIContext";
|
20 |
import SidePanel from "./components/side-panel/SidePanel";
|
21 |
+
import { P5WithEditor } from "./components/p5/P5WithEditor";
|
22 |
import { P5Sketch } from "./components/p5/P5Sketch";
|
23 |
import ControlTray from "./components/control-tray/ControlTray";
|
24 |
import { IOSModal } from "./components/ios-modal/IOSModal";
|
|
|
32 |
// either the screen capture, the video or null, if null we hide it
|
33 |
const [videoStream, setVideoStream] = useState<MediaStream | null>(null);
|
34 |
const [showIOSModal, setShowIOSModal] = useState(false);
|
35 |
+
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
36 |
|
37 |
useEffect(() => {
|
38 |
+
const handleResize = () => {
|
39 |
+
setIsMobile(window.innerWidth < 768);
|
40 |
+
};
|
41 |
+
|
42 |
+
window.addEventListener('resize', handleResize);
|
43 |
+
|
44 |
+
// Check iOS on mount
|
45 |
if (isIOS()) {
|
46 |
setShowIOSModal(true);
|
47 |
}
|
48 |
+
|
49 |
+
return () => window.removeEventListener('resize', handleResize);
|
50 |
}, []);
|
51 |
|
52 |
return (
|
53 |
<div className="App">
|
54 |
<LiveAPIProvider>
|
55 |
<div className="streaming-console">
|
56 |
+
<SidePanel initialCollapsed={true} />
|
57 |
<main>
|
58 |
+
<h1 className="app-title">
|
59 |
+
LiveCoder <span className="subtitle">Built with <a target="_blank" href="https://ai.google.dev/gemini-api/docs/multimodal-live">Google Live API</a> + <a target="_blank" href="https://p5js.org/reference/">p5.js</a></span>
|
60 |
+
</h1>
|
61 |
<div className="main-app-area">
|
62 |
{/* APP goes here */}
|
63 |
+
<div className="p5-editor-container">
|
64 |
+
{isMobile ? <P5Sketch /> : <P5WithEditor />}
|
65 |
+
</div>
|
66 |
</div>
|
67 |
|
68 |
<video
|
src/components/p5/P5Sketch.tsx
CHANGED
@@ -50,8 +50,15 @@ function P5SketchComponent() {
|
|
50 |
setConfig({
|
51 |
model: "models/gemini-2.0-flash-exp",
|
52 |
generationConfig: {
|
53 |
-
temperature: 0.1,
|
54 |
-
responseModalities: "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
},
|
56 |
systemInstruction: {
|
57 |
parts: [
|
@@ -66,12 +73,12 @@ When a user requests a sketch:
|
|
66 |
|
67 |
You can create sketches using:
|
68 |
- Basic shapes, colors, and animations
|
69 |
-
- Mouse and keyboard interaction
|
70 |
- Sound effects (p5.sound library)
|
71 |
- Sprite-based games (p5.play library)
|
72 |
- Full window canvas with automatic resizing
|
73 |
|
74 |
-
Focus on creating visually engaging and interactive experiences.`
|
75 |
},
|
76 |
],
|
77 |
},
|
@@ -135,11 +142,13 @@ Focus on creating visually engaging and interactive experiences.`
|
|
135 |
<div
|
136 |
ref={containerRef}
|
137 |
style={{
|
138 |
-
width: "
|
139 |
-
height: "
|
140 |
alignItems: "center",
|
141 |
justifyContent: "center",
|
142 |
display: "flex",
|
|
|
|
|
143 |
}}
|
144 |
/>
|
145 |
);
|
|
|
50 |
setConfig({
|
51 |
model: "models/gemini-2.0-flash-exp",
|
52 |
generationConfig: {
|
53 |
+
temperature: 0.1,
|
54 |
+
responseModalities: "audio",
|
55 |
+
speechConfig: {
|
56 |
+
voiceConfig: {
|
57 |
+
prebuiltVoiceConfig: {
|
58 |
+
voiceName: "Puck"
|
59 |
+
}
|
60 |
+
}
|
61 |
+
}
|
62 |
},
|
63 |
systemInstruction: {
|
64 |
parts: [
|
|
|
73 |
|
74 |
You can create sketches using:
|
75 |
- Basic shapes, colors, and animations
|
76 |
+
- Mouse and keyboard interaction (mouseX, mouseY, keyPressed, keyCode, etc.)
|
77 |
- Sound effects (p5.sound library)
|
78 |
- Sprite-based games (p5.play library)
|
79 |
- Full window canvas with automatic resizing
|
80 |
|
81 |
+
Focus on creating visually engaging and interactive experiences. As soon as the user requests a sketch, confirm you heard them, THEN you should create the sketch and then explain what it does and how to interact with it. Be EXTREMELY brief and pithy.`
|
82 |
},
|
83 |
],
|
84 |
},
|
|
|
142 |
<div
|
143 |
ref={containerRef}
|
144 |
style={{
|
145 |
+
width: "100%",
|
146 |
+
height: "100%",
|
147 |
alignItems: "center",
|
148 |
justifyContent: "center",
|
149 |
display: "flex",
|
150 |
+
position: "relative",
|
151 |
+
background: "#000"
|
152 |
}}
|
153 |
/>
|
154 |
);
|
src/components/p5/P5WithEditor.tsx
ADDED
@@ -0,0 +1,617 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Copyright 2024 Google LLC
|
3 |
+
*
|
4 |
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5 |
+
* you may not use this file except in compliance with the License.
|
6 |
+
* You may obtain a copy of the License at
|
7 |
+
*
|
8 |
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9 |
+
*
|
10 |
+
* Unless required by applicable law or agreed to in writing, software
|
11 |
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12 |
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 |
+
* See the License for the specific language governing permissions and
|
14 |
+
* limitations under the License.
|
15 |
+
*/
|
16 |
+
import { SchemaType } from "@google/generative-ai";
|
17 |
+
import { useEffect, useRef, memo, useState, useCallback } from "react";
|
18 |
+
import { useLiveAPIContext } from "../../contexts/LiveAPIContext";
|
19 |
+
import type { ToolCall } from "../../multimodal-live-types";
|
20 |
+
import { Editor } from "@monaco-editor/react";
|
21 |
+
import "./p5-with-editor.scss";
|
22 |
+
import { Send, Code } from "lucide-react";
|
23 |
+
|
24 |
+
// Add type definitions for window functions
|
25 |
+
declare global {
|
26 |
+
interface Window {
|
27 |
+
initSketch: (container: HTMLElement) => void;
|
28 |
+
updateSketch: (code: string, container: HTMLElement) => boolean;
|
29 |
+
removeSketch: () => void;
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
interface SketchArgs {
|
34 |
+
sketch: string;
|
35 |
+
}
|
36 |
+
|
37 |
+
function P5WithEditorComponent() {
|
38 |
+
const containerRef = useRef<HTMLDivElement>(null);
|
39 |
+
const editorPanelRef = useRef<HTMLDivElement>(null);
|
40 |
+
const { client, setConfig } = useLiveAPIContext();
|
41 |
+
const [editorContent, setEditorContent] = useState<string>("");
|
42 |
+
const [displayedContent, setDisplayedContent] = useState<string>("");
|
43 |
+
const [isResizing, setIsResizing] = useState(false);
|
44 |
+
const [editorWidth, setEditorWidth] = useState(40); // percentage
|
45 |
+
const [editorHeight, setEditorHeight] = useState(50); // percentage for mobile
|
46 |
+
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
47 |
+
const [isSending, setIsSending] = useState(false);
|
48 |
+
const [isAnimatingCode, setIsAnimatingCode] = useState(false);
|
49 |
+
const [cursorPosition, setCursorPosition] = useState<{ lineNumber: number, column: number } | null>(null);
|
50 |
+
const editorRef = useRef<any>(null);
|
51 |
+
const animationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
52 |
+
|
53 |
+
// Function to handle editor mounting
|
54 |
+
const handleEditorDidMount = (editor: any) => {
|
55 |
+
editorRef.current = editor;
|
56 |
+
};
|
57 |
+
|
58 |
+
// Helper function to calculate cursor position from text position
|
59 |
+
const calculateCursorPosition = (text: string, position: number): { lineNumber: number, column: number } => {
|
60 |
+
const textUpToPosition = text.substring(0, position);
|
61 |
+
const lines = textUpToPosition.split('\n');
|
62 |
+
const lineNumber = lines.length;
|
63 |
+
const column = lines[lines.length - 1].length + 1;
|
64 |
+
|
65 |
+
return { lineNumber, column };
|
66 |
+
};
|
67 |
+
|
68 |
+
// Helper function to animate code reveal
|
69 |
+
const animateCodeReveal = (newCode: string, currentPosition = 0, chunkSize = 3) => {
|
70 |
+
if (currentPosition >= newCode.length) {
|
71 |
+
// Add a small delay before finishing to let the user see the completed code
|
72 |
+
setTimeout(() => {
|
73 |
+
setIsAnimatingCode(false);
|
74 |
+
setCursorPosition(null);
|
75 |
+
|
76 |
+
// Clear any decorations when animation is complete
|
77 |
+
if (editorRef.current) {
|
78 |
+
try {
|
79 |
+
const model = editorRef.current.getModel();
|
80 |
+
if (model) {
|
81 |
+
editorRef.current.deltaDecorations([], []);
|
82 |
+
}
|
83 |
+
} catch (e) {
|
84 |
+
console.error("Error clearing decorations:", e);
|
85 |
+
}
|
86 |
+
}
|
87 |
+
}, 500);
|
88 |
+
|
89 |
+
return;
|
90 |
+
}
|
91 |
+
|
92 |
+
setIsAnimatingCode(true);
|
93 |
+
|
94 |
+
// Calculate next chunk to reveal
|
95 |
+
const nextPosition = Math.min(currentPosition + chunkSize, newCode.length);
|
96 |
+
const nextChunk = newCode.substring(0, nextPosition);
|
97 |
+
|
98 |
+
// Update displayed content
|
99 |
+
setDisplayedContent(nextChunk);
|
100 |
+
|
101 |
+
// Update cursor position
|
102 |
+
const cursorPos = calculateCursorPosition(nextChunk, nextPosition);
|
103 |
+
setCursorPosition(cursorPos);
|
104 |
+
|
105 |
+
// Move editor cursor to the current position if editor is available
|
106 |
+
if (editorRef.current) {
|
107 |
+
try {
|
108 |
+
editorRef.current.setPosition(cursorPos);
|
109 |
+
editorRef.current.revealPositionInCenter(cursorPos);
|
110 |
+
|
111 |
+
// Add decoration to highlight the current line
|
112 |
+
const model = editorRef.current.getModel();
|
113 |
+
if (model) {
|
114 |
+
const decorations = [{
|
115 |
+
range: {
|
116 |
+
startLineNumber: cursorPos.lineNumber,
|
117 |
+
startColumn: 1,
|
118 |
+
endLineNumber: cursorPos.lineNumber,
|
119 |
+
endColumn: model.getLineMaxColumn(cursorPos.lineNumber)
|
120 |
+
},
|
121 |
+
options: {
|
122 |
+
isWholeLine: true,
|
123 |
+
className: 'current-line-highlight',
|
124 |
+
glyphMarginClassName: 'current-line-glyph'
|
125 |
+
}
|
126 |
+
}];
|
127 |
+
|
128 |
+
editorRef.current.deltaDecorations([], decorations);
|
129 |
+
}
|
130 |
+
} catch (e) {
|
131 |
+
console.error("Error setting cursor position or decorations:", e);
|
132 |
+
}
|
133 |
+
}
|
134 |
+
|
135 |
+
// Schedule next chunk reveal with variable speed
|
136 |
+
// Start faster, then slow down a bit for longer code
|
137 |
+
const baseDelay = 5; // Base delay in ms
|
138 |
+
const progressFactor = Math.min(currentPosition / newCode.length * 3, 1); // Slow down as we progress
|
139 |
+
const randomFactor = Math.random() * 10; // Add some randomness
|
140 |
+
|
141 |
+
// Calculate delay: faster at start, slower as we progress
|
142 |
+
const delay = baseDelay + (progressFactor * 15) + randomFactor;
|
143 |
+
|
144 |
+
animationTimeoutRef.current = setTimeout(() => {
|
145 |
+
// Increase chunk size as we progress for a more natural typing effect
|
146 |
+
const newChunkSize = Math.floor(chunkSize + (progressFactor * 5));
|
147 |
+
animateCodeReveal(newCode, nextPosition, newChunkSize);
|
148 |
+
}, delay);
|
149 |
+
};
|
150 |
+
|
151 |
+
// Clean up animation timeout on unmount
|
152 |
+
useEffect(() => {
|
153 |
+
return () => {
|
154 |
+
if (animationTimeoutRef.current) {
|
155 |
+
clearTimeout(animationTimeoutRef.current);
|
156 |
+
}
|
157 |
+
};
|
158 |
+
}, []);
|
159 |
+
|
160 |
+
// Helper function to format code for the model
|
161 |
+
const formatCodeForModel = (code: string): string => {
|
162 |
+
// Trim whitespace and ensure it's not too long
|
163 |
+
const trimmedCode = code.trim();
|
164 |
+
|
165 |
+
// If code is very long, add a note about it
|
166 |
+
if (trimmedCode.length > 5000) {
|
167 |
+
const truncatedCode = trimmedCode.substring(0, 5000);
|
168 |
+
return `${truncatedCode}\n\n// Note: Code was truncated as it was too long. This is just the first part of the code.`;
|
169 |
+
}
|
170 |
+
|
171 |
+
return trimmedCode;
|
172 |
+
};
|
173 |
+
|
174 |
+
useEffect(() => {
|
175 |
+
const handleResize = () => {
|
176 |
+
setIsMobile(window.innerWidth < 768);
|
177 |
+
};
|
178 |
+
|
179 |
+
window.addEventListener('resize', handleResize);
|
180 |
+
return () => window.removeEventListener('resize', handleResize);
|
181 |
+
}, []);
|
182 |
+
|
183 |
+
// Intercept messages to include the current editor content
|
184 |
+
useEffect(() => {
|
185 |
+
// Store the original send method
|
186 |
+
const originalSend = client.send.bind(client);
|
187 |
+
|
188 |
+
// Override the send method to include editor content
|
189 |
+
client.send = (parts: any, turnComplete = true) => {
|
190 |
+
// Convert to array if it's not already
|
191 |
+
const partsArray = Array.isArray(parts) ? parts : [parts];
|
192 |
+
|
193 |
+
// Check if there's text content and we have editor content
|
194 |
+
const hasTextContent = partsArray.some(part => part.text);
|
195 |
+
|
196 |
+
if (hasTextContent && editorContent) {
|
197 |
+
// Show sending indicator
|
198 |
+
setIsSending(true);
|
199 |
+
|
200 |
+
// Format the code before sending
|
201 |
+
const formattedCode = formatCodeForModel(editorContent);
|
202 |
+
|
203 |
+
// Add the current editor content as context
|
204 |
+
partsArray.push({
|
205 |
+
text: `\n\n--- CURRENT P5.JS CODE IN EDITOR ---\n\`\`\`javascript\n${formattedCode}\n\`\`\`\n\nPlease consider this code when responding to my request. If I'm asking for changes or improvements, use this as the starting point.`
|
206 |
+
});
|
207 |
+
|
208 |
+
// Hide sending indicator after a short delay
|
209 |
+
setTimeout(() => setIsSending(false), 1000);
|
210 |
+
}
|
211 |
+
|
212 |
+
// Call the original send method with our modified parts
|
213 |
+
return originalSend(partsArray, turnComplete);
|
214 |
+
};
|
215 |
+
|
216 |
+
// Cleanup: restore the original method when component unmounts
|
217 |
+
return () => {
|
218 |
+
client.send = originalSend;
|
219 |
+
};
|
220 |
+
}, [client, editorContent]);
|
221 |
+
|
222 |
+
useEffect(() => {
|
223 |
+
if (containerRef.current) {
|
224 |
+
window.initSketch(containerRef.current);
|
225 |
+
|
226 |
+
// Add a test sketch to verify the component is working
|
227 |
+
const testSketch = `
|
228 |
+
// Press ▶ Play to connect to the
|
229 |
+
// Gemini 2.0 Live API
|
230 |
+
// and start chatting to change this code
|
231 |
+
|
232 |
+
let boids = [];
|
233 |
+
|
234 |
+
function setup() {
|
235 |
+
createCanvas(containerWidth, containerHeight);
|
236 |
+
// Create 30 boids with random positions and velocities
|
237 |
+
for (let i = 0; i < 30; i++) {
|
238 |
+
boids.push({
|
239 |
+
pos: createVector(random(width), random(height)),
|
240 |
+
vel: p5.Vector.random2D().mult(random(2, 3)),
|
241 |
+
col: "#2d2d2d"
|
242 |
+
});
|
243 |
+
}
|
244 |
+
}
|
245 |
+
|
246 |
+
function draw() {
|
247 |
+
background(30, 30, 30);
|
248 |
+
|
249 |
+
let mouse = createVector(mouseX, mouseY);
|
250 |
+
|
251 |
+
for (let boid of boids) {
|
252 |
+
// Update velocity based on simple rules
|
253 |
+
let sep = createVector();
|
254 |
+
let coh = createVector();
|
255 |
+
let ali = createVector();
|
256 |
+
let count = 0;
|
257 |
+
|
258 |
+
// Calculate all forces in one loop
|
259 |
+
for (let other of boids) {
|
260 |
+
if (other === boid) continue;
|
261 |
+
let d = p5.Vector.dist(boid.pos, other.pos);
|
262 |
+
|
263 |
+
if (d < 50) {
|
264 |
+
// Separation (avoid others)
|
265 |
+
let diff = p5.Vector.sub(boid.pos, other.pos);
|
266 |
+
diff.div(d);
|
267 |
+
sep.add(diff);
|
268 |
+
|
269 |
+
// Alignment (match velocity)
|
270 |
+
ali.add(other.vel);
|
271 |
+
|
272 |
+
// Cohesion (move toward others)
|
273 |
+
coh.add(other.pos);
|
274 |
+
|
275 |
+
count++;
|
276 |
+
}
|
277 |
+
}
|
278 |
+
|
279 |
+
if (count > 0) {
|
280 |
+
// Apply flocking behavior
|
281 |
+
sep.mult(0.3);
|
282 |
+
ali.div(count).mult(0.2);
|
283 |
+
coh.div(count).sub(boid.pos).mult(0.1);
|
284 |
+
|
285 |
+
boid.vel.add(sep).add(ali).add(coh);
|
286 |
+
|
287 |
+
// Add subtle mouse attraction
|
288 |
+
let mouseForce = p5.Vector.sub(mouse, boid.pos);
|
289 |
+
mouseForce.mult(0.01);
|
290 |
+
boid.vel.add(mouseForce);
|
291 |
+
}
|
292 |
+
|
293 |
+
// Limit speed and update position
|
294 |
+
boid.vel.limit(4);
|
295 |
+
boid.pos.add(boid.vel);
|
296 |
+
|
297 |
+
// Wrap around edges
|
298 |
+
boid.pos.x = (boid.pos.x + width) % width;
|
299 |
+
boid.pos.y = (boid.pos.y + height) % height;
|
300 |
+
|
301 |
+
// Draw boid
|
302 |
+
push();
|
303 |
+
translate(boid.pos.x, boid.pos.y);
|
304 |
+
rotate(boid.vel.heading());
|
305 |
+
fill(boid.col);
|
306 |
+
noStroke();
|
307 |
+
triangle(16, 0, -8, 6, -8, -6);
|
308 |
+
pop();
|
309 |
+
}
|
310 |
+
}
|
311 |
+
|
312 |
+
function windowResized() {
|
313 |
+
resizeCanvas(containerWidth, containerHeight);
|
314 |
+
}
|
315 |
+
`;
|
316 |
+
|
317 |
+
// Set the editor content and displayed content
|
318 |
+
setEditorContent(testSketch);
|
319 |
+
setDisplayedContent(testSketch);
|
320 |
+
|
321 |
+
// Update the sketch with the test code
|
322 |
+
window.updateSketch(testSketch, containerRef.current);
|
323 |
+
}
|
324 |
+
|
325 |
+
// Cleanup on unmount
|
326 |
+
return () => {
|
327 |
+
window.removeSketch();
|
328 |
+
};
|
329 |
+
}, []);
|
330 |
+
|
331 |
+
useEffect(() => {
|
332 |
+
setConfig({
|
333 |
+
model: "models/gemini-2.0-flash-exp",
|
334 |
+
generationConfig: {
|
335 |
+
temperature: 0.1,
|
336 |
+
responseModalities: "audio",
|
337 |
+
speechConfig: {
|
338 |
+
voiceConfig: {
|
339 |
+
prebuiltVoiceConfig: {
|
340 |
+
voiceName: "Puck"
|
341 |
+
}
|
342 |
+
}
|
343 |
+
}
|
344 |
+
},
|
345 |
+
systemInstruction: {
|
346 |
+
parts: [
|
347 |
+
{
|
348 |
+
text: `You are a P5.js creative coding expert that helps users create interactive sketches.
|
349 |
+
|
350 |
+
When a user requests a sketch:
|
351 |
+
1. Always use the updateSketch function to create or modify sketches
|
352 |
+
2. NEVER output code directly in the response - only use the function
|
353 |
+
3. After the sketch is created, explain what the sketch does and how to interact with it
|
354 |
+
4. If the user's request is unclear, just take your best guess to create a sketch
|
355 |
+
|
356 |
+
You can create sketches using:
|
357 |
+
- Basic shapes, colors, and animations (do not ever try to import a photo or image)
|
358 |
+
- Mouse and keyboard interaction
|
359 |
+
- Sound effects (p5.sound library)
|
360 |
+
- Sprite-based games (p5.play library)
|
361 |
+
- Full window canvas with automatic resizing
|
362 |
+
|
363 |
+
Focus on creating visually engaging and interactive experiences. As soon as the user requests a sketch, confirm you heard them, THEN you should create the sketch and then explain what it does and how to interact with it. Be EXTREMELY brief and pithy.`
|
364 |
+
},
|
365 |
+
],
|
366 |
+
},
|
367 |
+
tools: [
|
368 |
+
{
|
369 |
+
functionDeclarations: [
|
370 |
+
{
|
371 |
+
name: "updateSketch",
|
372 |
+
description: "Create or update the P5.js sketch with new code. The sketch will run in a full-window canvas.",
|
373 |
+
parameters: {
|
374 |
+
type: SchemaType.OBJECT,
|
375 |
+
properties: {
|
376 |
+
sketch: {
|
377 |
+
type: SchemaType.STRING,
|
378 |
+
description: "Complete P5.js sketch code including all variable declarations, setup(), draw(), and any additional functions needed. The code should be a complete, self-contained sketch."
|
379 |
+
}
|
380 |
+
},
|
381 |
+
required: ["sketch"]
|
382 |
+
}
|
383 |
+
}
|
384 |
+
],
|
385 |
+
},
|
386 |
+
],
|
387 |
+
});
|
388 |
+
}, [setConfig]);
|
389 |
+
|
390 |
+
useEffect(() => {
|
391 |
+
const onToolCall = async (toolCall: ToolCall) => {
|
392 |
+
console.log("Received tool call:", toolCall);
|
393 |
+
|
394 |
+
if (!toolCall.functionCalls || toolCall.functionCalls.length === 0) {
|
395 |
+
console.error("No function calls in tool call");
|
396 |
+
return;
|
397 |
+
}
|
398 |
+
|
399 |
+
for (const fc of toolCall.functionCalls) {
|
400 |
+
if (fc.name === "updateSketch") {
|
401 |
+
if (!containerRef.current) {
|
402 |
+
console.error("Container ref is not available");
|
403 |
+
await client.sendToolResponse({
|
404 |
+
functionResponses: [
|
405 |
+
{
|
406 |
+
response: {
|
407 |
+
success: false,
|
408 |
+
message: "Container not available"
|
409 |
+
},
|
410 |
+
id: fc.id,
|
411 |
+
},
|
412 |
+
],
|
413 |
+
});
|
414 |
+
continue;
|
415 |
+
}
|
416 |
+
|
417 |
+
try {
|
418 |
+
// Parse the arguments from the function call
|
419 |
+
if (!fc.args) {
|
420 |
+
throw new Error("No arguments provided");
|
421 |
+
}
|
422 |
+
|
423 |
+
const args = fc.args as SketchArgs;
|
424 |
+
if (!args.sketch) {
|
425 |
+
throw new Error("No sketch code provided");
|
426 |
+
}
|
427 |
+
|
428 |
+
console.log("Sketch code received:", args.sketch);
|
429 |
+
|
430 |
+
// First update the editor content state (but not displayed yet)
|
431 |
+
setEditorContent(args.sketch);
|
432 |
+
|
433 |
+
// Clear any existing animation
|
434 |
+
if (animationTimeoutRef.current) {
|
435 |
+
clearTimeout(animationTimeoutRef.current);
|
436 |
+
}
|
437 |
+
|
438 |
+
// Start the animation to reveal the code
|
439 |
+
animateCodeReveal(args.sketch);
|
440 |
+
|
441 |
+
// Update the sketch immediately (don't wait for animation)
|
442 |
+
const result = window.updateSketch(args.sketch, containerRef.current);
|
443 |
+
console.log("Sketch update result:", result);
|
444 |
+
|
445 |
+
// Send the function response back to Gemini
|
446 |
+
await client.sendToolResponse({
|
447 |
+
functionResponses: [
|
448 |
+
{
|
449 |
+
response: {
|
450 |
+
success: result,
|
451 |
+
message: result ? "Sketch updated successfully" : "Failed to update sketch"
|
452 |
+
},
|
453 |
+
id: fc.id,
|
454 |
+
},
|
455 |
+
],
|
456 |
+
});
|
457 |
+
} catch (error) {
|
458 |
+
console.error("Error handling updateSketch function call:", error);
|
459 |
+
// Send error response back to Gemini
|
460 |
+
await client.sendToolResponse({
|
461 |
+
functionResponses: [
|
462 |
+
{
|
463 |
+
response: {
|
464 |
+
success: false,
|
465 |
+
message: `Error: ${error}`
|
466 |
+
},
|
467 |
+
id: fc.id,
|
468 |
+
},
|
469 |
+
],
|
470 |
+
});
|
471 |
+
}
|
472 |
+
} else {
|
473 |
+
console.error("Unhandled function call:", fc.name);
|
474 |
+
}
|
475 |
+
}
|
476 |
+
};
|
477 |
+
|
478 |
+
client.on("toolcall", onToolCall);
|
479 |
+
return () => {
|
480 |
+
client.off("toolcall", onToolCall);
|
481 |
+
};
|
482 |
+
}, [client]);
|
483 |
+
|
484 |
+
const handleEditorChange = (value: string | undefined) => {
|
485 |
+
if (!value) {
|
486 |
+
console.warn("Editor value is undefined or empty");
|
487 |
+
return;
|
488 |
+
}
|
489 |
+
|
490 |
+
// Always update both editor content and displayed content when user types
|
491 |
+
setEditorContent(value);
|
492 |
+
setDisplayedContent(value);
|
493 |
+
|
494 |
+
if (!containerRef.current) {
|
495 |
+
console.warn("Container ref is not available");
|
496 |
+
return;
|
497 |
+
}
|
498 |
+
|
499 |
+
try {
|
500 |
+
console.log("Updating sketch from editor");
|
501 |
+
const result = window.updateSketch(value, containerRef.current);
|
502 |
+
console.log("Editor update result:", result);
|
503 |
+
} catch (error) {
|
504 |
+
console.error("Error updating sketch from editor:", error);
|
505 |
+
}
|
506 |
+
};
|
507 |
+
|
508 |
+
const startResizing = useCallback((e: React.MouseEvent) => {
|
509 |
+
setIsResizing(true);
|
510 |
+
e.preventDefault();
|
511 |
+
}, []);
|
512 |
+
|
513 |
+
const stopResizing = useCallback(() => {
|
514 |
+
setIsResizing(false);
|
515 |
+
}, []);
|
516 |
+
|
517 |
+
const resize = useCallback((e: MouseEvent) => {
|
518 |
+
if (isResizing && editorPanelRef.current) {
|
519 |
+
if (isMobile) {
|
520 |
+
// Vertical resizing for mobile
|
521 |
+
const containerHeight = editorPanelRef.current.parentElement?.clientHeight || 0;
|
522 |
+
const newHeight = (e.clientY / containerHeight) * 100;
|
523 |
+
setEditorHeight(Math.min(Math.max(20, newHeight), 80)); // Limit between 20% and 80%
|
524 |
+
} else {
|
525 |
+
// Horizontal resizing for desktop
|
526 |
+
const containerWidth = editorPanelRef.current.parentElement?.clientWidth || 0;
|
527 |
+
const newWidth = (e.clientX / containerWidth) * 100;
|
528 |
+
setEditorWidth(Math.min(Math.max(20, newWidth), 80)); // Limit between 20% and 80%
|
529 |
+
|
530 |
+
// Force p5.js to update its dimensions
|
531 |
+
if (containerRef.current) {
|
532 |
+
// Small delay to let the DOM update
|
533 |
+
setTimeout(() => {
|
534 |
+
const event = new Event('resize');
|
535 |
+
window.dispatchEvent(event);
|
536 |
+
}, 50);
|
537 |
+
}
|
538 |
+
}
|
539 |
+
}
|
540 |
+
}, [isResizing, isMobile]);
|
541 |
+
|
542 |
+
useEffect(() => {
|
543 |
+
if (isResizing) {
|
544 |
+
window.addEventListener('mousemove', resize);
|
545 |
+
window.addEventListener('mouseup', stopResizing);
|
546 |
+
}
|
547 |
+
return () => {
|
548 |
+
window.removeEventListener('mousemove', resize);
|
549 |
+
window.removeEventListener('mouseup', stopResizing);
|
550 |
+
};
|
551 |
+
}, [isResizing, resize, stopResizing]);
|
552 |
+
|
553 |
+
// Add a new useEffect to handle editor width changes
|
554 |
+
useEffect(() => {
|
555 |
+
if (!isMobile && containerRef.current) {
|
556 |
+
// Force p5.js to update its dimensions when editor width changes
|
557 |
+
const event = new Event('resize');
|
558 |
+
window.dispatchEvent(event);
|
559 |
+
}
|
560 |
+
}, [editorWidth, isMobile]);
|
561 |
+
|
562 |
+
return (
|
563 |
+
<div className="p5-with-editor">
|
564 |
+
{/* Editor Panel */}
|
565 |
+
<div
|
566 |
+
ref={editorPanelRef}
|
567 |
+
className="editor-panel"
|
568 |
+
style={isMobile ? { height: `${editorHeight}%` } : { width: `${editorWidth}%` }}
|
569 |
+
>
|
570 |
+
<Editor
|
571 |
+
height="100%"
|
572 |
+
defaultLanguage="javascript"
|
573 |
+
value={displayedContent}
|
574 |
+
onChange={handleEditorChange}
|
575 |
+
onMount={handleEditorDidMount}
|
576 |
+
theme="vs-dark"
|
577 |
+
options={{
|
578 |
+
minimap: { enabled: false },
|
579 |
+
fontSize: 14,
|
580 |
+
wordWrap: 'on',
|
581 |
+
automaticLayout: true,
|
582 |
+
readOnly: isAnimatingCode, // Make editor read-only during animation
|
583 |
+
cursorBlinking: isAnimatingCode ? 'smooth' : 'blink',
|
584 |
+
cursorStyle: isAnimatingCode ? 'line' : 'line',
|
585 |
+
cursorWidth: isAnimatingCode ? 3 : 1
|
586 |
+
}}
|
587 |
+
/>
|
588 |
+
<div
|
589 |
+
className="resize-handle"
|
590 |
+
onMouseDown={startResizing}
|
591 |
+
/>
|
592 |
+
{isSending && (
|
593 |
+
<div className="sending-indicator">
|
594 |
+
<Send size={16} />
|
595 |
+
<span>Sending code</span>
|
596 |
+
</div>
|
597 |
+
)}
|
598 |
+
{isAnimatingCode && (
|
599 |
+
<div className="animation-indicator">
|
600 |
+
<Code size={16} />
|
601 |
+
<span>Receiving code</span>
|
602 |
+
</div>
|
603 |
+
)}
|
604 |
+
</div>
|
605 |
+
|
606 |
+
{/* Sketch Panel */}
|
607 |
+
<div className="sketch-panel">
|
608 |
+
<div
|
609 |
+
ref={containerRef}
|
610 |
+
className="sketch-container"
|
611 |
+
/>
|
612 |
+
</div>
|
613 |
+
</div>
|
614 |
+
);
|
615 |
+
}
|
616 |
+
|
617 |
+
export const P5WithEditor = memo(P5WithEditorComponent);
|
src/components/p5/p5-with-editor.scss
ADDED
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.p5-with-editor {
|
2 |
+
display: flex;
|
3 |
+
width: 100%;
|
4 |
+
height: 100%;
|
5 |
+
overflow: hidden;
|
6 |
+
position: relative;
|
7 |
+
border-radius: 10px;
|
8 |
+
|
9 |
+
@media (max-width: 768px) {
|
10 |
+
flex-direction: column;
|
11 |
+
}
|
12 |
+
|
13 |
+
.editor-panel {
|
14 |
+
width: 40%;
|
15 |
+
height: 100%;
|
16 |
+
border-right: 1px solid #2d2d2d;
|
17 |
+
background: #1e1e1e;
|
18 |
+
position: relative;
|
19 |
+
|
20 |
+
@media (max-width: 768px) {
|
21 |
+
width: 100%;
|
22 |
+
height: 50%;
|
23 |
+
border-right: none;
|
24 |
+
border-bottom: 1px solid #2d2d2d;
|
25 |
+
}
|
26 |
+
|
27 |
+
.resize-handle {
|
28 |
+
position: absolute;
|
29 |
+
right: -5px;
|
30 |
+
top: 0;
|
31 |
+
bottom: 0;
|
32 |
+
width: 10px;
|
33 |
+
cursor: col-resize;
|
34 |
+
z-index: 10;
|
35 |
+
|
36 |
+
@media (max-width: 768px) {
|
37 |
+
right: 0;
|
38 |
+
top: auto;
|
39 |
+
bottom: -5px;
|
40 |
+
width: 100%;
|
41 |
+
height: 10px;
|
42 |
+
cursor: row-resize;
|
43 |
+
}
|
44 |
+
|
45 |
+
&:hover {
|
46 |
+
background: rgba(255, 255, 255, 0.1);
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
.sending-indicator {
|
51 |
+
position: absolute;
|
52 |
+
top: 10px;
|
53 |
+
right: 10px;
|
54 |
+
background-color: #1f94ff;
|
55 |
+
color: white;
|
56 |
+
padding: 8px 12px;
|
57 |
+
border-radius: 20px;
|
58 |
+
font-size: 13px;
|
59 |
+
font-weight: 500;
|
60 |
+
z-index: 20;
|
61 |
+
display: flex;
|
62 |
+
align-items: center;
|
63 |
+
gap: 6px;
|
64 |
+
box-shadow: 0 2px 10px rgba(31, 148, 255, 0.4);
|
65 |
+
animation: slideInRight 0.3s ease-out, pulseBlue 2s infinite;
|
66 |
+
}
|
67 |
+
|
68 |
+
.animation-indicator {
|
69 |
+
position: absolute;
|
70 |
+
top: 10px;
|
71 |
+
left: 10px;
|
72 |
+
background-color: #0d9c53;
|
73 |
+
color: white;
|
74 |
+
padding: 8px 12px;
|
75 |
+
border-radius: 20px;
|
76 |
+
font-size: 13px;
|
77 |
+
font-weight: 500;
|
78 |
+
z-index: 20;
|
79 |
+
display: flex;
|
80 |
+
align-items: center;
|
81 |
+
gap: 6px;
|
82 |
+
box-shadow: 0 2px 10px rgba(13, 156, 83, 0.4);
|
83 |
+
animation: slideInLeft 0.3s ease-out, pulseGreen 2s infinite;
|
84 |
+
|
85 |
+
svg {
|
86 |
+
animation: pulse 1.5s ease infinite;
|
87 |
+
}
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
.sketch-panel {
|
92 |
+
flex: 1;
|
93 |
+
height: 100%;
|
94 |
+
position: relative;
|
95 |
+
min-width: 0;
|
96 |
+
|
97 |
+
@media (max-width: 768px) {
|
98 |
+
height: 50%;
|
99 |
+
}
|
100 |
+
|
101 |
+
.sketch-container {
|
102 |
+
width: 100%;
|
103 |
+
height: 100%;
|
104 |
+
display: flex;
|
105 |
+
align-items: center;
|
106 |
+
justify-content: center;
|
107 |
+
position: relative;
|
108 |
+
background: #000;
|
109 |
+
overflow: hidden;
|
110 |
+
|
111 |
+
canvas {
|
112 |
+
display: block !important;
|
113 |
+
max-width: 100% !important;
|
114 |
+
max-height: 100% !important;
|
115 |
+
position: relative !important;
|
116 |
+
margin: auto;
|
117 |
+
object-fit: contain;
|
118 |
+
}
|
119 |
+
|
120 |
+
// Fix for p5.js accessibility tooltips
|
121 |
+
canvas[data-testid],
|
122 |
+
canvas#defaultCanvas0,
|
123 |
+
.p5-managed-canvas {
|
124 |
+
&::before,
|
125 |
+
&::after {
|
126 |
+
display: none !important;
|
127 |
+
content: none !important;
|
128 |
+
}
|
129 |
+
}
|
130 |
+
}
|
131 |
+
}
|
132 |
+
}
|
133 |
+
|
134 |
+
@keyframes fadeInOut {
|
135 |
+
0% { opacity: 0; }
|
136 |
+
20% { opacity: 1; }
|
137 |
+
80% { opacity: 1; }
|
138 |
+
100% { opacity: 0; }
|
139 |
+
}
|
140 |
+
|
141 |
+
@keyframes pulseBlue {
|
142 |
+
0% { box-shadow: 0 0 5px rgba(31, 148, 255, 0.4); }
|
143 |
+
50% { box-shadow: 0 0 15px rgba(31, 148, 255, 0.7); }
|
144 |
+
100% { box-shadow: 0 0 5px rgba(31, 148, 255, 0.4); }
|
145 |
+
}
|
146 |
+
|
147 |
+
@keyframes pulseGreen {
|
148 |
+
0% { box-shadow: 0 0 5px rgba(13, 156, 83, 0.4); }
|
149 |
+
50% { box-shadow: 0 0 15px rgba(13, 156, 83, 0.7); }
|
150 |
+
100% { box-shadow: 0 0 5px rgba(13, 156, 83, 0.4); }
|
151 |
+
}
|
152 |
+
|
153 |
+
@keyframes slideInRight {
|
154 |
+
from { transform: translateX(20px); opacity: 0; }
|
155 |
+
to { transform: translateX(0); opacity: 1; }
|
156 |
+
}
|
157 |
+
|
158 |
+
@keyframes slideInLeft {
|
159 |
+
from { transform: translateX(-20px); opacity: 0; }
|
160 |
+
to { transform: translateX(0); opacity: 1; }
|
161 |
+
}
|
162 |
+
|
163 |
+
@keyframes spin {
|
164 |
+
from { transform: rotate(0deg); }
|
165 |
+
to { transform: rotate(360deg); }
|
166 |
+
}
|
167 |
+
|
168 |
+
@keyframes pulse {
|
169 |
+
0% { transform: scale(1); }
|
170 |
+
50% { transform: scale(1.2); }
|
171 |
+
100% { transform: scale(1); }
|
172 |
+
}
|
173 |
+
|
174 |
+
@keyframes ellipsis {
|
175 |
+
0% { content: '.'; }
|
176 |
+
33% { content: '..'; }
|
177 |
+
66% { content: '...'; }
|
178 |
+
100% { content: '.'; }
|
179 |
+
}
|
180 |
+
|
181 |
+
// Editor line highlighting
|
182 |
+
.current-line-highlight {
|
183 |
+
background-color: rgba(13, 156, 83, 0.1) !important;
|
184 |
+
border-left: 2px solid #0d9c53 !important;
|
185 |
+
}
|
186 |
+
|
187 |
+
.current-line-glyph {
|
188 |
+
background-color: #0d9c53;
|
189 |
+
width: 4px !important;
|
190 |
+
margin-left: 3px;
|
191 |
+
}
|
192 |
+
|
193 |
+
// Enhance cursor visibility during animation
|
194 |
+
:global(.monaco-editor .cursor) {
|
195 |
+
transition: all 0.1s ease;
|
196 |
+
}
|
197 |
+
|
198 |
+
// Add a subtle typing sound effect visual cue
|
199 |
+
@keyframes cursorPulse {
|
200 |
+
0% { opacity: 1; }
|
201 |
+
50% { opacity: 0.5; }
|
202 |
+
100% { opacity: 1; }
|
203 |
+
}
|
src/components/side-panel/SidePanel.tsx
CHANGED
@@ -30,9 +30,13 @@ const filterOptions = [
|
|
30 |
{ value: "none", label: "All" },
|
31 |
];
|
32 |
|
33 |
-
|
|
|
|
|
|
|
|
|
34 |
const { connected, client } = useLiveAPIContext();
|
35 |
-
const [open, setOpen] = useState(window.innerWidth >= 768);
|
36 |
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
37 |
const loggerRef = useRef<HTMLDivElement>(null);
|
38 |
const loggerLastHeightRef = useRef<number>(-1);
|
@@ -43,7 +47,9 @@ export default function SidePanel() {
|
|
43 |
const handleResize = () => {
|
44 |
const mobileScreen = window.innerWidth < 768;
|
45 |
setIsMobile(mobileScreen);
|
46 |
-
|
|
|
|
|
47 |
};
|
48 |
|
49 |
// Initial check
|
@@ -54,7 +60,7 @@ export default function SidePanel() {
|
|
54 |
|
55 |
// Cleanup
|
56 |
return () => window.removeEventListener('resize', handleResize);
|
57 |
-
}, []);
|
58 |
|
59 |
const [textInput, setTextInput] = useState("");
|
60 |
const [selectedOption, setSelectedOption] = useState<{
|
|
|
30 |
{ value: "none", label: "All" },
|
31 |
];
|
32 |
|
33 |
+
interface SidePanelProps {
|
34 |
+
initialCollapsed?: boolean;
|
35 |
+
}
|
36 |
+
|
37 |
+
export default function SidePanel({ initialCollapsed = false }: SidePanelProps) {
|
38 |
const { connected, client } = useLiveAPIContext();
|
39 |
+
const [open, setOpen] = useState(!initialCollapsed && window.innerWidth >= 768);
|
40 |
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
41 |
const loggerRef = useRef<HTMLDivElement>(null);
|
42 |
const loggerLastHeightRef = useRef<number>(-1);
|
|
|
47 |
const handleResize = () => {
|
48 |
const mobileScreen = window.innerWidth < 768;
|
49 |
setIsMobile(mobileScreen);
|
50 |
+
if (!initialCollapsed) {
|
51 |
+
setOpen(!mobileScreen);
|
52 |
+
}
|
53 |
};
|
54 |
|
55 |
// Initial check
|
|
|
60 |
|
61 |
// Cleanup
|
62 |
return () => window.removeEventListener('resize', handleResize);
|
63 |
+
}, [initialCollapsed]);
|
64 |
|
65 |
const [textInput, setTextInput] = useState("");
|
66 |
const [selectedOption, setSelectedOption] = useState<{
|