mirror of
https://github.com/deiucanta/chatpad.git
synced 2026-03-11 09:04:31 +00:00
Merge 0e68ebb4f7 into 9e64f19c12
This commit is contained in:
commit
451150cffc
26 changed files with 828 additions and 212 deletions
|
|
@ -18,8 +18,8 @@
|
|||
"@types/react-dom": "^18.0.11",
|
||||
"buffer": "^5.7.1",
|
||||
"parcel": "^2.8.3",
|
||||
"path-browserify": "^1.0.1",
|
||||
"parcel-reporter-static-files-copy": "^1.5.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"process": "^0.11.10"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -32,8 +32,8 @@
|
|||
"@tabler/icons-react": "^2.9.0",
|
||||
"@tanstack/react-location": "^3.7.4",
|
||||
"@types/node": "18.15.0",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"dexie": "^3.2.3",
|
||||
"dexie-export-import": "^4.0.6",
|
||||
"dexie-react-hooks": "^1.1.3",
|
||||
|
|
@ -41,6 +41,8 @@
|
|||
"eslint": "8.36.0",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"gpt-token-utils": "^1.2.0",
|
||||
"i18next": "^22.4.15",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "^4.0.1",
|
||||
"next": "13.2.4",
|
||||
|
|
@ -48,6 +50,7 @@
|
|||
"openai-ext": "^1.2.6",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-markdown": "^8.0.6",
|
||||
"remark-gfm": "^3.0.1",
|
||||
|
|
|
|||
157
src/assets/i18n/en.json
Normal file
157
src/assets/i18n/en.json
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
{
|
||||
"translation": {
|
||||
"notAnotherUI": "Not just another ChatGPT user-interface!",
|
||||
"changeOpenAIKey": "Change OpenAI Key",
|
||||
"enterOpenAIKey": "Enter OpenAI Key",
|
||||
"downloadDesktopApp": "Download Desktop App",
|
||||
"freeAndOpenSource": {
|
||||
"title": "Free and open source",
|
||||
"description": "This app is provided for free and the source code is available on GitHub."
|
||||
},
|
||||
"privacyFocused": {
|
||||
"title": "Privacy focused",
|
||||
"description": "No tracking, no cookies, no bullshit. All your data is stored locally."
|
||||
},
|
||||
"bestExperience": {
|
||||
"title": "Best experience",
|
||||
"description": "Crafted with love and care to provide the best experience possible."
|
||||
},
|
||||
"search": "Search",
|
||||
"lightMode": "Light Mode",
|
||||
"darkMode": "Dark Mode",
|
||||
"settings": "Settings",
|
||||
"database": "Database",
|
||||
"sourceCode": "Source Code",
|
||||
"followOnTwitter": "Follow on Twitter",
|
||||
"giveFeedback": "Give Feedback",
|
||||
"chats": "Chats",
|
||||
"newChat": "New Chat",
|
||||
"prompts": "Prompts",
|
||||
"newPrompt": "New Prompt",
|
||||
"savePrompt": "Save Prompt",
|
||||
"createPrompt": "Create Prompt",
|
||||
"savedPrompt": {
|
||||
"title": "Saved",
|
||||
"message": "Prompt created"
|
||||
},
|
||||
"error": {
|
||||
"title": "Error"
|
||||
},
|
||||
"networkError": {
|
||||
"title": "Error",
|
||||
"message": "No internet connection."
|
||||
},
|
||||
"chatIdError": {
|
||||
"title": "Error",
|
||||
"message": "chatId is not defined. Please create a chat to get started."
|
||||
},
|
||||
"apiKeyError": {
|
||||
"title": "Error",
|
||||
"message": "OpenAI API Key is not defined. Please set your API Key"
|
||||
},
|
||||
"title": "Title",
|
||||
"content": "Content",
|
||||
"save": "save",
|
||||
"youAre": "You are",
|
||||
"respondInTone": "Respond in",
|
||||
"respondInStyle": "Respond in",
|
||||
"tone": "tone",
|
||||
"style": "style",
|
||||
"youAreChatGPT": "You are ChatGPT, a large language model trained by OpenAI.",
|
||||
"defineTitleChat": "What would be a short and relevant title for this chat ? You must strictly answer with only the title, no other text is allowed.",
|
||||
"character": "Character",
|
||||
"toneList": "Tone",
|
||||
"styleList": "Style",
|
||||
"format": "Format",
|
||||
"yourMessageHere": "Your message here...",
|
||||
"deleteChats": "Delete Chats",
|
||||
"confirmDeleteChats": "Are you sure you want to delete your chats?",
|
||||
"deleteChat": "Delete Chat",
|
||||
"confirmDeleteChat": "Are you sure you want to delete this chat?",
|
||||
"delete": "Delete",
|
||||
"deleted": "Deleted",
|
||||
"chatDeleted": "Chat deleted.",
|
||||
"removeChatError": {
|
||||
"title": "Error",
|
||||
"message": "Can't remove chat. Please refresh the page and try again."
|
||||
},
|
||||
"openAIAPIKey": "OpenAI API Key",
|
||||
"getYourOpenAIAPIkey": "Get your OpenAI API key",
|
||||
"APIKeyStoredLocally": "The API Key is stored locally on your browser and never sent anywhere else.",
|
||||
"openAIType": "OpenAI Type",
|
||||
"openAIKeySaved": {
|
||||
"title": "saved",
|
||||
"message": "Your OpenAI Key has been saved."
|
||||
},
|
||||
"openAITypeSaved": {
|
||||
"title": "saved",
|
||||
"message": "Your OpenAI Type has been saved."
|
||||
},
|
||||
"openIATTypeCustom": "Custom (e.g. Azure OpenAI)",
|
||||
"openAIModel": "OpenAI Model (OpenAI Only)",
|
||||
"openAIModelSaved": {
|
||||
"title": "saved",
|
||||
"message": "Your OpenAI Model has been saved."
|
||||
},
|
||||
"warning": "Warning",
|
||||
"warningCostMessage": "The displayed cost was not updated yet to reflect the costs for each model. Right now it will always show the cost for GPT-3.5 on OpenAI.",
|
||||
"openAIAuth": "OpenAI Auth (Custom Only)",
|
||||
"openAIAuthSaved": {
|
||||
"title": "saved",
|
||||
"message": "Your OpenAI Auth has been saved."
|
||||
},
|
||||
"none": "None",
|
||||
"bearerToken": "Bearer Token",
|
||||
"apiKey": "API Key",
|
||||
"openAIBaseSaved": {
|
||||
"title": "saved",
|
||||
"message": "Your OpenAI Base has been saved."
|
||||
},
|
||||
"openAIAPIBase": "OpenAI API Base (Custom Only)",
|
||||
"openAIAPIVersion": "OpenAI API Version (Custom Only)",
|
||||
"openAIVersionSaved": {
|
||||
"title": "saved",
|
||||
"message": "Your OpenAI Version has been saved."
|
||||
},
|
||||
"newChatFromPrompt": "New Chat From Prompt",
|
||||
"copied": "Copied",
|
||||
"copy": "Copy",
|
||||
"words": "words",
|
||||
"editPrompt": "Edit Prompt",
|
||||
"promptUpdated": {
|
||||
"title": "saved",
|
||||
"message": "Prompt updated"
|
||||
},
|
||||
"editChat": "Edit Chat",
|
||||
"chatUpdated": {
|
||||
"title": "saved",
|
||||
"message": "Chat updated"
|
||||
},
|
||||
"name": "Name",
|
||||
"deleteAllData": "Delete All Data",
|
||||
"confirmDeleteAllData": "Are you sure you want to delete your data?",
|
||||
"messages": "Messages",
|
||||
"exportingData" : {
|
||||
"title": "Exporting Data",
|
||||
"message": "Your data is being exported."
|
||||
},
|
||||
"exportData": "Export Data",
|
||||
"importingData" : {
|
||||
"title": "Import Data",
|
||||
"message": "Your data is being imported."
|
||||
},
|
||||
"importData": "Import Data",
|
||||
"selectedFileError": {
|
||||
"title": "Error",
|
||||
"message": "The file you selected is invalid"
|
||||
},
|
||||
"edit": "Edit",
|
||||
"promptDeleted": "Prompt deleted.",
|
||||
"removePromptError": {
|
||||
"title": "Error",
|
||||
"message": "Can't remove prompt. Please refresh the page and try again."
|
||||
},
|
||||
"deletePrompt": "Delete Prompt",
|
||||
"confirmDeletePrompt": "Are you sure you want to delete this prompt?"
|
||||
}
|
||||
}
|
||||
157
src/assets/i18n/fr.json
Normal file
157
src/assets/i18n/fr.json
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
{
|
||||
"translation": {
|
||||
"notAnotherUI": "Ce n'est pas juste une autre interface de ChatGPT !",
|
||||
"changeOpenAIKey": "Modifier la clé API d'OpenAI",
|
||||
"enterOpenAIKey": "Entrer la clé API d'OpenAI",
|
||||
"downloadDesktopApp": "Télécharger l'application de bureau",
|
||||
"freeAndOpenSource": {
|
||||
"title": "Gratuit et open source",
|
||||
"description": "Cette application est gratuite et le code source est disponible sur GitHub."
|
||||
},
|
||||
"privacyFocused": {
|
||||
"title": "Centré sur la vie privée",
|
||||
"description": "Pas de suivi, pas de cookies, pas de mensonge. Toutes vos données sont stockées localement."
|
||||
},
|
||||
"bestExperience": {
|
||||
"title": "La meilleure expérience",
|
||||
"description": "Fabriqué avec amour et soin pour offrir la meilleure expérience possible."
|
||||
},
|
||||
"search": "Rechercher",
|
||||
"lightMode": "Mode clair",
|
||||
"darkMode": "Mode sombre",
|
||||
"settings": "Paramètres",
|
||||
"database": "Base de données",
|
||||
"sourceCode": "Code source",
|
||||
"followOnTwitter": "Suivez-nous sur Twitter",
|
||||
"giveFeedback": "Donnez votre avis",
|
||||
"chats": "Conversations",
|
||||
"newChat": "Nouvelle conversation",
|
||||
"prompts": "Prompts",
|
||||
"newPrompt": "Nouveau Prompt",
|
||||
"savePrompt": "Enregistrer le Prompt",
|
||||
"createPrompt": "Créer un Prompt",
|
||||
"savedPrompt": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Prompt créée"
|
||||
},
|
||||
"error": {
|
||||
"title": "Erreur"
|
||||
},
|
||||
"networkError": {
|
||||
"title": "Erreur",
|
||||
"message": "Pas de connexion internet."
|
||||
},
|
||||
"chatIdError": {
|
||||
"title": "Erreur",
|
||||
"message": "le ChatId n'est pas défini. Veuillez créer une conversation pour commencer."
|
||||
},
|
||||
"apiKeyError": {
|
||||
"title": "Erreur",
|
||||
"message": "La clé API OpenAI n'est pas définie. Veuillez définir votre clé API"
|
||||
},
|
||||
"title": "Titre",
|
||||
"content": "Contenu",
|
||||
"save": "Enregistrer",
|
||||
"youAre": "Tu es",
|
||||
"respondInTone": "Réponds sur un ton",
|
||||
"respondInStyle": "Réponds dans un style",
|
||||
"tone": "",
|
||||
"style": "",
|
||||
"youAreChatGPT": "Tu es ChatGPT, un modèle de langage avancé entraîné par OpenAI.",
|
||||
"defineTitleChat": "Titre court et pertinent pour cette discussion ? Vous devez répondre strictement avec le titre, aucun autre texte n'est autorisé.",
|
||||
"character": "Personnage",
|
||||
"toneList": "Ton",
|
||||
"styleList": "Style",
|
||||
"format": "Format",
|
||||
"yourMessageHere": "Votre message ici...",
|
||||
"deleteChats": "Supprimer les conversations",
|
||||
"confirmDeleteChats": "Êtes-vous sûr de vouloir supprimer les conversations ?",
|
||||
"deleteChat": "Supprimer la conversation",
|
||||
"confirmDeleteChat": "Êtes-vous sûr de vouloir supprimer la conversation ?",
|
||||
"delete": "Supprimer",
|
||||
"deleted": "Supprimée",
|
||||
"chatDeleted": "Conversation supprimée.",
|
||||
"removeChatError": {
|
||||
"title": "Erreur",
|
||||
"message": "Impossible de supprimer la conversation. Veuillez actualiser la page et réessayer."
|
||||
},
|
||||
"openAIAPIKey": "Clé API OpenAI",
|
||||
"getYourOpenAIAPIkey": "Obtenez votre clé API OpenAI",
|
||||
"APIKeyStoredLocally": "La clé API est stockée localement sur votre navigateur et n'est jamais envoyée ailleurs.",
|
||||
"openAIType": "Type d'OpenAI",
|
||||
"openAIKeySaved": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Votre clé OpenAI a été enregistrée."
|
||||
},
|
||||
"openAITypeSaved": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Votre type d'OpenAI a été enregistré."
|
||||
},
|
||||
"openIATTypeCustom": "Personnalisé (par exemple: Azure OpenAI)",
|
||||
"openAIModel": "Modèle OpenAI (OpenAI seulement)",
|
||||
"openAIModelSaved": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Votre modèle OpenAI a été enregistré."
|
||||
},
|
||||
"warning": "Avertissement",
|
||||
"warningCostMessage": "Le coût affiché n'a pas encore été mis à jour pour refléter les coûts de chaque modèle. Pour l'instant, le coût affiché est toujours celui du modèle GPT-3.5 de l'OpenAI.",
|
||||
"openAIAuth": "Methode d'authentification OpenAI (personnalisé seulement)",
|
||||
"openAIAuthSaved": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Votre methode d'authentification OpenAI a été enregistré."
|
||||
},
|
||||
"none": "Aucune",
|
||||
"bearerToken": "Bearer Token",
|
||||
"apiKey": "Clé API",
|
||||
"openAIBaseSaved": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Votre URL d'API OpenAI a été enregistrée."
|
||||
},
|
||||
"openAIAPIBase": "URL d'API OpenAI (personnalisé uniquement)",
|
||||
"openAIAPIVersion": "Version de l'API OpenAI (personnalisé uniquement)",
|
||||
"openAIVersionSaved": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Votre version OpenAI a été enregistrée."
|
||||
},
|
||||
"newChatFromPrompt": "Nouvelle conversattion à partir du prompt",
|
||||
"copied": "Copié !",
|
||||
"copy": "Copier",
|
||||
"words": "mots",
|
||||
"editPrompt": "Modifier le prompt",
|
||||
"promptUpdated": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Prompt enregistré"
|
||||
},
|
||||
"editChat": "Modifier la conversation",
|
||||
"chatUpdated": {
|
||||
"title": "Sauvegardé",
|
||||
"message": "Conversation enregistrée"
|
||||
},
|
||||
"name": "Nom",
|
||||
"deleteAllData": "Supprimer toutes les données",
|
||||
"confirmDeleteAllData": "Êtes-vous sûr de vouloir effacer toutes vos données ?",
|
||||
"messages": "Messages",
|
||||
"exportingData" : {
|
||||
"title": "Exportation de données",
|
||||
"message": "Vos données sont en cours d'exportation."
|
||||
},
|
||||
"exportData": "Exporter les données",
|
||||
"importingData" : {
|
||||
"title": "Importation de données",
|
||||
"message": "Vos données sont en cours d'importation."
|
||||
},
|
||||
"importData": "Importer les données",
|
||||
"selectedFileError": {
|
||||
"title": "Erreur",
|
||||
"message": "Le fichier sélectionné n'est pas valide"
|
||||
},
|
||||
"edit": "Modifier",
|
||||
"promptDeleted": "Prompt supprimée.",
|
||||
"removePromptError": {
|
||||
"title": "Erreur",
|
||||
"message": "Impossible de supprimer le prompt. Veuillez rafraîchir la page et réessayer."
|
||||
},
|
||||
"deletePrompt": "Supprimer le prompt",
|
||||
"confirmDeletePrompt": "Êtes-vous sûr de vouloir supprimer ce prompt ?"
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
import { ChatRoute } from "../routes/ChatRoute";
|
||||
import { IndexRoute } from "../routes/IndexRoute";
|
||||
import { Layout } from "./Layout";
|
||||
import '../i18n';
|
||||
|
||||
const history = createHashHistory();
|
||||
const location = new ReactLocation({ history });
|
||||
|
|
@ -30,7 +31,6 @@ export function App() {
|
|||
setColorScheme(value || (colorScheme === "dark" ? "light" : "dark"));
|
||||
|
||||
useHotkeys([["mod+J", () => toggleColorScheme()]]);
|
||||
|
||||
return (
|
||||
<Router
|
||||
location={location}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useChatId } from "../hooks/useChatId";
|
|||
import { DeleteChatModal } from "./DeleteChatModal";
|
||||
import { EditChatModal } from "./EditChatModal";
|
||||
import { MainLink } from "./MainLink";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function Chats({ search }: { search: string }) {
|
||||
const chatId = useChatId();
|
||||
|
|
@ -55,10 +56,10 @@ export function Chats({ search }: { search: string }) {
|
|||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<EditChatModal chat={chat}>
|
||||
<Menu.Item>Edit</Menu.Item>
|
||||
<Menu.Item>{t('edit')}</Menu.Item>
|
||||
</EditChatModal>
|
||||
<DeleteChatModal chat={chat}>
|
||||
<Menu.Item>Delete</Menu.Item>
|
||||
<Menu.Item>{t('delete')}</Menu.Item>
|
||||
</DeleteChatModal>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { IconPlaylistAdd, IconPlus } from "@tabler/icons-react";
|
|||
import { nanoid } from "nanoid";
|
||||
import { useEffect, useState } from "react";
|
||||
import { db } from "../db";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function CreatePromptModal({ content }: { content?: string }) {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
|
@ -27,17 +28,17 @@ export function CreatePromptModal({ content }: { content?: string }) {
|
|||
return (
|
||||
<>
|
||||
{content ? (
|
||||
<Tooltip label="Save Prompt" position="left">
|
||||
<Tooltip label={t('savePrompt')} position="left">
|
||||
<ActionIcon onClick={open}>
|
||||
<IconPlaylistAdd opacity={0.5} size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button fullWidth onClick={open} leftIcon={<IconPlus size={20} />}>
|
||||
New Prompt
|
||||
{t('newPrompt')}
|
||||
</Button>
|
||||
)}
|
||||
<Modal opened={opened} onClose={close} title="Create Prompt" size="lg">
|
||||
<Modal opened={opened} onClose={close} title={t('createPrompt')} size="lg">
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
try {
|
||||
|
|
@ -51,22 +52,22 @@ export function CreatePromptModal({ content }: { content?: string }) {
|
|||
createdAt: new Date(),
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "Prompt created",
|
||||
title: t('savedPrompt.title'),
|
||||
message: t('savedPrompt.message'),
|
||||
});
|
||||
close();
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -78,14 +79,14 @@ export function CreatePromptModal({ content }: { content?: string }) {
|
|||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Title"
|
||||
label={t('title')}
|
||||
value={title}
|
||||
onChange={(event) => setTitle(event.currentTarget.value)}
|
||||
formNoValidate
|
||||
data-autofocus
|
||||
/>
|
||||
<Textarea
|
||||
placeholder="Content"
|
||||
placeholder={t('content')}
|
||||
autosize
|
||||
minRows={5}
|
||||
maxRows={10}
|
||||
|
|
@ -93,7 +94,7 @@ export function CreatePromptModal({ content }: { content?: string }) {
|
|||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
/>
|
||||
<Button type="submit" loading={submitting}>
|
||||
Save
|
||||
{t('save')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { cloneElement, ReactElement } from "react";
|
|||
import { db } from "../db";
|
||||
import { DeleteAllDataModal } from "./DeleteAllDataModal";
|
||||
import { DeleteChatsModal } from "./DeleteChatsModal";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function DatabaseModal({ children }: { children: ReactElement }) {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
|
@ -27,7 +28,7 @@ export function DatabaseModal({ children }: { children: ReactElement }) {
|
|||
<Modal
|
||||
opened={opened}
|
||||
onClose={close}
|
||||
title="Database"
|
||||
title={t('database')}
|
||||
size="lg"
|
||||
withinPortal
|
||||
keepMounted
|
||||
|
|
@ -45,7 +46,7 @@ export function DatabaseModal({ children }: { children: ReactElement }) {
|
|||
tt="uppercase"
|
||||
fw={700}
|
||||
>
|
||||
Chats
|
||||
{t('chats')}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card withBorder sx={{ flex: 1, marginLeft: -1 }}>
|
||||
|
|
@ -59,7 +60,7 @@ export function DatabaseModal({ children }: { children: ReactElement }) {
|
|||
tt="uppercase"
|
||||
fw={700}
|
||||
>
|
||||
Messages
|
||||
{t('messages')}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card withBorder sx={{ flex: 1, marginLeft: -1 }}>
|
||||
|
|
@ -73,7 +74,7 @@ export function DatabaseModal({ children }: { children: ReactElement }) {
|
|||
tt="uppercase"
|
||||
fw={700}
|
||||
>
|
||||
Prompts
|
||||
{t('prompts')}
|
||||
</Text>
|
||||
</Card>
|
||||
</Flex>
|
||||
|
|
@ -89,12 +90,12 @@ export function DatabaseModal({ children }: { children: ReactElement }) {
|
|||
"application/json"
|
||||
);
|
||||
notifications.show({
|
||||
title: "Exporting Data",
|
||||
message: "Your data is being exported.",
|
||||
title: t('exportingData.title'),
|
||||
message: t('exportingData.message'),
|
||||
});
|
||||
}}
|
||||
>
|
||||
Export Data
|
||||
{t('exportData')}
|
||||
</Button>
|
||||
<input
|
||||
id="file-upload-btn"
|
||||
|
|
@ -110,15 +111,15 @@ export function DatabaseModal({ children }: { children: ReactElement }) {
|
|||
})
|
||||
.then(() => {
|
||||
notifications.show({
|
||||
title: "Importing data",
|
||||
message: "Your data is being imported.",
|
||||
title: t('importingData.title'),
|
||||
message: t('importingData.message'),
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('selectedFileError.title'),
|
||||
color: "red",
|
||||
message: "The file you selected is invalid",
|
||||
message: t('selectedFileError.message'),
|
||||
});
|
||||
});
|
||||
}}
|
||||
|
|
@ -131,7 +132,7 @@ export function DatabaseModal({ children }: { children: ReactElement }) {
|
|||
variant="default"
|
||||
leftIcon={<IconDatabaseImport size={20} />}
|
||||
>
|
||||
Import Data
|
||||
{t('importData')}
|
||||
</Button>
|
||||
</Group>
|
||||
<Group>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Button, Modal, Stack, Text } from "@mantine/core";
|
|||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconTrash } from "@tabler/icons-react";
|
||||
import { db } from "../db";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function DeleteAllDataModal({ onOpen }: { onOpen: () => void }) {
|
||||
const [opened, { open, close }] = useDisclosure(false, { onOpen });
|
||||
|
|
@ -14,17 +15,17 @@ export function DeleteAllDataModal({ onOpen }: { onOpen: () => void }) {
|
|||
color="red"
|
||||
leftIcon={<IconTrash size={20} />}
|
||||
>
|
||||
Delete All Data
|
||||
{t('deleteAllData')}
|
||||
</Button>
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={close}
|
||||
title="Delete All Data"
|
||||
title={t('deleteAllData')}
|
||||
size="md"
|
||||
withinPortal
|
||||
>
|
||||
<Stack>
|
||||
<Text size="sm">Are you sure you want to delete your data?</Text>
|
||||
<Text size="sm">{t('confirmDeleteAllData')}</Text>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await db.delete();
|
||||
|
|
@ -33,7 +34,7 @@ export function DeleteAllDataModal({ onOpen }: { onOpen: () => void }) {
|
|||
}}
|
||||
color="red"
|
||||
>
|
||||
Delete
|
||||
{t('delete')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { cloneElement, ReactElement, useEffect, useState } from "react";
|
|||
import { Chat, db } from "../db";
|
||||
import { useApiKey } from "../hooks/useApiKey";
|
||||
import { useChatId } from "../hooks/useChatId";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function DeleteChatModal({
|
||||
chat,
|
||||
|
|
@ -29,7 +30,7 @@ export function DeleteChatModal({
|
|||
return (
|
||||
<>
|
||||
{cloneElement(children, { onClick: open })}
|
||||
<Modal opened={opened} onClose={close} title="Delete Chat">
|
||||
<Modal opened={opened} onClose={close} title={t('deleteChat')}>
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
try {
|
||||
|
|
@ -43,22 +44,21 @@ export function DeleteChatModal({
|
|||
close();
|
||||
|
||||
notifications.show({
|
||||
title: "Deleted",
|
||||
message: "Chat deleted.",
|
||||
title: t('deleted'),
|
||||
message: t('chatDeleted'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('removeChatError.title'),
|
||||
color: "red",
|
||||
message:
|
||||
"Can't remove chat. Please refresh the page and try again.",
|
||||
message: t('removeChatError.message'),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -67,9 +67,9 @@ export function DeleteChatModal({
|
|||
}}
|
||||
>
|
||||
<Stack>
|
||||
<Text size="sm">Are you sure you want to delete this chat?</Text>
|
||||
<Text size="sm">{t('confirmDeleteChat')}</Text>
|
||||
<Button type="submit" color="red" loading={submitting}>
|
||||
Delete
|
||||
{t('delete')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Button, Modal, Stack, Text } from "@mantine/core";
|
|||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconTrash } from "@tabler/icons-react";
|
||||
import { db } from "../db";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function DeleteChatsModal({ onOpen }: { onOpen: () => void }) {
|
||||
const [opened, { open, close }] = useDisclosure(false, { onOpen });
|
||||
|
|
@ -14,17 +15,17 @@ export function DeleteChatsModal({ onOpen }: { onOpen: () => void }) {
|
|||
color="red"
|
||||
leftIcon={<IconTrash size={20} />}
|
||||
>
|
||||
Delete Chats
|
||||
{t('deleteChats')}
|
||||
</Button>
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={close}
|
||||
title="Delete Chats"
|
||||
title={t('deleteChats')}
|
||||
size="md"
|
||||
withinPortal
|
||||
>
|
||||
<Stack>
|
||||
<Text size="sm">Are you sure you want to delete your chats?</Text>
|
||||
<Text size="sm">{t('confirmDeleteChats')}</Text>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await db.chats.clear();
|
||||
|
|
@ -34,7 +35,7 @@ export function DeleteChatsModal({ onOpen }: { onOpen: () => void }) {
|
|||
}}
|
||||
color="red"
|
||||
>
|
||||
Delete
|
||||
{t('delete')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { useEffect, useState } from "react";
|
|||
import { db, Prompt } from "../db";
|
||||
import { useApiKey } from "../hooks/useApiKey";
|
||||
import { useChatId } from "../hooks/useChatId";
|
||||
import {t} from "i18next";
|
||||
import * as tty from "tty";
|
||||
|
||||
export function DeletePromptModal({ prompt }: { prompt: Prompt }) {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
|
@ -23,7 +25,7 @@ export function DeletePromptModal({ prompt }: { prompt: Prompt }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Modal opened={opened} onClose={close} title="Delete Prompt" size="md">
|
||||
<Modal opened={opened} onClose={close} title={t('deletePrompt')} size="md">
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
try {
|
||||
|
|
@ -33,22 +35,21 @@ export function DeletePromptModal({ prompt }: { prompt: Prompt }) {
|
|||
close();
|
||||
|
||||
notifications.show({
|
||||
title: "Deleted",
|
||||
message: "Chat deleted.",
|
||||
title: t('deleted'),
|
||||
message: t('promptDeleted'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('removePromptError.title'),
|
||||
color: "red",
|
||||
message:
|
||||
"Can't remove chat. Please refresh the page and try again.",
|
||||
message: t('removePromptError.message'),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -57,14 +58,14 @@ export function DeletePromptModal({ prompt }: { prompt: Prompt }) {
|
|||
}}
|
||||
>
|
||||
<Stack>
|
||||
<Text size="sm">Are you sure you want to delete this prompt?</Text>
|
||||
<Text size="sm">{t('confirmDeletePrompt')}</Text>
|
||||
<Button type="submit" color="red" loading={submitting}>
|
||||
Delete
|
||||
{t('delete')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
<Tooltip label="Delete Prompt">
|
||||
<Tooltip label={t('deletePrompt')}>
|
||||
<ActionIcon color="red" size="lg" onClick={open}>
|
||||
<IconTrash size={20} />
|
||||
</ActionIcon>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useDisclosure } from "@mantine/hooks";
|
|||
import { notifications } from "@mantine/notifications";
|
||||
import { cloneElement, ReactElement, useEffect, useState } from "react";
|
||||
import { Chat, db } from "../db";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function EditChatModal({
|
||||
chat,
|
||||
|
|
@ -22,7 +23,7 @@ export function EditChatModal({
|
|||
return (
|
||||
<>
|
||||
{cloneElement(children, { onClick: open })}
|
||||
<Modal opened={opened} onClose={close} title="Edit Chat" withinPortal>
|
||||
<Modal opened={opened} onClose={close} title={t('editChat')} withinPortal>
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
try {
|
||||
|
|
@ -32,22 +33,22 @@ export function EditChatModal({
|
|||
chat.description = value;
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "",
|
||||
title: t('chatUpdated.title'),
|
||||
message: t('chatUpdated.message'),
|
||||
});
|
||||
close();
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -59,14 +60,14 @@ export function EditChatModal({
|
|||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Name"
|
||||
label={t('name')}
|
||||
value={value}
|
||||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
formNoValidate
|
||||
data-autofocus
|
||||
/>
|
||||
<Button type="submit" loading={submitting}>
|
||||
Save
|
||||
{t('save')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { notifications } from "@mantine/notifications";
|
|||
import { IconPencil } from "@tabler/icons-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { db, Prompt } from "../db";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
|
@ -26,7 +27,7 @@ export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Modal opened={opened} onClose={close} title="Edit Prompt" size="lg">
|
||||
<Modal opened={opened} onClose={close} title={t('editPrompt')} size="lg">
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
try {
|
||||
|
|
@ -37,21 +38,21 @@ export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
|||
chat.content = value;
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "Prompt updated",
|
||||
title: t('promptUpdated.title'),
|
||||
message: t('promptUpdated.message'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -63,14 +64,14 @@ export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
|||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Title"
|
||||
label={t('title')}
|
||||
value={title}
|
||||
onChange={(event) => setTitle(event.currentTarget.value)}
|
||||
formNoValidate
|
||||
data-autofocus
|
||||
/>
|
||||
<Textarea
|
||||
label="Content"
|
||||
label={t('content')}
|
||||
autosize
|
||||
minRows={5}
|
||||
maxRows={10}
|
||||
|
|
@ -78,12 +79,12 @@ export function EditPromptModal({ prompt }: { prompt: Prompt }) {
|
|||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
/>
|
||||
<Button type="submit" loading={submitting}>
|
||||
Save
|
||||
{t('save')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
<Tooltip label="Edit Prompt">
|
||||
<Tooltip label={t('editPrompt')}>
|
||||
<ActionIcon size="lg" onClick={open}>
|
||||
<IconPencil size={20} />
|
||||
</ActionIcon>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import { LogoText } from "./Logo";
|
|||
import { Prompts } from "./Prompts";
|
||||
import { SettingsModal } from "./SettingsModal";
|
||||
import { config } from "../utils/config";
|
||||
import {t} from "i18next";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -51,7 +52,8 @@ declare global {
|
|||
export function Layout() {
|
||||
const theme = useMantineTheme();
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [tab, setTab] = useState<"Chats" | "Prompts">("Chats");
|
||||
// @ts-ignore
|
||||
const [tab, setTab] = useState<"Chats" | "Prompts">(t('chats'));
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
const navigate = useNavigate();
|
||||
const router = useRouter();
|
||||
|
|
@ -126,10 +128,10 @@ export function Layout() {
|
|||
fullWidth
|
||||
value={tab}
|
||||
onChange={(value) => setTab(value as typeof tab)}
|
||||
data={["Chats", "Prompts"]}
|
||||
data={[t('chats'), t('prompts')]}
|
||||
/>
|
||||
<Box sx={{ padding: 4 }}>
|
||||
{tab === "Chats" && (
|
||||
{tab === t('chats') && (
|
||||
<Button
|
||||
fullWidth
|
||||
leftIcon={<IconPlus size={20} />}
|
||||
|
|
@ -137,17 +139,17 @@ export function Layout() {
|
|||
const id = nanoid();
|
||||
db.chats.add({
|
||||
id,
|
||||
description: "New Chat",
|
||||
description: t('newChat'),
|
||||
totalTokens: 0,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
navigate({ to: `/chats/${id}` });
|
||||
}}
|
||||
>
|
||||
New Chat
|
||||
{t('newChat')}
|
||||
</Button>
|
||||
)}
|
||||
{tab === "Prompts" && <CreatePromptModal />}
|
||||
{tab === t('prompts') && <CreatePromptModal />}
|
||||
</Box>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section
|
||||
|
|
@ -163,7 +165,7 @@ export function Layout() {
|
|||
<TextInput
|
||||
variant="unstyled"
|
||||
radius={0}
|
||||
placeholder="Search"
|
||||
placeholder={t('search')}
|
||||
value={search}
|
||||
onChange={(event) =>
|
||||
setSearch(event.currentTarget.value.toLowerCase())
|
||||
|
|
@ -180,8 +182,8 @@ export function Layout() {
|
|||
/>
|
||||
</Navbar.Section>
|
||||
<Navbar.Section grow component={ScrollArea}>
|
||||
{tab === "Chats" && <Chats search={search} />}
|
||||
{tab === "Prompts" && (
|
||||
{tab === t('chats') && <Chats search={search} />}
|
||||
{tab === t('prompts') && (
|
||||
<Prompts search={search} onPlay={() => setTab("Chats")} />
|
||||
)}
|
||||
</Navbar.Section>
|
||||
|
|
@ -189,7 +191,7 @@ export function Layout() {
|
|||
<Center>
|
||||
{config.allowDarkModeToggle && (
|
||||
<Tooltip
|
||||
label={colorScheme === "dark" ? "Light Mode" : "Dark Mode"}
|
||||
label={colorScheme === "dark" ? t('lightMode') : t('darkMode')}
|
||||
>
|
||||
<ActionIcon
|
||||
sx={{ flex: 1 }}
|
||||
|
|
@ -206,7 +208,7 @@ export function Layout() {
|
|||
)}
|
||||
{config.allowSettingsModal && (
|
||||
<SettingsModal>
|
||||
<Tooltip label="Settings">
|
||||
<Tooltip label={t('settings')}>
|
||||
<ActionIcon sx={{ flex: 1 }} size="xl">
|
||||
<IconSettings size={20} />
|
||||
</ActionIcon>
|
||||
|
|
@ -215,14 +217,14 @@ export function Layout() {
|
|||
)}
|
||||
{config.allowDatabaseModal && (
|
||||
<DatabaseModal>
|
||||
<Tooltip label="Database">
|
||||
<Tooltip label={t('database')}>
|
||||
<ActionIcon sx={{ flex: 1 }} size="xl">
|
||||
<IconDatabase size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</DatabaseModal>
|
||||
)}
|
||||
<Tooltip label="Source Code">
|
||||
<Tooltip label={t('sourceCode')}>
|
||||
<ActionIcon
|
||||
component="a"
|
||||
href="https://github.com/deiucanta/chatpad"
|
||||
|
|
@ -234,7 +236,7 @@ export function Layout() {
|
|||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{config.showTwitterLink && (
|
||||
<Tooltip label="Follow on Twitter">
|
||||
<Tooltip label={t('followOnTwitter')}>
|
||||
<ActionIcon
|
||||
component="a"
|
||||
href="https://twitter.com/deiucanta"
|
||||
|
|
@ -247,7 +249,7 @@ export function Layout() {
|
|||
</Tooltip>
|
||||
)}
|
||||
{config.showFeedbackLink && (
|
||||
<Tooltip label="Give Feedback">
|
||||
<Tooltip label={t('giveFeedback')}>
|
||||
<ActionIcon
|
||||
component="a"
|
||||
href="https://feedback.chatpad.ai"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import "../styles/markdown.scss";
|
|||
import { CreatePromptModal } from "./CreatePromptModal";
|
||||
import { LogoIcon } from "./Logo";
|
||||
import { ScrollIntoView } from "./ScrollIntoView";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function MessageItem({ message }: { message: Message }) {
|
||||
const clipboard = useClipboard({ timeout: 500 });
|
||||
|
|
@ -55,7 +56,7 @@ export function MessageItem({ message }: { message: Message }) {
|
|||
<CopyButton value={String(props.children)}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip
|
||||
label={copied ? "Copied" : "Copy"}
|
||||
label={copied ? t('copied') : t('copy')}
|
||||
position="left"
|
||||
>
|
||||
<ActionIcon
|
||||
|
|
@ -74,7 +75,7 @@ export function MessageItem({ message }: { message: Message }) {
|
|||
{message.role === "assistant" && (
|
||||
<Box>
|
||||
<Text size="sm" color="dimmed">
|
||||
{wordCount} words
|
||||
{wordCount} {t('words')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
|
@ -83,18 +84,13 @@ export function MessageItem({ message }: { message: Message }) {
|
|||
<CreatePromptModal content={message.content} />
|
||||
<CopyButton value={message.content}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip label={copied ? "Copied" : "Copy"} position="left">
|
||||
<Tooltip label={copied ? t('copied') : t('copy')} position="left">
|
||||
<ActionIcon onClick={copy}>
|
||||
<IconCopy opacity={0.5} size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</CopyButton>
|
||||
{/* <Tooltip label={`${wordCount} words`} position="left">
|
||||
<ActionIcon>
|
||||
<IconInfoCircle opacity={0.5} size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip> */}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { db } from "../db";
|
|||
import { createChatCompletion } from "../utils/openai";
|
||||
import { DeletePromptModal } from "./DeletePromptModal";
|
||||
import { EditPromptModal } from "./EditPromptModal";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function Prompts({
|
||||
onPlay,
|
||||
|
|
@ -80,7 +81,7 @@ export function Prompts({
|
|||
</Text>
|
||||
</Box>
|
||||
<Group spacing="none">
|
||||
<Tooltip label="New Chat From Prompt">
|
||||
<Tooltip label={t('newChatFromPrompt')}>
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
onClick={async () => {
|
||||
|
|
@ -88,7 +89,7 @@ export function Prompts({
|
|||
const id = nanoid();
|
||||
await db.chats.add({
|
||||
id,
|
||||
description: "New Chat",
|
||||
description: t('newChat'),
|
||||
totalTokens: 0,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
|
@ -105,8 +106,7 @@ export function Prompts({
|
|||
const result = await createChatCompletion(apiKey, [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"You are ChatGPT, a large language model trained by OpenAI.",
|
||||
content: t('youAreChatGPT'),
|
||||
},
|
||||
{ role: "user", content: prompt.content },
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { cloneElement, ReactElement, useEffect, useState } from "react";
|
|||
import { db } from "../db";
|
||||
import { config } from "../utils/config";
|
||||
import { checkOpenAIKey } from "../utils/openai";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function SettingsModal({ children }: { children: ReactElement }) {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
|
@ -58,7 +59,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
return (
|
||||
<>
|
||||
{cloneElement(children, { onClick: open })}
|
||||
<Modal opened={opened} onClose={close} title="Settings" size="lg">
|
||||
<Modal opened={opened} onClose={close} title={t('settings')} size="lg">
|
||||
<Stack>
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
|
|
@ -71,21 +72,21 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
console.log(apiKey);
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "Your OpenAI Key has been saved.",
|
||||
title: t('openAIKeySaved.title'),
|
||||
message: t('openAIKeySaved.message'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -97,7 +98,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
>
|
||||
<Flex gap="xs" align="end">
|
||||
<PasswordInput
|
||||
label="OpenAI API Key"
|
||||
label={t('openAIAPIKey')}
|
||||
placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
sx={{ flex: 1 }}
|
||||
value={value}
|
||||
|
|
@ -105,7 +106,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
formNoValidate
|
||||
/>
|
||||
<Button type="submit" loading={submitting}>
|
||||
Save
|
||||
{t('save')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
|
|
@ -116,19 +117,18 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
href="https://platform.openai.com/account/api-keys"
|
||||
target="_blank"
|
||||
>
|
||||
Get your OpenAI API key
|
||||
{t('getYourOpenAIAPIkey')}
|
||||
</Anchor>
|
||||
</Text>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<Text size="sm" color="dimmed">
|
||||
The API Key is stored locally on your browser and never sent
|
||||
anywhere else.
|
||||
{t('APIKeyStoredLocally')}
|
||||
</Text>
|
||||
</List.Item>
|
||||
</List>
|
||||
<Select
|
||||
label="OpenAI Type"
|
||||
label={t('openAIType')}
|
||||
value={type}
|
||||
onChange={async (value) => {
|
||||
setSubmitting(true);
|
||||
|
|
@ -137,21 +137,21 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
openAiApiType: value ?? 'openai',
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "Your OpenAI Type has been saved.",
|
||||
title: t('openAIKeySaved.title'),
|
||||
message: t('openAIKeySaved.message'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -161,10 +161,10 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
}
|
||||
}}
|
||||
withinPortal
|
||||
data={[{ "value": "openai", "label": "OpenAI"}, { "value": "custom", "label": "Custom (e.g. Azure OpenAI)"}]}
|
||||
data={[{ "value": "openai", "label": "OpenAI"}, { "value": "custom", "label": t('openIATTypeCustom')}]}
|
||||
/>
|
||||
<Select
|
||||
label="OpenAI Model (OpenAI Only)"
|
||||
label={t('openAIModel')}
|
||||
value={model}
|
||||
onChange={async (value) => {
|
||||
setSubmitting(true);
|
||||
|
|
@ -173,21 +173,21 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
openAiModel: value ?? undefined,
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "Your OpenAI Model has been saved.",
|
||||
title: t('openAIModelSaved.title'),
|
||||
message: t('openAIModelSaved.message'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -199,12 +199,11 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
withinPortal
|
||||
data={config.availableModels}
|
||||
/>
|
||||
<Alert color="orange" title="Warning">
|
||||
The displayed cost was not updated yet to reflect the costs for each
|
||||
model. Right now it will always show the cost for GPT-3.5 on OpenAI.
|
||||
<Alert color="orange" title={t('warning')}>
|
||||
{t('warningCostMessage')}
|
||||
</Alert>
|
||||
<Select
|
||||
label="OpenAI Auth (Custom Only)"
|
||||
label={t('openAIAuth')}
|
||||
value={auth}
|
||||
onChange={async (value) => {
|
||||
setSubmitting(true);
|
||||
|
|
@ -213,21 +212,21 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
openAiApiAuth: value ?? 'none',
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "Your OpenAI Auth has been saved.",
|
||||
title: t('openAIModelSaved.title'),
|
||||
message: t('openAIModelSaved.message'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -237,7 +236,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
}
|
||||
}}
|
||||
withinPortal
|
||||
data={[{ "value": "none", "label": "None"}, { "value": "bearer-token", "label": "Bearer Token"}, { "value": "api-key", "label": "API Key"}]}
|
||||
data={[{ "value": "none", "label": t('none')}, { "value": "bearer-token", "label": t('bearerToken')}, { "value": "api-key", "label": t('apiKey')}]}
|
||||
/>
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
|
|
@ -249,21 +248,21 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
console.log(row);
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "Your OpenAI Base has been saved.",
|
||||
title: t('openAIBaseSaved.title'),
|
||||
message: t('openAIBaseSaved.message'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -275,7 +274,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
>
|
||||
<Flex gap="xs" align="end">
|
||||
<TextInput
|
||||
label="OpenAI API Base (Custom Only)"
|
||||
label={t('openAIAPIBase')}
|
||||
placeholder="https://<resource-name>.openai.azure.com/openai/deployments/<deployment>"
|
||||
sx={{ flex: 1 }}
|
||||
value={base}
|
||||
|
|
@ -283,7 +282,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
formNoValidate
|
||||
/>
|
||||
<Button type="submit" loading={submitting}>
|
||||
Save
|
||||
{t('save')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
|
|
@ -297,21 +296,21 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
console.log(row);
|
||||
});
|
||||
notifications.show({
|
||||
title: "Saved",
|
||||
message: "Your OpenAI Version has been saved.",
|
||||
title: t('openAIVersionSaved.title'),
|
||||
message: t('openAIVersionSaved.message'),
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -323,7 +322,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
>
|
||||
<Flex gap="xs" align="end">
|
||||
<TextInput
|
||||
label="OpenAI API Version (Custom Only)"
|
||||
label={t('openAIAPIVersion')}
|
||||
placeholder="2023-03-15-preview"
|
||||
sx={{ flex: 1 }}
|
||||
value={version}
|
||||
|
|
@ -331,7 +330,7 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
|||
formNoValidate
|
||||
/>
|
||||
<Button type="submit" loading={submitting}>
|
||||
Save
|
||||
{t('save')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
|
|
|
|||
22
src/i18n.tsx
Normal file
22
src/i18n.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import i18n from "i18next";
|
||||
import {initReactI18next} from "react-i18next";
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import * as en from './assets/i18n/en.json';
|
||||
import * as fr from './assets/i18n/fr.json';
|
||||
|
||||
export const resources = {
|
||||
en,
|
||||
fr
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
});
|
||||
export default i18n;
|
||||
11
src/react-i18next.d.tsx
Normal file
11
src/react-i18next.d.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// import the original type declarations
|
||||
import 'react-i18next';
|
||||
// import all namespaces (for the default language, only)
|
||||
import { resources, defaultNS } from './i18n';
|
||||
|
||||
declare module 'i18next' {
|
||||
interface CustomTypeOptions {
|
||||
defaultNS: typeof defaultNS;
|
||||
resources: typeof resources['en'];
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import {
|
|||
createChatCompletion,
|
||||
createStreamChatCompletion,
|
||||
} from "../utils/openai";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function ChatRoute() {
|
||||
const chatId = useChatId();
|
||||
|
|
@ -55,14 +56,13 @@ export function ChatRoute() {
|
|||
|
||||
const getSystemMessage = () => {
|
||||
const message: string[] = [];
|
||||
if (writingCharacter) message.push(`You are ${writingCharacter}.`);
|
||||
if (writingTone) message.push(`Respond in ${writingTone} tone.`);
|
||||
if (writingStyle) message.push(`Respond in ${writingStyle} style.`);
|
||||
// @ts-ignore
|
||||
if (writingCharacter) message.push(t('youAre') + ` ${writingCharacter}.`);
|
||||
if (writingTone) message.push(t('respondInTone') + ` ${writingTone} ` + t('tone'));
|
||||
if (writingStyle) message.push(t('respondInStyle') +` ${writingStyle} ` + t('style'));
|
||||
if (writingFormat) message.push(writingFormat);
|
||||
if (message.length === 0)
|
||||
message.push(
|
||||
"You are ChatGPT, a large language model trained by OpenAI."
|
||||
);
|
||||
message.push(t('youAreChatGPT'));
|
||||
return message.join(" ");
|
||||
};
|
||||
|
||||
|
|
@ -71,18 +71,18 @@ export function ChatRoute() {
|
|||
|
||||
if (!chatId) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('chatIdError.title'),
|
||||
color: "red",
|
||||
message: "chatId is not defined. Please create a chat to get started.",
|
||||
message: t('chatIdError.message'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('apiKeyError.title'),
|
||||
color: "red",
|
||||
message: "OpenAI API Key is not defined. Please set your API Key",
|
||||
message: t('apiKeyError.message'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ export function ChatRoute() {
|
|||
|
||||
setSubmitting(false);
|
||||
|
||||
if (chat?.description === "New Chat") {
|
||||
if (chat?.description === t('newChat')) {
|
||||
const messages = await db.messages
|
||||
.where({ chatId })
|
||||
.sortBy("createdAt");
|
||||
|
|
@ -142,8 +142,7 @@ export function ChatRoute() {
|
|||
})),
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"What would be a short and relevant title for this chat ? You must strictly answer with only the title, no other text is allowed.",
|
||||
content: t('defineTitleChat'),
|
||||
},
|
||||
]);
|
||||
const chatDescription =
|
||||
|
|
@ -151,7 +150,7 @@ export function ChatRoute() {
|
|||
|
||||
if (createChatDescription.data.usage) {
|
||||
await db.chats.where({ id: chatId }).modify((chat) => {
|
||||
chat.description = chatDescription ?? "New Chat";
|
||||
chat.description = chatDescription ?? t('newChat');
|
||||
if (chat.totalTokens) {
|
||||
chat.totalTokens +=
|
||||
createChatDescription.data.usage!.total_tokens;
|
||||
|
|
@ -164,15 +163,15 @@ export function ChatRoute() {
|
|||
} catch (error: any) {
|
||||
if (error.toJSON().message === "Network Error") {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('networkError.title'),
|
||||
color: "red",
|
||||
message: "No internet connection.",
|
||||
message: t('networkError.message'),
|
||||
});
|
||||
}
|
||||
const message = error.response?.data?.error?.message;
|
||||
if (message) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
title: t('error.title'),
|
||||
color: "red",
|
||||
message,
|
||||
});
|
||||
|
|
@ -264,7 +263,7 @@ export function ChatRoute() {
|
|||
value={writingCharacter}
|
||||
onChange={setWritingCharacter}
|
||||
data={config.writingCharacters}
|
||||
placeholder="Character"
|
||||
placeholder={t('character')}
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
|
|
@ -274,7 +273,7 @@ export function ChatRoute() {
|
|||
value={writingTone}
|
||||
onChange={setWritingTone}
|
||||
data={config.writingTones}
|
||||
placeholder="Tone"
|
||||
placeholder={t('toneList')}
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
|
|
@ -284,7 +283,7 @@ export function ChatRoute() {
|
|||
value={writingStyle}
|
||||
onChange={setWritingStyle}
|
||||
data={config.writingStyles}
|
||||
placeholder="Style"
|
||||
placeholder={t('styleList')}
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
|
|
@ -294,7 +293,7 @@ export function ChatRoute() {
|
|||
value={writingFormat}
|
||||
onChange={setWritingFormat}
|
||||
data={config.writingFormats}
|
||||
placeholder="Format"
|
||||
placeholder={t('format')}
|
||||
variant="filled"
|
||||
searchable
|
||||
clearable
|
||||
|
|
@ -306,7 +305,7 @@ export function ChatRoute() {
|
|||
<Textarea
|
||||
key={chatId}
|
||||
sx={{ flex: 1 }}
|
||||
placeholder="Your message here..."
|
||||
placeholder={t('yourMessageHere')}
|
||||
autosize
|
||||
autoFocus
|
||||
disabled={submitting}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,30 @@ import { Logo } from "../components/Logo";
|
|||
import { SettingsModal } from "../components/SettingsModal";
|
||||
import { db } from "../db";
|
||||
import { config } from "../utils/config";
|
||||
import {t} from "i18next";
|
||||
|
||||
export function IndexRoute() {
|
||||
const settings = useLiveQuery(() => db.settings.get("general"));
|
||||
const { openAiApiKey } = settings ?? {};
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: IconCurrencyDollar,
|
||||
title: t('freeAndOpenSource.title'),
|
||||
description: t('freeAndOpenSource.description'),
|
||||
},
|
||||
{
|
||||
icon: IconLock,
|
||||
title: t('privacyFocused.title'),
|
||||
description: t('privacyFocused.description'),
|
||||
},
|
||||
{
|
||||
icon: IconNorthStar,
|
||||
title: t('bestExperience.title'),
|
||||
description: t('bestExperience.description'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center py="xl" sx={{ height: "100%" }}>
|
||||
|
|
@ -34,7 +53,7 @@ export function IndexRoute() {
|
|||
<Logo style={{ maxWidth: 240 }} />
|
||||
</Text>
|
||||
<Text mt={4} size="xl">
|
||||
Not just another ChatGPT user-interface!
|
||||
{t('notAnotherUI')}
|
||||
</Text>
|
||||
<SimpleGrid
|
||||
mt={50}
|
||||
|
|
@ -42,8 +61,8 @@ export function IndexRoute() {
|
|||
spacing={30}
|
||||
breakpoints={[{ maxWidth: "md", cols: 1 }]}
|
||||
>
|
||||
{features.map((feature) => (
|
||||
<div key={feature.title}>
|
||||
{features.map((feature, index) => (
|
||||
<div key={index}>
|
||||
<ThemeIcon variant="outline" size={50} radius={50}>
|
||||
<feature.icon size={26} stroke={1.5} />
|
||||
</ThemeIcon>
|
||||
|
|
@ -64,7 +83,7 @@ export function IndexRoute() {
|
|||
variant={openAiApiKey ? "light" : "filled"}
|
||||
leftIcon={<IconKey size={20} />}
|
||||
>
|
||||
{openAiApiKey ? "Change OpenAI Key" : "Enter OpenAI Key"}
|
||||
{openAiApiKey ? t('changeOpenAIKey') : t('enterOpenAIKey')}
|
||||
</Button>
|
||||
</SettingsModal>
|
||||
)}
|
||||
|
|
@ -77,7 +96,7 @@ export function IndexRoute() {
|
|||
variant="outline"
|
||||
leftIcon={<IconCloudDownload size={20} />}
|
||||
>
|
||||
Download Desktop App
|
||||
{t('downloadDesktopApp')}
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
|
|
@ -85,25 +104,4 @@ export function IndexRoute() {
|
|||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: IconCurrencyDollar,
|
||||
title: "Free and open source",
|
||||
description:
|
||||
"This app is provided for free and the source code is available on GitHub.",
|
||||
},
|
||||
{
|
||||
icon: IconLock,
|
||||
title: "Privacy focused",
|
||||
description:
|
||||
"No tracking, no cookies, no bullshit. All your data is stored locally.",
|
||||
},
|
||||
{
|
||||
icon: IconNorthStar,
|
||||
title: "Best experience",
|
||||
description:
|
||||
"Crafted with love and care to provide the best experience possible.",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"defaultModel": "gpt-3.5-turbo",
|
||||
"defaultModel": "gpt-3.5-turbo",
|
||||
"defaultType": "openai",
|
||||
"defaultAuth": "api-key",
|
||||
"defaultBase": "",
|
||||
227
src/static/config_fr.json
Normal file
227
src/static/config_fr.json
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
{
|
||||
"defaultModel": "gpt-3.5-turbo",
|
||||
"defaultType": "openai",
|
||||
"defaultAuth": "api-key",
|
||||
"defaultBase": "",
|
||||
"defaultVersion": "",
|
||||
"defaultKey": "",
|
||||
"availableModels": [
|
||||
{
|
||||
"value": "gpt-3.5-turbo",
|
||||
"label": "GPT-3.5-TURBO (ChatGPT par défaut)"
|
||||
},
|
||||
{
|
||||
"value": "gpt-3.5-turbo-0613",
|
||||
"label": "GPT-3.5-TURBO-0613"
|
||||
},
|
||||
{
|
||||
"value": "gpt-3.5-turbo-0301",
|
||||
"label": "GPT-3.5-TURBO-0301 (Legacy)"
|
||||
},
|
||||
{
|
||||
"value": "gpt-3.5-turbo-16k",
|
||||
"label": "GPT-3.5-TURBO-16K"
|
||||
},
|
||||
{
|
||||
"value": "gpt-3.5-turbo-16k-0613",
|
||||
"label": "GPT-3.5-TURBO-16K-0613"
|
||||
},
|
||||
{
|
||||
"value": "gpt-4",
|
||||
"label": "GPT-4 (Bêta limitée)"
|
||||
},
|
||||
{
|
||||
"value": "gpt-4-0613",
|
||||
"label": "GPT-4-0613 (Bêta limitée)"
|
||||
},
|
||||
{
|
||||
"value": "gpt-4-0314",
|
||||
"label": "GPT-4-0314 (Bêta limitée, Legacy)"
|
||||
},
|
||||
{
|
||||
"value": "gpt-4-32k",
|
||||
"label": "GPT-4-32K (Bêta limitée)"
|
||||
},
|
||||
{
|
||||
"value": "gpt-4-32k-0613",
|
||||
"label": "GPT-4-32K-0613 (Bêta limitée)"
|
||||
},
|
||||
{
|
||||
"value": "gpt-4-32k-0314",
|
||||
"label": "GPT-4-32K-0314 (Bêta limitée, Legacy)"
|
||||
}
|
||||
],
|
||||
"writingCharacters": [
|
||||
{
|
||||
"label": "Humoriste",
|
||||
"value": "Un artiste de spectacle qui divertit son public en racontant des blagues et des histoires humoristiques."
|
||||
},
|
||||
{
|
||||
"label": "Coach de vie",
|
||||
"value": "Un professionnel qui aide les individus à identifier et atteindre leurs objectifs personnels et professionnels."
|
||||
},
|
||||
{
|
||||
"label": "Conseiller en orientation",
|
||||
"value": "Un professionnel qui aide les individus à explorer et à choisir des carrières, à développer des stratégies de recherche d'emploi et à améliorer leur performance professionnelle."
|
||||
},
|
||||
{
|
||||
"label": "Nutritionniste",
|
||||
"value": "Un professionnel de la santé qui se spécialise dans l'étude de la nutrition et de ses effets sur le corps."
|
||||
},
|
||||
{
|
||||
"label": "Chef de produit",
|
||||
"value": "Un professionnel qui supervise le développement et la commercialisation des produits d'une entreprise."
|
||||
},
|
||||
{
|
||||
"label": "Entraîneur personnel",
|
||||
"value": "Un professionnel de la remise en forme qui travaille avec des individus pour élaborer des programmes d'exercices personnalisés et améliorer leur santé et leur condition physique globales."
|
||||
},
|
||||
{
|
||||
"label": "Expert en astuces de vie",
|
||||
"value": "Une personne qui utilise des méthodes non conventionnelles pour résoudre des problèmes et augmenter la productivité dans la vie quotidienne."
|
||||
},
|
||||
{
|
||||
"label": "Conseiller en voyages",
|
||||
"value": "Un professionnel qui aide les individus à planifier et réserver des arrangements de voyage."
|
||||
},
|
||||
{
|
||||
"label": "Coach pleine conscience",
|
||||
"value": "Un professionnel qui aide les individus à développer des pratiques de pleine conscience pour réduire le stress et améliorer le bien-être."
|
||||
},
|
||||
{
|
||||
"label": "Conseiller financier",
|
||||
"value": "Un professionnel qui fournit des conseils et des orientations sur la planification financière, les stratégies d'investissement et la planification de la retraite."
|
||||
},
|
||||
{
|
||||
"label": "Tuteur linguistiques",
|
||||
"value": "Un enseignant qui aide les individus à apprendre et à améliorer leurs compétences linguistiques."
|
||||
},
|
||||
{
|
||||
"label": "Guide touristique",
|
||||
"value": "Un professionnel qui guide les visites et fournit des informations sur les attractions locales et la culture."
|
||||
},
|
||||
{
|
||||
"label": "Expert marketing",
|
||||
"value": "Un professionnel qui développe et met en oeuvre des stratégies de marketing pour promouvoir des produits et services."
|
||||
},
|
||||
{
|
||||
"label": "Développeur logiciels",
|
||||
"value": "Un professionnel qui conçoit, développe et maintient des applications et des systèmes informatiques."
|
||||
},
|
||||
{
|
||||
"label": "Coach relations amoureuses",
|
||||
"value": "Un professionnel qui aide les individus à améliorer leurs compétences en matière de rencontres et de relations amoureuses."
|
||||
},
|
||||
{
|
||||
"label": "Expert en bricolage",
|
||||
"value": "Une personne qui est habile à réaliser une large gamme de projets de bricolage autour de la maison."
|
||||
},
|
||||
{
|
||||
"label": "Journaliste",
|
||||
"value": "Un professionnel qui enquête et rapporte sur les événements et les nouvelles en cours."
|
||||
},
|
||||
{
|
||||
"label": "Écrivain technique",
|
||||
"value": "Un professionnel qui écrit sur la technologie et les sujets connexes pour une variété d'audiences."
|
||||
},
|
||||
{
|
||||
"label": "Chef cuisinier",
|
||||
"value": "Un professionnel de la cuisine qui prépare des repas et gère les opérations de la cuisine."
|
||||
},
|
||||
{
|
||||
"label": "Vendeur professionnel",
|
||||
"value": "Un professionnel qui vend des produits et services aux entreprises et aux consommateurs."
|
||||
},
|
||||
{
|
||||
"label": "Avocat startup technologique",
|
||||
"value": "Un professionnel du droit qui se spécialise dans la fourniture de conseils et de services juridiques aux entreprises technologiques en démarrage."
|
||||
},
|
||||
{
|
||||
"label": "Designer graphique",
|
||||
"value": "Un professionnel qui conçoit des supports visuels tels que des logos, des brochures et des sites web."
|
||||
},
|
||||
{
|
||||
"label": "Chercheur universitaire",
|
||||
"value": "Un professionnel qui mène des recherches et produit des travaux universitaires dans un domaine universitaire particulier."
|
||||
},
|
||||
{
|
||||
"label": "Soutien à la clientèle",
|
||||
"value": "Un professionnel qui fournit une assistance et un soutien aux clients ayant des questions ou des problèmes avec les produits ou services d'une entreprise."
|
||||
},
|
||||
{
|
||||
"label": "Consultant RH",
|
||||
"value": "Un professionnel qui fournit des conseils et des orientations aux organisations en matière de gestion et de stratégie des ressources humaines."
|
||||
}
|
||||
],
|
||||
"writingTones": [
|
||||
"Affirmé",
|
||||
"Autoritaire",
|
||||
"Détendu",
|
||||
"Confiant",
|
||||
"Condescendant",
|
||||
"Conversationnel",
|
||||
"Diplomatique",
|
||||
"Direct",
|
||||
"Éloquent",
|
||||
"Formel",
|
||||
"Amical",
|
||||
"Humoristique",
|
||||
"Informatif",
|
||||
"Inspirant",
|
||||
"Intense",
|
||||
"Irritable",
|
||||
"Blagueur",
|
||||
"Polie",
|
||||
"Sarcastique",
|
||||
"Sincère",
|
||||
"Apaisant",
|
||||
"Sévère",
|
||||
"Compassionnel",
|
||||
"Tactique",
|
||||
"Spirituel"
|
||||
],
|
||||
"writingStyles": [
|
||||
"Académique",
|
||||
"Analytique",
|
||||
"Argumentatif",
|
||||
"Conversationnel",
|
||||
"Créatif",
|
||||
"Critique",
|
||||
"Descriptif",
|
||||
"Explicatif",
|
||||
"Informatif",
|
||||
"Instructif",
|
||||
"Investigatif",
|
||||
"Journalistique",
|
||||
"Métaphorique",
|
||||
"Narratif",
|
||||
"Persuasif",
|
||||
"Poétique",
|
||||
"Satirique",
|
||||
"Technique"
|
||||
],
|
||||
"writingFormats": [
|
||||
{
|
||||
"value": "Réponds de manière concise autant que possible",
|
||||
"label": "Concis"
|
||||
},
|
||||
{
|
||||
"value": "Réfléchir étape par étape",
|
||||
"label": "Étape par étape"
|
||||
},
|
||||
{
|
||||
"value": "Réponds avec un niveau de détail minutieux",
|
||||
"label": "Détailler"
|
||||
},
|
||||
{
|
||||
"value": "Explique comme si j'avais cinq ans",
|
||||
"label": "Explique comme si j'avais 5 ans"
|
||||
}
|
||||
],
|
||||
"showDownloadLink": true,
|
||||
"allowDarkModeToggle": true,
|
||||
"allowSettingsModal": true,
|
||||
"allowDatabaseModal": true,
|
||||
"showTwitterLink": true,
|
||||
"showFeedbackLink": true
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import i18n from "i18next";
|
||||
|
||||
interface Config {
|
||||
defaultModel: AvailableModel["value"];
|
||||
defaultType: 'openai' | 'custom';
|
||||
|
|
@ -36,7 +38,7 @@ interface WritingFormat {
|
|||
export let config: Config;
|
||||
|
||||
export const loadConfig = async () => {
|
||||
const response = await fetch("config.json");
|
||||
const response = await fetch("config_"+i18n.resolvedLanguage+".json");
|
||||
config = await response.json();
|
||||
return config;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"esModuleInterop": true,
|
||||
"lib": ["es2018", "dom"],
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
yarn.lock
52
yarn.lock
|
|
@ -35,7 +35,7 @@
|
|||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7":
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.3", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7":
|
||||
version "7.21.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
|
||||
integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
|
||||
|
|
@ -1273,10 +1273,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||
|
||||
"@types/react-dom@18.0.11":
|
||||
version "18.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33"
|
||||
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
|
||||
"@types/react-dom@^18.0.11":
|
||||
version "18.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63"
|
||||
integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
|
|
@ -1289,10 +1289,10 @@
|
|||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@18.0.28":
|
||||
version "18.0.28"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065"
|
||||
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
|
||||
"@types/react@^18.0.28":
|
||||
version "18.2.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.15.tgz#14792b35df676c20ec3cf595b262f8c615a73066"
|
||||
integrity sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
|
|
@ -2741,6 +2741,13 @@ html-dom-parser@1.2.0:
|
|||
domhandler "4.3.1"
|
||||
htmlparser2 "7.2.0"
|
||||
|
||||
html-parse-stringify@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||
dependencies:
|
||||
void-elements "3.1.0"
|
||||
|
||||
html-react-parser@1.4.12:
|
||||
version "1.4.12"
|
||||
resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-1.4.12.tgz#5d4336e3853a75e4ac678c9815c15c58581bb30e"
|
||||
|
|
@ -2791,6 +2798,20 @@ human-signals@^4.3.0:
|
|||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2"
|
||||
integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==
|
||||
|
||||
i18next-browser-languagedetector@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.1.tgz#ead34592edc96c6c3a618a51cb57ad027c5b5d87"
|
||||
integrity sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.19.4"
|
||||
|
||||
i18next@^22.4.15:
|
||||
version "22.4.15"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.15.tgz#951882b751872994f8502b5a6ef6f796e6a7d7f8"
|
||||
integrity sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.6"
|
||||
|
||||
ieee754@^1.1.13:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
|
|
@ -4232,6 +4253,14 @@ react-error-overlay@6.0.9:
|
|||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
||||
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||
|
||||
react-i18next@^12.2.2:
|
||||
version "12.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.2.2.tgz#38a6fad11acf4f2abfc5611bdb6b1918d0f47578"
|
||||
integrity sha512-KBB6buBmVKXUWNxXHdnthp+38gPyBT46hJCAIQ8rX19NFL/m2ahte2KARfIDf2tMnSAL7wwck6eDOd/9zn6aFg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.6"
|
||||
html-parse-stringify "^3.0.1"
|
||||
|
||||
react-icons@^4.8.0:
|
||||
version "4.8.0"
|
||||
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.8.0.tgz#621e900caa23b912f737e41be57f27f6b2bff445"
|
||||
|
|
@ -5036,6 +5065,11 @@ vfile@^5.0.0:
|
|||
unist-util-stringify-position "^3.0.0"
|
||||
vfile-message "^3.0.0"
|
||||
|
||||
void-elements@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
|
||||
|
||||
weak-lru-cache@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz#fdbb6741f36bae9540d12f480ce8254060dccd19"
|
||||
|
|
|
|||
Loading…
Reference in a new issue