From a3da33d8244066adecf4276b38a16f41c2d19674 Mon Sep 17 00:00:00 2001
From: ZauberNerd <zaubernerd@zaubernerd.de>
Date: Fri, 7 Jul 2023 13:36:32 +0200
Subject: [PATCH 1/7] Replace create-react-app with vite

create-react-app seems to struggle a bit [1] and many folks are
recommending vite as an alternative, as that project is more active.
For me the reason to switch to vite is to reduce dependencies and speed
up the build process.

Replacing create-react-app with vite reduce the number of dependencies
from 1762 to 444.

I was also quite surprised, by how small the actual diff in this commit
is. From that perspective there is almost no discernible difference
between the two tools.

[1]: https://github.com/reactjs/react.dev/pull/5487#issuecomment-1409720741
---
 Dockerfile                        |  3 +-
 app/craco.config.js               | 43 --------------------------
 app/{public => }/index.html       | 12 ++++----
 app/package.json                  | 33 +++++++-------------
 app/public/lang/en-us.json        | 50 +++++++++----------------------
 app/src/core/tokenizer/wrapper.ts | 29 +++++++-----------
 app/src/{index.scss => index.css} |  0
 app/src/index.tsx                 |  2 +-
 app/src/react-app-env.d.ts        |  1 -
 app/src/vite-env.d.ts             |  1 +
 app/vite.config.js                | 35 ++++++++++++++++++++++
 11 files changed, 79 insertions(+), 130 deletions(-)
 delete mode 100644 app/craco.config.js
 rename app/{public => }/index.html (92%)
 rename app/src/{index.scss => index.css} (100%)
 delete mode 100644 app/src/react-app-env.d.ts
 create mode 100644 app/src/vite-env.d.ts
 create mode 100644 app/vite.config.js

diff --git a/Dockerfile b/Dockerfile
index dc071ea..de9cb55 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,8 +14,7 @@ COPY ./app/tsconfig.json ./
 # Install Node.js dependencies
 RUN npm install
 
-# Copy craco.config.js, public, and src directories
-COPY ./app/craco.config.js ./craco.config.js
+# Copy public, and src directories
 COPY ./app/public ./public
 COPY ./app/src ./src
 
diff --git a/app/craco.config.js b/app/craco.config.js
deleted file mode 100644
index 2e6c13c..0000000
--- a/app/craco.config.js
+++ /dev/null
@@ -1,43 +0,0 @@
-const cracoWasm = require("craco-wasm");
-const webpack = require("webpack");
-const path = require("path");
-
-module.exports = {
-  plugins: [
-    cracoWasm(),
-  ],
-  eslint: {
-    enable: false
-  },
-  babel: {
-    plugins: [
-      [
-        'formatjs',
-        {
-          removeDefaultMessage: false,
-          idInterpolationPattern: '[sha512:contenthash:base64:6]',
-          ast: true
-        }
-      ]
-    ]
-  },
-  webpack: {
-    configure: {
-      resolve: {
-        fallback: {
-          buffer: require.resolve("buffer"),
-        },
-        alias: {
-          '@ffmpeg/ffmpeg': path.resolve(__dirname, 'src/stub.js')
-        },
-      },
-      plugins: [
-        new webpack.ProvidePlugin({
-          Buffer: ["buffer", "Buffer"],
-        }),
-      ],
-      ignoreWarnings: [/Failed to parse source map/],
-      cache: false,
-    },
-  },
-}
\ No newline at end of file
diff --git a/app/public/index.html b/app/index.html
similarity index 92%
rename from app/public/index.html
rename to app/index.html
index bcbbbba..ec20b03 100644
--- a/app/public/index.html
+++ b/app/index.html
@@ -3,23 +3,23 @@
 
 <head>
   <meta charset="utf-8" />
-  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+  <link rel="icon" href="/favicon.ico" />
   <!-- mobile app viewport -->
   <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
   <meta name="theme-color" content="#000000" />
   <meta name="description" content="An open source ChatGPT app with a voice." />
-  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+  <link rel="apple-touch-icon" href="/logo192.png" />
   <!--
       manifest.json provides metadata used when your web app is installed on a
       user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
     -->
-  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+  <link rel="manifest" href="/manifest.json" />
   <!--
-      Notice the use of %PUBLIC_URL% in the tags above.
+      Notice the use of  in the tags above.
       It will be replaced with the URL of the `public` folder during the build.
       Only files inside the `public` folder can be referenced from the HTML.
 
-      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
       work correctly both with client-side routing and a non-root public URL.
       Learn how to configure a non-root public URL by running `npm run build`.
     -->
@@ -86,6 +86,8 @@
       To begin the development, run `npm start` or `yarn start`.
       To create a production bundle, use `npm run build` or `yarn build`.
     -->
+
+    <script type="module" src="/src/index.tsx"></script>
 </body>
 
 </html>
\ No newline at end of file
diff --git a/app/package.json b/app/package.json
index c279503..66721a1 100644
--- a/app/package.json
+++ b/app/package.json
@@ -3,7 +3,6 @@
   "version": "0.2.3",
   "dependencies": {
     "@chengsokdara/use-whisper": "^0.2.0",
-    "@emotion/css": "^11.10.6",
     "@emotion/react": "^11.10.6",
     "@emotion/styled": "^11.10.6",
     "@mantine/core": "^5.10.5",
@@ -13,9 +12,9 @@
     "@mantine/spotlight": "^5.10.5",
     "@msgpack/msgpack": "^3.0.0-beta2",
     "@reduxjs/toolkit": "^1.9.3",
-    "@svgr/webpack": "^6.5.1",
     "broadcast-channel": "^4.20.2",
     "buffer": "^6.0.3",
+    "comlink": "^4.4.1",
     "events": "^3.3.0",
     "idb-keyval": "^6.2.0",
     "jshashes": "^1.0.8",
@@ -30,6 +29,7 @@
     "react-redux": "^8.0.5",
     "react-router-dom": "^6.8.2",
     "react-syntax-highlighter": "^15.5.0",
+    "redux": "4.2.1",
     "redux-persist": "^6.0.0",
     "rehype-katex": "^6.0.2",
     "remark-gfm": "^3.0.1",
@@ -37,27 +37,16 @@
     "sentence-splitter": "^4.2.0",
     "slugify": "^1.6.5",
     "uuid": "^9.0.0",
-    "workerize-loader": "^2.0.2",
     "y-indexeddb": "^9.0.9",
     "y-protocols": "^1.0.5",
     "yjs": "^13.5.50"
   },
-  "overrides": {
-    "@svgr/webpack": "$@svgr/webpack"
-  },
   "scripts": {
-    "start": "craco start",
-    "build": "craco build",
-    "test": "craco test",
-    "eject": "craco eject",
+    "start": "vite",
+    "build": "vite build",
+    "serve": "vite preview",
     "extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file public/lang/en-us.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'"
   },
-  "eslintConfig": {
-    "extends": [
-      "react-app",
-      "react-app/jest"
-    ]
-  },
   "browserslist": {
     "production": [
       ">0.2%",
@@ -71,20 +60,18 @@
     ]
   },
   "devDependencies": {
-    "@craco/craco": "^7.1.0",
     "@formatjs/cli": "^6.0.4",
-    "@types/jest": "^27.5.2",
     "@types/node": "^16.18.13",
     "@types/react": "^18.0.28",
     "@types/react-dom": "^18.0.11",
     "@types/react-helmet": "^6.1.6",
     "@types/react-syntax-highlighter": "^15.5.6",
     "@types/uuid": "^9.0.1",
-    "babel-plugin-formatjs": "^10.4.0",
-    "craco-wasm": "^0.0.1",
+    "@vitejs/plugin-react": "^4.0.2",
+    "babel-plugin-formatjs": "^10.5.3",
     "http-proxy-middleware": "^2.0.6",
-    "react-scripts": "^5.0.1",
-    "sass": "^1.58.3",
-    "typescript": "^4.9.5"
+    "typescript": "^4.9.5",
+    "vite": "^4.4.1",
+    "vite-plugin-comlink": "^3.0.5"
   }
 }
diff --git a/app/public/lang/en-us.json b/app/public/lang/en-us.json
index 3596ef8..7c0d436 100644
--- a/app/public/lang/en-us.json
+++ b/app/public/lang/en-us.json
@@ -2,9 +2,6 @@
   "+G35mR": {
     "defaultMessage": "Open sidebar"
   },
-  "+H2Qtw": {
-    "defaultMessage": "Show quick settings below message input"
-  },
   "+LMWDJ": {
     "defaultMessage": "Chat History",
     "description": "Heading for the chat history screen"
@@ -56,8 +53,8 @@
     "defaultMessage": "Save changes",
     "description": "Label for a button that appears when the user is editing the text of one of their messages, to save the changes"
   },
-  "E4+fv5": {
-    "defaultMessage": "Auto-scroll to the bottom of the page when opening a chat"
+  "CIe3Dt": {
+    "defaultMessage": "Note: This model will only work if your OpenAI account has been granted you have been given access to it. <a>Request access here.</a>"
   },
   "FEzBCd": {
     "defaultMessage": "Untitled",
@@ -73,12 +70,12 @@
   "KKa5Br": {
     "defaultMessage": "Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). <a>Click here to sign up.</a>"
   },
+  "KbUcBc": {
+    "defaultMessage": "Your ElevenLabs API Key"
+  },
   "L5s+z7": {
     "defaultMessage": "OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription."
   },
-  "LHOuNA": {
-    "defaultMessage": "Auto-scroll while generating a response"
-  },
   "MI5gZ+": {
     "defaultMessage": "Download SVG"
   },
@@ -86,9 +83,6 @@
     "defaultMessage": "Chat with GPT",
     "description": "app name"
   },
-  "NRJ4IQ": {
-    "defaultMessage": "Note: GPT-4 will only work if your OpenAI account has been granted access to the new model. <a>Request access here.</a>"
-  },
   "NgCT/u": {
     "defaultMessage": "Enter your email address"
   },
@@ -105,10 +99,6 @@
   "SQJto2": {
     "defaultMessage": "Sign in"
   },
-  "SQh9En": {
-    "defaultMessage": "Sign in <h>to sync</h>",
-    "description": "Label for sign in button, indicating the purpose of signing in is to sync your data between devices"
-  },
   "T8gKkC": {
     "defaultMessage": "Delete this chat"
   },
@@ -116,6 +106,10 @@
     "defaultMessage": "Edit",
     "description": "Label for the button the user can click to edit the text of one of their messages"
   },
+  "UbN3HK": {
+    "defaultMessage": "Sign in <h>to sync</h>",
+    "description": "Label for sign in button, which indicates that the purpose of signing in is to sync your data between devices. Less important text inside <h> tags is hidden on small screens."
+  },
   "VL24Xt": {
     "defaultMessage": "Search your chats"
   },
@@ -126,10 +120,6 @@
     "defaultMessage": "Or sign in to an existing account",
     "description": "Label for a button on the Create Account page that lets the user sign into their existing account instead"
   },
-  "aR9WsJ": {
-    "defaultMessage": "UI Settings",
-    "description": "Heading for the setting that lets users customize various UI elements"
-  },
   "bIacvz": {
     "defaultMessage": "Chat with GPT - Unofficial ChatGPT app",
     "description": "HTML title tag"
@@ -138,9 +128,6 @@
     "defaultMessage": "Hello, how can I help you today?",
     "description": "A friendly message that appears at the start of new chat sessions"
   },
-  "gNu/AE": {
-    "defaultMessage": "Show microphone button in message input"
-  },
   "gzJlXS": {
     "defaultMessage": "Share",
     "description": "Label for a button which shares the text of a chat message using the user device's share functionality"
@@ -155,6 +142,9 @@
     "defaultMessage": "Save and Close",
     "description": "Label for the button that closes the Settings screen, saving any changes"
   },
+  "jU4MQH": {
+    "defaultMessage": "Rename this chat"
+  },
   "jtu3jt": {
     "defaultMessage": "You can find your API key by clicking your avatar or initials in the top right of the ElevenLabs website, then clicking Profile. Your API key is stored only on this device and never transmitted to anyone except ElevenLabs."
   },
@@ -162,9 +152,8 @@
     "defaultMessage": "Copied",
     "description": "Label for copy-to-clipboard button after a successful copy"
   },
-  "pv41j8": {
-    "defaultMessage": "Temperature: {temperature, number, ::.0}",
-    "description": "Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses"
+  "mL4Q28": {
+    "defaultMessage": "Custom Voice ID"
   },
   "raQMIg": {
     "defaultMessage": "Cancel",
@@ -196,20 +185,9 @@
     "defaultMessage": "Copy",
     "description": "Label for copy-to-clipboard button"
   },
-  "w5Dmuu": {
-    "defaultMessage": "Customize system prompt",
-    "description": "Label for the button that opens a modal for customizing the 'system prompt', a message used to customize and influence how the AI responds."
-  },
   "xXbJso": {
     "defaultMessage": "Sign out"
   },
-  "xqpqZE": {
-    "defaultMessage": "Your Elevenlabs API Key"
-  },
-  "y1F8Hs": {
-    "defaultMessage": "Your OpenAI API Key",
-    "description": "Heading for the OpenAI API key setting on the settings screen"
-  },
   "zBmup+": {
     "defaultMessage": "Regenerate",
     "description": "Label for the button used to ask the AI to regenerate one of its messages. Since message generations are stochastic, the resulting message will be different."
diff --git a/app/src/core/tokenizer/wrapper.ts b/app/src/core/tokenizer/wrapper.ts
index 2dfa636..0e4ca96 100644
--- a/app/src/core/tokenizer/wrapper.ts
+++ b/app/src/core/tokenizer/wrapper.ts
@@ -1,28 +1,19 @@
 import { OpenAIMessage } from "../chat/types";
 import type { ChatHistoryTrimmerOptions } from "./chat-history-trimmer";
 // @ts-ignore
-import tokenizer from 'workerize-loader!./worker';
+import tokenizer from "./worker?worker&url";
 
-let worker: any;
+const worker = new ComlinkWorker<typeof import("./worker")>(
+  new URL(tokenizer, import.meta.url)
+);
 
-async function getWorker() {
-    if (!worker) {
-        worker = await tokenizer();
-    }
-    return worker;
-}
-
-export async function runChatTrimmer(messages: OpenAIMessage[], options: ChatHistoryTrimmerOptions): Promise<OpenAIMessage[]> {
-    const worker = await getWorker();
-    return worker.runChatTrimmer(messages, options);
+export async function runChatTrimmer(
+  messages: OpenAIMessage[],
+  options: ChatHistoryTrimmerOptions
+): Promise<OpenAIMessage[]> {
+  return worker.runChatTrimmer(messages, options);
 }
 
 export async function countTokens(messages: OpenAIMessage[]) {
-    const worker = await getWorker();
-    return await worker.countTokensForMessages(messages);
+  return await worker.countTokensForMessages(messages);
 }
-
-// preload the worker
-getWorker().then(w => {
-    (window as any).worker = w;
-})
\ No newline at end of file
diff --git a/app/src/index.scss b/app/src/index.css
similarity index 100%
rename from app/src/index.scss
rename to app/src/index.css
diff --git a/app/src/index.tsx b/app/src/index.tsx
index 9c1f60b..97326ba 100644
--- a/app/src/index.tsx
+++ b/app/src/index.tsx
@@ -12,7 +12,7 @@ import store, { persistor } from './store';
 import ChatPage from './components/pages/chat';
 import LandingPage from './components/pages/landing';
 
-import './index.scss';
+import "./index.css";
 
 const router = createBrowserRouter([
     {
diff --git a/app/src/react-app-env.d.ts b/app/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5..0000000
--- a/app/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-/// <reference types="react-scripts" />
diff --git a/app/src/vite-env.d.ts b/app/src/vite-env.d.ts
new file mode 100644
index 0000000..eb0cdc4
--- /dev/null
+++ b/app/src/vite-env.d.ts
@@ -0,0 +1 @@
+/// <reference types="vite-plugin-comlink/client" />
diff --git a/app/vite.config.js b/app/vite.config.js
new file mode 100644
index 0000000..5bcc743
--- /dev/null
+++ b/app/vite.config.js
@@ -0,0 +1,35 @@
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+import { comlink } from "vite-plugin-comlink";
+
+export default defineConfig(() => {
+  return {
+    build: {
+      outDir: "build",
+    },
+    resolve: {
+      alias: {
+        "@ffmpeg/ffmpeg": "./src/stub.js",
+      },
+    },
+    plugins: [
+      react({
+        babel: {
+          plugins: [
+            [
+              "formatjs",
+              {
+                idInterpolationPattern: "[sha512:contenthash:base64:6]",
+                ast: true,
+              },
+            ],
+          ],
+        },
+      }),
+      comlink(),
+    ],
+    worker: {
+      plugins: [comlink()],
+    },
+  };
+});

From ba6544ba538102182f8f019f4c4ec0c3cc469196 Mon Sep 17 00:00:00 2001
From: ZauberNerd <zaubernerd@zaubernerd.de>
Date: Sat, 8 Jul 2023 11:24:04 +0200
Subject: [PATCH 2/7] Fix non-serializable error message

Using Redux Toolkit together with redux-persist requires some additional
configuration to disable the non-serializable error message:
https://redux-toolkit.js.org/usage/usage-guide#use-with-redux-persist
---
 app/src/store/index.ts | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/app/src/store/index.ts b/app/src/store/index.ts
index 1870901..19833b1 100644
--- a/app/src/store/index.ts
+++ b/app/src/store/index.ts
@@ -1,11 +1,20 @@
 import { configureStore } from '@reduxjs/toolkit';
 import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
+import {
+  FLUSH,
+  PAUSE,
+  PERSIST,
+  PURGE,
+  REGISTER,
+  REHYDRATE,
+  persistReducer,
+  persistStore,
+} from "redux-persist";
 import storage from 'redux-persist/lib/storage';
-import { persistReducer, persistStore } from 'redux-persist';
 import messageReducer from './message';
-import uiReducer from './ui';
 import settingsUIReducer from './settings-ui';
 import sidebarReducer from './sidebar';
+import uiReducer from './ui';
 
 const persistConfig = {
   key: 'root',
@@ -26,12 +35,18 @@ const persistMessageConfig = {
 
 const store = configureStore({
   reducer: {
-    message: persistReducer(persistMessageConfig, messageReducer),
+    message: persistReducer<ReturnType<typeof messageReducer>>(persistMessageConfig, messageReducer),
     ui: uiReducer,
     settingsUI: settingsUIReducer,
-    sidebar: persistReducer(persistSidebarConfig, sidebarReducer),
+    sidebar: persistReducer<ReturnType<typeof sidebarReducer>>(persistSidebarConfig, sidebarReducer),
   },
-})
+  middleware: (getDefaultMiddleware) =>
+    getDefaultMiddleware({
+      serializableCheck: {
+        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
+      },
+    }),
+});
 
 export type RootState = ReturnType<typeof store.getState>;
 export type AppDispatch = typeof store.dispatch;

From 6e791e1b972af5296fc828a4b2e2c6c04982fcc5 Mon Sep 17 00:00:00 2001
From: ZauberNerd <zaubernerd@zaubernerd.de>
Date: Sat, 8 Jul 2023 11:32:16 +0200
Subject: [PATCH 3/7] Update extracted translations to remove errors in dev
 console

Starting the app causes several errors about missing translations to be
printed to the browsers dev console.
---
 app/public/lang/en-us.json | 50 +++++++++++---------------------------
 1 file changed, 14 insertions(+), 36 deletions(-)

diff --git a/app/public/lang/en-us.json b/app/public/lang/en-us.json
index 3596ef8..7c0d436 100644
--- a/app/public/lang/en-us.json
+++ b/app/public/lang/en-us.json
@@ -2,9 +2,6 @@
   "+G35mR": {
     "defaultMessage": "Open sidebar"
   },
-  "+H2Qtw": {
-    "defaultMessage": "Show quick settings below message input"
-  },
   "+LMWDJ": {
     "defaultMessage": "Chat History",
     "description": "Heading for the chat history screen"
@@ -56,8 +53,8 @@
     "defaultMessage": "Save changes",
     "description": "Label for a button that appears when the user is editing the text of one of their messages, to save the changes"
   },
-  "E4+fv5": {
-    "defaultMessage": "Auto-scroll to the bottom of the page when opening a chat"
+  "CIe3Dt": {
+    "defaultMessage": "Note: This model will only work if your OpenAI account has been granted you have been given access to it. <a>Request access here.</a>"
   },
   "FEzBCd": {
     "defaultMessage": "Untitled",
@@ -73,12 +70,12 @@
   "KKa5Br": {
     "defaultMessage": "Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). <a>Click here to sign up.</a>"
   },
+  "KbUcBc": {
+    "defaultMessage": "Your ElevenLabs API Key"
+  },
   "L5s+z7": {
     "defaultMessage": "OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription."
   },
-  "LHOuNA": {
-    "defaultMessage": "Auto-scroll while generating a response"
-  },
   "MI5gZ+": {
     "defaultMessage": "Download SVG"
   },
@@ -86,9 +83,6 @@
     "defaultMessage": "Chat with GPT",
     "description": "app name"
   },
-  "NRJ4IQ": {
-    "defaultMessage": "Note: GPT-4 will only work if your OpenAI account has been granted access to the new model. <a>Request access here.</a>"
-  },
   "NgCT/u": {
     "defaultMessage": "Enter your email address"
   },
@@ -105,10 +99,6 @@
   "SQJto2": {
     "defaultMessage": "Sign in"
   },
-  "SQh9En": {
-    "defaultMessage": "Sign in <h>to sync</h>",
-    "description": "Label for sign in button, indicating the purpose of signing in is to sync your data between devices"
-  },
   "T8gKkC": {
     "defaultMessage": "Delete this chat"
   },
@@ -116,6 +106,10 @@
     "defaultMessage": "Edit",
     "description": "Label for the button the user can click to edit the text of one of their messages"
   },
+  "UbN3HK": {
+    "defaultMessage": "Sign in <h>to sync</h>",
+    "description": "Label for sign in button, which indicates that the purpose of signing in is to sync your data between devices. Less important text inside <h> tags is hidden on small screens."
+  },
   "VL24Xt": {
     "defaultMessage": "Search your chats"
   },
@@ -126,10 +120,6 @@
     "defaultMessage": "Or sign in to an existing account",
     "description": "Label for a button on the Create Account page that lets the user sign into their existing account instead"
   },
-  "aR9WsJ": {
-    "defaultMessage": "UI Settings",
-    "description": "Heading for the setting that lets users customize various UI elements"
-  },
   "bIacvz": {
     "defaultMessage": "Chat with GPT - Unofficial ChatGPT app",
     "description": "HTML title tag"
@@ -138,9 +128,6 @@
     "defaultMessage": "Hello, how can I help you today?",
     "description": "A friendly message that appears at the start of new chat sessions"
   },
-  "gNu/AE": {
-    "defaultMessage": "Show microphone button in message input"
-  },
   "gzJlXS": {
     "defaultMessage": "Share",
     "description": "Label for a button which shares the text of a chat message using the user device's share functionality"
@@ -155,6 +142,9 @@
     "defaultMessage": "Save and Close",
     "description": "Label for the button that closes the Settings screen, saving any changes"
   },
+  "jU4MQH": {
+    "defaultMessage": "Rename this chat"
+  },
   "jtu3jt": {
     "defaultMessage": "You can find your API key by clicking your avatar or initials in the top right of the ElevenLabs website, then clicking Profile. Your API key is stored only on this device and never transmitted to anyone except ElevenLabs."
   },
@@ -162,9 +152,8 @@
     "defaultMessage": "Copied",
     "description": "Label for copy-to-clipboard button after a successful copy"
   },
-  "pv41j8": {
-    "defaultMessage": "Temperature: {temperature, number, ::.0}",
-    "description": "Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses"
+  "mL4Q28": {
+    "defaultMessage": "Custom Voice ID"
   },
   "raQMIg": {
     "defaultMessage": "Cancel",
@@ -196,20 +185,9 @@
     "defaultMessage": "Copy",
     "description": "Label for copy-to-clipboard button"
   },
-  "w5Dmuu": {
-    "defaultMessage": "Customize system prompt",
-    "description": "Label for the button that opens a modal for customizing the 'system prompt', a message used to customize and influence how the AI responds."
-  },
   "xXbJso": {
     "defaultMessage": "Sign out"
   },
-  "xqpqZE": {
-    "defaultMessage": "Your Elevenlabs API Key"
-  },
-  "y1F8Hs": {
-    "defaultMessage": "Your OpenAI API Key",
-    "description": "Heading for the OpenAI API key setting on the settings screen"
-  },
   "zBmup+": {
     "defaultMessage": "Regenerate",
     "description": "Label for the button used to ask the AI to regenerate one of its messages. Since message generations are stochastic, the resulting message will be different."

From 4b91a1972b432f35c809feeb690b57633bbf2668 Mon Sep 17 00:00:00 2001
From: ZauberNerd <zaubernerd@zaubernerd.de>
Date: Sat, 8 Jul 2023 10:57:34 +0200
Subject: [PATCH 4/7] Replace CRA proxy config with vite's proxy config

---
 app/package.json      |  1 -
 app/src/setupProxy.js | 14 --------------
 app/vite.config.js    |  9 +++++++++
 3 files changed, 9 insertions(+), 15 deletions(-)
 delete mode 100644 app/src/setupProxy.js

diff --git a/app/package.json b/app/package.json
index 66721a1..33f1bbf 100644
--- a/app/package.json
+++ b/app/package.json
@@ -69,7 +69,6 @@
     "@types/uuid": "^9.0.1",
     "@vitejs/plugin-react": "^4.0.2",
     "babel-plugin-formatjs": "^10.5.3",
-    "http-proxy-middleware": "^2.0.6",
     "typescript": "^4.9.5",
     "vite": "^4.4.1",
     "vite-plugin-comlink": "^3.0.5"
diff --git a/app/src/setupProxy.js b/app/src/setupProxy.js
deleted file mode 100644
index 06b4038..0000000
--- a/app/src/setupProxy.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* eslint-disable @typescript-eslint/no-var-requires */
-/* eslint-disable no-undef */
-const { createProxyMiddleware } = require('http-proxy-middleware');
-
-module.exports = function (app) {
-    app.use(
-        '/chatapi',
-        createProxyMiddleware({
-            target: 'http://localhost:3001',
-            secure: false,
-            changeOrigin: true,
-        })
-    );
-};
\ No newline at end of file
diff --git a/app/vite.config.js b/app/vite.config.js
index 5bcc743..e8aefa1 100644
--- a/app/vite.config.js
+++ b/app/vite.config.js
@@ -4,6 +4,15 @@ import { comlink } from "vite-plugin-comlink";
 
 export default defineConfig(() => {
   return {
+    server: {
+      proxy: {
+        "/chatapi": {
+          target: "http://localhost:3001",
+          secure: false,
+          changeOrigin: true,
+        },
+      },
+    },
     build: {
       outDir: "build",
     },

From ff9f3517ec01ede9e1b790cf6fbc7e4fa603d5a1 Mon Sep 17 00:00:00 2001
From: ZauberNerd <zaubernerd@zaubernerd.de>
Date: Fri, 7 Jul 2023 15:21:20 +0200
Subject: [PATCH 5/7] Replace Node.js's Buffer with native Typed Arrays on the
 client-side

Vite does not natively ship a Buffer polyfill and most of the
functionality that is required here, can be implemented natively (except
for the byte-wise compare, for that I had to write my own function).
---
 app/package.json              |  1 -
 app/src/core/tokenizer/bpe.ts | 23 ++++++++++++++---------
 app/src/core/utils/index.ts   | 23 +++++++++++++++++++++++
 3 files changed, 37 insertions(+), 10 deletions(-)

diff --git a/app/package.json b/app/package.json
index 33f1bbf..4303d16 100644
--- a/app/package.json
+++ b/app/package.json
@@ -13,7 +13,6 @@
     "@msgpack/msgpack": "^3.0.0-beta2",
     "@reduxjs/toolkit": "^1.9.3",
     "broadcast-channel": "^4.20.2",
-    "buffer": "^6.0.3",
     "comlink": "^4.4.1",
     "events": "^3.3.0",
     "idb-keyval": "^6.2.0",
diff --git a/app/src/core/tokenizer/bpe.ts b/app/src/core/tokenizer/bpe.ts
index e8d5b96..c71cb3b 100644
--- a/app/src/core/tokenizer/bpe.ts
+++ b/app/src/core/tokenizer/bpe.ts
@@ -1,7 +1,12 @@
+import { compareUint8Array } from "../utils";
+
 const MAX_NUM_THREADS = 128;
 
 type MergeRange = { start: number, end: number };
 
+const textDecoder = new TextDecoder();
+const textEncoder = new TextEncoder();
+
 export class RankMap {
     private values = new Map<string, number>();
 
@@ -14,23 +19,23 @@ export class RankMap {
     }
 
     public set(bytes: Uint8Array, rank: number) {
-        const key = Buffer.from(bytes).toString();
+        const key = textDecoder.decode(bytes);
         this.values.set(key, rank);
     }
 
     public get(bytes: Uint8Array) {
-        const key = Buffer.from(bytes).toString();
+        const key = textDecoder.decode(bytes);
         return this.values.get(key);
     }
 
     public keys() {
-        return Array.from(this.values.keys()).map(k => Buffer.from(k));
+        return Array.from(this.values.keys()).map(k => textEncoder.encode(k));
     }
 
     public inverted() {
         const inverted = new Map<number, Uint8Array>();
         for (const [key, value] of Array.from(this.values.entries())) {
-            inverted.set(value, new Uint8Array(Buffer.from(key)));
+            inverted.set(value, textEncoder.encode(key));
         }
         return inverted;
     }
@@ -100,10 +105,10 @@ export class CoreBPE {
         const decoder: Map<number, Uint8Array> = encoder.inverted();
 
         const specialTokensDecoder: Map<number, Uint8Array> = new Map(
-            Array.from(specialTokensEncoder.entries()).map(([k, v]) => [v, new Uint8Array(Buffer.from(k))])
+            Array.from(specialTokensEncoder.entries()).map(([k, v]) => [v, textEncoder.encode(k)])
         );
         const sortedTokenBytes: Uint8Array[] = Array.from(encoder.keys());
-        sortedTokenBytes.sort((a, b) => Buffer.compare(a, b));
+        sortedTokenBytes.sort((a, b) => compareUint8Array(a, b));
 
         this.encoder = encoder;
         this.specialTokensEncoder = specialTokensEncoder;
@@ -136,7 +141,7 @@ export class CoreBPE {
         const ret: number[] = [];
         let match: RegExpExecArray | null;
         while ((match = regex.exec(text)) !== null) {
-            const piece = new Uint8Array(Buffer.from(match[0]));
+            const piece = textEncoder.encode(match[0]);
             const token = this.encoder.get(piece);
             if (token !== undefined) {
                 ret.push(token);
@@ -167,7 +172,7 @@ export class CoreBPE {
             const end = nextSpecial === null ? text.length : nextSpecial.index;
             let match: RegExpExecArray | null;
             while ((match = regex.exec(text.slice(start, end))) !== null) {
-                const piece = new Uint8Array(Buffer.from(match[0]));
+                const piece = textEncoder.encode(match[0]);
                 const token = this.encoder.get(piece);
                 if (token !== undefined) {
                     lastPieceTokenLen = 1;
@@ -208,7 +213,7 @@ export class CoreBPE {
         if (token !== undefined) {
             return token;
         }
-        const pieceStr = Buffer.from(piece).toString("utf-8");
+        const pieceStr = textDecoder.decode(piece);
         if (this.specialTokensEncoder.has(pieceStr)) {
             return this.specialTokensEncoder.get(pieceStr)!;
         }
diff --git a/app/src/core/utils/index.ts b/app/src/core/utils/index.ts
index ac1ec39..89699f1 100644
--- a/app/src/core/utils/index.ts
+++ b/app/src/core/utils/index.ts
@@ -39,6 +39,29 @@ export function cloneArrayBuffer(buffer: ArrayBuffer): ArrayBuffer {
     return newBuffer;
 }
 
+/**
+ * Lexicographically compare two `Uint8Array` instances.
+ *
+ * @param {Uint8Array} a - The first `Uint8Array` instance to compare.
+ * @param {Uint8Array} b - The second `Uint8Array` instance to compare.
+ * @returns {number} The comparison result. -1 if `a` is "less" than `b`, 1 if `a` is "greater" than `b`, or 0 if they are "equal".
+ */
+export function compareUint8Array(a: Uint8Array, b: Uint8Array): number {
+  if (a === b) return 0;
+
+  const len = Math.min(a.byteLength, b.byteLength);
+
+  for (let i = 0; i < len; ++i) {
+    if (a[i] < b[i]) return -1;
+    if (a[i] > b[i]) return 1;
+  }
+
+  if (a.byteLength < b.byteLength) return -1;
+  if (a.byteLength > b.byteLength) return 1;
+
+  return 0;
+}
+
 /**
  * Shares the specified text using the Web Share API if available in the user's browser.
  *

From 07013035632e0e6e30e175de21db5b58db5f9b4e Mon Sep 17 00:00:00 2001
From: ZauberNerd <zaubernerd@zaubernerd.de>
Date: Sat, 8 Jul 2023 15:25:25 +0200
Subject: [PATCH 6/7] Use comlink without vite-plugin-comlink

For some reason the vite-plugin-comlink doesn't seem to work in this
constellation.
I tried to reproduce in a new vite project and there it did work.
But also, the plugin does some horrible magic by replacing a
non-existant constructor `ComLinkWorker()` with the `wrap()` method
and then the only real benefit is, that we wouldn't need to write the
`expose()` method in the worker. So for that reason I'm removing the
plugin and using the comlink package directly.
---
 app/package.json                  | 3 +--
 app/src/core/tokenizer/worker.ts  | 5 ++++-
 app/src/core/tokenizer/wrapper.ts | 5 +++--
 app/vite.config.js                | 5 -----
 4 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/app/package.json b/app/package.json
index 4303d16..d0eb0e8 100644
--- a/app/package.json
+++ b/app/package.json
@@ -69,7 +69,6 @@
     "@vitejs/plugin-react": "^4.0.2",
     "babel-plugin-formatjs": "^10.5.3",
     "typescript": "^4.9.5",
-    "vite": "^4.4.1",
-    "vite-plugin-comlink": "^3.0.5"
+    "vite": "^4.4.1"
   }
 }
diff --git a/app/src/core/tokenizer/worker.ts b/app/src/core/tokenizer/worker.ts
index 3fa81f3..ce79750 100644
--- a/app/src/core/tokenizer/worker.ts
+++ b/app/src/core/tokenizer/worker.ts
@@ -1,3 +1,4 @@
+import { expose } from "comlink";
 import * as methods from ".";
 import { OpenAIMessage } from "../chat/types";
 import { ChatHistoryTrimmer, ChatHistoryTrimmerOptions } from "./chat-history-trimmer";
@@ -13,4 +14,6 @@ export function countTokensForText(text: string) {
 
 export function countTokensForMessages(messages: OpenAIMessage[]) {
     return methods.countTokensForMessages(messages);
-}
\ No newline at end of file
+}
+
+expose({ runChatTrimmer, countTokensForText, countTokensForMessages });
diff --git a/app/src/core/tokenizer/wrapper.ts b/app/src/core/tokenizer/wrapper.ts
index 0e4ca96..daa9394 100644
--- a/app/src/core/tokenizer/wrapper.ts
+++ b/app/src/core/tokenizer/wrapper.ts
@@ -1,10 +1,11 @@
+import { wrap } from "comlink";
 import { OpenAIMessage } from "../chat/types";
 import type { ChatHistoryTrimmerOptions } from "./chat-history-trimmer";
 // @ts-ignore
 import tokenizer from "./worker?worker&url";
 
-const worker = new ComlinkWorker<typeof import("./worker")>(
-  new URL(tokenizer, import.meta.url)
+const worker = wrap<typeof import("./worker")>(
+  new Worker(new URL(tokenizer, import.meta.url), { type: "module" })
 );
 
 export async function runChatTrimmer(
diff --git a/app/vite.config.js b/app/vite.config.js
index e8aefa1..9b66e32 100644
--- a/app/vite.config.js
+++ b/app/vite.config.js
@@ -1,6 +1,5 @@
 import react from "@vitejs/plugin-react";
 import { defineConfig } from "vite";
-import { comlink } from "vite-plugin-comlink";
 
 export default defineConfig(() => {
   return {
@@ -35,10 +34,6 @@ export default defineConfig(() => {
           ],
         },
       }),
-      comlink(),
     ],
-    worker: {
-      plugins: [comlink()],
-    },
   };
 });

From 25fd17a5542f8aab93d149642f2b08f3d972a7c3 Mon Sep 17 00:00:00 2001
From: Cogent Apps <cogentapps@fastmail.com>
Date: Sun, 9 Jul 2023 19:24:17 +0000
Subject: [PATCH 7/7] extend browser compatability

---
 app/vite.config.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/app/vite.config.js b/app/vite.config.js
index 9b66e32..d031e01 100644
--- a/app/vite.config.js
+++ b/app/vite.config.js
@@ -14,6 +14,10 @@ export default defineConfig(() => {
     },
     build: {
       outDir: "build",
+      target: "es2020"
+    },
+    esbuild: {
+      target: "es2020"
     },
     resolve: {
       alias: {