From 6f135a0ccebe055559ba81c3f002096ea49dc391 Mon Sep 17 00:00:00 2001 From: Yuri Filipe Date: Sat, 18 Nov 2023 15:12:15 -0300 Subject: [PATCH 1/6] Translation of the application into Brazilian Portuguese --- app/locales/index.ts | 3 + app/locales/pt.ts | 466 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 app/locales/pt.ts diff --git a/app/locales/index.ts b/app/locales/index.ts index 79e314facdd..1d84de22cf1 100644 --- a/app/locales/index.ts +++ b/app/locales/index.ts @@ -1,5 +1,6 @@ import cn from "./cn"; import en from "./en"; +import pt from "./pt"; import tw from "./tw"; import id from "./id"; import fr from "./fr"; @@ -23,6 +24,7 @@ export type { LocaleType, PartialLocaleType } from "./cn"; const ALL_LANGS = { cn, en, + pt, tw, jp, ko, @@ -47,6 +49,7 @@ export const AllLangs = Object.keys(ALL_LANGS) as Lang[]; export const ALL_LANG_OPTIONS: Record = { cn: "简体中文", en: "English", + pt: "Português", tw: "繁體中文", jp: "日本語", ko: "한국어", diff --git a/app/locales/pt.ts b/app/locales/pt.ts new file mode 100644 index 00000000000..e984dc99a65 --- /dev/null +++ b/app/locales/pt.ts @@ -0,0 +1,466 @@ +import { SubmitKey } from "../store/config"; +import { LocaleType } from "../locales/index"; +import { getClientConfig } from "../config/client"; + +const isApp = !!getClientConfig()?.isApp; + +const pt: LocaleType = { + WIP: "Em breve...", + Error: { + Unauthorized: isApp + ? "Chave API inválida, por favor verifique em [Configurações](/#/settings)." + : "Acesso não autorizado, por favor insira o código de acesso em [auth](/#/auth) ou insira sua Chave API OpenAI.", + }, + Auth: { + Title: "Necessário Código de Acesso", + Tips: "Por favor, insira o código de acesso abaixo", + SubTips: "Ou insira sua Chave API OpenAI", + Input: "código de acesso", + Confirm: "Confirmar", + Later: "Depois", + }, + ChatItem: { + ChatItemCount: (count: number) => `${count} mensagens`, + }, + Chat: { + SubTitle: (count: number) => `${count} mensagens`, + EditMessage: { + Title: "Editar Todas as Mensagens", + Topic: { + Title: "Tópico", + SubTitle: "Mudar o tópico atual", + }, + }, + Actions: { + ChatList: "Ir Para Lista de Chat", + CompressedHistory: "Prompt de Memória Histórica Comprimida", + Export: "Exportar Todas as Mensagens como Markdown", + Copy: "Copiar", + Stop: "Parar", + Retry: "Tentar Novamente", + Pin: "Fixar", + PinToastContent: "Fixada 1 mensagem para prompts contextuais", + PinToastAction: "Visualizar", + Delete: "Deletar", + Edit: "Editar", + }, + Commands: { + new: "Iniciar um novo chat", + newm: "Iniciar um novo chat com máscara", + next: "Próximo Chat", + prev: "Chat Anterior", + clear: "Limpar Contexto", + del: "Deletar Chat", + }, + InputActions: { + Stop: "Parar", + ToBottom: "Para o Mais Recente", + Theme: { + auto: "Automático", + light: "Tema Claro", + dark: "Tema Escuro", + }, + Prompt: "Prompts", + Masks: "Máscaras", + Clear: "Limpar Contexto", + Settings: "Configurações", + }, + Rename: "Renomear Chat", + Typing: "Digitando…", + Input: (submitKey: string) => { + var inputHints = `${submitKey} para enviar`; + if (submitKey === String(SubmitKey.Enter)) { + inputHints += ", Shift + Enter para quebrar linha"; + } + return inputHints + ", / para buscar prompts, : para usar comandos"; + }, + Send: "Enviar", + Config: { + Reset: "Redefinir para Padrão", + SaveAs: "Salvar como Máscara", + }, + IsContext: "Prompt Contextual", + }, + Export: { + Title: "Exportar Mensagens", + Copy: "Copiar Tudo", + Download: "Baixar", + MessageFromYou: "Mensagem De Você", + MessageFromChatGPT: "Mensagem De ChatGPT", + Share: "Compartilhar para ShareGPT", + Format: { + Title: "Formato de Exportação", + SubTitle: "Markdown ou Imagem PNG", + }, + IncludeContext: { + Title: "Incluindo Contexto", + SubTitle: "Exportar prompts de contexto na máscara ou não", + }, + Steps: { + Select: "Selecionar", + Preview: "Pré-visualizar", + }, + Image: { + Toast: "Capturando Imagem...", + Modal: + "Pressione longamente ou clique com o botão direito para salvar a imagem", + }, + }, + Select: { + Search: "Buscar", + All: "Selecionar Tudo", + Latest: "Selecionar Mais Recente", + Clear: "Limpar", + }, + Memory: { + Title: "Prompt de Memória", + EmptyContent: "Nada ainda.", + Send: "Enviar Memória", + Copy: "Copiar Memória", + Reset: "Resetar Sessão", + ResetConfirm: + "Resetar irá limpar o histórico de conversa atual e a memória histórica. Você tem certeza que quer resetar?", + }, + Home: { + NewChat: "Novo Chat", + DeleteChat: "Confirmar para deletar a conversa selecionada?", + DeleteToast: "Chat Deletado", + Revert: "Reverter", + }, + Settings: { + Title: "Configurações", + SubTitle: "Todas as Configurações", + Danger: { + Reset: { + Title: "Resetar Todas as Configurações", + SubTitle: "Resetar todos os itens de configuração para o padrão", + Action: "Resetar", + Confirm: "Confirmar para resetar todas as configurações para o padrão?", + }, + Clear: { + Title: "Limpar Todos os Dados", + SubTitle: "Limpar todas as mensagens e configurações", + Action: "Limpar", + Confirm: "Confirmar para limpar todas as mensagens e configurações?", + }, + }, + Lang: { + Name: "Idioma", + All: "Todos os Idiomas", + }, + Avatar: "Avatar", + FontSize: { + Title: "Tamanho da Fonte", + SubTitle: "Ajustar o tamanho da fonte do conteúdo do chat", + }, + InjectSystemPrompts: { + Title: "Inserir Prompts de Sistema", + SubTitle: "Inserir um prompt de sistema global para cada requisição", + }, + InputTemplate: { + Title: "Modelo de Entrada", + SubTitle: "A mensagem mais recente será preenchida neste modelo", + }, + + Update: { + Version: (x: string) => `Versão: ${x}`, + IsLatest: "Última versão", + CheckUpdate: "Verificar Atualização", + IsChecking: "Verificando atualização...", + FoundUpdate: (x: string) => `Nova versão encontrada: ${x}`, + GoToUpdate: "Atualizar", + }, + SendKey: "Tecla de Envio", + Theme: "Tema", + TightBorder: "Borda Ajustada", + SendPreviewBubble: { + Title: "Bolha de Pré-visualização de Envio", + SubTitle: "Pré-visualizar markdown na bolha", + }, + AutoGenerateTitle: { + Title: "Gerar Título Automaticamente", + SubTitle: "Gerar um título adequado baseado no conteúdo da conversa", + }, + Sync: { + CloudState: "Última Atualização", + NotSyncYet: "Ainda não sincronizado", + Success: "Sincronização bem sucedida", + Fail: "Falha na sincronização", + + Config: { + Modal: { + Title: "Configurar Sincronização", + Check: "Verificar Conexão", + }, + SyncType: { + Title: "Tipo de Sincronização", + SubTitle: "Escolha seu serviço de sincronização favorito", + }, + Proxy: { + Title: "Habilitar Proxy CORS", + SubTitle: "Habilitar um proxy para evitar restrições de cross-origin", + }, + ProxyUrl: { + Title: "Endpoint de Proxy", + SubTitle: "Apenas aplicável ao proxy CORS embutido para este projeto", + }, + + WebDav: { + Endpoint: "Endpoint WebDAV", + UserName: "Nome de Usuário", + Password: "Senha", + }, + + UpStash: { + Endpoint: "URL REST Redis UpStash", + UserName: "Nome do Backup", + Password: "Token REST Redis UpStash", + }, + }, + + LocalState: "Dados Locais", + Overview: (overview: any) => { + return `${overview.chat} chats,${overview.message} mensagens,${overview.prompt} prompts,${overview.mask} máscaras`; + }, + ImportFailed: "Falha ao importar do arquivo", + }, + Mask: { + Splash: { + Title: "Tela de Início da Máscara", + SubTitle: + "Mostrar uma tela de início da máscara antes de iniciar novo chat", + }, + Builtin: { + Title: "Esconder Máscaras Embutidas", + SubTitle: "Esconder máscaras embutidas na lista de máscaras", + }, + }, + Prompt: { + Disable: { + Title: "Desabilitar auto-completar", + SubTitle: "Digite / para acionar auto-completar", + }, + List: "Lista de Prompts", + ListCount: (builtin: number, custom: number) => + `${builtin} embutidos, ${custom} definidos pelo usuário`, + Edit: "Editar", + Modal: { + Title: "Lista de Prompts", + Add: "Adicionar Um", + Search: "Buscar Prompts", + }, + EditModal: { + Title: "Editar Prompt", + }, + }, + HistoryCount: { + Title: "Contagem de Mensagens Anexadas", + SubTitle: "Número de mensagens enviadas anexadas por requisição", + }, + CompressThreshold: { + Title: "Limite de Compressão de Histórico", + SubTitle: + "Irá comprimir se o comprimento das mensagens não comprimidas exceder o valor", + }, + + Usage: { + Title: "Saldo da Conta", + SubTitle(used: any, total: any) { + return `Usado este mês ${used}, assinatura ${total}`; + }, + IsChecking: "Verificando...", + Check: "Verificar", + NoAccess: "Insira a Chave API para verificar o saldo", + }, + Access: { + AccessCode: { + Title: "Código de Acesso", + SubTitle: "Controle de Acesso Habilitado", + Placeholder: "Insira o Código", + }, + CustomEndpoint: { + Title: "Endpoint Personalizado", + SubTitle: "Use serviço personalizado Azure ou OpenAI", + }, + Provider: { + Title: "Provedor do Modelo", + SubTitle: "Selecione Azure ou OpenAI", + }, + OpenAI: { + ApiKey: { + Title: "Chave API OpenAI", + SubTitle: "Usar Chave API OpenAI personalizada", + Placeholder: "sk-xxx", + }, + + Endpoint: { + Title: "Endpoint OpenAI", + SubTitle: + "Deve começar com http(s):// ou usar /api/openai como padrão", + }, + }, + Azure: { + ApiKey: { + Title: "Chave API Azure", + SubTitle: "Verifique sua chave API do console Azure", + Placeholder: "Chave API Azure", + }, + + Endpoint: { + Title: "Endpoint Azure", + SubTitle: "Exemplo: ", + }, + + ApiVerion: { + Title: "Versão API Azure", + SubTitle: "Verifique sua versão API do console Azure", + }, + }, + CustomModel: { + Title: "Modelos Personalizados", + SubTitle: "Opções de modelo personalizado, separados por vírgula", + }, + }, + + Model: "Modelo", + Temperature: { + Title: "Temperatura", + SubTitle: "Um valor maior torna a saída mais aleatória", + }, + TopP: { + Title: "Top P", + SubTitle: "Não altere este valor junto com a temperatura", + }, + MaxTokens: { + Title: "Máximo de Tokens", + SubTitle: "Comprimento máximo de tokens de entrada e tokens gerados", + }, + PresencePenalty: { + Title: "Penalidade de Presença", + SubTitle: + "Um valor maior aumenta a probabilidade de falar sobre novos tópicos", + }, + FrequencyPenalty: { + Title: "Penalidade de Frequência", + SubTitle: + "Um valor maior diminui a probabilidade de repetir a mesma linha", + }, + }, + Store: { + DefaultTopic: "Nova Conversa", + BotHello: "Olá! Como posso ajudá-lo hoje?", + Error: "Algo deu errado, por favor tente novamente mais tarde.", + Prompt: { + History: (content: string) => + "Este é um resumo do histórico de chat como um recapitulativo: " + + content, + Topic: + "Por favor, gere um título de quatro a cinco palavras resumindo nossa conversa sem qualquer introdução, pontuação, aspas, períodos, símbolos ou texto adicional. Remova as aspas que o envolvem.", + Summarize: + "Resuma a discussão brevemente em 200 palavras ou menos para usar como um prompt para o contexto futuro.", + }, + }, + Copy: { + Success: "Copiado para a área de transferência", + Failed: + "Falha na cópia, por favor conceda permissão para acessar a área de transferência", + }, + Download: { + Success: "Conteúdo baixado para seu diretório.", + Failed: "Falha no download.", + }, + Context: { + Toast: (x: any) => `Com ${x} prompts contextuais`, + Edit: "Configurações do Chat Atual", + Add: "Adicionar um Prompt", + Clear: "Contexto Limpo", + Revert: "Reverter", + }, + Plugin: { + Name: "Plugin", + }, + FineTuned: { + Sysmessage: "Você é um assistente que", + }, + Mask: { + Name: "Máscara", + Page: { + Title: "Template de Prompt", + SubTitle: (count: number) => `${count} templates de prompt`, + Search: "Buscar Templates", + Create: "Criar", + }, + Item: { + Info: (count: number) => `${count} prompts`, + Chat: "Chat", + View: "Visualizar", + Edit: "Editar", + Delete: "Deletar", + DeleteConfirm: "Confirmar para deletar?", + }, + EditModal: { + Title: (readonly: boolean) => + `Editar Template de Prompt ${readonly ? "(somente leitura)" : ""}`, + Download: "Baixar", + Clone: "Clonar", + }, + Config: { + Avatar: "Avatar do Bot", + Name: "Nome do Bot", + Sync: { + Title: "Usar Configuração Global", + SubTitle: "Usar configuração global neste chat", + Confirm: + "Confirmar para substituir a configuração personalizada pela configuração global?", + }, + HideContext: { + Title: "Esconder Prompts de Contexto", + SubTitle: "Não mostrar prompts de contexto no chat", + }, + Share: { + Title: "Compartilhar Esta Máscara", + SubTitle: "Gerar um link para esta máscara", + Action: "Copiar Link", + }, + }, + }, + NewChat: { + Return: "Retornar", + Skip: "Apenas Começar", + Title: "Escolher uma Máscara", + SubTitle: "Converse com a Alma por trás da Máscara", + More: "Encontre Mais", + NotShow: "Nunca Mostrar Novamente", + ConfirmNoShow: + "Confirmar para desabilitar?Você pode habilitar nas configurações depois.", + }, + + UI: { + Confirm: "Confirmar", + Cancel: "Cancelar", + Close: "Fechar", + Create: "Criar", + Edit: "Editar", + Export: "Exportar", + Import: "Importar", + Sync: "Sincronizar", + Config: "Configurar", + }, + Exporter: { + Description: { + Title: "Apenas mensagens após a limpeza do contexto serão exibidas", + }, + Model: "Modelo", + Messages: "Mensagens", + Topic: "Tópico", + Time: "Tempo", + }, + + URLCommand: { + Code: "Código de acesso detectado a partir da url, confirmar para aplicar? ", + Settings: + "Configurações detectadas a partir da url, confirmar para aplicar?", + }, +}; + +export default pt; From 536ace8e10553c6101308ec09f2fa65bc84d2416 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 19 Nov 2023 18:24:51 +0800 Subject: [PATCH 2/6] feat: animate streaming response to make more smooth --- app/client/platforms/openai.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 930d606900a..dc79d2cece7 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -115,12 +115,33 @@ export class ChatGPTApi implements LLMApi { if (shouldStream) { let responseText = ""; + let remainText = ""; let finished = false; + // animate response to make it looks smooth + function animateResponseText() { + if (finished || controller.signal.aborted) { + responseText += remainText; + console.log("[Response Animation] finished"); + return; + } + + if (remainText.length > 0) { + responseText += remainText[0]; + remainText = remainText.slice(1); + options.onUpdate?.(responseText, remainText[0]); + } + + requestAnimationFrame(animateResponseText); + } + + // start animaion + animateResponseText(); + const finish = () => { if (!finished) { - options.onFinish(responseText); finished = true; + options.onFinish(responseText + remainText); } }; @@ -183,8 +204,7 @@ export class ChatGPTApi implements LLMApi { }; const delta = json.choices[0]?.delta?.content; if (delta) { - responseText += delta; - options.onUpdate?.(responseText, delta); + remainText += delta; } } catch (e) { console.error("[Request] parse error", text); From dc7159a4504682f6bfad104d5d03168412c550f1 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 19 Nov 2023 18:32:54 +0800 Subject: [PATCH 3/6] feat: close #3301 enable or diable default models with -all / +all --- README.md | 6 ++++-- README_CN.md | 5 +++-- app/utils/model.ts | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d4e304f9d04..ef7f2e1d9fb 100644 --- a/README.md +++ b/README.md @@ -216,9 +216,11 @@ If you want to disable parse settings from url, set this to 1. ### `CUSTOM_MODELS` (optional) > Default: Empty -> Example: `+llama,+claude-2,-gpt-3.5-turbo,gpt-4-1106-preview:gpt-4-turbo` means add `llama, claude-2` to model list, and remove `gpt-3.5-turbo` from list, and display `gpt-4-1106-preview` as `gpt-4-turbo`. +> Example: `+llama,+claude-2,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo` means add `llama, claude-2` to model list, and remove `gpt-3.5-turbo` from list, and display `gpt-4-1106-preview` as `gpt-4-turbo`. -To control custom models, use `+` to add a custom model, use `-` to hide a model, use `name:displayName` to customize model name, separated by comma. +To control custom models, use `+` to add a custom model, use `-` to hide a model, use `name=displayName` to customize model name, separated by comma. + +User `-all` to disable all default models, `+all` to enable all default models. ## Requirements diff --git a/README_CN.md b/README_CN.md index dde8c19b352..3b713255a47 100644 --- a/README_CN.md +++ b/README_CN.md @@ -122,9 +122,10 @@ Azure Api 版本,你可以在这里找到:[Azure 文档](https://learn.micro ### `CUSTOM_MODELS` (可选) -> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-1106-preview:gpt-4-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`,并将 `gpt-4-1106-preview` 模型名字展示为 `gpt-4-turbo`。 +> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`,并将 `gpt-4-1106-preview` 模型名字展示为 `gpt-4-turbo`。 +> 如果你想先禁用所有模型,再启用指定模型,可以使用 `-all,+gpt-3.5-turbo`,则表示仅启用 `gpt-3.5-turbo` -用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名:展示名` 来自定义模型的展示名,用英文逗号隔开。 +用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。 ## 开发 diff --git a/app/utils/model.ts b/app/utils/model.ts index d5c009c02d2..bf7300806ff 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -27,6 +27,12 @@ export function collectModelTable( const nameConfig = m.startsWith("+") || m.startsWith("-") ? m.slice(1) : m; const [name, displayName] = nameConfig.split(":"); + + // enable or disable all models + if (name === "all") { + Object.values(modelTable).forEach((m) => (m.available = available)); + } + modelTable[name] = { name, displayName: displayName || name, From 45b88ebb2a720c62d60e63a873004d3cd9734801 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 19 Nov 2023 18:34:39 +0800 Subject: [PATCH 4/6] feat: close #3304 use `=` instead of `:` to map model name in CUSTOM_MODELS --- app/utils/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils/model.ts b/app/utils/model.ts index bf7300806ff..74b28a66ae8 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -26,7 +26,7 @@ export function collectModelTable( const available = !m.startsWith("-"); const nameConfig = m.startsWith("+") || m.startsWith("-") ? m.slice(1) : m; - const [name, displayName] = nameConfig.split(":"); + const [name, displayName] = nameConfig.split("="); // enable or disable all models if (name === "all") { From 6aade62ce2f131caeaefc18689fea502ec1a3966 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 19 Nov 2023 18:42:30 +0800 Subject: [PATCH 5/6] feat: close #3300 support multiple api keys --- README.md | 2 +- README_CN.md | 2 +- app/config/server.ts | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ef7f2e1d9fb..3050fcc95e1 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ Access password, separated by comma. ### `OPENAI_API_KEY` (required) -Your openai api key. +Your openai api key, join multiple api keys with comma. ### `BASE_URL` (optional) diff --git a/README_CN.md b/README_CN.md index 3b713255a47..0ef508f61ac 100644 --- a/README_CN.md +++ b/README_CN.md @@ -68,7 +68,7 @@ code1,code2,code3 ### `OPENAI_API_KEY` (必填项) -OpanAI 密钥,你在 openai 账户页面申请的 api key。 +OpanAI 密钥,你在 openai 账户页面申请的 api key,使用英文逗号隔开多个 key,这样可以随机轮询这些 key。 ### `CODE` (可选) diff --git a/app/config/server.ts b/app/config/server.ts index 2f2e7d7fd8a..2398805a264 100644 --- a/app/config/server.ts +++ b/app/config/server.ts @@ -62,9 +62,17 @@ export const getServerSideConfig = () => { const isAzure = !!process.env.AZURE_URL; + const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? ""; + const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim()); + const randomIndex = Math.floor(Math.random() * apiKeys.length); + const apiKey = apiKeys[randomIndex]; + console.log( + `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`, + ); + return { baseUrl: process.env.BASE_URL, - apiKey: process.env.OPENAI_API_KEY, + apiKey, openaiOrgId: process.env.OPENAI_ORG_ID, isAzure, From f2485931d9b3680234f4816f4526759c8d4b741e Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 19 Nov 2023 19:15:11 +0800 Subject: [PATCH 6/6] feat: better animation speed --- app/client/platforms/openai.ts | 8 +++++--- app/locales/index.ts | 2 +- app/locales/pt.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index dc79d2cece7..8ea864692d5 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -127,9 +127,11 @@ export class ChatGPTApi implements LLMApi { } if (remainText.length > 0) { - responseText += remainText[0]; - remainText = remainText.slice(1); - options.onUpdate?.(responseText, remainText[0]); + const fetchCount = Math.max(1, Math.round(remainText.length / 60)); + const fetchText = remainText.slice(0, fetchCount); + responseText += fetchText; + remainText = remainText.slice(fetchCount); + options.onUpdate?.(responseText, fetchText); } requestAnimationFrame(animateResponseText); diff --git a/app/locales/index.ts b/app/locales/index.ts index 1d84de22cf1..cfbdff2977e 100644 --- a/app/locales/index.ts +++ b/app/locales/index.ts @@ -24,8 +24,8 @@ export type { LocaleType, PartialLocaleType } from "./cn"; const ALL_LANGS = { cn, en, - pt, tw, + pt, jp, ko, id, diff --git a/app/locales/pt.ts b/app/locales/pt.ts index e984dc99a65..55a40497045 100644 --- a/app/locales/pt.ts +++ b/app/locales/pt.ts @@ -145,7 +145,7 @@ const pt: LocaleType = { }, }, Lang: { - Name: "Idioma", + Name: "Language", All: "Todos os Idiomas", }, Avatar: "Avatar",