소스 검색

fix conflict

cyhhao 2 년 전
부모
커밋
efd3310711
20개의 변경된 파일334개의 추가작업 그리고 171개의 파일을 삭제
  1. 21 17
      .github/workflows/sync.yml
  2. 6 3
      README.md
  3. 6 8
      app/components/chat.tsx
  4. 47 0
      app/components/error.tsx
  5. 15 15
      app/components/home.tsx
  6. 1 0
      app/constant.ts
  7. 1 0
      app/locales/cn.ts
  8. 1 0
      app/locales/en.ts
  9. 1 0
      app/locales/es.ts
  10. 5 2
      app/locales/index.ts
  11. 164 0
      app/locales/it.ts
  12. 1 0
      app/locales/tw.ts
  13. 0 2
      app/page.tsx
  14. 27 0
      app/polyfill.ts
  15. 1 1
      app/store/app.ts
  16. 15 0
      app/styles/globals.scss
  17. 8 7
      app/styles/highlight.scss
  18. 13 9
      app/utils.ts
  19. 0 1
      package.json
  20. 1 106
      yarn.lock

+ 21 - 17
.github/workflows/sync.yml

@@ -1,29 +1,33 @@
 name: Upstream Sync
 name: Upstream Sync
 
 
+permissions:
+  contents: write
+
 on:
 on:
   schedule:
   schedule:
-    - cron: '0 */12 * * *' # every 12 hours
-  workflow_dispatch: # on button click
+    - cron: "0 */6 * * *" # every 6 hours
+  workflow_dispatch:
 
 
 jobs:
 jobs:
   sync_latest_from_upstream:
   sync_latest_from_upstream:
     name: Sync latest commits from upstream repo
     name: Sync latest commits from upstream repo
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
+    if: ${{ github.event.repository.fork }}
 
 
     steps:
     steps:
-    # Step 1: run a standard checkout action, provided by github
-    - name: Checkout target repo
-      uses: actions/checkout@v3
+      # Step 1: run a standard checkout action
+      - name: Checkout target repo
+        uses: actions/checkout@v3
+
+      # Step 2: run the sync action
+      - name: Sync upstream changes
+        id: sync
+        uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
+        with:
+          upstream_sync_repo: Yidadaa/ChatGPT-Next-Web
+          upstream_sync_branch: main
+          target_sync_branch: main
+          target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
 
 
-    # Step 2: run the sync action
-    - name: Sync upstream changes
-      id: sync
-      uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
-      with:
-        upstream_sync_repo: Yidadaa/ChatGPT-Next-Web
-        upstream_sync_branch: main
-        target_sync_branch: main
-        target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
-        
-        # Set test_mode true to run tests instead of the true action!!
-        test_mode: false
+          # Set test_mode true to run tests instead of the true action!!
+          test_mode: false

+ 6 - 3
README.md

@@ -74,7 +74,9 @@ One-Click to deploy your own ChatGPT web UI.
 - 前往 vercel 控制台,删除掉原先的 project,然后新建 project,选择你刚刚 fork 出来的项目重新进行部署即可;
 - 前往 vercel 控制台,删除掉原先的 project,然后新建 project,选择你刚刚 fork 出来的项目重新进行部署即可;
 - 在重新部署的过程中,请手动添加名为 `OPENAI_API_KEY` 的环境变量,并填入你的 api key 作为值。
 - 在重新部署的过程中,请手动添加名为 `OPENAI_API_KEY` 的环境变量,并填入你的 api key 作为值。
 
 
-本项目会持续更新,如果你想让代码库总是保持更新,可以查看 [Github 的文档](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) 了解如何让 fork 的项目与上游代码同步,建议定期进行同步操作以获得新功能。
+本项目会持续更新,当你 Fork 项目之后,默认会每天自动同步上游代码,无需额外操作。
+
+如果你想让手动立即更新,可以查看 [Github 的文档](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) 了解如何让 fork 的项目与上游代码同步。
 
 
 你可以 star/watch 本项目或者 follow 作者来及时获得新功能更新通知。
 你可以 star/watch 本项目或者 follow 作者来及时获得新功能更新通知。
 
 
@@ -87,7 +89,9 @@ We recommend that you follow the steps below to re-deploy:
 - Go to the Vercel dashboard, delete the original project, then create a new project and select the project you just forked to redeploy;
 - Go to the Vercel dashboard, delete the original project, then create a new project and select the project you just forked to redeploy;
 - Please manually add an environment variable named `OPENAI_API_KEY` and enter your API key as the value during the redeploy process.
 - Please manually add an environment variable named `OPENAI_API_KEY` and enter your API key as the value during the redeploy process.
 
 
-This project will be continuously maintained. If you want to keep the code repository up to date, you can check out the [Github documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code. It is recommended to perform synchronization operations regularly.
+This project will be continuously updated, and after forking the project, the upstream code will be automatically synchronized every day without additional operations.
+
+If you want to update instantly, you can check out the [Github documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code. 
 
 
 You can star or watch this project or follow author to get release notifictions in time.
 You can star or watch this project or follow author to get release notifictions in time.
 
 
@@ -184,7 +188,6 @@ docker run -d -p 3000:3000 -e OPENAI_API_KEY="" -e CODE="" yidadaa/chatgpt-next-
 
 
 
 
 ## 鸣谢 Special Thanks
 ## 鸣谢 Special Thanks
-
 ### 捐赠者 Sponsor
 ### 捐赠者 Sponsor
 
 
 [@mushan0x0](https://github.com/mushan0x0)
 [@mushan0x0](https://github.com/mushan0x0)

+ 6 - 8
app/components/chat.tsx

@@ -12,14 +12,7 @@ import BotIcon from "../icons/bot.svg";
 import AddIcon from "../icons/add.svg";
 import AddIcon from "../icons/add.svg";
 import DeleteIcon from "../icons/delete.svg";
 import DeleteIcon from "../icons/delete.svg";
 
 
-import {
-  Message,
-  SubmitKey,
-  useChatStore,
-  ChatSession,
-  BOT_HELLO,
-  ROLES,
-} from "../store";
+import { Message, SubmitKey, useChatStore, BOT_HELLO, ROLES } from "../store";
 
 
 import {
 import {
   copyToClipboard,
   copyToClipboard,
@@ -462,6 +455,7 @@ export function Chat(props: {
 
 
   // Auto focus
   // Auto focus
   useEffect(() => {
   useEffect(() => {
+    if (props.sideBarShowing && isMobileScreen()) return;
     inputRef.current?.focus();
     inputRef.current?.focus();
   }, []);
   }, []);
 
 
@@ -599,6 +593,7 @@ export function Chat(props: {
                         if (!isMobileScreen()) return;
                         if (!isMobileScreen()) return;
                         setUserInput(message.content);
                         setUserInput(message.content);
                       }}
                       }}
+                      onMouseOver={() => inputRef.current?.blur()}
                     >
                     >
                       <Markdown content={message.content} />
                       <Markdown content={message.content} />
                     </div>
                     </div>
@@ -633,6 +628,9 @@ export function Chat(props: {
               setAutoScroll(false);
               setAutoScroll(false);
               setTimeout(() => setPromptHints([]), 500);
               setTimeout(() => setPromptHints([]), 500);
             }}
             }}
+            onMouseOver={() => {
+              inputRef.current?.focus();
+            }}
             autoFocus={!props?.sideBarShowing}
             autoFocus={!props?.sideBarShowing}
           />
           />
           <IconButton
           <IconButton

+ 47 - 0
app/components/error.tsx

@@ -0,0 +1,47 @@
+import React from "react";
+import { IconButton } from "./button";
+import GithubIcon from "../icons/github.svg";
+import { ISSUE_URL } from "../constant";
+
+interface IErrorBoundaryState {
+  hasError: boolean;
+  error: Error | null;
+  info: React.ErrorInfo | null;
+}
+
+export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
+  constructor(props: any) {
+    super(props);
+    this.state = { hasError: false, error: null, info: null };
+  }
+
+  componentDidCatch(error: Error, info: React.ErrorInfo) {
+    // Update state with error details
+    this.setState({ hasError: true, error, info });
+  }
+
+  render() {
+    if (this.state.hasError) {
+      // Render error message
+      return (
+        <div className="error">
+          <h2>Oops, something went wrong!</h2>
+          <pre>
+            <code>{this.state.error?.toString()}</code>
+            <code>{this.state.info?.componentStack}</code>
+          </pre>
+
+          <a href={ISSUE_URL} className="report">
+            <IconButton
+              text="Report This Error"
+              icon={<GithubIcon />}
+              bordered
+            />
+          </a>
+        </div>
+      );
+    }
+    // if no error occurred, render children
+    return this.props.children;
+  }
+}

+ 15 - 15
app/components/home.tsx

@@ -1,6 +1,8 @@
 "use client";
 "use client";
 
 
-import { useState, useRef, useEffect, useLayoutEffect } from "react";
+require("../polyfill");
+
+import { useState, useEffect } from "react";
 
 
 import { IconButton } from "./button";
 import { IconButton } from "./button";
 import styles from "./home.module.scss";
 import styles from "./home.module.scss";
@@ -14,25 +16,15 @@ import AddIcon from "../icons/add.svg";
 import LoadingIcon from "../icons/three-dots.svg";
 import LoadingIcon from "../icons/three-dots.svg";
 import CloseIcon from "../icons/close.svg";
 import CloseIcon from "../icons/close.svg";
 
 
-import {
-  Message,
-  SubmitKey,
-  useChatStore,
-  ChatSession,
-  BOT_HELLO,
-} from "../store";
-import {
-  copyToClipboard,
-  downloadAs,
-  isMobileScreen,
-  selectOrCopy,
-} from "../utils";
+import { useChatStore } from "../store";
+import { isMobileScreen } from "../utils";
 import Locale from "../locales";
 import Locale from "../locales";
 import { ChatList } from "./chat-list";
 import { ChatList } from "./chat-list";
 import { Chat } from "./chat";
 import { Chat } from "./chat";
 
 
 import dynamic from "next/dynamic";
 import dynamic from "next/dynamic";
 import { REPO_URL } from "../constant";
 import { REPO_URL } from "../constant";
+import { ErrorBoundary } from "./error";
 
 
 export function Loading(props: { noLogo?: boolean }) {
 export function Loading(props: { noLogo?: boolean }) {
   return (
   return (
@@ -78,7 +70,7 @@ const useHasHydrated = () => {
   return hasHydrated;
   return hasHydrated;
 };
 };
 
 
-export function Home() {
+function _Home() {
   const [createNewSession, currentIndex, removeSession] = useChatStore(
   const [createNewSession, currentIndex, removeSession] = useChatStore(
     (state) => [
     (state) => [
       state.newSession,
       state.newSession,
@@ -191,3 +183,11 @@ export function Home() {
     </div>
     </div>
   );
   );
 }
 }
+
+export function Home() {
+  return (
+    <ErrorBoundary>
+      <_Home></_Home>
+    </ErrorBoundary>
+  );
+}

+ 1 - 0
app/constant.ts

@@ -1,6 +1,7 @@
 export const OWNER = "Yidadaa";
 export const OWNER = "Yidadaa";
 export const REPO = "ChatGPT-Next-Web";
 export const REPO = "ChatGPT-Next-Web";
 export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
 export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
+export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
 export const UPDATE_URL = `${REPO_URL}#%E4%BF%9D%E6%8C%81%E6%9B%B4%E6%96%B0-keep-updated`;
 export const UPDATE_URL = `${REPO_URL}#%E4%BF%9D%E6%8C%81%E6%9B%B4%E6%96%B0-keep-updated`;
 export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
 export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
 export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
 export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;

+ 1 - 0
app/locales/cn.ts

@@ -58,6 +58,7 @@ const cn = {
         en: "English",
         en: "English",
         tw: "繁體中文",
         tw: "繁體中文",
         es: "Español",
         es: "Español",
+        it: "Italiano",
       },
       },
     },
     },
     Avatar: "头像",
     Avatar: "头像",

+ 1 - 0
app/locales/en.ts

@@ -60,6 +60,7 @@ const en: LocaleType = {
         en: "English",
         en: "English",
         tw: "繁體中文",
         tw: "繁體中文",
         es: "Español",
         es: "Español",
+        it: "Italiano",
       },
       },
     },
     },
     Avatar: "Avatar",
     Avatar: "Avatar",

+ 1 - 0
app/locales/es.ts

@@ -60,6 +60,7 @@ const es: LocaleType = {
         en: "Inglés",
         en: "Inglés",
         tw: "繁體中文",
         tw: "繁體中文",
         es: "Español",
         es: "Español",
+        it: "Italiano",
       },
       },
     },
     },
     Avatar: "Avatar",
     Avatar: "Avatar",

+ 5 - 2
app/locales/index.ts

@@ -2,10 +2,11 @@ import CN from "./cn";
 import EN from "./en";
 import EN from "./en";
 import TW from "./tw";
 import TW from "./tw";
 import ES from "./es";
 import ES from "./es";
+import IT from "./it";
 
 
 export type { LocaleType } from "./cn";
 export type { LocaleType } from "./cn";
 
 
-export const AllLangs = ["cn", "tw", "en", "es"] as const;
+export const AllLangs = ["en", "cn", "tw", "es", "it"] as const;
 type Lang = (typeof AllLangs)[number];
 type Lang = (typeof AllLangs)[number];
 
 
 const LANG_KEY = "lang";
 const LANG_KEY = "lang";
@@ -47,6 +48,8 @@ export function getLang(): Lang {
     return "tw";
     return "tw";
   } else if (lang.includes("es")) {
   } else if (lang.includes("es")) {
     return "es";
     return "es";
+  } else if (lang.includes("it")) {
+    return "it";
   } else {
   } else {
     return "en";
     return "en";
   }
   }
@@ -57,4 +60,4 @@ export function changeLang(lang: Lang) {
   location.reload();
   location.reload();
 }
 }
 
 
-export default { en: EN, cn: CN, tw: TW, es: ES }[getLang()];
+export default { en: EN, cn: CN, tw: TW, es: ES, it: IT }[getLang()];

+ 164 - 0
app/locales/it.ts

@@ -0,0 +1,164 @@
+import { SubmitKey } from "../store/app";
+import type { LocaleType } from "./index";
+
+const it: LocaleType = {
+  WIP: "Work in progress...",
+  Error: {
+    Unauthorized:
+      "Accesso non autorizzato, inserire il codice di accesso nella pagina delle impostazioni.",
+  },
+  ChatItem: {
+    ChatItemCount: (count: number) => `${count} messaggi`,
+  },
+  Chat: {
+    SubTitle: (count: number) => `${count} messaggi con ChatGPT`,
+    Actions: {
+      ChatList: "Vai alla Chat List",
+      CompressedHistory: "Prompt di memoria della cronologia compressa",
+      Export: "Esportazione di tutti i messaggi come Markdown",
+      Copy: "Copia",
+      Stop: "Stop",
+      Retry: "Riprova",
+    },
+    Rename: "Rinomina Chat",
+    Typing: "Typing…",
+    Input: (submitKey: string) => {
+      var inputHints = `Scrivi qualcosa e premi ${submitKey} per inviare`;
+      if (submitKey === String(SubmitKey.Enter)) {
+        inputHints += ", premi Shift + Enter per andare a capo";
+      }
+      return inputHints;
+    },
+    Send: "Invia",
+  },
+  Export: {
+    Title: "Tutti i messaggi",
+    Copy: "Copia tutto",
+    Download: "Scarica",
+  },
+  Memory: {
+    Title: "Prompt di memoria",
+    EmptyContent: "Vuoto.",
+    Copy: "Copia tutto",
+  },
+  Home: {
+    NewChat: "Nuova Chat",
+    DeleteChat: "Confermare la cancellazione della conversazione selezionata?",
+  },
+  Settings: {
+    Title: "Impostazioni",
+    SubTitle: "Tutte le impostazioni",
+    Actions: {
+      ClearAll: "Cancella tutti i dati",
+      ResetAll: "Resetta tutte le impostazioni",
+      Close: "Chiudi",
+    },
+    Lang: {
+      Name: "Lingue",
+      Options: {
+        cn: "简体中文",
+        en: "English",
+        tw: "繁體中文",
+        es: "Español",
+        it: "Italiano",
+      },
+    },
+    Avatar: "Avatar",
+    FontSize: {
+      Title: "Dimensione carattere",
+      SubTitle: "Regolare la dimensione dei caratteri del contenuto della chat",
+    },
+    Update: {
+      Version: (x: string) => `Versione: ${x}`,
+      IsLatest: "Ultima versione",
+      CheckUpdate: "Controlla aggiornamenti",
+      IsChecking: "Sto controllando gli aggiornamenti...",
+      FoundUpdate: (x: string) => `Trovata nuova versione: ${x}`,
+      GoToUpdate: "Aggiorna",
+    },
+    SendKey: "Tasto invia",
+    Theme: "tema",
+    TightBorder: "Bordi stretti",
+    SendPreviewBubble: "Invia l'anteprima della bolla",
+    Prompt: {
+      Disable: {
+        Title: "Disabilita l'auto completamento",
+        SubTitle: "Input / per attivare il completamento automatico",
+      },
+      List: "Elenco dei suggerimenti",
+      ListCount: (builtin: number, custom: number) =>
+        `${builtin} built-in, ${custom} user-defined`,
+      Edit: "Modifica",
+    },
+    HistoryCount: {
+      Title: "Conteggio dei messaggi allegati",
+      SubTitle: "Numero di messaggi inviati allegati per richiesta",
+    },
+    CompressThreshold: {
+      Title: "Soglia di compressione della cronologia",
+      SubTitle:
+        "Comprimerà se la lunghezza dei messaggi non compressi supera il valore",
+    },
+    Token: {
+      Title: "Chiave API",
+      SubTitle:
+        "Utilizzare la chiave per ignorare il limite del codice di accesso",
+      Placeholder: "OpenAI API Key",
+    },
+    Usage: {
+      Title: "Bilancio Account",
+      SubTitle(used: any) {
+        return `Usato in questo mese $${used}`;
+      },
+      IsChecking: "Controllando...",
+      Check: "Controlla ancora",
+    },
+    AccessCode: {
+      Title: "Codice d'accesso",
+      SubTitle: "Controllo d'accesso abilitato",
+      Placeholder: "Inserisci il codice d'accesso",
+    },
+    Model: "Modello GPT",
+    Temperature: {
+      Title: "Temperature",
+      SubTitle: "Un valore maggiore rende l'output più casuale",
+    },
+    MaxTokens: {
+      Title: "Token massimi",
+      SubTitle: "Lunghezza massima dei token in ingresso e dei token generati",
+    },
+    PresencePenlty: {
+      Title: "Penalità di presenza",
+      SubTitle:
+        "Un valore maggiore aumenta la probabilità di parlare di nuovi argomenti",
+    },
+  },
+  Store: {
+    DefaultTopic: "Nuova conversazione",
+    BotHello: "Ciao, come posso aiutarti oggi?",
+    Error: "Qualcosa è andato storto, riprova più tardi.",
+    Prompt: {
+      History: (content: string) =>
+        "Questo è un riassunto della cronologia delle chat tra l'IA e l'utente:" +
+        content,
+      Topic:
+        "Si prega di generare un titolo di quattro o cinque parole che riassuma la nostra conversazione senza alcuna traccia, punteggiatura, virgolette, punti, simboli o testo aggiuntivo. Rimuovere le virgolette",
+      Summarize:
+        "Riassumi brevemente la nostra discussione in 200 caratteri o meno per usarla come spunto per una futura conversazione.",
+    },
+    ConfirmClearAll:
+      "Confermi la cancellazione di tutti i dati della chat e delle impostazioni?",
+  },
+  Copy: {
+    Success: "Copiato sugli appunti",
+    Failed:
+      "Copia fallita, concedere l'autorizzazione all'accesso agli appunti",
+  },
+  Context: {
+    Toast: (x: any) => `Con ${x} prompts contestuali`,
+    Edit: "Prompt contestuali e di memoria",
+    Add: "Aggiungi altro",
+  },
+};
+
+export default it;

+ 1 - 0
app/locales/tw.ts

@@ -59,6 +59,7 @@ const tw: LocaleType = {
         en: "English",
         en: "English",
         tw: "繁體中文",
         tw: "繁體中文",
         es: "Español",
         es: "Español",
+        it: "Italiano",
       },
       },
     },
     },
     Avatar: "大頭貼",
     Avatar: "大頭貼",

+ 0 - 2
app/page.tsx

@@ -1,7 +1,5 @@
 import { Analytics } from "@vercel/analytics/react";
 import { Analytics } from "@vercel/analytics/react";
 
 
-import "array.prototype.at";
-
 import { Home } from "./components/home";
 import { Home } from "./components/home";
 
 
 export default function App() {
 export default function App() {

+ 27 - 0
app/polyfill.ts

@@ -0,0 +1,27 @@
+declare global {
+  interface Array<T> {
+    at(index: number): T | undefined;
+  }
+}
+
+if (!Array.prototype.at) {
+  Array.prototype.at = function (index: number) {
+    // Get the length of the array
+    const length = this.length;
+
+    // Convert negative index to a positive index
+    if (index < 0) {
+      index = length + index;
+    }
+
+    // Return undefined if the index is out of range
+    if (index < 0 || index >= length) {
+      return undefined;
+    }
+
+    // Use Array.prototype.slice method to get value at the specified index
+    return Array.prototype.slice.call(this, index, index + 1)[0];
+  };
+}
+
+export {};

+ 1 - 1
app/store/app.ts

@@ -103,7 +103,7 @@ export function filterConfig(oldConfig: ModelConfig): Partial<ModelConfig> {
       return isValidModel(x as string);
       return isValidModel(x as string);
     },
     },
     max_tokens(x) {
     max_tokens(x) {
-      return isValidNumber(x as number, 100, 4000);
+      return isValidNumber(x as number, 100, 32000);
     },
     },
     presence_penalty(x) {
     presence_penalty(x) {
       return isValidNumber(x as number, -2, 2);
       return isValidNumber(x as number, -2, 2);

+ 15 - 0
app/styles/globals.scss

@@ -268,3 +268,18 @@ pre {
     filter: brightness(0.9);
     filter: brightness(0.9);
   }
   }
 }
 }
+
+.error {
+  width: 80%;
+  border-radius: 20px;
+  border: var(--border-in-light);
+  box-shadow: var(--card-shadow);
+  padding: 20px;
+  overflow: auto;
+  background-color: var(--white);
+  color: var(--black);
+
+  pre {
+    overflow: auto;
+  }
+}

+ 8 - 7
app/styles/highlight.scss

@@ -8,16 +8,22 @@
     font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
     font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
   }
   }
 
 
-  pre code.hljs {
+  pre code {
     display: block;
     display: block;
     overflow-x: auto;
     overflow-x: auto;
     padding: 1em;
     padding: 1em;
   }
   }
 
 
-  code.hljs {
+  code {
     padding: 3px 5px;
     padding: 3px 5px;
   }
   }
 
 
+  .hljs,
+  pre {
+    background: #1a1b26;
+    color: #cbd2ea;
+  }
+
   /*!
   /*!
   Theme: Tokyo-night-Dark
   Theme: Tokyo-night-Dark
   origin: https://github.com/enkia/tokyo-night-vscode-theme
   origin: https://github.com/enkia/tokyo-night-vscode-theme
@@ -99,11 +105,6 @@
     color: #c0caf5;
     color: #c0caf5;
   }
   }
 
 
-  .hljs {
-    background: #1a1b26;
-    color: #9aa5ce;
-  }
-
   .hljs-emphasis {
   .hljs-emphasis {
     font-style: italic;
     font-style: italic;
   }
   }

+ 13 - 9
app/utils.ts

@@ -5,15 +5,19 @@ export function trimTopic(topic: string) {
   return topic.replace(/[,。!?、,.!?]*$/, "");
   return topic.replace(/[,。!?、,.!?]*$/, "");
 }
 }
 
 
-export function copyToClipboard(text: string) {
-  navigator.clipboard
-    .writeText(text)
-    .then((res) => {
-      showToast(Locale.Copy.Success);
-    })
-    .catch((err) => {
-      showToast(Locale.Copy.Failed);
-    });
+export async function copyToClipboard(text: string) {
+  try {
+    await navigator.clipboard.writeText(text);
+  } catch (error) {
+    const textarea = document.createElement("textarea");
+    textarea.value = text;
+    document.body.appendChild(textarea);
+    textarea.select();
+    document.execCommand("copy");
+    document.body.removeChild(textarea);
+  } finally {
+    showToast(Locale.Copy.Success);
+  }
 }
 }
 
 
 export function downloadAs(text: string, filename: string) {
 export function downloadAs(text: string, filename: string) {

+ 0 - 1
package.json

@@ -14,7 +14,6 @@
   "dependencies": {
   "dependencies": {
     "@svgr/webpack": "^6.5.1",
     "@svgr/webpack": "^6.5.1",
     "@vercel/analytics": "^0.1.11",
     "@vercel/analytics": "^0.1.11",
-    "array.prototype.at": "^1.1.1",
     "emoji-picker-react": "^4.4.7",
     "emoji-picker-react": "^4.4.7",
     "eventsource-parser": "^0.1.0",
     "eventsource-parser": "^0.1.0",
     "fuse.js": "^6.6.2",
     "fuse.js": "^6.6.2",

+ 1 - 106
yarn.lock

@@ -1365,11 +1365,6 @@
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
   integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
   integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
 
 
-"@types/prismjs@^1.0.0":
-  version "1.26.0"
-  resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654"
-  integrity sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==
-
 "@types/prop-types@*", "@types/prop-types@^15.0.0":
 "@types/prop-types@*", "@types/prop-types@^15.0.0":
   version "15.7.5"
   version "15.7.5"
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
@@ -1570,16 +1565,6 @@ array-union@^2.1.0:
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
   integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
   integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
 
 
-array.prototype.at@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/array.prototype.at/-/array.prototype.at-1.1.1.tgz#6deda3cd3c704afa16361387ea344e0b8d8831b5"
-  integrity sha512-n/wYNLJy/fVEU9EGPt2ww920hy1XX3XB2yTREFy1QsxctBgQV/tZIwg1G8jVxELna4pLCzg/xvvS/DDXtI4NNg==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.4"
-    es-abstract "^1.20.4"
-    es-shim-unscopables "^1.0.0"
-
 array.prototype.flat@^1.3.1:
 array.prototype.flat@^1.3.1:
   version "1.3.1"
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2"
   resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2"
@@ -1769,21 +1754,11 @@ chalk@^4.0.0:
     ansi-styles "^4.1.0"
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
     supports-color "^7.1.0"
 
 
-character-entities-legacy@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
-  integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
-
 character-entities@^2.0.0:
 character-entities@^2.0.0:
   version "2.0.2"
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
   resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
   integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
   integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
 
 
-character-reference-invalid@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
-  integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
-
 "chokidar@>=3.0.0 <4.0.0":
 "chokidar@>=3.0.0 <4.0.0":
   version "3.5.3"
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -2879,13 +2854,6 @@ hast-util-parse-selector@^3.0.0:
   dependencies:
   dependencies:
     "@types/hast" "^2.0.0"
     "@types/hast" "^2.0.0"
 
 
-hast-util-to-string@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz#b008b0a4ea472bf34dd390b7eea1018726ae152a"
-  integrity sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==
-  dependencies:
-    "@types/hast" "^2.0.0"
-
 hast-util-to-text@^3.0.0, hast-util-to-text@^3.1.0:
 hast-util-to-text@^3.0.0, hast-util-to-text@^3.1.0:
   version "3.1.2"
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz#ecf30c47141f41e91a5d32d0b1e1859fd2ac04f2"
   resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz#ecf30c47141f41e91a5d32d0b1e1859fd2ac04f2"
@@ -2982,19 +2950,6 @@ internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5:
     has "^1.0.3"
     has "^1.0.3"
     side-channel "^1.0.4"
     side-channel "^1.0.4"
 
 
-is-alphabetical@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b"
-  integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==
-
-is-alphanumerical@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875"
-  integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==
-  dependencies:
-    is-alphabetical "^2.0.0"
-    is-decimal "^2.0.0"
-
 is-arguments@^1.1.1:
 is-arguments@^1.1.1:
   version "1.1.1"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
@@ -3063,11 +3018,6 @@ is-date-object@^1.0.1, is-date-object@^1.0.5:
   dependencies:
   dependencies:
     has-tostringtag "^1.0.0"
     has-tostringtag "^1.0.0"
 
 
-is-decimal@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7"
-  integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==
-
 is-docker@^2.0.0, is-docker@^2.1.1:
 is-docker@^2.0.0, is-docker@^2.1.1:
   version "2.2.1"
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
   resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
@@ -3095,11 +3045,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
   dependencies:
   dependencies:
     is-extglob "^2.1.1"
     is-extglob "^2.1.1"
 
 
-is-hexadecimal@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027"
-  integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==
-
 is-map@^2.0.1, is-map@^2.0.2:
 is-map@^2.0.1, is-map@^2.0.2:
   version "2.0.2"
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
   resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
@@ -4156,20 +4101,6 @@ parent-module@^1.0.0:
   dependencies:
   dependencies:
     callsites "^3.0.0"
     callsites "^3.0.0"
 
 
-parse-entities@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.1.tgz#4e2a01111fb1c986549b944af39eeda258fc9e4e"
-  integrity sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==
-  dependencies:
-    "@types/unist" "^2.0.0"
-    character-entities "^2.0.0"
-    character-entities-legacy "^3.0.0"
-    character-reference-invalid "^2.0.0"
-    decode-named-character-reference "^1.0.0"
-    is-alphanumerical "^2.0.0"
-    is-decimal "^2.0.0"
-    is-hexadecimal "^2.0.0"
-
 parse-json@^5.0.0:
 parse-json@^5.0.0:
   version "5.2.0"
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
@@ -4180,11 +4111,6 @@ parse-json@^5.0.0:
     json-parse-even-better-errors "^2.3.0"
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
     lines-and-columns "^1.1.6"
 
 
-parse-numeric-range@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3"
-  integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==
-
 parse5@^6.0.0:
 parse5@^6.0.0:
   version "6.0.1"
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
@@ -4338,16 +4264,6 @@ readdirp@~3.6.0:
   dependencies:
   dependencies:
     picomatch "^2.2.1"
     picomatch "^2.2.1"
 
 
-refractor@^4.7.0:
-  version "4.8.1"
-  resolved "https://registry.yarnpkg.com/refractor/-/refractor-4.8.1.tgz#fbdd889333a3d86c9c864479622855c9b38e9d42"
-  integrity sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==
-  dependencies:
-    "@types/hast" "^2.0.0"
-    "@types/prismjs" "^1.0.0"
-    hastscript "^7.0.0"
-    parse-entities "^4.0.0"
-
 regenerate-unicode-properties@^10.1.0:
 regenerate-unicode-properties@^10.1.0:
   version "10.1.0"
   version "10.1.0"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
@@ -4425,7 +4341,7 @@ rehype-katex@^6.0.2:
     unist-util-remove-position "^4.0.0"
     unist-util-remove-position "^4.0.0"
     unist-util-visit "^4.0.0"
     unist-util-visit "^4.0.0"
 
 
-rehype-parse@^8.0.0, rehype-parse@^8.0.2:
+rehype-parse@^8.0.0:
   version "8.0.4"
   version "8.0.4"
   resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-8.0.4.tgz#3d17c9ff16ddfef6bbcc8e6a25a99467b482d688"
   resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-8.0.4.tgz#3d17c9ff16ddfef6bbcc8e6a25a99467b482d688"
   integrity sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==
   integrity sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==
@@ -4435,18 +4351,6 @@ rehype-parse@^8.0.0, rehype-parse@^8.0.2:
     parse5 "^6.0.0"
     parse5 "^6.0.0"
     unified "^10.0.0"
     unified "^10.0.0"
 
 
-rehype-prism-plus@^1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/rehype-prism-plus/-/rehype-prism-plus-1.5.1.tgz#b5f4eb3c789a13ffe874c81039665e144bcb1cae"
-  integrity sha512-mowYefSfrIkMMxkb0fwuEXlvc5nA9b1vQ6mzujM81Qx28RI0mo7jCHsBZ2tJ4eIJKXdFn+EdPkZZBGB10K02vg==
-  dependencies:
-    hast-util-to-string "^2.0.0"
-    parse-numeric-range "^1.3.0"
-    refractor "^4.7.0"
-    rehype-parse "^8.0.2"
-    unist-util-filter "^4.0.0"
-    unist-util-visit "^4.0.0"
-
 remark-breaks@^3.0.2:
 remark-breaks@^3.0.2:
   version "3.0.2"
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/remark-breaks/-/remark-breaks-3.0.2.tgz#f466b9d3474d7323146c0149fc1496dabadd908e"
   resolved "https://registry.yarnpkg.com/remark-breaks/-/remark-breaks-3.0.2.tgz#f466b9d3474d7323146c0149fc1496dabadd908e"
@@ -4996,15 +4900,6 @@ unified@^10.0.0:
     trough "^2.0.0"
     trough "^2.0.0"
     vfile "^5.0.0"
     vfile "^5.0.0"
 
 
-unist-util-filter@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/unist-util-filter/-/unist-util-filter-4.0.1.tgz#fd885dd48adaad345de5f5dc706ec4ff44a8d074"
-  integrity sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q==
-  dependencies:
-    "@types/unist" "^2.0.0"
-    unist-util-is "^5.0.0"
-    unist-util-visit-parents "^5.0.0"
-
 unist-util-find-after@^4.0.0:
 unist-util-find-after@^4.0.0:
   version "4.0.1"
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz#80c69c92b0504033638ce11973f4135f2c822e2d"
   resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz#80c69c92b0504033638ce11973f4135f2c822e2d"