Compare commits

..

15 Commits

Author SHA1 Message Date
Cogent Apps c75663bf90
Merge pull request #165 from ZauberNerd/service-worker
Add service worker and make PWA installable
2023-07-15 15:31:53 -07:00
Cogent Apps 946190eb9d
Update publish.yml 2023-07-10 21:20:57 -07:00
ZauberNerd 36c1cb47c0
Implement service worker using vite-plugin-pwa 2023-07-10 00:42:54 +02:00
Cogent Apps 8184c950d7
Merge pull request #145 from QJoly/main
Arm support
2023-07-09 13:52:50 -07:00
Cogent Apps a13fff35da fix dockerfile for vite 2023-07-09 20:45:52 +00:00
Cogent Apps f9122c5227
Merge branch 'main' into service-worker 2023-07-09 12:40:19 -07:00
Cogent Apps 4f5e7fbdc8
Merge pull request #166 from ZauberNerd/fix-non-serializable-error
Fix non-serializable error message
2023-07-09 12:28:07 -07:00
Cogent Apps 71a6e699c8
Merge pull request #167 from ZauberNerd/update-translations
Update extracted translations to remove errors in dev console
2023-07-09 12:27:29 -07:00
Cogent Apps d7e493c347
Merge pull request #164 from ZauberNerd/replace-create-react-app-vite
Replace create react app with vite
2023-07-09 12:26:36 -07:00
Cogent Apps 25fd17a554 extend browser compatability 2023-07-09 19:24:17 +00:00
ZauberNerd 6e791e1b97
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.
2023-07-08 11:32:16 +02:00
ZauberNerd ba6544ba53
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
2023-07-08 11:24:04 +02:00
ZauberNerd d5131d167d
Add service worker and make PWA installable
This commit adds a service worker to make the app installable as a PWA.
In addition to caching all generated build artifacts (css and js filse)
the service worker also caches all other static files via a
stale-while-revalidate strategy (png, ico, json, css from the public
folder).
Further, this commit adds a Notification which will be shown when the
service worker updates and clicking on the button inside it causes
the service worker to update and the page to reload.

In order to make a PWA installable we need to follow some guide lines:
https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable

Fixes: #147
2023-07-07 22:15:19 +02:00
QJoly c85299f816 Set up Docker Buildx 2023-05-02 15:53:42 +02:00
QJoly d7fe13b324 Setup QEMU and Multi-Arch docker build 2023-05-02 15:34:06 +02:00
11 changed files with 182 additions and 47 deletions

View File

@ -11,7 +11,9 @@ name: publish image
on: on:
push: push:
branches: ['release'] branches:
- release
- beta
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
@ -28,6 +30,15 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
image: tonistiigi/binfmt:latest
platforms: arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with: with:
@ -46,5 +57,6 @@ jobs:
with: with:
context: . context: .
push: true push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@ -14,9 +14,12 @@ COPY ./app/tsconfig.json ./
# Install Node.js dependencies # Install Node.js dependencies
RUN npm install RUN npm install
COPY ./app/vite.config.js ./
# Copy public, and src directories # Copy public, and src directories
COPY ./app/public ./public COPY ./app/public ./public
COPY ./app/src ./src COPY ./app/src ./src
COPY ./app/index.html ./
# Set environment variables # Set environment variables
ENV NODE_ENV=production ENV NODE_ENV=production
@ -29,14 +32,6 @@ FROM node:19-bullseye-slim AS server
# Set the working directory # Set the working directory
WORKDIR /app WORKDIR /app
# Update the package index and install required dependencies
# RUN apt-get update && \
# apt-get install -y \
# curl \
# build-essential \
# libssl-dev \
# openssl
COPY ./server/package.json ./server/tsconfig.json ./ COPY ./server/package.json ./server/tsconfig.json ./
# Install Node.js dependencies from package.json # Install Node.js dependencies from package.json
@ -45,7 +40,7 @@ RUN npm install
# Copy the rest of the application code into the working directory # Copy the rest of the application code into the working directory
COPY ./server/src ./src COPY ./server/src ./src
RUN CI=true sh -c "cd /app && mkdir data && npm run start && rm -rf data" RUN CI=true sh -c "cd /app && npm run start && rm -rf data"
COPY --from=build /app/build /app/public COPY --from=build /app/build /app/public

View File

@ -3,33 +3,24 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
<link rel="preconnect" crossorigin="anonymous" href="https://fonts.googleapis.com">
<link rel="preconnect" crossorigin="anonymous" href="https://cdnjs.cloudflare.com">
<link rel="preconnect" crossorigin="anonymous" href="https://cdn.jsdelivr.net">
<link rel="icon" href="/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="/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="/manifest.json" />
<!--
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", "/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`.
-->
<title>Chat with GPT | Unofficial ChatGPT app</title> <title>Chat with GPT | Unofficial ChatGPT app</title>
<link rel="stylesheet" media="all" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" /> <link rel="stylesheet" crossorigin="anonymous" media="all" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" />
<link rel="stylesheet" media="all" href="https://fonts.googleapis.com/css?family=Open+Sans:100,400,300,500,700,800" /> <link rel="stylesheet" crossorigin="anonymous" media="all" href="https://fonts.googleapis.com/css?family=Open+Sans:100,400,300,500,700,800" />
<link rel="stylesheet" media="all" href="https://fonts.googleapis.com/css?family=Fira+Code:100,400,300,500,700,800" /> <link rel="stylesheet" crossorigin="anonymous" media="all" href="https://fonts.googleapis.com/css?family=Fira+Code:100,400,300,500,700,800" />
<link href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,600,700&display=swap" rel="stylesheet"> <link rel="stylesheet" crossorigin="anonymous" href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,600,700&display=swap">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css" <link rel="stylesheet" crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css" integrity="sha384-Xi8rHCmBmhbuyyhbI88391ZKP2dmfnOl4rT9ZfRI7mLTdk1wblIUnrIq35nqwEvC">
integrity="sha384-Xi8rHCmBmhbuyyhbI88391ZKP2dmfnOl4rT9ZfRI7mLTdk1wblIUnrIq35nqwEvC" crossorigin="anonymous">
<link rel="stylesheet" href="/prose.css" /> <link rel="stylesheet" href="/prose.css" />
<link rel="canonical" href="https://www.chatwithgpt.ai" /> <link rel="canonical" href="https://www.chatwithgpt.ai" />
<style> <style>

View File

@ -69,6 +69,8 @@
"@vitejs/plugin-react": "^4.0.2", "@vitejs/plugin-react": "^4.0.2",
"babel-plugin-formatjs": "^10.5.3", "babel-plugin-formatjs": "^10.5.3",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^4.4.1" "vite": "^4.4.1",
"vite-plugin-pwa": "^0.16.4",
"workbox-window": "^7.0.0"
} }
} }

View File

@ -1,8 +0,0 @@
{
"short_name": "Chat with GPT",
"name": "Chat with GPT",
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -4,6 +4,7 @@ import { useChatSpotlightProps } from '../spotlight';
import { LoginModal, CreateAccountModal } from './auth-modals'; import { LoginModal, CreateAccountModal } from './auth-modals';
import Header, { HeaderProps, SubHeader } from './header'; import Header, { HeaderProps, SubHeader } from './header';
import MessageInput from './input'; import MessageInput from './input';
import { InstallUpdateNotification } from './pwa-notifications';
import SettingsDrawer from './settings'; import SettingsDrawer from './settings';
import Sidebar from './sidebar'; import Sidebar from './sidebar';
import AudioControls from './tts-controls'; import AudioControls from './tts-controls';
@ -90,6 +91,7 @@ export function Page(props: {
<SettingsDrawer /> <SettingsDrawer />
<LoginModal /> <LoginModal />
<CreateAccountModal /> <CreateAccountModal />
<InstallUpdateNotification />
</Main> </Main>
</Container> </Container>
</SpotlightProvider>; </SpotlightProvider>;

View File

@ -0,0 +1,37 @@
import { Button, Notification } from "@mantine/core";
import { useCallback } from "react";
import { useRegisterSW } from "virtual:pwa-register/react";
export function InstallUpdateNotification() {
const {
offlineReady: [_, setOfflineReady],
needRefresh: [needRefresh, setNeedRefresh],
updateServiceWorker,
} = useRegisterSW({
onRegistered(r) {
console.log("SW Registered:", r);
},
onRegisterError(error) {
console.log("SW registration error", error);
},
});
const onClose = () => {
setOfflineReady(false);
setNeedRefresh(false);
};
const onUpdate = useCallback(async () => {
updateServiceWorker(true);
}, []);
return needRefresh ? (
<Notification title="Update available!" onClose={onClose}>
Click{" "}
<Button compact onClick={onUpdate}>
Update now
</Button>{" "}
to get the latest version.
</Notification>
) : null;
}

View File

@ -86,4 +86,4 @@ async function bootstrapApplication() {
); );
} }
bootstrapApplication(); bootstrapApplication();

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';
const persistConfig = { const persistConfig = {
key: 'root', key: 'root',
@ -22,16 +31,20 @@ 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),
}, },
}) 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;

View File

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

View File

@ -1,5 +1,6 @@
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";
export default defineConfig(() => { export default defineConfig(() => {
return { return {
@ -14,6 +15,11 @@ export default defineConfig(() => {
}, },
build: { build: {
outDir: "build", outDir: "build",
target: "es2020",
sourcemap: true,
},
esbuild: {
target: "es2020",
}, },
resolve: { resolve: {
alias: { alias: {
@ -34,6 +40,90 @@ export default defineConfig(() => {
], ],
}, },
}), }),
VitePWA({
registerType: "autoUpdate",
includeAssets: ["favicon.ico", "lang/*.json"],
manifest: {
short_name: "Chat with GPT",
name: "Chat with GPT",
start_url: ".",
display: "standalone",
theme_color: "#000000",
background_color: "#ffffff",
icons: [
{
src: "logo192.png",
type: "image/png",
sizes: "192x192",
},
{
src: "logo512.png",
type: "image/png",
sizes: "512x512",
},
],
},
workbox: {
runtimeCaching: [
{
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
handler: "CacheFirst",
options: {
cacheName: "google-fonts-cache",
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365,
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
handler: "CacheFirst",
options: {
cacheName: "gstatic-fonts-cache",
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365,
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: /^https:\/\/cdnjs\.cloudflare\.com\/.*/i,
handler: "CacheFirst",
options: {
cacheName: "cloudflare-js-cdn",
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365,
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: /^https:\/\/cdn\.jsdelivr\.net\/.*/i,
handler: "CacheFirst",
options: {
cacheName: "jsdelivr-cdn",
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365,
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
},
}),
], ],
}; };
}); });