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<{
|