From cc594d4e3e37a1b34716c9e8253e982e29964d7d Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Fri, 2 Aug 2024 14:44:58 -0700 Subject: [PATCH 01/24] add SemVer tag to docker image publication --- .../build-and-push-image-semver.yaml | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/workflows/build-and-push-image-semver.yaml diff --git a/.github/workflows/build-and-push-image-semver.yaml b/.github/workflows/build-and-push-image-semver.yaml new file mode 100644 index 000000000..8fb6d35c2 --- /dev/null +++ b/.github/workflows/build-and-push-image-semver.yaml @@ -0,0 +1,115 @@ +name: Publish AnythingLLM Docker image on Release (amd64 & arm64) + +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +on: + release: + types: [published] + +jobs: + push_multi_platform_to_registries: + name: Push Docker multi-platform image to multiple registries + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Check if DockerHub build needed + shell: bash + run: | + # Check if the secret for USERNAME is set (don't even check for the password) + if [[ -z "${{ secrets.DOCKER_USERNAME }}" ]]; then + echo "DockerHub build not needed" + echo "enabled=false" >> $GITHUB_OUTPUT + else + echo "DockerHub build needed" + echo "enabled=true" >> $GITHUB_OUTPUT + fi + id: dockerhub + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a + # Only login to the Docker Hub if the repo is mintplex/anythingllm, to allow for forks to build on GHCR + if: steps.dockerhub.outputs.enabled == 'true' + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: | + ${{ steps.dockerhub.outputs.enabled == 'true' && 'mintplexlabs/anythingllm' || '' }} + ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build and push multi-platform Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + sbom: true + provenance: mode=max + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # For Docker scout there are some intermediary reported CVEs which exists outside + # of execution content or are unreachable by an attacker but exist in image. + # We create VEX files for these so they don't show in scout summary. + - name: Collect known and verified CVE exceptions + id: cve-list + run: | + # Collect CVEs from filenames in vex folder + CVE_NAMES="" + for file in ./docker/vex/*.vex.json; do + [ -e "$file" ] || continue + filename=$(basename "$file") + stripped_filename=${filename%.vex.json} + CVE_NAMES+=" $stripped_filename" + done + echo "CVE_EXCEPTIONS=$CVE_NAMES" >> $GITHUB_OUTPUT + shell: bash + + # About VEX attestations https://docs.docker.com/scout/explore/exceptions/ + # Justifications https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md#status-justifications + - name: Add VEX attestations + env: + CVE_EXCEPTIONS: ${{ steps.cve-list.outputs.CVE_EXCEPTIONS }} + run: | + echo $CVE_EXCEPTIONS + curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- + for cve in $CVE_EXCEPTIONS; do + for tag in "${{ join(fromJSON(steps.meta.outputs.json).tags, ' ') }}"; do + echo "Attaching VEX exception $cve to $tag" + docker scout attestation add \ + --file "./docker/vex/$cve.vex.json" \ + --predicate-type https://openvex.dev/ns/v0.2.0 \ + $tag + done + done + shell: bash From e28c7412c2fadffecf1f1ea6bca9818db8d3511e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn?= Date: Mon, 5 Aug 2024 13:18:25 -0300 Subject: [PATCH 02/24] Fix typo (#2041) --- frontend/src/pages/FineTuning/Steps/Introduction/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/FineTuning/Steps/Introduction/index.jsx b/frontend/src/pages/FineTuning/Steps/Introduction/index.jsx index 7b2a0b199..1b784d961 100644 --- a/frontend/src/pages/FineTuning/Steps/Introduction/index.jsx +++ b/frontend/src/pages/FineTuning/Steps/Introduction/index.jsx @@ -90,7 +90,7 @@ export default function Introduction({ setSettings, setStep }) {

In summary, if you are getting good results with RAG currently, creating a fine-tune can squeeze even more performance out - of a model. Fine-Tunes are are for improving response quality and + of a model. Fine-Tunes are for improving response quality and general responses, but they are not for knowledge recall - that is what RAG is for! Together, it is a powerful combination.

From b96129961ece0ed81a30b82ac1a51ad17d151982 Mon Sep 17 00:00:00 2001 From: Mr Simon C Date: Mon, 5 Aug 2024 17:29:56 +0100 Subject: [PATCH 03/24] 2034 automate port forwarding for GitHub codespaces using gh cli (#2035) * open the server port to public on container post create * install gh on container start * remove port command until needed * look for port 3001 to be available and make public * install netcat * run background nc task before server start * add sleep 5 due to error clashing with server start * remove netcat, use gh cli --- .devcontainer/devcontainer.json | 6 +++--- .vscode/tasks.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 00bdb5428..254ae0244 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -78,8 +78,8 @@ }, "updateContentCommand": "cd server && yarn && cd ../collector && PUPPETEER_DOWNLOAD_BASE_URL=https://storage.googleapis.com/chrome-for-testing-public yarn && cd ../frontend && yarn && cd .. && yarn setup:envs && yarn prisma:setup && echo \"Please run yarn dev:server, yarn dev:collector, and yarn dev:frontend in separate terminal tabs.\"", // Use 'postCreateCommand' to run commands after the container is created. - // This configures VITE for github codespaces - "postCreateCommand": "if [ \"${CODESPACES}\" = \"true\" ]; then echo 'VITE_API_BASE=\"https://$CODESPACE_NAME-3001.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN/api\"' > ./frontend/.env; fi", + // This configures VITE for github codespaces and installs gh cli + "postCreateCommand": "if [ \"${CODESPACES}\" = \"true\" ]; then echo 'VITE_API_BASE=\"https://$CODESPACE_NAME-3001.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN/api\"' > ./frontend/.env && (type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) && sudo mkdir -p -m 755 /etc/apt/keyrings && wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null && sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg && echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null && sudo apt update && sudo apt install gh -y; fi", "portsAttributes": { "3001": { "label": "Backend", @@ -208,4 +208,4 @@ } // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fd0eb3bd2..6783e17e9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -46,7 +46,7 @@ } } }, - "command": "cd ${workspaceFolder}/server/ && yarn dev", + "command": "if [ \"${CODESPACES}\" = \"true\" ]; then while ! gh codespace ports -c $CODESPACE_NAME | grep 3001; do sleep 1; done; gh codespace ports visibility 3001:public -c $CODESPACE_NAME; fi & cd ${workspaceFolder}/server/ && yarn dev", "runOptions": { "instanceLimit": 1, "reevaluateOnRerun": true From f787502755981244df068aa3e582ae2db314f051 Mon Sep 17 00:00:00 2001 From: Ramon Oliveira <69830144+im-ramon@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:45:28 -0300 Subject: [PATCH 04/24] Adds full Brazilian Portuguese translation for the user interface (#2054) Adds the complete Brazilian Portuguese translation for the user interface. --- frontend/src/locales/pt_BR/common.js | 496 +++++++++++++++++++++++++++ frontend/src/locales/resources.js | 4 + 2 files changed, 500 insertions(+) create mode 100644 frontend/src/locales/pt_BR/common.js diff --git a/frontend/src/locales/pt_BR/common.js b/frontend/src/locales/pt_BR/common.js new file mode 100644 index 000000000..a0f8d495c --- /dev/null +++ b/frontend/src/locales/pt_BR/common.js @@ -0,0 +1,496 @@ +const TRANSLATIONS = { + common: { + "workspaces-name": "Nome dos Workspaces", + error: "erro", + success: "sucesso", + user: "Usuário", + selection: "Seleção de Modelo", + saving: "Salvando...", + save: "Salvar alterações", + previous: "Página Anterior", + next: "Próxima Página", + }, + + // Setting Sidebar menu items. + settings: { + title: "Configurações da Instância", + system: "Configurações Gerais", + invites: "Convites", + users: "Usuários", + workspaces: "Workspaces", + "workspace-chats": "Chats do Workspace", + customization: "Customização", + "api-keys": "API para Desenvolvedores", + llm: "LLM", + transcription: "Transcrição", + embedder: "Incorporador", + "text-splitting": "Divisor de Texto e Fragmentação", + "voice-speech": "Voz e Fala", + "vector-database": "Banco de Dados Vetorial", + embeds: "Incorporar Chat", + "embed-chats": "Histórico de Chats Incorporados", + security: "Segurança", + "event-logs": "Logs de Eventos", + privacy: "Privacidade e Dados", + "ai-providers": "Provedores de IA", + "agent-skills": "Habilidades do Agente", + admin: "Admin", + tools: "Ferramentas", + "experimental-features": "Recursos Experimentais", + contact: "Contato com Suporte", + }, + + // Page Definitions + login: { + "multi-user": { + welcome: "Bem-vindo ao", + "placeholder-username": "Nome de Usuário", + "placeholder-password": "Senha", + login: "Entrar", + validating: "Validando...", + "forgot-pass": "Esqueceu a senha", + reset: "Redefinir", + }, + "sign-in": { + start: "Faça login na sua", + end: "conta.", + }, + "password-reset": { + title: "Redefinição de Senha", + description: + "Forneça as informações necessárias abaixo para redefinir sua senha.", + "recovery-codes": "Códigos de Recuperação", + "recovery-code": "Código de Recuperação {{index}}", + "back-to-login": "Voltar ao Login", + }, + }, + + welcomeMessage: { + part1: + "Bem-vindo ao AnythingLLM, AnythingLLM é uma ferramenta de IA de código aberto da Mintplex Labs que transforma qualquer coisa em um chatbot treinado que você pode consultar e conversar. AnythingLLM é um software BYOK (bring-your-own-keys | traga suas próprias chaves), portanto, não há assinatura, taxa ou cobranças para este software fora dos serviços que você deseja usar com ele.", + part2: + "AnythingLLM é a maneira mais fácil de reunir produtos de IA poderosos como OpenAi, GPT-4, LangChain, PineconeDB, ChromaDB e outros serviços em um pacote organizado sem complicações para aumentar sua produtividade em 100x.", + part3: + "AnythingLLM pode ser executado totalmente localmente em sua máquina com pouca sobrecarga que você nem perceberá que está lá! Não é necessário GPU. A instalação em nuvem e localmente também está disponível.\nO ecossistema de ferramentas de IA fica mais poderoso a cada dia. AnythingLLM facilita o uso.", + githubIssue: "Criar uma issue no Github", + user1: "Como eu começo?!", + part4: + 'É simples. Todas as coleções são organizadas em grupos que chamamos de "Workspaces". Workspaces são grupos de arquivos, documentos, imagens, PDFs e outros arquivos que serão transformados em algo que os LLMs podem entender e usar em conversas.\n\nVocê pode adicionar e remover arquivos a qualquer momento.', + createWorkspace: "Crie seu primeiro workspace", + user2: + "Isso é como um Dropbox de IA ou algo assim? E quanto a conversar? Não é um chatbot?", + part5: + "AnythingLLM é mais do que um Dropbox mais inteligente.\n\nAnythingLLM oferece duas maneiras de conversar com seus dados:\n\nConsulta: Seus chats retornarão dados ou inferências encontradas com os documentos em seu workspace ao qual tem acesso. Adicionar mais documentos ao Workspace o torna mais inteligente!\n\nConversacional: Seus documentos + seu histórico de chat em andamento contribuem para o conhecimento do LLM ao mesmo tempo. Ótimo para adicionar informações em tempo real baseadas em texto ou correções e mal-entendidos que o LLM possa ter.\n\nVocê pode alternar entre qualquer modo \nno meio da conversa!", + user3: "Uau, isso soa incrível, deixe-me experimentar já!", + part6: "Divirta-se!", + starOnGithub: "Dar estrela no GitHub", + contact: "Contato Mintplex Labs", + }, + + "new-workspace": { + title: "Novo Workspace", + placeholder: "Meu Workspace", + }, + + // Workspace Settings menu items + "workspaces—settings": { + general: "Configurações Gerais", + chat: "Configurações de Chat", + vector: "Banco de Dados Vetorial", + members: "Membros", + agent: "Configuração do Agente", + }, + + // General Appearance + general: { + vector: { + title: "Contagem de Vetores", + description: "Número total de vetores no seu banco de dados vetorial.", + }, + names: { + description: "Isso mudará apenas o nome de exibição do seu workspace.", + }, + message: { + title: "Mensagens de Chat Sugeridas", + description: + "Personalize as mensagens que serão sugeridas aos usuários do seu workspace.", + add: "Adicionar nova mensagem", + save: "Salvar Mensagens", + heading: "Explique para mim", + body: "os benefícios do AnythingLLM", + }, + pfp: { + title: "Imagem de Perfil do Assistente", + description: + "Personalize a imagem de perfil do assistente para este workspace.", + image: "Imagem do Workspace", + remove: "Remover Imagem do Workspace", + }, + delete: { + title: "Excluir Workspace", + description: + "Excluir este workspace e todos os seus dados. Isso excluirá o workspace para todos os usuários.", + delete: "Excluir Workspace", + deleting: "Excluindo Workspace...", + "confirm-start": "Você está prestes a excluir todo o seu", + "confirm-end": + "workspace. Isso removerá todas as incorporações vetoriais no seu banco de dados vetorial.\n\nOs arquivos de origem originais permanecerão intactos. Esta ação é irreversível.", + }, + }, + + // Chat Settings + chat: { + llm: { + title: "Provedor de LLM do Workspace", + description: + "O provedor e modelo específico de LLM que será usado para este workspace. Por padrão, usa o provedor e as configurações do sistema LLM.", + search: "Pesquisar todos os provedores de LLM", + }, + model: { + title: "Modelo de Chat do Workspace", + description: + "O modelo de chat específico que será usado para este workspace. Se vazio, usará a preferência do LLM do sistema.", + wait: "-- aguardando modelos --", + }, + mode: { + title: "Modo de Chat", + chat: { + title: "Chat", + "desc-start": "fornecerá respostas com o conhecimento geral do LLM", + and: "e", + "desc-end": "contexto do documento encontrado.", + }, + query: { + title: "Consulta", + "desc-start": "fornecerá respostas", + only: "somente", + "desc-end": "se o contexto do documento for encontrado.", + }, + }, + history: { + title: "Histórico de Chat", + "desc-start": + "O número de chats anteriores que serão incluídos na memória de curto prazo da resposta.", + recommend: "Recomendado: 20. ", + "desc-end": + "Qualquer coisa acima de 45 provavelmente levará a falhas contínuas de chat dependendo do tamanho da mensagem.", + }, + prompt: { + title: "Prompt", + description: + "O prompt que será usado neste workspace. Defina o contexto e as instruções para que a IA gere uma resposta. Você deve fornecer um prompt cuidadosamente elaborado para que a IA possa gerar uma resposta relevante e precisa.", + }, + refusal: { + title: "Resposta de Recusa no Modo de Consulta", + "desc-start": "Quando estiver no modo", + query: "consulta", + "desc-end": + ", você pode querer retornar uma resposta de recusa personalizada quando nenhum contexto for encontrado.", + }, + temperature: { + title: "Temperatura do LLM", + "desc-start": + 'Esta configuração controla o quão "criativas" serão as respostas do seu LLM.', + "desc-end": + "Quanto maior o número, mais criativa será a resposta. Para alguns modelos, isso pode levar a respostas incoerentes quando configurado muito alto.", + hint: "A maioria dos LLMs tem vários intervalos aceitáveis de valores válidos. Consulte seu provedor de LLM para essa informação.", + }, + }, + + // Vector Database + "vector-workspace": { + identifier: "Identificador do Banco de Dados Vetorial", + snippets: { + title: "Máximo de Trechos de Contexto", + description: + "Esta configuração controla a quantidade máxima de trechos de contexto que será enviada ao LLM por chat ou consulta.", + recommend: "Recomendado: 4", + }, + doc: { + title: "Limite de Similaridade de Documentos", + description: + "A pontuação mínima de similaridade necessária para que uma fonte seja considerada relacionada ao chat. Quanto maior o número, mais semelhante a fonte deve ser ao chat.", + zero: "Sem restrição", + low: "Baixo (pontuação de similaridade ≥ 0,25)", + medium: "Médio (pontuação de similaridade ≥ 0,50)", + high: "Alto (pontuação de similaridade ≥ 0,75)", + }, + reset: { + reset: "Redefinir Banco de Dados Vetorial", + resetting: "Limpando vetores...", + confirm: + "Você está prestes a redefinir o banco de dados vetorial deste workspace. Isso removerá todas as incorporações vetoriais atualmente embutidas.\n\nOs arquivos de origem originais permanecerão intactos. Esta ação é irreversível.", + error: "O banco de dados vetorial do workspace não pôde ser redefinido!", + success: + "O banco de dados vetorial do workspace foi redefinido com sucesso!", + }, + }, + + // Agent Configuration + agent: { + "performance-warning": + "O desempenho dos LLMs que não suportam explicitamente a chamada de ferramentas depende muito das capacidades e da precisão do modelo. Algumas habilidades podem ser limitadas ou não funcionais.", + provider: { + title: "Provedor de LLM do Agente do Workspace", + description: + "O provedor e modelo específico de LLM que será usado para o agente @agent deste workspace.", + }, + mode: { + chat: { + title: "Modelo de Chat do Agente do Workspace", + description: + "O modelo de chat específico que será usado para o agente @agent deste workspace.", + }, + title: "Modelo do Agente do Workspace", + description: + "O modelo de LLM específico que será usado para o agente @agent deste workspace.", + wait: "-- aguardando modelos --", + }, + + skill: { + title: "Habilidades padrão do agente", + description: + "Melhore as habilidades naturais do agente padrão com essas habilidades pré-construídas. Esta configuração se aplica a todos os workspaces.", + rag: { + title: "RAG e memória de longo prazo", + description: + 'Permitir que o agente utilize seus documentos locais para responder a uma consulta ou pedir ao agente para "lembrar" peças de conteúdo para recuperação de memória de longo prazo.', + }, + view: { + title: "Visualizar e resumir documentos", + description: + "Permitir que o agente liste e resuma o conteúdo dos arquivos do workspace atualmente incorporados.", + }, + scrape: { + title: "Raspagem de sites", + description: + "Permitir que o agente visite e raspe o conteúdo de sites.", + }, + generate: { + title: "Gerar gráficos", + description: + "Habilitar o agente padrão para gerar vários tipos de gráficos a partir dos dados fornecidos ou dados no chat.", + }, + save: { + title: "Gerar e salvar arquivos no navegador", + description: + "Habilitar o agente padrão para gerar e gravar arquivos que podem ser salvos e baixados no seu navegador.", + }, + web: { + title: "Pesquisa e navegação na web ao vivo", + "desc-start": + "Permitir que seu agente pesquise na web para responder suas perguntas conectando-se a um provedor de pesquisa na web (SERP).", + "desc-end": + "A pesquisa na web durante as sessões do agente não funcionará até que isso seja configurado.", + }, + }, + }, + + // Workspace Chats + recorded: { + title: "Chats do Workspace", + description: + "Estes são todos os chats e mensagens gravados que foram enviados pelos usuários ordenados por data de criação.", + export: "Exportar", + table: { + id: "Id", + by: "Enviado Por", + workspace: "Workspace", + prompt: "Prompt", + response: "Resposta", + at: "Enviado Em", + }, + }, + + // Appearance + appearance: { + title: "Aparência", + description: "Personalize as configurações de aparência da sua plataforma.", + logo: { + title: "Personalizar Logo", + description: + "Envie seu logotipo personalizado para tornar seu chatbot seu.", + add: "Adicionar um logotipo personalizado", + recommended: "Tamanho recomendado: 800 x 200", + remove: "Remover", + replace: "Substituir", + }, + message: { + title: "Personalizar Mensagens", + description: + "Personalize as mensagens automáticas exibidas aos seus usuários.", + new: "Novo", + system: "sistema", + user: "usuário", + message: "mensagem", + assistant: "Assistente de Chat AnythingLLM", + "double-click": "Clique duas vezes para editar...", + save: "Salvar Mensagens", + }, + icons: { + title: "Ícones de Rodapé Personalizados", + description: + "Personalize os ícones de rodapé exibidos na parte inferior da barra lateral.", + icon: "Ícone", + link: "Link", + }, + }, + + // API Keys + api: { + title: "Chaves API", + description: + "As chaves API permitem que o titular acesse e gerencie programaticamente esta instância do AnythingLLM.", + link: "Leia a documentação da API", + generate: "Gerar Nova Chave API", + table: { + key: "Chave API", + by: "Criado Por", + created: "Criado", + }, + }, + + llm: { + title: "Preferência de LLM", + description: + "Estas são as credenciais e configurações para seu provedor preferido de chat e incorporação de LLM. É importante que essas chaves estejam atualizadas e corretas, caso contrário, o AnythingLLM não funcionará corretamente.", + provider: "Provedor de LLM", + }, + + transcription: { + title: "Preferência de Modelo de Transcrição", + description: + "Estas são as credenciais e configurações para seu provedor preferido de modelo de transcrição. É importante que essas chaves estejam atualizadas e corretas, caso contrário, os arquivos de mídia e áudio não serão transcritos.", + provider: "Provedor de Transcrição", + "warn-start": + "Usar o modelo whisper local em máquinas com RAM ou CPU limitados pode travar o AnythingLLM ao processar arquivos de mídia.", + "warn-recommend": + "Recomendamos pelo menos 2GB de RAM e upload de arquivos <10Mb.", + "warn-end": + "O modelo embutido será baixado automaticamente no primeiro uso.", + }, + + embedding: { + title: "Preferência de Incorporação", + "desc-start": + "Ao usar um LLM que não suporta nativamente um mecanismo de incorporação - pode ser necessário especificar adicionalmente as credenciais para incorporação de texto.", + "desc-end": + "A incorporação é o processo de transformar texto em vetores. Essas credenciais são necessárias para transformar seus arquivos e prompts em um formato que o AnythingLLM possa usar para processar.", + provider: { + title: "Provedor de Incorporação", + description: + "Não é necessária configuração ao usar o mecanismo de incorporação nativo do AnythingLLM.", + }, + }, + + text: { + title: "Preferências de Divisão e Fragmentação de Texto", + "desc-start": + "Às vezes, você pode querer alterar a maneira padrão como novos documentos são divididos e fragmentados antes de serem inseridos em seu banco de dados de vetores.", + "desc-end": + "Você só deve modificar esta configuração se entender como a divisão de texto funciona e seus efeitos colaterais.", + "warn-start": "As alterações aqui se aplicarão apenas a", + "warn-center": "documentos recém-incorporados", + "warn-end": ", não documentos existentes.", + size: { + title: "Tamanho do Fragmento de Texto", + description: + "Este é o comprimento máximo de caracteres que pode estar presente em um único vetor.", + recommend: "O comprimento máximo do modelo de incorporação é", + }, + + overlap: { + title: "Sobreposição de Fragmento de Texto", + description: + "Esta é a sobreposição máxima de caracteres que ocorre durante a fragmentação entre dois fragmentos de texto adjacentes.", + }, + }, + + // Vector Database + vector: { + title: "Banco de Dados Vetorial", + description: + "Estas são as credenciais e configurações de como sua instância do AnythingLLM funcionará. É importante que essas chaves estejam atualizadas e corretas.", + provider: { + title: "Provedor de Banco de Dados Vetorial", + description: "Não há configuração necessária para o LanceDB.", + }, + }, + + // Embeddable Chat Widgets + embeddable: { + title: "Widgets de Chat Incorporáveis", + description: + "Os widgets de chat incorporáveis são interfaces de chat públicas vinculadas a um único workspace. Eles permitem que você construa workspaces que você pode publicar para o mundo.", + create: "Criar incorporação", + table: { + workspace: "Workspace", + chats: "Chats Enviados", + Active: "Domínios Ativos", + }, + }, + + "embed-chats": { + title: "Incorporar Chats", + description: + "Estes são todos os chats e mensagens registrados de qualquer incorporação que você publicou.", + table: { + embed: "Incorporação", + sender: "Remetente", + message: "Mensagem", + response: "Resposta", + at: "Enviado Em", + }, + }, + + multi: { + title: "Modo Multiusuário", + description: + "Configure sua instância para suportar sua equipe ativando o Modo Multiusuário.", + enable: { + "is-enable": "Modo Multiusuário está Ativado", + enable: "Ativar Modo Multiusuário", + description: + "Por padrão, você será o único administrador. Como administrador, você precisará criar contas para todos os novos usuários ou administradores. Não perca sua senha, pois apenas um usuário Administrador pode redefinir senhas.", + username: "Nome de usuário da conta de Administrador", + password: "Senha da conta de Administrador", + }, + password: { + title: "Proteção por Senha", + description: + "Proteja sua instância do AnythingLLM com uma senha. Se você esquecer esta senha, não há método de recuperação, então certifique-se de salvar esta senha.", + }, + instance: { + title: "Proteger Instância com Senha", + description: + "Por padrão, você será o único administrador. Como administrador, você precisará criar contas para todos os novos usuários ou administradores. Não perca sua senha, pois apenas um usuário Administrador pode redefinir senhas.", + password: "Senha da instância", + }, + }, + + // Event Logs + event: { + title: "Logs de Eventos", + description: + "Veja todas as ações e eventos acontecendo nesta instância para monitoramento.", + clear: "Limpar Logs de Eventos", + table: { + type: "Tipo de Evento", + user: "Usuário", + occurred: "Ocorreu Em", + }, + }, + + // Privacy & Data-Handling + privacy: { + title: "Privacidade e Tratamento de Dados", + description: + "Esta é a sua configuração de como os provedores de terceiros conectados e o AnythingLLM tratam seus dados.", + llm: "Seleção de LLM", + embedding: "Preferência de Incorporação", + vector: "Banco de Dados Vetorial", + anonymous: "Telemetria Anônima Ativada", + }, +}; + +export default TRANSLATIONS; diff --git a/frontend/src/locales/resources.js b/frontend/src/locales/resources.js index 5c729f1b2..9725e7c3d 100644 --- a/frontend/src/locales/resources.js +++ b/frontend/src/locales/resources.js @@ -21,6 +21,7 @@ import French from "./fr/common.js"; import Mandarin from "./zh/common.js"; import Russian from "./ru/common.js"; import Italian from "./it/common.js"; +import Portuguese from "./pt_BR/common.js"; export const defaultNS = "common"; export const resources = { @@ -45,4 +46,7 @@ export const resources = { it: { common: Italian, }, + pt: { + common: Portuguese, + }, }; From be3b0b491676da816f38daa007060837eab08333 Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Tue, 6 Aug 2024 10:16:17 -0700 Subject: [PATCH 05/24] Youtube loader whitespace fix (#2051) youtube loader whitespace fix --- .../YoutubeTranscript/YoutubeLoader/youtube-transcript.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/collector/utils/extensions/YoutubeTranscript/YoutubeLoader/youtube-transcript.js b/collector/utils/extensions/YoutubeTranscript/YoutubeLoader/youtube-transcript.js index c81c0ec56..f868875b2 100644 --- a/collector/utils/extensions/YoutubeTranscript/YoutubeLoader/youtube-transcript.js +++ b/collector/utils/extensions/YoutubeTranscript/YoutubeLoader/youtube-transcript.js @@ -47,10 +47,12 @@ class YoutubeTranscript { let transcript = ""; const chunks = transcriptXML.getElementsByTagName("text"); for (const chunk of chunks) { - transcript += chunk.textContent; + // Add space after each text chunk + transcript += chunk.textContent + " "; } - return transcript; + // Trim extra whitespace + return transcript.trim().replace(/\s+/g, " "); } catch (e) { throw new YoutubeTranscriptError(e); } From 0d4560b9e47a407f4033e311f0ef6705d54f7e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20=C3=9Cnl=C3=BC?= Date: Tue, 6 Aug 2024 19:17:55 +0200 Subject: [PATCH 06/24] 2049 remove break that prevents fetching files from gitlab repo (#2050) fix: remove unnecessary break Remove unnecessary break that prevents checking next pages for blob objects. --- .../extensions/RepoLoader/GitlabRepo/RepoLoader/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/collector/utils/extensions/RepoLoader/GitlabRepo/RepoLoader/index.js b/collector/utils/extensions/RepoLoader/GitlabRepo/RepoLoader/index.js index c90932986..7d5c8438c 100644 --- a/collector/utils/extensions/RepoLoader/GitlabRepo/RepoLoader/index.js +++ b/collector/utils/extensions/RepoLoader/GitlabRepo/RepoLoader/index.js @@ -223,10 +223,6 @@ class GitLabRepoLoader { const objects = Array.isArray(data) ? data.filter((item) => item.type === "blob") : []; // only get files, not paths or submodules - if (objects.length === 0) { - fetching = false; - break; - } // Apply ignore path rules to found objects. If any rules match it is an invalid file path. console.log( From 6666fff0c23e3c75156cb342c7a802b000901e99 Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Tue, 6 Aug 2024 10:27:36 -0700 Subject: [PATCH 07/24] Support multiple preset prompts in single message (#2036) support multiple preset prompts in single message --- server/utils/chats/index.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/server/utils/chats/index.js b/server/utils/chats/index.js index 2068d3511..dd0f6076f 100644 --- a/server/utils/chats/index.js +++ b/server/utils/chats/index.js @@ -14,17 +14,6 @@ async function grepCommand(message, user = null) { const userPresets = await SlashCommandPresets.getUserPresets(user?.id); const availableCommands = Object.keys(VALID_COMMANDS); - // Check if the message starts with any preset command - const foundPreset = userPresets.find((p) => message.startsWith(p.command)); - if (!!foundPreset) { - // Replace the preset command with the corresponding prompt - const updatedMessage = message.replace( - foundPreset.command, - foundPreset.prompt - ); - return updatedMessage; - } - // Check if the message starts with any built-in command for (let i = 0; i < availableCommands.length; i++) { const cmd = availableCommands[i]; @@ -34,7 +23,15 @@ async function grepCommand(message, user = null) { } } - return message; + // Replace all preset commands with their corresponding prompts + // Allows multiple commands in one message + let updatedMessage = message; + for (const preset of userPresets) { + const regex = new RegExp(preset.command, "g"); + updatedMessage = updatedMessage.replace(regex, preset.prompt); + } + + return updatedMessage; } async function chatWithWorkspace( From 0141f91dda8d114214e195a949af54150c7ff3a0 Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Wed, 7 Aug 2024 08:39:07 -0700 Subject: [PATCH 08/24] Hide upload element for default role users (#2061) resolves #2060 --- .../ChatContainer/PromptInput/AttachItem/index.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/AttachItem/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/AttachItem/index.jsx index 74f22f90c..bebbfe570 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/AttachItem/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/AttachItem/index.jsx @@ -1,3 +1,4 @@ +import useUser from "@/hooks/useUser"; import { PaperclipHorizontal } from "@phosphor-icons/react"; import { Tooltip } from "react-tooltip"; @@ -6,6 +7,9 @@ import { Tooltip } from "react-tooltip"; * @returns */ export default function AttachItem() { + const { user } = useUser(); + if (!!user && user.role === "default") return null; + return ( <> + )} + + ); +} + +function DemoVoiceSample({ voiceId }) { + const playerRef = useRef(null); + const [speaking, setSpeaking] = useState(false); + const [loading, setLoading] = useState(false); + const [audioSrc, setAudioSrc] = useState(null); + + async function speakMessage(e) { + e.preventDefault(); + if (speaking) { + playerRef?.current?.pause(); + return; + } + + try { + if (!audioSrc) { + setLoading(true); + const client = new PiperTTSClient({ voiceId }); + const blobUrl = await client.getAudioBlobForText( + "Hello, welcome to AnythingLLM!" + ); + setAudioSrc(blobUrl); + setLoading(false); + client.worker?.terminate(); + PiperTTSClient._instance = null; + } else { + playerRef.current.play(); + } + } catch (e) { + console.error(e); + setLoading(false); + setSpeaking(false); + } + } + + useEffect(() => { + function setupPlayer() { + if (!playerRef?.current) return; + playerRef.current.addEventListener("play", () => { + setSpeaking(true); + }); + + playerRef.current.addEventListener("pause", () => { + playerRef.current.currentTime = 0; + setSpeaking(false); + setAudioSrc(null); + }); + } + setupPlayer(); + }, []); + + return ( + + ); +} diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/index.jsx index 56d32e847..88d063387 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/index.jsx @@ -1,9 +1,11 @@ import { useEffect, useState } from "react"; import NativeTTSMessage from "./native"; import AsyncTTSMessage from "./asyncTts"; +import PiperTTSMessage from "./piperTTS"; import System from "@/models/system"; export default function TTSMessage({ slug, chatId, message }) { + const [settings, setSettings] = useState({}); const [provider, setProvider] = useState("native"); const [loading, setLoading] = useState(true); @@ -11,13 +13,26 @@ export default function TTSMessage({ slug, chatId, message }) { async function getSettings() { const _settings = await System.keys(); setProvider(_settings?.TextToSpeechProvider ?? "native"); + setSettings(_settings); setLoading(false); } getSettings(); }, []); if (!chatId || loading) return null; - if (provider !== "native") - return ; - return ; + + switch (provider) { + case "openai": + case "elevenlabs": + return ; + case "piper_local": + return ( + + ); + default: + return ; + } } diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/piperTTS.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/piperTTS.jsx new file mode 100644 index 000000000..d384faf1e --- /dev/null +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/piperTTS.jsx @@ -0,0 +1,90 @@ +import { useEffect, useState, useRef } from "react"; +import { SpeakerHigh, PauseCircle, CircleNotch } from "@phosphor-icons/react"; +import { Tooltip } from "react-tooltip"; +import PiperTTSClient from "@/utils/piperTTS"; + +export default function PiperTTS({ voiceId = null, message }) { + const playerRef = useRef(null); + const [speaking, setSpeaking] = useState(false); + const [loading, setLoading] = useState(false); + const [audioSrc, setAudioSrc] = useState(null); + + async function speakMessage(e) { + e.preventDefault(); + if (speaking) { + playerRef?.current?.pause(); + return; + } + + try { + if (!audioSrc) { + setLoading(true); + const client = new PiperTTSClient({ voiceId }); + const blobUrl = await client.getAudioBlobForText(message); + setAudioSrc(blobUrl); + setLoading(false); + } else { + playerRef.current.play(); + } + } catch (e) { + console.error(e); + setLoading(false); + setSpeaking(false); + } + } + + useEffect(() => { + function setupPlayer() { + if (!playerRef?.current) return; + playerRef.current.addEventListener("play", () => { + setSpeaking(true); + }); + + playerRef.current.addEventListener("pause", () => { + playerRef.current.currentTime = 0; + setSpeaking(false); + }); + } + setupPlayer(); + }, []); + + return ( +
+ + +
+ ); +} diff --git a/frontend/src/media/ttsproviders/piper.png b/frontend/src/media/ttsproviders/piper.png new file mode 100644 index 0000000000000000000000000000000000000000..32d3ec5a7b25ce21f82cc8a6b4259114228d1b94 GIT binary patch literal 11283 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 LDHmO`y7|8Vg9saI literal 0 HcmV?d00001 diff --git a/frontend/src/pages/GeneralSettings/AudioPreference/tts.jsx b/frontend/src/pages/GeneralSettings/AudioPreference/tts.jsx index dee8a8444..0ebab72de 100644 --- a/frontend/src/pages/GeneralSettings/AudioPreference/tts.jsx +++ b/frontend/src/pages/GeneralSettings/AudioPreference/tts.jsx @@ -7,9 +7,11 @@ import CTAButton from "@/components/lib/CTAButton"; import OpenAiLogo from "@/media/llmprovider/openai.png"; import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png"; import ElevenLabsIcon from "@/media/ttsproviders/elevenlabs.png"; +import PiperTTSIcon from "@/media/ttsproviders/piper.png"; import BrowserNative from "@/components/TextToSpeech/BrowserNative"; import OpenAiTTSOptions from "@/components/TextToSpeech/OpenAiOptions"; import ElevenLabsTTSOptions from "@/components/TextToSpeech/ElevenLabsOptions"; +import PiperTTSOptions from "@/components/TextToSpeech/PiperTTSOptions"; const PROVIDERS = [ { @@ -33,6 +35,13 @@ const PROVIDERS = [ options: (settings) => , description: "Use ElevenLabs's text to speech voices and technology.", }, + { + name: "PiperTTS", + value: "piper_local", + logo: PiperTTSIcon, + options: (settings) => , + description: "Run TTS models locally in your browser privately.", + }, ]; export default function TextToSpeechProvider({ settings }) { diff --git a/frontend/src/utils/piperTTS/index.js b/frontend/src/utils/piperTTS/index.js new file mode 100644 index 000000000..5016af79e --- /dev/null +++ b/frontend/src/utils/piperTTS/index.js @@ -0,0 +1,138 @@ +import showToast from "../toast"; + +export default class PiperTTSClient { + static _instance; + voiceId = "en_US-hfc_female-medium"; + worker = null; + + constructor({ voiceId } = { voiceId: null }) { + if (PiperTTSClient._instance) { + this.voiceId = voiceId !== null ? voiceId : this.voiceId; + return PiperTTSClient._instance; + } + + this.voiceId = voiceId !== null ? voiceId : this.voiceId; + PiperTTSClient._instance = this; + return this; + } + + #getWorker() { + if (!this.worker) + this.worker = new Worker(new URL("./worker.js", import.meta.url), { + type: "module", + }); + return this.worker; + } + + /** + * Get all available voices for a client + * @returns {Promise} + */ + static async voices() { + const tmpWorker = new Worker(new URL("./worker.js", import.meta.url), { + type: "module", + }); + tmpWorker.postMessage({ type: "voices" }); + return new Promise((resolve, reject) => { + let timeout = null; + const handleMessage = (event) => { + if (event.data.type !== "voices") { + console.log("PiperTTSWorker debug event:", event.data); + return; + } + resolve(event.data.voices); + tmpWorker.removeEventListener("message", handleMessage); + timeout && clearTimeout(timeout); + tmpWorker.terminate(); + }; + + timeout = setTimeout(() => { + reject("TTS Worker timed out."); + }, 30_000); + tmpWorker.addEventListener("message", handleMessage); + }); + } + + static async flush() { + const tmpWorker = new Worker(new URL("./worker.js", import.meta.url), { + type: "module", + }); + tmpWorker.postMessage({ type: "flush" }); + return new Promise((resolve, reject) => { + let timeout = null; + const handleMessage = (event) => { + if (event.data.type !== "flush") { + console.log("PiperTTSWorker debug event:", event.data); + return; + } + resolve(event.data.flushed); + tmpWorker.removeEventListener("message", handleMessage); + timeout && clearTimeout(timeout); + tmpWorker.terminate(); + }; + + timeout = setTimeout(() => { + reject("TTS Worker timed out."); + }, 30_000); + tmpWorker.addEventListener("message", handleMessage); + }); + } + + /** + * Runs prediction via webworker so we can get an audio blob back. + * @returns {Promise<{blobURL: string|null, error: string|null}>} objectURL blob: type. + */ + async waitForBlobResponse() { + return new Promise((resolve) => { + let timeout = null; + const handleMessage = (event) => { + if (event.data.type === "error") { + this.worker.removeEventListener("message", handleMessage); + timeout && clearTimeout(timeout); + return resolve({ blobURL: null, error: event.data.message }); + } + + if (event.data.type !== "result") { + console.log("PiperTTSWorker debug event:", event.data); + return; + } + resolve({ + blobURL: URL.createObjectURL(event.data.audio), + error: null, + }); + this.worker.removeEventListener("message", handleMessage); + timeout && clearTimeout(timeout); + }; + + timeout = setTimeout(() => { + resolve({ blobURL: null, error: "PiperTTSWorker Worker timed out." }); + }, 30_000); + this.worker.addEventListener("message", handleMessage); + }); + } + + async getAudioBlobForText(textToSpeak, voiceId = null) { + const primaryWorker = this.#getWorker(); + primaryWorker.postMessage({ + type: "init", + text: String(textToSpeak), + voiceId: voiceId ?? this.voiceId, + // Don't reference WASM because in the docker image + // the user will be connected to internet (mostly) + // and it bloats the app size on the frontend or app significantly + // and running the docker image fully offline is not an intended use-case unlike the app. + }); + + const { blobURL, error } = await this.waitForBlobResponse(); + if (!!error) { + showToast( + `Could not generate voice prediction. Error: ${error}`, + "error", + { clear: true } + ); + return; + } + + return blobURL; + } +} diff --git a/frontend/src/utils/piperTTS/worker.js b/frontend/src/utils/piperTTS/worker.js new file mode 100644 index 000000000..e0fa8aabb --- /dev/null +++ b/frontend/src/utils/piperTTS/worker.js @@ -0,0 +1,94 @@ +import * as TTS from "@mintplex-labs/piper-tts-web"; + +/** @type {import("@mintplexlabs/piper-web-tts").TtsSession | null} */ +let PIPER_SESSION = null; + +/** + * @typedef PredictionRequest + * @property {('init')} type + * @property {string} text - the text to inference on + * @property {import('@mintplexlabs/piper-web-tts').VoiceId} voiceId - the voiceID key to use. + * @property {string|null} baseUrl - the base URL to fetch WASMs from. + */ +/** + * @typedef PredictionRequestResponse + * @property {('result')} type + * @property {Blob} audio - the text to inference on + */ + +/** + * @typedef VoicesRequest + * @property {('voices')} type + * @property {string|null} baseUrl - the base URL to fetch WASMs from. + */ +/** + * @typedef VoicesRequestResponse + * @property {('voices')} type + * @property {[import("@mintplex-labs/piper-tts-web/dist/types")['Voice']]} voices - available voices in array + */ + +/** + * @typedef FlushRequest + * @property {('flush')} type + */ +/** + * @typedef FlushRequestResponse + * @property {('flush')} type + * @property {true} flushed + */ + +/** + * Web worker for generating client-side PiperTTS predictions + * @param {MessageEvent} event - The event object containing the prediction request + * @returns {Promise} + */ +async function main(event) { + if (event.data.type === "voices") { + const stored = await TTS.stored(); + const voices = await TTS.voices(); + voices.forEach((voice) => (voice.is_stored = stored.includes(voice.key))); + + self.postMessage({ type: "voices", voices }); + return; + } + + if (event.data.type === "flush") { + await TTS.flush(); + self.postMessage({ type: "flush", flushed: true }); + return; + } + + if (event.data?.type !== "init") return; + if (!PIPER_SESSION) { + PIPER_SESSION = new TTS.TtsSession({ + voiceId: event.data.voiceId, + progress: (e) => self.postMessage(JSON.stringify(e)), + logger: (msg) => self.postMessage(msg), + ...(!!event.data.baseUrl + ? { + wasmPaths: { + onnxWasm: `${event.data.baseUrl}/piper/ort/`, + piperData: `${event.data.baseUrl}/piper/piper_phonemize.data`, + piperWasm: `${event.data.baseUrl}/piper/piper_phonemize.wasm`, + }, + } + : {}), + }); + } + + if (event.data.voiceId && PIPER_SESSION.voiceId !== event.data.voiceId) + PIPER_SESSION.voiceId = event.data.voiceId; + + PIPER_SESSION.predict(event.data.text) + .then((res) => { + if (res instanceof Blob) { + self.postMessage({ type: "result", audio: res }); + return; + } + }) + .catch((error) => { + self.postMessage({ type: "error", message: error.message, error }); // Will be an error. + }); +} + +self.addEventListener("message", main); diff --git a/frontend/vite.config.js b/frontend/vite.config.js index b67e9ef7c..73b295be2 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -9,6 +9,14 @@ dns.setDefaultResultOrder("verbatim") // https://vitejs.dev/config/ export default defineConfig({ + assetsInclude: [ + './public/piper/ort-wasm-simd-threaded.wasm', + './public/piper/piper_phonemize.wasm', + './public/piper/piper_phonemize.data', + ], + worker: { + format: 'es' + }, server: { port: 3000, host: "localhost" @@ -60,7 +68,7 @@ export default defineConfig({ }, external: [ // Reduces transformation time by 50% and we don't even use this variant, so we can ignore. - /@phosphor-icons\/react\/dist\/ssr/ + /@phosphor-icons\/react\/dist\/ssr/, ] }, commonjsOptions: { @@ -68,6 +76,7 @@ export default defineConfig({ } }, optimizeDeps: { + include: ["@mintplex-labs/piper-tts-web"], esbuildOptions: { define: { global: "globalThis" diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0f62957b1..4a56e4f92 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -496,6 +496,11 @@ resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d" integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA== +"@mintplex-labs/piper-tts-web@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@mintplex-labs/piper-tts-web/-/piper-tts-web-1.0.4.tgz#016b196fa86dc8b616691dd381f3ca1939196444" + integrity sha512-Y24X+CJaGXoY5HFPSstHvJI6408OAtw3Pmq2OIYwpRpcwLLbgadWg8l1ODHNkgpB0Ps5fS9PAAQB60fHA3Bdag== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -532,6 +537,59 @@ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@remix-run/router@1.18.0": version "1.18.0" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.18.0.tgz#20b033d1f542a100c1d57cfd18ecf442d1784732" @@ -652,6 +710,13 @@ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== +"@types/node@>=13.7.0": + version "22.1.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.1.0.tgz#6d6adc648b5e03f0e83c78dc788c2b037d0ad94b" + integrity sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw== + dependencies: + undici-types "~6.13.0" + "@types/prop-types@*": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" @@ -1729,6 +1794,11 @@ flat-cache@^3.0.4: keyv "^4.5.3" rimraf "^3.0.2" +flatbuffers@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-1.12.0.tgz#72e87d1726cb1b216e839ef02658aa87dcef68aa" + integrity sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ== + flatted@^3.2.9: version "3.3.1" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" @@ -1898,6 +1968,11 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +guid-typescript@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/guid-typescript/-/guid-typescript-1.0.9.tgz#e35f77003535b0297ea08548f5ace6adb1480ddc" + integrity sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ== + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -2413,6 +2488,11 @@ lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +long@^5.0.0, long@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -2611,6 +2691,23 @@ once@^1.3.0: dependencies: wrappy "1" +onnxruntime-common@1.18.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/onnxruntime-common/-/onnxruntime-common-1.18.0.tgz#b904dc6ff134e7f21a3eab702fac17538f59e116" + integrity sha512-lufrSzX6QdKrktAELG5x5VkBpapbCeS3dQwrXbN0eD9rHvU0yAWl7Ztju9FvgAKWvwd/teEKJNj3OwM6eTZh3Q== + +onnxruntime-web@^1.18.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/onnxruntime-web/-/onnxruntime-web-1.18.0.tgz#cd46268d9472f89697da0a3282f13129f0acbfa0" + integrity sha512-o1UKj4ABIj1gmG7ae0RKJ3/GT+3yoF0RRpfDfeoe0huzRW4FDRLfbkDETmdFAvnJEXuYDE0YT+hhkia0352StQ== + dependencies: + flatbuffers "^1.12.0" + guid-typescript "^1.0.9" + long "^5.2.3" + onnxruntime-common "1.18.0" + platform "^1.3.6" + protobufjs "^7.2.4" + open@^8.4.0: version "8.4.2" resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" @@ -2713,6 +2810,11 @@ pirates@^4.0.1: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== +platform@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" + integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== + pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" @@ -2802,6 +2904,24 @@ prop-types@^15.6.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +protobufjs@^7.2.4: + version "7.3.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.2.tgz#60f3b7624968868f6f739430cfbc8c9370e26df4" + integrity sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -3612,6 +3732,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.13.0.tgz#e3e79220ab8c81ed1496b5812471afd7cf075ea5" + integrity sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg== + update-browserslist-db@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" diff --git a/server/endpoints/api/openai/index.js b/server/endpoints/api/openai/index.js index 309575115..cd732f424 100644 --- a/server/endpoints/api/openai/index.js +++ b/server/endpoints/api/openai/index.js @@ -154,6 +154,7 @@ function apiOpenAICompatibleEndpoints(app) { workspace.chatProvider ?? process.env.LLM_PROVIDER ?? "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent("api_sent_chat", { workspaceName: workspace?.name, @@ -180,6 +181,7 @@ function apiOpenAICompatibleEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent("api_sent_chat", { workspaceName: workspace?.name, diff --git a/server/endpoints/api/workspace/index.js b/server/endpoints/api/workspace/index.js index 719b73baf..3d4e90fb4 100644 --- a/server/endpoints/api/workspace/index.js +++ b/server/endpoints/api/workspace/index.js @@ -73,6 +73,7 @@ function apiWorkspaceEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent("api_workspace_created", { workspaceName: workspace?.name || "Unknown Workspace", @@ -622,6 +623,7 @@ function apiWorkspaceEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent("api_sent_chat", { workspaceName: workspace?.name, @@ -745,6 +747,7 @@ function apiWorkspaceEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent("api_sent_chat", { workspaceName: workspace?.name, diff --git a/server/endpoints/api/workspaceThread/index.js b/server/endpoints/api/workspaceThread/index.js index a636a85d2..e2c6af1c7 100644 --- a/server/endpoints/api/workspaceThread/index.js +++ b/server/endpoints/api/workspaceThread/index.js @@ -90,6 +90,7 @@ function apiWorkspaceThreadEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent("api_workspace_thread_created", { workspaceName: workspace?.name || "Unknown Workspace", @@ -416,6 +417,7 @@ function apiWorkspaceThreadEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent("api_sent_chat", { workspaceName: workspace?.name, @@ -567,6 +569,7 @@ function apiWorkspaceThreadEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent("api_sent_chat", { workspaceName: workspace?.name, diff --git a/server/endpoints/chat.js b/server/endpoints/chat.js index 787aba574..64beefeb6 100644 --- a/server/endpoints/chat.js +++ b/server/endpoints/chat.js @@ -98,6 +98,7 @@ function chatEndpoints(app) { Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", multiModal: Array.isArray(attachments) && attachments?.length !== 0, + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent( @@ -226,6 +227,7 @@ function chatEndpoints(app) { Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", multiModal: Array.isArray(attachments) && attachments?.length !== 0, + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent( diff --git a/server/endpoints/workspaceThreads.js b/server/endpoints/workspaceThreads.js index 42e502278..4e071992b 100644 --- a/server/endpoints/workspaceThreads.js +++ b/server/endpoints/workspaceThreads.js @@ -40,6 +40,7 @@ function workspaceThreadEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }, user?.id ); diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js index 4f523aaaf..43b093679 100644 --- a/server/endpoints/workspaces.js +++ b/server/endpoints/workspaces.js @@ -55,6 +55,7 @@ function workspaceEndpoints(app) { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }, user?.id ); diff --git a/server/models/documents.js b/server/models/documents.js index 80d4fd850..43ec5f9f4 100644 --- a/server/models/documents.js +++ b/server/models/documents.js @@ -142,6 +142,7 @@ const Document = { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent( "workspace_documents_added", @@ -185,6 +186,7 @@ const Document = { LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "lancedb", + TTSSelection: process.env.TTS_PROVIDER || "native", }); await EventLogs.logEvent( "workspace_documents_removed", diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 216f63ad5..b85f3cb8c 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -209,6 +209,9 @@ const SystemSettings = { // Eleven Labs TTS TTSElevenLabsKey: !!process.env.TTS_ELEVEN_LABS_KEY, TTSElevenLabsVoiceModel: process.env.TTS_ELEVEN_LABS_VOICE_MODEL, + // Piper TTS + TTSPiperTTSVoiceModel: + process.env.TTS_PIPER_VOICE_MODEL ?? "en_US-hfc_female-medium", // -------------------------------------------------------- // Agent Settings & Configs diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 85981994d..c579da188 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -477,6 +477,12 @@ const KEY_MAPPING = { envKey: "TTS_ELEVEN_LABS_VOICE_MODEL", checks: [], }, + + // PiperTTS Local + TTSPiperTTSVoiceModel: { + envKey: "TTS_PIPER_VOICE_MODEL", + checks: [], + }, }; function isNotEmpty(input = "") { @@ -536,7 +542,12 @@ function validOllamaLLMBasePath(input = "") { } function supportedTTSProvider(input = "") { - const validSelection = ["native", "openai", "elevenlabs"].includes(input); + const validSelection = [ + "native", + "openai", + "elevenlabs", + "piper_local", + ].includes(input); return validSelection ? null : `${input} is not a valid TTS provider.`; } From c97066526af37e42e9b29cca08f821786173517f Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Wed, 7 Aug 2024 11:35:37 -0700 Subject: [PATCH 11/24] New user account validations (#2037) * force lowercase and no space for new and edit user modals * edit account modal validations * use pattern for form validation + remove validations from edit user * revert comment deletions * comment fix * update validation message * update regex allow updating by block name changes to invalid names --------- Co-authored-by: timothycarambat --- .../UserMenu/AccountModal/index.jsx | 11 +++++++++- .../pages/Admin/Users/NewUserModal/index.jsx | 20 +++++++++++++++-- .../Users/UserRow/EditUserModal/index.jsx | 12 ++++++++-- server/models/user.js | 22 +++++++++++++++---- 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/UserMenu/AccountModal/index.jsx b/frontend/src/components/UserMenu/AccountModal/index.jsx index cb39fdd59..9fac4aaeb 100644 --- a/frontend/src/components/UserMenu/AccountModal/index.jsx +++ b/frontend/src/components/UserMenu/AccountModal/index.jsx @@ -7,6 +7,7 @@ import { Plus, X } from "@phosphor-icons/react"; export default function AccountModal({ user, hideModal }) { const { pfp, setPfp } = usePfp(); + const handleFileUpload = async (event) => { const file = event.target.files[0]; if (!file) return false; @@ -133,6 +134,10 @@ export default function AccountModal({ user, hideModal }) { required autoComplete="off" /> +

+ Username must be only contain lowercase letters, numbers, + underscores, and hyphens with no spaces +

diff --git a/frontend/src/pages/Admin/Users/NewUserModal/index.jsx b/frontend/src/pages/Admin/Users/NewUserModal/index.jsx index c784228b1..3af7ebd4a 100644 --- a/frontend/src/pages/Admin/Users/NewUserModal/index.jsx +++ b/frontend/src/pages/Admin/Users/NewUserModal/index.jsx @@ -7,6 +7,7 @@ import { RoleHintDisplay } from ".."; export default function NewUserModal({ closeModal }) { const [error, setError] = useState(null); const [role, setRole] = useState("default"); + const handleCreate = async (e) => { setError(null); e.preventDefault(); @@ -54,7 +55,18 @@ export default function NewUserModal({ closeModal }) { minLength={2} required={true} autoComplete="off" + pattern="^[a-z0-9_-]+$" + onInvalid={(e) => + e.target.setCustomValidity( + "Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces" + ) + } + onChange={(e) => e.target.setCustomValidity("")} /> +

+ Username must be only contain lowercase letters, numbers, + underscores, and hyphens with no spaces +