From 3b0a612c9cbbbac42ca9cee51c06204951ad67d2 Mon Sep 17 00:00:00 2001 From: Simos Date: Fri, 4 Jul 2025 11:30:14 +0200 Subject: [PATCH] Update package dependencies, add Git API routes, and implement audio transcription functionality. Introduce new components for Git management, enhance chat interface with microphone support, and improve UI elements for better user experience. --- package-lock.json | 426 +++++++------- public/icons/claude-ai-icon.svg | 1 + server/index.js | 156 ++++++ server/routes/git.js | 388 +++++++++++++ src/App.jsx | 57 +- src/components/ChatInterface.jsx | 673 ++++++++++++++++++++-- src/components/ClaudeLogo.jsx | 11 + src/components/ClaudeStatus.jsx | 107 ++++ src/components/GitPanel.jsx | 777 ++++++++++++++++++++++++++ src/components/MainContent.jsx | 84 ++- src/components/MicButton.jsx | 217 +++++++ src/components/MobileNav.jsx | 9 +- src/components/QuickSettingsPanel.jsx | 167 ++++-- src/components/Shell.jsx | 16 +- src/components/Sidebar.jsx | 134 ++++- src/hooks/useAudioRecorder.js | 109 ++++ src/index.css | 485 ++++++++++++++++ src/utils/whisper.js | 38 ++ 18 files changed, 3495 insertions(+), 360 deletions(-) create mode 100755 public/icons/claude-ai-icon.svg create mode 100755 server/routes/git.js create mode 100755 src/components/ClaudeLogo.jsx create mode 100755 src/components/ClaudeStatus.jsx create mode 100755 src/components/GitPanel.jsx create mode 100755 src/components/MicButton.jsx create mode 100755 src/hooks/useAudioRecorder.js create mode 100755 src/utils/whisper.js diff --git a/package-lock.json b/package-lock.json index 703503e..171b3be 100755 --- a/package-lock.json +++ b/package-lock.json @@ -75,9 +75,9 @@ } }, "node_modules/@anthropic-ai/claude-code": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-1.0.24.tgz", - "integrity": "sha512-4S6ly2297ngNlto7IFZeEicS9u0yRDhocOzndWFovGBb+iUoEPKdZa/rhVk/tcyCADL6S+mMkiGQOlqFDrN3JQ==", + "version": "1.0.43", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-1.0.43.tgz", + "integrity": "sha512-VnuRK4s/R9ZRTkwH4gUjsp4SiBQXq7Y0B47OtgeXIZYVQYkhTW8m+E0IisFzXXFIyTQrE0SodGCpvgLhAYzGCg==", "hasInstallScript": true, "bin": { "claude": "cli.js" @@ -109,30 +109,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -148,15 +148,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", "dev": true, "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -179,6 +179,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", @@ -259,12 +268,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -326,27 +335,27 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", + "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -421,9 +430,9 @@ } }, "node_modules/@codemirror/lang-json": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", - "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" @@ -456,9 +465,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz", - "integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", + "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -497,9 +506,9 @@ } }, "node_modules/@codemirror/theme-one-dark": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz", - "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -508,9 +517,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.37.2", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.2.tgz", - "integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==", + "version": "6.38.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.0.tgz", + "integrity": "sha512-yvSchUwHOdupXkd7xJ0ob36jdsSR/I+/C+VbY0ffBiL5NiSTEBDfB1ZGWbbIlDd5xgdUkody+lukAdOxYrOBeg==", "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1298,16 +1307,12 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1318,23 +1323,15 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1475,15 +1472,15 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.11", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz", - "integrity": "sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==", + "version": "1.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", + "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", - "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", "cpu": [ "arm" ], @@ -1494,9 +1491,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", - "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", "cpu": [ "arm64" ], @@ -1507,9 +1504,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", - "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", + "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", "cpu": [ "arm64" ], @@ -1520,9 +1517,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", - "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", "cpu": [ "x64" ], @@ -1533,9 +1530,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", - "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", "cpu": [ "arm64" ], @@ -1546,9 +1543,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", - "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", "cpu": [ "x64" ], @@ -1559,9 +1556,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", - "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", "cpu": [ "arm" ], @@ -1572,9 +1569,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", - "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", "cpu": [ "arm" ], @@ -1585,9 +1582,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", - "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", + "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", "cpu": [ "arm64" ], @@ -1598,9 +1595,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", - "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", + "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", "cpu": [ "arm64" ], @@ -1611,9 +1608,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", - "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", "cpu": [ "loong64" ], @@ -1624,9 +1621,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", - "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", "cpu": [ "ppc64" ], @@ -1637,9 +1634,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", - "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", "cpu": [ "riscv64" ], @@ -1650,9 +1647,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", - "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", "cpu": [ "riscv64" ], @@ -1663,9 +1660,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", - "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", "cpu": [ "s390x" ], @@ -1676,9 +1673,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", - "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", "cpu": [ "x64" ], @@ -1689,9 +1686,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", - "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", "cpu": [ "x64" ], @@ -1702,9 +1699,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", - "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", "cpu": [ "arm64" ], @@ -1715,9 +1712,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", - "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", "cpu": [ "ia32" ], @@ -1728,9 +1725,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", - "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", "cpu": [ "x64" ], @@ -1754,18 +1751,6 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, - "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1816,9 +1801,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -1878,9 +1863,9 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, "node_modules/@uiw/codemirror-extensions-basic-setup": { - "version": "4.23.13", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.13.tgz", - "integrity": "sha512-U1CnDFpq6ydNqrRDS5Bdnvgso8ezwwbrmKvmAD3hmoVyRDsDU6HTtmcV+w0rZ3kElUCkKI5lY0DMvTTQ4+L3RQ==", + "version": "4.23.14", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.14.tgz", + "integrity": "sha512-lCseubZqjN9bFwHJdQlZEKEo2yO1tCiMMVL0gu3ZXwhqMdfnd6ky/fUCYbn8aJkW+cXKVwjEVhpKjOphNiHoNw==", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", @@ -1904,15 +1889,15 @@ } }, "node_modules/@uiw/react-codemirror": { - "version": "4.23.13", - "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.13.tgz", - "integrity": "sha512-y65ULzxOAfpxrA/8epoAOeCfmJXu9z0P62BbGOkITJTtU7WI59KfPbbwj35npSsMAkAmDE841qZo2I8jst/THg==", + "version": "4.23.14", + "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.14.tgz", + "integrity": "sha512-/CmlSh8LGUEZCxg/f78MEkEMehKnVklqJvJlL10AXXrO/2xOyPqHb8SK10GhwOqd0kHhHgVYp4+6oK5S+UIEuQ==", "dependencies": { "@babel/runtime": "^7.18.6", "@codemirror/commands": "^6.1.0", "@codemirror/state": "^6.1.1", "@codemirror/theme-one-dark": "^6.0.0", - "@uiw/codemirror-extensions-basic-setup": "4.23.13", + "@uiw/codemirror-extensions-basic-setup": "4.23.14", "codemirror": "^6.0.0" }, "funding": { @@ -1934,15 +1919,15 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" }, "node_modules/@vitejs/plugin-react": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.2.tgz", - "integrity": "sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz", + "integrity": "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==", "dev": true, "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.11", + "@rolldown/pluginutils": "1.0.0-beta.19", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, @@ -2179,9 +2164,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -2198,8 +2183,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -2254,9 +2239,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true, "funding": [ { @@ -2452,9 +2437,9 @@ } }, "node_modules/codemirror": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", - "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", @@ -2756,9 +2741,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.5.167", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz", - "integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==", + "version": "1.5.179", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", + "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", "dev": true }, "node_modules/emoji-regex": { @@ -3156,15 +3141,6 @@ "node": ">=10.13.0" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4442,9 +4418,9 @@ } }, "node_modules/postcss": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", - "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -4560,7 +4536,7 @@ "postcss": "^8.2.14" } }, - "node_modules/postcss-selector-parser": { + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", @@ -4572,6 +4548,18 @@ "node": ">=4" } }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -4830,12 +4818,12 @@ } }, "node_modules/rollup": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", - "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", + "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", "dev": true, "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -4845,26 +4833,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.43.0", - "@rollup/rollup-android-arm64": "4.43.0", - "@rollup/rollup-darwin-arm64": "4.43.0", - "@rollup/rollup-darwin-x64": "4.43.0", - "@rollup/rollup-freebsd-arm64": "4.43.0", - "@rollup/rollup-freebsd-x64": "4.43.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", - "@rollup/rollup-linux-arm-musleabihf": "4.43.0", - "@rollup/rollup-linux-arm64-gnu": "4.43.0", - "@rollup/rollup-linux-arm64-musl": "4.43.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", - "@rollup/rollup-linux-riscv64-gnu": "4.43.0", - "@rollup/rollup-linux-riscv64-musl": "4.43.0", - "@rollup/rollup-linux-s390x-gnu": "4.43.0", - "@rollup/rollup-linux-x64-gnu": "4.43.0", - "@rollup/rollup-linux-x64-musl": "4.43.0", - "@rollup/rollup-win32-arm64-msvc": "4.43.0", - "@rollup/rollup-win32-ia32-msvc": "4.43.0", - "@rollup/rollup-win32-x64-msvc": "4.43.0", + "@rollup/rollup-android-arm-eabi": "4.44.1", + "@rollup/rollup-android-arm64": "4.44.1", + "@rollup/rollup-darwin-arm64": "4.44.1", + "@rollup/rollup-darwin-x64": "4.44.1", + "@rollup/rollup-freebsd-arm64": "4.44.1", + "@rollup/rollup-freebsd-x64": "4.44.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", + "@rollup/rollup-linux-arm-musleabihf": "4.44.1", + "@rollup/rollup-linux-arm64-gnu": "4.44.1", + "@rollup/rollup-linux-arm64-musl": "4.44.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-musl": "4.44.1", + "@rollup/rollup-linux-s390x-gnu": "4.44.1", + "@rollup/rollup-linux-x64-gnu": "4.44.1", + "@rollup/rollup-linux-x64-musl": "4.44.1", + "@rollup/rollup-win32-arm64-msvc": "4.44.1", + "@rollup/rollup-win32-ia32-msvc": "4.44.1", + "@rollup/rollup-win32-x64-msvc": "4.44.1", "fsevents": "~2.3.2" } }, @@ -5663,6 +5651,18 @@ "node": ">= 6" } }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/tailwindcss/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -6107,9 +6107,9 @@ } }, "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "engines": { "node": ">=10.0.0" }, diff --git a/public/icons/claude-ai-icon.svg b/public/icons/claude-ai-icon.svg new file mode 100755 index 0000000..853a243 --- /dev/null +++ b/public/icons/claude-ai-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/index.js b/server/index.js index a3a308d..e87ad76 100755 --- a/server/index.js +++ b/server/index.js @@ -28,8 +28,11 @@ const fs = require('fs').promises; const { spawn } = require('child_process'); const os = require('os'); const pty = require('node-pty'); +const fetch = require('node-fetch'); + const { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually } = require('./projects'); const { spawnClaude, abortClaudeSession } = require('./claude-cli'); +const gitRoutes = require('./routes/git'); // File system watcher for projects folder let projectsWatcher = null; @@ -144,6 +147,9 @@ app.use(cors()); app.use(express.json()); app.use(express.static(path.join(__dirname, '../dist'))); +// Git API Routes +app.use('/api/git', gitRoutes); + // API Routes app.get('/api/config', (req, res) => { // Always use the server's actual IP and port for WebSocket connections @@ -651,6 +657,156 @@ function handleShellConnection(ws) { console.error('❌ Shell WebSocket error:', error); }); } +// Audio transcription endpoint +app.post('/api/transcribe', async (req, res) => { + try { + const multer = require('multer'); + const upload = multer({ storage: multer.memoryStorage() }); + + // Handle multipart form data + upload.single('audio')(req, res, async (err) => { + if (err) { + return res.status(400).json({ error: 'Failed to process audio file' }); + } + + if (!req.file) { + return res.status(400).json({ error: 'No audio file provided' }); + } + + const apiKey = process.env.OPENAI_API_KEY; + if (!apiKey) { + return res.status(500).json({ error: 'OpenAI API key not configured. Please set OPENAI_API_KEY in server environment.' }); + } + + try { + // Create form data for OpenAI + const FormData = require('form-data'); + const formData = new FormData(); + formData.append('file', req.file.buffer, { + filename: req.file.originalname, + contentType: req.file.mimetype + }); + formData.append('model', 'whisper-1'); + formData.append('response_format', 'json'); + formData.append('language', 'en'); + + // Make request to OpenAI + const fetch = require('node-fetch'); + const response = await fetch('https://api.openai.com/v1/audio/transcriptions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + ...formData.getHeaders() + }, + body: formData + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.error?.message || `Whisper API error: ${response.status}`); + } + + const data = await response.json(); + let transcribedText = data.text || ''; + + // Check if enhancement mode is enabled + const mode = req.body.mode || 'default'; + + // If no transcribed text, return empty + if (!transcribedText) { + return res.json({ text: '' }); + } + + // If default mode, return transcribed text without enhancement + if (mode === 'default') { + return res.json({ text: transcribedText }); + } + + // Handle different enhancement modes + try { + const OpenAI = require('openai'); + const openai = new OpenAI({ apiKey }); + + let prompt, systemMessage, temperature = 0.7, maxTokens = 800; + + switch (mode) { + case 'prompt': + systemMessage = 'You are an expert prompt engineer who creates clear, detailed, and effective prompts.'; + prompt = `You are an expert prompt engineer. Transform the following rough instruction into a clear, detailed, and context-aware AI prompt. + +Your enhanced prompt should: +1. Be specific and unambiguous +2. Include relevant context and constraints +3. Specify the desired output format +4. Use clear, actionable language +5. Include examples where helpful +6. Consider edge cases and potential ambiguities + +Transform this rough instruction into a well-crafted prompt: +"${transcribedText}" + +Enhanced prompt:`; + break; + + case 'vibe': + case 'instructions': + case 'architect': + systemMessage = 'You are a helpful assistant that formats ideas into clear, actionable instructions for AI agents.'; + temperature = 0.5; // Lower temperature for more controlled output + prompt = `Transform the following idea into clear, well-structured instructions that an AI agent can easily understand and execute. + +IMPORTANT RULES: +- Format as clear, step-by-step instructions +- Add reasonable implementation details based on common patterns +- Only include details directly related to what was asked +- Do NOT add features or functionality not mentioned +- Keep the original intent and scope intact +- Use clear, actionable language an agent can follow + +Transform this idea into agent-friendly instructions: +"${transcribedText}" + +Agent instructions:`; + break; + + default: + // No enhancement needed + break; + } + + // Only make GPT call if we have a prompt + if (prompt) { + const completion = await openai.chat.completions.create({ + model: 'gpt-4o-mini', + messages: [ + { role: 'system', content: systemMessage }, + { role: 'user', content: prompt } + ], + temperature: temperature, + max_tokens: maxTokens + }); + + transcribedText = completion.choices[0].message.content || transcribedText; + } + + } catch (gptError) { + console.error('GPT processing error:', gptError); + // Fall back to original transcription if GPT fails + } + + res.json({ text: transcribedText }); + + } catch (error) { + console.error('Transcription error:', error); + res.status(500).json({ error: error.message }); + } + }); + } catch (error) { + console.error('Endpoint error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + // Serve React app for all other routes app.get('*', (req, res) => { diff --git a/server/routes/git.js b/server/routes/git.js new file mode 100755 index 0000000..ebe9794 --- /dev/null +++ b/server/routes/git.js @@ -0,0 +1,388 @@ +const express = require('express'); +const { exec } = require('child_process'); +const { promisify } = require('util'); +const path = require('path'); +const fs = require('fs').promises; + +const router = express.Router(); +const execAsync = promisify(exec); + +// Helper function to get the actual project path from the encoded project name +function getActualProjectPath(projectName) { + // Claude stores projects with dashes instead of slashes + // Convert "-Users-dmieloch-Dev-experiments-claudecodeui" to "/Users/dmieloch/Dev/experiments/claudecodeui" + return projectName.replace(/-/g, '/'); +} + +// Get git status for a project +router.get('/status', async (req, res) => { + const { project } = req.query; + + if (!project) { + return res.status(400).json({ error: 'Project name is required' }); + } + + try { + const projectPath = getActualProjectPath(project); + console.log('Git status for project:', project, '-> path:', projectPath); + + // Check if directory exists + try { + await fs.access(projectPath); + } catch { + console.error('Project path not found:', projectPath); + return res.json({ error: 'Project not found' }); + } + + // Check if it's a git repository + try { + await execAsync('git rev-parse --git-dir', { cwd: projectPath }); + } catch { + console.error('Not a git repository:', projectPath); + return res.json({ error: 'Not a git repository' }); + } + + // Get current branch + const { stdout: branch } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: projectPath }); + + // Get git status + const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd: projectPath }); + + const modified = []; + const added = []; + const deleted = []; + const untracked = []; + + statusOutput.split('\n').forEach(line => { + if (!line.trim()) return; + + const status = line.substring(0, 2); + const file = line.substring(3); + + if (status === 'M ' || status === ' M' || status === 'MM') { + modified.push(file); + } else if (status === 'A ' || status === 'AM') { + added.push(file); + } else if (status === 'D ' || status === ' D') { + deleted.push(file); + } else if (status === '??') { + untracked.push(file); + } + }); + + res.json({ + branch: branch.trim(), + modified, + added, + deleted, + untracked + }); + } catch (error) { + console.error('Git status error:', error); + res.json({ error: error.message }); + } +}); + +// Get diff for a specific file +router.get('/diff', async (req, res) => { + const { project, file } = req.query; + + if (!project || !file) { + return res.status(400).json({ error: 'Project name and file path are required' }); + } + + try { + const projectPath = getActualProjectPath(project); + + // Check if file is untracked + const { stdout: statusOutput } = await execAsync(`git status --porcelain "${file}"`, { cwd: projectPath }); + const isUntracked = statusOutput.startsWith('??'); + + let diff; + if (isUntracked) { + // For untracked files, show the entire file content as additions + const fileContent = await fs.readFile(path.join(projectPath, file), 'utf-8'); + const lines = fileContent.split('\n'); + diff = `--- /dev/null\n+++ b/${file}\n@@ -0,0 +1,${lines.length} @@\n` + + lines.map(line => `+${line}`).join('\n'); + } else { + // Get diff for tracked files + const { stdout } = await execAsync(`git diff HEAD -- "${file}"`, { cwd: projectPath }); + diff = stdout || ''; + + // If no unstaged changes, check for staged changes + if (!diff) { + const { stdout: stagedDiff } = await execAsync(`git diff --cached -- "${file}"`, { cwd: projectPath }); + diff = stagedDiff; + } + } + + res.json({ diff }); + } catch (error) { + console.error('Git diff error:', error); + res.json({ error: error.message }); + } +}); + +// Commit changes +router.post('/commit', async (req, res) => { + const { project, message, files } = req.body; + + if (!project || !message || !files || files.length === 0) { + return res.status(400).json({ error: 'Project name, commit message, and files are required' }); + } + + try { + const projectPath = getActualProjectPath(project); + + // Stage selected files + for (const file of files) { + await execAsync(`git add "${file}"`, { cwd: projectPath }); + } + + // Commit with message + const { stdout } = await execAsync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd: projectPath }); + + res.json({ success: true, output: stdout }); + } catch (error) { + console.error('Git commit error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Get list of branches +router.get('/branches', async (req, res) => { + const { project } = req.query; + + if (!project) { + return res.status(400).json({ error: 'Project name is required' }); + } + + try { + const projectPath = getActualProjectPath(project); + console.log('Git branches for project:', project, '-> path:', projectPath); + + // Get all branches + const { stdout } = await execAsync('git branch -a', { cwd: projectPath }); + + // Parse branches + const branches = stdout + .split('\n') + .map(branch => branch.trim()) + .filter(branch => branch && !branch.includes('->')) // Remove empty lines and HEAD pointer + .map(branch => { + // Remove asterisk from current branch + if (branch.startsWith('* ')) { + return branch.substring(2); + } + // Remove remotes/ prefix + if (branch.startsWith('remotes/origin/')) { + return branch.substring(15); + } + return branch; + }) + .filter((branch, index, self) => self.indexOf(branch) === index); // Remove duplicates + + res.json({ branches }); + } catch (error) { + console.error('Git branches error:', error); + res.json({ error: error.message }); + } +}); + +// Checkout branch +router.post('/checkout', async (req, res) => { + const { project, branch } = req.body; + + if (!project || !branch) { + return res.status(400).json({ error: 'Project name and branch are required' }); + } + + try { + const projectPath = getActualProjectPath(project); + + // Checkout the branch + const { stdout } = await execAsync(`git checkout "${branch}"`, { cwd: projectPath }); + + res.json({ success: true, output: stdout }); + } catch (error) { + console.error('Git checkout error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Create new branch +router.post('/create-branch', async (req, res) => { + const { project, branch } = req.body; + + if (!project || !branch) { + return res.status(400).json({ error: 'Project name and branch name are required' }); + } + + try { + const projectPath = getActualProjectPath(project); + + // Create and checkout new branch + const { stdout } = await execAsync(`git checkout -b "${branch}"`, { cwd: projectPath }); + + res.json({ success: true, output: stdout }); + } catch (error) { + console.error('Git create branch error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Get recent commits +router.get('/commits', async (req, res) => { + const { project, limit = 10 } = req.query; + + if (!project) { + return res.status(400).json({ error: 'Project name is required' }); + } + + try { + const projectPath = getActualProjectPath(project); + + // Get commit log with stats + const { stdout } = await execAsync( + `git log --pretty=format:'%H|%an|%ae|%ad|%s' --date=relative -n ${limit}`, + { cwd: projectPath } + ); + + const commits = stdout + .split('\n') + .filter(line => line.trim()) + .map(line => { + const [hash, author, email, date, ...messageParts] = line.split('|'); + return { + hash, + author, + email, + date, + message: messageParts.join('|') + }; + }); + + // Get stats for each commit + for (const commit of commits) { + try { + const { stdout: stats } = await execAsync( + `git show --stat --format='' ${commit.hash}`, + { cwd: projectPath } + ); + commit.stats = stats.trim().split('\n').pop(); // Get the summary line + } catch (error) { + commit.stats = ''; + } + } + + res.json({ commits }); + } catch (error) { + console.error('Git commits error:', error); + res.json({ error: error.message }); + } +}); + +// Get diff for a specific commit +router.get('/commit-diff', async (req, res) => { + const { project, commit } = req.query; + + if (!project || !commit) { + return res.status(400).json({ error: 'Project name and commit hash are required' }); + } + + try { + const projectPath = getActualProjectPath(project); + + // Get diff for the commit + const { stdout } = await execAsync( + `git show ${commit}`, + { cwd: projectPath } + ); + + res.json({ diff: stdout }); + } catch (error) { + console.error('Git commit diff error:', error); + res.json({ error: error.message }); + } +}); + +// Generate commit message based on staged changes +router.post('/generate-commit-message', async (req, res) => { + const { project, files } = req.body; + + if (!project || !files || files.length === 0) { + return res.status(400).json({ error: 'Project name and files are required' }); + } + + try { + const projectPath = getActualProjectPath(project); + + // Get diff for selected files + let combinedDiff = ''; + for (const file of files) { + try { + const { stdout } = await execAsync( + `git diff HEAD -- "${file}"`, + { cwd: projectPath } + ); + if (stdout) { + combinedDiff += `\n--- ${file} ---\n${stdout}`; + } + } catch (error) { + console.error(`Error getting diff for ${file}:`, error); + } + } + + // Use AI to generate commit message (simple implementation) + // In a real implementation, you might want to use GPT or Claude API + const message = generateSimpleCommitMessage(files, combinedDiff); + + res.json({ message }); + } catch (error) { + console.error('Generate commit message error:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Simple commit message generator (can be replaced with AI) +function generateSimpleCommitMessage(files, diff) { + const fileCount = files.length; + const isMultipleFiles = fileCount > 1; + + // Analyze the diff to determine the type of change + const additions = (diff.match(/^\+[^+]/gm) || []).length; + const deletions = (diff.match(/^-[^-]/gm) || []).length; + + // Determine the primary action + let action = 'Update'; + if (additions > 0 && deletions === 0) { + action = 'Add'; + } else if (deletions > 0 && additions === 0) { + action = 'Remove'; + } else if (additions > deletions * 2) { + action = 'Enhance'; + } else if (deletions > additions * 2) { + action = 'Refactor'; + } + + // Generate message based on files + if (isMultipleFiles) { + const components = new Set(files.map(f => { + const parts = f.split('/'); + return parts[parts.length - 2] || parts[0]; + })); + + if (components.size === 1) { + return `${action} ${[...components][0]} component`; + } else { + return `${action} multiple components`; + } + } else { + const fileName = files[0].split('/').pop(); + const componentName = fileName.replace(/\.(jsx?|tsx?|css|scss)$/, ''); + return `${action} ${componentName}`; + } +} + +module.exports = router; \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index dcfac64..c515fe3 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -53,6 +53,10 @@ function AppContent() { const saved = localStorage.getItem('showRawParameters'); return saved !== null ? JSON.parse(saved) : false; }); + const [autoScrollToBottom, setAutoScrollToBottom] = useState(() => { + const saved = localStorage.getItem('autoScrollToBottom'); + return saved !== null ? JSON.parse(saved) : true; + }); // Session Protection System: Track sessions with active conversations to prevent // automatic project updates from interrupting ongoing chats. When a user sends // a message, the session is marked as "active" and project updates are paused @@ -217,13 +221,18 @@ function AppContent() { // Handle URL-based session loading useEffect(() => { if (sessionId && projects.length > 0) { + // Only switch tabs on initial load, not on every project update + const shouldSwitchTab = !selectedSession || selectedSession.id !== sessionId; // Find the session across all projects for (const project of projects) { const session = project.sessions?.find(s => s.id === sessionId); if (session) { setSelectedProject(project); setSelectedSession(session); - setActiveTab('chat'); + // Only switch to chat tab if we're loading a different session + if (shouldSwitchTab) { + setActiveTab('chat'); + } return; } } @@ -245,7 +254,11 @@ function AppContent() { const handleSessionSelect = (session) => { setSelectedSession(session); - setActiveTab('chat'); + // Only switch to chat tab when user explicitly selects a session + // This prevents tab switching during automatic updates + if (activeTab !== 'git' && activeTab !== 'preview') { + setActiveTab('chat'); + } if (isMobile) { setSidebarOpen(false); } @@ -482,23 +495,29 @@ function AppContent() { isInputFocused={isInputFocused} /> )} - - {/* Quick Settings Panel */} - { - setAutoExpandTools(value); - localStorage.setItem('autoExpandTools', JSON.stringify(value)); - }} - showRawParameters={showRawParameters} - onShowRawParametersChange={(value) => { - setShowRawParameters(value); - localStorage.setItem('showRawParameters', JSON.stringify(value)); - }} - isMobile={isMobile} - /> + {/* Quick Settings Panel - Only show on chat tab */} + {activeTab === 'chat' && ( + { + setAutoExpandTools(value); + localStorage.setItem('autoExpandTools', JSON.stringify(value)); + }} + showRawParameters={showRawParameters} + onShowRawParametersChange={(value) => { + setShowRawParameters(value); + localStorage.setItem('showRawParameters', JSON.stringify(value)); + }} + autoScrollToBottom={autoScrollToBottom} + onAutoScrollChange={(value) => { + setAutoScrollToBottom(value); + localStorage.setItem('autoScrollToBottom', JSON.stringify(value)); + }} + isMobile={isMobile} + /> + )} {/* Tools Settings Modal */} { const isGrouped = prevMessage && prevMessage.type === message.type && prevMessage.type === 'assistant' && !prevMessage.isToolUse && !message.isToolUse; - const messageRef = React.useRef(null); const [isExpanded, setIsExpanded] = React.useState(false); - React.useEffect(() => { if (!autoExpandTools || !messageRef.current || !message.isToolUse) return; @@ -89,8 +91,8 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile ! ) : ( -
- C +
+
)}
@@ -195,7 +197,8 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
))}
- {showRawParameters && ( + + {showRawParameters && (
View raw parameters @@ -205,7 +208,6 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
)} - ); @@ -225,6 +227,101 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile ); })()} {message.toolInput && message.toolName !== 'Edit' && (() => { + // Debug log to see what we're dealing with + console.log('Tool display - name:', message.toolName, 'input type:', typeof message.toolInput); + + // Special handling for Write tool + if (message.toolName === 'Write') { + console.log('Write tool detected, toolInput:', message.toolInput); + try { + let input; + // Handle both JSON string and already parsed object + if (typeof message.toolInput === 'string') { + input = JSON.parse(message.toolInput); + } else { + input = message.toolInput; + } + + console.log('Parsed Write input:', input); + + if (input.file_path && input.content !== undefined) { + return ( +
+ + + + + 📄 Creating new file: + + +
+
+
+ + + New File + +
+
+ {createDiff('', input.content).map((diffLine, i) => ( +
+ + {diffLine.type === 'removed' ? '-' : '+'} + + + {diffLine.content} + +
+ ))} +
+
+ {showRawParameters && ( +
+ + View raw parameters + +
+                                    {message.toolInput}
+                                  
+
+ )} +
+
+ ); + } + } catch (e) { + // Fall back to regular display + } + } + // Special handling for TodoWrite tool if (message.toolName === 'TodoWrite') { try { @@ -251,7 +348,100 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile )} - + + ); + } + } catch (e) { + // Fall back to regular display + } + } + + // Special handling for Bash tool + if (message.toolName === 'Bash') { + try { + const input = JSON.parse(message.toolInput); + return ( +
+ + + + + Running command + +
+
+
+ + + + Terminal +
+
+ $ {input.command} +
+
+ {input.description && ( +
+ {input.description} +
+ )} + {showRawParameters && ( +
+ + View raw parameters + +
+                                  {message.toolInput}
+                                
+
+ )} +
+
+ ); + } catch (e) { + // Fall back to regular display + } + } + + // Special handling for Read tool + if (message.toolName === 'Read') { + try { + const input = JSON.parse(message.toolInput); + if (input.file_path) { + // Extract filename + const filename = input.file_path.split('/').pop(); + const pathParts = input.file_path.split('/'); + const directoryPath = pathParts.slice(0, -1).join('/'); + + // Simple heuristic to show only relevant path parts + // Show the last 2-3 directory parts before the filename + const relevantParts = pathParts.slice(-4, -1); // Get up to 3 directories before filename + const relativePath = relevantParts.length > 0 ? relevantParts.join('/') + '/' : ''; + + return ( +
+ + + + + + + + {relativePath} + {filename} + + {showRawParameters && ( +
+
+ + View raw parameters + +
+                                    {message.toolInput}
+                                  
+
+
+ )}
); } @@ -345,6 +535,106 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile // Fall through to regular handling } } + + // Special handling for interactive prompts + if (content.includes('Do you want to proceed?') && message.toolName === 'Bash') { + const lines = content.split('\n'); + const promptIndex = lines.findIndex(line => line.includes('Do you want to proceed?')); + const beforePrompt = lines.slice(0, promptIndex).join('\n'); + const promptLines = lines.slice(promptIndex); + + // Extract the question and options + const questionLine = promptLines.find(line => line.includes('Do you want to proceed?')) || ''; + const options = []; + + // Parse numbered options (1. Yes, 2. No, etc.) + promptLines.forEach(line => { + const optionMatch = line.match(/^\s*(\d+)\.\s+(.+)$/); + if (optionMatch) { + options.push({ + number: optionMatch[1], + text: optionMatch[2].trim() + }); + } + }); + + // Find which option was selected (usually indicated by "> 1" or similar) + const selectedMatch = content.match(/>\s*(\d+)/); + const selectedOption = selectedMatch ? selectedMatch[1] : null; + + return ( +
+ {beforePrompt && ( +
+
{beforePrompt}
+
+ )} +
+
+
+ + + +
+
+

+ Interactive Prompt +

+

+ {questionLine} +

+ + {/* Option buttons */} +
+ {options.map((option) => ( + + ))} +
+ + {selectedOption && ( +
+

+ ✓ Claude selected option {selectedOption} +

+

+ In the CLI, you would select this option interactively using arrow keys or by typing the number. +

+
+ )} +
+
+
+
+ ); + } const fileEditMatch = content.match(/The file (.+?) has been updated\./); if (fileEditMatch) { @@ -363,6 +653,43 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile ); } + // Handle Write tool output for file creation + const fileCreateMatch = content.match(/(?:The file|File) (.+?) has been (?:created|written)(?: successfully)?\.?/); + if (fileCreateMatch) { + return ( +
+
+ File created successfully +
+ +
+ ); + } + + // Special handling for Write tool - hide content if it's just the file content + if (message.toolName === 'Write' && !message.toolResult.isError) { + // For Write tool, the diff is already shown in the tool input section + // So we just show a success message here + return ( +
+
+ + + + File written successfully +
+

+ The file content is displayed in the diff view above +

+
+ ); + } + if (content.includes('cat -n') && content.includes('→')) { return (
@@ -407,17 +734,100 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile )} + ) : message.isInteractivePrompt ? ( + // Special handling for interactive prompts +
+
+
+ + + +
+
+

+ Interactive Prompt +

+ {(() => { + const lines = message.content.split('\n').filter(line => line.trim()); + const questionLine = lines.find(line => line.includes('?')) || lines[0] || ''; + const options = []; + + // Parse the menu options + lines.forEach(line => { + // Match lines like "❯ 1. Yes" or " 2. No" + const optionMatch = line.match(/[❯\s]*(\d+)\.\s+(.+)/); + if (optionMatch) { + const isSelected = line.includes('❯'); + options.push({ + number: optionMatch[1], + text: optionMatch[2].trim(), + isSelected + }); + } + }); + + return ( + <> +

+ {questionLine} +

+ + {/* Option buttons */} +
+ {options.map((option) => ( + + ))} +
+ +
+

+ ⏳ Waiting for your response in the CLI +

+

+ Please select an option in your terminal where Claude is running. +

+
+ + ); + })()} +
+
+
) : (
{message.type === 'assistant' ? ( -
+
{ return inline ? ( - + {children} - + ) : (
@@ -454,7 +864,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
)} -
+
{new Date(message.timestamp).toLocaleTimeString()}
@@ -472,7 +882,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile // - onReplaceTemporarySession: Called to replace temporary session ID with real WebSocket session ID // // This ensures uninterrupted chat experience by pausing sidebar refreshes during conversations. -function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, messages, onFileOpen, onInputFocusChange, onSessionActive, onSessionInactive, onReplaceTemporarySession, onNavigateToSession, onShowSettings, autoExpandTools, showRawParameters }) { +function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, messages, onFileOpen, onInputFocusChange, onSessionActive, onSessionInactive, onReplaceTemporarySession, onNavigateToSession, onShowSettings, autoExpandTools, showRawParameters, autoScrollToBottom }) { const [input, setInput] = useState(() => { if (typeof window !== 'undefined' && selectedProject) { return localStorage.getItem(`draft_input_${selectedProject.name}`) || ''; @@ -504,6 +914,14 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess const [atSymbolPosition, setAtSymbolPosition] = useState(-1); const [canAbortSession, setCanAbortSession] = useState(false); const [isUserScrolledUp, setIsUserScrolledUp] = useState(false); + const scrollPositionRef = useRef({ height: 0, top: 0 }); + const [showCommandMenu, setShowCommandMenu] = useState(false); + const [slashCommands, setSlashCommands] = useState([]); + const [filteredCommands, setFilteredCommands] = useState([]); + const [isTextareaExpanded, setIsTextareaExpanded] = useState(false); + const [selectedCommandIndex, setSelectedCommandIndex] = useState(-1); + const [slashPosition, setSlashPosition] = useState(-1); + const [claudeStatus, setClaudeStatus] = useState(null); // Memoized diff calculation to prevent recalculating on every render @@ -718,8 +1136,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess const messages = await loadSessionMessages(selectedProject.name, selectedSession.id); setSessionMessages(messages); // convertedMessages will be automatically updated via useMemo - // Scroll to bottom after loading session messages - setTimeout(() => scrollToBottom(), 200); + // Scroll to bottom after loading session messages if auto-scroll is enabled + if (autoScrollToBottom) { + setTimeout(() => scrollToBottom(), 200); + } } else { // Reset the flag after handling system session change setIsSystemSessionChange(false); @@ -920,7 +1340,16 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess timestamp: new Date() }]); break; - + case 'claude-interactive-prompt': + // Handle interactive prompts from CLI + setChatMessages(prev => [...prev, { + type: 'assistant', + content: latestMessage.data, + timestamp: new Date(), + isInteractivePrompt: true + }]); + break; + case 'claude-error': setChatMessages(prev => [...prev, { type: 'error', @@ -932,6 +1361,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess case 'claude-complete': setIsLoading(false); setCanAbortSession(false); + setClaudeStatus(null); + // Session Protection: Mark session as inactive to re-enable automatic project updates // Conversation is complete, safe to allow project updates again @@ -952,6 +1383,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess case 'session-aborted': setIsLoading(false); setCanAbortSession(false); + setClaudeStatus(null); // Session Protection: Mark session as inactive when aborted // User or system aborted the conversation, re-enable project updates @@ -960,13 +1392,52 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess } setChatMessages(prev => [...prev, { - type: 'error', - content: latestMessage.success ? - 'Session aborted successfully' : - 'Failed to abort session - it may have already completed', + type: 'assistant', + content: 'Session interrupted by user.', timestamp: new Date() }]); break; + + case 'claude-status': + // Handle Claude working status messages + console.log('🔔 Received claude-status message:', latestMessage); + const statusData = latestMessage.data; + if (statusData) { + // Parse the status message to extract relevant information + let statusInfo = { + text: 'Working...', + tokens: 0, + can_interrupt: true + }; + + // Check for different status message formats + if (statusData.message) { + statusInfo.text = statusData.message; + } else if (statusData.status) { + statusInfo.text = statusData.status; + } else if (typeof statusData === 'string') { + statusInfo.text = statusData; + } + + // Extract token count + if (statusData.tokens) { + statusInfo.tokens = statusData.tokens; + } else if (statusData.token_count) { + statusInfo.tokens = statusData.token_count; + } + + // Check if can interrupt + if (statusData.can_interrupt !== undefined) { + statusInfo.can_interrupt = statusData.can_interrupt; + } + + console.log('📊 Setting claude status:', statusInfo); + setClaudeStatus(statusInfo); + setIsLoading(true); + setCanAbortSession(statusInfo.can_interrupt); + } + break; + } } }, [messages]); @@ -1057,21 +1528,48 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess return chatMessages.slice(-maxMessages); }, [chatMessages]); + // Capture scroll position before render when auto-scroll is disabled useEffect(() => { - // Only auto-scroll to bottom when new messages arrive if user hasn't scrolled up + if (!autoScrollToBottom && scrollContainerRef.current) { + const container = scrollContainerRef.current; + scrollPositionRef.current = { + height: container.scrollHeight, + top: container.scrollTop + }; + } + }); + + useEffect(() => { + // Only auto-scroll to bottom when new messages arrive if: + // 1. Auto-scroll is enabled in settings + // 2. User hasn't manually scrolled up if (scrollContainerRef.current && chatMessages.length > 0) { - if (!isUserScrolledUp) { - setTimeout(() => scrollToBottom(), 0); + if (autoScrollToBottom) { + if (!isUserScrolledUp) { + setTimeout(() => scrollToBottom(), 0); + } + } else { + // When auto-scroll is disabled, preserve the visual position + const container = scrollContainerRef.current; + const prevHeight = scrollPositionRef.current.height; + const prevTop = scrollPositionRef.current.top; + const newHeight = container.scrollHeight; + const heightDiff = newHeight - prevHeight; + + // If content was added above the current view, adjust scroll position + if (heightDiff > 0 && prevTop > 0) { + container.scrollTop = prevTop + heightDiff; + } } } - }, [chatMessages.length, isUserScrolledUp, scrollToBottom]); + }, [chatMessages.length, isUserScrolledUp, scrollToBottom, autoScrollToBottom]); // Scroll to bottom when component mounts with existing messages useEffect(() => { - if (scrollContainerRef.current && chatMessages.length > 0) { + if (scrollContainerRef.current && chatMessages.length > 0 && autoScrollToBottom) { setTimeout(() => scrollToBottom(), 100); // Small delay to ensure rendering } - }, [scrollToBottom]); + }, [scrollToBottom, autoScrollToBottom]); // Add scroll event listener to detect user scrolling useEffect(() => { @@ -1087,9 +1585,36 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess if (textareaRef.current) { textareaRef.current.style.height = 'auto'; textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'; + + // Check if initially expanded + const lineHeight = parseInt(window.getComputedStyle(textareaRef.current).lineHeight); + const isExpanded = textareaRef.current.scrollHeight > lineHeight * 2; + setIsTextareaExpanded(isExpanded); } }, []); // Only run once on mount + const handleTranscript = useCallback((text) => { + if (text.trim()) { + setInput(prevInput => { + const newInput = prevInput.trim() ? `${prevInput} ${text}` : text; + + // Update textarea height after setting new content + setTimeout(() => { + if (textareaRef.current) { + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'; + + // Check if expanded after transcript + const lineHeight = parseInt(window.getComputedStyle(textareaRef.current).lineHeight); + const isExpanded = textareaRef.current.scrollHeight > lineHeight * 2; + setIsTextareaExpanded(isExpanded); + } + }, 0); + + return newInput; + }); + } + }, []); const handleSubmit = (e) => { e.preventDefault(); @@ -1104,6 +1629,12 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess setChatMessages(prev => [...prev, userMessage]); setIsLoading(true); setCanAbortSession(true); + // Set a default status when starting + setClaudeStatus({ + text: 'Processing', + tokens: 0, + can_interrupt: true + }); // Always scroll to bottom when user sends a message (they're actively participating) setTimeout(() => scrollToBottom(), 0); @@ -1151,6 +1682,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess }); setInput(''); + setIsTextareaExpanded(false); // Clear the saved draft since message was sent if (selectedProject) { localStorage.removeItem(`draft_input_${selectedProject.name}`); @@ -1245,7 +1777,14 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess setCanAbortSession(false); }; - // Abort functionality is not yet implemented at the backend + const handleAbortSession = () => { + if (currentSessionId && canAbortSession) { + sendMessage({ + type: 'abort-session', + sessionId: currentSessionId + }); + } + }; // Don't render if no project is selected if (!selectedProject) { @@ -1281,11 +1820,13 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
) : chatMessages.length === 0 ? ( -
-

Start a conversation with Claude

-

- Ask questions about your code, request changes, or get help with development tasks -

+
+
+

Start a conversation with Claude

+

+ Ask questions about your code, request changes, or get help with development tasks +

+
) : ( <> @@ -1312,7 +1853,6 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess onShowSettings={onShowSettings} autoExpandTools={autoExpandTools} showRawParameters={showRawParameters} - /> ); })} @@ -1347,7 +1887,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess {isUserScrolledUp && chatMessages.length > 0 && (