feat(i18n): settings static content

pull/969/head
ManishMadan2882 1 month ago
parent f6c66f6ee4
commit 4fcc80719e

@ -10,10 +10,12 @@
"dependencies": {
"@reduxjs/toolkit": "^1.9.2",
"@vercel/analytics": "^0.1.10",
"i18next": "^23.11.5",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-i18next": "^14.1.2",
"react-markdown": "^8.0.7",
"react-redux": "^8.0.5",
"react-router-dom": "^6.8.1",
@ -354,11 +356,12 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
"integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.13.11"
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
@ -1485,7 +1488,7 @@
"version": "18.0.10",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
"devOptional": true,
"dev": true,
"dependencies": {
"@types/react": "*"
}
@ -4134,6 +4137,15 @@
"react-is": "^16.7.0"
}
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"license": "MIT",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/human-signals": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz",
@ -4158,6 +4170,29 @@
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/i18next": {
"version": "23.11.5",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz",
"integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@ -6678,6 +6713,28 @@
"react": ">= 16.8 || 18.0.0"
}
},
"node_modules/react-i18next": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.2.tgz",
"integrity": "sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.9",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"react": ">= 16.8.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -6875,9 +6932,10 @@
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/regexp.prototype.flags": {
"version": "1.4.3",
@ -7923,6 +7981,15 @@
"vite": "^2.6.0 || 3 || 4 || 5"
}
},
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

@ -21,10 +21,12 @@
"dependencies": {
"@reduxjs/toolkit": "^1.9.2",
"@vercel/analytics": "^0.1.10",
"i18next": "^23.11.5",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-i18next": "^14.1.2",
"react-markdown": "^8.0.7",
"react-redux": "^8.0.5",
"react-router-dom": "^6.8.1",
@ -58,5 +60,4 @@
"vite": "^5.0.13",
"vite-plugin-svgr": "^4.2.0"
}
}

@ -7,6 +7,7 @@ import { inject } from '@vercel/analytics';
import { useMediaQuery } from './hooks';
import { useState } from 'react';
import Setting from './settings';
import './locale/i18n';
inject();

@ -14,7 +14,7 @@ import {
import Send from './../assets/send.svg';
import SendDark from './../assets/send_dark.svg';
import Spinner from './../assets/spinner.svg';
import SpinnerDark from './../assets/spinner-dark.svg'
import SpinnerDark from './../assets/spinner-dark.svg';
import { FEEDBACK, Query } from './conversationModels';
import { sendFeedback } from './conversationApi';
import ArrowDown from './../assets/arrow-down.svg';

@ -0,0 +1,39 @@
{
"language": "English",
"chat":"Chat",
"newChat":"New Chat",
"myPlan":"My Plan",
"about":"About",
"inputPlaceholder":"Type your message here...",
"tagline":"DocsGPT uses GenAI, please review critial information using sources.",
"sourceDocs":"Source Docs",
"settings":{
"label":"Settings",
"general":{
"label":"General",
"selectTheme":"Select Theme",
"light":"Light",
"dark":"Dark",
"selectLanguage":"Select Language",
"chunks":"Chunks processed per query",
"prompt":"Active Prompt",
"deleteAllLabel":"Delete all Conversation",
"deleteAllBtn":"Delete all",
"addNew":"Add New"
},
"documents":{
"label":"Documents",
"name":"Document Name",
"date":"Vector Date",
"type":"Type",
"tokenUsage":"Token Usage"
},
"apiKeys":{
"label":"API Keys",
"name":"Name",
"key":"API Key",
"sourceDoc":"Source Document",
"createNew":"Create New"
}
}
}

@ -0,0 +1,39 @@
{
"language": "Spanish",
"chat": "Chat",
"newChat": "Nuevo Chat",
"myPlan": "Mi Plan",
"about": "Acerca de",
"inputPlaceholder": "Escribe tu mensaje aquí...",
"tagline": "DocsGPT utiliza GenAI, por favor revisa información crítica utilizando fuentes.",
"sourceDocs": "Documentos Fuente",
"settings": {
"label": "Configuración",
"general": {
"label":"General",
"selectTheme": "Seleccionar Tema",
"light":"de luz",
"dark":"oscura",
"selectLanguage": "Seleccionar Idioma",
"chunks": "Trozos procesados por consulta",
"prompt": "Prompt Activo",
"deleteAllLabel": "Eliminar toda la Conversación",
"deleteAllBtn": "Eliminar todo",
"addNew": "Agregar Nuevo"
},
"documents": {
"label":"Documentos",
"name": "Nombre del Documento",
"date": "Fecha Vector",
"type": "Tipo",
"tokenUsage": "Uso de Tokens"
},
"apiKeys": {
"label":"Claves API",
"name": "Nombre",
"key": "Clave de API",
"sourceDoc": "Documento Fuente",
"createNew": "Crear Nuevo"
}
}
}

@ -0,0 +1,21 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './en.json'; //English
import es from './es.json'; //Spanish
i18n.use(initReactI18next).init({
resources: {
en: {
translation: en,
},
es: {
translation: es,
},
},
});
const locale = localStorage.getItem('docsgpt-locale') ?? 'en';
i18n.changeLanguage(locale);
export default i18n;

@ -19,11 +19,11 @@ const Modal = (props: ModalProps) => {
} absolute z-30 h-screen w-screen bg-gray-alpha`}
>
{props.render()}
<div className=" mx-auto flex w-[90vw] max-w-lg flex-row-reverse rounded-b-lg bg-white dark:bg-outer-space pb-5 pr-5 shadow-lg">
<div className=" mx-auto flex w-[90vw] max-w-lg flex-row-reverse rounded-b-lg bg-white pb-5 pr-5 shadow-lg dark:bg-outer-space">
<div>
<button
onClick={() => props.handleSubmit()}
className="ml-auto h-10 w-20 rounded-3xl bg-violet-800 text-white dark:text-silver transition-all hover:bg-violet-700"
className="ml-auto h-10 w-20 rounded-3xl bg-violet-800 text-white transition-all hover:bg-violet-700 dark:text-silver"
>
{props.textDelete ? 'Delete' : 'Save'}
</button>

@ -9,13 +9,14 @@ import {
import { selectSourceDocs } from '../preferences/preferenceSlice';
import Exit from '../assets/exit.svg';
import Trash from '../assets/trash.svg';
import { useTranslation } from 'react-i18next';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME ||
'huggingface_sentence-transformers/all-mpnet-base-v2';
const APIKeys: React.FC = () => {
const { t } = useTranslation();
const [isCreateModalOpen, setCreateModal] = React.useState(false);
const [isSaveKeyModalOpen, setSaveKeyModal] = React.useState(false);
const [newKey, setNewKey] = React.useState('');
@ -97,7 +98,7 @@ const APIKeys: React.FC = () => {
onClick={() => setCreateModal(true)}
className="rounded-full bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
>
Create new
{t('settings.apiKeys.createNew')}
</button>
</div>
{isCreateModalOpen && (
@ -117,11 +118,15 @@ const APIKeys: React.FC = () => {
<table className="block w-max table-auto content-center justify-center rounded-xl border text-center dark:border-chinese-silver dark:text-bright-gray">
<thead>
<tr>
<th className="border-r p-4 md:w-[244px]">Name</th>
<th className="border-r p-4 md:w-[244px]">
{t('settings.apiKeys.name')}
</th>
<th className="w-[244px] border-r px-4 py-2">
{t('settings.apiKeys.sourceDoc')}
</th>
<th className="w-[244px] border-r px-4 py-2">
Source document
{t('settings.apiKeys.key')}
</th>
<th className="w-[244px] border-r px-4 py-2">API Key</th>
<th className="px-4 py-2"></th>
</tr>
</thead>

@ -1,10 +1,12 @@
import { DocumentsProps } from '../models/misc';
import Trash from '../assets/trash.svg';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
const Documents: React.FC<DocumentsProps> = ({
documents,
handleDeleteDocument,
}) => {
const { t } = useTranslation();
return (
<div className="mt-8">
<div className="flex flex-col">
@ -12,10 +14,18 @@ const Documents: React.FC<DocumentsProps> = ({
<table className="block w-max table-auto content-center justify-center rounded-xl border text-center dark:border-chinese-silver dark:text-bright-gray">
<thead>
<tr>
<th className="border-r p-4 md:w-[244px]">Document Name</th>
<th className="w-[244px] border-r px-4 py-2">Vector Date</th>
<th className="w-[244px] border-r px-4 py-2">Token usage</th>
<th className="w-[244px] border-r px-4 py-2">Type</th>
<th className="border-r p-4 md:w-[244px]">
{t('settings.documents.name')}
</th>
<th className="w-[244px] border-r px-4 py-2">
{t('settings.documents.date')}
</th>
<th className="w-[244px] border-r px-4 py-2">
{t('settings.documents.tokenUsage')}
</th>
<th className="w-[244px] border-r px-4 py-2">
{t('settings.documents.type')}
</th>
<th className="px-4 py-2"></th>
</tr>
</thead>

@ -1,7 +1,8 @@
import React from 'react';
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Prompts from './Prompts';
import { useDarkTheme } from '../hooks';
import { useTranslation } from 'react-i18next';
import Dropdown from '../components/Dropdown';
import {
selectPrompt,
@ -14,8 +15,22 @@ import {
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const General: React.FC = () => {
const themes = ['Light', 'Dark'];
const languages = ['English'];
const {
t,
i18n: { changeLanguage, language },
} = useTranslation();
const themes = [t('settings.general.light'), t('settings.general.dark')];
const languageOptions = [
{
label: 'English',
value: 'en',
},
{
label: 'Spanish',
value: 'es',
},
];
const chunks = ['0', '2', '4', '6', '8', '10'];
const [prompts, setPrompts] = React.useState<
{ name: string; id: string; type: string }[]
@ -23,12 +38,18 @@ const General: React.FC = () => {
const selectedChunks = useSelector(selectChunks);
const [isDarkTheme, toggleTheme] = useDarkTheme();
const [selectedTheme, setSelectedTheme] = React.useState(
isDarkTheme ? 'Dark' : 'Light',
isDarkTheme ? t('settings.general.dark') : t('settings.general.light'),
);
const dispatch = useDispatch();
const [selectedLanguage, setSelectedLanguage] = React.useState(languages[0]);
const locale = localStorage.getItem('docsgpt-locale');
const [selectedLanguage, setSelectedLanguage] = React.useState(
locale ? languageOptions.find((option) => option.value === locale) : 'en',
);
const selectedPrompt = useSelector(selectPrompt);
useEffect(() => {
console.log(selectedLanguage);
console.log(language);
}, [selectedLanguage]);
React.useEffect(() => {
const fetchPrompts = async () => {
try {
@ -48,7 +69,9 @@ const General: React.FC = () => {
return (
<div className="mt-[59px]">
<div className="mb-5">
<p className="font-bold text-jet dark:text-bright-gray">Select Theme</p>
<p className="font-bold text-jet dark:text-bright-gray">
{t('settings.general.selectTheme')}
</p>
<Dropdown
options={themes}
selectedValue={selectedTheme}
@ -63,12 +86,16 @@ const General: React.FC = () => {
</div>
<div className="mb-5">
<p className="font-bold text-jet dark:text-bright-gray">
Select Language
{t('settings.general.selectLanguage')}
</p>
<Dropdown
options={languages}
selectedValue={selectedLanguage}
onSelect={setSelectedLanguage}
options={languageOptions}
selectedValue={selectedLanguage ?? languageOptions[0]}
onSelect={(selectedOption: { label: string; value: string }) => {
setSelectedLanguage(selectedOption);
changeLanguage(selectedOption.value);
localStorage.setItem('docsgpt-locale', selectedOption.value);
}}
size="w-56"
rounded="3xl"
border="border"
@ -76,7 +103,7 @@ const General: React.FC = () => {
</div>
<div className="mb-5">
<p className="font-bold text-jet dark:text-bright-gray">
Chunks processed per query
{t('settings.general.chunks')}
</p>
<Dropdown
options={chunks}
@ -100,13 +127,15 @@ const General: React.FC = () => {
</div>
<div className="w-56">
<p className="font-bold text-jet dark:text-bright-gray">
Delete all conversations
{t('settings.general.deleteAllLabel')}
</p>
<button
className="mt-2 flex w-full cursor-pointer items-center justify-between rounded-3xl border border-solid border-red-500 px-5 py-3 text-red-500 hover:bg-red-500 hover:text-white"
onClick={() => dispatch(setModalStateDeleteConv('ACTIVE'))}
>
<span className="overflow-hidden text-ellipsis ">Delete all</span>
<span className="overflow-hidden text-ellipsis ">
{t('settings.general.deleteAllBtn')}
</span>
</button>
</div>
</div>

@ -2,9 +2,8 @@ import React from 'react';
import { PromptProps, ActiveState } from '../models/misc';
import Dropdown from '../components/Dropdown';
import PromptsModal from '../preferences/PromptsModal';
import { useTranslation } from 'react-i18next';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const Prompts: React.FC<PromptProps> = ({
prompts,
selectedPrompt,
@ -34,7 +33,10 @@ const Prompts: React.FC<PromptProps> = ({
});
const [modalType, setModalType] = React.useState<'ADD' | 'EDIT'>('ADD');
const [modalState, setModalState] = React.useState<ActiveState>('INACTIVE');
const {
t,
i18n: { changeLanguage, language },
} = useTranslation();
const handleAddPrompt = async () => {
try {
const response = await fetch(`${apiHost}/api/create_prompt`, {
@ -158,7 +160,9 @@ const Prompts: React.FC<PromptProps> = ({
<div>
<div className="flex flex-row items-center gap-8">
<div>
<p className="font-semibold dark:text-bright-gray">Active Prompt</p>
<p className="font-semibold dark:text-bright-gray">
{t('settings.general.prompt')}
</p>
<Dropdown
options={prompts}
selectedValue={selectedPrompt.name}
@ -193,7 +197,7 @@ const Prompts: React.FC<PromptProps> = ({
setModalState('ACTIVE');
}}
>
Add new
{t('settings.general.addNew')}
</button>
</div>
</div>

@ -11,12 +11,18 @@ import {
import { Doc } from '../preferences/preferenceApi';
import ArrowLeft from '../assets/arrow-left.svg';
import ArrowRight from '../assets/arrow-right.svg';
import { useTranslation } from 'react-i18next';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const Settings: React.FC = () => {
const dispatch = useDispatch();
const tabs = ['General', 'Documents', 'API Keys'];
const { t } = useTranslation();
const tabs = [
t('settings.general.label'),
t('settings.documents.label'),
t('settings.apiKeys.label'),
];
const [activeTab, setActiveTab] = React.useState('General');
const [widgetScreenshot, setWidgetScreenshot] = React.useState<File | null>(
null,
@ -45,7 +51,7 @@ const Settings: React.FC = () => {
return (
<div className="wa p-4 pt-20 md:p-12">
<p className="text-2xl font-bold text-eerie-black dark:text-bright-gray">
Settings
{t('settings.label')}
</p>
<div className="mt-6 flex flex-row items-center space-x-4 overflow-x-auto md:space-x-8 ">
<div className="md:hidden">
@ -100,9 +106,9 @@ const Settings: React.FC = () => {
function renderActiveTab() {
switch (activeTab) {
case 'General':
case t('settings.general.label'):
return <General />;
case 'Documents':
case t('settings.documents.label'):
return (
<Documents
documents={documents}
@ -116,7 +122,7 @@ const Settings: React.FC = () => {
onWidgetScreenshotChange={updateWidgetScreenshot} // Add this line
/>
);
case 'API Keys':
case t('settings.apiKeys.label'):
return <APIKeys />;
default:
return null;

Loading…
Cancel
Save