Merge branch 'main' into service-worker

main^2
Cogent Apps 2023-07-09 12:40:19 -07:00 committed by GitHub
commit f9122c5227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 148 additions and 163 deletions

View File

@ -14,8 +14,7 @@ COPY ./app/tsconfig.json ./
# Install Node.js dependencies # Install Node.js dependencies
RUN npm install RUN npm install
# Copy craco.config.js, public, and src directories # Copy public, and src directories
COPY ./app/craco.config.js ./craco.config.js
COPY ./app/public ./public COPY ./app/public ./public
COPY ./app/src ./src COPY ./app/src ./src

View File

@ -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,
},
},
}

View File

@ -3,23 +3,23 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<!-- mobile app viewport --> <!-- mobile app viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" /> <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="theme-color" content="#000000" />
<meta name="description" content="An open source ChatGPT app with a voice." /> <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 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/ 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. 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. 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. 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`. 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 begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
<script type="module" src="/src/index.tsx"></script>
</body> </body>
</html> </html>

View File

@ -3,7 +3,6 @@
"version": "0.2.3", "version": "0.2.3",
"dependencies": { "dependencies": {
"@chengsokdara/use-whisper": "^0.2.0", "@chengsokdara/use-whisper": "^0.2.0",
"@emotion/css": "^11.10.6",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@mantine/core": "^5.10.5", "@mantine/core": "^5.10.5",
@ -13,9 +12,8 @@
"@mantine/spotlight": "^5.10.5", "@mantine/spotlight": "^5.10.5",
"@msgpack/msgpack": "^3.0.0-beta2", "@msgpack/msgpack": "^3.0.0-beta2",
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.3",
"@svgr/webpack": "^6.5.1",
"broadcast-channel": "^4.20.2", "broadcast-channel": "^4.20.2",
"buffer": "^6.0.3", "comlink": "^4.4.1",
"events": "^3.3.0", "events": "^3.3.0",
"idb-keyval": "^6.2.0", "idb-keyval": "^6.2.0",
"jshashes": "^1.0.8", "jshashes": "^1.0.8",
@ -30,6 +28,7 @@
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.8.2", "react-router-dom": "^6.8.2",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"redux": "4.2.1",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"rehype-katex": "^6.0.2", "rehype-katex": "^6.0.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
@ -37,27 +36,16 @@
"sentence-splitter": "^4.2.0", "sentence-splitter": "^4.2.0",
"slugify": "^1.6.5", "slugify": "^1.6.5",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"workerize-loader": "^2.0.2",
"y-indexeddb": "^9.0.9", "y-indexeddb": "^9.0.9",
"y-protocols": "^1.0.5", "y-protocols": "^1.0.5",
"yjs": "^13.5.50" "yjs": "^13.5.50"
}, },
"overrides": {
"@svgr/webpack": "$@svgr/webpack"
},
"scripts": { "scripts": {
"start": "craco start", "start": "vite",
"build": "craco build", "build": "vite build",
"test": "craco test", "serve": "vite preview",
"eject": "craco eject",
"extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file public/lang/en-us.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'" "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": { "browserslist": {
"production": [ "production": [
">0.2%", ">0.2%",
@ -71,20 +59,16 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@craco/craco": "^7.1.0",
"@formatjs/cli": "^6.0.4", "@formatjs/cli": "^6.0.4",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.13", "@types/node": "^16.18.13",
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/react-helmet": "^6.1.6", "@types/react-helmet": "^6.1.6",
"@types/react-syntax-highlighter": "^15.5.6", "@types/react-syntax-highlighter": "^15.5.6",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"babel-plugin-formatjs": "^10.4.0", "@vitejs/plugin-react": "^4.0.2",
"craco-wasm": "^0.0.1", "babel-plugin-formatjs": "^10.5.3",
"http-proxy-middleware": "^2.0.6", "typescript": "^4.9.5",
"react-scripts": "^5.0.1", "vite": "^4.4.1"
"sass": "^1.58.3",
"typescript": "^4.9.5"
} }
} }

View File

@ -2,9 +2,6 @@
"+G35mR": { "+G35mR": {
"defaultMessage": "Open sidebar" "defaultMessage": "Open sidebar"
}, },
"+H2Qtw": {
"defaultMessage": "Show quick settings below message input"
},
"+LMWDJ": { "+LMWDJ": {
"defaultMessage": "Chat History", "defaultMessage": "Chat History",
"description": "Heading for the chat history screen" "description": "Heading for the chat history screen"
@ -56,8 +53,8 @@
"defaultMessage": "Save changes", "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" "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": { "CIe3Dt": {
"defaultMessage": "Auto-scroll to the bottom of the page when opening a chat" "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": { "FEzBCd": {
"defaultMessage": "Untitled", "defaultMessage": "Untitled",
@ -73,12 +70,12 @@
"KKa5Br": { "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>" "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": { "L5s+z7": {
"defaultMessage": "OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription." "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+": { "MI5gZ+": {
"defaultMessage": "Download SVG" "defaultMessage": "Download SVG"
}, },
@ -86,9 +83,6 @@
"defaultMessage": "Chat with GPT", "defaultMessage": "Chat with GPT",
"description": "app name" "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": { "NgCT/u": {
"defaultMessage": "Enter your email address" "defaultMessage": "Enter your email address"
}, },
@ -105,10 +99,6 @@
"SQJto2": { "SQJto2": {
"defaultMessage": "Sign in" "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": { "T8gKkC": {
"defaultMessage": "Delete this chat" "defaultMessage": "Delete this chat"
}, },
@ -116,6 +106,10 @@
"defaultMessage": "Edit", "defaultMessage": "Edit",
"description": "Label for the button the user can click to edit the text of one of their messages" "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": { "VL24Xt": {
"defaultMessage": "Search your chats" "defaultMessage": "Search your chats"
}, },
@ -126,10 +120,6 @@
"defaultMessage": "Or sign in to an existing account", "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" "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": { "bIacvz": {
"defaultMessage": "Chat with GPT - Unofficial ChatGPT app", "defaultMessage": "Chat with GPT - Unofficial ChatGPT app",
"description": "HTML title tag" "description": "HTML title tag"
@ -138,9 +128,6 @@
"defaultMessage": "Hello, how can I help you today?", "defaultMessage": "Hello, how can I help you today?",
"description": "A friendly message that appears at the start of new chat sessions" "description": "A friendly message that appears at the start of new chat sessions"
}, },
"gNu/AE": {
"defaultMessage": "Show microphone button in message input"
},
"gzJlXS": { "gzJlXS": {
"defaultMessage": "Share", "defaultMessage": "Share",
"description": "Label for a button which shares the text of a chat message using the user device's share functionality" "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", "defaultMessage": "Save and Close",
"description": "Label for the button that closes the Settings screen, saving any changes" "description": "Label for the button that closes the Settings screen, saving any changes"
}, },
"jU4MQH": {
"defaultMessage": "Rename this chat"
},
"jtu3jt": { "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." "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", "defaultMessage": "Copied",
"description": "Label for copy-to-clipboard button after a successful copy" "description": "Label for copy-to-clipboard button after a successful copy"
}, },
"pv41j8": { "mL4Q28": {
"defaultMessage": "Temperature: {temperature, number, ::.0}", "defaultMessage": "Custom Voice ID"
"description": "Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses"
}, },
"raQMIg": { "raQMIg": {
"defaultMessage": "Cancel", "defaultMessage": "Cancel",
@ -196,20 +185,9 @@
"defaultMessage": "Copy", "defaultMessage": "Copy",
"description": "Label for copy-to-clipboard button" "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": { "xXbJso": {
"defaultMessage": "Sign out" "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+": { "zBmup+": {
"defaultMessage": "Regenerate", "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." "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."

View File

@ -1,7 +1,12 @@
import { compareUint8Array } from "../utils";
const MAX_NUM_THREADS = 128; const MAX_NUM_THREADS = 128;
type MergeRange = { start: number, end: number }; type MergeRange = { start: number, end: number };
const textDecoder = new TextDecoder();
const textEncoder = new TextEncoder();
export class RankMap { export class RankMap {
private values = new Map<string, number>(); private values = new Map<string, number>();
@ -14,23 +19,23 @@ export class RankMap {
} }
public set(bytes: Uint8Array, rank: number) { public set(bytes: Uint8Array, rank: number) {
const key = Buffer.from(bytes).toString(); const key = textDecoder.decode(bytes);
this.values.set(key, rank); this.values.set(key, rank);
} }
public get(bytes: Uint8Array) { public get(bytes: Uint8Array) {
const key = Buffer.from(bytes).toString(); const key = textDecoder.decode(bytes);
return this.values.get(key); return this.values.get(key);
} }
public keys() { 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() { public inverted() {
const inverted = new Map<number, Uint8Array>(); const inverted = new Map<number, Uint8Array>();
for (const [key, value] of Array.from(this.values.entries())) { 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; return inverted;
} }
@ -100,10 +105,10 @@ export class CoreBPE {
const decoder: Map<number, Uint8Array> = encoder.inverted(); const decoder: Map<number, Uint8Array> = encoder.inverted();
const specialTokensDecoder: Map<number, Uint8Array> = new Map( 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()); 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.encoder = encoder;
this.specialTokensEncoder = specialTokensEncoder; this.specialTokensEncoder = specialTokensEncoder;
@ -136,7 +141,7 @@ export class CoreBPE {
const ret: number[] = []; const ret: number[] = [];
let match: RegExpExecArray | null; let match: RegExpExecArray | null;
while ((match = regex.exec(text)) !== 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); const token = this.encoder.get(piece);
if (token !== undefined) { if (token !== undefined) {
ret.push(token); ret.push(token);
@ -167,7 +172,7 @@ export class CoreBPE {
const end = nextSpecial === null ? text.length : nextSpecial.index; const end = nextSpecial === null ? text.length : nextSpecial.index;
let match: RegExpExecArray | null; let match: RegExpExecArray | null;
while ((match = regex.exec(text.slice(start, end))) !== 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); const token = this.encoder.get(piece);
if (token !== undefined) { if (token !== undefined) {
lastPieceTokenLen = 1; lastPieceTokenLen = 1;
@ -208,7 +213,7 @@ export class CoreBPE {
if (token !== undefined) { if (token !== undefined) {
return token; return token;
} }
const pieceStr = Buffer.from(piece).toString("utf-8"); const pieceStr = textDecoder.decode(piece);
if (this.specialTokensEncoder.has(pieceStr)) { if (this.specialTokensEncoder.has(pieceStr)) {
return this.specialTokensEncoder.get(pieceStr)!; return this.specialTokensEncoder.get(pieceStr)!;
} }

View File

@ -1,3 +1,4 @@
import { expose } from "comlink";
import * as methods from "."; import * as methods from ".";
import { OpenAIMessage } from "../chat/types"; import { OpenAIMessage } from "../chat/types";
import { ChatHistoryTrimmer, ChatHistoryTrimmerOptions } from "./chat-history-trimmer"; import { ChatHistoryTrimmer, ChatHistoryTrimmerOptions } from "./chat-history-trimmer";
@ -14,3 +15,5 @@ export function countTokensForText(text: string) {
export function countTokensForMessages(messages: OpenAIMessage[]) { export function countTokensForMessages(messages: OpenAIMessage[]) {
return methods.countTokensForMessages(messages); return methods.countTokensForMessages(messages);
} }
expose({ runChatTrimmer, countTokensForText, countTokensForMessages });

View File

@ -1,28 +1,20 @@
import { wrap } from "comlink";
import { OpenAIMessage } from "../chat/types"; import { OpenAIMessage } from "../chat/types";
import type { ChatHistoryTrimmerOptions } from "./chat-history-trimmer"; import type { ChatHistoryTrimmerOptions } from "./chat-history-trimmer";
// @ts-ignore // @ts-ignore
import tokenizer from 'workerize-loader!./worker'; import tokenizer from "./worker?worker&url";
let worker: any; const worker = wrap<typeof import("./worker")>(
new Worker(new URL(tokenizer, import.meta.url), { type: "module" })
);
async function getWorker() { export async function runChatTrimmer(
if (!worker) { messages: OpenAIMessage[],
worker = await tokenizer(); options: ChatHistoryTrimmerOptions
} ): Promise<OpenAIMessage[]> {
return worker; return worker.runChatTrimmer(messages, options);
}
export async function runChatTrimmer(messages: OpenAIMessage[], options: ChatHistoryTrimmerOptions): Promise<OpenAIMessage[]> {
const worker = await getWorker();
return worker.runChatTrimmer(messages, options);
} }
export async function countTokens(messages: OpenAIMessage[]) { 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;
})

View File

@ -39,6 +39,29 @@ export function cloneArrayBuffer(buffer: ArrayBuffer): ArrayBuffer {
return newBuffer; 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. * Shares the specified text using the Web Share API if available in the user's browser.
* *

View File

@ -14,7 +14,7 @@ import { setUpdateAvailable } from "./store/pwa";
import ChatPage from './components/pages/chat'; import ChatPage from './components/pages/chat';
import LandingPage from './components/pages/landing'; import LandingPage from './components/pages/landing';
import './index.scss'; import "./index.css";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {

View File

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View File

@ -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,
})
);
};

View File

@ -1,11 +1,20 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 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 storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import messageReducer from './message'; import messageReducer from './message';
import uiReducer from './ui';
import settingsUIReducer from './settings-ui'; import settingsUIReducer from './settings-ui';
import sidebarReducer from './sidebar'; import sidebarReducer from './sidebar';
import uiReducer from './ui';
import pwaReducer from './pwa'; import pwaReducer from './pwa';
const persistConfig = { const persistConfig = {
@ -23,17 +32,21 @@ const persistMessageConfig = {
storage, storage,
} }
const store = configureStore({ const store = configureStore({
reducer: { reducer: {
message: persistReducer(persistMessageConfig, messageReducer), message: persistReducer<ReturnType<typeof messageReducer>>(persistMessageConfig, messageReducer),
ui: uiReducer, ui: uiReducer,
settingsUI: settingsUIReducer, settingsUI: settingsUIReducer,
sidebar: persistReducer(persistSidebarConfig, sidebarReducer), sidebar: persistReducer<ReturnType<typeof sidebarReducer>>(persistSidebarConfig, sidebarReducer),
pwa: pwaReducer, pwa: pwaReducer,
}, },
}) middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
export type RootState = ReturnType<typeof store.getState>; export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch; export type AppDispatch = typeof store.dispatch;

1
app/src/vite-env.d.ts vendored 100644
View File

@ -0,0 +1 @@
/// <reference types="vite-plugin-comlink/client" />

43
app/vite.config.js 100644
View File

@ -0,0 +1,43 @@
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig(() => {
return {
server: {
proxy: {
"/chatapi": {
target: "http://localhost:3001",
secure: false,
changeOrigin: true,
},
},
},
build: {
outDir: "build",
target: "es2020"
},
esbuild: {
target: "es2020"
},
resolve: {
alias: {
"@ffmpeg/ffmpeg": "./src/stub.js",
},
},
plugins: [
react({
babel: {
plugins: [
[
"formatjs",
{
idInterpolationPattern: "[sha512:contenthash:base64:6]",
ast: true,
},
],
],
},
}),
],
};
});