diff --git a/README.md b/README.md index d3b9d29..392ecfa 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,18 @@ You can test DevWhisper's conversation flow directly from your terminal — with --- +## ⚙️ Configurable Recording Timeout + +The maximum voice recording duration is configurable via the **Settings panel** (⚙️ icon, top-right). + +- Default: **30 seconds** +- Range: **5 – 120 seconds** +- The setting is saved in `localStorage` and persists across sessions. +- While recording, a **live countdown** is displayed on the mic button showing remaining seconds. +- Recording **stops automatically** when the timeout expires and submits whatever was captured. + +--- + ## ⚠️ Notes - The ngrok URL changes every time you restart it — remember to update the Server URL in your Vapi tool settings each time. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2a3ba24..2ec7f19 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,7 +16,7 @@ "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.3", - "oxlint": "^1.71.0", + "oxlint": "^1.76.0", "vite": "^8.1.1" } }, @@ -87,9 +87,9 @@ } }, "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.74.0.tgz", - "integrity": "sha512-+gHd12muVI9ZLBaWLPkHt3Fj7jihFjgQ1MGtBaRL8vWrWrI0P7dLUty/cHrHS0oqPYIRgQUJsPu2CExQuMcwNw==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.76.0.tgz", + "integrity": "sha512-ZHIE5Zt9AsPDcY4nOlofXt0YfneEeo+QrKMPcPzLf2Z6Q8VtV2W73d7SFJ920WUwyik783u/doKCs3KXdwG+7w==", "cpu": [ "arm" ], @@ -104,9 +104,9 @@ } }, "node_modules/@oxlint/binding-android-arm64": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.74.0.tgz", - "integrity": "sha512-xjKdoMB+H+RCOByv/7l7nfIGW9mlOisqYdcyC75UqYuQecLpReAeEYUf2CNeDEI3KtmUgxpRw/+c63y4AeF/Bw==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.76.0.tgz", + "integrity": "sha512-shm/ngQilHK6bs+ElJWa4oHfNj5vL1Gl/iVEJldTQjpr0/67oSgr0KUpbmcnLig5Fo0v/l6j2567A7TOL89ONA==", "cpu": [ "arm64" ], @@ -121,9 +121,9 @@ } }, "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.74.0.tgz", - "integrity": "sha512-iUK7wvc6sejMKsC+Pt67mntoF5weFcyEunhZfLJceU6gL419mexz5wBkSx/EnkFBExMLNtOi9fnDSc5xfK0IzQ==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.76.0.tgz", + "integrity": "sha512-rvJmrAPKSQ9aWJ6wIS6CK2tJjwzfW0ApQH9qokq6sfDvmHwoyIHxHFMq7z7i7GiV6fdE6s8qvBqWKPTu8RmT6Q==", "cpu": [ "arm64" ], @@ -138,9 +138,9 @@ } }, "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.74.0.tgz", - "integrity": "sha512-ggKc/tn5SJ1u2yG2izC6VKODfYKV8MQ2AicJlNzOjuyrC29udvOef6/JzK2r32xqCnBDLFouR1VCkjzEI0/N9Q==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.76.0.tgz", + "integrity": "sha512-U/zYdb7VYKGY6pA9Vd2rYl9O/HlCylcOlb5PGPvVLtg+oLGsk6H3XGKEMHKyqD3nmmtmlmwb/8SwU2vfSAtvMw==", "cpu": [ "x64" ], @@ -155,9 +155,9 @@ } }, "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.74.0.tgz", - "integrity": "sha512-u++dH/43jy9hTLbneaWlS0gla/Bp1JdwJ2zgevCl8nDFUh6qRCGMxcL0f0lb7By3A9p/LfFr+7cG4HU1hG856g==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.76.0.tgz", + "integrity": "sha512-WvKG9CAriuo0XNiFzpXjDngUZcRGFNpaK2kLyMUsnJlShxkT96u+BpJQ3KqdQwGOrvI14L6V8bAwXwAYNNY6Jg==", "cpu": [ "x64" ], @@ -172,9 +172,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.74.0.tgz", - "integrity": "sha512-Sj1zmtFDVTPeIbIz4ZfcXAbFHqCmKCXdCUlAJzvTF7I20NTH1RDpoF2PhkqNODutJzVhJYmm3oz0GwgY+tvE2g==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.76.0.tgz", + "integrity": "sha512-qJ5+RH99TqFRq3UCDxkW0zJJu9c+OAHFY72vGlxZLEpuO+MpKo3POgqb8sYipL9KYm8XY6ofb0HsOuvY6hQNqQ==", "cpu": [ "arm" ], @@ -189,9 +189,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.74.0.tgz", - "integrity": "sha512-//PKyQb/tQXcHArx2f7z+oVI/eMS2Jpv+edNuAtOrgIhWdGcpHxogveAxzmF2rpH1AIHp4Hq04RF/rgJdiICnQ==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.76.0.tgz", + "integrity": "sha512-PvPCVptkgVARsucgIqFQQcSmJ6xc6GtnVB5bRBekRahTc9eObMtjHfMjy5M+C2tHt5UCMttWM9RuSk/H9NqYeg==", "cpu": [ "arm" ], @@ -206,9 +206,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.74.0.tgz", - "integrity": "sha512-/k1Me+aX2tjuH10K62mLS0y8cLkJBHX6Ce0xPK+eWeel4bSdEGZ8dv4+hYMzg0GrSmjwy4yAYsDPeEeKBft/2w==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.76.0.tgz", + "integrity": "sha512-3KeFDx8Bu4HPAXbuHZOr/oHvN+QT+JQhMw/NYPz7Z071xLSsG27Jfh9PIQVEY7hk1I+jr43ExqRIeJ6VKk2yLw==", "cpu": [ "arm64" ], @@ -223,9 +223,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.74.0.tgz", - "integrity": "sha512-3tFSjBxc5D8/zvjEuLvOqcA8ZXKD0+6NuaVO/edeamNc49MoAsbfaC9s1UiwODwgF6slGaF8yJA2TPkukd77tg==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.76.0.tgz", + "integrity": "sha512-oPFkkKTgl0K/EIg9fQ8oA3IGcI05/Mq1en04iFa41mmNPT+6KEiByVazTOZZJiHMBBrbsns1YJ2e1Scqwzesjw==", "cpu": [ "arm64" ], @@ -240,9 +240,9 @@ } }, "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.74.0.tgz", - "integrity": "sha512-9QggtPkSPXOCTu8Szis7auOK/sC7KdQaN+/TujP7YVVhzCAOhgdRfgv8uEz0r2tk5xdgus5rLYUrCDoZNtiRUw==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.76.0.tgz", + "integrity": "sha512-gN7yZ0eqflA5Fhf1wvHxGUltIV3FsvmB1zhNMDEK9vSHhc7E6qg9CuPeBgPZab66Tjzq6w6kHAtNEvnTHf4cyw==", "cpu": [ "ppc64" ], @@ -257,9 +257,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.74.0.tgz", - "integrity": "sha512-VM5VPUJ4DJIWiK+AZn8FScUqMr6OFrCAYybMYjEEi7W13ParI64MByiXTkKMqZpBmvQ9zxl9Ebq2VUOiZRJYUg==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.76.0.tgz", + "integrity": "sha512-S/HqMbn22mQrjtErUxEoS/a55u8kIeXvreIxiJu5G7Le3UecEd6SQZxrDIpuhtgaFnsY/nVra3ytP+pRljDilA==", "cpu": [ "riscv64" ], @@ -274,9 +274,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.74.0.tgz", - "integrity": "sha512-SaDY1gh9rOA592J54g+gu5hkOFFQBZsMmIYHs+NRHG+Uq0OxtuuCXMWQ3vu1830Eugv5uMXyjG+bv2Z9y4IXjw==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.76.0.tgz", + "integrity": "sha512-ZIga3097VJZolGZk6SrIAUokIGfRkxRlhiHDUznZptGBfwrhD7pNfD1rzEzsCwvk/1DX0A1bLz+liuNh5QKIVQ==", "cpu": [ "riscv64" ], @@ -291,9 +291,9 @@ } }, "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.74.0.tgz", - "integrity": "sha512-ZATQeHZCyr6MbDveg0obD5sxLHFOghtOdC5jwVwYlvFWqtFOxctgFEG6Ef/64hYvZrWyhyCckB10AelqLopeDA==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.76.0.tgz", + "integrity": "sha512-ZGiiA7pFzMJSyMWYZTVlPgbTsx+Vl8ihLGMIujPwaslUF7kIPPWAbVmAlTc+9lWDV+DCiB8Ikixu+lSHeOIIWQ==", "cpu": [ "s390x" ], @@ -308,9 +308,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.74.0.tgz", - "integrity": "sha512-+aIvJyrdeD7LwCQ2WYLMUWNmnbeDRSPb40aBYtPjD9+PTqUwgJnk+HK5yLfSMeqXrMrDhE9uTmtt2y50tvjhHw==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.76.0.tgz", + "integrity": "sha512-JLiy5WuvEBFTT6ErIFV35SLzi0R7Iri6MKU6dZbTxfIx8pndbbPs3Mj780nMipBFcPkti+okAPOJ9POKkHFEgg==", "cpu": [ "x64" ], @@ -325,9 +325,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.74.0.tgz", - "integrity": "sha512-XyktaR8lhK2qWiCK0Tk8oYD+/cgn+oHA6ddRnxSSXUKkkojkV78CmShZUxQF+yrBFs0SuW+JBOPG6hecyc/iZg==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.76.0.tgz", + "integrity": "sha512-z7lgKQtbo/I1NIe8G5NHLesxJDv0tRSUWTpXKb9Pm3E9nKFKfO4IOSDtFroKgXtOYb0jQbcdH+0wzTyMXVes+A==", "cpu": [ "x64" ], @@ -342,9 +342,9 @@ } }, "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.74.0.tgz", - "integrity": "sha512-mzbjrPl4neaVUiJ1fUiEUxTGaSZBoiKtaoB6jmIpz9S+VOA2vDYmJpihQ82w6178V5jxziclTg8Cgj5yF6tTDg==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.76.0.tgz", + "integrity": "sha512-JOjKymIpb9QcYfEhZsN6h4V9Ivd474W38cNIBRv6bg2TbIvogbMTH0Mg6YWW9TiRDqfcX+/Hyfsbo5vcSE5guQ==", "cpu": [ "arm64" ], @@ -359,9 +359,9 @@ } }, "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.74.0.tgz", - "integrity": "sha512-vUAe9okpS2Oa5+lX67lqHMuNUvfkleRKwrUDJ/WJBsgmddvZ1mrsh2HVmuFDRzqFELhaJhFaCNOuR6a7L3rtIA==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.76.0.tgz", + "integrity": "sha512-pqDWZiwcmByWUEm1NFUBNiT6aentCcaoMWJv0HbXEmuYermJ4sg8ppVrshubYP2MZ6SHccJJcpr6x469PuDFIw==", "cpu": [ "arm64" ], @@ -376,9 +376,9 @@ } }, "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.74.0.tgz", - "integrity": "sha512-yyXXJyYYSXL4I8K8jAWjJs+J3fa9gH2JmEbo4f5adm+1tNC9itseicBNuwK7BDHvqQ5J534s+yDULu89vYL2ZQ==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.76.0.tgz", + "integrity": "sha512-Ba0O659kgMv6pwO3z9PdO+K3aMxQRaw9HnG+e6AtOfgwcKFvYilciQYBoUBmxfQvOCKZe1SwjMkuB542NkuDMQ==", "cpu": [ "ia32" ], @@ -393,9 +393,9 @@ } }, "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.74.0.tgz", - "integrity": "sha512-VTC9IYTIMrVUk/i6Ms1ohzzDKZFkWn0KU2OBbPBzgmVZ2V30165T/zK4LztTr0Xgp9fZ1qQZ1rsZAu/rEmySlA==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.76.0.tgz", + "integrity": "sha512-5qcirPHO8nKfkoowEVWtpAoVTcYDy6g0UT0NGic450Qv8J2NrOqg4uQ8QppRP4MDTC7Xx47lbZnmadTH03CGGA==", "cpu": [ "x64" ], @@ -1108,9 +1108,9 @@ } }, "node_modules/oxlint": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.74.0.tgz", - "integrity": "sha512-odGl2s2x5IOJoj3A0v1k0PGBXVFBZeZ2+AK/+K2MJur7Ghi3bkyX5NuLUWHKqa4js1wjep3hJeuTQJOlr+4+dA==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.76.0.tgz", + "integrity": "sha512-6QoFioEU4fNdiUx/2Eo6TRd6NG7H7njnRCz8rhB66cZmMHDTqcm1Rjvl8Wry+ZTQMBAmyb4Mlf62Mk5X+eHSOw==", "dev": true, "license": "MIT", "bin": { @@ -1123,28 +1123,28 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.74.0", - "@oxlint/binding-android-arm64": "1.74.0", - "@oxlint/binding-darwin-arm64": "1.74.0", - "@oxlint/binding-darwin-x64": "1.74.0", - "@oxlint/binding-freebsd-x64": "1.74.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.74.0", - "@oxlint/binding-linux-arm-musleabihf": "1.74.0", - "@oxlint/binding-linux-arm64-gnu": "1.74.0", - "@oxlint/binding-linux-arm64-musl": "1.74.0", - "@oxlint/binding-linux-ppc64-gnu": "1.74.0", - "@oxlint/binding-linux-riscv64-gnu": "1.74.0", - "@oxlint/binding-linux-riscv64-musl": "1.74.0", - "@oxlint/binding-linux-s390x-gnu": "1.74.0", - "@oxlint/binding-linux-x64-gnu": "1.74.0", - "@oxlint/binding-linux-x64-musl": "1.74.0", - "@oxlint/binding-openharmony-arm64": "1.74.0", - "@oxlint/binding-win32-arm64-msvc": "1.74.0", - "@oxlint/binding-win32-ia32-msvc": "1.74.0", - "@oxlint/binding-win32-x64-msvc": "1.74.0" + "@oxlint/binding-android-arm-eabi": "1.76.0", + "@oxlint/binding-android-arm64": "1.76.0", + "@oxlint/binding-darwin-arm64": "1.76.0", + "@oxlint/binding-darwin-x64": "1.76.0", + "@oxlint/binding-freebsd-x64": "1.76.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.76.0", + "@oxlint/binding-linux-arm-musleabihf": "1.76.0", + "@oxlint/binding-linux-arm64-gnu": "1.76.0", + "@oxlint/binding-linux-arm64-musl": "1.76.0", + "@oxlint/binding-linux-ppc64-gnu": "1.76.0", + "@oxlint/binding-linux-riscv64-gnu": "1.76.0", + "@oxlint/binding-linux-riscv64-musl": "1.76.0", + "@oxlint/binding-linux-s390x-gnu": "1.76.0", + "@oxlint/binding-linux-x64-gnu": "1.76.0", + "@oxlint/binding-linux-x64-musl": "1.76.0", + "@oxlint/binding-openharmony-arm64": "1.76.0", + "@oxlint/binding-win32-arm64-msvc": "1.76.0", + "@oxlint/binding-win32-ia32-msvc": "1.76.0", + "@oxlint/binding-win32-x64-msvc": "1.76.0" }, "peerDependencies": { - "oxlint-tsgolint": ">=0.24.0", + "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "peerDependenciesMeta": { diff --git a/frontend/package.json b/frontend/package.json index 44efe43..1f9345a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,7 @@ "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.3", - "oxlint": "^1.71.0", + "oxlint": "^1.76.0", "vite": "^8.1.1" } } diff --git a/frontend/src/App.css b/frontend/src/App.css index 4f827f2..6119c09 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -580,13 +580,50 @@ cursor: not-allowed; } -.reindex-banner { - background: rgba(245, 158, 11, 0.12); - border: 1px solid rgba(245, 158, 11, 0.4); - color: var(--text-h); - padding: 12px 16px; - border-radius: 10px; - font-size: 0.9rem; +/* --- Suggested Prompts --- */ +.suggested-prompts { + display: flex; + flex-direction: column; + gap: 10px; +} + +.suggested-prompts-label { + font-size: 0.85rem; + font-weight: 600; + color: var(--text); + opacity: 0.7; + margin: 0; +} + +.suggested-prompts-grid { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.prompt-chip { + background: var(--accent-bg); + border: 1px solid var(--accent-border); + border-radius: 20px; + padding: 7px 14px; + font-size: 0.85rem; + color: var(--accent); + cursor: pointer; font-weight: 500; + transition: all 0.2s ease; text-align: left; +} + +.prompt-chip:hover { + background: var(--accent); + color: #fff; + border-color: var(--accent); + transform: translateY(-1px); +} + +@media (max-width: 480px) { + .prompt-chip { + font-size: 0.8rem; + padding: 6px 12px; + } } \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a634a77..8268b07 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,7 +2,6 @@ import { useState, useEffect, useRef, useCallback } from 'react' import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom' import HistoryPanel from './components/HistoryPanel.jsx' import ResponseOutput from './components/ResponseOutput.jsx' -import ThemeToggle from './components/ThemeToggle.jsx' import MicButton from './components/MicButton.jsx' import './App.css' import SettingsPanel from './components/SettingsPanel.jsx' @@ -14,6 +13,7 @@ function Home() { const [error, setError] = useState(null) const [isListening, setIsListening] = useState(false) const [speechSupported, setSpeechSupported] = useState(false) + const [countdown, setCountdown] = useState(null) const [lastSubmittedQuery, setLastSubmittedQuery] = useState('') const [reindexRecommended, setReindexRecommended] = useState(false) @@ -22,6 +22,18 @@ function Home() { const abortControllerRef = useRef(null) const mockTimerRef = useRef(null) const latestTranscriptRef = useRef('') + const countdownIntervalRef = useRef(null) + + // Retrieve or generate a stable session ID so query history shows up in history panel + const [hasStarted, setHasStarted] = useState(false) + + const SUGGESTED_PROMPTS = [ + "What does the preprocess function do?", + "Where is the model saved after training?", + "How do I debug a KeyError in the pipeline?", + "What functions are defined in main.py?" + ] + const submitQueryTextRef = useRef(null) const redirectedRef = useRef(false) @@ -37,12 +49,39 @@ function Home() { return newId }) + const getRecordingTimeout = () => + parseInt(localStorage.getItem('devwhisper_recording_timeout') || '30', 10) + + const stopCountdown = useCallback(() => { + if (countdownIntervalRef.current) { + clearInterval(countdownIntervalRef.current) + countdownIntervalRef.current = null + } + setCountdown(null) + }, []) + + const startCountdown = useCallback((seconds, onExpire) => { + setCountdown(seconds) + let remaining = seconds + countdownIntervalRef.current = setInterval(() => { + remaining -= 1 + if (isMountedRef.current) setCountdown(remaining) + if (remaining <= 0) { + clearInterval(countdownIntervalRef.current) + countdownIntervalRef.current = null + onExpire() + } + }, 1000) + }, []) + + // Clear conversation handler const handleClearChat = async () => { if (loading) return setResponse('') setQueryText('') setError(null) + setHasStarted(false) const newId = 'web-' + Math.random().toString(36).substring(2, 9) sessionStorage.setItem('devwhisper_session_id', newId) @@ -122,6 +161,7 @@ function Home() { abortControllerRef.current = new AbortController() const signal = abortControllerRef.current.signal + setHasStarted(true) setLoading(true) setError(null) setResponse('') @@ -261,6 +301,7 @@ function Home() { rec.onerror = (err) => { console.error('Speech Recognition Error:', err) if (isMountedRef.current) { + stopCountdown() setIsListening(false) if (err.error === 'not-allowed' || err.error === 'service-not-allowed') { setError('Microphone permission denied. Please allow mic access in your browser.') @@ -276,6 +317,7 @@ function Home() { rec.onend = () => { if (isMountedRef.current) { + stopCountdown() setIsListening(false) const finalQuery = latestTranscriptRef.current.trim() if (finalQuery) { @@ -294,6 +336,8 @@ function Home() { recognitionRef.current.abort() } + stopCountdown() + if (mockTimerRef.current) { clearTimeout(mockTimerRef.current) mockTimerRef.current = null @@ -303,11 +347,12 @@ function Home() { abortControllerRef.current.abort() } } - }, []) + }, [submitQueryText, stopCountdown]) const handleMicClick = () => { if (speechSupported && recognitionRef.current) { if (isListening) { + stopCountdown() recognitionRef.current.stop() } else { setQueryText('') @@ -315,6 +360,9 @@ function Home() { setError(null) try { recognitionRef.current.start() + startCountdown(getRecordingTimeout(), () => { + if (recognitionRef.current) recognitionRef.current.stop() + }) } catch (err) { console.error('Failed to start speech recognition:', err) setError('Failed to activate microphone. Please try again.') @@ -324,6 +372,7 @@ function Home() { // Mock Fallback for browsers/environments without SpeechRecognition if (isListening) { setIsListening(false) + stopCountdown() if (mockTimerRef.current) { clearTimeout(mockTimerRef.current) mockTimerRef.current = null @@ -332,18 +381,26 @@ function Home() { setIsListening(true) setQueryText('Listening...') setError(null) - if (mockTimerRef.current) { - clearTimeout(mockTimerRef.current) - } + const timeout = getRecordingTimeout() + startCountdown(timeout, () => { + if (isMountedRef.current) { + setIsListening(false) + const sampleQuery = 'In main.py, what functions are found?' + setQueryText(sampleQuery) + submitQueryText(sampleQuery) + } + }) + if (mockTimerRef.current) clearTimeout(mockTimerRef.current) mockTimerRef.current = setTimeout(() => { if (isMountedRef.current) { setIsListening(false) + stopCountdown() const sampleQuery = 'In main.py, what functions are found?' setQueryText(sampleQuery) submitQueryText(sampleQuery) mockTimerRef.current = null } - }, 3000) + }, timeout * 1000) } } } @@ -385,6 +442,7 @@ function Home() { isListening={isListening} onClick={handleMicClick} disabled={loading} + countdown={countdown} />
Try asking:
+