This commit is contained in:
GH Action - Upstream Sync
2023-05-18 01:23:15 +00:00
24 changed files with 1581 additions and 85 deletions

View File

@@ -3,8 +3,6 @@ import { getServerSideConfig } from "../config/server";
import md5 from "spark-md5";
import { ACCESS_CODE_PREFIX } from "../constant";
const serverConfig = getServerSideConfig();
function getIP(req: NextRequest) {
let ip = req.ip ?? req.headers.get("x-real-ip");
const forwardedFor = req.headers.get("x-forwarded-for");
@@ -34,6 +32,7 @@ export function auth(req: NextRequest) {
const hashedCode = md5.hash(accessCode ?? "").trim();
const serverConfig = getServerSideConfig();
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
console.log("[Auth] got access code:", accessCode);
console.log("[Auth] hashed access code:", hashedCode);

View File

@@ -25,10 +25,6 @@ export async function requestOpenai(req: NextRequest) {
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
}
if (!authValue || !authValue.startsWith("Bearer sk-")) {
console.error("[OpenAI Request] invalid api key provided", authValue);
}
return fetch(`${baseUrl}/${openaiPath}`, {
headers: {
"Content-Type": "application/json",

View File

@@ -3,7 +3,10 @@ import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import { ChatOptions, getHeaders, LLMApi, LLMUsage } from "../api";
import Locale from "../../locales";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import {
EventStreamContentType,
fetchEventSource,
} from "@microsoft/fetch-event-source";
import { prettyObject } from "@/app/utils/format";
export class ChatGPTApi implements LLMApi {
@@ -68,9 +71,13 @@ export class ChatGPTApi implements LLMApi {
if (shouldStream) {
let responseText = "";
let finished = false;
const finish = () => {
options.onFinish(responseText);
if (!finished) {
options.onFinish(responseText);
finished = true;
}
};
controller.signal.onabort = finish;
@@ -79,23 +86,44 @@ export class ChatGPTApi implements LLMApi {
...chatPayload,
async onopen(res) {
clearTimeout(requestTimeoutId);
if (res.status === 401) {
let extraInfo = { error: undefined };
const contentType = res.headers.get("content-type");
console.log(
"[OpenAI] request response content type: ",
contentType,
);
if (contentType?.startsWith("text/plain")) {
responseText = await res.clone().text();
return finish();
}
if (
!res.ok ||
res.headers.get("content-type") !== EventStreamContentType ||
res.status !== 200
) {
const responseTexts = [responseText];
let extraInfo = await res.clone().text();
try {
extraInfo = await res.clone().json();
const resJson = await res.clone().json();
extraInfo = prettyObject(resJson);
} catch {}
responseText += "\n\n" + Locale.Error.Unauthorized;
if (extraInfo.error) {
responseText += "\n\n" + prettyObject(extraInfo);
if (res.status === 401) {
responseTexts.push(Locale.Error.Unauthorized);
}
if (extraInfo) {
responseTexts.push(extraInfo);
}
responseText = responseTexts.join("\n\n");
return finish();
}
},
onmessage(msg) {
if (msg.data === "[DONE]") {
if (msg.data === "[DONE]" || finished) {
return finish();
}
const text = msg.data;
@@ -116,6 +144,7 @@ export class ChatGPTApi implements LLMApi {
onerror(e) {
options.onError?.(e);
},
openWhenHidden: true,
});
} else {
const res = await fetch(chatPath, chatPayload);

View File

@@ -53,7 +53,7 @@ import chatStyle from "./chat.module.scss";
import { ListItem, Modal, showModal } from "./ui-lib";
import { useLocation, useNavigate } from "react-router-dom";
import { LAST_INPUT_KEY, Path } from "../constant";
import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
import { Avatar } from "./emoji";
import { MaskAvatar, MaskConfig } from "./mask";
import { useMaskStore } from "../store/mask";
@@ -487,6 +487,16 @@ export function Chat() {
// stop response
const onUserStop = (messageId: number) => {
chatStore.updateCurrentSession((session) => {
const stopTiming = Date.now() - REQUEST_TIMEOUT_MS;
session.messages.forEach((m) => {
// check if should stop all stale messages
if (m.streaming && new Date(m.date).getTime() < stopTiming) {
m.isError = false;
m.streaming = false;
}
});
});
ChatControllerPool.stop(sessionIndex, messageId);
};

View File

@@ -73,6 +73,7 @@ const cn = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -75,6 +75,7 @@ const cs: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -76,6 +76,7 @@ const de: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -75,6 +75,7 @@ const en: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -75,6 +75,7 @@ const es: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

251
app/locales/fr.ts Normal file
View File

@@ -0,0 +1,251 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
const fr: LocaleType = {
WIP: "Prochainement...",
Error: {
Unauthorized:
"Accès non autorisé, veuillez saisir le code d'accès dans la page des paramètres.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} messages en total`,
},
Chat: {
SubTitle: (count: number) => `${count} messages échangés avec ChatGPT`,
Actions: {
ChatList: "Aller à la liste de discussion",
CompressedHistory: "Mémoire d'historique compressée Prompt",
Export: "Exporter tous les messages en tant que Markdown",
Copy: "Copier",
Stop: "Arrêter",
Retry: "Réessayer",
Delete: "Supprimer",
},
Rename: "Renommer la conversation",
Typing: "En train d'écrire…",
Input: (submitKey: string) => {
var inputHints = `Appuyez sur ${submitKey} pour envoyer`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter pour insérer un saut de ligne";
}
return inputHints + ", / pour rechercher des prompts";
},
Send: "Envoyer",
Config: {
Reset: "Restaurer les paramètres par défaut",
SaveAs: "Enregistrer en tant que masque",
},
},
Export: {
Title: "Tous les messages",
Copy: "Tout sélectionner",
Download: "Télécharger",
MessageFromYou: "Message de votre part",
MessageFromChatGPT: "Message de ChatGPT",
},
Memory: {
Title: "Prompt mémoire",
EmptyContent: "Rien encore.",
Send: "Envoyer la mémoire",
Copy: "Copier la mémoire",
Reset: "Réinitialiser la session",
ResetConfirm:
"La réinitialisation supprimera l'historique de la conversation actuelle ainsi que la mémoire de l'historique. Êtes-vous sûr de vouloir procéder à la réinitialisation?",
},
Home: {
NewChat: "Nouvelle discussion",
DeleteChat: "Confirmer la suppression de la conversation sélectionnée ?",
DeleteToast: "Conversation supprimée",
Revert: "Revenir en arrière",
},
Settings: {
Title: "Paramètres",
SubTitle: "Toutes les configurations",
Actions: {
ClearAll: "Effacer toutes les données",
ResetAll: "Réinitialiser les configurations",
Close: "Fermer",
ConfirmResetAll:
"Êtes-vous sûr de vouloir réinitialiser toutes les configurations?",
ConfirmClearAll: "Êtes-vous sûr de vouloir supprimer toutes les données?",
},
Lang: {
Name: "Language", // ATTENTION : si vous souhaitez ajouter une nouvelle traduction, ne traduisez pas cette valeur, laissez-la sous forme de `Language`
All: "Toutes les langues",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Vietnamese",
ru: "Русский",
cs: "Čeština",
},
},
Avatar: "Avatar",
FontSize: {
Title: "Taille des polices",
SubTitle: "Ajuste la taille de police du contenu de la conversation",
},
Update: {
Version: (x: string) => `Version : ${x}`,
IsLatest: "Dernière version",
CheckUpdate: "Vérifier la mise à jour",
IsChecking: "Vérification de la mise à jour...",
FoundUpdate: (x: string) => `Nouvelle version disponible : ${x}`,
GoToUpdate: "Mise à jour",
},
SendKey: "Clé d'envoi",
Theme: "Thème",
TightBorder: "Bordure serrée",
SendPreviewBubble: {
Title: "Aperçu de l'envoi dans une bulle",
SubTitle: "Aperçu du Markdown dans une bulle",
},
Mask: {
Title: "Écran de masque",
SubTitle:
"Afficher un écran de masque avant de démarrer une nouvelle discussion",
},
Prompt: {
Disable: {
Title: "Désactiver la saisie semi-automatique",
SubTitle: "Appuyez sur / pour activer la saisie semi-automatique",
},
List: "Liste de prompts",
ListCount: (builtin: number, custom: number) =>
`${builtin} intégré, ${custom} personnalisé`,
Edit: "Modifier",
Modal: {
Title: "Liste de prompts",
Add: "Ajouter un élément",
Search: "Rechercher des prompts",
},
EditModal: {
Title: "Modifier le prompt",
},
},
HistoryCount: {
Title: "Nombre de messages joints",
SubTitle: "Nombre de messages envoyés attachés par demande",
},
CompressThreshold: {
Title: "Seuil de compression de l'historique",
SubTitle:
"Comprimera si la longueur des messages non compressés dépasse cette valeur",
},
Token: {
Title: "Clé API",
SubTitle: "Utilisez votre clé pour ignorer la limite du code d'accès",
Placeholder: "Clé OpenAI API",
},
Usage: {
Title: "Solde du compte",
SubTitle(used: any, total: any) {
return `Épuisé ce mois-ci $${used}, abonnement $${total}`;
},
IsChecking: "Vérification...",
Check: "Vérifier",
NoAccess: "Entrez la clé API pour vérifier le solde",
},
AccessCode: {
Title: "Code d'accès",
SubTitle: "Contrôle d'accès activé",
Placeholder: "Code d'accès requis",
},
Model: "Modèle",
Temperature: {
Title: "Température",
SubTitle: "Une valeur plus élevée rendra les réponses plus aléatoires",
},
MaxTokens: {
Title: "Max Tokens",
SubTitle: "Longueur maximale des tokens d'entrée et des tokens générés",
},
PresencePenalty: {
Title: "Pénalité de présence",
SubTitle:
"Une valeur plus élevée augmentera la probabilité d'introduire de nouveaux sujets",
},
},
Store: {
DefaultTopic: "Nouvelle conversation",
BotHello: "Bonjour ! Comment puis-je vous aider aujourd'hui ?",
Error: "Quelque chose s'est mal passé, veuillez réessayer plus tard.",
Prompt: {
History: (content: string) =>
"Ceci est un résumé de l'historique des discussions entre l'IA et l'utilisateur : " +
content,
Topic:
"Veuillez générer un titre de quatre à cinq mots résumant notre conversation sans introduction, ponctuation, guillemets, points, symboles ou texte supplémentaire. Supprimez les guillemets inclus.",
Summarize:
"Résumez brièvement nos discussions en 200 mots ou moins pour les utiliser comme prompt de contexte futur.",
},
},
Copy: {
Success: "Copié dans le presse-papiers",
Failed:
"La copie a échoué, veuillez accorder l'autorisation d'accès au presse-papiers",
},
Context: {
Toast: (x: any) => `Avec ${x} contextes de prompts`,
Edit: "Contextes et mémoires de prompts",
Add: "Ajouter un prompt",
},
Plugin: {
Name: "Extension",
},
Mask: {
Name: "Masque",
Page: {
Title: "Modèle de prompt",
SubTitle: (count: number) => `${count} modèles de prompts`,
Search: "Rechercher des modèles",
Create: "Créer",
},
Item: {
Info: (count: number) => `${count} prompts`,
Chat: "Discussion",
View: "Vue",
Edit: "Modifier",
Delete: "Supprimer",
DeleteConfirm: "Confirmer la suppression?",
},
EditModal: {
Title: (readonly: boolean) =>
`Modifier le modèle de prompt ${readonly ? "(en lecture seule)" : ""}`,
Download: "Télécharger",
Clone: "Dupliquer",
},
Config: {
Avatar: "Avatar du bot",
Name: "Nom du bot",
},
},
NewChat: {
Return: "Retour",
Skip: "Passer",
Title: "Choisir un masque",
SubTitle: "Discutez avec l'âme derrière le masque",
More: "En savoir plus",
NotShow: "Ne pas afficher à nouveau",
ConfirmNoShow:
"Confirmez-vous vouloir désactiver cela? Vous pouvez le réactiver plus tard dans les paramètres.",
},
UI: {
Confirm: "Confirmer",
Cancel: "Annuler",
Close: "Fermer",
Create: "Créer",
Edit: "Éditer",
},
};
export default fr;

View File

@@ -1,6 +1,7 @@
import CN from "./cn";
import EN from "./en";
import TW from "./tw";
import FR from "./fr";
import ES from "./es";
import IT from "./it";
import TR from "./tr";
@@ -16,6 +17,7 @@ export const AllLangs = [
"en",
"cn",
"tw",
"fr",
"es",
"it",
"tr",
@@ -80,6 +82,7 @@ export default {
en: EN,
cn: CN,
tw: TW,
fr: FR,
es: ES,
it: IT,
tr: TR,

View File

@@ -75,6 +75,7 @@ const it: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -75,6 +75,7 @@ const jp: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -75,6 +75,7 @@ const ru: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -75,6 +75,7 @@ const tr: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -73,6 +73,7 @@ const tw: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -75,6 +75,7 @@ const vi: LocaleType = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",

View File

@@ -7,7 +7,7 @@ import Locale from "../locales";
import { showToast } from "../components/ui-lib";
import { ModelType } from "./config";
import { createEmptyMask, Mask } from "./mask";
import { StoreKey } from "../constant";
import { REQUEST_TIMEOUT_MS, StoreKey } from "../constant";
import { api, RequestMessage } from "../client/api";
import { ChatControllerPool } from "../client/controller";
import { prettyObject } from "../utils/format";