diff options
| author | soryu <soryu@soryu.co> | 2025-12-23 14:43:23 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2025-12-23 14:47:18 +0000 |
| commit | 555061b179b8ec034cb70f9a2dd6c823ced0f637 (patch) | |
| tree | 0545b4395dab6d957884d8d36bf15b8da529dc1f /makima/frontend | |
| parent | a32dc56d2e5447ef8988cb98b8686476cc94e70c (diff) | |
| download | soryu-555061b179b8ec034cb70f9a2dd6c823ced0f637.tar.gz soryu-555061b179b8ec034cb70f9a2dd6c823ced0f637.zip | |
Add file body and initial tool call system
Diffstat (limited to 'makima/frontend')
| -rw-r--r-- | makima/frontend/package-lock.json | 366 | ||||
| -rw-r--r-- | makima/frontend/package.json | 3 | ||||
| -rw-r--r-- | makima/frontend/src/components/charts/ChartRenderer.tsx | 181 | ||||
| -rw-r--r-- | makima/frontend/src/components/files/BodyRenderer.tsx | 125 | ||||
| -rw-r--r-- | makima/frontend/src/components/files/CliInput.tsx | 168 | ||||
| -rw-r--r-- | makima/frontend/src/components/files/FileDetail.tsx | 91 | ||||
| -rw-r--r-- | makima/frontend/src/lib/api.ts | 57 | ||||
| -rw-r--r-- | makima/frontend/src/routes/files.tsx | 41 | ||||
| -rw-r--r-- | makima/frontend/tsconfig.tsbuildinfo | 2 |
9 files changed, 999 insertions, 35 deletions
diff --git a/makima/frontend/package-lock.json b/makima/frontend/package-lock.json index 1b793bb..e305d2a 100644 --- a/makima/frontend/package-lock.json +++ b/makima/frontend/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router": "^7.1.0" + "react-router": "^7.1.0", + "recharts": "^3.6.0" }, "devDependencies": { "@react-router/dev": "^7.1.0", @@ -1063,6 +1064,40 @@ } } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.0.tgz", + "integrity": "sha512-dlzb07f5LDY+tzs+iLCSXV2yuhaYfezqyZQc+n6baLECWkOMEWxkECAOnXL0ba7lsA25fM9b2jtzpu/uxo1a7g==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@remix-run/node-fetch-server": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@remix-run/node-fetch-server/-/node-fetch-server-0.9.0.tgz", @@ -1361,6 +1396,16 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", @@ -1659,6 +1704,60 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1669,7 +1768,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", - "dev": true, + "devOptional": true, "dependencies": { "csstype": "^3.2.2" } @@ -1683,6 +1782,11 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1831,6 +1935,14 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", @@ -1859,7 +1971,117 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true + "devOptional": true + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } }, "node_modules/debug": { "version": "4.4.3", @@ -1878,6 +2100,11 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/dedent": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", @@ -1926,6 +2153,11 @@ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true }, + "node_modules/es-toolkit": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz", + "integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==" + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -1976,6 +2208,11 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, "node_modules/exit-hook": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", @@ -2040,6 +2277,23 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/isbot": { "version": "5.1.32", "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.32.tgz", @@ -2521,6 +2775,34 @@ "react": "^19.2.3" } }, + "node_modules/react-is": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", + "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -2564,6 +2846,50 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/recharts": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz", + "integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==", + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "node_modules/rollup": { "version": "4.54.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", @@ -2655,6 +2981,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -2714,6 +3045,14 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/valibot": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", @@ -2728,6 +3067,27 @@ } } }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", diff --git a/makima/frontend/package.json b/makima/frontend/package.json index 22c4c54..ef99c3d 100644 --- a/makima/frontend/package.json +++ b/makima/frontend/package.json @@ -13,7 +13,8 @@ "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router": "^7.1.0" + "react-router": "^7.1.0", + "recharts": "^3.6.0" }, "devDependencies": { "@react-router/dev": "^7.1.0", diff --git a/makima/frontend/src/components/charts/ChartRenderer.tsx b/makima/frontend/src/components/charts/ChartRenderer.tsx new file mode 100644 index 0000000..276b170 --- /dev/null +++ b/makima/frontend/src/components/charts/ChartRenderer.tsx @@ -0,0 +1,181 @@ +import { useMemo } from "react"; +import { + LineChart, + Line, + BarChart, + Bar, + PieChart, + Pie, + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + Cell, +} from "recharts"; +import type { ChartType } from "../../lib/api"; + +interface ChartRendererProps { + chartType: ChartType; + data: Record<string, unknown>[]; + title?: string; + config?: Record<string, unknown>; +} + +// Default color palette +const COLORS = [ + "#9bc3ff", + "#ff9b9b", + "#9bffb3", + "#ffeb9b", + "#d49bff", + "#9bfff0", + "#ff9beb", + "#b3ff9b", +]; + +export function ChartRenderer({ + chartType, + data, + title, + config, +}: ChartRendererProps) { + // Extract data keys (excluding 'name' which is used for labels) + const dataKeys = useMemo(() => { + if (data.length === 0) return []; + const keys = Object.keys(data[0]).filter((key) => key !== "name"); + return keys; + }, [data]); + + // Get colors from config or use defaults + const colors = (config?.colors as string[]) || COLORS; + + const renderChart = () => { + switch (chartType) { + case "line": + return ( + <LineChart data={data}> + <CartesianGrid strokeDasharray="3 3" stroke="#333" /> + <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} /> + <YAxis stroke="#9bc3ff" fontSize={12} /> + <Tooltip + contentStyle={{ + backgroundColor: "#111", + border: "1px solid #9bc3ff", + borderRadius: "0", + }} + /> + <Legend /> + {dataKeys.map((key, i) => ( + <Line + key={key} + type="monotone" + dataKey={key} + stroke={colors[i % colors.length]} + strokeWidth={2} + dot={{ fill: colors[i % colors.length] }} + /> + ))} + </LineChart> + ); + + case "bar": + return ( + <BarChart data={data}> + <CartesianGrid strokeDasharray="3 3" stroke="#333" /> + <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} /> + <YAxis stroke="#9bc3ff" fontSize={12} /> + <Tooltip + contentStyle={{ + backgroundColor: "#111", + border: "1px solid #9bc3ff", + borderRadius: "0", + }} + /> + <Legend /> + {dataKeys.map((key, i) => ( + <Bar key={key} dataKey={key} fill={colors[i % colors.length]} /> + ))} + </BarChart> + ); + + case "area": + return ( + <AreaChart data={data}> + <CartesianGrid strokeDasharray="3 3" stroke="#333" /> + <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} /> + <YAxis stroke="#9bc3ff" fontSize={12} /> + <Tooltip + contentStyle={{ + backgroundColor: "#111", + border: "1px solid #9bc3ff", + borderRadius: "0", + }} + /> + <Legend /> + {dataKeys.map((key, i) => ( + <Area + key={key} + type="monotone" + dataKey={key} + stroke={colors[i % colors.length]} + fill={colors[i % colors.length]} + fillOpacity={0.3} + /> + ))} + </AreaChart> + ); + + case "pie": + // For pie charts, use the first data key as value + const valueKey = dataKeys[0] || "value"; + return ( + <PieChart> + <Pie + data={data} + dataKey={valueKey} + nameKey="name" + cx="50%" + cy="50%" + outerRadius={80} + label={({ name, percent }) => + `${name}: ${((percent ?? 0) * 100).toFixed(0)}%` + } + labelLine={{ stroke: "#9bc3ff" }} + > + {data.map((_, i) => ( + <Cell key={i} fill={colors[i % colors.length]} /> + ))} + </Pie> + <Tooltip + contentStyle={{ + backgroundColor: "#111", + border: "1px solid #9bc3ff", + borderRadius: "0", + }} + /> + <Legend /> + </PieChart> + ); + + default: + return <div className="text-red-400">Unknown chart type: {chartType}</div>; + } + }; + + return ( + <div className="w-full"> + {title && ( + <h4 className="text-[#9bc3ff] font-mono text-sm mb-2">{title}</h4> + )} + <div className="h-64 w-full"> + <ResponsiveContainer width="100%" height="100%"> + {renderChart()} + </ResponsiveContainer> + </div> + </div> + ); +} diff --git a/makima/frontend/src/components/files/BodyRenderer.tsx b/makima/frontend/src/components/files/BodyRenderer.tsx new file mode 100644 index 0000000..9d008e2 --- /dev/null +++ b/makima/frontend/src/components/files/BodyRenderer.tsx @@ -0,0 +1,125 @@ +import type { BodyElement } from "../../lib/api"; +import { ChartRenderer } from "../charts/ChartRenderer"; + +interface BodyRendererProps { + elements: BodyElement[]; +} + +export function BodyRenderer({ elements }: BodyRendererProps) { + if (elements.length === 0) { + return ( + <div className="text-[#555] font-mono text-sm italic"> + No content yet. Use the CLI below to add content. + </div> + ); + } + + return ( + <div className="space-y-4"> + {elements.map((element, index) => ( + <BodyElementRenderer key={index} element={element} /> + ))} + </div> + ); +} + +function BodyElementRenderer({ element }: { element: BodyElement }) { + switch (element.type) { + case "heading": + return <HeadingElement level={element.level} text={element.text} />; + case "paragraph": + return <ParagraphElement text={element.text} />; + case "chart": + return ( + <ChartElement + chartType={element.chartType} + data={element.data} + title={element.title} + config={element.config} + /> + ); + case "image": + return ( + <ImageElement + src={element.src} + alt={element.alt} + caption={element.caption} + /> + ); + default: + return null; + } +} + +function HeadingElement({ level, text }: { level: number; text: string }) { + const className = "font-mono text-[#9bc3ff]"; + + switch (level) { + case 1: + return <h1 className={`${className} text-2xl font-bold`}>{text}</h1>; + case 2: + return <h2 className={`${className} text-xl font-bold`}>{text}</h2>; + case 3: + return <h3 className={`${className} text-lg font-semibold`}>{text}</h3>; + case 4: + return <h4 className={`${className} text-base font-semibold`}>{text}</h4>; + case 5: + return <h5 className={`${className} text-sm font-semibold`}>{text}</h5>; + case 6: + return <h6 className={`${className} text-xs font-semibold`}>{text}</h6>; + default: + return <h3 className={`${className} text-lg font-semibold`}>{text}</h3>; + } +} + +function ParagraphElement({ text }: { text: string }) { + return <p className="font-mono text-sm text-white/80 leading-relaxed">{text}</p>; +} + +function ChartElement({ + chartType, + data, + title, + config, +}: { + chartType: "line" | "bar" | "pie" | "area"; + data: Record<string, unknown>[]; + title?: string; + config?: Record<string, unknown>; +}) { + return ( + <div className="border border-[#333] p-4 bg-black/30"> + <ChartRenderer + chartType={chartType} + data={data} + title={title} + config={config} + /> + </div> + ); +} + +function ImageElement({ + src, + alt, + caption, +}: { + src: string; + alt?: string; + caption?: string; +}) { + return ( + <figure className="space-y-2"> + <img + src={src} + alt={alt || ""} + className="max-w-full border border-[#333]" + /> + {caption && ( + <figcaption className="text-[#555] font-mono text-xs italic"> + {caption} + </figcaption> + )} + </figure> + ); +} diff --git a/makima/frontend/src/components/files/CliInput.tsx b/makima/frontend/src/components/files/CliInput.tsx new file mode 100644 index 0000000..b20eb27 --- /dev/null +++ b/makima/frontend/src/components/files/CliInput.tsx @@ -0,0 +1,168 @@ +import { useState, useCallback, useRef, useEffect } from "react"; +import { chatWithFile, type BodyElement } from "../../lib/api"; + +interface CliInputProps { + fileId: string; + onUpdate: (body: BodyElement[], summary: string | null) => void; +} + +interface Message { + id: string; + type: "user" | "assistant" | "error"; + content: string; + toolCalls?: { name: string; success: boolean; message: string }[]; +} + +export function CliInput({ fileId, onUpdate }: CliInputProps) { + const [input, setInput] = useState(""); + const [loading, setLoading] = useState(false); + const [messages, setMessages] = useState<Message[]>([]); + const [expanded, setExpanded] = useState(false); + const inputRef = useRef<HTMLInputElement>(null); + const messagesRef = useRef<HTMLDivElement>(null); + + // Auto-scroll to bottom when messages change + useEffect(() => { + if (messagesRef.current) { + messagesRef.current.scrollTop = messagesRef.current.scrollHeight; + } + }, [messages]); + + const handleSubmit = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim() || loading) return; + + const userMessage = input.trim(); + setInput(""); + setExpanded(true); + + // Add user message + const userMsgId = Date.now().toString(); + setMessages((prev) => [ + ...prev, + { id: userMsgId, type: "user", content: userMessage }, + ]); + + setLoading(true); + + try { + const response = await chatWithFile(fileId, userMessage); + + // Add assistant response + const assistantMsgId = (Date.now() + 1).toString(); + setMessages((prev) => [ + ...prev, + { + id: assistantMsgId, + type: "assistant", + content: response.response, + toolCalls: response.toolCalls.map((tc) => ({ + name: tc.name, + success: tc.result.success, + message: tc.result.message, + })), + }, + ]); + + // Update parent with new body/summary + onUpdate(response.updatedBody, response.updatedSummary); + } catch (err) { + const errorMsgId = (Date.now() + 1).toString(); + setMessages((prev) => [ + ...prev, + { + id: errorMsgId, + type: "error", + content: err instanceof Error ? err.message : "An error occurred", + }, + ]); + } finally { + setLoading(false); + inputRef.current?.focus(); + } + }, + [input, loading, fileId, onUpdate] + ); + + const clearMessages = useCallback(() => { + setMessages([]); + }, []); + + return ( + <div className="border-t border-[rgba(117,170,252,0.35)] bg-[#0d1b2d]"> + {/* Messages Panel (expandable) */} + {expanded && messages.length > 0 && ( + <div + ref={messagesRef} + className="max-h-48 overflow-y-auto p-3 space-y-2 border-b border-[rgba(117,170,252,0.2)]" + > + {messages.map((msg) => ( + <div key={msg.id} className="font-mono text-xs"> + {msg.type === "user" && ( + <div className="flex gap-2"> + <span className="text-[#9bc3ff]">></span> + <span className="text-white/80">{msg.content}</span> + </div> + )} + {msg.type === "assistant" && ( + <div className="pl-4 space-y-1"> + <div className="text-[#75aafc]">{msg.content}</div> + {msg.toolCalls && msg.toolCalls.length > 0 && ( + <div className="text-[#555] text-[10px] space-y-0.5"> + {msg.toolCalls.map((tc, i) => ( + <div key={i}> + <span + className={ + tc.success ? "text-green-500" : "text-red-400" + } + > + {tc.success ? "+" : "x"} + </span>{" "} + {tc.name}: {tc.message} + </div> + ))} + </div> + )} + </div> + )} + {msg.type === "error" && ( + <div className="pl-4 text-red-400">{msg.content}</div> + )} + </div> + ))} + </div> + )} + + {/* Input Bar */} + <form onSubmit={handleSubmit} className="flex items-center gap-2 p-3"> + <span className="text-[#9bc3ff] font-mono text-sm">$</span> + <input + ref={inputRef} + type="text" + value={input} + onChange={(e) => setInput(e.target.value)} + placeholder={loading ? "Processing..." : "Add a heading, chart, or summary..."} + disabled={loading} + className="flex-1 bg-transparent border-none outline-none font-mono text-sm text-white placeholder-[#555]" + /> + {messages.length > 0 && ( + <button + type="button" + onClick={clearMessages} + className="text-[#555] hover:text-[#9bc3ff] font-mono text-xs transition-colors" + > + clear + </button> + )} + <button + type="submit" + disabled={loading || !input.trim()} + className="px-3 py-1 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] disabled:opacity-50 disabled:cursor-not-allowed transition-colors uppercase" + > + {loading ? "..." : "Send"} + </button> + </form> + </div> + ); +} diff --git a/makima/frontend/src/components/files/FileDetail.tsx b/makima/frontend/src/components/files/FileDetail.tsx index 643f35e..ffc67dd 100644 --- a/makima/frontend/src/components/files/FileDetail.tsx +++ b/makima/frontend/src/components/files/FileDetail.tsx @@ -1,5 +1,6 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import type { FileDetail as FileDetailType } from "../../lib/api"; +import { BodyRenderer } from "./BodyRenderer"; interface FileDetailProps { file: FileDetailType; @@ -19,6 +20,13 @@ export function FileDetail({ const [isEditing, setIsEditing] = useState(false); const [name, setName] = useState(file.name); const [description, setDescription] = useState(file.description || ""); + const [transcriptExpanded, setTranscriptExpanded] = useState(false); + + // Update local state when file changes + useEffect(() => { + setName(file.name); + setDescription(file.description || ""); + }, [file.name, file.description]); const handleSave = () => { onSave(file.id, name, description); @@ -116,27 +124,70 @@ export function FileDetail({ )} </div> - {/* Transcript */} - <div className="flex-1 overflow-y-auto p-4 space-y-3"> - {file.transcript.length === 0 ? ( - <div className="text-center text-[#9bc3ff] text-sm font-mono opacity-60 py-8"> - No transcript entries. + {/* Content */} + <div className="flex-1 overflow-y-auto p-4 space-y-6"> + {/* Summary Section */} + {file.summary && ( + <div className="border-l-2 border-[#9bc3ff] pl-4"> + <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2"> + Summary + </h3> + <p className="font-mono text-sm text-[#dbe7ff] leading-relaxed"> + {file.summary} + </p> </div> - ) : ( - file.transcript.map((entry) => ( - <div key={entry.id} className="font-mono text-sm"> - <div className="flex items-baseline gap-2 mb-1"> - <span className="text-[#75aafc] text-xs"> - [{entry.start.toFixed(2)}s - {entry.end.toFixed(2)}s] - </span> - <span className="text-[#9bc3ff] text-xs font-bold"> - {entry.speaker} - </span> - </div> - <p className="m-0 text-[#dbe7ff] leading-relaxed">{entry.text}</p> - </div> - )) )} + + {/* Body Content */} + <div> + <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-3"> + Content + </h3> + <BodyRenderer elements={file.body} /> + </div> + + {/* Collapsible Transcript Section */} + <div className="border-t border-dashed border-[rgba(117,170,252,0.35)] pt-4"> + <button + onClick={() => setTranscriptExpanded(!transcriptExpanded)} + className="flex items-center gap-2 font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors uppercase w-full text-left" + > + <span + className={`transition-transform ${ + transcriptExpanded ? "rotate-90" : "" + }`} + > + > + </span> + Transcript ({file.transcript.length} entries) + </button> + + {transcriptExpanded && ( + <div className="mt-4 space-y-3 pl-4"> + {file.transcript.length === 0 ? ( + <div className="text-[#9bc3ff] text-sm font-mono opacity-60"> + No transcript entries. + </div> + ) : ( + file.transcript.map((entry) => ( + <div key={entry.id} className="font-mono text-sm"> + <div className="flex items-baseline gap-2 mb-1"> + <span className="text-[#75aafc] text-xs"> + [{entry.start.toFixed(2)}s - {entry.end.toFixed(2)}s] + </span> + <span className="text-[#9bc3ff] text-xs font-bold"> + {entry.speaker} + </span> + </div> + <p className="m-0 text-[#dbe7ff] leading-relaxed"> + {entry.text} + </p> + </div> + )) + )} + </div> + )} + </div> </div> </div> ); diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index ec596ce..5ef9c22 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -49,6 +49,22 @@ export interface TranscriptEntry { isFinal: boolean; } +// Chart types for visualization +export type ChartType = "line" | "bar" | "pie" | "area"; + +// Body element types for structured content +export type BodyElement = + | { type: "heading"; level: number; text: string } + | { type: "paragraph"; text: string } + | { + type: "chart"; + chartType: ChartType; + title?: string; + data: Record<string, unknown>[]; + config?: Record<string, unknown>; + } + | { type: "image"; src: string; alt?: string; caption?: string }; + export interface FileSummary { id: string; name: string; @@ -66,6 +82,8 @@ export interface FileDetail { description: string | null; transcript: TranscriptEntry[]; location: string | null; + summary: string | null; + body: BodyElement[]; createdAt: string; updatedAt: string; } @@ -86,6 +104,28 @@ export interface UpdateFileRequest { name?: string; description?: string; transcript?: TranscriptEntry[]; + summary?: string; + body?: BodyElement[]; +} + +// Chat API types +export interface ChatRequest { + message: string; +} + +export interface ToolCallInfo { + name: string; + result: { + success: boolean; + message: string; + }; +} + +export interface ChatResponse { + response: string; + toolCalls: ToolCallInfo[]; + updatedBody: BodyElement[]; + updatedSummary: string | null; } // File API functions @@ -140,3 +180,20 @@ export async function deleteFile(id: string): Promise<void> { throw new Error(`Failed to delete file: ${res.statusText}`); } } + +// Chat API function +export async function chatWithFile( + id: string, + message: string +): Promise<ChatResponse> { + const res = await fetch(`${API_BASE}/api/v1/files/${id}/chat`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message }), + }); + if (!res.ok) { + const errorText = await res.text(); + throw new Error(`Chat failed: ${errorText || res.statusText}`); + } + return res.json(); +} diff --git a/makima/frontend/src/routes/files.tsx b/makima/frontend/src/routes/files.tsx index 86a24b8..00c334d 100644 --- a/makima/frontend/src/routes/files.tsx +++ b/makima/frontend/src/routes/files.tsx @@ -3,8 +3,9 @@ import { useParams, useNavigate } from "react-router"; import { Masthead } from "../components/Masthead"; import { FileList } from "../components/files/FileList"; import { FileDetail } from "../components/files/FileDetail"; +import { CliInput } from "../components/files/CliInput"; import { useFiles } from "../hooks/useFiles"; -import type { FileDetail as FileDetailType } from "../lib/api"; +import type { FileDetail as FileDetailType, BodyElement } from "../lib/api"; export default function FilesPage() { const { id } = useParams<{ id: string }>(); @@ -58,25 +59,45 @@ export default function FilesPage() { [editFile, fetchFile] ); + const handleBodyUpdate = useCallback( + (body: BodyElement[], summary: string | null) => { + if (fileDetail) { + setFileDetail({ + ...fileDetail, + body, + summary, + }); + } + }, + [fileDetail] + ); + return ( <div className="relative z-10 h-screen flex flex-col overflow-hidden"> <Masthead showTicker={false} showNav /> - <main className="flex-1 p-4 md:p-6 min-h-0 overflow-hidden"> + <main className="flex-1 p-4 md:p-6 min-h-0 overflow-hidden flex flex-col"> {error && ( - <div className="mb-4 p-3 border border-red-400/50 bg-red-400/10 text-red-400 font-mono text-sm"> + <div className="mb-4 p-3 border border-red-400/50 bg-red-400/10 text-red-400 font-mono text-sm shrink-0"> {error} </div> )} {id && fileDetail ? ( - <FileDetail - file={fileDetail} - loading={detailLoading} - onBack={handleBack} - onSave={handleSave} - onDelete={handleDelete} - /> + <div className="flex-1 flex flex-col min-h-0 overflow-hidden"> + <div className="flex-1 min-h-0 overflow-hidden"> + <FileDetail + file={fileDetail} + loading={detailLoading} + onBack={handleBack} + onSave={handleSave} + onDelete={handleDelete} + /> + </div> + <div className="shrink-0"> + <CliInput fileId={id} onUpdate={handleBodyUpdate} /> + </div> + </div> ) : id && detailLoading ? ( <div className="panel h-full flex items-center justify-center"> <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div> diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index 9bb4ba8..b2542f9 100644 --- a/makima/frontend/tsconfig.tsbuildinfo +++ b/makima/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/rewritelink.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/hooks/usefiles.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetextscramble.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/routes/_index.tsx","./src/routes/files.tsx","./src/routes/listen.tsx","./src/types/messages.ts"],"version":"5.9.3"}
\ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/rewritelink.tsx","./src/components/charts/chartrenderer.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/hooks/usefiles.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetextscramble.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/routes/_index.tsx","./src/routes/files.tsx","./src/routes/listen.tsx","./src/types/messages.ts"],"version":"5.9.3"}
\ No newline at end of file |
