diff --git a/browser_ai_extension/browse_ai/package-lock.json b/browser_ai_extension/browse_ai/package-lock.json index 3d70097..f56974d 100644 --- a/browser_ai_extension/browse_ai/package-lock.json +++ b/browser_ai_extension/browse_ai/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", - "clsx": "^2.1.1", + "clsx": "^2.0.0", "lucide-react": "^0.263.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -80,7 +80,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1462,7 +1461,6 @@ "integrity": "sha512-oSVZmGtDPmRZtVDqvdKUi/qgCsWp5IDY29wp8na8Bj4B3cc99hfNzvNhlMkVVxctkAOGUA3Km7MMpBHAnWfcIA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1823,7 +1821,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -2026,7 +2023,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", "engines": { "node": ">=6" } @@ -2865,7 +2861,6 @@ "integrity": "sha512-PErok3DZSA5WGMd6XXV3IRNO0mlB+wW3OzhFJLEec1jSERg2j1bxJ6e5Fh6N6fn3FH2T9AP4UYNb/pYlADB9sA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob-watcher": "^6.0.0", "gulp-cli": "^3.1.0", @@ -3306,7 +3301,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -3876,7 +3870,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4056,7 +4049,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4615,7 +4607,6 @@ "version": "1.14.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -4987,7 +4978,6 @@ "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/browser_ai_extension/browse_ai/package.json b/browser_ai_extension/browse_ai/package.json index 5862013..c55e74e 100644 --- a/browser_ai_extension/browse_ai/package.json +++ b/browser_ai_extension/browse_ai/package.json @@ -25,10 +25,12 @@ "dependencies": { "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", - "clsx": "^2.1.1", + "clsx": "^2.0.0", + "lottie-react": "^2.4.1", "lucide-react": "^0.263.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-voice-visualizer": "^2.0.8", "socket.io-client": "^4.7.2", "tailwind-merge": "^1.14.0" }, diff --git a/browser_ai_extension/browse_ai/pnpm-lock.yaml b/browser_ai_extension/browse_ai/pnpm-lock.yaml index e9a015b..bd51e5b 100644 --- a/browser_ai_extension/browse_ai/pnpm-lock.yaml +++ b/browser_ai_extension/browse_ai/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: clsx: specifier: ^2.0.0 version: 2.1.1 + lottie-react: + specifier: ^2.4.1 + version: 2.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lucide-react: specifier: ^0.263.0 version: 0.263.1(react@18.3.1) @@ -26,6 +29,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) + react-voice-visualizer: + specifier: ^2.0.8 + version: 2.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) socket.io-client: specifier: ^4.7.2 version: 4.8.1 @@ -1122,6 +1128,15 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lottie-react@2.4.1: + resolution: {integrity: sha512-LQrH7jlkigIIv++wIyrOYFLHSKQpEY4zehPicL9bQsrt1rnoKRYCYgpCUe5maqylNtacy58/sQDZTkwMcTRxZw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lottie-web@5.13.0: + resolution: {integrity: sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1333,6 +1348,12 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-voice-visualizer@2.0.8: + resolution: {integrity: sha512-oA/IyNeh15+mERgm2n33IAqQaMGBEFN1n3gs2V/0LbLyscqsbhNo8JOFXh2I9neIqYJ4q27TEzXzbO00Xq/aXg==} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -2703,6 +2724,14 @@ snapshots: dependencies: js-tokens: 4.0.0 + lottie-react@2.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + lottie-web: 5.13.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + lottie-web@5.13.0: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -2876,6 +2905,11 @@ snapshots: react-refresh@0.17.0: {} + react-voice-visualizer@2.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 diff --git a/browser_ai_extension/browse_ai/src/assets/bot-head.json b/browser_ai_extension/browse_ai/src/assets/bot-head.json new file mode 100644 index 0000000..b6a4a08 --- /dev/null +++ b/browser_ai_extension/browse_ai/src/assets/bot-head.json @@ -0,0 +1,12 @@ +PK RZ\X\xcc @\x8d manifest.json{"version":"1.0","revision":1,"keywords":"","author":"LottieFiles","generator":"dotLottie-js","animations":[{"id":"dcf09ed0-2080-4219-81e0-e18fae2bc4ec","direction":1,"speed":1,"playMode":"normal","loop":false,"autoplay":false,"hover":false,"intermission":0}]}PK RZ\X\xeeg 1\xd6 \x8cO 4 animations/dcf09ed0-2080-4219-81e0-e18fae2bc4ec.json\xed\\xdbn#\xc7 \xfd a\x9eg:\xd3\xf7 =\xe5\xe6 y `\xc4@^\x88E\xc0h\xa9 \xbd\x94(\x90t C \xc4\xc8[ \xd8 $0 ذ \xd6?\xe6S5\xf7 \x87"E\x8a;\xbb\xcb]PC\xf5\xf4TU\xdfN\x9d\xae\xae\xd1]ts \x9dG\xbf\x99_ߞ\xc9(\x8e^\xbe| \x9d\xa7qt \x9d\xcb4\xe0\xcb\xdf\xca/ד\xd58:\xbf\x8b^\xa1\xfa/g\xf3\xd5j:\xb9\x9c\xce&\xcb_\xac\xe6\xf3\xd9\xeb\xe9*\xf9ly\x96 +\xad\x85\x8a\xee\xe3h6\xfeb\xb2XF磻h\xf5 Kd=\x9f,&\xc9E\xa9k\xb9\xc0\x91\xe1ˊk\xcco\xa3\xf3 \xe3h\x8a+\x99 K.dz夶\xea/\x90Aw\xc6\xcb?\x8c\x97\xaf\xab\xdb\xe39 \xbf\x86\xbe\xbb\x88\x8d\xc4 *\x80~kҘ>\xe9 \xfe; \xe3\xcazE \x99\xa6q\xf1)*9\xaa\x84[\x8dZ)\x8a`WK\xb4 -\xd1 +UФ\xe6C\x85Nzx\xd96\x8c\x8a`v\xa3\x84Lȫ\xcb{ܜ\B $W P\xc9br\xf9{\xf4GD \xf9\xe7 9\xbd\xc1\xaf\xf2 \x95\xff\x8ab+\xbc\xa0\xd2Kآ v\xebx\xb9\x9c\xac\xf2\x91\xe1!\xa1\xe7[\xd2:Cg\x8a\xa1\xfb\xe9\xeb7\xff\xc0\xe7˟\xbez\xf3 >\xff=S\xc5 Btc 5\xb7\xe2\x90#\x98(%\x85\x976άH\xad<\xe8H\xc2vz\xee. +ԧ \xac㈦,\xaaM\xabR\xe7<\x97 JG2\xa4B \xd8$+\x9b\xf2>X\xe1\x99Q\x92 +\x89 ra| \xf2\xa9\xd4ƪ(\xbb\x8f\x9f\xa0\xd4{V*Ӻ'\xa0U\x85Z-=bj\x95\xb9 (\xdfCi\x90\xac4dM\x9d ˖uv4TzILB\x96쥳ջT\xa9\xa9S\xf2s\xcd\xeemv:\xe9\xed \xa9\x90\xd1r\xe9\xaeڧO\x87\xac\x90\xebwj\xaeHeVI\x90\xe5h\xf6\x88 \xa96\x8f \x847\x85 c\xab \xa8l\xabڼ -\xafƷ\x93 \xa2W A\xb9\xae\xeb }\x83{\xbf\xfa\xed\xaf?:\xfb\xd3\xe4b5_\x9c}\xbc\x98 ~\x8b\xaa\x8c&\x9f\x92 \xf6 \xac \x8e.x,p\x970 \xa5\xb0\xb7T\xb0\xbc\xdaNA.3i+\xfad\xbc\xbaj\xea!\xa4l M\xdeһ\xe8":_->\x87T\x9a_## \xe6\x97 \xa9\xb2/\xe2Q \xe2Dy\xfa"Sᕌ\x9d\x90\x86~M\x85\xb16\xb6BYM\xbf\x9aX\xa2\xf3GA \xa5b-\xact/\xd0]45G\x89\xcdbO\x95\x8c0\x99\x8f\xa5 X\xcb\W[\x85u"|\xa0\xbb &uJ\xa2\xa5 N\xc7 \xaa) \xc5 \x8aQ \x93\x80\xa4 \xe8!U H \xd0E\x95\x94ױ\xf2dB\xa2t \xca m \xacc\xd1@\xd1,V.\xbf\x9b\xca8\xe3 ) \xd6h\x83\xb7\xc2i\x98K\xf3\x8b\xd7 M\x9e\xbc\xf3Wێ\xee\xf8\xf6jz\x81\xee\xfft\xb5\x98\xbf\x9eT Ϳ\xf1 \xcc\xd0\xc9\xe8\xfb\xd9g|\xb9\x9e\xb1o\xe9\x99r4\xd3\xe1\xf9 \xfaRLk \xac \xea\xc7\xf8_\xccl][\xfe\xearW\xcb?N~7\x9d\xcdJ\xcbQ\xfer:\xb9Y\x9dQ!7`\xd2uL\xe8y \xe9\xb0 \xb3\xac\xe1r@\x92\xee\xc8\xc9`&\xb7\x9d\xcf tAdJ\x82;8\xe5\x8dM\xb3\x90 \xadD\xd6*ȸ( \x85ّ )\xbd3 \xd0% \x86*\xc5\xd4h\xfd#\xc8kWM = lq\xa9 FK\xed $ \xa1\xa8`\x8b \xb6+* \x96 +`]b\xf0 B \xd8I\xa7\xa4\xe3\x9b1'1\xc9B\x88\xa5\xd7X^\x98\xe5\xd5x\xe6$\xa2 |\xd2zHW 5 z\xb0\xb5\xab&\xc7C2PO\xb7 \xbbۂ\xd71\xb9\xe8tEݪ\x9e\xf6\xf8\xfb\xfb \x8c\xc3Lڠv\xbc\xc0$+<\xc7F\xc2Ur\xe6# .\x9d .= µ\xce(t \xcd)A\x9c\x8fW\xce>b\xb3 \xe1\xa94I٢< \xb6 \xbaJ\xf1\x95>R\xbe\x97\xaev\xab\xe0 \xd66ko5 >\xadV\xe3K\xbeڣf\x83\x85[\xf3'\xa2d9}\xc1\xb7\x8dF\x96\xdd\xdcn\xa0\xca\xdc\xda \xdaBj\xd0d [Jޗ\xa9 \xea6E "Ȩ ݶ 8W\xf00ey\xdeB\x8a \xc8 \xe01\xdf,;\xe1A\xa3O \xeaD\xa0N j \xa5\xbc0\x9a(A N\x95&\x9c T \x81\xf2RX\xed\xd0[\x98ֶ\xec\xad \x9b@ \xf0\xfa \xd4Wo\xfe\x8f\xcf\xf7D\x9e\xf0\xf9\xf6x +i\x81xRQ\xbcg]\xdf\xee\xe5&۔\x8b &z\xf1qJ \xaa0 \xbe5D5 \xa8(B ] ƕ\x9eo\xbb\xd0(\xda\xde\xed\x80牎 ė\xfe e\xe3\x9bW\xb3m\xfd\xe9\xe2b7 J\xf2 \xea\xaa k\xb1 \xe8[=4Z\xed\x8eC\xe7V\x8b\xae\xb3^ \xbc\xd5\xfb\xe3\x84.ɝ\xecdy\xd3 U\x9e\xa7\xa3 \xc4 X \xa4\xb6F\xaaJ-\x99\xbb J\xed\xdbA\xd2 \x9eȏʟB\xf7޳\x8e\xb0 { \xfb;\xcc\xccn\xc0\xe0\x89\xb4\xf5\x926\x84`)$ڸ \x9c\xb9=kX\xb1 +} f\xcf 6\xe0=\x8fÄ\xed\xd9\xf3 0y\xe9 \xa7 \xf4\xd8 \x92| ܼ g )\xe0c\xf6\xf4\xed\x8fcȕ\xe5\xd1\xc43\xee~\xc0\x9f\xcb\xdd \xc7U\xd6 \x94~,\x82Jt\xfd 킎\xb6 \x82\x97 s\x86\xdf \xcc \xa8cQc \xf4\xe8 d\xb7\xfdN\xa9\xca\xc0GW\xba\xcc\xd6\xcavkOKI\xb5\xd7ٔw\xb0\xae# \x9b\xb4\xf9\xa4\x94Ͳ]E\x8e@\x9e\xadٔ6\xd8 4kX9 +\xe9 \xc8 @\xae\x80$\xaa\xea) c\x99 \x82\xcd"c\xa0f\x8c R\xc7p \x83\xe8!\x99\xa3\xc11%=\x93S_ p \xc9 N : i$\xe9`\xfe y\x85O\xf4sAʉ, \xf0\x81 \x9b\xe0+‰4 \xe8S\xac[ 쾕 l l\xe2L l\xd6\xe1 I\x80Ů \xc7\xc3DC\x8d\x85 \x9a%|\xf6 d(c\xf7\x80\xae=\x9d\xf3o:\xe7G\xcaS\xe2JO\xf3a\x9f\xef#\xf9kS\x82\xe4?A \xbe)\xc9\xc3\xd1\xd2#\x91Ĕ"W i \x89J\x83\xe0 \xee\x92\xe62\xf5*\xa3\xf2N8\xa47/\xb58\xe0qC\xcbc!\xd2 f\xe9; \xac\xfch6\x9b\xde.\xb7=ʚl\xb9\xd1(=i!\xbd\xabl\x97}]g\xdaa \xbb\xfa\cx\x9e\x89\xdeF[\xef\x99 \xdc\xf1\x88\xf8\xb4.\xc7\xdb\xcb x \x86\xf5\xea2\xf0` \xf2 \xb1\x8fs\xe19O\xb1\xf6\x8a\x89\xb4)\xe8\xe6x\xc8\xf1)h\x9eό \xa2p\xc4> \xc9\xcc ?\x83\xcd \xf2\xbd\xe9\xaa) T \xafz\x808RB2\xd3M\xed\x91\xc9 +z \x84L9 \x89; O \xb0Y\x83 d\xc5P\xf1\xbe\x9f\xe7 \xf2\x90 \x8b\x9cU\xec\x84\xf0\x86,g\xb2\xe2Q \xb6\xc1\x89\xa6D-\xa1\xc1 T \x9a b\xc7DL\xd48h\xe4\xa4S\xcay\xc5{\x9c\x89sB\xe3\x81:\x87\xd5\xc7 b [͸ (\xda!5B\xa381\xa27U\x8a\x86\x80#k\xa6\xd6x!\xce B \xbeK)\xb5l \xda &\x8c \xa1\xad I .\x82 \x8f\xdb $f\x9a /N\xd4v=\xb5\x95\x8ez\xa9\xdc |\xd8\xd4v \x9f\xfd \x9f \x97=^\xae\xaa\xc6\xcb\xc4 + \xc3`\xb2\xca\xe5\xa7A\x85=\xbbĿ\xb6=\x80\xf78\x9a \x90b\x86#\xb8T \xf4W\xaf4\xf7\xb3\xd85ƽ \xa7\xed\xef \x81\x95x1G\xea &\xde \xff\xf3l\x945(\xe2 \x8d\xcb\xd0)k8Ϗ\xb1x\xf1\xab\xff\xec\xa1 \xb7GKd \xdcv:<\xde& \xbf\xc3! \xfb \xc0]g\xde qO\x88{B܁".\xd6\xf4&\xc4- \xb6?\xe0\xfb\xbf\x8fv\xca \xd8\xc1>{\x87\x97 \xb6\xfc\xab/ݤ\xfe΋\xc7\xdbJ\xa1-;}\xaapj\xef[ u\xb8t\xcb?Q\xc5\xf1\xa1 \xb1׃\xbd5\xe0N\xa0Y\xc5Y\xe1\xc1\xf8e\xf1 f>\x82\x99 \x91\x80e\xe32t̬1\xe3 \xab \xff PK RZ\X\xcc @\x8d manifest.jsonPK RZ\X\xeeg 1\xd6 \x8cO 4 / animations/dcf09ed0-2080-4219-81e0-e18fae2bc4ec.jsonPK \x9d W \ No newline at end of file diff --git a/browser_ai_extension/browse_ai/src/content/Overlay.tsx b/browser_ai_extension/browse_ai/src/content/Overlay.tsx deleted file mode 100644 index f80ceb8..0000000 --- a/browser_ai_extension/browse_ai/src/content/Overlay.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { useEffect, useState } from 'react' - -export const Overlay = () => { - const [isVisible, setIsVisible] = useState(false) - const [status, setStatus] = useState('') - const [isError, setIsError] = useState(false) - - useEffect(() => { - const handleMessage = (request: any, sender: any, sendResponse: any) => { - if (request.type === 'SHOW_OVERLAY_STATUS') { - setIsVisible(true) - setStatus(request.message) - setIsError(request.isError || false) - } else if (request.type === 'HIDE_OVERLAY') { - setIsVisible(false) - } - } - - chrome.runtime.onMessage.addListener(handleMessage) - - return () => { - chrome.runtime.onMessage.removeListener(handleMessage) - } - }, []) - - if (!isVisible) return null - - return ( -
- {!isError && ( -
- )} - {status} - -
- ) -} diff --git a/browser_ai_extension/browse_ai/src/content/index.tsx b/browser_ai_extension/browse_ai/src/content/index.tsx deleted file mode 100644 index 8003c75..0000000 --- a/browser_ai_extension/browse_ai/src/content/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import { createRoot } from 'react-dom/client' -import { Overlay } from './Overlay' - -// Create a container for the overlay -const container = document.createElement('div') -container.id = 'browser-ai-overlay-root' -// Ensure it's on top of everything and doesn't interfere with layout -container.style.position = 'fixed' -container.style.zIndex = '2147483647' // Max z-index -container.style.top = '0' -container.style.left = '0' -container.style.width = '0' -container.style.height = '0' -container.style.pointerEvents = 'none' // Allow clicks to pass through by default - -document.body.appendChild(container) - -const root = createRoot(container) -root.render() - -console.log('Browser.AI Overlay injected') diff --git a/browser_ai_extension/browse_ai/src/manifest.ts b/browser_ai_extension/browse_ai/src/manifest.ts index 902e4ee..b61f697 100644 --- a/browser_ai_extension/browse_ai/src/manifest.ts +++ b/browser_ai_extension/browse_ai/src/manifest.ts @@ -37,24 +37,13 @@ export default defineManifest({ side_panel: { default_path: 'sidepanel.html', }, - // @ts-ignore - content_scripts: [ - { - matches: [''], - // @ts-ignore - js: ['src/content/index.tsx'], - // @ts-ignore - run_at: 'document_end', - }, - ], - // @ts-ignore web_accessible_resources: [ { resources: ['img/icon16.png', 'img/icon32.png', 'img/icon48.png', 'img/icon128.png'], matches: [], }, ], - permissions: ['sidePanel', 'storage', 'debugger', 'tabs', 'activeTab', 'scripting'], + permissions: ['sidePanel', 'storage', 'debugger', 'tabs', 'activeTab'], // @ts-ignore host_permissions: [''], }) diff --git a/browser_ai_extension/browse_ai/src/sidepanel/SidePanel.tsx b/browser_ai_extension/browse_ai/src/sidepanel/SidePanel.tsx index 978bdea..07e269b 100644 --- a/browser_ai_extension/browse_ai/src/sidepanel/SidePanel.tsx +++ b/browser_ai_extension/browse_ai/src/sidepanel/SidePanel.tsx @@ -1,579 +1,82 @@ -import { useState, useEffect, useRef, useCallback } from 'react' -import { io, Socket } from 'socket.io-client' +import { useState, useEffect } from 'react'; +import { ConversationMode, Message } from './components/ConversationMode'; +import { TaskStatus } from './components/TaskStatus'; +import { io, Socket } from 'socket.io-client'; + +const SidePanel = () => { + const [socket, setSocket] = useState(null); + const [connected, setConnected] = useState(false); + const [messages, setMessages] = useState(() => { + const savedMessages = sessionStorage.getItem('chatMessages'); + return savedMessages ? JSON.parse(savedMessages) : []; + }); + const [intent, setIntent] = useState(null); + const [taskStatus, setTaskStatus] = useState({ + isRunning: false, + currentTask: null, + isPaused: false, + }); -import { ChatInput } from './components/ChatInput' -import { StepList } from './components/TaskProgress/StepList' -import { TaskStatusHeader } from './components/TaskProgress/TaskStatusHeader' -import { VoiceVisualizer } from './components/Visuals/VoiceVisualizer' -import { LogEvent } from './components/ExecutionLog' -import { Layout } from './components/Layout' -import { useTheme } from '../utils/theme' -import { voiceRecognition } from '../services/VoiceRecognition' - -import { - TaskStatus as ProtocolTaskStatus, - StartTaskPayload, - ExtensionSettings, - DEFAULT_SETTINGS, - WEBSOCKET_NAMESPACE, - MAX_RECONNECTION_ATTEMPTS, - RECONNECTION_DELAY_MS, -} from '../types/protocol' -import { loadSettings, onSettingsChanged, openOptionsPage } from '../utils/helpers' -import { - loadTaskStatus, - saveTaskStatus, - loadCdpEndpoint, - saveCdpEndpoint, - onTaskStatusChanged, -} from '../utils/state' - -export const SidePanel = () => { - const [socket, setSocket] = useState(null) - const [connected, setConnected] = useState(false) - const [logs, setLogs] = useState([]) - - // Theme context - const { theme, toggleTheme } = useTheme() - - // Task State - const [taskStatus, setTaskStatus] = useState({ - is_running: false, - current_task: null, - has_agent: false, - is_paused: false, - }) - - // UI State - const [concentrationMode, setConcentrationMode] = useState(false) - const [taskResult, setTaskResult] = useState('') - - const [settings, setSettings] = useState(DEFAULT_SETTINGS) - const [cdpEndpoint, setCdpEndpoint] = useState('') - const socketRef = useRef(null) - - // Scroll ref - const scrollRef = useRef(null) - - // Voice State Hook (lifted from ChatInput roughly, but we need to know global listening state) - // Actually, ChatInput manages listening state internally. - // We need to know if we are in "Voice Mode" to trigger Concentration Mode. - // We can infer this: If ChatInput triggers a start via voice, or if we toggle it. - // For now, let's add a manual toggle or infer from interaction. - // Requirement: "Implement concentration mode with voice... visualize the voice". - - // Let's track if voice is active via an event listener or shared state service if possible, - // but since VoiceRecognition is a singleton service, we can add a listener to it or just pass callbacks. - // Refactoring ChatInput to expose `isListening` or lifting the state up would be cleaner. - // For this plan, let's lift `isListening` state to SidePanel so we can drive the UI. - - const [isListening, setIsListening] = useState(false) - - // Auto-scroll logs useEffect(() => { - if (scrollRef.current && !concentrationMode) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight - } - }, [logs, concentrationMode]) + sessionStorage.setItem('chatMessages', JSON.stringify(messages)); + }, [messages]); - // Initial Load useEffect(() => { - let isMounted = true - - loadSettings().then((loadedSettings) => { - if (isMounted) setSettings(loadedSettings) - }) - - const handleSettingsChange = (newSettings: ExtensionSettings) => { - if (isMounted) setSettings(newSettings) - } - const unsubscribeSettings = onSettingsChanged(handleSettingsChange) - - loadTaskStatus().then((status) => { - if (isMounted && status) setTaskStatus(status) - }) - - loadCdpEndpoint().then((endpoint) => { - if (isMounted && endpoint) setCdpEndpoint(endpoint) - }) - - const handleTaskStatusChange = (newStatus: ProtocolTaskStatus) => { - if (isMounted) setTaskStatus(newStatus) - } - const unsubscribeTaskStatus = onTaskStatusChanged(handleTaskStatusChange) - - return () => { - isMounted = false - unsubscribeSettings() - unsubscribeTaskStatus() - } - }, []) - - // Persistence - useEffect(() => { - saveTaskStatus(taskStatus) - }, [taskStatus]) - useEffect(() => { - if (cdpEndpoint) saveCdpEndpoint(cdpEndpoint) - }, [cdpEndpoint]) - - // Update Page Overlay based on status - const updateOverlay = useCallback(async (status: string, isError: boolean = false) => { - try { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }) - if (tab?.id) { - chrome.tabs - .sendMessage(tab.id, { - type: 'SHOW_OVERLAY_STATUS', - message: status, - isError, - }) - .catch(() => {}) - } - } catch (e) { - console.error(e) - } - }, []) - - const hideOverlay = useCallback(async () => { - try { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }) - if (tab?.id) { - chrome.tabs.sendMessage(tab.id, { type: 'HIDE_OVERLAY' }).catch(() => {}) - } - } catch (e) {} - }, []) - - // Socket Connection - useEffect(() => { - if (socketRef.current) socketRef.current.close() - - const newSocket = io(`${settings.serverUrl}${WEBSOCKET_NAMESPACE}`, { - transports: ['websocket'], - reconnection: settings.autoReconnect, - reconnectionAttempts: MAX_RECONNECTION_ATTEMPTS, - reconnectionDelay: RECONNECTION_DELAY_MS, - }) + const newSocket = io('http://localhost:3000'); + setSocket(newSocket); newSocket.on('connect', () => { - setConnected(true) - newSocket.emit('extension_connect') - newSocket.emit('get_status') - }) + setConnected(true); + }); newSocket.on('disconnect', () => { - setConnected(false) - }) - - newSocket.on('status', (status: ProtocolTaskStatus) => { - setTaskStatus(status) - if (!status.is_running) { - if (status.current_task?.includes('failed')) { - updateOverlay('Task Failed', true) - } else if (status.current_task?.includes('completed')) { - updateOverlay('Task Completed', false) - setTimeout(hideOverlay, 3000) - } - } else { - updateOverlay('Browser.AI Running...', false) - } - }) - - newSocket.on('log_event', (event: LogEvent) => { - setLogs((prev) => [...prev, event]) - - // Update overlay with high-level steps - if (event.event_type === 'agent_step') { - const title = event.message.replace(/Step \d+:/i, '').trim() - updateOverlay(title || 'Processing step...', false) - } - - // Capture result from logs if available (heuristic) - if ( - event.event_type === 'agent_result' || - (event.message.includes('Result:') && event.level === 'INFO') - ) { - setTaskResult(event.message.replace('Result:', '').trim()) + setConnected(false); + }); + + newSocket.on('status', (status) => { + setTaskStatus(status); + if (!status.isRunning) { + setTimeout(() => { + setTaskStatus({ isRunning: false, currentTask: null, isPaused: false }); + setMessages([]); + }, 3000); } - }) - - newSocket.on('task_started', (data: { message: string }) => { - setLogs([]) - setTaskResult('') - updateOverlay('Starting Task...', false) - }) - - // Explicit task result event handling if backend sends it - newSocket.on( - 'task_result', - (result: { task: string; success: boolean; history: string | null }) => { - if (result.success && result.history) { - // Try to extract final text or just use a success message - setTaskResult('Task completed successfully.') - } - }, - ) - - setSocket(newSocket) - socketRef.current = newSocket + }); return () => { - newSocket.close() - } - }, [settings.serverUrl, updateOverlay, hideOverlay]) - - const getCdpEndpoint = async () => { - const endpoint = 'http://localhost:9222' - setCdpEndpoint(endpoint) - return endpoint - } - - const handleStartTask = async (task: string) => { - if (!task.trim() || !socket) return + newSocket.close(); + }; + }, []); - let endpoint = cdpEndpoint - if (!endpoint) { - endpoint = (await getCdpEndpoint()) || '' - } + const handleStartTask = (task: string, cdpEndpoint: string) => { + socket?.emit('start_task', { task, cdpEndpoint }); + }; - const payload: StartTaskPayload = { - task: task, - cdp_endpoint: endpoint, - is_extension: true, - } - - socket.emit('start_task', payload) - - setLogs([]) - setTaskResult('') - - // If we started via voice (implied if we are in concentration mode or isListening was true recently), - // keep concentration mode on. - // Otherwise, default to standard view unless toggled. - } - - const handleStopTask = () => { - socket?.emit('stop_task') - updateOverlay('Stopping...', false) - } - - const handlePauseTask = () => socket?.emit('pause_task') - const handleResumeTask = () => socket?.emit('resume_task') - - // Auto-switch to Concentration Mode when listening starts - useEffect(() => { - if (isListening) { - setConcentrationMode(true) - } - }, [isListening]) - - // Space bar to toggle listening - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if ( - e.code === 'Space' && - !( - document.activeElement?.tagName === 'INPUT' || - document.activeElement?.tagName === 'TEXTAREA' - ) - ) { - e.preventDefault() - setIsListening(true) - } - } - const handleKeyUp = (e: KeyboardEvent) => { - if ( - e.code === 'Space' && - !( - document.activeElement?.tagName === 'INPUT' || - document.activeElement?.tagName === 'TEXTAREA' - ) - ) { - setIsListening(false) - } - } - document.addEventListener('keydown', handleKeyDown) - document.addEventListener('keyup', handleKeyUp) - return () => { - document.removeEventListener('keydown', handleKeyDown) - document.removeEventListener('keyup', handleKeyUp) - } - }, []) + const handleDismissTask = () => { + setTaskStatus({ isRunning: false, currentTask: null, isPaused: false }); + setMessages([]); + }; return ( - - {/* Header */} -
-
-
-
- - - - -
-
-

- Browser.AI -

-
- - - {connected ? 'Online' : 'Offline'} - -
-
-
- -
- {/* Concentration Mode Toggle */} - - - {/* Voice Listening Toggle - Only in Concentration Mode */} - {concentrationMode && ( - - )} - - - -
-
-
- - {/* Main Content Area */} -
- {/* Active Task Banner / Sticky Header */} - {(taskStatus.is_running || taskStatus.current_task) && ( - l.level === 'ERROR') - ? 'failed' - : 'completed' - } - result={taskResult} - onClose={() => { - setTaskResult('') - // Optional: Clear task status here or just hide UI - }} - /> - )} - - {concentrationMode ? ( - /* Concentration Mode View */ -
- - - {/* Minimal controls for concentration mode */} - {taskStatus.is_running && ( -
- -
- )} -
- ) : ( - /* Standard View */ - <> - {!taskStatus.is_running && logs.length === 0 && ( -
-
- - - - -
-

- Ready to automate? -

-

- Type a task below or use voice to tell me what to do on this page. -

-
- )} - - {(taskStatus.is_running || logs.length > 0) && ( - - )} - - )} -
- - {/* Footer / Input */} - {/* Hide standard input in concentration mode if listening? No, user might want to type. */} - {/* But Concentration Mode usually implies Hands-Free. Let's keep it visible but minimal. */} - -
- -
- - {/* Voice Controls for Concentration Mode (if we hide standard input) */} - {concentrationMode && ( -
- -
- )} -
- ) -} - -export default SidePanel +
+ + +
+ ); +}; + +export default SidePanel; diff --git a/browser_ai_extension/browse_ai/src/sidepanel/components/ChatInput.tsx b/browser_ai_extension/browse_ai/src/sidepanel/components/ChatInput.tsx index c52ba99..7c7303c 100644 --- a/browser_ai_extension/browse_ai/src/sidepanel/components/ChatInput.tsx +++ b/browser_ai_extension/browse_ai/src/sidepanel/components/ChatInput.tsx @@ -12,10 +12,9 @@ interface ChatInputProps { isPaused?: boolean placeholder?: string enableVoice?: boolean // Optional voice input toggle - onListeningChange?: (isListening: boolean) => void // Prop to notify parent } -export const ChatInput = ({ onSendMessage, onStopTask, onPauseTask, onResumeTask, disabled = false, isRunning = false, isPaused = false, placeholder, enableVoice = true, onListeningChange }: ChatInputProps) => { +export const ChatInput = ({ onSendMessage, onStopTask, onPauseTask, onResumeTask, disabled = false, isRunning = false, isPaused = false, placeholder, enableVoice = true }: ChatInputProps) => { const [input, setInput] = useState('') const [isFocused, setIsFocused] = useState(false) const [isListening, setIsListening] = useState(false) @@ -23,13 +22,6 @@ export const ChatInput = ({ onSendMessage, onStopTask, onPauseTask, onResumeTask const [voiceError, setVoiceError] = useState(null) const textareaRef = useRef(null) - // Notify parent of listening state changes - useEffect(() => { - if (onListeningChange) { - onListeningChange(isListening) - } - }, [isListening, onListeningChange]) - // Hide scrollbar styles useEffect(() => { const styleElement = document.createElement('style') @@ -56,6 +48,7 @@ export const ChatInput = ({ onSendMessage, onStopTask, onPauseTask, onResumeTask useEffect(() => { if (enableVoice) { const isSupported = voiceRecognition.isRecognitionSupported() + console.log('🎤 Voice Recognition Support Check:', isSupported) if (isSupported) { voiceRecognition.initialize({ @@ -63,6 +56,9 @@ export const ChatInput = ({ onSendMessage, onStopTask, onPauseTask, onResumeTask interimResults: true, language: 'en-US' }) + console.log('🎤 Voice Recognition Initialized') + } else { + console.warn('🎤 Voice Recognition not available - button will be hidden') } } @@ -156,23 +152,17 @@ export const ChatInput = ({ onSendMessage, onStopTask, onPauseTask, onResumeTask }, [input]) return ( -
+