Trudy commited on
Commit
ac97894
·
1 Parent(s): 0fba851

Add P5WithEditor component and improve UI/UX

Browse files
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
- "dev": true,
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
- "dev": true,
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
- "dev": true
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
- "dev": true
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
- "dev": true
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
- "dev": true
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
- "dev": true
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
- "dev": true,
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
- "dev": true
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
- "dev": true,
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
- "dev": true,
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
- "dev": true
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
- "dev": true
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
- "dev": true,
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>Self-modifying P5.js Sketch</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(window.innerWidth, window.innerHeight);
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(window.innerWidth, window.innerHeight);
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(window.innerWidth, window.innerHeight);
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: center;
142
- justify-content: center;
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: center;
153
- justify-content: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- // Only show the modal on iOS devices
 
 
 
 
 
 
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
- <P5Sketch />
 
 
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, // Lower temperature for more deterministic outputs
54
- responseModalities: "text",
 
 
 
 
 
 
 
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: "100vw",
139
- height: "100vh",
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
- export default function SidePanel() {
 
 
 
 
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
- setOpen(!mobileScreen);
 
 
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<{