Merge branch 'langgenius:main' into fix/Upload-folders-to-knowledgebase

pull/17026/head^2
Silow 1 year ago committed by GitHub
commit ba04849e7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -76,7 +76,6 @@ jobs:
milvus-standalone milvus-standalone
pgvecto-rs pgvecto-rs
pgvector pgvector
opengauss
chroma chroma
elasticsearch elasticsearch

@ -31,25 +31,24 @@ jobs:
uses: tj-actions/changed-files@v45 uses: tj-actions/changed-files@v45
with: with:
files: web/** files: web/**
# to run pnpm, should install package canvas, but it always install failed on amd64 under ubuntu-latest - name: Install pnpm
# - name: Install pnpm uses: pnpm/action-setup@v4
# uses: pnpm/action-setup@v4 with:
# with: version: 10
# version: 10 run_install: false
# run_install: false
- name: Setup Node.js
# - name: Setup Node.js uses: actions/setup-node@v4
# uses: actions/setup-node@v4 if: steps.changed-files.outputs.any_changed == 'true'
# if: steps.changed-files.outputs.any_changed == 'true' with:
# with: node-version: 20
# node-version: 20 cache: pnpm
# cache: pnpm cache-dependency-path: ./web/package.json
# cache-dependency-path: ./web/package.json
- name: Install dependencies
# - name: Install dependencies if: steps.changed-files.outputs.any_changed == 'true'
# if: steps.changed-files.outputs.any_changed == 'true' run: pnpm install --frozen-lockfile
# run: pnpm install --frozen-lockfile
- name: Run tests
# - name: Run tests if: steps.changed-files.outputs.any_changed == 'true'
# if: steps.changed-files.outputs.any_changed == 'true' run: pnpm test
# run: pnpm test

1
.gitignore vendored

@ -103,6 +103,7 @@ celerybeat.pid
# Environments # Environments
.env .env
.env-local
.venv .venv
env/ env/
venv/ venv/

@ -0,0 +1,93 @@
# CONTRIBUIR
Así que estás buscando contribuir a Dify - eso es fantástico, estamos ansiosos por ver lo que haces. Como una startup con personal y financiación limitados, tenemos grandes ambiciones de diseñar el flujo de trabajo más intuitivo para construir y gestionar aplicaciones LLM. Cualquier ayuda de la comunidad cuenta, realmente.
Necesitamos ser ágiles y enviar rápidamente dado donde estamos, pero también queremos asegurarnos de que colaboradores como tú obtengan una experiencia lo más fluida posible al contribuir. Hemos elaborado esta guía de contribución con ese propósito, con el objetivo de familiarizarte con la base de código y cómo trabajamos con los colaboradores, para que puedas pasar rápidamente a la parte divertida.
Esta guía, como Dify mismo, es un trabajo en constante progreso. Agradecemos mucho tu comprensión si a veces se queda atrás del proyecto real, y damos la bienvenida a cualquier comentario para que podamos mejorar.
En términos de licencia, por favor tómate un minuto para leer nuestro breve [Acuerdo de Licencia y Colaborador](./LICENSE). La comunidad también se adhiere al [código de conducta](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Antes de empezar
¿Buscas algo en lo que trabajar? Explora nuestros [buenos primeros issues](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) y elige uno para comenzar.
¿Tienes un nuevo modelo o herramienta genial para añadir? Abre un PR en nuestro [repositorio de plugins](https://github.com/langgenius/dify-plugins) y muéstranos lo que has construido.
¿Necesitas actualizar un modelo existente, herramienta o corregir algunos errores? Dirígete a nuestro [repositorio oficial de plugins](https://github.com/langgenius/dify-official-plugins) y haz tu magia.
¡Únete a la diversión, contribuye y construyamos algo increíble juntos! 💡✨
No olvides vincular un issue existente o abrir uno nuevo en la descripción del PR.
### Informes de errores
> [!IMPORTANT]
> Por favor, asegúrate de incluir la siguiente información al enviar un informe de error:
- Un título claro y descriptivo
- Una descripción detallada del error, incluyendo cualquier mensaje de error
- Pasos para reproducir el error
- Comportamiento esperado
- **Logs**, si están disponibles, para problemas del backend, esto es realmente importante, puedes encontrarlos en los logs de docker-compose
- Capturas de pantalla o videos, si es aplicable
Cómo priorizamos:
| Tipo de Issue | Prioridad |
| ------------------------------------------------------------ | --------------- |
| Errores en funciones principales (servicio en la nube, no poder iniciar sesión, aplicaciones que no funcionan, fallos de seguridad) | Crítica |
| Errores no críticos, mejoras de rendimiento | Prioridad Media |
| Correcciones menores (errores tipográficos, UI confusa pero funcional) | Prioridad Baja |
### Solicitudes de funcionalidades
> [!NOTE]
> Por favor, asegúrate de incluir la siguiente información al enviar una solicitud de funcionalidad:
- Un título claro y descriptivo
- Una descripción detallada de la funcionalidad
- Un caso de uso para la funcionalidad
- Cualquier otro contexto o capturas de pantalla sobre la solicitud de funcionalidad
Cómo priorizamos:
| Tipo de Funcionalidad | Prioridad |
| ------------------------------------------------------------ | --------------- |
| Funcionalidades de alta prioridad etiquetadas por un miembro del equipo | Prioridad Alta |
| Solicitudes populares de funcionalidades de nuestro [tablero de comentarios de la comunidad](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Prioridad Media |
| Funcionalidades no principales y mejoras menores | Prioridad Baja |
| Valiosas pero no inmediatas | Futura-Funcionalidad |
## Enviando tu PR
### Proceso de Pull Request
1. Haz un fork del repositorio
2. Antes de redactar un PR, por favor crea un issue para discutir los cambios que quieres hacer
3. Crea una nueva rama para tus cambios
4. Por favor añade pruebas para tus cambios en consecuencia
5. Asegúrate de que tu código pasa las pruebas existentes
6. Por favor vincula el issue en la descripción del PR, `fixes #<número_del_issue>`
7. ¡Fusiona tu código!
### Configuración del proyecto
#### Frontend
Para configurar el servicio frontend, por favor consulta nuestra [guía completa](https://github.com/langgenius/dify/blob/main/web/README.md) en el archivo `web/README.md`. Este documento proporciona instrucciones detalladas para ayudarte a configurar el entorno frontend correctamente.
#### Backend
Para configurar el servicio backend, por favor consulta nuestras [instrucciones detalladas](https://github.com/langgenius/dify/blob/main/api/README.md) en el archivo `api/README.md`. Este documento contiene una guía paso a paso para ayudarte a poner en marcha el backend sin problemas.
#### Otras cosas a tener en cuenta
Recomendamos revisar este documento cuidadosamente antes de proceder con la configuración, ya que contiene información esencial sobre:
- Requisitos previos y dependencias
- Pasos de instalación
- Detalles de configuración
- Consejos comunes de solución de problemas
No dudes en contactarnos si encuentras algún problema durante el proceso de configuración.
## Obteniendo Ayuda
Si alguna vez te quedas atascado o tienes una pregunta urgente mientras contribuyes, simplemente envíanos tus consultas a través del issue relacionado de GitHub, o únete a nuestro [Discord](https://discord.gg/8Tpq4AcN9c) para una charla rápida.

@ -0,0 +1,93 @@
# CONTRIBUER
Vous cherchez donc à contribuer à Dify - c'est fantastique, nous avons hâte de voir ce que vous allez faire. En tant que startup avec un personnel et un financement limités, nous avons de grandes ambitions pour concevoir le flux de travail le plus intuitif pour construire et gérer des applications LLM. Toute aide de la communauté compte, vraiment.
Nous devons être agiles et livrer rapidement compte tenu de notre position, mais nous voulons aussi nous assurer que des contributeurs comme vous obtiennent une expérience aussi fluide que possible lors de leur contribution. Nous avons élaboré ce guide de contribution dans ce but, visant à vous familiariser avec la base de code et comment nous travaillons avec les contributeurs, afin que vous puissiez rapidement passer à la partie amusante.
Ce guide, comme Dify lui-même, est un travail en constante évolution. Nous apprécions grandement votre compréhension si parfois il est en retard par rapport au projet réel, et nous accueillons tout commentaire pour nous aider à nous améliorer.
En termes de licence, veuillez prendre une minute pour lire notre bref [Accord de Licence et de Contributeur](./LICENSE). La communauté adhère également au [code de conduite](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Avant de vous lancer
Vous cherchez quelque chose à réaliser ? Parcourez nos [problèmes pour débutants](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) et choisissez-en un pour commencer !
Vous avez un nouveau modèle ou un nouvel outil à ajouter ? Ouvrez une PR dans notre [dépôt de plugins](https://github.com/langgenius/dify-plugins) et montrez-nous ce que vous avez créé.
Vous devez mettre à jour un modèle existant, un outil ou corriger des bugs ? Rendez-vous sur notre [dépôt officiel de plugins](https://github.com/langgenius/dify-official-plugins) et faites votre magie !
Rejoignez l'aventure, contribuez, et construisons ensemble quelque chose d'extraordinaire ! 💡✨
N'oubliez pas de lier un problème existant ou d'ouvrir un nouveau problème dans la description de votre PR.
### Rapports de bugs
> [!IMPORTANT]
> Veuillez vous assurer d'inclure les informations suivantes lors de la soumission d'un rapport de bug :
- Un titre clair et descriptif
- Une description détaillée du bug, y compris tous les messages d'erreur
- Les étapes pour reproduire le bug
- Comportement attendu
- **Logs**, si disponibles, pour les problèmes de backend, c'est vraiment important, vous pouvez les trouver dans les logs de docker-compose
- Captures d'écran ou vidéos, si applicable
Comment nous priorisons :
| Type de Problème | Priorité |
| ------------------------------------------------------------ | --------------- |
| Bugs dans les fonctions principales (service cloud, impossibilité de se connecter, applications qui ne fonctionnent pas, failles de sécurité) | Critique |
| Bugs non critiques, améliorations de performance | Priorité Moyenne |
| Corrections mineures (fautes de frappe, UI confuse mais fonctionnelle) | Priorité Basse |
### Demandes de fonctionnalités
> [!NOTE]
> Veuillez vous assurer d'inclure les informations suivantes lors de la soumission d'une demande de fonctionnalité :
- Un titre clair et descriptif
- Une description détaillée de la fonctionnalité
- Un cas d'utilisation pour la fonctionnalité
- Tout autre contexte ou captures d'écran concernant la demande de fonctionnalité
Comment nous priorisons :
| Type de Fonctionnalité | Priorité |
| ------------------------------------------------------------ | --------------- |
| Fonctionnalités hautement prioritaires étiquetées par un membre de l'équipe | Priorité Haute |
| Demandes populaires de fonctionnalités de notre [tableau de feedback communautaire](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Priorité Moyenne |
| Fonctionnalités non essentielles et améliorations mineures | Priorité Basse |
| Précieuses mais non immédiates | Fonctionnalité Future |
## Soumettre votre PR
### Processus de Pull Request
1. Forkez le dépôt
2. Avant de rédiger une PR, veuillez créer un problème pour discuter des changements que vous souhaitez apporter
3. Créez une nouvelle branche pour vos changements
4. Veuillez ajouter des tests pour vos changements en conséquence
5. Assurez-vous que votre code passe les tests existants
6. Veuillez lier le problème dans la description de la PR, `fixes #<numéro_du_problème>`
7. Faites fusionner votre code !
### Configuration du projet
#### Frontend
Pour configurer le service frontend, veuillez consulter notre [guide complet](https://github.com/langgenius/dify/blob/main/web/README.md) dans le fichier `web/README.md`. Ce document fournit des instructions détaillées pour vous aider à configurer correctement l'environnement frontend.
#### Backend
Pour configurer le service backend, veuillez consulter nos [instructions détaillées](https://github.com/langgenius/dify/blob/main/api/README.md) dans le fichier `api/README.md`. Ce document contient un guide étape par étape pour vous aider à faire fonctionner le backend sans problème.
#### Autres choses à noter
Nous recommandons de revoir attentivement ce document avant de procéder à la configuration, car il contient des informations essentielles sur :
- Prérequis et dépendances
- Étapes d'installation
- Détails de configuration
- Conseils courants de dépannage
N'hésitez pas à nous contacter si vous rencontrez des problèmes pendant le processus de configuration.
## Obtenir de l'aide
Si jamais vous êtes bloqué ou avez une question urgente en contribuant, envoyez-nous simplement vos questions via le problème GitHub concerné, ou rejoignez notre [Discord](https://discord.gg/8Tpq4AcN9c) pour une discussion rapide.

@ -0,0 +1,93 @@
# 기여하기
Dify에 기여하려고 하시는군요 - 정말 멋집니다, 당신이 무엇을 할지 기대가 됩니다. 인력과 자금이 제한된 스타트업으로서, 우리는 LLM 애플리케이션을 구축하고 관리하기 위한 가장 직관적인 워크플로우를 설계하고자 하는 큰 야망을 가지고 있습니다. 커뮤니티의 모든 도움은 정말 중요합니다.
우리는 현재 상황에서 민첩하게 빠르게 배포해야 하지만, 동시에 당신과 같은 기여자들이 기여하는 과정에서 최대한 원활한 경험을 얻을 수 있도록 하고 싶습니다. 우리는 이러한 목적으로 이 기여 가이드를 작성했으며, 여러분이 코드베이스와 우리가 기여자들과 어떻게 협업하는지에 대해 친숙해질 수 있도록 돕고, 빠르게 재미있는 부분으로 넘어갈 수 있도록 하고자 합니다.
이 가이드는 Dify 자체와 마찬가지로 끊임없이 진행 중인 작업입니다. 때로는 실제 프로젝트보다 뒤처질 수 있다는 점을 이해해 주시면 감사하겠으며, 개선을 위한 피드백은 언제든지 환영합니다.
라이센스 측면에서, 간략한 [라이센스 및 기여자 동의서](./LICENSE)를 읽어보는 시간을 가져주세요. 커뮤니티는 또한 [행동 강령](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)을 준수합니다.
## 시작하기 전에
처리할 작업을 찾고 계신가요? [초보자를 위한 이슈](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)를 살펴보고 시작할 것을 선택하세요!
추가할 새로운 모델 런타임이나 도구가 있나요? 우리의 [플러그인 저장소](https://github.com/langgenius/dify-plugins)에 PR을 열고 당신이 만든 것을 보여주세요.
기존 모델 런타임, 도구를 업데이트하거나 버그를 수정해야 하나요? 우리의 [공식 플러그인 저장소](https://github.com/langgenius/dify-official-plugins)로 가서 당신의 마법을 펼치세요!
함께 즐기고, 기여하고, 멋진 것을 함께 만들어 봅시다! 💡✨
PR 설명에 기존 이슈를 연결하거나 새 이슈를 여는 것을 잊지 마세요.
### 버그 보고
> [!IMPORTANT]
> 버그 보고서를 제출할 때 다음 정보를 포함해 주세요:
- 명확하고 설명적인 제목
- 오류 메시지를 포함한 버그에 대한 상세한 설명
- 버그를 재현하는 단계
- 예상되는 동작
- 가능한 경우 **로그**, 백엔드 이슈의 경우 매우 중요합니다. docker-compose 로그에서 찾을 수 있습니다
- 해당되는 경우 스크린샷 또는 비디오
우선순위 결정 방법:
| 이슈 유형 | 우선순위 |
| ------------------------------------------------------------ | --------------- |
| 핵심 기능의 버그(클라우드 서비스, 로그인 불가, 애플리케이션 작동 불능, 보안 취약점) | 중대 |
| 비중요 버그, 성능 향상 | 중간 우선순위 |
| 사소한 수정(오타, 혼란스럽지만 작동하는 UI) | 낮은 우선순위 |
### 기능 요청
> [!NOTE]
> 기능 요청을 제출할 때 다음 정보를 포함해 주세요:
- 명확하고 설명적인 제목
- 기능에 대한 상세한 설명
- 해당 기능의 사용 사례
- 기능 요청에 관한 기타 컨텍스트 또는 스크린샷
우선순위 결정 방법:
| 기능 유형 | 우선순위 |
| ------------------------------------------------------------ | --------------- |
| 팀 구성원에 의해 레이블이 지정된 고우선순위 기능 | 높은 우선순위 |
| 우리의 [커뮤니티 피드백 보드](https://github.com/langgenius/dify/discussions/categories/feedbacks)에서 인기 있는 기능 요청 | 중간 우선순위 |
| 비핵심 기능 및 사소한 개선 | 낮은 우선순위 |
| 가치 있지만 즉시 필요하지 않은 기능 | 미래 기능 |
## PR 제출하기
### Pull Request 프로세스
1. 저장소를 포크하세요
2. PR을 작성하기 전에, 변경하고자 하는 내용에 대해 논의하기 위한 이슈를 생성해 주세요
3. 변경 사항을 위한 새 브랜치를 만드세요
4. 변경 사항에 대한 테스트를 적절히 추가해 주세요
5. 코드가 기존 테스트를 통과하는지 확인하세요
6. PR 설명에 이슈를 연결해 주세요, `fixes #<이슈_번호>`
7. 병합 완료!
### 프로젝트 설정하기
#### 프론트엔드
프론트엔드 서비스를 설정하려면, `web/README.md` 파일에 있는 우리의 [종합 가이드](https://github.com/langgenius/dify/blob/main/web/README.md)를 참조하세요. 이 문서는 프론트엔드 환경을 적절히 설정하는 데 도움이 되는 자세한 지침을 제공합니다.
#### 백엔드
백엔드 서비스를 설정하려면, `api/README.md` 파일에 있는 우리의 [상세 지침](https://github.com/langgenius/dify/blob/main/api/README.md)을 참조하세요. 이 문서는 백엔드를 원활하게 실행하는 데 도움이 되는 단계별 가이드를 포함하고 있습니다.
#### 기타 참고 사항
설정을 진행하기 전에 이 문서를 주의 깊게 검토하는 것을 권장합니다. 다음과 같은 필수 정보가 포함되어 있습니다:
- 필수 조건 및 종속성
- 설치 단계
- 구성 세부 정보
- 일반적인 문제 해결 팁
설정 과정에서 문제가 발생하면 언제든지 연락해 주세요.
## 도움 받기
기여하는 동안 막히거나 긴급한 질문이 있으면, 관련 GitHub 이슈를 통해 질문을 보내거나, 빠른 대화를 위해 우리의 [Discord](https://discord.gg/8Tpq4AcN9c)에 참여하세요.

@ -0,0 +1,93 @@
# CONTRIBUINDO
Então você está procurando contribuir para o Dify - isso é incrível, mal podemos esperar para ver o que você vai fazer. Como uma startup com equipe e financiamento limitados, temos grandes ambições de projetar o fluxo de trabalho mais intuitivo para construir e gerenciar aplicações LLM. Qualquer ajuda da comunidade conta, verdadeiramente.
Precisamos ser ágeis e entregar rapidamente considerando onde estamos, mas também queremos garantir que colaboradores como você tenham uma experiência o mais tranquila possível ao contribuir. Montamos este guia de contribuição com esse propósito, visando familiarizá-lo com a base de código e como trabalhamos com os colaboradores, para que você possa rapidamente passar para a parte divertida.
Este guia, como o próprio Dify, é um trabalho em constante evolução. Agradecemos muito a sua compreensão se às vezes ele ficar atrasado em relação ao projeto real, e damos as boas-vindas a qualquer feedback para que possamos melhorar.
Em termos de licenciamento, por favor, dedique um minuto para ler nosso breve [Acordo de Licença e Contribuidor](./LICENSE). A comunidade também adere ao [código de conduta](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Antes de começar
Procurando algo para resolver? Navegue por nossos [problemas para iniciantes](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) e escolha um para começar!
Tem um novo modelo ou ferramenta para adicionar? Abra um PR em nosso [repositório de plugins](https://github.com/langgenius/dify-plugins) e mostre-nos o que você construiu.
Precisa atualizar um modelo existente, ferramenta ou corrigir alguns bugs? Vá para nosso [repositório oficial de plugins](https://github.com/langgenius/dify-official-plugins) e faça sua mágica!
Junte-se à diversão, contribua e vamos construir algo incrível juntos! 💡✨
Não se esqueça de vincular um problema existente ou abrir um novo problema na descrição do PR.
### Relatórios de bugs
> [!IMPORTANT]
> Por favor, certifique-se de incluir as seguintes informações ao enviar um relatório de bug:
- Um título claro e descritivo
- Uma descrição detalhada do bug, incluindo quaisquer mensagens de erro
- Passos para reproduzir o bug
- Comportamento esperado
- **Logs**, se disponíveis, para problemas de backend, isso é realmente importante, você pode encontrá-los nos logs do docker-compose
- Capturas de tela ou vídeos, se aplicável
Como priorizamos:
| Tipo de Problema | Prioridade |
| ------------------------------------------------------------ | --------------- |
| Bugs em funções centrais (serviço em nuvem, não conseguir fazer login, aplicações não funcionando, falhas de segurança) | Crítica |
| Bugs não críticos, melhorias de desempenho | Prioridade Média |
| Correções menores (erros de digitação, interface confusa mas funcional) | Prioridade Baixa |
### Solicitações de recursos
> [!NOTE]
> Por favor, certifique-se de incluir as seguintes informações ao enviar uma solicitação de recurso:
- Um título claro e descritivo
- Uma descrição detalhada do recurso
- Um caso de uso para o recurso
- Qualquer outro contexto ou capturas de tela sobre a solicitação de recurso
Como priorizamos:
| Tipo de Recurso | Prioridade |
| ------------------------------------------------------------ | --------------- |
| Recursos de alta prioridade conforme rotulado por um membro da equipe | Prioridade Alta |
| Solicitações populares de recursos do nosso [quadro de feedback da comunidade](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Prioridade Média |
| Recursos não essenciais e melhorias menores | Prioridade Baixa |
| Valiosos mas não imediatos | Recurso Futuro |
## Enviando seu PR
### Processo de Pull Request
1. Faça um fork do repositório
2. Antes de elaborar um PR, por favor crie um problema para discutir as mudanças que você quer fazer
3. Crie um novo branch para suas alterações
4. Por favor, adicione testes para suas alterações conforme apropriado
5. Certifique-se de que seu código passa nos testes existentes
6. Por favor, vincule o problema na descrição do PR, `fixes #<número_do_problema>`
7. Faça o merge do seu código!
### Configurando o projeto
#### Frontend
Para configurar o serviço frontend, por favor consulte nosso [guia abrangente](https://github.com/langgenius/dify/blob/main/web/README.md) no arquivo `web/README.md`. Este documento fornece instruções detalhadas para ajudá-lo a configurar o ambiente frontend adequadamente.
#### Backend
Para configurar o serviço backend, por favor consulte nossas [instruções detalhadas](https://github.com/langgenius/dify/blob/main/api/README.md) no arquivo `api/README.md`. Este documento contém um guia passo a passo para ajudá-lo a colocar o backend em funcionamento sem problemas.
#### Outras coisas a observar
Recomendamos revisar este documento cuidadosamente antes de prosseguir com a configuração, pois ele contém informações essenciais sobre:
- Pré-requisitos e dependências
- Etapas de instalação
- Detalhes de configuração
- Dicas comuns de solução de problemas
Sinta-se à vontade para entrar em contato se encontrar quaisquer problemas durante o processo de configuração.
## Obtendo Ajuda
Se você ficar preso ou tiver uma dúvida urgente enquanto contribui, simplesmente envie suas perguntas através do problema relacionado no GitHub, ou entre no nosso [Discord](https://discord.gg/8Tpq4AcN9c) para uma conversa rápida.

@ -0,0 +1,93 @@
# KATKIDA BULUNMAK
Demek Dify'a katkıda bulunmak istiyorsunuz - bu harika, ne yapacağınızı görmek için sabırsızlanıyoruz. Sınırlı personel ve finansmana sahip bir startup olarak, LLM uygulamaları oluşturmak ve yönetmek için en sezgisel iş akışını tasarlama konusunda büyük hedeflerimiz var. Topluluktan gelen her türlü yardım gerçekten önemli.
Bulunduğumuz noktada çevik olmamız ve hızlı hareket etmemiz gerekiyor, ancak sizin gibi katkıda bulunanların mümkün olduğunca sorunsuz bir deneyim yaşamasını da sağlamak istiyoruz. Bu katkı rehberini bu amaçla hazırladık; sizi kod tabanıyla ve katkıda bulunanlarla nasıl çalıştığımızla tanıştırmayı, böylece hızlıca eğlenceli kısma geçebilmenizi hedefliyoruz.
Bu rehber, Dify'ın kendisi gibi, sürekli gelişen bir çalışmadır. Bazen gerçek projenin gerisinde kalırsa anlayışınız için çok minnettarız ve gelişmemize yardımcı olacak her türlü geri bildirimi memnuniyetle karşılıyoruz.
Lisanslama konusunda, lütfen kısa [Lisans ve Katkıda Bulunan Anlaşmamızı](./LICENSE) okumak için bir dakikanızı ayırın. Topluluk ayrıca [davranış kurallarına](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md) da uyar.
## Başlamadan Önce
Üzerinde çalışacak bir şey mi arıyorsunuz? [İlk katkıda bulunanlar için iyi sorunlarımıza](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) göz atın ve başlamak için birini seçin!
Eklenecek harika bir yeni model runtime'ı veya aracınız mı var? [Eklenti depomuzda](https://github.com/langgenius/dify-plugins) bir PR açın ve ne yaptığınızı bize gösterin.
Mevcut bir model runtime'ını, aracı güncellemek veya bazı hataları düzeltmek mi istiyorsunuz? [Resmi eklenti depomuza](https://github.com/langgenius/dify-official-plugins) gidin ve sihrinizi gösterin!
Eğlenceye katılın, katkıda bulunun ve birlikte harika bir şeyler inşa edelim! 💡✨
PR açıklamasında mevcut bir sorunu bağlamayı veya yeni bir sorun açmayı unutmayın.
### Hata Raporları
> [!IMPORTANT]
> Lütfen bir hata raporu gönderirken aşağıdaki bilgileri dahil ettiğinizden emin olun:
- Net ve açıklayıcı bir başlık
- Hata mesajları dahil hatanın ayrıntılı bir açıklaması
- Hatayı tekrarlamak için adımlar
- Beklenen davranış
- Mümkünse **Loglar**, backend sorunları için, bu gerçekten önemlidir, bunları docker-compose loglarında bulabilirsiniz
- Uygunsa ekran görüntüleri veya videolar
Nasıl önceliklendiriyoruz:
| Sorun Türü | Öncelik |
| ------------------------------------------------------------ | --------------- |
| Temel işlevlerdeki hatalar (bulut hizmeti, giriş yapamama, çalışmayan uygulamalar, güvenlik açıkları) | Kritik |
| Kritik olmayan hatalar, performans artışları | Orta Öncelik |
| Küçük düzeltmeler (yazım hataları, kafa karıştırıcı ama çalışan UI) | Düşük Öncelik |
### Özellik İstekleri
> [!NOTE]
> Lütfen bir özellik isteği gönderirken aşağıdaki bilgileri dahil ettiğinizden emin olun:
- Net ve açıklayıcı bir başlık
- Özelliğin ayrıntılı bir açıklaması
- Özellik için bir kullanım durumu
- Özellik isteği hakkında diğer bağlamlar veya ekran görüntüleri
Nasıl önceliklendiriyoruz:
| Özellik Türü | Öncelik |
| ------------------------------------------------------------ | --------------- |
| Bir ekip üyesi tarafından etiketlenen Yüksek Öncelikli Özellikler | Yüksek Öncelik |
| [Topluluk geri bildirim panosundan](https://github.com/langgenius/dify/discussions/categories/feedbacks) popüler özellik istekleri | Orta Öncelik |
| Temel olmayan özellikler ve küçük geliştirmeler | Düşük Öncelik |
| Değerli ama acil olmayan | Gelecek-Özellik |
## PR'nizi Göndermek
### Pull Request Süreci
1. Depoyu fork edin
2. Bir PR taslağı oluşturmadan önce, yapmak istediğiniz değişiklikleri tartışmak için lütfen bir sorun oluşturun
3. Değişiklikleriniz için yeni bir dal oluşturun
4. Lütfen değişiklikleriniz için uygun testler ekleyin
5. Kodunuzun mevcut testleri geçtiğinden emin olun
6. Lütfen PR açıklamasında sorunu bağlayın, `fixes #<sorun_numarası>`
7. Kodunuzu birleştirin!
### Projeyi Kurma
#### Frontend
Frontend hizmetini kurmak için, lütfen `web/README.md` dosyasındaki kapsamlı [rehberimize](https://github.com/langgenius/dify/blob/main/web/README.md) bakın. Bu belge, frontend ortamını düzgün bir şekilde kurmanıza yardımcı olacak ayrıntılı talimatlar sağlar.
#### Backend
Backend hizmetini kurmak için, lütfen `api/README.md` dosyasındaki detaylı [talimatlarımıza](https://github.com/langgenius/dify/blob/main/api/README.md) bakın. Bu belge, backend'i sorunsuz bir şekilde çalıştırmanıza yardımcı olacak adım adım bir kılavuz içerir.
#### Dikkat Edilecek Diğer Şeyler
Kuruluma geçmeden önce bu belgeyi dikkatlice incelemenizi öneririz, çünkü şunlar hakkında temel bilgiler içerir:
- Ön koşullar ve bağımlılıklar
- Kurulum adımları
- Yapılandırma detayları
- Yaygın sorun giderme ipuçları
Kurulum süreci sırasında herhangi bir sorunla karşılaşırsanız bizimle iletişime geçmekten çekinmeyin.
## Yardım Almak
Katkıda bulunurken takılırsanız veya yanıcı bir sorunuz olursa, sorularınızı ilgili GitHub sorunu aracılığıyla bize gönderin veya hızlı bir sohbet için [Discord'umuza](https://discord.gg/8Tpq4AcN9c) katılın.

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from pydantic import Field, NonNegativeInt, computed_field from pydantic import Field, NonNegativeInt
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings

@ -1,5 +1,3 @@
from typing import Optional
from pydantic import Field from pydantic import Field
from .apollo import ApolloSettingsSourceInfo from .apollo import ApolloSettingsSourceInfo

@ -54,7 +54,7 @@ workflow_run_fields = {
class WorkflowRunDetailApi(Resource): class WorkflowRunDetailApi(Resource):
@validate_app_token @validate_app_token
@marshal_with(workflow_run_fields) @marshal_with(workflow_run_fields)
def get(self, app_model: App, workflow_id: str): def get(self, app_model: App, workflow_run_id: str):
""" """
Get a workflow task running detail Get a workflow task running detail
""" """
@ -62,7 +62,7 @@ class WorkflowRunDetailApi(Resource):
if app_mode != AppMode.WORKFLOW: if app_mode != AppMode.WORKFLOW:
raise NotWorkflowAppError() raise NotWorkflowAppError()
workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == workflow_id).first() workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == workflow_run_id).first()
return workflow_run return workflow_run
@ -163,6 +163,6 @@ class WorkflowAppLogApi(Resource):
api.add_resource(WorkflowRunApi, "/workflows/run") api.add_resource(WorkflowRunApi, "/workflows/run")
api.add_resource(WorkflowRunDetailApi, "/workflows/run/<string:workflow_id>") api.add_resource(WorkflowRunDetailApi, "/workflows/run/<string:workflow_run_id>")
api.add_resource(WorkflowTaskStopApi, "/workflows/tasks/<string:task_id>/stop") api.add_resource(WorkflowTaskStopApi, "/workflows/tasks/<string:task_id>/stop")
api.add_resource(WorkflowAppLogApi, "/workflows/logs") api.add_resource(WorkflowAppLogApi, "/workflows/logs")

@ -142,6 +142,7 @@ class DatasetApi(DatasetApiResource):
Deletes a dataset given its ID. Deletes a dataset given its ID.
Args: Args:
_: ignore
dataset_id (UUID): The ID of the dataset to be deleted. dataset_id (UUID): The ID of the dataset to be deleted.
Returns: Returns:

@ -19,6 +19,8 @@ class PassportResource(Resource):
def get(self): def get(self):
system_features = FeatureService.get_system_features() system_features = FeatureService.get_system_features()
app_code = request.headers.get("X-App-Code") app_code = request.headers.get("X-App-Code")
user_id = request.args.get("user_id")
if app_code is None: if app_code is None:
raise Unauthorized("X-App-Code header is missing.") raise Unauthorized("X-App-Code header is missing.")
@ -36,6 +38,24 @@ class PassportResource(Resource):
if not app_model or app_model.status != "normal" or not app_model.enable_site: if not app_model or app_model.status != "normal" or not app_model.enable_site:
raise NotFound() raise NotFound()
if user_id:
end_user = (
db.session.query(EndUser).filter(EndUser.app_id == app_model.id, EndUser.session_id == user_id).first()
)
if end_user:
pass
else:
end_user = EndUser(
tenant_id=app_model.tenant_id,
app_id=app_model.id,
type="browser",
is_anonymous=True,
session_id=user_id,
)
db.session.add(end_user)
db.session.commit()
else:
end_user = EndUser( end_user = EndUser(
tenant_id=app_model.tenant_id, tenant_id=app_model.tenant_id,
app_id=app_model.id, app_id=app_model.id,
@ -43,7 +63,6 @@ class PassportResource(Resource):
is_anonymous=True, is_anonymous=True,
session_id=generate_session_id(), session_id=generate_session_id(),
) )
db.session.add(end_user) db.session.add(end_user)
db.session.commit() db.session.commit()

@ -332,7 +332,7 @@ class BaseAgentRunner(AppRunner):
agent_thought = updated_agent_thought agent_thought = updated_agent_thought
if thought: if thought:
agent_thought.thought = thought agent_thought.thought += thought
if tool_name: if tool_name:
agent_thought.tool = tool_name agent_thought.tool = tool_name

@ -12,11 +12,14 @@ class CotAgentOutputParser:
def handle_react_stream_output( def handle_react_stream_output(
cls, llm_response: Generator[LLMResultChunk, None, None], usage_dict: dict cls, llm_response: Generator[LLMResultChunk, None, None], usage_dict: dict
) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]: ) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]:
def parse_action(json_str): def parse_action(action) -> Union[str, AgentScratchpadUnit.Action]:
try:
action = json.loads(json_str, strict=False)
action_name = None action_name = None
action_input = None action_input = None
if isinstance(action, str):
try:
action = json.loads(action, strict=False)
except json.JSONDecodeError:
return action or ""
# cohere always returns a list # cohere always returns a list
if isinstance(action, list) and len(action) == 1: if isinstance(action, list) and len(action) == 1:
@ -34,17 +37,20 @@ class CotAgentOutputParser:
action_input=action_input, action_input=action_input,
) )
else: else:
return json_str or "" return json.dumps(action)
except:
return json_str or ""
def extra_json_from_code_block(code_block) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]: def extra_json_from_code_block(code_block) -> list[Union[list, dict]]:
code_blocks = re.findall(r"```(.*?)```", code_block, re.DOTALL) blocks = re.findall(r"```[json]*\s*([\[{].*[]}])\s*```", code_block, re.DOTALL | re.IGNORECASE)
if not code_blocks: if not blocks:
return return []
for block in code_blocks: try:
json_blocks = []
for block in blocks:
json_text = re.sub(r"^[a-zA-Z]+\n", "", block.strip(), flags=re.MULTILINE) json_text = re.sub(r"^[a-zA-Z]+\n", "", block.strip(), flags=re.MULTILINE)
yield parse_action(json_text) json_blocks.append(json.loads(json_text, strict=False))
return json_blocks
except:
return []
code_block_cache = "" code_block_cache = ""
code_block_delimiter_count = 0 code_block_delimiter_count = 0
@ -78,7 +84,7 @@ class CotAgentOutputParser:
delta = response_content[index : index + steps] delta = response_content[index : index + steps]
yield_delta = False yield_delta = False
if delta == "`": if not in_json and delta == "`":
last_character = delta last_character = delta
code_block_cache += delta code_block_cache += delta
code_block_delimiter_count += 1 code_block_delimiter_count += 1
@ -159,8 +165,14 @@ class CotAgentOutputParser:
if code_block_delimiter_count == 3: if code_block_delimiter_count == 3:
if in_code_block: if in_code_block:
last_character = delta last_character = delta
yield from extra_json_from_code_block(code_block_cache) action_json_list = extra_json_from_code_block(code_block_cache)
if action_json_list:
for action_json in action_json_list:
yield parse_action(action_json)
code_block_cache = "" code_block_cache = ""
else:
index += steps
continue
in_code_block = not in_code_block in_code_block = not in_code_block
code_block_delimiter_count = 0 code_block_delimiter_count = 0

@ -16,7 +16,6 @@ class ModelConfigConverter:
""" """
Convert app model config dict to entity. Convert app model config dict to entity.
:param app_config: app config :param app_config: app config
:param skip_check: skip check
:raises ProviderTokenNotInitError: provider token not init error :raises ProviderTokenNotInitError: provider token not init error
:return: app orchestration config entity :return: app orchestration config entity
""" """

@ -88,7 +88,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
:param user: account or end user :param user: account or end user
:param args: request args :param args: request args
:param invoke_from: invoke from source :param invoke_from: invoke from source
:param stream: is stream :param streaming: is stream
""" """
if not args.get("query"): if not args.get("query"):
raise ValueError("query is required") raise ValueError("query is required")
@ -181,10 +181,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
:param app_model: App :param app_model: App
:param workflow: Workflow :param workflow: Workflow
:param node_id: the node id
:param user: account or end user :param user: account or end user
:param args: request args :param args: request args
:param invoke_from: invoke from source :param streaming: is streamed
:param stream: is stream
""" """
if not node_id: if not node_id:
raise ValueError("node_id is required") raise ValueError("node_id is required")
@ -238,10 +238,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
:param app_model: App :param app_model: App
:param workflow: Workflow :param workflow: Workflow
:param node_id: the node id
:param user: account or end user :param user: account or end user
:param args: request args :param args: request args
:param invoke_from: invoke from source :param streaming: is stream
:param stream: is stream
""" """
if not node_id: if not node_id:
raise ValueError("node_id is required") raise ValueError("node_id is required")

@ -80,7 +80,7 @@ class AgentChatAppGenerator(MessageBasedAppGenerator):
:param user: account or end user :param user: account or end user
:param args: request args :param args: request args
:param invoke_from: invoke from source :param invoke_from: invoke from source
:param stream: is stream :param streaming: is stream
""" """
if not streaming: if not streaming:
raise ValueError("Agent Chat App does not support blocking mode") raise ValueError("Agent Chat App does not support blocking mode")

@ -157,6 +157,7 @@ class AppRunner:
:param files: files :param files: files
:param query: query :param query: query
:param memory: memory :param memory: memory
:param image_detail_config: the image quality config
:return: :return:
""" """
# get prompt without memory and context # get prompt without memory and context

@ -76,7 +76,7 @@ class ChatAppGenerator(MessageBasedAppGenerator):
:param user: account or end user :param user: account or end user
:param args: request args :param args: request args
:param invoke_from: invoke from source :param invoke_from: invoke from source
:param stream: is stream :param streaming: is stream
""" """
if not args.get("query"): if not args.get("query"):
raise ValueError("query is required") raise ValueError("query is required")

@ -74,7 +74,7 @@ class CompletionAppGenerator(MessageBasedAppGenerator):
:param user: account or end user :param user: account or end user
:param args: request args :param args: request args
:param invoke_from: invoke from source :param invoke_from: invoke from source
:param stream: is stream :param streaming: is stream
""" """
query = args["query"] query = args["query"]
if not isinstance(query, str): if not isinstance(query, str):

@ -158,7 +158,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
:param user: account or end user :param user: account or end user
:param application_generate_entity: application generate entity :param application_generate_entity: application generate entity
:param invoke_from: invoke from source :param invoke_from: invoke from source
:param stream: is stream :param streaming: is stream
:param workflow_thread_pool_id: workflow thread pool id :param workflow_thread_pool_id: workflow thread pool id
""" """
# init queue manager # init queue manager
@ -208,10 +208,10 @@ class WorkflowAppGenerator(BaseAppGenerator):
:param app_model: App :param app_model: App
:param workflow: Workflow :param workflow: Workflow
:param node_id: the node id
:param user: account or end user :param user: account or end user
:param args: request args :param args: request args
:param invoke_from: invoke from source :param streaming: is streamed
:param stream: is stream
""" """
if not node_id: if not node_id:
raise ValueError("node_id is required") raise ValueError("node_id is required")
@ -264,10 +264,10 @@ class WorkflowAppGenerator(BaseAppGenerator):
:param app_model: App :param app_model: App
:param workflow: Workflow :param workflow: Workflow
:param node_id: the node id
:param user: account or end user :param user: account or end user
:param args: request args :param args: request args
:param invoke_from: invoke from source :param streaming: is streamed
:param stream: is stream
""" """
if not node_id: if not node_id:
raise ValueError("node_id is required") raise ValueError("node_id is required")

@ -44,9 +44,6 @@ class WorkflowAppRunner(WorkflowBasedAppRunner):
def run(self) -> None: def run(self) -> None:
""" """
Run application Run application
:param application_generate_entity: application generate entity
:param queue_manager: application queue manager
:return:
""" """
app_config = self.application_generate_entity.app_config app_config = self.application_generate_entity.app_config
app_config = cast(WorkflowAppConfig, app_config) app_config = cast(WorkflowAppConfig, app_config)

@ -48,7 +48,7 @@ class MessageCycleManage:
def _generate_conversation_name(self, *, conversation_id: str, query: str) -> Optional[Thread]: def _generate_conversation_name(self, *, conversation_id: str, query: str) -> Optional[Thread]:
""" """
Generate conversation name. Generate conversation name.
:param conversation: conversation :param conversation_id: conversation id
:param query: query :param query: query
:return: thread :return: thread
""" """

@ -44,6 +44,7 @@ from core.app.entities.task_entities import (
WorkflowFinishStreamResponse, WorkflowFinishStreamResponse,
WorkflowStartStreamResponse, WorkflowStartStreamResponse,
) )
from core.app.task_pipeline.exc import WorkflowRunNotFoundError
from core.file import FILE_MODEL_IDENTITY, File from core.file import FILE_MODEL_IDENTITY, File
from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.ops.entities.trace_entity import TraceTaskName from core.ops.entities.trace_entity import TraceTaskName
@ -66,8 +67,6 @@ from models.workflow import (
WorkflowRunStatus, WorkflowRunStatus,
) )
from .exc import WorkflowRunNotFoundError
class WorkflowCycleManage: class WorkflowCycleManage:
def __init__( def __init__(
@ -154,7 +153,7 @@ class WorkflowCycleManage:
) -> WorkflowRun: ) -> WorkflowRun:
""" """
Workflow run success Workflow run success
:param workflow_run: workflow run :param workflow_run_id: workflow run id
:param start_at: start time :param start_at: start time
:param total_tokens: total tokens :param total_tokens: total tokens
:param total_steps: total steps :param total_steps: total steps
@ -166,7 +165,7 @@ class WorkflowCycleManage:
outputs = WorkflowEntry.handle_special_values(outputs) outputs = WorkflowEntry.handle_special_values(outputs)
workflow_run.status = WorkflowRunStatus.SUCCEEDED.value workflow_run.status = WorkflowRunStatus.SUCCEEDED
workflow_run.outputs = json.dumps(outputs or {}) workflow_run.outputs = json.dumps(outputs or {})
workflow_run.elapsed_time = time.perf_counter() - start_at workflow_run.elapsed_time = time.perf_counter() - start_at
workflow_run.total_tokens = total_tokens workflow_run.total_tokens = total_tokens
@ -201,7 +200,7 @@ class WorkflowCycleManage:
workflow_run = self._get_workflow_run(session=session, workflow_run_id=workflow_run_id) workflow_run = self._get_workflow_run(session=session, workflow_run_id=workflow_run_id)
outputs = WorkflowEntry.handle_special_values(dict(outputs) if outputs else None) outputs = WorkflowEntry.handle_special_values(dict(outputs) if outputs else None)
workflow_run.status = WorkflowRunStatus.PARTIAL_SUCCESSED.value workflow_run.status = WorkflowRunStatus.PARTIAL_SUCCEEDED.value
workflow_run.outputs = json.dumps(outputs or {}) workflow_run.outputs = json.dumps(outputs or {})
workflow_run.elapsed_time = time.perf_counter() - start_at workflow_run.elapsed_time = time.perf_counter() - start_at
workflow_run.total_tokens = total_tokens workflow_run.total_tokens = total_tokens
@ -237,7 +236,7 @@ class WorkflowCycleManage:
) -> WorkflowRun: ) -> WorkflowRun:
""" """
Workflow run failed Workflow run failed
:param workflow_run: workflow run :param workflow_run_id: workflow run id
:param start_at: start time :param start_at: start time
:param total_tokens: total tokens :param total_tokens: total tokens
:param total_steps: total steps :param total_steps: total steps

@ -36,7 +36,7 @@ class UploadFileParser:
""" """
get signed url from upload file get signed url from upload file
:param upload_file: UploadFile object :param upload_file_id: the id of UploadFile object
:return: :return:
""" """
base_url = dify_config.FILES_URL base_url = dify_config.FILES_URL

@ -60,6 +60,7 @@ class CodeExecutor:
""" """
Execute code Execute code
:param language: code language :param language: code language
:param preload: the preload script
:param code: code :param code: code
:return: :return:
""" """

@ -53,7 +53,7 @@ def pin_position_map(original_position_map: dict[str, int], pin_list: list[str])
""" """
Pin the items in the pin list to the beginning of the position map. Pin the items in the pin list to the beginning of the position map.
Overall logic: exclude > include > pin Overall logic: exclude > include > pin
:param position_map: the position map to be sorted and filtered :param original_position_map: the position map to be sorted and filtered
:param pin_list: the list of pins to be put at the beginning :param pin_list: the list of pins to be put at the beginning
:return: the sorted position map :return: the sorted position map
""" """

@ -38,12 +38,7 @@ class ToolParameterCache:
return None return None
def set(self, parameters: dict) -> None: def set(self, parameters: dict) -> None:
""" """Cache model provider credentials."""
Cache model provider credentials.
:param credentials: provider credentials
:return:
"""
redis_client.setex(self.cache_key, 86400, json.dumps(parameters)) redis_client.setex(self.cache_key, 86400, json.dumps(parameters))
def delete(self) -> None: def delete(self) -> None:

@ -38,7 +38,6 @@ class TTSModel(AIModel):
:param credentials: model credentials :param credentials: model credentials
:param voice: model timbre :param voice: model timbre
:param content_text: text content to be translated :param content_text: text content to be translated
:param streaming: output is streaming
:param user: unique user id :param user: unique user id
:return: translated audio file :return: translated audio file
""" """

@ -8,6 +8,7 @@ from datetime import timedelta
from typing import Any, Optional, Union from typing import Any, Optional, Union
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from cachetools import LRUCache
from flask import current_app from flask import current_app
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@ -70,6 +71,8 @@ provider_config_map: dict[str, dict[str, Any]] = {
class OpsTraceManager: class OpsTraceManager:
ops_trace_instances_cache: LRUCache = LRUCache(maxsize=128)
@classmethod @classmethod
def encrypt_tracing_config( def encrypt_tracing_config(
cls, tenant_id: str, tracing_provider: str, tracing_config: dict, current_trace_config=None cls, tenant_id: str, tracing_provider: str, tracing_config: dict, current_trace_config=None
@ -204,29 +207,33 @@ class OpsTraceManager:
return None return None
app_ops_trace_config = json.loads(app.tracing) if app.tracing else None app_ops_trace_config = json.loads(app.tracing) if app.tracing else None
if app_ops_trace_config is None: if app_ops_trace_config is None:
return None return None
if not app_ops_trace_config.get("enabled"):
return None
tracing_provider = app_ops_trace_config.get("tracing_provider") tracing_provider = app_ops_trace_config.get("tracing_provider")
if tracing_provider is None or tracing_provider not in provider_config_map: if tracing_provider is None or tracing_provider not in provider_config_map:
return None return None
# decrypt_token # decrypt_token
decrypt_trace_config = cls.get_decrypted_tracing_config(app_id, tracing_provider) decrypt_trace_config = cls.get_decrypted_tracing_config(app_id, tracing_provider)
if app_ops_trace_config.get("enabled"): if not decrypt_trace_config:
return None
trace_instance, config_class = ( trace_instance, config_class = (
provider_config_map[tracing_provider]["trace_instance"], provider_config_map[tracing_provider]["trace_instance"],
provider_config_map[tracing_provider]["config_class"], provider_config_map[tracing_provider]["config_class"],
) )
if not decrypt_trace_config: decrypt_trace_config_key = str(decrypt_trace_config)
return None tracing_instance = cls.ops_trace_instances_cache.get(decrypt_trace_config_key)
if tracing_instance is None:
# create new tracing_instance and update the cache if it absent
tracing_instance = trace_instance(config_class(**decrypt_trace_config)) tracing_instance = trace_instance(config_class(**decrypt_trace_config))
cls.ops_trace_instances_cache[decrypt_trace_config_key] = tracing_instance
logging.info(f"new tracing_instance for app_id: {app_id}")
return tracing_instance return tracing_instance
return None
@classmethod @classmethod
def get_app_config_through_message_id(cls, message_id: str): def get_app_config_through_message_id(cls, message_id: str):
app_model_config = None app_model_config = None

@ -177,7 +177,6 @@ class OpenGauss(BaseVector):
Search the nearest neighbors to a vector. Search the nearest neighbors to a vector.
:param query_vector: The input vector to search for similar items. :param query_vector: The input vector to search for similar items.
:param top_k: The number of nearest neighbors to return, default is 5.
:return: List of Documents that are nearest to the query vector. :return: List of Documents that are nearest to the query vector.
""" """
top_k = kwargs.get("top_k", 4) top_k = kwargs.get("top_k", 4)

@ -197,7 +197,6 @@ class OracleVector(BaseVector):
Search the nearest neighbors to a vector. Search the nearest neighbors to a vector.
:param query_vector: The input vector to search for similar items. :param query_vector: The input vector to search for similar items.
:param top_k: The number of nearest neighbors to return, default is 5.
:return: List of Documents that are nearest to the query vector. :return: List of Documents that are nearest to the query vector.
""" """
top_k = kwargs.get("top_k", 4) top_k = kwargs.get("top_k", 4)

@ -167,7 +167,6 @@ class PGVector(BaseVector):
Search the nearest neighbors to a vector. Search the nearest neighbors to a vector.
:param query_vector: The input vector to search for similar items. :param query_vector: The input vector to search for similar items.
:param top_k: The number of nearest neighbors to return, default is 5.
:return: List of Documents that are nearest to the query vector. :return: List of Documents that are nearest to the query vector.
""" """
top_k = kwargs.get("top_k", 4) top_k = kwargs.get("top_k", 4)

@ -22,7 +22,6 @@ class TidbService:
:param iam_url: The URL of the TiDB Cloud IAM API (required). :param iam_url: The URL of the TiDB Cloud IAM API (required).
:param public_key: The public key for the API (required). :param public_key: The public key for the API (required).
:param private_key: The private key for the API (required). :param private_key: The private key for the API (required).
:param display_name: The user-friendly display name of the cluster (required).
:param region: The region where the cluster will be created (required). :param region: The region where the cluster will be created (required).
:return: The response from the API. :return: The response from the API.
@ -149,13 +148,12 @@ class TidbService:
): ):
""" """
Update the status of a new TiDB Serverless cluster. Update the status of a new TiDB Serverless cluster.
:param tidb_serverless_list: The TiDB serverless list (required).
:param project_id: The project ID of the TiDB Cloud project (required). :param project_id: The project ID of the TiDB Cloud project (required).
:param api_url: The URL of the TiDB Cloud API (required). :param api_url: The URL of the TiDB Cloud API (required).
:param iam_url: The URL of the TiDB Cloud IAM API (required). :param iam_url: The URL of the TiDB Cloud IAM API (required).
:param public_key: The public key for the API (required). :param public_key: The public key for the API (required).
:param private_key: The private key for the API (required). :param private_key: The private key for the API (required).
:param display_name: The user-friendly display name of the cluster (required).
:param region: The region where the cluster will be created (required).
:return: The response from the API. :return: The response from the API.
""" """
@ -186,12 +184,12 @@ class TidbService:
) -> list[dict]: ) -> list[dict]:
""" """
Creates a new TiDB Serverless cluster. Creates a new TiDB Serverless cluster.
:param batch_size: The batch size (required).
:param project_id: The project ID of the TiDB Cloud project (required). :param project_id: The project ID of the TiDB Cloud project (required).
:param api_url: The URL of the TiDB Cloud API (required). :param api_url: The URL of the TiDB Cloud API (required).
:param iam_url: The URL of the TiDB Cloud IAM API (required). :param iam_url: The URL of the TiDB Cloud IAM API (required).
:param public_key: The public key for the API (required). :param public_key: The public key for the API (required).
:param private_key: The private key for the API (required). :param private_key: The private key for the API (required).
:param display_name: The user-friendly display name of the cluster (required).
:param region: The region where the cluster will be created (required). :param region: The region where the cluster will be created (required).
:return: The response from the API. :return: The response from the API.

@ -226,7 +226,6 @@ class WeaviateVector(BaseVector):
Args: Args:
query: Text to look up documents similar to. query: Text to look up documents similar to.
k: Number of Documents to return. Defaults to 4.
Returns: Returns:
List of Documents most similar to the query. List of Documents most similar to the query.

@ -7,11 +7,10 @@ class FirecrawlWebExtractor(BaseExtractor):
""" """
Crawl and scrape websites and return content in clean llm-ready markdown. Crawl and scrape websites and return content in clean llm-ready markdown.
Args: Args:
url: The URL to scrape. url: The URL to scrape.
api_key: The API key for Firecrawl. job_id: The crawl job id.
base_url: The base URL for the Firecrawl API. Defaults to 'https://api.firecrawl.dev'. tenant_id: The tenant id.
mode: The mode of operation. Defaults to 'scrape'. Options are 'crawl', 'scrape' and 'crawl_return_urls'. mode: The mode of operation. Defaults to 'scrape'. Options are 'crawl', 'scrape' and 'crawl_return_urls'.
only_main_content: Only return the main content of the page excluding headers, navs, footers, etc. only_main_content: Only return the main content of the page excluding headers, navs, footers, etc.
""" """

@ -14,15 +14,6 @@ class UnstructuredMarkdownExtractor(BaseExtractor):
Args: Args:
file_path: Path to the file to load. file_path: Path to the file to load.
remove_hyperlinks: Whether to remove hyperlinks from the text.
remove_images: Whether to remove images from the text.
encoding: File encoding to use. If `None`, the file will be loaded
with the default system encoding.
autodetect_encoding: Whether to try to autodetect the file encoding
if the specified encoding fails.
""" """
def __init__(self, file_path: str, api_url: Optional[str] = None, api_key: str = ""): def __init__(self, file_path: str, api_url: Optional[str] = None, api_key: str = ""):

@ -1,7 +1,7 @@
from enum import Enum from enum import Enum, StrEnum
class BuiltInField(str, Enum): class BuiltInField(StrEnum):
document_name = "document_name" document_name = "document_name"
uploader = "uploader" uploader = "uploader"
upload_date = "upload_date" upload_date = "upload_date"

@ -1,7 +1,7 @@
from enum import Enum from enum import StrEnum
class IndexType(str, Enum): class IndexType(StrEnum):
PARAGRAPH_INDEX = "text_model" PARAGRAPH_INDEX = "text_model"
QA_INDEX = "qa_model" QA_INDEX = "qa_model"
PARENT_CHILD_INDEX = "hierarchical_model" PARENT_CHILD_INDEX = "hierarchical_model"

@ -39,6 +39,8 @@ class ParentChildIndexProcessor(BaseIndexProcessor):
all_documents = [] # type: ignore all_documents = [] # type: ignore
if rules.parent_mode == ParentMode.PARAGRAPH: if rules.parent_mode == ParentMode.PARAGRAPH:
# Split the text documents into nodes. # Split the text documents into nodes.
if not rules.segmentation:
raise ValueError("No segmentation found in rules.")
splitter = self._get_splitter( splitter = self._get_splitter(
processing_rule_mode=process_rule.get("mode"), processing_rule_mode=process_rule.get("mode"),
max_tokens=rules.segmentation.max_tokens, max_tokens=rules.segmentation.max_tokens,

@ -100,6 +100,7 @@ class DatasetRetrieval:
:param hit_callback: hit callback :param hit_callback: hit callback
:param message_id: message id :param message_id: message id
:param memory: memory :param memory: memory
:param inputs: inputs
:return: :return:
""" """
dataset_ids = config.dataset_ids dataset_ids = config.dataset_ids
@ -734,6 +735,7 @@ class DatasetRetrieval:
Calculate keywords scores Calculate keywords scores
:param query: search query :param query: search query
:param documents: documents for reranking :param documents: documents for reranking
:param top_k: top k
:return: :return:
""" """
@ -1031,8 +1033,6 @@ class DatasetRetrieval:
) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]: ) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]:
""" """
Fetch model config Fetch model config
:param node_data: node data
:return:
""" """
if model is None: if model is None:
raise ValueError("single_retrieval_config is required") raise ValueError("single_retrieval_config is required")

@ -235,6 +235,7 @@ class ReactMultiDatasetRouter:
tools: List of tools the agent will have access to, used to format the tools: List of tools the agent will have access to, used to format the
prompt. prompt.
prefix: String to put before the list of tools. prefix: String to put before the list of tools.
format_instructions: The format instruction prompt.
Returns: Returns:
A PromptTemplate with the template assembled from the pieces here. A PromptTemplate with the template assembled from the pieces here.
""" """

@ -29,9 +29,7 @@ class Tool(ABC):
def fork_tool_runtime(self, runtime: ToolRuntime) -> "Tool": def fork_tool_runtime(self, runtime: ToolRuntime) -> "Tool":
""" """
fork a new tool with meta data fork a new tool with metadata
:param meta: the meta data of a tool call processing, tenant_id is required
:return: the new tool :return: the new tool
""" """
return self.__class__( return self.__class__(
@ -206,6 +204,7 @@ class Tool(ABC):
create a blob message create a blob message
:param blob: the blob :param blob: the blob
:param meta: the meta info of blob object
:return: the blob message :return: the blob message
""" """
return ToolInvokeMessage( return ToolInvokeMessage(

@ -153,7 +153,7 @@ class BuiltinToolProviderController(ToolProviderController):
""" """
validate the credentials of the provider validate the credentials of the provider
:param tool_name: the name of the tool, defined in `get_tools` :param user_id: use id
:param credentials: the credentials of the tool :param credentials: the credentials of the tool
""" """
# validate credentials format # validate credentials format
@ -167,7 +167,7 @@ class BuiltinToolProviderController(ToolProviderController):
""" """
validate the credentials of the provider validate the credentials of the provider
:param tool_name: the name of the tool, defined in `get_tools` :param user_id: use id
:param credentials: the credentials of the tool :param credentials: the credentials of the tool
""" """
pass pass

@ -28,9 +28,7 @@ class BuiltinTool(Tool):
def fork_tool_runtime(self, runtime: ToolRuntime) -> "BuiltinTool": def fork_tool_runtime(self, runtime: ToolRuntime) -> "BuiltinTool":
""" """
fork a new tool with meta data fork a new tool with metadata
:param meta: the meta data of a tool call processing, tenant_id is required
:return: the new tool :return: the new tool
""" """
return self.__class__( return self.__class__(
@ -43,7 +41,7 @@ class BuiltinTool(Tool):
""" """
invoke model invoke model
:param model_config: the model config :param user_id: the user id
:param prompt_messages: the prompt messages :param prompt_messages: the prompt messages
:param stop: the stop words :param stop: the stop words
:return: the model result :return: the model result
@ -64,7 +62,6 @@ class BuiltinTool(Tool):
""" """
get max tokens get max tokens
:param model_config: the model config
:return: the max tokens :return: the max tokens
""" """
if self.runtime is None: if self.runtime is None:

@ -145,7 +145,6 @@ class ApiToolProviderController(ToolProviderController):
""" """
fetch tools from database fetch tools from database
:param user_id: the user id
:param tenant_id: the tenant id :param tenant_id: the tenant id
:return: the tools :return: the tools
""" """

@ -35,9 +35,7 @@ class ApiTool(Tool):
def fork_tool_runtime(self, runtime: ToolRuntime): def fork_tool_runtime(self, runtime: ToolRuntime):
""" """
fork a new tool with meta data fork a new tool with metadata
:param meta: the meta data of a tool call processing, tenant_id is required
:return: the new tool :return: the new tool
""" """
if self.api_bundle is None: if self.api_bundle is None:

@ -264,7 +264,7 @@ class ToolParameter(PluginParameter):
:param name: the name of the parameter :param name: the name of the parameter
:param llm_description: the description presented to the LLM :param llm_description: the description presented to the LLM
:param type: the type of the parameter :param typ: the type of the parameter
:param required: if the parameter is required :param required: if the parameter is required
:param options: the options of the parameter :param options: the options of the parameter
""" """

@ -313,7 +313,6 @@ class ToolEngine:
""" """
Create message file Create message file
:param messages: messages
:return: message file ids :return: message file ids
""" """
result = [] result = []

@ -161,8 +161,11 @@ class ToolManager:
get the tool runtime get the tool runtime
:param provider_type: the type of the provider :param provider_type: the type of the provider
:param provider_name: the name of the provider :param provider_id: the id of the provider
:param tool_name: the name of the tool :param tool_name: the name of the tool
:param tenant_id: the tenant id
:param invoke_from: invoke from
:param tool_invoke_from: the tool invoke from
:return: the tool :return: the tool
""" """
@ -427,8 +430,6 @@ class ToolManager:
get the absolute path of the icon of the hardcoded provider get the absolute path of the icon of the hardcoded provider
:param provider: the name of the provider :param provider: the name of the provider
:param tenant_id: the id of the tenant
:return: the absolute path of the icon, the mime type of the icon :return: the absolute path of the icon, the mime type of the icon
""" """
# get provider # get provider
@ -672,7 +673,8 @@ class ToolManager:
""" """
get the api provider get the api provider
:param provider_name: the name of the provider :param tenant_id: the id of the tenant
:param provider_id: the id of the provider
:return: the provider controller, the credentials :return: the provider controller, the credentials
""" """

@ -84,12 +84,8 @@ class ModelInvocationUtils:
:param user_id: user id :param user_id: user id
:param tenant_id: tenant id, the tenant id of the creator of the tool :param tenant_id: tenant id, the tenant id of the creator of the tool
:param tool_provider: tool provider :param tool_type: tool type
:param tool_id: tool id
:param tool_name: tool name :param tool_name: tool name
:param provider: model provider
:param model: model name
:param model_parameters: model parameters
:param prompt_messages: prompt messages :param prompt_messages: prompt messages
:return: AssistantPromptMessage :return: AssistantPromptMessage
""" """

@ -200,6 +200,8 @@ class ApiBasedToolSchemaParser:
parse openapi yaml to tool bundle parse openapi yaml to tool bundle
:param yaml: the yaml string :param yaml: the yaml string
:param extra_info: the extra info
:param warning: the warning message
:return: the tool bundle :return: the tool bundle
""" """
warning = warning if warning is not None else {} warning = warning if warning is not None else {}
@ -281,6 +283,8 @@ class ApiBasedToolSchemaParser:
parse openapi plugin yaml to tool bundle parse openapi plugin yaml to tool bundle
:param json: the json string :param json: the json string
:param extra_info: the extra info
:param warning: the warning message
:return: the tool bundle :return: the tool bundle
""" """
warning = warning if warning is not None else {} warning = warning if warning is not None else {}
@ -315,6 +319,8 @@ class ApiBasedToolSchemaParser:
auto parse to tool bundle auto parse to tool bundle
:param content: the content :param content: the content
:param extra_info: the extra info
:param warning: the warning message
:return: tools bundle, schema_type :return: tools bundle, schema_type
""" """
warning = warning if warning is not None else {} warning = warning if warning is not None else {}

@ -182,7 +182,6 @@ class WorkflowToolProviderController(ToolProviderController):
""" """
fetch tools from database fetch tools from database
:param user_id: the user id
:param tenant_id: the tenant id :param tenant_id: the tenant id
:return: the tools :return: the tools
""" """

@ -127,9 +127,8 @@ class WorkflowTool(Tool):
def fork_tool_runtime(self, runtime: ToolRuntime) -> "WorkflowTool": def fork_tool_runtime(self, runtime: ToolRuntime) -> "WorkflowTool":
""" """
fork a new tool with meta data fork a new tool with metadata
:param meta: the meta data of a tool call processing, tenant_id is required
:return: the new tool :return: the new tool
""" """
return self.__class__( return self.__class__(
@ -212,7 +211,6 @@ class WorkflowTool(Tool):
""" """
extract files from the result extract files from the result
:param result: the result
:return: the result, files :return: the result, files
""" """
files: list[File] = [] files: list[File] = []

@ -375,11 +375,25 @@ def _process_sub_conditions(
for condition in sub_conditions: for condition in sub_conditions:
key = FileAttribute(condition.key) key = FileAttribute(condition.key)
values = [file_manager.get_attr(file=file, attr=key) for file in files] values = [file_manager.get_attr(file=file, attr=key) for file in files]
expected_value = condition.value
if key == FileAttribute.EXTENSION:
if not isinstance(expected_value, str):
raise TypeError("Expected value must be a string when key is FileAttribute.EXTENSION")
if expected_value and not expected_value.startswith("."):
expected_value = "." + expected_value
normalized_values = []
for value in values:
if value and isinstance(value, str):
if not value.startswith("."):
value = "." + value
normalized_values.append(value)
values = normalized_values
sub_group_results = [ sub_group_results = [
_evaluate_condition( _evaluate_condition(
value=value, value=value,
operator=condition.comparison_operator, operator=condition.comparison_operator,
expected=condition.value, expected=expected_value,
) )
for value in values for value in values
] ]

@ -95,7 +95,6 @@ class VariableTemplateParser:
Args: Args:
inputs: A dictionary containing the values for the template variables. inputs: A dictionary containing the values for the template variables.
remove_template_variables: A boolean indicating whether to remove the template variables from the output.
Returns: Returns:
The formatted string with template variables replaced by their values. The formatted string with template variables replaced by their values.

@ -204,6 +204,8 @@ class WorkflowEntry:
NOTE: only parameter_extractor/question_classifier are supported NOTE: only parameter_extractor/question_classifier are supported
:param node_data: node data :param node_data: node data
:param node_id: node id
:param tenant_id: tenant id
:param user_id: user id :param user_id: user id
:param user_inputs: user inputs :param user_inputs: user inputs
:return: :return:

@ -134,8 +134,9 @@ def _build_from_local_file(
if row is None: if row is None:
raise ValueError("Invalid upload file") raise ValueError("Invalid upload file")
file_type = FileType(mapping.get("type", "custom")) file_type = _standardize_file_type(extension="." + row.extension, mime_type=row.mime_type)
file_type = _standardize_file_type(file_type, extension="." + row.extension, mime_type=row.mime_type) if file_type.value != mapping.get("type", "custom"):
raise ValueError("Detected file type does not match the specified type. Please verify the file.")
return File( return File(
id=mapping.get("id"), id=mapping.get("id"),
@ -173,10 +174,9 @@ def _build_from_remote_url(
if upload_file is None: if upload_file is None:
raise ValueError("Invalid upload file") raise ValueError("Invalid upload file")
file_type = FileType(mapping.get("type", "custom")) file_type = _standardize_file_type(extension="." + upload_file.extension, mime_type=upload_file.mime_type)
file_type = _standardize_file_type( if file_type.value != mapping.get("type", "custom"):
file_type, extension="." + upload_file.extension, mime_type=upload_file.mime_type raise ValueError("Detected file type does not match the specified type. Please verify the file.")
)
return File( return File(
id=mapping.get("id"), id=mapping.get("id"),
@ -196,10 +196,11 @@ def _build_from_remote_url(
raise ValueError("Invalid file url") raise ValueError("Invalid file url")
mime_type, filename, file_size = _get_remote_file_info(url) mime_type, filename, file_size = _get_remote_file_info(url)
extension = mimetypes.guess_extension(mime_type) or "." + filename.split(".")[-1] if "." in filename else ".bin" extension = mimetypes.guess_extension(mime_type) or ("." + filename.split(".")[-1] if "." in filename else ".bin")
file_type = FileType(mapping.get("type", "custom")) file_type = _standardize_file_type(extension=extension, mime_type=mime_type)
file_type = _standardize_file_type(file_type, extension=extension, mime_type=mime_type) if file_type.value != mapping.get("type", "custom"):
raise ValueError("Detected file type does not match the specified type. Please verify the file.")
return File( return File(
id=mapping.get("id"), id=mapping.get("id"),
@ -250,8 +251,10 @@ def _build_from_tool_file(
raise ValueError(f"ToolFile {mapping.get('tool_file_id')} not found") raise ValueError(f"ToolFile {mapping.get('tool_file_id')} not found")
extension = "." + tool_file.file_key.split(".")[-1] if "." in tool_file.file_key else ".bin" extension = "." + tool_file.file_key.split(".")[-1] if "." in tool_file.file_key else ".bin"
file_type = FileType(mapping.get("type", "custom"))
file_type = _standardize_file_type(file_type, extension=extension, mime_type=tool_file.mimetype) file_type = _standardize_file_type(extension=extension, mime_type=tool_file.mimetype)
if file_type.value != mapping.get("type", "custom"):
raise ValueError("Detected file type does not match the specified type. Please verify the file.")
return File( return File(
id=mapping.get("id"), id=mapping.get("id"),
@ -302,12 +305,10 @@ def _is_file_valid_with_config(
return True return True
def _standardize_file_type(file_type: FileType, /, *, extension: str = "", mime_type: str = "") -> FileType: def _standardize_file_type(*, extension: str = "", mime_type: str = "") -> FileType:
""" """
If custom type, try to guess the file type by extension and mime_type. Infer the possible actual type of the file based on the extension and mime_type
""" """
if file_type != FileType.CUSTOM:
return FileType(file_type)
guessed_type = None guessed_type = None
if extension: if extension:
guessed_type = _get_file_type_by_extension(extension) guessed_type = _get_file_type_by_extension(extension)

@ -791,7 +791,7 @@ class Conversation(db.Model): # type: ignore[name-defined]
WorkflowRunStatus.SUCCEEDED: 0, WorkflowRunStatus.SUCCEEDED: 0,
WorkflowRunStatus.FAILED: 0, WorkflowRunStatus.FAILED: 0,
WorkflowRunStatus.STOPPED: 0, WorkflowRunStatus.STOPPED: 0,
WorkflowRunStatus.PARTIAL_SUCCESSED: 0, WorkflowRunStatus.PARTIAL_SUCCEEDED: 0,
} }
for message in messages: for message in messages:
@ -802,7 +802,7 @@ class Conversation(db.Model): # type: ignore[name-defined]
{ {
"success": status_counts[WorkflowRunStatus.SUCCEEDED], "success": status_counts[WorkflowRunStatus.SUCCEEDED],
"failed": status_counts[WorkflowRunStatus.FAILED], "failed": status_counts[WorkflowRunStatus.FAILED],
"partial_success": status_counts[WorkflowRunStatus.PARTIAL_SUCCESSED], "partial_success": status_counts[WorkflowRunStatus.PARTIAL_SUCCEEDED],
} }
if messages if messages
else None else None

@ -109,7 +109,7 @@ class Workflow(Base):
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False) app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
type: Mapped[str] = mapped_column(db.String(255), nullable=False) type: Mapped[str] = mapped_column(db.String(255), nullable=False)
version: Mapped[str] version: Mapped[str] = mapped_column(db.String(255), nullable=False)
marked_name: Mapped[str] = mapped_column(default="", server_default="") marked_name: Mapped[str] = mapped_column(default="", server_default="")
marked_comment: Mapped[str] = mapped_column(default="", server_default="") marked_comment: Mapped[str] = mapped_column(default="", server_default="")
graph: Mapped[str] = mapped_column(sa.Text) graph: Mapped[str] = mapped_column(sa.Text)
@ -352,7 +352,7 @@ class WorkflowRunStatus(StrEnum):
SUCCEEDED = "succeeded" SUCCEEDED = "succeeded"
FAILED = "failed" FAILED = "failed"
STOPPED = "stopped" STOPPED = "stopped"
PARTIAL_SUCCESSED = "partial-succeeded" PARTIAL_SUCCEEDED = "partial-succeeded"
class WorkflowRun(Base): class WorkflowRun(Base):
@ -755,7 +755,8 @@ class WorkflowAppLog(Base):
__tablename__ = "workflow_app_logs" __tablename__ = "workflow_app_logs"
__table_args__ = ( __table_args__ = (
db.PrimaryKeyConstraint("id", name="workflow_app_log_pkey"), db.PrimaryKeyConstraint("id", name="workflow_app_log_pkey"),
db.Index("workflow_app_log_app_idx", "tenant_id", "app_id"), db.Index("workflow_app_log_app_idx", "tenant_id", "app_id", "created_at"),
db.Index("workflow_app_log_workflow_run_idx", "workflow_run_id"),
) )
id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()"))

@ -913,6 +913,8 @@ class RegisterService:
db.session.commit() db.session.commit()
except WorkSpaceNotAllowedCreateError: except WorkSpaceNotAllowedCreateError:
db.session.rollback() db.session.rollback()
logging.exception("Register failed")
raise AccountRegisterError("Workspace is not allowed to create.")
except AccountRegisterError as are: except AccountRegisterError as are:
db.session.rollback() db.session.rollback()
logging.exception("Register failed") logging.exception("Register failed")

@ -513,6 +513,7 @@ class AppDslService:
""" """
Export app Export app
:param app_model: App instance :param app_model: App instance
:param include_secret: Whether include secret variable
:return: :return:
""" """
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)

@ -1,4 +1,4 @@
from enum import Enum from enum import StrEnum
from typing import Literal, Optional from typing import Literal, Optional
from pydantic import BaseModel from pydantic import BaseModel
@ -11,7 +11,7 @@ class SegmentUpdateEntity(BaseModel):
enabled: Optional[bool] = None enabled: Optional[bool] = None
class ParentMode(str, Enum): class ParentMode(StrEnum):
FULL_DOC = "full-doc" FULL_DOC = "full-doc"
PARAGRAPH = "paragraph" PARAGRAPH = "paragraph"

@ -28,7 +28,6 @@ class BuiltinToolManageService:
""" """
list builtin tool provider tools list builtin tool provider tools
:param user_id: the id of the user
:param tenant_id: the id of the tenant :param tenant_id: the id of the tenant
:param provider: the name of the provider :param provider: the name of the provider

@ -60,6 +60,7 @@ class ToolTransformService:
""" """
repack provider repack provider
:param tenant_id: the tenant id
:param provider: the provider dict :param provider: the provider dict
""" """
if isinstance(provider, dict) and "icon" in provider: if isinstance(provider, dict) and "icon" in provider:

@ -222,7 +222,7 @@ class WorkflowToolManageService:
Delete a workflow tool. Delete a workflow tool.
:param user_id: the user id :param user_id: the user id
:param tenant_id: the tenant id :param tenant_id: the tenant id
:param workflow_app_id: the workflow app id :param workflow_tool_id: the workflow tool id
""" """
db.session.query(WorkflowToolProvider).filter( db.session.query(WorkflowToolProvider).filter(
WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == workflow_tool_id WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == workflow_tool_id
@ -238,7 +238,7 @@ class WorkflowToolManageService:
Get a workflow tool. Get a workflow tool.
:param user_id: the user id :param user_id: the user id
:param tenant_id: the tenant id :param tenant_id: the tenant id
:param workflow_app_id: the workflow app id :param workflow_tool_id: the workflow tool id
:return: the tool :return: the tool
""" """
db_tool: WorkflowToolProvider | None = ( db_tool: WorkflowToolProvider | None = (
@ -313,7 +313,7 @@ class WorkflowToolManageService:
List workflow tool provider tools. List workflow tool provider tools.
:param user_id: the user id :param user_id: the user id
:param tenant_id: the tenant id :param tenant_id: the tenant id
:param workflow_app_id: the workflow app id :param workflow_tool_id: the workflow tool id
:return: the list of tools :return: the list of tools
""" """
db_tool: WorkflowToolProvider | None = ( db_tool: WorkflowToolProvider | None = (

@ -10,11 +10,7 @@ from extensions.ext_mail import mail
@shared_task(queue="mail") @shared_task(queue="mail")
def send_deletion_success_task(to): def send_deletion_success_task(to):
"""Send email to user regarding account deletion. """Send email to user regarding account deletion."""
Args:
log (AccountDeletionLog): Account deletion log object
"""
if not mail.is_inited(): if not mail.is_inited():
return return

@ -17,8 +17,6 @@ from models.workflow import WorkflowRun
def process_trace_tasks(file_info): def process_trace_tasks(file_info):
""" """
Async process trace tasks Async process trace tasks
:param tasks_data: List of dictionaries containing task data
Usage: process_trace_tasks.delay(tasks_data) Usage: process_trace_tasks.delay(tasks_data)
""" """
from core.ops.ops_trace_manager import OpsTraceManager from core.ops.ops_trace_manager import OpsTraceManager

@ -0,0 +1,70 @@
import json
from collections.abc import Generator
from core.agent.entities import AgentScratchpadUnit
from core.agent.output_parser.cot_output_parser import CotAgentOutputParser
from core.model_runtime.entities.llm_entities import AssistantPromptMessage, LLMResultChunk, LLMResultChunkDelta
def mock_llm_response(text) -> Generator[LLMResultChunk, None, None]:
for i in range(len(text)):
yield LLMResultChunk(
model="model",
prompt_messages=[],
delta=LLMResultChunkDelta(index=0, message=AssistantPromptMessage(content=text[i], tool_calls=[])),
)
def test_cot_output_parser():
test_cases = [
{
"input": 'Through: abc\nAction: ```{"action": "Final Answer", "action_input": "```echarts\n {}\n```"}```',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# code block with json
{
"input": 'Through: abc\nAction: ```json\n{"action": "Final Answer", "action_input": "```echarts\n {'
'}\n```"}```',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# code block with JSON
{
"input": 'Through: abc\nAction: ```JSON\n{"action": "Final Answer", "action_input": "```echarts\n {'
'}\n```"}```',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# list
{
"input": 'Through: abc\nAction: ```[{"action": "Final Answer", "action_input": "```echarts\n {}\n```"}]```',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# no code block
{
"input": 'Through: abc\nAction: {"action": "Final Answer", "action_input": "```echarts\n {}\n```"}',
"action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"},
"output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}',
},
# no code block and json
{"input": "Through: abc\nAction: efg", "action": {}, "output": "Through: abc\n efg"},
]
parser = CotAgentOutputParser()
usage_dict = {}
for test_case in test_cases:
# mock llm_response as a generator by text
llm_response: Generator[LLMResultChunk, None, None] = mock_llm_response(test_case["input"])
results = parser.handle_react_stream_output(llm_response, usage_dict)
output = ""
for result in results:
if isinstance(result, str):
output += result
elif isinstance(result, AgentScratchpadUnit.Action):
if test_case["action"]:
assert result.to_dict() == test_case["action"]
output += json.dumps(result.to_dict())
if test_case["output"]:
assert output == test_case["output"]

@ -50,8 +50,8 @@ const ActivateForm = () => {
{checkRes && !checkRes.is_valid && ( {checkRes && !checkRes.is_valid && (
<div className="flex flex-col md:w-[400px]"> <div className="flex flex-col md:w-[400px]">
<div className="mx-auto w-full"> <div className="mx-auto w-full">
<div className="mb-3 flex h-20 w-20 items-center justify-center rounded-[20px] border border-gray-100 p-5 text-[40px] font-bold shadow-lg">🤷</div> <div className="mb-3 flex h-20 w-20 items-center justify-center rounded-[20px] border border-divider-regular bg-components-option-card-option-bg p-5 text-[40px] font-bold shadow-lg">🤷</div>
<h2 className="text-[32px] font-bold text-gray-900">{t('login.invalid')}</h2> <h2 className="text-[32px] font-bold text-text-primary">{t('login.invalid')}</h2>
</div> </div>
<div className="mx-auto mt-6 w-full"> <div className="mx-auto mt-6 w-full">
<Button variant='primary' className='w-full !text-sm'> <Button variant='primary' className='w-full !text-sm'>

@ -1,27 +1,15 @@
import React from 'react' import React from 'react'
import Header from '../signin/_header' import Header from '../signin/_header'
import style from '../signin/page.module.css'
import ActivateForm from './activateForm' import ActivateForm from './activateForm'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
const Activate = () => { const Activate = () => {
return ( return (
<div className={cn( <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
style.background, <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>
'flex min-h-screen w-full',
'sm:p-4 lg:p-8',
'gap-x-20',
'justify-center lg:justify-start',
)}>
<div className={
cn(
'flex w-full shrink-0 flex-col rounded-2xl bg-white shadow',
'space-between',
)
}>
<Header /> <Header />
<ActivateForm /> <ActivateForm />
<div className='px-8 py-6 text-sm font-normal text-gray-500'> <div className='px-8 py-6 text-sm font-normal text-text-tertiary'>
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved. © {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
</div> </div>
</div> </div>

@ -1,4 +0,0 @@
.logo {
background: #fff center no-repeat url(./team-28x28.png);
background-size: 56px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

@ -77,6 +77,7 @@ import {
correctToolProvider, correctToolProvider,
} from '@/utils' } from '@/utils'
import PluginDependency from '@/app/components/workflow/plugin-dependency' import PluginDependency from '@/app/components/workflow/plugin-dependency'
import { supportFunctionCall } from '@/utils/tool-call'
type PublishConfig = { type PublishConfig = {
modelConfig: ModelConfig modelConfig: ModelConfig
@ -347,12 +348,7 @@ const Configuration: FC = () => {
}, },
) )
const isFunctionCall = (() => { const isFunctionCall = supportFunctionCall(currModel?.features)
const features = currModel?.features
if (!features)
return false
return features.includes(ModelFeatureEnum.toolCall) || features.includes(ModelFeatureEnum.multiToolCall)
})()
// Fill old app data missing model mode. // Fill old app data missing model mode.
useEffect(() => { useEffect(() => {

@ -214,6 +214,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps)
/> />
</div> </div>
</div> </div>
{isAppsFull && <AppsFull className='mt-4' loc='app-create' />}
<div className='flex items-center justify-between pb-10 pt-5'> <div className='flex items-center justify-between pb-10 pt-5'>
<div className='system-xs-regular flex cursor-pointer items-center gap-1 text-text-tertiary' onClick={onCreateFromTemplate}> <div className='system-xs-regular flex cursor-pointer items-center gap-1 text-text-tertiary' onClick={onCreateFromTemplate}>
<span>{t('app.newApp.noIdeaTip')}</span> <span>{t('app.newApp.noIdeaTip')}</span>
@ -251,13 +252,6 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps)
</div> </div>
</div> </div>
</div> </div>
{
isAppsFull && (
<div className='px-8 py-2'>
<AppsFull loc='app-create' />
</div>
)
}
</> </>
} }
type CreateAppDialogProps = CreateAppProps & { type CreateAppDialogProps = CreateAppProps & {

@ -96,7 +96,7 @@ const DuplicateAppModal = ({
className='h-10' className='h-10'
/> />
</div> </div>
{isAppsFull && <AppsFull loc='app-duplicate-create' />} {isAppsFull && <AppsFull className='mt-4' loc='app-duplicate-create' />}
</div> </div>
<div className='flex flex-row-reverse'> <div className='flex flex-row-reverse'>
<Button disabled={isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{t('app.duplicate')}</Button> <Button disabled={isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{t('app.duplicate')}</Button>

@ -44,7 +44,10 @@ const OPTION_MAP = {
: ''}${IS_CE_EDITION : ''}${IS_CE_EDITION
? `, ? `,
baseUrl: '${url}'` baseUrl: '${url}'`
: ''} : ''},
systemVariables: {
// user_id: 'YOU CAN DEFINE USER ID HERE',
},
} }
</script> </script>
<script <script

@ -164,7 +164,7 @@ const Answer: FC<AnswerProps> = ({
) )
} }
{ {
(hasAgentThoughts || content) && ( (hasAgentThoughts) && (
<AgentContent <AgentContent
item={item} item={item}
responding={responding} responding={responding}

@ -3,11 +3,16 @@ import type { IChatItem } from './chat/type'
import type { ChatItem, ChatItemInTree } from './types' import type { ChatItem, ChatItemInTree } from './types'
async function decodeBase64AndDecompress(base64String: string) { async function decodeBase64AndDecompress(base64String: string) {
try {
const binaryString = atob(base64String) const binaryString = atob(base64String)
const compressedUint8Array = Uint8Array.from(binaryString, char => char.charCodeAt(0)) const compressedUint8Array = Uint8Array.from(binaryString, char => char.charCodeAt(0))
const decompressedStream = new Response(compressedUint8Array).body?.pipeThrough(new DecompressionStream('gzip')) const decompressedStream = new Response(compressedUint8Array).body?.pipeThrough(new DecompressionStream('gzip'))
const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer() const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer()
return new TextDecoder().decode(decompressedArrayBuffer) return new TextDecoder().decode(decompressedArrayBuffer)
}
catch {
return undefined
}
} }
async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> { async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> {
@ -16,12 +21,26 @@ async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> {
const entriesArray = Array.from(urlParams.entries()) const entriesArray = Array.from(urlParams.entries())
await Promise.all( await Promise.all(
entriesArray.map(async ([key, value]) => { entriesArray.map(async ([key, value]) => {
if (!key.startsWith('sys.'))
inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value)) inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value))
}), }),
) )
return inputs return inputs
} }
async function getProcessedSystemVariablesFromUrlParams(): Promise<Record<string, any>> {
const urlParams = new URLSearchParams(window.location.search)
const systemVariables: Record<string, any> = {}
const entriesArray = Array.from(urlParams.entries())
await Promise.all(
entriesArray.map(async ([key, value]) => {
if (key.startsWith('sys.'))
systemVariables[key.slice(4)] = await decodeBase64AndDecompress(decodeURIComponent(value))
}),
)
return systemVariables
}
function isValidGeneratedAnswer(item?: ChatItem | ChatItemInTree): boolean { function isValidGeneratedAnswer(item?: ChatItem | ChatItemInTree): boolean {
return !!item && item.isAnswer && !item.id.startsWith('answer-placeholder-') && !item.isOpeningStatement return !!item && item.isAnswer && !item.id.startsWith('answer-placeholder-') && !item.isOpeningStatement
} }
@ -166,6 +185,7 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch
export { export {
getProcessedInputsFromUrlParams, getProcessedInputsFromUrlParams,
getProcessedSystemVariablesFromUrlParams,
isValidGeneratedAnswer, isValidGeneratedAnswer,
getLastAnswer, getLastAnswer,
buildChatItemTree, buildChatItemTree,

@ -1,3 +1,4 @@
import classNames from '@/utils/classnames'
import type { FC } from 'react' import type { FC } from 'react'
type LogoEmbeddedChatHeaderProps = { type LogoEmbeddedChatHeaderProps = {
@ -7,13 +8,16 @@ type LogoEmbeddedChatHeaderProps = {
const LogoEmbeddedChatHeader: FC<LogoEmbeddedChatHeaderProps> = ({ const LogoEmbeddedChatHeader: FC<LogoEmbeddedChatHeaderProps> = ({
className, className,
}) => { }) => {
return ( return <picture>
<source media="(resolution: 1x)" srcSet='/logo/logo-embedded-chat-header.png' />
<source media="(resolution: 2x)" srcSet='/logo/logo-embedded-chat-header@2x.png' />
<source media="(resolution: 3x)" srcSet='/logo/logo-embedded-chat-header@3x.png' />
<img <img
src='/logo/logo-embedded-chat-header.png' src='/logo/logo-embedded-chat-header.png'
className={`block h-6 w-auto ${className}`}
alt='logo' alt='logo'
className={classNames('block h-6 w-auto', className)}
/> />
) </picture>
} }
export default LogoEmbeddedChatHeader export default LogoEmbeddedChatHeader

@ -238,7 +238,7 @@ const SimpleSelect: FC<ISelectProps> = ({
)} )}
{!disabled && ( {!disabled && (
<ListboxOptions className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-components-panel-bg-blur backdrop-blur-sm py-1 text-base shadow-lg border-components-panel-border border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}> <ListboxOptions className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-xl bg-components-panel-bg-blur backdrop-blur-sm py-1 text-base shadow-lg border-components-panel-border border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}>
{items.map((item: Item) => ( {items.map((item: Item) => (
<ListboxOption <ListboxOption
key={item.value} key={item.value}

@ -21,11 +21,7 @@ export default function Select({
<div className="w-56 text-right"> <div className="w-56 text-right">
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
<div> <div>
<MenuButton className="h-[44px]justify-center inline-flex w-full items-center <MenuButton className="h-[44px]justify-center inline-flex w-full items-center rounded-lg border border-components-button-secondary-border px-[10px] py-[6px] text-[13px] font-medium text-text-primary hover:bg-state-base-hover">
rounded-lg border border-gray-200
px-[10px] py-[6px] text-[13px]
font-medium text-gray-900
hover:bg-gray-100">
<GlobeAltIcon className="mr-1 h-5 w-5" aria-hidden="true" /> <GlobeAltIcon className="mr-1 h-5 w-5" aria-hidden="true" />
{item?.name} {item?.name}
</MenuButton> </MenuButton>
@ -39,12 +35,12 @@ export default function Select({
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<MenuItems className="absolute right-0 z-10 mt-2 w-[200px] origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"> <MenuItems className="absolute right-0 z-10 mt-2 w-[200px] origin-top-right divide-y divide-divider-regular rounded-md bg-components-panel-bg shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="px-1 py-1 "> <div className="px-1 py-1 ">
{items.map((item) => { {items.map((item) => {
return <MenuItem key={item.value}> return <MenuItem key={item.value}>
<button <button
className={'group flex w-full items-center rounded-lg px-3 py-2 text-sm text-gray-700 data-[active]:bg-gray-100'} className={'group flex w-full items-center rounded-lg px-3 py-2 text-sm text-text-secondary data-[active]:bg-state-base-hover'}
onClick={(evt) => { onClick={(evt) => {
evt.preventDefault() evt.preventDefault()
onChange && onChange(item.value) onChange && onChange(item.value)
@ -63,50 +59,3 @@ export default function Select({
</div> </div>
) )
} }
export function InputSelect({
items,
value,
onChange,
}: ISelectProps) {
const item = items.filter(item => item.value === value)[0]
return (
<div className="w-full">
<Menu as="div" className="w-full">
<div>
<MenuButton className="block h-[38px] w-full appearance-none rounded-md border border-gray-300 px-3 py-2 text-left shadow-sm placeholder:text-gray-400 sm:text-sm">
{item?.name}
</MenuButton>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems className="absolute right-0 z-10 mt-2 w-full origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="px-1 py-1 ">
{items.map((item) => {
return <MenuItem key={item.value}>
<button
className={'group flex w-full items-center rounded-md px-2 py-2 text-sm data-[active]:bg-gray-100'}
onClick={() => {
onChange && onChange(item.value)
}}
>
{item.name}
</button>
</MenuItem>
})}
</div>
</MenuItems>
</Transition>
</Menu>
</div>
)
}

@ -3,35 +3,82 @@ import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import UpgradeBtn from '../upgrade-btn' import UpgradeBtn from '../upgrade-btn'
import AppsInfo from '../usage-info/apps-info' import ProgressBar from '@/app/components/billing/progress-bar'
import Button from '@/app/components/base/button'
import { mailToSupport } from '@/app/components/header/utils/util'
import { useProviderContext } from '@/context/provider-context'
import { useAppContext } from '@/context/app-context'
import { Plan } from '@/app/components/billing/type'
import s from './style.module.css' import s from './style.module.css'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import GridMask from '@/app/components/base/grid-mask'
const AppsFull: FC<{ loc: string; className?: string }> = ({ const LOW = 50
const MIDDLE = 80
const AppsFull: FC<{ loc: string; className?: string; }> = ({
loc, loc,
className, className,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { plan } = useProviderContext()
const { userProfile, langeniusVersionInfo } = useAppContext()
const isTeam = plan.type === Plan.team
const usage = plan.usage.buildApps
const total = plan.total.buildApps
const percent = usage / total * 100
const color = (() => {
if (percent < LOW)
return 'bg-components-progress-bar-progress-solid'
if (percent < MIDDLE)
return 'bg-components-progress-warning-progress'
return 'bg-components-progress-error-progress'
})()
return ( return (
<GridMask wrapperClassName='rounded-lg' canvasClassName='rounded-lg' gradientClassName='rounded-lg'>
<div className={cn( <div className={cn(
'mt-6 flex cursor-pointer flex-col rounded-lg border-2 border-solid border-transparent px-3.5 py-4 shadow-md transition-all duration-200 ease-in-out', 'flex flex-col gap-3 rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg p-4 shadow-xs backdrop-blur-sm',
className, className,
)}> )}>
<div className='flex items-center justify-between'> <div className='flex justify-between'>
<div className={cn(s.textGradient, 'text-base font-semibold leading-[24px]')}> {!isTeam && (
<div>{t('billing.apps.fullTipLine1')}</div> <div>
<div>{t('billing.apps.fullTipLine2')}</div> <div className={cn('title-xl-semi-bold mb-1', s.textGradient)}>
{t('billing.apps.fullTip1')}
</div>
<div className='system-xs-regular text-text-tertiary'>{t('billing.apps.fullTip1des')}</div>
</div>
)}
{isTeam && (
<div>
<div className={cn('title-xl-semi-bold mb-1', s.textGradient)}>
{t('billing.apps.fullTip2')}
</div>
<div className='system-xs-regular text-text-tertiary'>{t('billing.apps.fullTip2des')}</div>
</div>
)}
{(plan.type === Plan.sandbox || plan.type === Plan.professional) && (
<UpgradeBtn isShort loc={loc} />
)}
{plan.type !== Plan.sandbox && plan.type !== Plan.professional && (
<Button variant='secondary-accent'>
<a target='_blank' rel='noopener noreferrer' href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}>
{t('billing.apps.contactUs')}
</a>
</Button>
)}
</div> </div>
<div className='flex'> <div className='flex flex-col gap-2'>
<UpgradeBtn loc={loc} /> <div className='system-xs-medium flex items-center justify-between text-text-secondary'>
<div>{t('billing.usagePage.buildApps')}</div>
<div>{usage}/{total}</div>
</div> </div>
<ProgressBar
percent={percent}
color={color}
/>
</div> </div>
<AppsInfo className='mt-4' />
</div> </div>
</GridMask>
) )
} }
export default React.memo(AppsFull) export default React.memo(AppsFull)

@ -1,5 +1,5 @@
.textGradient { .textGradient {
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%); background: linear-gradient(92deg, #0EBCF3 -29.55%, #2250F2 75.22%);
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;

@ -1,27 +0,0 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import UpgradeBtn from '../upgrade-btn'
import s from './style.module.css'
import cn from '@/utils/classnames'
import GridMask from '@/app/components/base/grid-mask'
const AppsFull: FC = () => {
const { t } = useTranslation()
return (
<GridMask wrapperClassName='rounded-lg' canvasClassName='rounded-lg' gradientClassName='rounded-lg'>
<div className='col-span-1 flex min-h-[160px] cursor-pointer flex-col rounded-lg border-2 border-solid border-transparent px-3.5 pt-3.5 shadow-xs transition-all duration-200 ease-in-out hover:shadow-lg'>
<div className={cn(s.textGradient, 'text-base font-semibold leading-[24px]')}>
<div>{t('billing.apps.fullTipLine1')}</div>
<div>{t('billing.apps.fullTipLine2')}</div>
</div>
<div className='mt-8 flex'>
<UpgradeBtn loc='app-create' />
</div>
</div>
</GridMask>
)
}
export default React.memo(AppsFull)

@ -1,7 +0,0 @@
.textGradient {
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
}

@ -124,7 +124,7 @@ Chat applications support session persistence, allowing previous chat history to
- `created_at` (int) Creation timestamp, e.g.: 1705395332 - `created_at` (int) Creation timestamp, e.g.: 1705395332
- `event: agent_thought` thought of Agent, contains the thought of LLM, input and output of tool calls (Only supported in Agent mode) - `event: agent_thought` thought of Agent, contains the thought of LLM, input and output of tool calls (Only supported in Agent mode)
- `id` (string) Agent thought ID, every iteration has a unique agent thought ID - `id` (string) Agent thought ID, every iteration has a unique agent thought ID
- `task_id` (string) (string) Task ID, used for request tracking and the below Stop Generate API - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
- `message_id` (string) Unique message ID - `message_id` (string) Unique message ID
- `position` (int) Position of current agent thought, each message may have multiple thoughts in order. - `position` (int) Position of current agent thought, each message may have multiple thoughts in order.
- `thought` (string) What LLM is thinking about - `thought` (string) What LLM is thinking about

@ -124,7 +124,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `created_at` (int) 作成タイムスタンプ、例1705395332 - `created_at` (int) 作成タイムスタンプ、例1705395332
- `event: agent_thought` エージェントの思考、LLMの思考、ツール呼び出しの入力と出力を含みますエージェントモードでのみサポート - `event: agent_thought` エージェントの思考、LLMの思考、ツール呼び出しの入力と出力を含みますエージェントモードでのみサポート
- `id` (string) エージェント思考ID、各反復には一意のエージェント思考IDがあります - `id` (string) エージェント思考ID、各反復には一意のエージェント思考IDがあります
- `task_id` (string) (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
- `message_id` (string) 一意のメッセージID - `message_id` (string) 一意のメッセージID
- `position` (int) 現在のエージェント思考の位置、各メッセージには順番に複数の思考が含まれる場合があります。 - `position` (int) 現在のエージェント思考の位置、各メッセージには順番に複数の思考が含まれる場合があります。
- `thought` (string) LLMが考えていること - `thought` (string) LLMが考えていること

@ -51,7 +51,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
- `custom` 具体类型包含:其他文件类型 - `custom` 具体类型包含:其他文件类型
- `transfer_method` (string) 传递方式,`remote_url` 图片地址 / `local_file` 上传文件 - `transfer_method` (string) 传递方式,`remote_url` 图片地址 / `local_file` 上传文件
- `url` (string) 图片地址(仅当传递方式为 `remote_url` 时) - `url` (string) 图片地址(仅当传递方式为 `remote_url` 时)
- `upload_file_id` (string) (string) 上传文件 ID仅当传递方式为 `local_file` 时) - `upload_file_id` (string) 上传文件 ID仅当传递方式为 `local_file` 时)
- `response_mode` (string) Required - `response_mode` (string) Required
返回响应模式,支持: 返回响应模式,支持:
- `streaming` 流式模式(推荐)。基于 SSE**[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)**)实现类似打字机输出方式的流式返回。 - `streaming` 流式模式(推荐)。基于 SSE**[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)**)实现类似打字机输出方式的流式返回。
@ -318,7 +318,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
--- ---
<Heading <Heading
url='/workflows/run/:workflow_id' url='/workflows/run/:workflow_run_id'
method='GET' method='GET'
title='获取workflow执行情况' title='获取workflow执行情况'
name='#get-workflow-run-detail' name='#get-workflow-run-detail'
@ -327,7 +327,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
<Col> <Col>
根据 workflow 执行 ID 获取 workflow 任务当前执行结果 根据 workflow 执行 ID 获取 workflow 任务当前执行结果
### Path ### Path
- `workflow_id` (string) workflow 执行 ID,可在流式返回 Chunk 中获取 - `workflow_run_id` (string) workflow_run_id,可在流式返回 Chunk 中获取
### Response ### Response
- `id` (string) workflow 执行 ID - `id` (string) workflow 执行 ID
- `workflow_id` (string) 关联的 Workflow ID - `workflow_id` (string) 关联的 Workflow ID
@ -343,9 +343,9 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
</Col> </Col>
<Col sticky> <Col sticky>
### Request Example ### Request Example
<CodeGroup title="Request" tag="GET" label="/workflows/run/:workflow_id" targetCode={`curl -X GET '${props.appDetail.api_base_url}/workflows/run/:workflow_id' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json'`}> <CodeGroup title="Request" tag="GET" label="/workflows/run/:workflow_run_id" targetCode={`curl -X GET '${props.appDetail.api_base_url}/workflows/run/:workflow_run_id' \\\n-H 'Authorization: Bearer {api_key}' \\\n-H 'Content-Type: application/json'`}>
```bash {{ title: 'cURL' }} ```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/workflows/run/:workflow_id' \ curl -X GET '${props.appDetail.api_base_url}/workflows/run/:workflow_run_id' \
-H 'Authorization: Bearer {api_key}' \ -H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' -H 'Content-Type: application/json'
``` ```

@ -142,7 +142,7 @@ const CreateAppModal = ({
<p className='body-xs-regular text-text-tertiary'>{t('app.answerIcon.descriptionInExplore')}</p> <p className='body-xs-regular text-text-tertiary'>{t('app.answerIcon.descriptionInExplore')}</p>
</div> </div>
)} )}
{!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />} {!isEditModal && isAppsFull && <AppsFull className='mt-4' loc='app-explore-create' />}
</div> </div>
<div className='flex flex-row-reverse'> <div className='flex flex-row-reverse'>
<Button disabled={!isEditModal && isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button> <Button disabled={!isEditModal && isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button>

@ -85,7 +85,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
<MenuItems <MenuItems
className=" className="
absolute right-0 mt-1.5 w-60 max-w-80 absolute right-0 mt-1.5 w-60 max-w-80
origin-top-right divide-y divide-divider-subtle rounded-lg bg-components-panel-bg-blur origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur
shadow-lg focus:outline-none shadow-lg focus:outline-none
" "
> >

@ -40,7 +40,9 @@ const WorkplaceSelector = () => {
gap-1.5 p-0.5 hover:bg-state-base-hover ${open && 'bg-state-base-hover'} rounded-[10px] gap-1.5 p-0.5 hover:bg-state-base-hover ${open && 'bg-state-base-hover'} rounded-[10px]
`, `,
)}> )}>
<div className='flex h-7 w-7 items-center justify-center rounded-lg bg-[#EFF4FF] text-xs font-medium text-primary-600'>{currentWorkspace?.name[0].toLocaleUpperCase()}</div> <div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px]'>
<span className='bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text font-semibold uppercase text-shadow-shadow-1 opacity-90'>{currentWorkspace?.name[0]?.toLocaleUpperCase()}</span>
</div>
<div className='flex flex-row'> <div className='flex flex-row'>
<div className={'system-sm-medium max-w-[80px] truncate text-text-secondary'}>{currentWorkspace?.name}</div> <div className={'system-sm-medium max-w-[80px] truncate text-text-secondary'}>{currentWorkspace?.name}</div>
<RiArrowDownSLine className='h-4 w-4 text-text-secondary' /> <RiArrowDownSLine className='h-4 w-4 text-text-secondary' />
@ -58,18 +60,20 @@ const WorkplaceSelector = () => {
<MenuItems <MenuItems
className={cn( className={cn(
` `
shadows-shadow-lg absolute left-[-15px] mt-1 flex w-[280px] flex-col items-start rounded-xl shadows-shadow-lg absolute left-[-15px] mt-1 flex w-[280px] flex-col items-start rounded-xl bg-components-panel-bg-blur backdrop-blur-[5px]
`, `,
)} )}
> >
<div className="flex w-full flex-col items-start self-stretch rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 pb-2 shadow-lg "> <div className="flex w-full flex-col items-start self-stretch rounded-xl border-[0.5px] border-components-panel-border p-1 pb-2 shadow-lg ">
<div className='flex items-start self-stretch px-3 pb-0.5 pt-1'> <div className='flex items-start self-stretch px-3 pb-0.5 pt-1'>
<span className='system-xs-medium-uppercase flex-1 text-text-tertiary'>{t('common.userProfile.workspace')}</span> <span className='system-xs-medium-uppercase flex-1 text-text-tertiary'>{t('common.userProfile.workspace')}</span>
</div> </div>
{ {
workspaces.map(workspace => ( workspaces.map(workspace => (
<div className='flex items-center gap-2 self-stretch rounded-lg py-1 pl-3 pr-2 hover:bg-state-base-hover' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}> <div className='flex items-center gap-2 self-stretch rounded-lg py-1 pl-3 pr-2 hover:bg-state-base-hover' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
<div className='flex h-6 w-6 items-center justify-center rounded-md bg-[#EFF4FF] text-xs font-medium text-primary-600'>{workspace.name[0].toLocaleUpperCase()}</div> <div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px]'>
<span className='bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text font-semibold uppercase text-shadow-shadow-1 opacity-90'>{workspace?.name[0]?.toLocaleUpperCase()}</span>
</div>
<div className='system-md-regular line-clamp-1 grow cursor-pointer overflow-hidden text-ellipsis text-text-secondary'>{workspace.name}</div> <div className='system-md-regular line-clamp-1 grow cursor-pointer overflow-hidden text-ellipsis text-text-secondary'>{workspace.name}</div>
<PlanBadge plan={workspace.plan as Plan} /> <PlanBadge plan={workspace.plan as Plan} />
</div> </div>

@ -25,7 +25,7 @@ const Collapse = ({
const toggle = () => setOpen(!open) const toggle = () => setOpen(!open)
return ( return (
<div className={classNames('bg-background-section-burn rounded-xl', wrapperClassName)}> <div className={classNames('bg-background-section-burn rounded-xl overflow-hidden', wrapperClassName)}>
<div className='flex cursor-pointer items-center justify-between px-3 py-2 text-xs font-medium leading-[18px] text-text-secondary' onClick={toggle}> <div className='flex cursor-pointer items-center justify-between px-3 py-2 text-xs font-medium leading-[18px] text-text-secondary' onClick={toggle}>
{title} {title}
{ {

@ -15,7 +15,6 @@ import I18n from '@/context/i18n'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import Avatar from '@/app/components/base/avatar' import Avatar from '@/app/components/base/avatar'
import type { InvitationResult } from '@/models/common' import type { InvitationResult } from '@/models/common'
import LogoEmbeddedChatHeader from '@/app/components/base/logo/logo-embedded-chat-header'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { Plan } from '@/app/components/billing/type' import { Plan } from '@/app/components/billing/type'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
@ -56,7 +55,9 @@ const MembersPage = () => {
<> <>
<div className='flex flex-col'> <div className='flex flex-col'>
<div className='mb-4 flex items-center gap-3 rounded-xl border-l-[0.5px] border-t-[0.5px] border-divider-subtle bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 p-3 pr-5'> <div className='mb-4 flex items-center gap-3 rounded-xl border-l-[0.5px] border-t-[0.5px] border-divider-subtle bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 p-3 pr-5'>
<LogoEmbeddedChatHeader className='!h-12 !w-12' /> <div className='flex h-12 w-12 items-center justify-center rounded-xl bg-components-icon-bg-blue-solid text-[20px]'>
<span className='bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text font-semibold uppercase text-shadow-shadow-1 opacity-90'>{currentWorkspace?.name[0]?.toLocaleUpperCase()}</span>
</div>
<div className='grow'> <div className='grow'>
<div className='system-md-semibold text-text-secondary'>{currentWorkspace?.name}</div> <div className='system-md-semibold text-text-secondary'>{currentWorkspace?.name}</div>
{enableBilling && ( {enableBilling && (

@ -55,6 +55,7 @@ export enum ModelFeatureEnum {
toolCall = 'tool-call', toolCall = 'tool-call',
multiToolCall = 'multi-tool-call', multiToolCall = 'multi-tool-call',
agentThought = 'agent-thought', agentThought = 'agent-thought',
streamToolCall = 'stream-tool-call',
vision = 'vision', vision = 'vision',
video = 'video', video = 'video',
document = 'document', document = 'document',

@ -1,11 +1,8 @@
import { useCallback, useMemo, useState } from 'react' import { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import { useDebounce } from 'ahooks' import { useDebounce } from 'ahooks'
import { import {
RiAlertFill, RiAlertFill,
RiArrowDownSLine,
RiArrowRightUpLine,
RiBrainLine, RiBrainLine,
} from '@remixicon/react' } from '@remixicon/react'
import SystemModelSelector from './system-model-selector' import SystemModelSelector from './system-model-selector'
@ -13,7 +10,6 @@ import ProviderAddedCard from './provider-added-card'
import type { import type {
ConfigurationMethodEnum, ConfigurationMethodEnum,
CustomConfigurationModelFixedFields, CustomConfigurationModelFixedFields,
ModelProvider, ModelProvider,
} from './declarations' } from './declarations'
import { import {
@ -22,18 +18,12 @@ import {
} from './declarations' } from './declarations'
import { import {
useDefaultModel, useDefaultModel,
useMarketplaceAllPlugins,
useModelModalHandler, useModelModalHandler,
} from './hooks' } from './hooks'
import Divider from '@/app/components/base/divider' import InstallFromMarketplace from './install-from-marketplace'
import Loading from '@/app/components/base/loading'
import ProviderCard from '@/app/components/plugins/provider-card'
import List from '@/app/components/plugins/marketplace/list'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import type { Plugin } from '@/app/components/plugins/types'
import { MARKETPLACE_URL_PREFIX } from '@/config'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { getLocaleOnClient } from '@/i18n' import { useSelector as useAppContextSelector } from '@/context/app-context'
type Props = { type Props = {
searchText: string searchText: string
@ -50,6 +40,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text) const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text)
const { data: ttsDefaultModel } = useDefaultModel(ModelTypeEnum.tts) const { data: ttsDefaultModel } = useDefaultModel(ModelTypeEnum.tts)
const { modelProviders: providers } = useProviderContext() const { modelProviders: providers } = useProviderContext()
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel
const [configuredProviders, notConfiguredProviders] = useMemo(() => { const [configuredProviders, notConfiguredProviders] = useMemo(() => {
const configuredProviders: ModelProvider[] = [] const configuredProviders: ModelProvider[] = []
@ -94,19 +85,6 @@ const ModelProviderPage = ({ searchText }: Props) => {
}, [configuredProviders, debouncedSearchText, notConfiguredProviders]) }, [configuredProviders, debouncedSearchText, notConfiguredProviders])
const handleOpenModal = useModelModalHandler() const handleOpenModal = useModelModalHandler()
const [collapse, setCollapse] = useState(false)
const locale = getLocaleOnClient()
const {
plugins: allPlugins,
isLoading: isAllPluginsLoading,
} = useMarketplaceAllPlugins(providers, searchText)
const cardRender = useCallback((plugin: Plugin) => {
if (plugin.type === 'bundle')
return null
return <ProviderCard key={plugin.plugin_id} payload={plugin} />
}, [])
return ( return (
<div className='relative -mt-2 pt-1'> <div className='relative -mt-2 pt-1'>
@ -168,38 +146,15 @@ const ModelProviderPage = ({ searchText }: Props) => {
</div> </div>
</> </>
)} )}
<div className='mb-2'>
<Divider className='!mt-4 h-px' />
<div className='flex items-center justify-between'>
<div className='system-md-semibold flex cursor-pointer items-center gap-1 text-text-primary' onClick={() => setCollapse(!collapse)}>
<RiArrowDownSLine className={cn('h-4 w-4', collapse && '-rotate-90')} />
{t('common.modelProvider.installProvider')}
</div>
<div className='mb-2 flex items-center pt-2'>
<span className='system-sm-regular pr-1 text-text-tertiary'>{t('common.modelProvider.discoverMore')}</span>
<Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='system-sm-medium inline-flex items-center text-text-accent'>
{t('plugin.marketplace.difyMarketplace')}
<RiArrowRightUpLine className='h-4 w-4' />
</Link>
</div>
</div>
{!collapse && isAllPluginsLoading && <Loading type='area' />}
{ {
!isAllPluginsLoading && !collapse && ( enable_marketplace && (
<List <InstallFromMarketplace
marketplaceCollections={[]} providers={providers}
marketplaceCollectionPluginsMap={{}} searchText={searchText}
plugins={allPlugins}
showInstallButton
locale={locale}
cardContainerClassName='grid grid-cols-2 gap-2'
cardRender={cardRender}
emptyClassName='h-auto'
/> />
) )
} }
</div> </div>
</div>
) )
} }

@ -0,0 +1,81 @@
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import {
RiArrowDownSLine,
RiArrowRightUpLine,
} from '@remixicon/react'
import type {
ModelProvider,
} from './declarations'
import {
useMarketplaceAllPlugins,
} from './hooks'
import Divider from '@/app/components/base/divider'
import Loading from '@/app/components/base/loading'
import ProviderCard from '@/app/components/plugins/provider-card'
import List from '@/app/components/plugins/marketplace/list'
import type { Plugin } from '@/app/components/plugins/types'
import { MARKETPLACE_URL_PREFIX } from '@/config'
import cn from '@/utils/classnames'
import { getLocaleOnClient } from '@/i18n'
type InstallFromMarketplaceProps = {
providers: ModelProvider[]
searchText: string
}
const InstallFromMarketplace = ({
providers,
searchText,
}: InstallFromMarketplaceProps) => {
const { t } = useTranslation()
const [collapse, setCollapse] = useState(false)
const locale = getLocaleOnClient()
const {
plugins: allPlugins,
isLoading: isAllPluginsLoading,
} = useMarketplaceAllPlugins(providers, searchText)
const cardRender = useCallback((plugin: Plugin) => {
if (plugin.type === 'bundle')
return null
return <ProviderCard key={plugin.plugin_id} payload={plugin} />
}, [])
return (
<div className='mb-2'>
<Divider className='!mt-4 h-px' />
<div className='flex items-center justify-between'>
<div className='system-md-semibold flex cursor-pointer items-center gap-1 text-text-primary' onClick={() => setCollapse(!collapse)}>
<RiArrowDownSLine className={cn('h-4 w-4', collapse && '-rotate-90')} />
{t('common.modelProvider.installProvider')}
</div>
<div className='mb-2 flex items-center pt-2'>
<span className='system-sm-regular pr-1 text-text-tertiary'>{t('common.modelProvider.discoverMore')}</span>
<Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='system-sm-medium inline-flex items-center text-text-accent'>
{t('plugin.marketplace.difyMarketplace')}
<RiArrowRightUpLine className='h-4 w-4' />
</Link>
</div>
</div>
{!collapse && isAllPluginsLoading && <Loading type='area' />}
{
!isAllPluginsLoading && !collapse && (
<List
marketplaceCollections={[]}
marketplaceCollectionPluginsMap={{}}
plugins={allPlugins}
showInstallButton
locale={locale}
cardContainerClassName='grid grid-cols-2 gap-2'
cardRender={cardRender}
emptyClassName='h-auto'
/>
)
}
</div>
)
}
export default InstallFromMarketplace

@ -15,6 +15,7 @@ import { useLanguage } from '../hooks'
import PopupItem from './popup-item' import PopupItem from './popup-item'
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general' import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import { supportFunctionCall } from '@/utils/tool-call'
type PopupProps = { type PopupProps = {
defaultModel?: DefaultModel defaultModel?: DefaultModel
@ -50,7 +51,7 @@ const Popup: FC<PopupProps> = ({
return true return true
return scopeFeatures.every((feature) => { return scopeFeatures.every((feature) => {
if (feature === ModelFeatureEnum.toolCall) if (feature === ModelFeatureEnum.toolCall)
return modelItem.features?.some(featureItem => featureItem === ModelFeatureEnum.toolCall || featureItem === ModelFeatureEnum.multiToolCall) return supportFunctionCall(modelItem.features)
return modelItem.features?.some(featureItem => featureItem === feature) return modelItem.features?.some(featureItem => featureItem === feature)
}) })
}) })

@ -1,5 +1,6 @@
import { CONVERSATION_ID_INFO } from '../base/chat/constants' import { CONVERSATION_ID_INFO } from '../base/chat/constants'
import { fetchAccessToken } from '@/service/share' import { fetchAccessToken } from '@/service/share'
import { getProcessedSystemVariablesFromUrlParams } from '../base/chat/utils'
export const checkOrSetAccessToken = async () => { export const checkOrSetAccessToken = async () => {
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
@ -12,7 +13,8 @@ export const checkOrSetAccessToken = async () => {
} }
if (!accessTokenJson[sharedToken]) { if (!accessTokenJson[sharedToken]) {
const res = await fetchAccessToken(sharedToken) const sysUserId = (await getProcessedSystemVariablesFromUrlParams()).user_id
const res = await fetchAccessToken(sharedToken, sysUserId)
accessTokenJson[sharedToken] = res.access_token accessTokenJson[sharedToken] = res.access_token
localStorage.setItem('token', JSON.stringify(accessTokenJson)) localStorage.setItem('token', JSON.stringify(accessTokenJson))
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save