Skip to content

Commit

Permalink
feat: support multiple instance translate service (#828)
Browse files Browse the repository at this point in the history
* provides the ability to multiple instances of translate service from buildin

* support openai translate service multiple instances

* multiple instance in translate window

* config translate service

* add instance config name

* display instanceName in translate window
  • Loading branch information
xtyuns authored Jun 9, 2024
1 parent 8a37653 commit dbe45a9
Show file tree
Hide file tree
Showing 17 changed files with 285 additions and 191 deletions.
1 change: 1 addition & 0 deletions src/i18n/locales/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@
"services": {
"help": "Konfigurationsanleitung",
"no_need": "Keine Konfiguration erforderlich",
"instance_name": "Konfigurationsname",
"translate": {
"deepl": {
"title": "DeepL",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
"services": {
"help": "Configuration Guide",
"no_need": "No configuration required",
"instance_name": "Configuration Name",
"translate": {
"deepl": {
"title": "DeepL",
Expand Down
5 changes: 3 additions & 2 deletions src/i18n/locales/fr_FR.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"translation": {
"services": {
"help": "Guide de configuration",
"no_need": "Aucune configuration requise",
"instance_name": "Nom de la configuration",
"translate": {
"youdao": {
"appkey": "ID d'application",
Expand Down Expand Up @@ -246,8 +249,6 @@
"title": "Eudic"
}
},
"help": "Guide de configuration",
"no_need": "Aucune configuration requise",
"tts": {
"lingva_tts": {
"title": "Lingva",
Expand Down
5 changes: 3 additions & 2 deletions src/i18n/locales/it_IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"coming": "Prossimamente…"
},
"services": {
"help": "Guida alla configurazione",
"no_need": "Nessuna configurazione richiesta",
"instance_name": "Nome della configurazione",
"translate": {
"youdao": {
"appkey": "ID app",
Expand Down Expand Up @@ -212,8 +215,6 @@
"name": "Nome della raccolta"
}
},
"help": "Guida alla configurazione",
"no_need": "Nessuna configurazione richiesta",
"tts": {
"lingva_tts": {
"title": "Lingva",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/pt_BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
"services": {
"help": "Guia de Configuração",
"no_need": "Nenhuma Configuração Requerida",
"instance_name": "Nome da configuração",
"translate": {
"deepl": {
"title": "DeepL",
Expand Down
5 changes: 3 additions & 2 deletions src/i18n/locales/pt_PT.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"coming": "Em breve…"
},
"services": {
"help": "Guia de Configuração",
"no_need": "Nenhuma Configuração Requerida",
"instance_name": "Nome da configuração",
"translate": {
"youdao": {
"appkey": "ID do App",
Expand Down Expand Up @@ -219,8 +222,6 @@
"title": "Eudic"
}
},
"help": "Guia de Configuração",
"no_need": "Nenhuma Configuração Requerida",
"tts": {
"lingva_tts": {
"title": "Lingva",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ru_RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
"services": {
"help": "Руководство по настройке",
"no_need": "Настройка не требуется",
"instance_name": "Имя конфигурации",
"translate": {
"deepl": {
"title": "DeepL",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh_CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
"services": {
"help": "配置指南",
"no_need": "无需配置",
"instance_name": "配置名称",
"translate": {
"deepl": {
"title": "DeepL",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh_TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
"services": {
"help": "設定指南",
"no_need": "無需設定",
"instance_name": "配置名稱",
"translate": {
"deepl": {
"title": "DeepL",
Expand Down
27 changes: 24 additions & 3 deletions src/services/translate/openai/Config.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { useConfig } from '../../../hooks/useConfig';
import { useToastStyle } from '../../../hooks';
import { translate } from './index';
import { Language } from './index';
import { INSTANCE_NAME_CONFIG_KEY } from '../../../utils/service_instance';

export function Config(props) {
const { updateServiceList, onClose } = props;
const { instanceKey, updateServiceList, onClose } = props;
const [openaiConfig, setOpenaiConfig] = useConfig(
'openai',
instanceKey,
{
[INSTANCE_NAME_CONFIG_KEY]: 'OpenAI',
service: 'openai',
requestPath: 'https://api.openai.com/v1/chat/completions',
model: 'gpt-3.5-turbo',
Expand Down Expand Up @@ -67,7 +69,7 @@ export function Config(props) {
() => {
setIsLoading(false);
setOpenaiConfig(openaiConfig, true);
updateServiceList('openai');
updateServiceList(instanceKey);
onClose();
},
(e) => {
Expand All @@ -78,6 +80,25 @@ export function Config(props) {
}}
>
<Toaster />
<div className='config-item'>
<Input
label={t('services.instance_name')}
labelPlacement='outside-left'
value={openaiConfig[INSTANCE_NAME_CONFIG_KEY]}
variant='bordered'
classNames={{
base: 'justify-between',
label: 'text-[length:--nextui-font-size-medium]',
mainWrapper: 'max-w-[50%]',
}}
onValueChange={(value) => {
setOpenaiConfig({
...openaiConfig,
[INSTANCE_NAME_CONFIG_KEY]: value,
});
}}
/>
</div>
<div className='config-item'>
<h3 className='my-auto'>{t('services.help')}</h3>
<Button
Expand Down
32 changes: 32 additions & 0 deletions src/utils/service_instance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export enum ServiceSourceType {
BUILDIN = 'buildin',
PLUGIN = 'plugin',
}

export function getServiceSouceType(serviceInstanceKey: string): ServiceSourceType {
if (serviceInstanceKey.startsWith('[plugin]')) {
return ServiceSourceType.PLUGIN
} else {
return ServiceSourceType.BUILDIN
}
}

export function whetherPluginService(serviceInstanceKey: string): boolean {
return getServiceSouceType(serviceInstanceKey) === ServiceSourceType.PLUGIN
}


// The serviceInstanceKey consists of the service name and it's id, separated by @
// In earlier versions, the @ separator and id were optional, so they all have only one instance.
export function createServiceInstanceKey(serviceName: string): string {
const randomId = Math.random().toString(36).substring(2)
return `${serviceName}@${randomId}`
}


// if the serviceInstanceKey is from a plugin, serviceName is it's pluginId
export function getServiceName(serviceInstanceKey: string): string {
return serviceInstanceKey.split('@')[0]
}

export const INSTANCE_NAME_CONFIG_KEY = 'instanceName'
30 changes: 18 additions & 12 deletions src/window/Config/pages/Service/Translate/ConfigModal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ import React from 'react';

import * as builtinServices from '../../../../../../services/translate';
import { PluginConfig } from '../../PluginConfig';
import { ServiceSourceType, getServiceName, getServiceSouceType, whetherPluginService } from '../../../../../../utils/service_instance';

export default function ConfigModal(props) {
const { isOpen, onOpenChange, name, updateServiceList, pluginList } = props;
const serviceType = name.startsWith('[plugin]') ? 'plugin' : 'builtin';
const { serviceInstanceKey, pluginList, isOpen, onOpenChange, updateServiceInstanceList } = props;

const serviceSourceType = getServiceSouceType(serviceInstanceKey)
const pluginServiceFlag = whetherPluginService(serviceInstanceKey)
const serviceName = getServiceName(serviceInstanceKey)

const { t } = useTranslation();
const ConfigComponent = name.startsWith('[plugin]') ? PluginConfig : builtinServices[name].Config;
const ConfigComponent = pluginServiceFlag ? PluginConfig : builtinServices[serviceName].Config;

return serviceType === 'plugin' && !(name in pluginList) ? (
return pluginServiceFlag && !(serviceName in pluginList) ? (
<></>
) : (
<Modal
Expand All @@ -23,36 +28,37 @@ export default function ConfigModal(props) {
{(onClose) => (
<>
<ModalHeader>
{serviceType === 'builtin' && (
{serviceSourceType === ServiceSourceType.BUILDIN && (
<>
<img
src={builtinServices[name].info.icon}
src={builtinServices[serviceName].info.icon}
className='h-[24px] w-[24px] my-auto'
draggable={false}
/>
<Spacer x={2} />
{t(`services.translate.${name}.title`)}
{t(`services.translate.${serviceName}.title`)}
</>
)}
{serviceType === 'plugin' && (
{pluginServiceFlag && (
<>
<img
src={pluginList[name].icon}
src={pluginList[serviceName].icon}
className='h-[24px] w-[24px] my-auto'
draggable={false}
/>

<Spacer x={2} />
{`${pluginList[name].display} [${t('common.plugin')}]`}
{`${pluginList[serviceName].display} [${t('common.plugin')}]`}
</>
)}
</ModalHeader>
<ModalBody>
<ConfigComponent
name={name}
name={serviceName}
instanceKey={serviceInstanceKey}
pluginType='translate'
pluginList={pluginList}
updateServiceList={updateServiceList}
updateServiceList={updateServiceInstanceList}
onClose={onClose}
/>
</ModalBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { useTranslation } from 'react-i18next';
import React from 'react';

import * as builtinServices from '../../../../../../services/translate';
import { createServiceInstanceKey } from '../../../../../../utils/service_instance';

export default function SelectModal(props) {
const { isOpen, onOpenChange, setConfigName, onConfigOpen } = props;
const { isOpen, onOpenChange, setCurrentConfigKey, onConfigOpen } = props;
const { t } = useTranslation();

return (
Expand All @@ -25,7 +26,7 @@ export default function SelectModal(props) {
<Button
fullWidth
onPress={() => {
setConfigName(x);
setCurrentConfigKey(createServiceInstanceKey(x));
onConfigOpen();
}}
startContent={
Expand Down
33 changes: 18 additions & 15 deletions src/window/Config/pages/Service/Translate/ServiceItem/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ import React from 'react';

import * as builtinServices from '../../../../../../services/translate';
import { useConfig } from '../../../../../../hooks';
import { INSTANCE_NAME_CONFIG_KEY, ServiceSourceType, getServiceName, getServiceSouceType } from '../../../../../../utils/service_instance';

export default function ServiceItem(props) {
const { name, deleteService, setConfigName, onConfigOpen, pluginList, ...drag } = props;
const serviceType = name.startsWith('[plugin]') ? 'plugin' : 'builtin';
const { serviceInstanceKey, pluginList, deleteServiceInstance, setCurrentConfigKey, onConfigOpen, ...drag } = props;
const { t } = useTranslation();
const [serviceConfig, setServiceConfig] = useConfig(name, {});
const [serviceInstanceConfig, setServiceInstanceConfig] = useConfig(serviceInstanceKey, {});

return serviceType === 'plugin' && !(name in pluginList) ? (
const serviceSourceType = getServiceSouceType(serviceInstanceKey)
const serviceName = getServiceName(serviceInstanceKey)

return serviceSourceType === ServiceSourceType.PLUGIN && !(serviceName in pluginList) ? (
<></>
) : (
serviceConfig !== null && (
serviceInstanceConfig !== null && (
<div className='bg-content2 rounded-md px-[10px] py-[20px] flex justify-between'>
<div className='flex'>
<div
Expand All @@ -28,43 +31,43 @@ export default function ServiceItem(props) {
</div>

<Spacer x={2} />
{serviceType === 'builtin' && (
{serviceSourceType === ServiceSourceType.BUILDIN && (
<>
<img
src={`${builtinServices[name].info.icon}`}
src={`${builtinServices[serviceName].info.icon}`}
className='h-[24px] w-[24px] my-auto'
draggable={false}
/>
<Spacer x={2} />
<h2 className='my-auto'>{t(`services.translate.${name}.title`)}</h2>
<h2 className='my-auto'>{serviceInstanceConfig[INSTANCE_NAME_CONFIG_KEY] || t(`services.translate.${serviceName}.title`)}</h2>
</>
)}
{serviceType === 'plugin' && (
{serviceSourceType === ServiceSourceType.PLUGIN && (
<>
<img
src={pluginList[name].icon}
src={pluginList[serviceName].icon}
className='h-[24px] w-[24px] my-auto'
draggable={false}
/>
<Spacer x={2} />
<h2 className='my-auto'>{`${pluginList[name].display} [${t('common.plugin')}]`}</h2>
<h2 className='my-auto'>{`${serviceInstanceConfig[INSTANCE_NAME_CONFIG_KEY] || pluginList[serviceName].display} [${t('common.plugin')}]`}</h2>
</>
)}
</div>
<div className='flex'>
<Switch
size='sm'
isSelected={serviceConfig['enable'] ?? true}
isSelected={serviceInstanceConfig['enable'] ?? true}
onValueChange={(v) => {
setServiceConfig({ ...serviceConfig, enable: v });
setServiceInstanceConfig({ ...serviceInstanceConfig, enable: v });
}}
/>
<Button
isIconOnly
size='sm'
variant='light'
onPress={() => {
setConfigName(name);
setCurrentConfigKey(serviceInstanceKey);
onConfigOpen();
}}
>
Expand All @@ -77,7 +80,7 @@ export default function ServiceItem(props) {
variant='light'
color='danger'
onPress={() => {
deleteService(name);
deleteServiceInstance(serviceInstanceKey);
}}
>
<MdDeleteOutline className='text-2xl' />
Expand Down
Loading

0 comments on commit dbe45a9

Please sign in to comment.