Commit 110b5b01 authored by jyong's avatar jyong

Merge branch 'main' into feat/dataset-notion-import

parents 1b3028d7 a66ef721
......@@ -2,7 +2,8 @@
<p align="center">
<a href="./README.md">English</a> |
<a href="./README_CN.md">简体中文</a> |
<a href="./README_JA.md">日本語</a>
<a href="./README_JA.md">日本語</a> |
<a href="./README_ES.md">Español</a>
</p>
[Website](https://dify.ai)[Docs](https://docs.dify.ai)[Twitter](https://twitter.com/dify_ai)[Discord](https://discord.gg/FngNHpbcY7)
......@@ -82,6 +83,32 @@ A: English and Chinese are currently supported, and you can contribute language
[![Star History Chart](https://api.star-history.com/svg?repos=langgenius/dify&type=Date)](https://star-history.com/#langgenius/dify&Date)
## Contributing
We welcome you to contribute to Dify to help make Dify better. We welcome contributions in various ways, submitting code, issues, new ideas, or sharing the interesting and useful AI applications you have created based on Dify. At the same time, we also welcome you to share Dify at different events, conferences, and social media.
### Submit a Pull Request
To ensure proper review, all code contributions, including from contributors with direct commit access, must be submitted as PR requests and approved by core developers before merging branches.
We welcome PRs from everyone! If you're willing to help out, you can learn more about how to contribute code to the project in the [Contribution Guide](CONTRIBUTING.md).
### Submit issues or ideas
You can submit your issues or ideas by adding issues to the Dify repository. If you encounter issues, please describe the steps you took to encounter the issue as much as possible so we can better discover it. If you have any new ideas for our product, we also welcome your feedback. Please share your insights as much as possible so we can get more feedback and further discussion in the community.
### Share your applications
We encourage all community members to share their AI applications built on Dify, which can be applied to different scenarios or different users. This will provide powerful inspiration for people who want to create AI capabilities! You can share your experience by [submitting an issue in the Dify-user-case repository](https://github.com/langgenius/dify-user-case/issues).
### Share Dify with others
We encourage community contributors to actively demonstrate different aspects of using Dify. You can talk or share any feature of using Dify at meetups and conferences, blogs or social media. We believe your unique sharing will be of great help to others! Mention @Dify.AI on Twitter and/or communicate on [Discord](https://discord.gg/FngNHpbcY7) so we can give pointers and tips and help you spread the word by promoting your content on the different Dify communication channels.
### Help others
You can also help people in need of help on Discord, GitHub issues or other social platforms, guide others to solve problems encountered during use and share usage experiences. This is also a great contribution! If you want to become a maintainer of the Dify community, please contact the official team via [Discord](https://discord.gg/FngNHpbcY7) or email us at support@dify.ai.
## Contact Us
If you have any questions, suggestions, or partnership inquiries, feel free to contact us through the following channels:
......@@ -92,12 +119,6 @@ If you have any questions, suggestions, or partnership inquiries, feel free to c
We're eager to assist you and together create more fun and useful AI applications!
## Contributing
To ensure proper review, all code contributions - including those from contributors with direct commit access - must be submitted via pull requests and approved by the core development team prior to being merged.
We welcome all pull requests! If you'd like to help, check out the [Contribution Guide](CONTRIBUTING.md) for more information on how to get started.
## Security
To protect your privacy, please avoid posting security issues on GitHub. Instead, send your questions to security@dify.ai and we will provide you with a more detailed answer.
......
......@@ -2,7 +2,8 @@
<p align="center">
<a href="./README.md">English</a> |
<a href="./README_CN.md">简体中文</a> |
<a href="./README_JA.md">日本語</a>
<a href="./README_JA.md">日本語</a> |
<a href="./README_ES.md">Español</a>
</p>
......@@ -83,6 +84,29 @@ A: 现已支持英文与中文,你可以为我们贡献语言包。
[![Star History Chart](https://api.star-history.com/svg?repos=langgenius/dify&type=Date)](https://star-history.com/#langgenius/dify&Date)
## 贡献
我们欢迎你为 Dify 作出贡献帮助 Dify 变得更好。我们欢迎各种方式的贡献,提交代码、问题、新想法、或者分享你基于 Dify 创建出的各种有趣有用的 AI 应用。同时,我们也欢迎你在不同的活动、研讨会、社交媒体上分享 Dify。
### 贡献代码
为了确保正确审查,所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交 PR 请求并在合并分支之前得到核心开发人员的批准。
我们欢迎所有人提交 PR!如果您愿意提供帮助,可以在 [贡献指南](CONTRIBUTING_CN.md) 中了解有关如何为项目做出代码贡献的更多信息。
### 提交问题或想法
你可以通过 Dify 代码仓库新增 issues 来提交你的问题或想法。如遇到问题,请尽可能描述你遇到问题的操作步骤,以便我们更好地发现它。如果你对我们的产品有任何新想法,也欢迎向我们反馈,请尽可能多地分享你的见解,以便我们在社区中获得更多反馈和进一步讨论。
### 分享你的应用
我们鼓励所有社区成员分享他们基于 Dify 创造出的 AI 应用,它们可以是应用于不同情景或不同用户,这将有助于为希望基于 AI 能力创造的人们提供强大灵感!你可以通过 [Dify-user-case 仓库项目提交 issue](https://github.com/langgenius/dify-user-case) 来分享你的应用案例。
### 向别人分享 Dify
我们鼓励社区贡献者们积极展示你使用 Dify 的不同角度。你可以通过线下研讨会、博客或社交媒体上谈论或分享你使用 Dify 的任意功能,相信你独特的使用分享会给别人带来非常大的帮助!如果你需要任何指导帮助,欢迎联系我们 support@dify.ai ,你也可以在 twitter @Dify.AI 或在 [Discord 社区](https://discord.gg/FngNHpbcY7)交流来帮助你传播信息。
### 帮助别人
你还可以在 Discord、GitHub issues或其他社交平台上帮助需要帮助的人,指导别人解决使用过程中遇到的问题和分享使用经验。这也是个非常了不起的贡献!如果你希望成为 Dify 社区的维护者,请通过[Discord 社区](https://discord.gg/FngNHpbcY7) 联系官方团队或邮件联系我们 support@dify.ai.
## 联系我们
如果您有任何问题、建议或合作意向,欢迎通过以下方式联系我们:
......@@ -91,12 +115,6 @@ A: 现已支持英文与中文,你可以为我们贡献语言包。
- 在我们的 [Discord 社区](https://discord.gg/FngNHpbcY7) 上加入讨论
- 发送邮件至 hello@dify.ai
## 贡献代码
为了确保正确审查,所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交 PR 请求并在合并分支之前得到核心开发人员的批准。
我们欢迎所有人提交 PR!如果您愿意提供帮助,可以在 [贡献指南](CONTRIBUTING_CN.md) 中了解有关如何为项目做出贡献的更多信息。
## 安全
为了保护您的隐私,请避免在 GitHub 上发布安全问题。发送问题至 security@dify.ai,我们将为您做更细致的解答。
......
![](./images/describe-en.png)
<p align="center">
<a href="./README.md">English</a> |
<a href="./README_CN.md">简体中文</a> |
<a href="./README_JA.md">日本語</a> |
<a href="./README_ES.md">Español</a>
</p>
[Sitio web](https://dify.ai)[Documentación](https://docs.dify.ai)[Twitter](https://twitter.com/dify_ai)[Discord](https://discord.gg/FngNHpbcY7)
**Dify** es una plataforma LLMOps fácil de usar diseñada para capacitar a más personas para que creen aplicaciones sostenibles basadas en IA. Con orquestación visual para varios tipos de aplicaciones, Dify ofrece aplicaciones listas para usar que también pueden funcionar como APIs de Backend-as-a-Service. Unifica tu proceso de desarrollo con una API para la integración de complementos y conjuntos de datos, y agiliza tus operaciones utilizando una interfaz única para la ingeniería de indicaciones, análisis visual y mejora continua.
Las aplicaciones creadas con Dify incluyen:
- Sitios web listos para usar que admiten el modo de formulario y el modo de conversación por chat.
- Una API única que abarca capacidades de complementos, mejora de contexto y más, lo que te ahorra esfuerzo de programación en el backend.
- Análisis visual de datos, revisión de registros y anotación para aplicaciones.
Dify es compatible con Langchain, lo que significa que gradualmente admitiremos múltiples LLMs, actualmente compatibles con:
- GPT 3 (text-davinci-003)
- GPT 3.5 Turbo (ChatGPT)
- GPT-4
## Usar servicios en la nube
Visita [Dify.ai](https://dify.ai)
## Instalar la Edición Comunitaria
### Requisitos del sistema
Antes de instalar Dify, asegúrate de que tu máquina cumple con los siguientes requisitos mínimos del sistema:
- CPU >= 1 Core
- RAM >= 4GB
### Inicio rápido
La forma más sencilla de iniciar el servidor de Dify es ejecutar nuestro archivo [docker-compose.yml](docker/docker-compose.yaml). Antes de ejecutar el comando de instalación, asegúrate de que [Docker](https://docs.docker.com/get-docker/) y [Docker Compose](https://docs.docker.com/compose/install/) estén instalados en tu máquina:
```bash
cd docker
docker compose up -d
```
Después de ejecutarlo, puedes acceder al panel de control de Dify en tu navegador desde [http://localhost/install](http://localhost/install) y comenzar el proceso de instalación de inicialización.
### Configuración
Si necesitas personalizar la configuración, consulta los comentarios en nuestro archivo [docker-compose.yml](docker/docker-compose.yaml) y configura manualmente la configuración del entorno. Después de realizar los cambios, ejecuta nuevamente 'docker-compose up -d'.
## Hoja de ruta
Funciones en desarrollo:
- **Conjuntos de datos**, admitiendo más conjuntos de datos, por ejemplo, sincronización de contenido desde Notion o páginas web.
Admitiremos más conjuntos de datos, incluidos texto, páginas web e incluso contenido de Notion. Los usuarios pueden construir aplicaciones de IA basadas en sus propias fuentes de datos
- **Complementos**, introduciendo complementos estándar de ChatGPT para aplicaciones, o utilizando complementos producidos por Dify.
Lanzaremos complementos que cumplan con el estándar de ChatGPT, o nuestros propios complementos de Dify para habilitar más capacidades en las aplicaciones.
- **Modelos de código abierto**, por ejemplo, adoptar Llama como proveedor de modelos o para un ajuste adicional.
Trabajaremos con excelentes modelos de código abierto como Llama, proporcionándolos como opciones de modelos en nuestra plataforma o utilizándolos para un ajuste adicional.
## Preguntas y respuestas
**P: ¿Qué puedo hacer con Dify?**
R: Dify es una herramienta de desarrollo y operaciones de LLM, simple pero poderosa. Puedes usarla para construir aplicaciones de calidad comercial y asistentes personales. Si deseas desarrollar tus propias aplicaciones, LangDifyGenius puede ahorrarte trabajo en el backend al integrar con OpenAI y ofrecer capacidades de operaciones visuales, lo que te permite mejorar y entrenar continuamente tu modelo GPT.
**P: ¿Cómo uso Dify para "entrenar" mi propio modelo?**
R: Una aplicación valiosa consta de Ingeniería de indicaciones, mejora de contexto y ajuste fino. Hemos creado un enfoque de programación híbrida que combina las indicaciones con lenguajes de programación (similar a un motor de plantillas), lo que facilita la incorporación de texto largo o la captura de subtítulos de un video de YouTube ingresado por el usuario, todo lo cual se enviará como contexto para que los LLM lo procesen. Damos gran importancia a la operabilidad de la aplicación, con los datos generados por los usuarios durante el uso de la aplicación disponibles para análisis, anotación y entrenamiento continuo. Sin las herramientas adecuadas, estos pasos pueden llevar mucho tiempo.
**P: ¿Qué necesito preparar si quiero crear mi propia aplicación?**
R: Suponemos que ya tienes una clave de API de OpenAI; si no la tienes, por favor regístrate. ¡Si ya tienes contenido que pueda servir como contexto de entrenamiento, eso es genial!
**P: ¿Qué idiomas de interfaz están disponibles?**
R: Actualmente se admiten inglés y chino, y puedes contribuir con paquetes de idiomas.
## Historial de estrellas
[![Gráfico de historial de estrellas](https://api.star-history.com/svg?repos=langgenius/dify&type=Date)](https://star-history.com/#langgenius/dify&Date)
## Contáctanos
Si tienes alguna pregunta, sugerencia o consulta sobre asociación, no dudes en contactarnos a través de los siguientes canales:
- Presentar un problema o una solicitud de extracción en nuestro repositorio de GitHub.
- Únete a la discusión en nuestra comunidad de [Discord](https://discord.gg/FngNHpbcY7).
- Envía un correo electrónico a hello@dify.ai.
¡Estamos ansiosos por ayudarte y crear juntos aplicaciones de IA más divertidas y útiles!
## Contribuciones
Para garantizar una revisión adecuada, todas las contribuciones de código, incluidas las de los colaboradores con acceso directo a los compromisos, deben enviarse mediante solicitudes de extracción y ser aprobadas por el equipo principal de
desarrollo antes de fusionarse.
¡Agradecemos todas las solicitudes de extracción! Si deseas ayudar, consulta la [Guía de Contribución](CONTRIBUTING.md) para obtener más información sobre cómo comenzar.
## Seguridad
Para proteger tu privacidad, evita publicar problemas de seguridad en GitHub. En su lugar, envía tus preguntas a security@dify.ai y te proporcionaremos una respuesta más detallada.
## Citación
Este software utiliza el siguiente software de código abierto:
- Chase, H. (2022). LangChain [Software de computadora]. https://github.com/hwchase17/langchain
- Liu, J. (2022). LlamaIndex [Software de computadora]. doi: 10.5281/zenodo.1234.
Para obtener más información, consulta el sitio web oficial o el texto de la licencia del software correspondiente.
## Licencia
Este repositorio está disponible bajo la [Licencia de código abierto de Dify](LICENSE).
......@@ -2,7 +2,8 @@
<p align="center">
<a href="./README.md">English</a> |
<a href="./README_CN.md">简体中文</a> |
<a href="./README_JA.md">日本語</a>
<a href="./README_JA.md">日本語</a> |
<a href="./README_ES.md">Español</a>
</p>
[Web サイト](https://dify.ai)[ドキュメント](https://docs.dify.ai)[Twitter](https://twitter.com/dify_ai)[Discord](https://discord.gg/FngNHpbcY7)
......
......@@ -73,6 +73,7 @@ VECTOR_STORE=weaviate
WEAVIATE_ENDPOINT=http://localhost:8080
WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
WEAVIATE_GRPC_ENABLED=false
WEAVIATE_BATCH_SIZE=100
# Qdrant configuration, use `path:` prefix for local mode or `https://your-qdrant-cluster-url.qdrant.io` for remote mode
QDRANT_URL=path:storage/qdrant
......
......@@ -43,6 +43,7 @@ DEFAULTS = {
'SENTRY_TRACES_SAMPLE_RATE': 1.0,
'SENTRY_PROFILES_SAMPLE_RATE': 1.0,
'WEAVIATE_GRPC_ENABLED': 'True',
'WEAVIATE_BATCH_SIZE': 100,
'CELERY_BACKEND': 'database',
'PDF_PREVIEW': 'True',
'LOG_LEVEL': 'INFO',
......@@ -78,7 +79,7 @@ class Config:
self.CONSOLE_URL = get_env('CONSOLE_URL')
self.API_URL = get_env('API_URL')
self.APP_URL = get_env('APP_URL')
self.CURRENT_VERSION = "0.3.1"
self.CURRENT_VERSION = "0.3.3"
self.COMMIT_SHA = get_env('COMMIT_SHA')
self.EDITION = "SELF_HOSTED"
self.DEPLOY_ENV = get_env('DEPLOY_ENV')
......@@ -138,6 +139,7 @@ class Config:
self.WEAVIATE_ENDPOINT = get_env('WEAVIATE_ENDPOINT')
self.WEAVIATE_API_KEY = get_env('WEAVIATE_API_KEY')
self.WEAVIATE_GRPC_ENABLED = get_bool_env('WEAVIATE_GRPC_ENABLED')
self.WEAVIATE_BATCH_SIZE = int(get_env('WEAVIATE_BATCH_SIZE'))
# qdrant settings
self.QDRANT_URL = get_env('QDRANT_URL')
......
......@@ -8,6 +8,7 @@ from werkzeug.exceptions import NotFound, Unauthorized
from controllers.console import api
from controllers.console.wraps import only_edition_cloud
from extensions.ext_database import db
from libs.helper import supported_language
from models.model import RecommendedApp, App, InstalledApp
......@@ -47,8 +48,7 @@ class InsertExploreAppListApi(Resource):
parser.add_argument('desc', type=str, location='json')
parser.add_argument('copyright', type=str, location='json')
parser.add_argument('privacy_policy', type=str, location='json')
parser.add_argument('language', type=str, required=True, nullable=False, choices=['en-US', 'zh-Hans'],
location='json')
parser.add_argument('language', type=supported_language, required=True, nullable=False, location='json')
parser.add_argument('category', type=str, required=True, nullable=False, location='json')
parser.add_argument('position', type=int, required=True, nullable=False, location='json')
args = parser.parse_args()
......
......@@ -215,7 +215,11 @@ class AppTemplateApi(Resource):
account = current_user
interface_language = account.interface_language
return {'data': demo_model_templates.get(interface_language)}
templates = demo_model_templates.get(interface_language)
if not templates:
templates = demo_model_templates.get('en-US')
return {'data': templates}
class AppApi(Resource):
......
......@@ -75,7 +75,12 @@ class LLMCallbackHandler(BaseCallbackHandler):
self.conversation_message_task.save_message(self.llm_message)
def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
self.conversation_message_task.append_message_text(token)
try:
self.conversation_message_task.append_message_text(token)
except ConversationTaskStoppedException as ex:
self.on_llm_error(error=ex)
raise ex
self.llm_message.completion += token
def on_llm_error(
......
......@@ -4,6 +4,7 @@ models = {
'gpt-4': 'openai', # 8,192 tokens
'gpt-4-32k': 'openai', # 32,768 tokens
'gpt-3.5-turbo': 'openai', # 4,096 tokens
'gpt-3.5-turbo-16k': 'openai', # 16384 tokens
'text-davinci-003': 'openai', # 4,097 tokens
'text-davinci-002': 'openai', # 4,097 tokens
'text-curie-001': 'openai', # 2,049 tokens
......@@ -16,6 +17,7 @@ max_context_token_length = {
'gpt-4': 8192,
'gpt-4-32k': 32768,
'gpt-3.5-turbo': 4096,
'gpt-3.5-turbo-16k': 16384,
'text-davinci-003': 4097,
'text-davinci-002': 4097,
'text-curie-001': 2049,
......@@ -29,11 +31,13 @@ models_by_mode = {
'gpt-4', # 8,192 tokens
'gpt-4-32k', # 32,768 tokens
'gpt-3.5-turbo', # 4,096 tokens
'gpt-3.5-turbo-16k', # 16,384 tokens
],
'completion': [
'gpt-4', # 8,192 tokens
'gpt-4-32k', # 32,768 tokens
'gpt-3.5-turbo', # 4,096 tokens
'gpt-3.5-turbo-16k', # 16,384 tokens
'text-davinci-003', # 4,097 tokens
'text-davinci-002' # 4,097 tokens
'text-curie-001', # 2,049 tokens
......@@ -57,9 +61,13 @@ model_prices = {
'completion': Decimal('0.12')
},
'gpt-3.5-turbo': {
'prompt': Decimal('0.002'),
'prompt': Decimal('0.0015'),
'completion': Decimal('0.002')
},
'gpt-3.5-turbo-16k': {
'prompt': Decimal('0.003'),
'completion': Decimal('0.004')
},
'text-davinci-003': {
'prompt': Decimal('0.02'),
'completion': Decimal('0.02')
......@@ -77,7 +85,7 @@ model_prices = {
'completion': Decimal('0.0004')
},
'text-embedding-ada-002': {
'usage': Decimal('0.0004'),
'usage': Decimal('0.0001'),
}
}
......
......@@ -25,7 +25,7 @@ class XLSXParser(BaseParser):
if all(v is None for v in row):
continue
if keys == []:
keys = row
keys = list(map(str, row))
else:
data.append(json.dumps(dict(zip(keys, row)), ensure_ascii=False))
return data
data.append(json.dumps(dict(zip(keys, list(map(str, row)))), ensure_ascii=False))
return '\n\n'.join(data)
......@@ -95,7 +95,8 @@ class AzureProvider(BaseProvider):
if not models:
raise ValidateFailedError("Please add deployments for 'text-davinci-003', "
"'gpt-3.5-turbo', 'text-embedding-ada-002'.")
"'gpt-3.5-turbo', 'text-embedding-ada-002' (required) "
"and 'gpt-4', 'gpt-35-turbo-16k' (optional).")
fixed_model_ids = [
'text-davinci-003',
......
......@@ -27,7 +27,8 @@ class VectorStore:
self._client = WeaviateVectorStoreClient(
endpoint=app.config['WEAVIATE_ENDPOINT'],
api_key=app.config['WEAVIATE_API_KEY'],
grpc_enabled=app.config['WEAVIATE_GRPC_ENABLED']
grpc_enabled=app.config['WEAVIATE_GRPC_ENABLED'],
batch_size=app.config['WEAVIATE_BATCH_SIZE']
)
elif self._vector_store == 'qdrant':
self._client = QdrantVectorStoreClient(
......
......@@ -18,21 +18,33 @@ from llama_index.readers.weaviate.utils import (
class WeaviateVectorStoreClient(BaseVectorStoreClient):
def __init__(self, endpoint: str, api_key: str, grpc_enabled: bool):
self._client = self.init_from_config(endpoint, api_key, grpc_enabled)
def __init__(self, endpoint: str, api_key: str, grpc_enabled: bool, batch_size: int):
self._client = self.init_from_config(endpoint, api_key, grpc_enabled, batch_size)
def init_from_config(self, endpoint: str, api_key: str, grpc_enabled: bool):
def init_from_config(self, endpoint: str, api_key: str, grpc_enabled: bool, batch_size: int):
auth_config = weaviate.auth.AuthApiKey(api_key=api_key)
weaviate.connect.connection.has_grpc = grpc_enabled
return weaviate.Client(
client = weaviate.Client(
url=endpoint,
auth_client_secret=auth_config,
timeout_config=(5, 60),
startup_period=None
)
client.batch.configure(
# `batch_size` takes an `int` value to enable auto-batching
# (`None` is used for manual batching)
batch_size=batch_size,
# dynamically update the `batch_size` based on import speed
dynamic=True,
# `timeout_retries` takes an `int` value to retry on time outs
timeout_retries=3,
)
return client
def get_index(self, service_context: ServiceContext, config: dict) -> GPTVectorStoreIndex:
index_struct = WeaviateIndexDict()
......
......@@ -2,7 +2,7 @@ version: '3.1'
services:
# API service
api:
image: langgenius/dify-api:0.3.1
image: langgenius/dify-api:0.3.3
restart: always
environment:
# Startup mode, 'api' starts the API server.
......@@ -110,7 +110,7 @@ services:
# worker service
# The Celery worker for processing the queue.
worker:
image: langgenius/dify-api:0.3.1
image: langgenius/dify-api:0.3.3
restart: always
environment:
# Startup mode, 'worker' starts the Celery worker for processing the queue.
......@@ -156,7 +156,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:0.3.1
image: langgenius/dify-web:0.3.3
restart: always
environment:
EDITION: SELF_HOSTED
......@@ -168,6 +168,8 @@ services:
# console or api domain.
# example: http://udify.app
APP_URL: ''
# The DSN for Sentry error reporting. If not set, Sentry error reporting will be disabled.
SENTRY_DSN: ''
# The postgres database.
db:
......
......@@ -13,5 +13,3 @@ NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
# SENTRY
NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_SENTRY_ORG=
NEXT_PUBLIC_SENTRY_PROJECT=
\ No newline at end of file
......@@ -3,6 +3,7 @@ import type { FC } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { usePathname, useRouter, useSelectedLayoutSegments } from 'next/navigation'
import useSWR, { SWRConfig } from 'swr'
import * as Sentry from '@sentry/react'
import Header from '../components/header'
import { fetchAppList } from '@/service/apps'
import { fetchDatasets } from '@/service/datasets'
......@@ -12,11 +13,29 @@ import { AppContextProvider } from '@/context/app-context'
import DatasetsContext from '@/context/datasets-context'
import type { LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
const isDevelopment = process.env.NODE_ENV === 'development'
export type ICommonLayoutProps = {
children: React.ReactNode
}
const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => {
useEffect(() => {
const SENTRY_DSN = document?.body?.getAttribute('data-public-sentry-dsn')
if (!isDevelopment && SENTRY_DSN) {
Sentry.init({
dsn: SENTRY_DSN,
integrations: [
new Sentry.BrowserTracing({
}),
new Sentry.Replay(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
})
}
}, [])
const router = useRouter()
const pathname = usePathname()
const segments = useSelectedLayoutSegments()
......
......@@ -52,7 +52,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
mutateTemplates()
setIsWithTemplate(false)
}
}, [show])
}, [mutateTemplates, show])
const isCreatingRef = useRef(false)
const onCreate: MouseEventHandler = useCallback(async () => {
......@@ -97,7 +97,6 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
return <>
{showEmojiPicker && <EmojiPicker
onSelect={(icon, icon_background) => {
console.log(icon, icon_background)
setEmoji({ icon, icon_background })
setShowEmojiPicker(false)
}}
......
......@@ -140,7 +140,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
<div className='text-xs text-gray-500 mt-2'>{t('common.datasetMenus.emptyTip')}</div>
<a
className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
href={`https://docs.dify.ai/${locale === 'en' ? '' : 'v/zh-hans'}/application/prompt-engineering`}
href={`https://docs.dify.ai/${locale === 'zh-Hans' ? 'v/zh-hans' : ''}/application/prompt-engineering`}
target='_blank'
>
<BookOpenIcon className='mr-1' />
......
......@@ -53,6 +53,7 @@ export type IChatProps = {
displayScene?: DisplayScene
useCurrentUserAvatar?: boolean
isResponsing?: boolean
canStopResponsing?: boolean
abortResponsing?: () => void
controlClearQuery?: number
controlFocus?: number
......@@ -412,6 +413,7 @@ const Chat: FC<IChatProps> = ({
displayScene,
useCurrentUserAvatar,
isResponsing,
canStopResponsing,
abortResponsing,
controlClearQuery,
controlFocus,
......@@ -508,7 +510,7 @@ const Chat: FC<IChatProps> = ({
{
!isHideSendInput && (
<div className={cn(!feedbackDisabled && '!left-3.5 !right-3.5', 'absolute z-10 bottom-0 left-0 right-0')}>
{isResponsing && (
{(isResponsing && canStopResponsing) && (
<div className='flex justify-center mb-4'>
<Button className='flex items-center space-x-1 bg-white' onClick={() => abortResponsing?.()}>
{stopIcon}
......
......@@ -26,8 +26,10 @@ export type IConifgModelProps = {
const options = [
{ id: 'gpt-3.5-turbo', name: 'gpt-3.5-turbo', type: AppType.chat },
{ id: 'gpt-3.5-turbo-16k', name: 'gpt-3.5-turbo-16k', type: AppType.chat },
{ id: 'gpt-4', name: 'gpt-4', type: AppType.chat }, // 8k version
{ id: 'gpt-3.5-turbo', name: 'gpt-3.5-turbo', type: AppType.completion },
{ id: 'gpt-3.5-turbo-16k', name: 'gpt-3.5-turbo-16k', type: AppType.completion },
{ id: 'text-davinci-003', name: 'text-davinci-003', type: AppType.completion },
{ id: 'gpt-4', name: 'gpt-4', type: AppType.completion }, // 8k version
]
......
......@@ -16,7 +16,7 @@ import type { IChatItem } from '@/app/components/app/chat'
import Chat from '@/app/components/app/chat'
import ConfigContext from '@/context/debug-configuration'
import { ToastContext } from '@/app/components/base/toast'
import { fetchConvesationMessages, fetchSuggestedQuestions, sendChatMessage, sendCompletionMessage } from '@/service/debug'
import { fetchConvesationMessages, fetchSuggestedQuestions, sendChatMessage, sendCompletionMessage, stopChatMessageResponding } from '@/service/debug'
import Button from '@/app/components/base/button'
import type { ModelConfig as BackendModelConfig } from '@/types/app'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
......@@ -75,6 +75,8 @@ const Debug: FC<IDebug> = ({
const [abortController, setAbortController] = useState<AbortController | null>(null)
const [isShowFormattingChangeConfirm, setIsShowFormattingChangeConfirm] = useState(false)
const [isShowSuggestion, setIsShowSuggestion] = useState(false)
const [messageTaskId, setMessageTaskId] = useState('')
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
useEffect(() => {
if (formattingChanged && chatList.some(item => !item.isAnswer))
......@@ -83,7 +85,7 @@ const Debug: FC<IDebug> = ({
setFormattingChanged(false)
}, [formattingChanged])
const clearConversation = () => {
const clearConversation = async () => {
setConversationId(null)
abortController?.abort()
setResponsingFalse()
......@@ -202,18 +204,20 @@ const Debug: FC<IDebug> = ({
let _newConversationId: null | string = null
setHasStopResponded(false)
setResponsingTrue()
setIsShowSuggestion(false)
sendChatMessage(appId, data, {
getAbortController: (abortController) => {
setAbortController(abortController)
},
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId }: any) => {
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
responseItem.content = responseItem.content + message
if (isFirstMessage && newConversationId) {
setConversationId(newConversationId)
_newConversationId = newConversationId
}
setMessageTaskId(taskId)
if (messageId)
responseItem.id = messageId
......@@ -253,7 +257,7 @@ const Debug: FC<IDebug> = ({
}
}))
}
if (suggestedQuestionsAfterAnswerConfig.enabled) {
if (suggestedQuestionsAfterAnswerConfig.enabled && !getHasStopResponded()) {
const { data }: any = await fetchSuggestedQuestions(appId, responseItem.id)
setSuggestQuestions(data)
setIsShowSuggestion(true)
......@@ -375,8 +379,10 @@ const Debug: FC<IDebug> = ({
feedbackDisabled
useCurrentUserAvatar
isResponsing={isResponsing}
abortResponsing={() => {
abortController?.abort()
canStopResponsing={!!messageTaskId}
abortResponsing={async () => {
await stopChatMessageResponding(appId, messageTaskId)
setHasStopResponded(true)
setResponsingFalse()
}}
isShowSuggestion={doShowSuggestion}
......@@ -395,6 +401,7 @@ const Debug: FC<IDebug> = ({
className="mt-2"
content={completionRes}
isLoading={!completionRes && isResponsing}
isInstalledApp={false}
/>
)}
</div>
......
'use client'
import type { FC } from 'react'
import React from 'react'
import { AppMode } from '@/types/app'
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import type { AppMode } from '@/types/app'
import I18n from '@/context/i18n'
import Button from '@/app/components/base/button'
import Modal from '@/app/components/base/modal'
......@@ -23,8 +23,6 @@ const StepNum: FC<{ children: React.ReactNode }> = ({ children }) =>
{children}
</div>
const GithubIcon = ({ className }: { className: string }) => {
return (
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
......@@ -73,7 +71,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({
<div className='text-gray-500 text-xs mt-1 mb-2'>{t(`${prefixCustomize}.way1.step2Tip`)}</div>
<pre className='box-border py-3 px-4 bg-gray-100 text-xs font-medium rounded-lg select-text'>
export const APP_ID = '{appId}'<br />
export const API_KEY = {`'<Web API Key From Dify>'`}
export const API_KEY = {'\'<Web API Key From Dify>\''}
</pre>
</div>
</div>
......
......@@ -7,11 +7,11 @@ import { Trans, useTranslation } from 'react-i18next'
import s from './style.module.css'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Switch from '@/app/components/base/switch'
import AppIcon from '@/app/components/base/app-icon'
import { SimpleSelect } from '@/app/components/base/select'
import type { AppDetailResponse } from '@/models/app'
import type { Language } from '@/types/app'
import EmojiPicker from '@/app/components/base/emoji-picker'
export type ISettingsModalProps = {
appInfo: AppDetailResponse
......@@ -42,11 +42,14 @@ const SettingsModal: FC<ISettingsModalProps> = ({
onSave,
}) => {
const [isShowMore, setIsShowMore] = useState(false)
const { title, description, copyright, privacy_policy, default_language } = appInfo.site
const { title, description, copyright, privacy_policy, default_language, icon, icon_background } = appInfo.site
const [inputInfo, setInputInfo] = useState({ title, desc: description, copyright, privacyPolicy: privacy_policy })
const [language, setLanguage] = useState(default_language)
const [saveLoading, setSaveLoading] = useState(false)
const { t } = useTranslation()
// Emoji Picker
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
const [emoji, setEmoji] = useState({ icon, icon_background })
const onHide = () => {
onClose()
......@@ -64,6 +67,8 @@ const SettingsModal: FC<ISettingsModalProps> = ({
prompt_public: false,
copyright: inputInfo.copyright,
privacy_policy: inputInfo.privacyPolicy,
icon: emoji.icon,
icon_background: emoji.icon_background,
}
await onSave(params)
setSaveLoading(false)
......@@ -77,69 +82,88 @@ const SettingsModal: FC<ISettingsModalProps> = ({
}
return (
<Modal
title={t(`${prefixSettings}.title`)}
isShow={isShow}
onClose={onHide}
className={`${s.settingsModal}`}
>
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.webName`)}</div>
<div className='flex mt-2'>
<AppIcon className='!mr-3 self-center' />
<input className={`flex-grow rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
value={inputInfo.title}
onChange={onChange('title')} />
</div>
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.webDesc`)}</div>
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.webDescTip`)}</p>
<textarea
rows={3}
className={`mt-2 pt-2 pb-2 px-3 rounded-lg bg-gray-100 w-full ${s.settingsTip} text-gray-900`}
value={inputInfo.desc}
onChange={onChange('desc')}
placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string}
/>
<div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div>
<SimpleSelect
items={Object.keys(LANGUAGE_MAP).map(lang => ({ name: LANGUAGE_MAP[lang as Language], value: lang }))}
defaultValue={language}
onSelect={item => setLanguage(item.value as Language)}
/>
{!isShowMore && <div className='w-full cursor-pointer mt-8' onClick={() => setIsShowMore(true)}>
<div className='flex justify-between'>
<div className={`font-medium ${s.settingTitle} flex-grow text-gray-900`}>{t(`${prefixSettings}.more.entry`)}</div>
<div className='flex-shrink-0 w-4 h-4 text-gray-500'>
<ChevronRightIcon />
</div>
<>
{showEmojiPicker && <EmojiPicker
onSelect={(icon, icon_background) => {
console.log(icon, icon_background)
setEmoji({ icon, icon_background })
setShowEmojiPicker(false)
}}
onClose={() => {
setEmoji({ icon: '🤖', icon_background: '#FFEAD5' })
setShowEmojiPicker(false)
}}
/>}
<Modal
title={t(`${prefixSettings}.title`)}
isShow={isShow}
onClose={onHide}
className={`${s.settingsModal}`}
>
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.webName`)}</div>
<div className='flex mt-2'>
<AppIcon size='large'
onClick={() => { setShowEmojiPicker(true) }}
className='cursor-pointer !mr-3 self-center'
icon={emoji.icon}
background={emoji.icon_background}
/>
<input className={`flex-grow rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
value={inputInfo.title}
onChange={onChange('title')} />
</div>
<p className={`mt-1 ${s.policy} text-gray-500`}>{t(`${prefixSettings}.more.copyright`)} & {t(`${prefixSettings}.more.privacyPolicy`)}</p>
</div>}
{isShowMore && <>
<hr className='w-full mt-6' />
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.copyright`)}</div>
<input className={`w-full mt-2 rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
value={inputInfo.copyright}
onChange={onChange('copyright')}
placeholder={t(`${prefixSettings}.more.copyRightPlaceholder`) as string}
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.webDesc`)}</div>
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.webDescTip`)}</p>
<textarea
rows={3}
className={`mt-2 pt-2 pb-2 px-3 rounded-lg bg-gray-100 w-full ${s.settingsTip} text-gray-900`}
value={inputInfo.desc}
onChange={onChange('desc')}
placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string}
/>
<div className={`mt-8 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.privacyPolicy`)}</div>
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>
<Trans
i18nKey={`${prefixSettings}.more.privacyPolicyTip`}
components={{ privacyPolicyLink: <Link href={'https://langgenius.ai/privacy-policy'} target='_blank' className='text-primary-600' /> }}
/>
</p>
<input className={`w-full mt-2 rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
value={inputInfo.privacyPolicy}
onChange={onChange('privacyPolicy')}
placeholder={t(`${prefixSettings}.more.privacyPolicyPlaceholder`) as string}
<div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div>
<SimpleSelect
items={Object.keys(LANGUAGE_MAP).map(lang => ({ name: LANGUAGE_MAP[lang as Language], value: lang }))}
defaultValue={language}
onSelect={item => setLanguage(item.value as Language)}
/>
</>}
<div className='mt-10 flex justify-end'>
<Button className='mr-2 flex-shrink-0' onClick={onHide}>{t('common.operation.cancel')}</Button>
<Button type='primary' className='flex-shrink-0' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button>
</div>
</Modal >
{!isShowMore && <div className='w-full cursor-pointer mt-8' onClick={() => setIsShowMore(true)}>
<div className='flex justify-between'>
<div className={`font-medium ${s.settingTitle} flex-grow text-gray-900`}>{t(`${prefixSettings}.more.entry`)}</div>
<div className='flex-shrink-0 w-4 h-4 text-gray-500'>
<ChevronRightIcon />
</div>
</div>
<p className={`mt-1 ${s.policy} text-gray-500`}>{t(`${prefixSettings}.more.copyright`)} & {t(`${prefixSettings}.more.privacyPolicy`)}</p>
</div>}
{isShowMore && <>
<hr className='w-full mt-6' />
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.copyright`)}</div>
<input className={`w-full mt-2 rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
value={inputInfo.copyright}
onChange={onChange('copyright')}
placeholder={t(`${prefixSettings}.more.copyRightPlaceholder`) as string}
/>
<div className={`mt-8 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.privacyPolicy`)}</div>
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>
<Trans
i18nKey={`${prefixSettings}.more.privacyPolicyTip`}
components={{ privacyPolicyLink: <Link href={'https://langgenius.ai/privacy-policy'} target='_blank' className='text-primary-600' /> }}
/>
</p>
<input className={`w-full mt-2 rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
value={inputInfo.privacyPolicy}
onChange={onChange('privacyPolicy')}
placeholder={t(`${prefixSettings}.more.privacyPolicyPlaceholder`) as string}
/>
</>}
<div className='mt-10 flex justify-end'>
<Button className='mr-2 flex-shrink-0' onClick={onHide}>{t('common.operation.cancel')}</Button>
<Button type='primary' className='flex-shrink-0' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button>
</div>
</Modal >
</>
)
}
export default React.memo(SettingsModal)
import React from 'react'
import './style.css'
interface ILoadingProps {
type ILoadingProps = {
type?: 'area' | 'app'
}
const Loading = (
{ type = 'area' }: ILoadingProps = { type: 'area' }
{ type = 'area' }: ILoadingProps = { type: 'area' },
) => {
return (
<div className={`flex w-full justify-center items-center ${type === 'app' ? 'h-full' : ''}`}>
......
.spin-animation path {
animation: custom 2s linear infinite;
animation: custom 1s linear infinite;
}
@keyframes custom {
......@@ -29,13 +29,13 @@
}
.spin-animation path:nth-child(2) {
animation-delay: 0.5s;
animation-delay: 0.25s;
}
.spin-animation path:nth-child(3) {
animation-delay: 1s;
animation-delay: 0.5s;
}
.spin-animation path:nth-child(4) {
animation-delay: 1.5s;
}
\ No newline at end of file
animation-delay: 1s;
}
......@@ -83,7 +83,8 @@ const Header: FC<IHeaderProps> = ({
'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14',
s.header,
isBordered ? 'border-b border-gray-200' : '',
)}>
)}
>
<div className={classNames(
s[`header-${langeniusVersionInfo.current_env}`],
'flex flex-1 items-center justify-between px-4',
......
......@@ -14,7 +14,7 @@ import { ToastContext } from '@/app/components/base/toast'
import Sidebar from '@/app/components/share/chat/sidebar'
import ConfigSence from '@/app/components/share/chat/config-scence'
import Header from '@/app/components/share/header'
import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, sendChatMessage, updateFeedback } from '@/service/share'
import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, sendChatMessage, stopChatMessageResponding, updateFeedback } from '@/service/share'
import type { ConversationItem, SiteInfo } from '@/models/share'
import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
import type { Feedbacktype, IChatItem } from '@/app/components/app/chat'
......@@ -191,7 +191,7 @@ const Main: FC<IMainProps> = ({
}, [chatList, currConversationId])
// user can not edit inputs if user had send message
const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation
const createNewChat = () => {
const createNewChat = async () => {
// if new chat is already exist, do not create new chat
abortController?.abort()
setResponsingFalse()
......@@ -332,6 +332,9 @@ const Main: FC<IMainProps> = ({
const [isShowSuggestion, setIsShowSuggestion] = useState(false)
const doShowSuggestion = isShowSuggestion && !isResponsing
const [suggestQuestions, setSuggestQuestions] = useState<string[]>([])
const [messageTaskId, setMessageTaskId] = useState('')
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
const handleSend = async (message: string) => {
if (isResponsing) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
......@@ -370,18 +373,20 @@ const Main: FC<IMainProps> = ({
let tempNewConversationId = ''
setHasStopResponded(false)
setResponsingTrue()
setIsShowSuggestion(false)
sendChatMessage(data, {
getAbortController: (abortController) => {
setAbortController(abortController)
},
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId }: any) => {
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
responseItem.content = responseItem.content + message
responseItem.id = messageId
if (isFirstMessage && newConversationId)
tempNewConversationId = newConversationId
setMessageTaskId(taskId)
// closesure new list is outdated.
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
......@@ -409,7 +414,7 @@ const Main: FC<IMainProps> = ({
resetNewConversationInputs()
setChatNotStarted()
setCurrConversationId(tempNewConversationId, appId, true)
if (suggestedQuestionsAfterAnswerConfig?.enabled) {
if (suggestedQuestionsAfterAnswerConfig?.enabled && !getHasStopResponded()) {
const { data }: any = await fetchSuggestedQuestions(responseItem.id, isInstalledApp, installedAppInfo?.id)
setSuggestQuestions(data)
setIsShowSuggestion(true)
......@@ -532,8 +537,10 @@ const Main: FC<IMainProps> = ({
isHideFeedbackEdit
onFeedback={handleFeedback}
isResponsing={isResponsing}
abortResponsing={() => {
abortController?.abort()
canStopResponsing={!!messageTaskId}
abortResponsing={async () => {
await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id)
setHasStopResponded(true)
setResponsingFalse()
}}
checkCanSend={checkCanSend}
......
......@@ -45,7 +45,7 @@ export const ChatBtn: FC<{ onClick: () => void, className?: string }> = ({
return (
<Button
type='primary'
className={cn(className, `space-x-2 flex items-center ${s.customBtn}`)}
className={cn(className, `!p-0 space-x-2 flex items-center ${s.customBtn}`)}
onClick={onClick}>
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M18 10.5C18 14.366 14.418 17.5 10 17.5C8.58005 17.506 7.17955 17.1698 5.917 16.52L2 17.5L3.338 14.377C2.493 13.267 2 11.934 2 10.5C2 6.634 5.582 3.5 10 3.5C14.418 3.5 18 6.634 18 10.5ZM7 9.5H5V11.5H7V9.5ZM15 9.5H13V11.5H15V9.5ZM9 9.5H11V11.5H9V9.5Z" fill="white" />
......
......@@ -85,7 +85,7 @@ const ConfigSence: FC<IConfigSenceProps> = ({
</div>
<Button
type="primary"
className='w-[80px] !h-8'
className='w-[80px] !h-8 !p-0'
onClick={onSend}
disabled={!query || query === ''}
>
......
......@@ -21,6 +21,7 @@ const LocaleLayout = ({
data-api-prefix={process.env.NEXT_PUBLIC_API_PREFIX}
data-pubic-api-prefix={process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX}
data-public-edition={process.env.NEXT_PUBLIC_EDITION}
data-public-sentry-dsn={process.env.NEXT_PUBLIC_SENTRY_DSN}
>
{/* @ts-expect-error Async Server Component */}
<I18nServer locale={locale}>{children}</I18nServer>
......
......@@ -6,6 +6,7 @@ export NEXT_PUBLIC_DEPLOY_ENV=${DEPLOY_ENV}
export NEXT_PUBLIC_EDITION=${EDITION}
export NEXT_PUBLIC_API_PREFIX=${CONSOLE_URL}/console/api
export NEXT_PUBLIC_PUBLIC_API_PREFIX=${APP_URL}/api
export NEXT_PUBLIC_SENTRY_DSN=${SENTRY_DSN}
/usr/local/bin/pm2 -v
/usr/local/bin/pm2-runtime start /app/web/pm2.json
const { withSentryConfig } = require('@sentry/nextjs')
const isDevelopment = process.env.NODE_ENV === 'development'
const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN
const SENTRY_ORG = process.env.NEXT_PUBLIC_SENTRY_ORG
const SENTRY_PROJECT = process.env.NEXT_PUBLIC_SENTRY_PROJECT
const isHideSentry = isDevelopment || !SENTRY_DSN || !SENTRY_ORG || !SENTRY_PROJECT
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
......@@ -46,25 +38,6 @@ const nextConfig = {
},
]
},
...(isHideSentry
? {}
: {
sentry: {
hideSourceMaps: true,
},
}),
}
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup
const sentryWebpackPluginOptions = {
org: SENTRY_ORG,
project: SENTRY_PROJECT,
silent: true, // Suppresses all logs
sourcemaps: {
assets: './**',
ignore: ['./node_modules/**'],
},
// https://github.com/getsentry/sentry-webpack-plugin#options.
}
module.exports = isHideSentry ? withMDX(nextConfig) : withMDX(withSentryConfig(nextConfig, sentryWebpackPluginOptions))
module.exports = withMDX(nextConfig)
{
"name": "dify-web",
"version": "0.3.1",
"version": "0.3.3",
"private": true,
"scripts": {
"dev": "next dev",
......@@ -20,10 +20,10 @@
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@next/mdx": "^13.2.4",
"@sentry/nextjs": "^7.53.1",
"@sentry/react": "^7.54.0",
"@sentry/utils": "^7.54.0",
"@tailwindcss/typography": "^0.5.9",
"@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.9",
"@types/crypto-js": "^4.1.1",
"@types/lodash-es": "^4.17.7",
"@types/node": "18.15.0",
......@@ -33,8 +33,8 @@
"@types/react-syntax-highlighter": "^15.5.6",
"@types/react-window": "^1.8.5",
"@types/react-window-infinite-loader": "^1.0.6",
"autoprefixer": "^10.4.14",
"ahooks": "^3.7.5",
"autoprefixer": "^10.4.14",
"classnames": "^2.3.2",
"copy-to-clipboard": "^3.3.3",
"crypto-js": "^4.1.1",
......
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Replay may only be enabled for the client-side
integrations: [new Sentry.Replay()],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 0.1,
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
})
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 0.1,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
})
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 0.1,
})
......@@ -23,7 +23,8 @@ const baseOptions = {
}
export type IOnDataMoreInfo = {
conversationId: string | undefined
conversationId?: string
taskId?: string
messageId: string
errorMessage?: string
}
......@@ -101,6 +102,7 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted
// can not use format here. Because message is splited.
onData(unicodeToChar(bufferObj.answer), isFirstMessage, {
conversationId: bufferObj.conversation_id,
taskId: bufferObj.task_id,
messageId: bufferObj.id,
})
isFirstMessage = false
......
......@@ -15,6 +15,10 @@ export const sendChatMessage = async (appId: string, body: Record<string, any>,
}, { onData, onCompleted, onError, getAbortController })
}
export const stopChatMessageResponding = async (appId: string, taskId: string) => {
return post(`apps/${appId}/chat-messages/${taskId}/stop`)
}
export const sendCompletionMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onError }: {
onData: IOnData
onCompleted: IOnCompleted
......
......@@ -34,6 +34,10 @@ export const sendChatMessage = async (body: Record<string, any>, { onData, onCom
}, { onData, onCompleted, isPublicAPI: !isInstalledApp, onError, getAbortController })
}
export const stopChatMessageResponding = async (appId: string, taskId: string, isInstalledApp: boolean, installedAppId = '') => {
return getAction('post', isInstalledApp)(getUrl(`chat-messages/${taskId}/stop`, isInstalledApp, installedAppId))
}
export const sendCompletionMessage = async (body: Record<string, any>, { onData, onCompleted, onError }: {
onData: IOnData
onCompleted: IOnCompleted
......
......@@ -179,6 +179,9 @@ export type SiteConfig = {
copyright: string
/** Privacy Policy */
privacy_policy: string
icon: string
icon_background: string
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment