From e239a63bec1d459983ea3c12ccbacfd86c85df83 Mon Sep 17 00:00:00 2001 From: luis Date: Sun, 5 Feb 2023 22:46:37 -0500 Subject: [PATCH 01/12] chore: flesh out RangeInput component feat: setup generation_settings page --- .gitignore | 2 + src/index.tsx | 2 + src/pages/GenerationSettings/index.tsx | 86 ++++++++++++++++++++++++++ src/shared/RangeInput.tsx | 69 ++++++++++++++------- 4 files changed, 136 insertions(+), 23 deletions(-) create mode 100644 src/pages/GenerationSettings/index.tsx diff --git a/.gitignore b/.gitignore index 7cb6c2f..3f518b8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /dist /node_modules .pnpm-debug.log +package-lock.json +src/pages/Chat/mocks.tsx diff --git a/src/index.tsx b/src/index.tsx index 6107c1d..297fdf4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,7 @@ import NavBar from "./shared/NavBar"; const ChatPage = lazy(() => import("./pages/Chat")); const CharacterSettings = lazy(() => import("./pages/CharacterSettings")); +const GenerationSettings = lazy(() => import("./pages/GenerationSettings")); const Home: Component = () =>

haven't made a homepage

; @@ -19,6 +20,7 @@ const App: Component = () => ( + diff --git a/src/pages/GenerationSettings/index.tsx b/src/pages/GenerationSettings/index.tsx new file mode 100644 index 0000000..9237df6 --- /dev/null +++ b/src/pages/GenerationSettings/index.tsx @@ -0,0 +1,86 @@ +import { Component } from "solid-js"; +import { Save, X } from "lucide-solid"; + +import Button from "../../shared/Button"; +import RangeInput from "../../shared/RangeInput"; + +const GenerationSettings: Component = () => ( + <> +

Generation Settings Settings

+

Some settings might not show up depending on which inference backend is being used.

+
+ +
+ + + + + + + + +
+ + + +
+
+ +); + +export default GenerationSettings; \ No newline at end of file diff --git a/src/shared/RangeInput.tsx b/src/shared/RangeInput.tsx index 2bb9202..18a28bd 100644 --- a/src/shared/RangeInput.tsx +++ b/src/shared/RangeInput.tsx @@ -1,26 +1,49 @@ -import { Component } from "solid-js"; +import { Component, Show } from "solid-js"; -const RangeInput: Component = () => ( -
- - -
-); +const RangeInput: Component<{ + label: string; + helperText?: string; + min: number; + max: number; + step: number; + placeholder?: any; +}> = (props) => { + + return ( +
+
    + + +
+ +

+ {props.helperText} +

+
+ +
+ ); +}; export default RangeInput; From 1dac0e8de3af5bcf141f48d0bbf997211e7360eb Mon Sep 17 00:00:00 2001 From: luis Date: Mon, 6 Feb 2023 15:29:33 -0500 Subject: [PATCH 02/12] fix typo with importing Register component --- src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index efffb7b..82b43b0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,7 +10,7 @@ const CharacterSettings = lazy(() => import("./pages/CharacterSettings")); const GenerationSettings = lazy(() => import("./pages/GenerationSettings")); const Home = lazy(() => import("./pages/Home")); const Account = lazy(() => import("./pages/Account")); -const Register = lazy(() => import("./pages/Account/Register")); +const Register = lazy(() => import("./pages/Account/register")); const App: Component = () => ( From 5c0ad6fd2f9655e3104e2f1847d98a6acffea60f Mon Sep 17 00:00:00 2001 From: luis Date: Wed, 8 Feb 2023 18:54:42 -0500 Subject: [PATCH 03/12] update ranger slider component --- .gitignore | 3 +- pnpm-lock.yaml | 2 +- src/pages/GenerationSettings/index.tsx | 14 ++++----- src/shared/RangeInput.tsx | 40 +++++++++++++++++--------- src/tailwind.css | 20 +++++++++++++ 5 files changed, 56 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 3f518b8..f373f2d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ /dist /node_modules .pnpm-debug.log -package-lock.json -src/pages/Chat/mocks.tsx +src/pages/Chat/mocks.tsx \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6197d0b..fda75aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2040,7 +2040,7 @@ packages: dependencies: fast-glob: 3.2.12 postcss: 8.4.21 - tailwindcss: 3.2.4 + tailwindcss: 3.2.4_postcss@8.4.21 transitivePeerDependencies: - ts-node dev: true diff --git a/src/pages/GenerationSettings/index.tsx b/src/pages/GenerationSettings/index.tsx index 9237df6..0ff5632 100644 --- a/src/pages/GenerationSettings/index.tsx +++ b/src/pages/GenerationSettings/index.tsx @@ -17,7 +17,7 @@ const GenerationSettings: Component = () => ( min={16} max={512} step={4} - placeholder={196} + value={196} /> ( min={0.1} max={2} step={0.01} - placeholder={0.5} + value={0.5} /> ( min={0} max={1} step={0.01} - placeholder={0.9} + value={0.9} /> ( min={0} max={100} step={1} - placeholder={0} + value={0} /> ( min={0} max={1} step={0.01} - placeholder={1} + value={1} /> ( min={0} max={3} step={0.01} - placeholder={1.05} + value={1.05} /> ( min={0} max={1} step={0.05} - placeholder={0.6} + value={0.6} />
diff --git a/src/shared/RangeInput.tsx b/src/shared/RangeInput.tsx index 18a28bd..312720a 100644 --- a/src/shared/RangeInput.tsx +++ b/src/shared/RangeInput.tsx @@ -1,23 +1,39 @@ -import { Component, Show } from "solid-js"; +import { Component, Show, createSignal } from "solid-js"; +import type { JSX } from "solid-js"; const RangeInput: Component<{ label: string; + value: number; helperText?: string; min: number; max: number; step: number; - placeholder?: any; }> = (props) => { + const [value, setValue] = createSignal(props.value); + + function updateRangeSliders() { + Array.from(document.getElementsByTagName('input')).forEach(input => { + input.style.backgroundSize = (Number(input.value) - Number(input.min)) * 100 / (Number(input.max) - Number(input.min)) + '% 100%'; + }); + } + + const onInput: JSX.EventHandler = (event) => { + setValue(Number(event.currentTarget.value)); + updateRangeSliders(); + }; + + window.onload = updateRangeSliders; + return (
    -
- +

{props.helperText}

@@ -26,21 +42,19 @@ const RangeInput: Component<{ type="range" class=" form-range - h-6 - w-full appearance-none - bg-transparent - focusable-field - !outline-purple-400/50 + accent-purple-400/50 + cursor-ew-resize + h-1 + w-full rounded-xl - p-0 focus:shadow-none focus:outline-none focus:ring-0 " min={props.min} max={props.max} step={props.step} - placeholder={props.placeholder} - id="customRange2" + onInput={onInput} + value={value()} />
); diff --git a/src/tailwind.css b/src/tailwind.css index 45c0cdb..4b14f7f 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -56,4 +56,24 @@ .form-check-input:checked[type="radio"] { background-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%22-4 -4 8 8%22%3E%3Ccircle r=%222%22 fill=%22%23fff%22/%3E%3C/svg%3E"); } + + input[type="range"] { + background: rgb(255 255 255 / 0.05); + background-image: linear-gradient(rgb(152 64 160), rgb(152 64 160)); + background-repeat: no-repeat; + } + + input[type="range"]::-webkit-slider-thumb { + height: 20px; + width: 20px; + border-radius: 50%; + background: rgb(152 64 160); + box-shadow: 0 0 2px 0 #555; + } + + input[type="range"]::-webkit-slider-runnable-track { + box-shadow: none; + border: none; + background: transparent; + } } From 7d535333f7ad0c79bc48fb6c9fcefd3cc2a4c389 Mon Sep 17 00:00:00 2001 From: luis Date: Thu, 9 Feb 2023 15:52:35 -0500 Subject: [PATCH 04/12] merge upstream and swap window.onload for createEffect in RangeInput component --- .eslintrc.js | 5 +- README.md | 43 ++++++- package.json | 6 + pnpm-lock.yaml | 46 ++++++++ src/App.tsx | 30 +++++ src/api/characters/list.ts | 15 +++ src/api/index.ts | 19 +++ src/api/users/login.ts | 31 +++++ src/api/utils.ts | 22 ++++ src/hooks/auth.tsx | 55 +++++++++ src/index.tsx | 41 +++---- src/models/Character.tsx | 14 ++- src/models/User.tsx | 7 ++ src/pages/Account/index.tsx | 6 - src/pages/Account/login.tsx | 28 ----- src/pages/Account/register.tsx | 31 ----- src/pages/CharacterSettings/index.tsx | 8 +- src/pages/Chat/components/InputBar.tsx | 2 +- src/pages/Chat/index.tsx | 35 +++--- src/pages/Chat/mocks.ts | 13 ++ src/pages/Home/components/CreateNewCard.tsx | 14 +-- src/pages/Home/index.tsx | 124 +++++++------------- src/pages/Login/LoginForm.tsx | 27 +++++ src/pages/Login/index.tsx | 87 ++++++++++++++ src/providers/AppStoreProvider.tsx | 35 ++++++ src/shared/Alert.tsx | 39 ++++++ src/shared/Button.tsx | 15 ++- src/shared/CharacterCard.tsx | 26 ++-- src/shared/Divider.tsx | 6 + src/shared/NavBar.tsx | 13 +- src/shared/PageHeader.tsx | 15 +++ src/shared/RangeInput.tsx | 7 +- src/shared/RequiresAuth.tsx | 26 ++++ src/shared/TextInput.tsx | 20 ++-- src/tailwind.css | 13 +- 35 files changed, 681 insertions(+), 243 deletions(-) create mode 100644 src/App.tsx create mode 100644 src/api/characters/list.ts create mode 100644 src/api/index.ts create mode 100644 src/api/users/login.ts create mode 100644 src/api/utils.ts create mode 100644 src/hooks/auth.tsx create mode 100644 src/models/User.tsx delete mode 100644 src/pages/Account/index.tsx delete mode 100644 src/pages/Account/login.tsx delete mode 100644 src/pages/Account/register.tsx create mode 100644 src/pages/Chat/mocks.ts create mode 100644 src/pages/Login/LoginForm.tsx create mode 100644 src/pages/Login/index.tsx create mode 100644 src/providers/AppStoreProvider.tsx create mode 100644 src/shared/Alert.tsx create mode 100644 src/shared/Divider.tsx create mode 100644 src/shared/PageHeader.tsx create mode 100644 src/shared/RequiresAuth.tsx diff --git a/.eslintrc.js b/.eslintrc.js index b234761..d4a7312 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,7 @@ module.exports = { "airbnb-base", "airbnb-typescript/base", "plugin:solid/typescript", - "plugin:tailwindcss/recommended", + "plugin:tailwindcss/recommended", "prettier", ], overrides: [], @@ -17,7 +17,8 @@ module.exports = { project: "./tsconfig.json", }, rules: { + "@typescript-eslint/explicit-module-boundary-types": "error", "import/extensions": ["error", "never"], - "tailwindcss/no-custom-classname": "off", + "tailwindcss/no-custom-classname": "off", }, }; diff --git a/README.md b/README.md index 3788338..1ebc3af 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,45 @@ The official UI for interacting with the Pygmalion models. -Work in progress, not ready for use yet. +Very early work in progress. + +## Contributing + +If you wish to contribute, this section has some relevant information. + +### Tech stack + +The important parts of the stack are: + +- [SolidJS](https://www.solidjs.com/) for interactivity +- [TailwindCSS](https://tailwindcss.com/) for styling +- [pnpm](https://pnpm.io/) for dependency management + +### Quick start + +If you have Node and `pnpm` installed and working, you can start the development server with: + +```bash +# install dependencies +$ pnpm install + +# start the dev server +$ pnpm start +``` + +By default, it expects the back-end to be running locally at `http://localhost:3000`. If that's not the case, you can override this with the `CORE_API_SERVER` environment variable. + +### Code quality checks + +The project uses ESLint for linting, Prettier for enforcing code style and TypeScript to check for type errors. When opening a PR, please make sure you're not introducing any new errors in any of these checks by running: + +```bash +# auto-fixes any style problems +$ pnpm run style:fix + +# auto-fixes any linting problems, and prints the ones that can't be auto-fixed +$ pnpm run lint:fix + +# runs the TypeScript compiler so any type errors will be shown +$ pnpm run typecheck +``` diff --git a/package.json b/package.json index 0b4da76..e148c75 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,18 @@ "license": "AGPL-3.0", "dependencies": { "@solidjs/router": "^0.6.0", + "@tanstack/solid-query": "^4.24.4", + "js-cookie": "^3.0.1", + "lodash": "^4.17.21", "lucide-solid": "0.105.0-alpha.9", "showdown": "^2.1.0", "solid-js": "^1.6.9" }, "devDependencies": { "@babel/core": "^7.20.12", + "@types/js-cookie": "^3.0.2", + "@types/lodash": "^4.14.191", + "@types/node": "^18.13.0", "@types/showdown": "^2.0.0", "@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/parser": "^5.48.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fda75aa..0459bc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,6 +3,10 @@ lockfileVersion: 5.4 specifiers: '@babel/core': ^7.20.12 '@solidjs/router': ^0.6.0 + '@tanstack/solid-query': ^4.24.4 + '@types/js-cookie': ^3.0.2 + '@types/lodash': ^4.14.191 + '@types/node': ^18.13.0 '@types/showdown': ^2.0.0 '@typescript-eslint/eslint-plugin': ^5.48.1 '@typescript-eslint/parser': ^5.48.1 @@ -14,6 +18,8 @@ specifiers: eslint-plugin-import: ^2.27.4 eslint-plugin-solid: ^0.9.3 eslint-plugin-tailwindcss: ^3.8.0 + js-cookie: ^3.0.1 + lodash: ^4.17.21 lucide-solid: 0.105.0-alpha.9 parcel: ^2.8.2 postcss: ^8.4.21 @@ -27,12 +33,18 @@ specifiers: dependencies: '@solidjs/router': 0.6.0_solid-js@1.6.9 + '@tanstack/solid-query': 4.24.4_solid-js@1.6.9 + js-cookie: 3.0.1 + lodash: 4.17.21 lucide-solid: 0.105.0-alpha.9_solid-js@1.6.9 showdown: 2.1.0 solid-js: 1.6.9 devDependencies: '@babel/core': 7.20.12 + '@types/js-cookie': 3.0.2 + '@types/lodash': 4.14.191 + '@types/node': 18.13.0 '@types/showdown': 2.0.0 '@typescript-eslint/eslint-plugin': 5.48.1_oomjohfipuyaxs2zyomcx4f5by '@typescript-eslint/parser': 5.48.1_7uibuqfxkfaozanbtbziikiqje @@ -1173,11 +1185,28 @@ packages: tslib: 2.4.1 dev: true + /@tanstack/query-core/4.24.4: + resolution: {integrity: sha512-9dqjv9eeB6VHN7lD3cLo16ZAjfjCsdXetSAD5+VyKqLUvcKTL0CklGQRJu+bWzdrS69R6Ea4UZo8obHYZnG6aA==} + dev: false + + /@tanstack/solid-query/4.24.4_solid-js@1.6.9: + resolution: {integrity: sha512-aAFNJ3liO0JiBwhAiE/1uwvViz0fokCMwUILUlueHuCZ95LDq+I9Pw9pYjoXLyPoFfp36N398AyV8pALZqbDRg==} + peerDependencies: + solid-js: ^1.5.7 + dependencies: + '@tanstack/query-core': 4.24.4 + solid-js: 1.6.9 + dev: false + /@trysound/sax/0.2.0: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} dev: true + /@types/js-cookie/3.0.2: + resolution: {integrity: sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==} + dev: true + /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -1186,6 +1215,14 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/lodash/4.14.191: + resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} + dev: true + + /@types/node/18.13.0: + resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} + dev: true + /@types/parse-json/4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: true @@ -2643,6 +2680,11 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /js-cookie/3.0.1: + resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} + engines: {node: '>=12'} + dev: false + /js-sdsl/4.2.0: resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} dev: true @@ -2839,6 +2881,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + /lru-cache/5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..aefdeb7 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,30 @@ +import { Component, lazy } from "solid-js"; + +import { Route, Routes } from "@solidjs/router"; + +import NavBar from "./shared/NavBar"; + +const ChatPage = lazy(() => import("./pages/Chat")); +const CharacterSettings = lazy(() => import("./pages/CharacterSettings")); +const GenerationSettings = lazy(() => import("./pages/GenerationSettings")); +const Home = lazy(() => import("./pages/Home")); +const Login = lazy(() => import("./pages/Login")); + +const App: Component = () => ( +
+ +
+
+ + + + + + + +
+
+
+); + +export default App; diff --git a/src/api/characters/list.ts b/src/api/characters/list.ts new file mode 100644 index 0000000..4634ec9 --- /dev/null +++ b/src/api/characters/list.ts @@ -0,0 +1,15 @@ +/* eslint-disable import/prefer-default-export */ +import { BASE_CORE_API_URL } from ".."; +import Character from "../../models/Character"; +import { camelize } from "../utils"; + +/** Fetches available characters. */ +export const fetchCharacters = async (jwt: string): Promise => { + const res = await fetch(`${BASE_CORE_API_URL}/characters`, { + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + const rawCharacters: Record[] = await res.json(); + return rawCharacters.map((char) => camelize(char)) as unknown as Character[]; +}; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..b2f33c6 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,19 @@ +const CORE_API_SERVER = process.env.CORE_API_SERVER || "http://localhost:3000"; + +/** Base path to the v1 core API. */ +export const BASE_CORE_API_URL = `${CORE_API_SERVER}/api/v1`; + +/** Minimal JWT decoder. Does not validate signature. */ +export const parseJWT = (jwt: string): unknown => { + const base64Url = jwt.split(".")[1]; + const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + const jsonPayload = decodeURIComponent( + window + .atob(base64) + .split("") + .map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`) + .join("") + ); + + return JSON.parse(jsonPayload); +}; diff --git a/src/api/users/login.ts b/src/api/users/login.ts new file mode 100644 index 0000000..59ac1f5 --- /dev/null +++ b/src/api/users/login.ts @@ -0,0 +1,31 @@ +/* eslint-disable import/prefer-default-export */ +import { BASE_CORE_API_URL } from ".."; + +interface LoginData { + email: string; + password: string; +} + +interface SuccessfulLoginResponse { + id: string; + email: string; + display_name: string; + jwt: string; +} + +interface FailedLoginResponse { + error: string; + code: number; +} + +type LoginResponse = SuccessfulLoginResponse | FailedLoginResponse; + +/** POSTs `data` to the login endpoint. */ +export const performLogin = async (data: LoginData): Promise => { + const body = JSON.stringify(data); + const res = await fetch(`${BASE_CORE_API_URL}/users/login`, { + method: "POST", + body, + }); + return res.json(); +}; diff --git a/src/api/utils.ts b/src/api/utils.ts new file mode 100644 index 0000000..eb4be77 --- /dev/null +++ b/src/api/utils.ts @@ -0,0 +1,22 @@ +/* eslint-disable import/prefer-default-export */ +import { camelCase, isArray, transform, isObject } from "lodash"; + +/** Recursively transforms all object keys into camelCase. */ +export const camelize = ( + obj: Record +): Record => + transform( + obj, + ( + result: Record, + value: unknown, + key: string, + target: unknown + ) => { + const camelKey = isArray(target) ? key : camelCase(key); + // eslint-disable-next-line no-param-reassign + result[camelKey] = isObject(value) + ? camelize(value as Record) + : value; + } + ); diff --git a/src/hooks/auth.tsx b/src/hooks/auth.tsx new file mode 100644 index 0000000..049b339 --- /dev/null +++ b/src/hooks/auth.tsx @@ -0,0 +1,55 @@ +import Cookies from "js-cookie"; +import { parseJWT } from "../api"; +import { useAppStore } from "../providers/AppStoreProvider"; + +/** Hook to interact with authentication data within the global app store. */ +const useAuth = (): { + isAuthenticated: () => boolean; + jwt: () => string | undefined; + login: (jwt: string) => void; + logout: (jwt: string) => void; +} => { + const [appStore, updateAppStore] = useAppStore(); + + /** + * Logs the user in by saving the JWT to the store and cookie jar, and saves a + * decoded version of the JWT for use elsewhere. + */ + const login = (jwt: string) => { + updateAppStore("auth", { jwt, user: parseJWT(jwt) }); + Cookies.set("jwt", jwt, { + sameSite: "strict", + expires: 7, + }); + }; + + /** + * Logs the user out by clearing out the authentication cookie and deleting + * the JWT from the store. + */ + const logout = () => { + updateAppStore("auth", { jwt: undefined, user: undefined }); + Cookies.remove("jwt"); + }; + + /** Returns whether the user is currently authenticated. */ + const isAuthenticated = () => { + if (appStore.auth.jwt) { + return true; + } + + const jwt = Cookies.get("jwt"); + if (!jwt) { + return false; + } + + login(jwt); + return true; + }; + + const jwt = () => appStore.auth.jwt; + + return { isAuthenticated, jwt, login, logout }; +}; + +export default useAuth; diff --git a/src/index.tsx b/src/index.tsx index 82b43b0..b24a870 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,35 +1,22 @@ -// eslint-disable-next-line import/no-extraneous-dependencies +import { Component } from "solid-js"; import { render } from "solid-js/web"; -import { Component, lazy } from "solid-js"; -import { Router, Routes, Route } from "@solidjs/router"; -import NavBar from "./shared/NavBar"; +import { Router } from "@solidjs/router"; +import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"; -const ChatPage = lazy(() => import("./pages/Chat")); -const CharacterSettings = lazy(() => import("./pages/CharacterSettings")); -const GenerationSettings = lazy(() => import("./pages/GenerationSettings")); -const Home = lazy(() => import("./pages/Home")); -const Account = lazy(() => import("./pages/Account")); -const Register = lazy(() => import("./pages/Account/register")); +import App from "./App"; +import { AppStoreProvider } from "./providers/AppStoreProvider"; -const App: Component = () => ( +const queryClient = new QueryClient(); + +const AppContainer: Component = () => ( -
- -
-
- - - - - - - - -
-
-
+ + + + +
); -render(() => , document.getElementById("root") as HTMLElement); +render(() => , document.getElementById("root") as HTMLElement); diff --git a/src/models/Character.tsx b/src/models/Character.tsx index 48fe010..1cf57e1 100644 --- a/src/models/Character.tsx +++ b/src/models/Character.tsx @@ -1,9 +1,13 @@ interface Character { + id: string; + name: string; - avatarUrl?: string; description: string; - greeting: string; - persona: string; - exampleConversations: string; - visibility: "public" | "unlisted" | "private"; + avatarId?: string; + visibility: "public" | "private" | "unlisted"; + + createdAt: string; + updatedAt: string; } + +export default Character; diff --git a/src/models/User.tsx b/src/models/User.tsx new file mode 100644 index 0000000..cf02538 --- /dev/null +++ b/src/models/User.tsx @@ -0,0 +1,7 @@ +interface User { + id: string; + email: string; + displayName: string; +} + +export default User; diff --git a/src/pages/Account/index.tsx b/src/pages/Account/index.tsx deleted file mode 100644 index 7949ce2..0000000 --- a/src/pages/Account/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -const Account = () => ( - // TODO(11b): This should be the account page where the user can see and edit - // his own info (password, display name, created bots, etc.). -

not implemented yet

-); -export default Account; diff --git a/src/pages/Account/login.tsx b/src/pages/Account/login.tsx deleted file mode 100644 index 55bc091..0000000 --- a/src/pages/Account/login.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { A } from "@solidjs/router"; -import Button from "../../shared/Button"; -import TextInput from "../../shared/TextInput"; - -const Account = () => ( -
-
-
-

Log in

-
- - -
- - - - -
-
-
-); - -export default Account; diff --git a/src/pages/Account/register.tsx b/src/pages/Account/register.tsx deleted file mode 100644 index 10273fb..0000000 --- a/src/pages/Account/register.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import Button from "../../shared/Button"; -import TextInput from "../../shared/TextInput"; - -const Register = () => ( -
-
-
-

Register

-
- - - -
- - -
-
-
-); - -export default Register; diff --git a/src/pages/CharacterSettings/index.tsx b/src/pages/CharacterSettings/index.tsx index 05e4821..b1f1b01 100644 --- a/src/pages/CharacterSettings/index.tsx +++ b/src/pages/CharacterSettings/index.tsx @@ -4,6 +4,7 @@ import { Save, X } from "lucide-solid"; import Button from "../../shared/Button"; import TextInput from "../../shared/TextInput"; import RadioGroup, { RadioOption } from "../../shared/RadioGroup"; +import PageHeader from "../../shared/PageHeader"; const visibilityOptions: RadioOption[] = [ { @@ -38,9 +39,10 @@ const visibilityOptions: RadioOption[] = [ const CharacterSettings: Component = () => ( <> -

Character Settings

-

Configure BOT's character.

-
+
= (props) => ( ); /** Bar containing the message text input and some attached buttons. */ -const InputBar = () => ( +const InputBar: Component = () => (
( -
- -
-
- {(message) => } +const ChatPage: Component = () => ( + +
+ +
+
+ {(message) => } +
+
-
-
+ ); export default ChatPage; diff --git a/src/pages/Chat/mocks.ts b/src/pages/Chat/mocks.ts new file mode 100644 index 0000000..c8bb83b --- /dev/null +++ b/src/pages/Chat/mocks.ts @@ -0,0 +1,13 @@ +import Message from "../../models/Message"; + +export const mockMessages: Message[] = [ + { + speaker: { + name: "John", + avatarUrl: "#", + isHuman: true, + }, + utterance: "Hi Robot", + timestamp: new Date(), + }, +]; diff --git a/src/pages/Home/components/CreateNewCard.tsx b/src/pages/Home/components/CreateNewCard.tsx index 3c1f7c1..6f2d647 100644 --- a/src/pages/Home/components/CreateNewCard.tsx +++ b/src/pages/Home/components/CreateNewCard.tsx @@ -2,12 +2,12 @@ import { A } from "@solidjs/router"; import { Plus } from "lucide-solid"; import { Component } from "solid-js"; -const CreateNewCard: Component = (props) => ( - -
- -
-
- ); +const CreateNewCard: Component = () => ( + +
+ +
+
+); export default CreateNewCard; diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 54ace4f..6fcac82 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,90 +1,52 @@ -import { createSignal, For, onMount } from "solid-js"; -import CharacterCard from "../../shared/CharacterCard"; -import CreateNewCard from "./components/CreateNewCard"; +import { Component, For, Suspense } from "solid-js"; -const HomePage = () => { - const testTemplate = [ - { - name: "Ibuki-chan", - avatarUrl: "", - description: "", - }, - { - name: "Ibuki-chan", - avatarUrl: "", - description: "", - }, - { - name: "Ibuki-chan", - avatarUrl: "", - description: "", - }, - { - name: "Ibuki-chan", - avatarUrl: "", - description: "", - }, - { - name: "Ibuki-chan", - avatarUrl: "", - description: "", - }, - ] as Character[]; +import { createQuery } from "@tanstack/solid-query"; - const [recentlyChatedCharacters, setRecentlyChatedCharacters] = - createSignal(testTemplate); - const [recommendedCharacters, setRecommendedCharacters] = - createSignal(testTemplate); - const [popularCharacters, setPopularCharacters] = createSignal(testTemplate); - const [recentlyCreatedCharacters, setRecentlyCreatedCharacters] = - createSignal(testTemplate); +import { fetchCharacters } from "../../api/characters/list"; +import Character from "../../models/Character"; +import CharacterCard from "../../shared/CharacterCard"; +import PageHeader from "../../shared/PageHeader"; +import useAuth from "../../hooks/auth"; +import RequiresAuth from "../../shared/RequiresAuth"; - return ( - <> -

Recently Chatted Characters

-

Characters that you recently chatted.

-
-
- - {(e: Character) => ( - - )} - -
+const CharacterGroup: Component<{ + title: string; + description: string; + characters: Character[]; +}> = (props) => ( + <> + -

Recommended Characters

-

Characters that are recommended for you

-
-
- - {(e: Character) => ( - - )} - -
+
+ + {(character: Character) => ( + + )} + +
+ +); -

Popular characters

-

Characters that are popular in the community.

-
-
- - {(e: Character) => ( - - )} - -
+const HomePage: Component = () => { + const { jwt } = useAuth(); + const query = createQuery( + () => ["characters"], + () => fetchCharacters(jwt()!) + ); -

Recently Created Characters

-

Characters that are made recently.

-
-
- - {(e: Character) => ( - - )} - -
- + return ( + + + + + ); }; export default HomePage; diff --git a/src/pages/Login/LoginForm.tsx b/src/pages/Login/LoginForm.tsx new file mode 100644 index 0000000..0890686 --- /dev/null +++ b/src/pages/Login/LoginForm.tsx @@ -0,0 +1,27 @@ +import { Component } from "solid-js"; + +import Button from "../../shared/Button"; +import TextInput from "../../shared/TextInput"; + +const LoginForm: Component<{ + isLoading: boolean; + onSubmit: (evt: Event) => void; +}> = (props) => ( +
props.onSubmit(evt)} class="flex flex-col gap-6"> +
+ + +
+ + +
+); + +export default LoginForm; diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx new file mode 100644 index 0000000..bcd300e --- /dev/null +++ b/src/pages/Login/index.tsx @@ -0,0 +1,87 @@ +import { Component, createEffect, createMemo, Show } from "solid-js"; + +import { useLocation, useNavigate } from "@solidjs/router"; +import { createMutation } from "@tanstack/solid-query"; + +import { performLogin } from "../../api/users/login"; +import useAuth from "../../hooks/auth"; +import Alert from "../../shared/Alert"; +import Divider from "../../shared/Divider"; +import PageHeader from "../../shared/PageHeader"; +import LoginForm from "./LoginForm"; + +const LoginPage: Component = () => { + const { login } = useAuth(); + const { state } = useLocation(); + const navigate = useNavigate(); + const mutation = createMutation(performLogin); + + /** Friendly error message passed out of the mutation, if it exists. */ + const loginError = createMemo(() => { + if (mutation.data) { + if ("error" in mutation.data) { + return mutation.data.error; + } + } + + if (mutation.error) { + const err = mutation.error as Error; + if (err.message.includes("NetworkError")) { + return "We couldn't reach our servers."; + } + return "Something went wrong."; + } + + return null; + }); + + /** Form submission callback to handle POSTing to the back-end. */ + const onSubmit = (evt: Event) => { + evt.preventDefault(); + if (!evt.target) { + return; + } + + const form = new FormData(evt.target as HTMLFormElement); + mutation.mutate({ + email: form.get("email")?.valueOf() as string, + password: form.get("password")?.valueOf() as string, + }); + }; + + /** Side-effect to take care of a successful login. */ + createEffect(() => { + if (!mutation.data || !("jwt" in mutation.data)) return; + + login(mutation.data.jwt); + + let redirectTo = "/"; + if (state && "redirectTo" in state) { + redirectTo = state.redirectTo as string; + } + navigate(redirectTo, { replace: true }); + }); + + return ( +
+
+
+ + + + + + + + {loginError()} + + +
+
+ ); +}; + +export default LoginPage; diff --git a/src/providers/AppStoreProvider.tsx b/src/providers/AppStoreProvider.tsx new file mode 100644 index 0000000..dbe6648 --- /dev/null +++ b/src/providers/AppStoreProvider.tsx @@ -0,0 +1,35 @@ +import { Component, createContext, JSX, useContext } from "solid-js"; +import { createStore, SetStoreFunction } from "solid-js/store"; + +import User from "../models/User"; + +interface AppStore { + // TODO(11b): Type up proper domain models and use them here. + auth: { jwt?: string; user?: User }; +} + +const defaultStore = { + auth: { + jwt: undefined, + user: undefined, + }, +}; + +const AppStoreContext = createContext(); +type AppStoreContextValue = [AppStore, SetStoreFunction]; + +/** Provides a SolidJS store for storing global data. */ +export const AppStoreProvider: Component<{ + children: JSX.Element; +}> = (props) => { + const [appStore, updateAppStore] = createStore(defaultStore); + + return ( + + {props.children} + + ); +}; + +export const useAppStore = (): AppStoreContextValue => + useContext(AppStoreContext)!; diff --git a/src/shared/Alert.tsx b/src/shared/Alert.tsx new file mode 100644 index 0000000..332a34f --- /dev/null +++ b/src/shared/Alert.tsx @@ -0,0 +1,39 @@ +import { AlertTriangle } from "lucide-solid"; +import { LucideProps } from "lucide-solid/dist/types/types"; +import { Component, JSX, createMemo } from "solid-js"; + +type AlertSchema = "error"; + +const schemaToClasses: Record = { + error: "bg-red-500/10 text-red-400", +}; + +const schemaToIcon: Record JSX.Element> = { + error: AlertTriangle, +}; + +const Alert: Component<{ + title: JSX.Element; + children: JSX.Element; + schema: AlertSchema; +}> = (props) => { + const classes = createMemo(() => + [schemaToClasses[props.schema], "rounded-lg p-4 text-sm flex gap-2"].join( + " " + ) + ); + + return ( + + ); +}; + +export default Alert; diff --git a/src/shared/Button.tsx b/src/shared/Button.tsx index d13168a..5b07c37 100644 --- a/src/shared/Button.tsx +++ b/src/shared/Button.tsx @@ -7,10 +7,17 @@ const schemaNameToClass: Record = { secondary: "btn-secondary", }; -const Button: Component<{ children: JSX.Element; schema?: ButtonSchema }> = ( - props -) => ( - ); diff --git a/src/shared/CharacterCard.tsx b/src/shared/CharacterCard.tsx index 620df5e..6dbc9f4 100644 --- a/src/shared/CharacterCard.tsx +++ b/src/shared/CharacterCard.tsx @@ -1,17 +1,25 @@ -import { Component, Show } from "solid-js"; +import { Component } from "solid-js"; -const CharacterCard: Component<{ displayName: string; avatarUrl?: string }> = ( +import { A } from "@solidjs/router"; + +import Character from "../models/Character"; + +const CharacterCard: Component<{ character: Character; href: string }> = ( props ) => ( +
-
- - {props.displayName} - +
+
+ +
+ {props.character.name} +

{props.character.description}

- ); +
+); export default CharacterCard; diff --git a/src/shared/Divider.tsx b/src/shared/Divider.tsx new file mode 100644 index 0000000..0a0880e --- /dev/null +++ b/src/shared/Divider.tsx @@ -0,0 +1,6 @@ +import { Component } from "solid-js"; + +/** A subtle horizontal divider line. */ +const Divider: Component = () =>
; + +export default Divider; diff --git a/src/shared/NavBar.tsx b/src/shared/NavBar.tsx index b881b99..a8f00e7 100644 --- a/src/shared/NavBar.tsx +++ b/src/shared/NavBar.tsx @@ -1,14 +1,7 @@ -import { Component, JSX } from "solid-js"; +import { MessageCircle, Settings, User, Users } from "lucide-solid"; +import { Component } from "solid-js"; + import { A } from "@solidjs/router"; -import { - ChevronLeft, - Home, - Menu, - MessageCircle, - Settings, - Users, - User, -} from "lucide-solid"; const NavBar: Component = () => ( diff --git a/src/shared/PageHeader.tsx b/src/shared/PageHeader.tsx new file mode 100644 index 0000000..332bc3b --- /dev/null +++ b/src/shared/PageHeader.tsx @@ -0,0 +1,15 @@ +import { Component, Show } from "solid-js"; + +import Divider from "./Divider"; + +const PageHeader: Component<{ title: string; subtitle?: string }> = (props) => ( + <> +

{props.title}

+ +

{props.subtitle}

+
+ + +); + +export default PageHeader; diff --git a/src/shared/RangeInput.tsx b/src/shared/RangeInput.tsx index 312720a..1282571 100644 --- a/src/shared/RangeInput.tsx +++ b/src/shared/RangeInput.tsx @@ -1,5 +1,6 @@ -import { Component, Show, createSignal } from "solid-js"; +import { Component, Show, createSignal, createEffect } from "solid-js"; import type { JSX } from "solid-js"; +import { update } from "lodash"; const RangeInput: Component<{ label: string; @@ -22,8 +23,8 @@ const RangeInput: Component<{ setValue(Number(event.currentTarget.value)); updateRangeSliders(); }; - - window.onload = updateRangeSliders; + + createEffect(updateRangeSliders); return (
diff --git a/src/shared/RequiresAuth.tsx b/src/shared/RequiresAuth.tsx new file mode 100644 index 0000000..ac79319 --- /dev/null +++ b/src/shared/RequiresAuth.tsx @@ -0,0 +1,26 @@ +import { Component, createEffect, JSX, Show } from "solid-js"; + +import { useLocation, useNavigate } from "@solidjs/router"; + +import useAuth from "../hooks/auth"; + +const RequiresAuth: Component<{ children: JSX.Element }> = (props) => { + const { isAuthenticated } = useAuth(); + const location = useLocation(); + const navigate = useNavigate(); + + createEffect(() => { + if (isAuthenticated()) { + return; + } + + navigate("/account/login", { + replace: true, + state: { redirectTo: location.pathname }, + }); + }); + + return {props.children}; +}; + +export default RequiresAuth; diff --git a/src/shared/TextInput.tsx b/src/shared/TextInput.tsx index 239be80..01ec850 100644 --- a/src/shared/TextInput.tsx +++ b/src/shared/TextInput.tsx @@ -1,25 +1,27 @@ import { Component, Show, createMemo } from "solid-js"; const TextInput: Component<{ - label?: string; fieldName: string; + label?: string; helperText?: string; placeholder?: string; isMultiline?: boolean; - class?: string; type?: string; + required?: boolean; }> = (props) => { - const placeholder = createMemo( - () => props.placeholder || "Type something here..." + const placeholder = createMemo(() => + props.placeholder !== undefined + ? props.placeholder + : "Type something here..." ); return ( -
+
diff --git a/src/tailwind.css b/src/tailwind.css index 4b14f7f..455b8f2 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -42,7 +42,8 @@ .btn-primary { @apply btn-base; - @apply bg-purple-600 hover:bg-purple-500 active:bg-purple-400; + @apply bg-purple-600 enabled:hover:bg-purple-500 enabled:active:bg-purple-400; + @apply disabled:cursor-not-allowed disabled:bg-purple-900 disabled:text-white/25; } .btn-secondary { @@ -50,6 +51,16 @@ @apply bg-stone-600 hover:bg-stone-500 active:bg-stone-400; } + /** + * Cards. + */ + .focusable-card { + @apply _focusable-base; + @apply max-h-96 max-w-[10rem]; + @apply rounded-md shadow-md; + @apply bg-background-lighter hover:bg-zinc-900 active:bg-zinc-800; + } + /** * Other form stuff. */ From f4ed039532cd4dd5ec132274df10d3e12195157e Mon Sep 17 00:00:00 2001 From: luis Date: Thu, 9 Feb 2023 15:59:31 -0500 Subject: [PATCH 05/12] Revert "merge upstream and swap window.onload for createEffect in RangeInput component" This reverts commit 7d535333f7ad0c79bc48fb6c9fcefd3cc2a4c389. --- .eslintrc.js | 5 +- README.md | 43 +------ package.json | 6 - pnpm-lock.yaml | 46 -------- src/App.tsx | 30 ----- src/api/characters/list.ts | 15 --- src/api/index.ts | 19 --- src/api/users/login.ts | 31 ----- src/api/utils.ts | 22 ---- src/hooks/auth.tsx | 55 --------- src/index.tsx | 41 ++++--- src/models/Character.tsx | 14 +-- src/models/User.tsx | 7 -- src/pages/Account/index.tsx | 6 + src/pages/Account/login.tsx | 28 +++++ src/pages/Account/register.tsx | 31 +++++ src/pages/CharacterSettings/index.tsx | 8 +- src/pages/Chat/components/InputBar.tsx | 2 +- src/pages/Chat/index.tsx | 35 +++--- src/pages/Chat/mocks.ts | 13 -- src/pages/Home/components/CreateNewCard.tsx | 14 +-- src/pages/Home/index.tsx | 124 +++++++++++++------- src/pages/Login/LoginForm.tsx | 27 ----- src/pages/Login/index.tsx | 87 -------------- src/providers/AppStoreProvider.tsx | 35 ------ src/shared/Alert.tsx | 39 ------ src/shared/Button.tsx | 15 +-- src/shared/CharacterCard.tsx | 26 ++-- src/shared/Divider.tsx | 6 - src/shared/NavBar.tsx | 13 +- src/shared/PageHeader.tsx | 15 --- src/shared/RangeInput.tsx | 7 +- src/shared/RequiresAuth.tsx | 26 ---- src/shared/TextInput.tsx | 20 ++-- src/tailwind.css | 13 +- 35 files changed, 243 insertions(+), 681 deletions(-) delete mode 100644 src/App.tsx delete mode 100644 src/api/characters/list.ts delete mode 100644 src/api/index.ts delete mode 100644 src/api/users/login.ts delete mode 100644 src/api/utils.ts delete mode 100644 src/hooks/auth.tsx delete mode 100644 src/models/User.tsx create mode 100644 src/pages/Account/index.tsx create mode 100644 src/pages/Account/login.tsx create mode 100644 src/pages/Account/register.tsx delete mode 100644 src/pages/Chat/mocks.ts delete mode 100644 src/pages/Login/LoginForm.tsx delete mode 100644 src/pages/Login/index.tsx delete mode 100644 src/providers/AppStoreProvider.tsx delete mode 100644 src/shared/Alert.tsx delete mode 100644 src/shared/Divider.tsx delete mode 100644 src/shared/PageHeader.tsx delete mode 100644 src/shared/RequiresAuth.tsx diff --git a/.eslintrc.js b/.eslintrc.js index d4a7312..b234761 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,7 @@ module.exports = { "airbnb-base", "airbnb-typescript/base", "plugin:solid/typescript", - "plugin:tailwindcss/recommended", + "plugin:tailwindcss/recommended", "prettier", ], overrides: [], @@ -17,8 +17,7 @@ module.exports = { project: "./tsconfig.json", }, rules: { - "@typescript-eslint/explicit-module-boundary-types": "error", "import/extensions": ["error", "never"], - "tailwindcss/no-custom-classname": "off", + "tailwindcss/no-custom-classname": "off", }, }; diff --git a/README.md b/README.md index 1ebc3af..3788338 100644 --- a/README.md +++ b/README.md @@ -2,45 +2,4 @@ The official UI for interacting with the Pygmalion models. -Very early work in progress. - -## Contributing - -If you wish to contribute, this section has some relevant information. - -### Tech stack - -The important parts of the stack are: - -- [SolidJS](https://www.solidjs.com/) for interactivity -- [TailwindCSS](https://tailwindcss.com/) for styling -- [pnpm](https://pnpm.io/) for dependency management - -### Quick start - -If you have Node and `pnpm` installed and working, you can start the development server with: - -```bash -# install dependencies -$ pnpm install - -# start the dev server -$ pnpm start -``` - -By default, it expects the back-end to be running locally at `http://localhost:3000`. If that's not the case, you can override this with the `CORE_API_SERVER` environment variable. - -### Code quality checks - -The project uses ESLint for linting, Prettier for enforcing code style and TypeScript to check for type errors. When opening a PR, please make sure you're not introducing any new errors in any of these checks by running: - -```bash -# auto-fixes any style problems -$ pnpm run style:fix - -# auto-fixes any linting problems, and prints the ones that can't be auto-fixed -$ pnpm run lint:fix - -# runs the TypeScript compiler so any type errors will be shown -$ pnpm run typecheck -``` +Work in progress, not ready for use yet. diff --git a/package.json b/package.json index e148c75..0b4da76 100644 --- a/package.json +++ b/package.json @@ -22,18 +22,12 @@ "license": "AGPL-3.0", "dependencies": { "@solidjs/router": "^0.6.0", - "@tanstack/solid-query": "^4.24.4", - "js-cookie": "^3.0.1", - "lodash": "^4.17.21", "lucide-solid": "0.105.0-alpha.9", "showdown": "^2.1.0", "solid-js": "^1.6.9" }, "devDependencies": { "@babel/core": "^7.20.12", - "@types/js-cookie": "^3.0.2", - "@types/lodash": "^4.14.191", - "@types/node": "^18.13.0", "@types/showdown": "^2.0.0", "@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/parser": "^5.48.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0459bc9..fda75aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,10 +3,6 @@ lockfileVersion: 5.4 specifiers: '@babel/core': ^7.20.12 '@solidjs/router': ^0.6.0 - '@tanstack/solid-query': ^4.24.4 - '@types/js-cookie': ^3.0.2 - '@types/lodash': ^4.14.191 - '@types/node': ^18.13.0 '@types/showdown': ^2.0.0 '@typescript-eslint/eslint-plugin': ^5.48.1 '@typescript-eslint/parser': ^5.48.1 @@ -18,8 +14,6 @@ specifiers: eslint-plugin-import: ^2.27.4 eslint-plugin-solid: ^0.9.3 eslint-plugin-tailwindcss: ^3.8.0 - js-cookie: ^3.0.1 - lodash: ^4.17.21 lucide-solid: 0.105.0-alpha.9 parcel: ^2.8.2 postcss: ^8.4.21 @@ -33,18 +27,12 @@ specifiers: dependencies: '@solidjs/router': 0.6.0_solid-js@1.6.9 - '@tanstack/solid-query': 4.24.4_solid-js@1.6.9 - js-cookie: 3.0.1 - lodash: 4.17.21 lucide-solid: 0.105.0-alpha.9_solid-js@1.6.9 showdown: 2.1.0 solid-js: 1.6.9 devDependencies: '@babel/core': 7.20.12 - '@types/js-cookie': 3.0.2 - '@types/lodash': 4.14.191 - '@types/node': 18.13.0 '@types/showdown': 2.0.0 '@typescript-eslint/eslint-plugin': 5.48.1_oomjohfipuyaxs2zyomcx4f5by '@typescript-eslint/parser': 5.48.1_7uibuqfxkfaozanbtbziikiqje @@ -1185,28 +1173,11 @@ packages: tslib: 2.4.1 dev: true - /@tanstack/query-core/4.24.4: - resolution: {integrity: sha512-9dqjv9eeB6VHN7lD3cLo16ZAjfjCsdXetSAD5+VyKqLUvcKTL0CklGQRJu+bWzdrS69R6Ea4UZo8obHYZnG6aA==} - dev: false - - /@tanstack/solid-query/4.24.4_solid-js@1.6.9: - resolution: {integrity: sha512-aAFNJ3liO0JiBwhAiE/1uwvViz0fokCMwUILUlueHuCZ95LDq+I9Pw9pYjoXLyPoFfp36N398AyV8pALZqbDRg==} - peerDependencies: - solid-js: ^1.5.7 - dependencies: - '@tanstack/query-core': 4.24.4 - solid-js: 1.6.9 - dev: false - /@trysound/sax/0.2.0: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} dev: true - /@types/js-cookie/3.0.2: - resolution: {integrity: sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==} - dev: true - /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -1215,14 +1186,6 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/lodash/4.14.191: - resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} - dev: true - - /@types/node/18.13.0: - resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} - dev: true - /@types/parse-json/4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: true @@ -2680,11 +2643,6 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true - /js-cookie/3.0.1: - resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} - engines: {node: '>=12'} - dev: false - /js-sdsl/4.2.0: resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} dev: true @@ -2881,10 +2839,6 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true - /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false - /lru-cache/5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index aefdeb7..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, lazy } from "solid-js"; - -import { Route, Routes } from "@solidjs/router"; - -import NavBar from "./shared/NavBar"; - -const ChatPage = lazy(() => import("./pages/Chat")); -const CharacterSettings = lazy(() => import("./pages/CharacterSettings")); -const GenerationSettings = lazy(() => import("./pages/GenerationSettings")); -const Home = lazy(() => import("./pages/Home")); -const Login = lazy(() => import("./pages/Login")); - -const App: Component = () => ( -
- -
-
- - - - - - - -
-
-
-); - -export default App; diff --git a/src/api/characters/list.ts b/src/api/characters/list.ts deleted file mode 100644 index 4634ec9..0000000 --- a/src/api/characters/list.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import { BASE_CORE_API_URL } from ".."; -import Character from "../../models/Character"; -import { camelize } from "../utils"; - -/** Fetches available characters. */ -export const fetchCharacters = async (jwt: string): Promise => { - const res = await fetch(`${BASE_CORE_API_URL}/characters`, { - headers: { - Authorization: `Bearer ${jwt}`, - }, - }); - const rawCharacters: Record[] = await res.json(); - return rawCharacters.map((char) => camelize(char)) as unknown as Character[]; -}; diff --git a/src/api/index.ts b/src/api/index.ts deleted file mode 100644 index b2f33c6..0000000 --- a/src/api/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -const CORE_API_SERVER = process.env.CORE_API_SERVER || "http://localhost:3000"; - -/** Base path to the v1 core API. */ -export const BASE_CORE_API_URL = `${CORE_API_SERVER}/api/v1`; - -/** Minimal JWT decoder. Does not validate signature. */ -export const parseJWT = (jwt: string): unknown => { - const base64Url = jwt.split(".")[1]; - const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); - const jsonPayload = decodeURIComponent( - window - .atob(base64) - .split("") - .map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`) - .join("") - ); - - return JSON.parse(jsonPayload); -}; diff --git a/src/api/users/login.ts b/src/api/users/login.ts deleted file mode 100644 index 59ac1f5..0000000 --- a/src/api/users/login.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import { BASE_CORE_API_URL } from ".."; - -interface LoginData { - email: string; - password: string; -} - -interface SuccessfulLoginResponse { - id: string; - email: string; - display_name: string; - jwt: string; -} - -interface FailedLoginResponse { - error: string; - code: number; -} - -type LoginResponse = SuccessfulLoginResponse | FailedLoginResponse; - -/** POSTs `data` to the login endpoint. */ -export const performLogin = async (data: LoginData): Promise => { - const body = JSON.stringify(data); - const res = await fetch(`${BASE_CORE_API_URL}/users/login`, { - method: "POST", - body, - }); - return res.json(); -}; diff --git a/src/api/utils.ts b/src/api/utils.ts deleted file mode 100644 index eb4be77..0000000 --- a/src/api/utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import { camelCase, isArray, transform, isObject } from "lodash"; - -/** Recursively transforms all object keys into camelCase. */ -export const camelize = ( - obj: Record -): Record => - transform( - obj, - ( - result: Record, - value: unknown, - key: string, - target: unknown - ) => { - const camelKey = isArray(target) ? key : camelCase(key); - // eslint-disable-next-line no-param-reassign - result[camelKey] = isObject(value) - ? camelize(value as Record) - : value; - } - ); diff --git a/src/hooks/auth.tsx b/src/hooks/auth.tsx deleted file mode 100644 index 049b339..0000000 --- a/src/hooks/auth.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Cookies from "js-cookie"; -import { parseJWT } from "../api"; -import { useAppStore } from "../providers/AppStoreProvider"; - -/** Hook to interact with authentication data within the global app store. */ -const useAuth = (): { - isAuthenticated: () => boolean; - jwt: () => string | undefined; - login: (jwt: string) => void; - logout: (jwt: string) => void; -} => { - const [appStore, updateAppStore] = useAppStore(); - - /** - * Logs the user in by saving the JWT to the store and cookie jar, and saves a - * decoded version of the JWT for use elsewhere. - */ - const login = (jwt: string) => { - updateAppStore("auth", { jwt, user: parseJWT(jwt) }); - Cookies.set("jwt", jwt, { - sameSite: "strict", - expires: 7, - }); - }; - - /** - * Logs the user out by clearing out the authentication cookie and deleting - * the JWT from the store. - */ - const logout = () => { - updateAppStore("auth", { jwt: undefined, user: undefined }); - Cookies.remove("jwt"); - }; - - /** Returns whether the user is currently authenticated. */ - const isAuthenticated = () => { - if (appStore.auth.jwt) { - return true; - } - - const jwt = Cookies.get("jwt"); - if (!jwt) { - return false; - } - - login(jwt); - return true; - }; - - const jwt = () => appStore.auth.jwt; - - return { isAuthenticated, jwt, login, logout }; -}; - -export default useAuth; diff --git a/src/index.tsx b/src/index.tsx index b24a870..82b43b0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,22 +1,35 @@ -import { Component } from "solid-js"; +// eslint-disable-next-line import/no-extraneous-dependencies import { render } from "solid-js/web"; +import { Component, lazy } from "solid-js"; +import { Router, Routes, Route } from "@solidjs/router"; -import { Router } from "@solidjs/router"; -import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"; +import NavBar from "./shared/NavBar"; -import App from "./App"; -import { AppStoreProvider } from "./providers/AppStoreProvider"; +const ChatPage = lazy(() => import("./pages/Chat")); +const CharacterSettings = lazy(() => import("./pages/CharacterSettings")); +const GenerationSettings = lazy(() => import("./pages/GenerationSettings")); +const Home = lazy(() => import("./pages/Home")); +const Account = lazy(() => import("./pages/Account")); +const Register = lazy(() => import("./pages/Account/register")); -const queryClient = new QueryClient(); - -const AppContainer: Component = () => ( +const App: Component = () => ( - - - - - +
+ +
+
+ + + + + + + + +
+
+
); -render(() => , document.getElementById("root") as HTMLElement); +render(() => , document.getElementById("root") as HTMLElement); diff --git a/src/models/Character.tsx b/src/models/Character.tsx index 1cf57e1..48fe010 100644 --- a/src/models/Character.tsx +++ b/src/models/Character.tsx @@ -1,13 +1,9 @@ interface Character { - id: string; - name: string; + avatarUrl?: string; description: string; - avatarId?: string; - visibility: "public" | "private" | "unlisted"; - - createdAt: string; - updatedAt: string; + greeting: string; + persona: string; + exampleConversations: string; + visibility: "public" | "unlisted" | "private"; } - -export default Character; diff --git a/src/models/User.tsx b/src/models/User.tsx deleted file mode 100644 index cf02538..0000000 --- a/src/models/User.tsx +++ /dev/null @@ -1,7 +0,0 @@ -interface User { - id: string; - email: string; - displayName: string; -} - -export default User; diff --git a/src/pages/Account/index.tsx b/src/pages/Account/index.tsx new file mode 100644 index 0000000..7949ce2 --- /dev/null +++ b/src/pages/Account/index.tsx @@ -0,0 +1,6 @@ +const Account = () => ( + // TODO(11b): This should be the account page where the user can see and edit + // his own info (password, display name, created bots, etc.). +

not implemented yet

+); +export default Account; diff --git a/src/pages/Account/login.tsx b/src/pages/Account/login.tsx new file mode 100644 index 0000000..55bc091 --- /dev/null +++ b/src/pages/Account/login.tsx @@ -0,0 +1,28 @@ +import { A } from "@solidjs/router"; +import Button from "../../shared/Button"; +import TextInput from "../../shared/TextInput"; + +const Account = () => ( +
+
+
+

Log in

+
+ + +
+ + + + +
+
+
+); + +export default Account; diff --git a/src/pages/Account/register.tsx b/src/pages/Account/register.tsx new file mode 100644 index 0000000..10273fb --- /dev/null +++ b/src/pages/Account/register.tsx @@ -0,0 +1,31 @@ +import Button from "../../shared/Button"; +import TextInput from "../../shared/TextInput"; + +const Register = () => ( +
+
+
+

Register

+
+ + + +
+ + +
+
+
+); + +export default Register; diff --git a/src/pages/CharacterSettings/index.tsx b/src/pages/CharacterSettings/index.tsx index b1f1b01..05e4821 100644 --- a/src/pages/CharacterSettings/index.tsx +++ b/src/pages/CharacterSettings/index.tsx @@ -4,7 +4,6 @@ import { Save, X } from "lucide-solid"; import Button from "../../shared/Button"; import TextInput from "../../shared/TextInput"; import RadioGroup, { RadioOption } from "../../shared/RadioGroup"; -import PageHeader from "../../shared/PageHeader"; const visibilityOptions: RadioOption[] = [ { @@ -39,10 +38,9 @@ const visibilityOptions: RadioOption[] = [ const CharacterSettings: Component = () => ( <> - +

Character Settings

+

Configure BOT's character.

+
= (props) => ( ); /** Bar containing the message text input and some attached buttons. */ -const InputBar: Component = () => ( +const InputBar = () => (
( - -
- -
-
- {(message) => } -
-
+const ChatPage = () => ( +
+ +
+
+ {(message) => }
+
- +
); export default ChatPage; diff --git a/src/pages/Chat/mocks.ts b/src/pages/Chat/mocks.ts deleted file mode 100644 index c8bb83b..0000000 --- a/src/pages/Chat/mocks.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Message from "../../models/Message"; - -export const mockMessages: Message[] = [ - { - speaker: { - name: "John", - avatarUrl: "#", - isHuman: true, - }, - utterance: "Hi Robot", - timestamp: new Date(), - }, -]; diff --git a/src/pages/Home/components/CreateNewCard.tsx b/src/pages/Home/components/CreateNewCard.tsx index 6f2d647..3c1f7c1 100644 --- a/src/pages/Home/components/CreateNewCard.tsx +++ b/src/pages/Home/components/CreateNewCard.tsx @@ -2,12 +2,12 @@ import { A } from "@solidjs/router"; import { Plus } from "lucide-solid"; import { Component } from "solid-js"; -const CreateNewCard: Component = () => ( - -
- -
-
-); +const CreateNewCard: Component = (props) => ( + +
+ +
+
+ ); export default CreateNewCard; diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 6fcac82..54ace4f 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,52 +1,90 @@ -import { Component, For, Suspense } from "solid-js"; +import { createSignal, For, onMount } from "solid-js"; +import CharacterCard from "../../shared/CharacterCard"; +import CreateNewCard from "./components/CreateNewCard"; -import { createQuery } from "@tanstack/solid-query"; +const HomePage = () => { + const testTemplate = [ + { + name: "Ibuki-chan", + avatarUrl: "", + description: "", + }, + { + name: "Ibuki-chan", + avatarUrl: "", + description: "", + }, + { + name: "Ibuki-chan", + avatarUrl: "", + description: "", + }, + { + name: "Ibuki-chan", + avatarUrl: "", + description: "", + }, + { + name: "Ibuki-chan", + avatarUrl: "", + description: "", + }, + ] as Character[]; -import { fetchCharacters } from "../../api/characters/list"; -import Character from "../../models/Character"; -import CharacterCard from "../../shared/CharacterCard"; -import PageHeader from "../../shared/PageHeader"; -import useAuth from "../../hooks/auth"; -import RequiresAuth from "../../shared/RequiresAuth"; + const [recentlyChatedCharacters, setRecentlyChatedCharacters] = + createSignal(testTemplate); + const [recommendedCharacters, setRecommendedCharacters] = + createSignal(testTemplate); + const [popularCharacters, setPopularCharacters] = createSignal(testTemplate); + const [recentlyCreatedCharacters, setRecentlyCreatedCharacters] = + createSignal(testTemplate); -const CharacterGroup: Component<{ - title: string; - description: string; - characters: Character[]; -}> = (props) => ( - <> - + return ( + <> +

Recently Chatted Characters

+

Characters that you recently chatted.

+
+
+ + {(e: Character) => ( + + )} + +
-
- - {(character: Character) => ( - - )} - -
- -); +

Recommended Characters

+

Characters that are recommended for you

+
+
+ + {(e: Character) => ( + + )} + +
-const HomePage: Component = () => { - const { jwt } = useAuth(); - const query = createQuery( - () => ["characters"], - () => fetchCharacters(jwt()!) - ); +

Popular characters

+

Characters that are popular in the community.

+
+
+ + {(e: Character) => ( + + )} + +
- return ( - - - - - +

Recently Created Characters

+

Characters that are made recently.

+
+
+ + {(e: Character) => ( + + )} + +
+ ); }; export default HomePage; diff --git a/src/pages/Login/LoginForm.tsx b/src/pages/Login/LoginForm.tsx deleted file mode 100644 index 0890686..0000000 --- a/src/pages/Login/LoginForm.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Component } from "solid-js"; - -import Button from "../../shared/Button"; -import TextInput from "../../shared/TextInput"; - -const LoginForm: Component<{ - isLoading: boolean; - onSubmit: (evt: Event) => void; -}> = (props) => ( -
props.onSubmit(evt)} class="flex flex-col gap-6"> -
- - -
- - -
-); - -export default LoginForm; diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx deleted file mode 100644 index bcd300e..0000000 --- a/src/pages/Login/index.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Component, createEffect, createMemo, Show } from "solid-js"; - -import { useLocation, useNavigate } from "@solidjs/router"; -import { createMutation } from "@tanstack/solid-query"; - -import { performLogin } from "../../api/users/login"; -import useAuth from "../../hooks/auth"; -import Alert from "../../shared/Alert"; -import Divider from "../../shared/Divider"; -import PageHeader from "../../shared/PageHeader"; -import LoginForm from "./LoginForm"; - -const LoginPage: Component = () => { - const { login } = useAuth(); - const { state } = useLocation(); - const navigate = useNavigate(); - const mutation = createMutation(performLogin); - - /** Friendly error message passed out of the mutation, if it exists. */ - const loginError = createMemo(() => { - if (mutation.data) { - if ("error" in mutation.data) { - return mutation.data.error; - } - } - - if (mutation.error) { - const err = mutation.error as Error; - if (err.message.includes("NetworkError")) { - return "We couldn't reach our servers."; - } - return "Something went wrong."; - } - - return null; - }); - - /** Form submission callback to handle POSTing to the back-end. */ - const onSubmit = (evt: Event) => { - evt.preventDefault(); - if (!evt.target) { - return; - } - - const form = new FormData(evt.target as HTMLFormElement); - mutation.mutate({ - email: form.get("email")?.valueOf() as string, - password: form.get("password")?.valueOf() as string, - }); - }; - - /** Side-effect to take care of a successful login. */ - createEffect(() => { - if (!mutation.data || !("jwt" in mutation.data)) return; - - login(mutation.data.jwt); - - let redirectTo = "/"; - if (state && "redirectTo" in state) { - redirectTo = state.redirectTo as string; - } - navigate(redirectTo, { replace: true }); - }); - - return ( -
-
-
- - - - - - - - {loginError()} - - -
-
- ); -}; - -export default LoginPage; diff --git a/src/providers/AppStoreProvider.tsx b/src/providers/AppStoreProvider.tsx deleted file mode 100644 index dbe6648..0000000 --- a/src/providers/AppStoreProvider.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Component, createContext, JSX, useContext } from "solid-js"; -import { createStore, SetStoreFunction } from "solid-js/store"; - -import User from "../models/User"; - -interface AppStore { - // TODO(11b): Type up proper domain models and use them here. - auth: { jwt?: string; user?: User }; -} - -const defaultStore = { - auth: { - jwt: undefined, - user: undefined, - }, -}; - -const AppStoreContext = createContext(); -type AppStoreContextValue = [AppStore, SetStoreFunction]; - -/** Provides a SolidJS store for storing global data. */ -export const AppStoreProvider: Component<{ - children: JSX.Element; -}> = (props) => { - const [appStore, updateAppStore] = createStore(defaultStore); - - return ( - - {props.children} - - ); -}; - -export const useAppStore = (): AppStoreContextValue => - useContext(AppStoreContext)!; diff --git a/src/shared/Alert.tsx b/src/shared/Alert.tsx deleted file mode 100644 index 332a34f..0000000 --- a/src/shared/Alert.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { AlertTriangle } from "lucide-solid"; -import { LucideProps } from "lucide-solid/dist/types/types"; -import { Component, JSX, createMemo } from "solid-js"; - -type AlertSchema = "error"; - -const schemaToClasses: Record = { - error: "bg-red-500/10 text-red-400", -}; - -const schemaToIcon: Record JSX.Element> = { - error: AlertTriangle, -}; - -const Alert: Component<{ - title: JSX.Element; - children: JSX.Element; - schema: AlertSchema; -}> = (props) => { - const classes = createMemo(() => - [schemaToClasses[props.schema], "rounded-lg p-4 text-sm flex gap-2"].join( - " " - ) - ); - - return ( - - ); -}; - -export default Alert; diff --git a/src/shared/Button.tsx b/src/shared/Button.tsx index 5b07c37..d13168a 100644 --- a/src/shared/Button.tsx +++ b/src/shared/Button.tsx @@ -7,17 +7,10 @@ const schemaNameToClass: Record = { secondary: "btn-secondary", }; -const Button: Component<{ - children: JSX.Element; - schema?: ButtonSchema; - type?: "submit" | "reset" | "button"; - disabled?: boolean; -}> = (props) => ( - ); diff --git a/src/shared/CharacterCard.tsx b/src/shared/CharacterCard.tsx index 6dbc9f4..620df5e 100644 --- a/src/shared/CharacterCard.tsx +++ b/src/shared/CharacterCard.tsx @@ -1,25 +1,17 @@ -import { Component } from "solid-js"; +import { Component, Show } from "solid-js"; -import { A } from "@solidjs/router"; - -import Character from "../models/Character"; - -const CharacterCard: Component<{ character: Character; href: string }> = ( +const CharacterCard: Component<{ displayName: string; avatarUrl?: string }> = ( props ) => ( -
-
-
- -
- {props.character.name} -

{props.character.description}

+
+ + {props.displayName} +
-
-); + ); export default CharacterCard; diff --git a/src/shared/Divider.tsx b/src/shared/Divider.tsx deleted file mode 100644 index 0a0880e..0000000 --- a/src/shared/Divider.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Component } from "solid-js"; - -/** A subtle horizontal divider line. */ -const Divider: Component = () =>
; - -export default Divider; diff --git a/src/shared/NavBar.tsx b/src/shared/NavBar.tsx index a8f00e7..b881b99 100644 --- a/src/shared/NavBar.tsx +++ b/src/shared/NavBar.tsx @@ -1,7 +1,14 @@ -import { MessageCircle, Settings, User, Users } from "lucide-solid"; -import { Component } from "solid-js"; - +import { Component, JSX } from "solid-js"; import { A } from "@solidjs/router"; +import { + ChevronLeft, + Home, + Menu, + MessageCircle, + Settings, + Users, + User, +} from "lucide-solid"; const NavBar: Component = () => ( diff --git a/src/shared/PageHeader.tsx b/src/shared/PageHeader.tsx deleted file mode 100644 index 332bc3b..0000000 --- a/src/shared/PageHeader.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, Show } from "solid-js"; - -import Divider from "./Divider"; - -const PageHeader: Component<{ title: string; subtitle?: string }> = (props) => ( - <> -

{props.title}

- -

{props.subtitle}

-
- - -); - -export default PageHeader; diff --git a/src/shared/RangeInput.tsx b/src/shared/RangeInput.tsx index 1282571..312720a 100644 --- a/src/shared/RangeInput.tsx +++ b/src/shared/RangeInput.tsx @@ -1,6 +1,5 @@ -import { Component, Show, createSignal, createEffect } from "solid-js"; +import { Component, Show, createSignal } from "solid-js"; import type { JSX } from "solid-js"; -import { update } from "lodash"; const RangeInput: Component<{ label: string; @@ -23,8 +22,8 @@ const RangeInput: Component<{ setValue(Number(event.currentTarget.value)); updateRangeSliders(); }; - - createEffect(updateRangeSliders); + + window.onload = updateRangeSliders; return (
diff --git a/src/shared/RequiresAuth.tsx b/src/shared/RequiresAuth.tsx deleted file mode 100644 index ac79319..0000000 --- a/src/shared/RequiresAuth.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, createEffect, JSX, Show } from "solid-js"; - -import { useLocation, useNavigate } from "@solidjs/router"; - -import useAuth from "../hooks/auth"; - -const RequiresAuth: Component<{ children: JSX.Element }> = (props) => { - const { isAuthenticated } = useAuth(); - const location = useLocation(); - const navigate = useNavigate(); - - createEffect(() => { - if (isAuthenticated()) { - return; - } - - navigate("/account/login", { - replace: true, - state: { redirectTo: location.pathname }, - }); - }); - - return {props.children}; -}; - -export default RequiresAuth; diff --git a/src/shared/TextInput.tsx b/src/shared/TextInput.tsx index 01ec850..239be80 100644 --- a/src/shared/TextInput.tsx +++ b/src/shared/TextInput.tsx @@ -1,27 +1,25 @@ import { Component, Show, createMemo } from "solid-js"; const TextInput: Component<{ - fieldName: string; label?: string; + fieldName: string; helperText?: string; placeholder?: string; isMultiline?: boolean; + class?: string; type?: string; - required?: boolean; }> = (props) => { - const placeholder = createMemo(() => - props.placeholder !== undefined - ? props.placeholder - : "Type something here..." + const placeholder = createMemo( + () => props.placeholder || "Type something here..." ); return ( -
+
diff --git a/src/tailwind.css b/src/tailwind.css index 455b8f2..4b14f7f 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -42,8 +42,7 @@ .btn-primary { @apply btn-base; - @apply bg-purple-600 enabled:hover:bg-purple-500 enabled:active:bg-purple-400; - @apply disabled:cursor-not-allowed disabled:bg-purple-900 disabled:text-white/25; + @apply bg-purple-600 hover:bg-purple-500 active:bg-purple-400; } .btn-secondary { @@ -51,16 +50,6 @@ @apply bg-stone-600 hover:bg-stone-500 active:bg-stone-400; } - /** - * Cards. - */ - .focusable-card { - @apply _focusable-base; - @apply max-h-96 max-w-[10rem]; - @apply rounded-md shadow-md; - @apply bg-background-lighter hover:bg-zinc-900 active:bg-zinc-800; - } - /** * Other form stuff. */ From 8c4b29e75a12b40acd6525f36e3683d831e44c28 Mon Sep 17 00:00:00 2001 From: luis Date: Thu, 9 Feb 2023 16:03:20 -0500 Subject: [PATCH 06/12] swap window.onload for createEffect in RangeInput component --- pnpm-lock.yaml | 6 ++++-- src/App.tsx | 2 ++ src/shared/RangeInput.tsx | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8db958..0459bc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,7 +61,7 @@ devDependencies: prettier: 2.8.3 prettier-plugin-tailwindcss: 0.2.1_prettier@2.8.3 solid-refresh: 0.4.2_solid-js@1.6.9 - tailwindcss: 3.2.4 + tailwindcss: 3.2.4_postcss@8.4.21 typescript: 4.9.4 packages: @@ -3552,10 +3552,12 @@ packages: stable: 0.1.8 dev: true - /tailwindcss/3.2.4: + /tailwindcss/3.2.4_postcss@8.4.21: resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==} engines: {node: '>=12.13.0'} hasBin: true + peerDependencies: + postcss: ^8.0.9 dependencies: arg: 5.0.2 chokidar: 3.5.3 diff --git a/src/App.tsx b/src/App.tsx index a1625db..aefdeb7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import NavBar from "./shared/NavBar"; const ChatPage = lazy(() => import("./pages/Chat")); const CharacterSettings = lazy(() => import("./pages/CharacterSettings")); +const GenerationSettings = lazy(() => import("./pages/GenerationSettings")); const Home = lazy(() => import("./pages/Home")); const Login = lazy(() => import("./pages/Login")); @@ -17,6 +18,7 @@ const App: Component = () => ( + diff --git a/src/shared/RangeInput.tsx b/src/shared/RangeInput.tsx index 312720a..15c782f 100644 --- a/src/shared/RangeInput.tsx +++ b/src/shared/RangeInput.tsx @@ -1,4 +1,4 @@ -import { Component, Show, createSignal } from "solid-js"; +import { Component, Show, createSignal, createEffect } from "solid-js"; import type { JSX } from "solid-js"; const RangeInput: Component<{ @@ -23,7 +23,7 @@ const RangeInput: Component<{ updateRangeSliders(); }; - window.onload = updateRangeSliders; + createEffect(updateRangeSliders); return (
From 469fd7cd0d0eb0e48fa1dd2a936e89e6de382bab Mon Sep 17 00:00:00 2001 From: luis Date: Sat, 11 Feb 2023 16:34:39 -0500 Subject: [PATCH 07/12] merge upstream, add DropdownItem component --- src/pages/GenerationSettings/index.tsx | 10 ++++++++++ src/shared/Button.tsx | 2 ++ src/shared/Dropdown.tsx | 21 +++++++++++++++++++++ src/shared/DropdownItem.tsx | 13 +++++++++++++ src/tailwind.css | 1 + 5 files changed, 47 insertions(+) create mode 100644 src/shared/Dropdown.tsx create mode 100644 src/shared/DropdownItem.tsx diff --git a/src/pages/GenerationSettings/index.tsx b/src/pages/GenerationSettings/index.tsx index 0ff5632..b8ee25c 100644 --- a/src/pages/GenerationSettings/index.tsx +++ b/src/pages/GenerationSettings/index.tsx @@ -3,6 +3,8 @@ import { Save, X } from "lucide-solid"; import Button from "../../shared/Button"; import RangeInput from "../../shared/RangeInput"; +import Dropdown from "../../shared/Dropdown"; +import DropdownItem from "../../shared/DropdownItem"; const GenerationSettings: Component = () => ( <> @@ -10,6 +12,14 @@ const GenerationSettings: Component = () => (

Some settings might not show up depending on which inference backend is being used.

+ + Classic-Pygmalion-6b + Calibrated-Pygmalion-6b + GPU-Pygmalion-6b + DragonSlayer-Pygmalion-6b + Classic-Pygmalion-2.7b + +
= { const Button: Component<{ children: JSX.Element; + onClick?: JSX.EventHandler; schema?: ButtonSchema; type?: "submit" | "reset" | "button"; disabled?: boolean; @@ -17,6 +18,7 @@ const Button: Component<{ type={props.type || "button"} class={`${schemaNameToClass[props.schema || "primary"]} justify-center`} disabled={props.disabled} + onClick={props.onClick} > {props.children} diff --git a/src/shared/Dropdown.tsx b/src/shared/Dropdown.tsx new file mode 100644 index 0000000..c8b5414 --- /dev/null +++ b/src/shared/Dropdown.tsx @@ -0,0 +1,21 @@ +import { Component, JSX, Show, createSignal } from "solid-js"; + +import Button from "./Button"; + +import { ChevronDown } from "lucide-solid"; + +const Dropdown: Component<{ children: JSX.Element, label: string }> = (props) => { + const [open, setOpen] = createSignal(false); + + return( +
+ +
{props.children}
+
+ ); +}; + +export default Dropdown; \ No newline at end of file diff --git a/src/shared/DropdownItem.tsx b/src/shared/DropdownItem.tsx new file mode 100644 index 0000000..1121e64 --- /dev/null +++ b/src/shared/DropdownItem.tsx @@ -0,0 +1,13 @@ +import { Component, JSX, createSignal } from "solid-js"; + +const DropdownItem: Component<{ + children: JSX.Element; + reference?: any; + onClick?: JSX.EventHandler; + }> = (props) => { + return ( + {props.children} + ); +}; + +export default DropdownItem; diff --git a/src/tailwind.css b/src/tailwind.css index 455b8f2..e8bd77e 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -87,4 +87,5 @@ border: none; background: transparent; } + } From 7f6d27e2e8cd37acca94e8e7d6fae6c466abbb56 Mon Sep 17 00:00:00 2001 From: luis Date: Sat, 11 Feb 2023 16:39:52 -0500 Subject: [PATCH 08/12] style fix --- src/shared/Dropdown.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/shared/Dropdown.tsx b/src/shared/Dropdown.tsx index c8b5414..3fdcb63 100644 --- a/src/shared/Dropdown.tsx +++ b/src/shared/Dropdown.tsx @@ -1,15 +1,21 @@ import { Component, JSX, Show, createSignal } from "solid-js"; +import { ChevronDown } from "lucide-solid"; import Button from "./Button"; -import { ChevronDown } from "lucide-solid"; -const Dropdown: Component<{ children: JSX.Element, label: string }> = (props) => { +const Dropdown: Component<{ children: JSX.Element; label: string }> = ( + props +) => { const [open, setOpen] = createSignal(false); - return( + return (
- @@ -18,4 +24,4 @@ const Dropdown: Component<{ children: JSX.Element, label: string }> = (props) => ); }; -export default Dropdown; \ No newline at end of file +export default Dropdown; From b065e5c1b59fb3bbb26a8025e4da1c70705d26d5 Mon Sep 17 00:00:00 2001 From: luis Date: Sat, 18 Feb 2023 20:03:14 -0500 Subject: [PATCH 09/12] replace dropdown-menu div with popper --- package.json | 4 +++- pnpm-lock.yaml | 19 +++++++++++++++++++ src/shared/Dropdown.tsx | 37 ++++++++++++++++++++++++------------- src/shared/DropdownItem.tsx | 2 +- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index e148c75..acea79e 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,15 @@ }, "license": "AGPL-3.0", "dependencies": { + "@popperjs/core": "^2.11.6", "@solidjs/router": "^0.6.0", "@tanstack/solid-query": "^4.24.4", "js-cookie": "^3.0.1", "lodash": "^4.17.21", "lucide-solid": "0.105.0-alpha.9", "showdown": "^2.1.0", - "solid-js": "^1.6.9" + "solid-js": "^1.6.9", + "solid-popper": "^0.3.0" }, "devDependencies": { "@babel/core": "^7.20.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0459bc9..ae56dab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,6 +2,7 @@ lockfileVersion: 5.4 specifiers: '@babel/core': ^7.20.12 + '@popperjs/core': ^2.11.6 '@solidjs/router': ^0.6.0 '@tanstack/solid-query': ^4.24.4 '@types/js-cookie': ^3.0.2 @@ -27,11 +28,13 @@ specifiers: prettier-plugin-tailwindcss: ^0.2.1 showdown: ^2.1.0 solid-js: ^1.6.9 + solid-popper: ^0.3.0 solid-refresh: ^0.4.2 tailwindcss: ^3.2.4 typescript: ^4.9.4 dependencies: + '@popperjs/core': 2.11.6 '@solidjs/router': 0.6.0_solid-js@1.6.9 '@tanstack/solid-query': 4.24.4_solid-js@1.6.9 js-cookie: 3.0.1 @@ -39,6 +42,7 @@ dependencies: lucide-solid: 0.105.0-alpha.9_solid-js@1.6.9 showdown: 2.1.0 solid-js: 1.6.9 + solid-popper: 0.3.0_4d4c2ryvs4cd2topoga5pasuua devDependencies: '@babel/core': 7.20.12 @@ -1171,6 +1175,10 @@ packages: nullthrows: 1.1.1 dev: true + /@popperjs/core/2.11.6: + resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} + dev: false + /@solidjs/router/0.6.0_solid-js@1.6.9: resolution: {integrity: sha512-7ug2fzXXhvvDBL4CQyMvMM9o3dgBE6PoRh38T8UTmMnYz4rcCfROqSZc9yq+YEC96qWt5OvJgZ1Uj/4EAQXlfA==} peerDependencies: @@ -3447,6 +3455,17 @@ packages: dependencies: csstype: 3.1.1 + /solid-popper/0.3.0_4d4c2ryvs4cd2topoga5pasuua: + resolution: {integrity: sha512-XlfEWAyxGGqFgg/uRpF+BemSfCqjbLA8p6fToDa+6v3paw3eBQj0TU08aBOIj2VeigaEiz8ZTlDx1eBLVRivBg==} + engines: {node: '>=10'} + peerDependencies: + '@popperjs/core': ^2.11 + solid-js: ^1.2 + dependencies: + '@popperjs/core': 2.11.6 + solid-js: 1.6.9 + dev: false + /solid-refresh/0.4.2_solid-js@1.6.9: resolution: {integrity: sha512-6g1HsgQkY0X0ZmsaydNgHwRaQIhH3bAbagZiYwWnGO7mqli50ehlwQUN18RZ2MH3fTIs9Y1bankZapVhMVuijg==} peerDependencies: diff --git a/src/shared/Dropdown.tsx b/src/shared/Dropdown.tsx index 3fdcb63..e465df4 100644 --- a/src/shared/Dropdown.tsx +++ b/src/shared/Dropdown.tsx @@ -1,26 +1,37 @@ -import { Component, JSX, Show, createSignal } from "solid-js"; - import { ChevronDown } from "lucide-solid"; +import { Component, JSX, createSignal } from "solid-js"; +import usePopper from 'solid-popper'; + import Button from "./Button"; const Dropdown: Component<{ children: JSX.Element; label: string }> = ( props ) => { + const [open, setOpen] = createSignal(false); + const [dropdown, setDropdown] = createSignal(); + const [dropdownMenu, setDropdownMenu] = createSignal(); + + usePopper(dropdown, dropdownMenu, { + placement: 'bottom-start', + }); return ( -
- -
{props.children}
-
+ <> +
+ +
+ +
{props.children}
+ ); }; diff --git a/src/shared/DropdownItem.tsx b/src/shared/DropdownItem.tsx index 1121e64..582a787 100644 --- a/src/shared/DropdownItem.tsx +++ b/src/shared/DropdownItem.tsx @@ -1,4 +1,4 @@ -import { Component, JSX, createSignal } from "solid-js"; +import { Component, JSX } from "solid-js"; const DropdownItem: Component<{ children: JSX.Element; From 9e4c193fb36a759c951dd3246602f0d13ed32558 Mon Sep 17 00:00:00 2001 From: luis Date: Sat, 18 Feb 2023 20:04:46 -0500 Subject: [PATCH 10/12] run style fix --- src/pages/GenerationSettings/index.tsx | 149 +++++++++++++------------ src/shared/Dropdown.tsx | 37 +++--- src/shared/DropdownItem.tsx | 18 +-- src/shared/RangeInput.tsx | 32 ++++-- src/tailwind.css | 1 - 5 files changed, 128 insertions(+), 109 deletions(-) diff --git a/src/pages/GenerationSettings/index.tsx b/src/pages/GenerationSettings/index.tsx index b8ee25c..e41659b 100644 --- a/src/pages/GenerationSettings/index.tsx +++ b/src/pages/GenerationSettings/index.tsx @@ -9,88 +9,91 @@ import DropdownItem from "../../shared/DropdownItem"; const GenerationSettings: Component = () => ( <>

Generation Settings Settings

-

Some settings might not show up depending on which inference backend is being used.

+

+ Some settings might not show up depending on which inference backend is + being used. +

- Classic-Pygmalion-6b - Calibrated-Pygmalion-6b - GPU-Pygmalion-6b - DragonSlayer-Pygmalion-6b - Classic-Pygmalion-2.7b + Classic-Pygmalion-6b + Calibrated-Pygmalion-6b + GPU-Pygmalion-6b + DragonSlayer-Pygmalion-6b + Classic-Pygmalion-2.7b
- - - - - - - + + + + + + + -
- +
+ - -
+ +
); -export default GenerationSettings; \ No newline at end of file +export default GenerationSettings; diff --git a/src/shared/Dropdown.tsx b/src/shared/Dropdown.tsx index e465df4..61dc727 100644 --- a/src/shared/Dropdown.tsx +++ b/src/shared/Dropdown.tsx @@ -1,36 +1,41 @@ import { ChevronDown } from "lucide-solid"; import { Component, JSX, createSignal } from "solid-js"; -import usePopper from 'solid-popper'; +import usePopper from "solid-popper"; import Button from "./Button"; - const Dropdown: Component<{ children: JSX.Element; label: string }> = ( props ) => { - const [open, setOpen] = createSignal(false); const [dropdown, setDropdown] = createSignal(); const [dropdownMenu, setDropdownMenu] = createSignal(); usePopper(dropdown, dropdownMenu, { - placement: 'bottom-start', + placement: "bottom-start", }); return ( <> -
- -
- -
{props.children}
+
+ +
+ +
+ {props.children} +
); }; diff --git a/src/shared/DropdownItem.tsx b/src/shared/DropdownItem.tsx index 582a787..b35bd5b 100644 --- a/src/shared/DropdownItem.tsx +++ b/src/shared/DropdownItem.tsx @@ -1,13 +1,17 @@ import { Component, JSX } from "solid-js"; const DropdownItem: Component<{ - children: JSX.Element; - reference?: any; - onClick?: JSX.EventHandler; - }> = (props) => { - return ( - {props.children} + children: JSX.Element; + reference?: any; + onClick?: JSX.EventHandler; +}> = (props) => ( + + {props.children} + ); -}; export default DropdownItem; diff --git a/src/shared/RangeInput.tsx b/src/shared/RangeInput.tsx index 15c782f..e3b9e93 100644 --- a/src/shared/RangeInput.tsx +++ b/src/shared/RangeInput.tsx @@ -9,29 +9,37 @@ const RangeInput: Component<{ max: number; step: number; }> = (props) => { - const [value, setValue] = createSignal(props.value); - + function updateRangeSliders() { - Array.from(document.getElementsByTagName('input')).forEach(input => { - input.style.backgroundSize = (Number(input.value) - Number(input.min)) * 100 / (Number(input.max) - Number(input.min)) + '% 100%'; + Array.from(document.getElementsByTagName("input")).forEach((input) => { + input.style.backgroundSize = + `${((Number(input.value) - Number(input.min)) * 100) / + (Number(input.max) - Number(input.min)) + }% 100%`; }); } const onInput: JSX.EventHandler = (event) => { setValue(Number(event.currentTarget.value)); updateRangeSliders(); - }; + }; createEffect(updateRangeSliders); return (
    - - + +

@@ -42,12 +50,12 @@ const RangeInput: Component<{ type="range" class=" form-range - appearance-none - accent-purple-400/50 - cursor-ew-resize h-1 w-full + cursor-ew-resize + appearance-none rounded-xl + accent-purple-400/50 focus:shadow-none focus:outline-none focus:ring-0 " min={props.min} diff --git a/src/tailwind.css b/src/tailwind.css index e8bd77e..455b8f2 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -87,5 +87,4 @@ border: none; background: transparent; } - } From bb4ba7e3cdbeaaccb88c2f84f500a6bbc471df32 Mon Sep 17 00:00:00 2001 From: luis Date: Sun, 19 Feb 2023 16:01:23 -0500 Subject: [PATCH 11/12] add another border in gen settings and make dropdown component hide when click outside of it --- src/pages/GenerationSettings/index.tsx | 3 ++- src/shared/Dropdown.tsx | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pages/GenerationSettings/index.tsx b/src/pages/GenerationSettings/index.tsx index e41659b..2146153 100644 --- a/src/pages/GenerationSettings/index.tsx +++ b/src/pages/GenerationSettings/index.tsx @@ -13,8 +13,8 @@ const GenerationSettings: Component = () => ( Some settings might not show up depending on which inference backend is being used.

+
- Classic-Pygmalion-6b Calibrated-Pygmalion-6b @@ -22,6 +22,7 @@ const GenerationSettings: Component = () => ( DragonSlayer-Pygmalion-6b Classic-Pygmalion-2.7b +
= ( placement: "bottom-start", }); + window.onclick = function(e) { + const dropdownButton = (dropdown() as unknown as HTMLElement).children[0]; + const dropdownButtonChevron = (dropdown() as unknown as HTMLElement).children[0].children[0]; + + if(open() && e.target !== dropdownButton && e.target !== dropdownButtonChevron) { + setOpen(!open()) + } + } + return ( <> -
+