This commit is contained in:
GH Action - Upstream Sync 2023-05-12 00:58:55 +00:00
commit bfc924bc2a
22 changed files with 349 additions and 21 deletions

View File

@ -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("");
}}
/>

View File

@ -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"]}

View File

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

View File

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

View File

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

View File

@ -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
app/icons/down.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 575 B

View File

@ -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"

View File

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

245
app/locales/cs.ts Normal file
View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) => {