From b25498f6a0984d9fcd8f747375aa9793c88baa06 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Sun, 30 Mar 2025 20:37:08 -0700 Subject: [PATCH] Updating UI --- src/ketr-chat/package-lock.json | 471 ++++++++++++++++++++++++++++++++ src/ketr-chat/package.json | 5 + src/ketr-chat/src/App.css | 50 ++-- src/ketr-chat/src/App.tsx | 263 +++++++++++++++--- src/ketr-chat/src/index.css | 3 +- src/server.py | 32 ++- 6 files changed, 753 insertions(+), 71 deletions(-) diff --git a/src/ketr-chat/package-lock.json b/src/ketr-chat/package-lock.json index 145ad82..734400d 100644 --- a/src/ketr-chat/package-lock.json +++ b/src/ketr-chat/package-lock.json @@ -8,6 +8,11 @@ "name": "ketr-chat", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fontsource/roboto": "^5.2.5", + "@mui/icons-material": "^7.0.1", + "@mui/material": "^7.0.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", @@ -2213,6 +2218,152 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", @@ -2309,6 +2460,14 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fontsource/roboto": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.5.tgz", + "integrity": "sha512-70r2UZ0raqLn5W+sPeKhqlf8wGvUXFWlofaDlcbt/S3d06+17gXKr3VNqDODB0I1ASme3dGT5OJj9NABt7OTZQ==", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -2782,6 +2941,241 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.0.1.tgz", + "integrity": "sha512-T5DNVnSD9pMbj4Jk/Uphz+yvj9dfpl2+EqsOuJtG12HxEihNG5pd3qzX5yM1Id4dDwKRvM3dPVcxyzavTFhJeA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.0.1.tgz", + "integrity": "sha512-x8Em7LISFQ6s/KeZj6ZKwJHq2WttRNe9KJLWFa72eQx7B53s/TzMKOEjGKB/YyhOx+bqqSv1pMvK373M4Xf07A==", + "dependencies": { + "@babel/runtime": "^7.26.10" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.0.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.0.1.tgz", + "integrity": "sha512-tQwjIIsn/UUSCHoCIQVkANuLua67h7Ro9M9gIHoGWaFbJFuF6cSO4Oda2olDVqIs4SWG+PaDChuu6SngxsaoyQ==", + "dependencies": { + "@babel/runtime": "^7.26.10", + "@mui/core-downloads-tracker": "^7.0.1", + "@mui/system": "^7.0.1", + "@mui/types": "^7.4.0", + "@mui/utils": "^7.0.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.0.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==" + }, + "node_modules/@mui/private-theming": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.0.1.tgz", + "integrity": "sha512-1kQ7REYjjzDukuMfTbAjm3pLEhD7gUMC2bWhg9VD6f6sHzyokKzX0XHzlr3IdzNWBjPytGkzHpPIRQrUOoPLCQ==", + "dependencies": { + "@babel/runtime": "^7.26.10", + "@mui/utils": "^7.0.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.0.1.tgz", + "integrity": "sha512-BeGe4xZmF7tESKhmctYrL54Kl25kGHPKVdZYM5qj5Xz76WM/poY+d8EmAqUesT6k2rbJWPp2gtOAXXinNCGunQ==", + "dependencies": { + "@babel/runtime": "^7.26.10", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.0.1.tgz", + "integrity": "sha512-pK+puz0hRPHEKGlcPd80mKYD3jpyi0uVIwWffox1WZgPTQMw2dCKLcD+9ndMDJADnrKzmKlpoH756PPFh2UvWA==", + "dependencies": { + "@babel/runtime": "^7.26.10", + "@mui/private-theming": "^7.0.1", + "@mui/styled-engine": "^7.0.1", + "@mui/types": "^7.4.0", + "@mui/utils": "^7.0.1", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.0.tgz", + "integrity": "sha512-TxJ4ezEeedWHBjOmLtxI203a9DII9l4k83RXmz1PYSAmnyEcK2PglTNmJGxswC/wM5cdl9ap2h8lnXvt2swAGQ==", + "dependencies": { + "@babel/runtime": "^7.26.10" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.0.1.tgz", + "integrity": "sha512-SJKrrebNpmK9rJCnVL29nGPhPXQYtBZmb7Dsp0f58uIUhQfAKcBXHE4Kjs06SX4CwqeCuwEVgcHY+MgAO6XQ/g==", + "dependencies": { + "@babel/runtime": "^7.26.10", + "@mui/types": "^7.4.0", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==" + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -2898,6 +3292,15 @@ } } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3608,6 +4011,11 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" + }, "node_modules/@types/q": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", @@ -3639,6 +4047,14 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -5355,6 +5771,14 @@ "wrap-ansi": "^7.0.0" } }, + "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/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6356,6 +6780,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -7732,6 +8165,11 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -8543,6 +8981,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -14302,6 +14753,21 @@ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -15885,6 +16351,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/src/ketr-chat/package.json b/src/ketr-chat/package.json index 4bb1d22..ad72aae 100644 --- a/src/ketr-chat/package.json +++ b/src/ketr-chat/package.json @@ -3,6 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fontsource/roboto": "^5.2.5", + "@mui/icons-material": "^7.0.1", + "@mui/material": "^7.0.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", diff --git a/src/ketr-chat/src/App.css b/src/ketr-chat/src/App.css index a61b278..ec4fc42 100644 --- a/src/ketr-chat/src/App.css +++ b/src/ketr-chat/src/App.css @@ -5,14 +5,27 @@ div { .App { display: flex; text-align: center; - max-height: 100%; + max-height: 100%; + height: 100%; flex-direction: column; } - + + .Container { + display: flex; + flex-direction: row; + overflow: auto; + margin: 0 auto; + padding: 1rem; + border: 1px solid green; + max-width: 80%; + height: 100vh; + } .ChatBox { display: flex; - flex-direction: "row"; + flex-direction: column; flex-grow: 1; + border: 1px solid red; + max-width: 800px; } .Controls { @@ -26,39 +39,10 @@ div { box-sizing: border-box; overflow-x: visible; min-width: 10rem; -} - -.container { - display: flex; flex-grow: 1; - flex-direction: column; - height: 100vh; - overflow: auto; - max-width: 800px; - margin: 0 auto; - padding: 1rem; + border: 1px solid magenta; } -.query-box { - display: flex; - margin: 20px 0; -} - -.query-box input { - flex-grow: 1; - padding: 8px; - margin-right: 10px; - font-size: 1rem; -} - -.query-box button, -.user-box button { - padding: 0.5rem 1rem; - background-color: #4CAF50; - color: white; - border: none; - cursor: pointer; -} .Conversation { display: flex; diff --git a/src/ketr-chat/src/App.tsx b/src/ketr-chat/src/App.tsx index a4fca44..aa7b6b9 100644 --- a/src/ketr-chat/src/App.tsx +++ b/src/ketr-chat/src/App.tsx @@ -1,4 +1,20 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Switch from '@mui/material/Switch'; +import Card from '@mui/material/Card'; +import CardHeader from '@mui/material/CardHeader'; +import CardContent from '@mui/material/CardContent'; +import TextField from '@mui/material/TextField'; +import Accordion from '@mui/material/Accordion'; +import AccordionActions from '@mui/material/AccordionActions'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import Typography from '@mui/material/Typography'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import Button from '@mui/material/Button'; +import Container from '@mui/material/Container'; + import PropagateLoader from "react-spinners/PropagateLoader"; import Markdown from 'react-markdown'; import './App.css'; @@ -6,30 +22,202 @@ import rehypeKatex from 'rehype-katex' import remarkMath from 'remark-math' import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; + const welcomeMessage = { "role": "assistant", "content": "Welcome to Ketr-Chat. I have real-time access to a lot of information. Ask things like 'What are the headlines from cnn.com?' or 'What is the weather in Portland, OR?'" }; const loadingMessage = { "role": "assistant", "content": "Instancing chat session..." }; //const url: string = "https://ai.ketrenos.com" -const getConnectionBase = (url: string): string => { - if (!url.match(/.*battle-linux.*/)) { - return url; +const getConnectionBase = (loc: any): string => { + if (!loc.host.match(/.*battle-linux.*/)) { + return loc.protocol + "//" + loc.host; } else { - return 'battle-linux.ketrenos.com:5000'; + return loc.protocol + "//battle-linux.ketrenos.com:5000"; } } -const Controls = () => { - const tools = ["get_stock_price", "get_weather", "site_summary", "RAG JPK", "RAG LKML"] +type Tool = { + label: string, + enabled: boolean +}; + +interface ControlsParams { + sessionId: string, + connectionBase: string +}; + +const Controls = ({ sessionId, connectionBase }: ControlsParams) => { + const [systemPrompt, setSystemPrompt] = useState(""); + const [editSystemPrompt, setEditSystemPrompt] = useState(systemPrompt); + + useEffect(() => { + if (systemPrompt !== "") { + return; + } + const fetchSystemPrompt = async () => { + // Make the fetch request with proper headers + const response = await fetch(connectionBase + `/api/system-prompt/${sessionId}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }); + const data = await response.json(); + console.log(data); + setSystemPrompt(data["system-prompt"]); + setEditSystemPrompt(data["system-prompt"]); + } + + fetchSystemPrompt(); + }, [sessionId, systemPrompt, setSystemPrompt, setEditSystemPrompt, connectionBase]); + + const tools: Tool[] = [{ + label: "get_stock_price", + enabled: true, + }, { + label: "get_weather", + enabled: true + }, { + label: "site_summary", + enabled: true + }]; + + const rags: Tool[] = [{ + label: "RAG JPK", + enabled: false + }, { + label: "RAG LKML", + enabled: false + }]; + const toggleTool = (event: any) => { + console.log(`${event.target.value} clicked`) + }; + + const sendSystemPrompt = async () => { + if (!editSystemPrompt.trim()) { + setEditSystemPrompt(systemPrompt) + return; + } + + try { + const response = await fetch(connectionBase + `/api/system-prompt/${sessionId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify({ "system-prompt": editSystemPrompt.trim() }), + }); + + const data = await response.json(); + console.log(data); + if (data["system-prompt"] !== systemPrompt) { + setSystemPrompt(data["system-prompt"].trim()); + } + } catch (error) { + console.error('Fetch error:', error); + } + }; + + const resetSystemPrompt = async () => { + try { + const response = await fetch(connectionBase + `/api/system-prompt/${sessionId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify({ "system-prompt": "" }), + }); + + const data = await response.json(); + const systemPrompt = data["system-prompt"].trim(); + setSystemPrompt(systemPrompt); + setEditSystemPrompt(systemPrompt) + } catch (error) { + console.error('Fetch error:', error); + } + }; + + const handleKeyPress = (event: any) => { + if (event.key === 'Enter' && event.ctrlKey) { + switch (event.target.id) { + case 'SystemPromptInput': + sendSystemPrompt(); + break; + } + } }; return (
-
Enabled Tools
{ - tools.map((tool, index) => { - return (
toggleTool(e.target.checked)} />{tool}
); - }) - }
); + + } + > + Tools + + + These tools can be made available to the LLM for obtaining real-time information from the Internet. + + + + { + tools.map((tool, index) => { + return (} onChange={toggleTool} label={tool.label} />); + }) + } + + + + } + > + RAG + + + These RAG databases can be enabled / disabled for adding additional context based on the chat request. + + + + { + rags.map((rag, index) => { + return (} onChange={toggleTool} label={rag.label} />); + }) + } + + + + } + > + System Prompt + + + setEditSystemPrompt(e.target.value)} + onKeyDown={handleKeyPress} + placeholder="Enter the new system prompt.." + id="SystemPromptInput" + /> +
+ + +
+
+
+ ); } const App = () => { @@ -56,12 +244,12 @@ const App = () => { }, [sessionId, setConversation]); useEffect(() => { - const url = new URL(window.location.href); + const url = new URL(loc.href); const pathParts = url.pathname.split('/').filter(Boolean); if (!pathParts.length) { console.log("No session id -- creating a new session") - fetch(loc.protocol + "//" + getConnectionBase(loc.host) + `/api/context`, { + fetch(getConnectionBase(loc) + `/api/context`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -79,12 +267,12 @@ const App = () => { setSessionId(pathParts[0]); } - }, [setSessionId, loc.host, loc.protocol]); + }, [setSessionId, loc]); - const handleKeyPress = (event: React.KeyboardEvent) => { + const handleKeyPress = (event: any) => { if (event.key === 'Enter') { - switch (event.currentTarget.id) { - case 'query-input': + switch (event.target.id) { + case 'QueryInput': sendQuery(); break; } @@ -121,6 +309,9 @@ const App = () => { // Clear input setQuery(''); + setTimeout(() => { + document.getElementById("QueryIput")?.focus(); + }, 1000); try { setProcessing(true); @@ -135,7 +326,7 @@ const App = () => { ]); // Make the fetch request with proper headers - const response = await fetch(loc.protocol + "//" + getConnectionBase(loc.host) + `/api/chat/${sessionId}`, { + const response = await fetch(getConnectionBase(loc) + `/api/chat/${sessionId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -249,7 +440,7 @@ const App = () => { }; return ( -
+
@@ -301,22 +492,28 @@ const App = () => { />
- -
-
- setQuery(e.target.value)} - onKeyDown={handleKeyPress} - placeholder="Enter your query..." - id="query-input" - /> - +
+ setQuery(e.target.value)} + onKeyDown={handleKeyPress} + placeholder="Enter your question..." + id="QueryInput" + /> + + + +
- + {sessionId !== undefined && } + + ); }; diff --git a/src/ketr-chat/src/index.css b/src/ketr-chat/src/index.css index b6e7de4..ba5d783 100644 --- a/src/ketr-chat/src/index.css +++ b/src/ketr-chat/src/index.css @@ -5,10 +5,9 @@ body { sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - overflow: hidden; padding: 0; height: 100%; - width: 100%; + width: 100%; } code { diff --git a/src/server.py b/src/server.py index 611920a..0167dbe 100644 --- a/src/server.py +++ b/src/server.py @@ -356,6 +356,26 @@ class WebServer: return RedirectResponse(url=f"/{context['id']}", status_code=307) #return JSONResponse({"redirect": f"/{context['id']}"}) + @self.app.put('/api/system-prompt/{context_id}') + async def put_system_prompt(context_id: str, request: Request): + if not is_valid_uuid(context_id): + logging.warning(f"Invalid context_id: {context_id}") + return JSONResponse({"error": "Invalid context_id"}, status_code=400) + context = self.upsert_context(context_id) + data = await request.json() + system_prompt = data["system-prompt"].strip() + if not system_prompt: + system_prompt = system_message + context["system"] = [{"role": "system", "content": system_prompt}] + return JSONResponse({ "system-prompt": system_prompt }) + + @self.app.get('/api/system-prompt/{context_id}') + async def get_system_prompt(context_id: str): + context = self.upsert_context(context_id) + system_prompt = context["system"][0]["content"]; + logging.info(f"returning system prompt as '{system_prompt}'") + return JSONResponse({ "system-prompt": system_prompt }) + @self.app.post('/api/chat/{context_id}') async def chat_endpoint(context_id: str, request: Request): context = self.upsert_context(context_id) @@ -408,8 +428,9 @@ class WebServer: self.logging.info(f"Serve index.html for {path}") return FileResponse('/opt/airc/src/ketr-chat/build/index.html') - def create_context(self): - context_id = str(uuid.uuid4()) + def create_context(self, context_id = None): + if not context_id: + context_id = str(uuid.uuid4()) context = { "id": context_id, "system": [{"role": "system", "content": system_message}], @@ -421,9 +442,14 @@ class WebServer: return context def upsert_context(self, context_id): + if not context_id: + logging.warning("No context ID provided. Creating a new context.") + return self.create_context() if context_id in self.contexts: + logging.info(f"Context {context_id} found.") return self.contexts[context_id] - return self.create_context() + logging.info(f"Context {context_id} not found. Creating new context.") + return self.create_context(context_id) async def chat(self, context, content): content = content.strip()