Sfoglia il codice sorgente

Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web

GH Action - Upstream Sync 1 anno fa
parent
commit
bfc924bc2a

+ 15 - 2
app/components/chat.tsx

@@ -230,7 +230,9 @@ export function PromptHints(props: {
   useEffect(() => {
     const onKeyDown = (e: KeyboardEvent) => {
       if (noPrompts) return;
-
+      if (e.metaKey || e.altKey || e.ctrlKey) {
+        return;
+      }
       // arrow up / down to select prompt
       const changeIndex = (delta: number) => {
         e.stopPropagation();
@@ -491,7 +493,11 @@ export function Chat() {
   // check if should send message
   const onInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
     // if ArrowUp and no userInput, fill with last input
-    if (e.key === "ArrowUp" && userInput.length <= 0) {
+    if (
+      e.key === "ArrowUp" &&
+      userInput.length <= 0 &&
+      !(e.metaKey || e.altKey || e.ctrlKey)
+    ) {
       setUserInput(localStorage.getItem(LAST_INPUT_KEY) ?? "");
       e.preventDefault();
       return;
@@ -789,7 +795,14 @@ export function Chat() {
           scrollToBottom={scrollToBottom}
           hitBottom={hitBottom}
           showPromptHints={() => {
+            // Click again to close
+            if (promptHints.length > 0) {
+              setPromptHints([]);
+              return;
+            }
+
             inputRef.current?.focus();
+            setUserInput("/");
             onSearch("");
           }}
         />

+ 5 - 5
app/components/mask.tsx

@@ -14,7 +14,7 @@ import CopyIcon from "../icons/copy.svg";
 
 import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask";
 import { Message, ModelConfig, ROLES, useChatStore } from "../store";
-import { Input, List, ListItem, Modal, Popover } from "./ui-lib";
+import { Input, List, ListItem, Modal, Popover, Select } from "./ui-lib";
 import { Avatar, AvatarPicker } from "./emoji";
 import Locale, { AllLangs, Lang } from "../locales";
 import { useNavigate } from "react-router-dom";
@@ -116,7 +116,7 @@ function ContextPromptItem(props: {
   return (
     <div className={chatStyle["context-prompt-row"]}>
       {!focusingInput && (
-        <select
+        <Select
           value={props.prompt.role}
           className={chatStyle["context-role"]}
           onChange={(e) =>
@@ -131,7 +131,7 @@ function ContextPromptItem(props: {
               {r}
             </option>
           ))}
-        </select>
+        </Select>
       )}
       <Input
         value={props.prompt.content}
@@ -307,7 +307,7 @@ export function MaskPage() {
               autoFocus
               onInput={(e) => onSearch(e.currentTarget.value)}
             />
-            <select
+            <Select
               className={styles["mask-filter-lang"]}
               value={filterLang ?? Locale.Settings.Lang.All}
               onChange={(e) => {
@@ -327,7 +327,7 @@ export function MaskPage() {
                   {Locale.Settings.Lang.Options[lang]}
                 </option>
               ))}
-            </select>
+            </Select>
 
             <IconButton
               className={styles["mask-create"]}

+ 3 - 3
app/components/model-config.tsx

@@ -2,7 +2,7 @@ import { ALL_MODELS, ModalConfigValidator, ModelConfig } from "../store";
 
 import Locale from "../locales";
 import { InputRange } from "./input-range";
-import { List, ListItem } from "./ui-lib";
+import { List, ListItem, Select } from "./ui-lib";
 
 export function ModelConfigList(props: {
   modelConfig: ModelConfig;
@@ -11,7 +11,7 @@ export function ModelConfigList(props: {
   return (
     <>
       <ListItem title={Locale.Settings.Model}>
-        <select
+        <Select
           value={props.modelConfig.model}
           onChange={(e) => {
             props.updateConfig(
@@ -27,7 +27,7 @@ export function ModelConfigList(props: {
               {v.name}
             </option>
           ))}
-        </select>
+        </Select>
       </ListItem>
       <ListItem
         title={Locale.Settings.Temperature.Title}

+ 15 - 7
app/components/settings.tsx

@@ -10,7 +10,15 @@ import ClearIcon from "../icons/clear.svg";
 import LoadingIcon from "../icons/three-dots.svg";
 import EditIcon from "../icons/edit.svg";
 import EyeIcon from "../icons/eye.svg";
-import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
+import {
+  Input,
+  List,
+  ListItem,
+  Modal,
+  PasswordInput,
+  Popover,
+  Select,
+} from "./ui-lib";
 import { ModelConfigList } from "./model-config";
 
 import { IconButton } from "./button";
@@ -368,7 +376,7 @@ export function Settings() {
           </ListItem>
 
           <ListItem title={Locale.Settings.SendKey}>
-            <select
+            <Select
               value={config.submitKey}
               onChange={(e) => {
                 updateConfig(
@@ -382,11 +390,11 @@ export function Settings() {
                   {v}
                 </option>
               ))}
-            </select>
+            </Select>
           </ListItem>
 
           <ListItem title={Locale.Settings.Theme}>
-            <select
+            <Select
               value={config.theme}
               onChange={(e) => {
                 updateConfig(
@@ -399,11 +407,11 @@ export function Settings() {
                   {v}
                 </option>
               ))}
-            </select>
+            </Select>
           </ListItem>
 
           <ListItem title={Locale.Settings.Lang.Name}>
-            <select
+            <Select
               value={getLang()}
               onChange={(e) => {
                 changeLang(e.target.value as any);
@@ -414,7 +422,7 @@ export function Settings() {
                   {Locale.Settings.Lang.Options[lang]}
                 </option>
               ))}
-            </select>
+            </Select>
           </ListItem>
 
           <ListItem

+ 25 - 0
app/components/ui-lib.module.scss

@@ -203,3 +203,28 @@
   resize: none;
   min-width: 50px;
 }
+
+.select-with-icon {
+  position: relative;
+  max-width: fit-content;
+  
+  .select-with-icon-select {
+    height: 100%;
+    border: var(--border-in-light);
+    padding: 10px 25px 10px 10px;
+    border-radius: 10px;
+    appearance: none;
+    cursor: pointer;
+    background-color: var(--white);
+    color: var(--black);
+    text-align: center;
+  }
+
+  .select-with-icon-icon {
+    position: absolute;
+    top: 50%;
+    right: 10px;
+    transform: translateY(-50%);
+    pointer-events: none;
+  }
+}

+ 18 - 0
app/components/ui-lib.tsx

@@ -3,6 +3,7 @@ import LoadingIcon from "../icons/three-dots.svg";
 import CloseIcon from "../icons/close.svg";
 import EyeIcon from "../icons/eye.svg";
 import EyeOffIcon from "../icons/eye-off.svg";
+import DownIcon from "../icons/down.svg";
 
 import { createRoot } from "react-dom/client";
 import React, { HTMLProps, useEffect, useState } from "react";
@@ -244,3 +245,20 @@ export function PasswordInput(props: HTMLProps<HTMLInputElement>) {
     </div>
   );
 }
+
+export function Select(
+  props: React.DetailedHTMLProps<
+    React.SelectHTMLAttributes<HTMLSelectElement>,
+    HTMLSelectElement
+  >,
+) {
+  const { className, children, ...otherProps } = props;
+  return (
+    <div className={`${styles["select-with-icon"]} ${className}`}>
+      <select className={styles["select-with-icon-select"]} {...otherProps}>
+        {children}
+      </select>
+      <DownIcon className={styles["select-with-icon-icon"]} />
+    </div>
+  );
+}

+ 1 - 0
app/icons/down.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0)  rotate(-90 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path  id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6.333333333333333 4)  rotate(0 2 4)" d="M4,8L0,4L4,0 " /></g></g></svg>

+ 4 - 3
app/layout.tsx

@@ -13,7 +13,7 @@ export const metadata = {
     title: "ChatGPT Next Web",
     statusBarStyle: "default",
   },
-  themeColor: "#fafafa",
+  viewport: "width=device-width, initial-scale=1, maximum-scale=1",
 };
 
 export default function RootLayout({
@@ -25,8 +25,9 @@ export default function RootLayout({
     <html lang="en">
       <head>
         <meta
-          name="viewport"
-          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
+          name="theme-color"
+          content="#fafafa"
+          media="(prefers-color-scheme: light)"
         />
         <meta
           name="theme-color"

+ 1 - 0
app/locales/cn.ts

@@ -80,6 +80,7 @@ const cn = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "头像",

+ 245 - 0
app/locales/cs.ts

@@ -0,0 +1,245 @@
+import { SubmitKey } from "../store/config";
+import type { LocaleType } from "./index";
+
+const cs: LocaleType = {
+  WIP: "V přípravě...",
+  Error: {
+    Unauthorized:
+      "Neoprávněný přístup, zadejte přístupový kód na stránce nastavení.",
+  },
+  ChatItem: {
+    ChatItemCount: (count: number) => `${count} zpráv`,
+  },
+  Chat: {
+    SubTitle: (count: number) => `${count} zpráv s ChatGPT`,
+    Actions: {
+      ChatList: "Přejít na seznam chatů",
+      CompressedHistory: "Pokyn z komprimované paměti historie",
+      Export: "Exportovat všechny zprávy jako Markdown",
+      Copy: "Kopírovat",
+      Stop: "Zastavit",
+      Retry: "Zopakovat",
+      Delete: "Smazat",
+    },
+    Rename: "Přejmenovat chat",
+    Typing: "Píše...",
+    Input: (submitKey: string) => {
+      var inputHints = `${submitKey} pro odeslání`;
+      if (submitKey === String(SubmitKey.Enter)) {
+        inputHints += ", Shift + Enter pro řádkování";
+      }
+      return inputHints + ", / pro vyhledávání pokynů";
+    },
+    Send: "Odeslat",
+    Config: {
+      Reset: "Obnovit výchozí",
+      SaveAs: "Uložit jako Masku",
+    },
+  },
+  Export: {
+    Title: "Všechny zprávy",
+    Copy: "Kopírovat vše",
+    Download: "Stáhnout",
+    MessageFromYou: "Zpráva od vás",
+    MessageFromChatGPT: "Zpráva z ChatGPT",
+  },
+  Memory: {
+    Title: "Pokyn z paměti",
+    EmptyContent: "Zatím nic.",
+    Send: "Odeslat paměť",
+    Copy: "Kopírovat paměť",
+    Reset: "Obnovit relaci",
+    ResetConfirm:
+      "Resetováním se vymaže historie aktuálních konverzací i paměť historie pokynů. Opravdu chcete provést obnovu?",
+  },
+  Home: {
+    NewChat: "Nový chat",
+    DeleteChat: "Potvrzujete smazání vybrané konverzace?",
+    DeleteToast: "Chat smazán",
+    Revert: "Zvrátit",
+  },
+  Settings: {
+    Title: "Nastavení",
+    SubTitle: "Všechna nastavení",
+    Actions: {
+      ClearAll: "Vymazat všechna data",
+      ResetAll: "Obnovit veškeré nastavení",
+      Close: "Zavřít",
+      ConfirmResetAll: "Jste si jisti, že chcete obnovit všechna nastavení?",
+      ConfirmClearAll: "Jste si jisti, že chcete smazat všechna data?",
+    },
+    Lang: {
+      Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
+        All: "Všechny jazyky",
+        Options: {
+        cn: "简体中文",
+        en: "English",
+        tw: "繁體中文",
+        es: "Español",
+        it: "Italiano",
+        tr: "Türkçe",
+        jp: "日本語",
+        de: "Deutsch",
+        vi: "Vietnamese",
+        ru: "Русский",
+        cs: "Čeština",
+      },
+    },
+    Avatar: "Avatar",
+    FontSize: {
+      Title: "Velikost písma",
+      SubTitle: "Nastavení velikosti písma obsahu chatu",
+    },
+    Update: {
+      Version: (x: string) => `Verze: ${x}`,
+      IsLatest: "Aktuální verze",
+      CheckUpdate: "Zkontrolovat aktualizace",
+      IsChecking: "Kontrola aktualizace...",
+      FoundUpdate: (x: string) => `Nalezena nová verze: ${x}`,
+      GoToUpdate: "Aktualizovat",
+    },
+    SendKey: "Odeslat klíč",
+    Theme: "Téma",
+    TightBorder: "Těsné ohraničení",
+    SendPreviewBubble: {
+      Title: "Odesílat chatovací bublinu s náhledem",
+      SubTitle: "Zobrazit v náhledu bubliny",
+    },
+    Mask: {
+      Title: "Úvodní obrazovka Masek",
+      SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
+    },
+    Prompt: {
+      Disable: {
+        Title: "Deaktivovat automatické dokončování",
+        SubTitle: "Zadejte / pro spuštění automatického dokončování",
+      },
+      List: "Seznam pokynů",
+      ListCount: (builtin: number, custom: number) =>
+        `${builtin} vestavěných, ${custom} uživatelských`,
+      Edit: "Upravit",
+      Modal: {
+        Title: "Seznam pokynů",
+        Add: "Přidat pokyn",
+        Search: "Hledat pokyny",
+      },
+      EditModal: {
+        Title: "Editovat pokyn",
+      },
+    },
+    HistoryCount: {
+      Title: "Počet připojených zpráv",
+      SubTitle: "Počet odeslaných připojených zpráv na žádost",
+    },
+    CompressThreshold: {
+      Title: "Práh pro kompresi historie",
+      SubTitle:
+        "Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu",
+    },
+    Token: {
+      Title: "API klíč",
+      SubTitle: "Použitím klíče ignorujete omezení přístupového kódu",
+      Placeholder: "Klíč API OpenAI",
+    },
+    Usage: {
+      Title: "Stav účtu",
+      SubTitle(used: any, total: any) {
+        return `Použito tento měsíc $${used}, předplaceno $${total}`;
+      },
+      IsChecking: "Kontroluji...",
+      Check: "Zkontrolovat",
+      NoAccess: "Pro kontrolu zůstatku zadejte klíč API",
+    },
+    AccessCode: {
+      Title: "Přístupový kód",
+      SubTitle: "Kontrola přístupu povolena",
+      Placeholder: "Potřebujete přístupový kód",
+    },
+    Model: "Model",
+    Temperature: {
+      Title: "Teplota",
+      SubTitle: "Větší hodnota činí výstup náhodnějším",
+    },
+    MaxTokens: {
+      Title: "Max. počet tokenů",
+      SubTitle: "Maximální délka vstupního tokenu a generovaných tokenů",
+    },
+    PresencePenlty: {
+      Title: "Přítomnostní korekce",
+      SubTitle:
+        "Větší hodnota zvyšuje pravděpodobnost nových témat.",
+    },
+  },
+  Store: {
+    DefaultTopic: "Nová konverzace",
+    BotHello: "Ahoj! Jak mohu dnes pomoci?",
+    Error: "Něco se pokazilo, zkuste to prosím později.",
+    Prompt: {
+      History: (content: string) =>
+        "Toto je shrnutí historie chatu mezi umělou inteligencí a uživatelem v podobě rekapitulace: " +
+        content,
+      Topic:
+        "Vytvořte prosím název o čtyřech až pěti slovech vystihující průběh našeho rozhovoru bez jakýchkoli úvodních slov, interpunkčních znamének, uvozovek, teček, symbolů nebo dalšího textu. Odstraňte uvozovky.",
+      Summarize:
+        "Krátce shrň naši diskusi v rozsahu do 200 slov a použij ji jako podnět pro budoucí kontext.",
+      },
+  },
+  Copy: {
+    Success: "Zkopírováno do schránky",
+    Failed: "Kopírování selhalo, prosím, povolte přístup ke schránce",
+  },
+  Context: {
+    Toast: (x: any) => `Použití ${x} kontextových pokynů`,
+    Edit: "Kontextové a paměťové pokyny",
+    Add: "Přidat pokyn",
+  },
+  Plugin: {
+    Name: "Plugin",
+  },
+  Mask: {
+    Name: "Maska",
+    Page: {
+      Title: "Šablona pokynu",
+      SubTitle: (count: number) => `${count} šablon pokynů`,
+      Search: "Hledat v šablonách",
+      Create: "Vytvořit",
+    },
+    Item: {
+      Info: (count: number) => `${count} pokynů`,
+      Chat: "Chat",
+      View: "Zobrazit",
+      Edit: "Upravit",
+      Delete: "Smazat",
+      DeleteConfirm: "Potvrdit smazání?",
+    },
+    EditModal: {
+      Title: (readonly: boolean) =>
+        `Editovat šablonu pokynu ${readonly ? "(pouze ke čtení)" : ""}`,
+      Download: "Stáhnout",
+      Clone: "Duplikovat",
+    },
+    Config: {
+      Avatar: "Avatar Bota",
+      Name: "Jméno Bota",
+    },
+  },
+  NewChat: {
+    Return: "Zpět",
+    Skip: "Přeskočit",
+    Title: "Vyberte Masku",
+    SubTitle: "Chatovat s duší za Maskou",
+    More: "Najít více",
+    NotShow: "Nezobrazovat znovu",
+    ConfirmNoShow: "Potvrdit zakázání?Můžete jej povolit později v nastavení.",
+},
+
+  UI: {
+    Confirm: "Potvrdit",
+    Cancel: "Zrušit",
+    Close: "Zavřít",
+    Create: "Vytvořit",
+    Edit: "Upravit",
+  }
+};
+
+export default cs;

+ 1 - 0
app/locales/de.ts

@@ -83,6 +83,7 @@ const de: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "Avatar",

+ 1 - 0
app/locales/en.ts

@@ -82,6 +82,7 @@ const en: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "Avatar",

+ 1 - 0
app/locales/es.ts

@@ -82,6 +82,7 @@ const es: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "Avatar",

+ 3 - 0
app/locales/index.ts

@@ -8,6 +8,7 @@ import JP from "./jp";
 import DE from "./de";
 import VI from "./vi";
 import RU from "./ru";
+import CS from "./cs";
 
 export type { LocaleType } from "./cn";
 
@@ -22,6 +23,7 @@ export const AllLangs = [
   "de",
   "vi",
   "ru",
+  "cs",
 ] as const;
 export type Lang = (typeof AllLangs)[number];
 
@@ -85,4 +87,5 @@ export default {
   de: DE,
   vi: VI,
   ru: RU,
+  cs: CS,
 }[getLang()] as typeof CN;

+ 1 - 0
app/locales/it.ts

@@ -82,6 +82,7 @@ const it: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "Avatar",

+ 1 - 0
app/locales/jp.ts

@@ -82,6 +82,7 @@ const jp: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "アバター",

+ 1 - 0
app/locales/ru.ts

@@ -82,6 +82,7 @@ const ru: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
       Avatar: "Аватар",

+ 1 - 0
app/locales/tr.ts

@@ -82,6 +82,7 @@ const tr: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "Avatar",

+ 1 - 0
app/locales/tw.ts

@@ -80,6 +80,7 @@ const tw: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "大頭貼",

+ 1 - 0
app/locales/vi.ts

@@ -82,6 +82,7 @@ const vi: LocaleType = {
         de: "Deutsch",
         vi: "Vietnamese",
         ru: "Русский",
+        cs: "Čeština",
       },
     },
     Avatar: "Ảnh đại diện",

+ 1 - 1
app/requests.ts

@@ -43,7 +43,7 @@ const makeRequestParam = (
   };
 };
 
-function getHeaders() {
+export function getHeaders() {
   const accessStore = useAccessStore.getState();
   let headers: Record<string, string> = {};
 

+ 4 - 0
app/store/access.ts

@@ -1,6 +1,7 @@
 import { create } from "zustand";
 import { persist } from "zustand/middleware";
 import { StoreKey } from "../constant";
+import { getHeaders } from "../requests";
 import { BOT_HELLO } from "./chat";
 import { ALL_MODELS } from "./config";
 
@@ -55,6 +56,9 @@ export const useAccessStore = create<AccessControlStore>()(
         fetch("/api/config", {
           method: "post",
           body: null,
+          headers: {
+            ...getHeaders(),
+          },
         })
           .then((res) => res.json())
           .then((res: DangerConfig) => {