Compare commits

..

36 Commits
1.1.0 ... main

Author SHA1 Message Date
NanoNova
a30945312a
fix: typos (#16385) 2025-03-21 11:14:40 +08:00
Ron
bf682302ee
fix error with literal_eval (#16297)
Co-authored-by: Novice <novice12185727@gmail.com>
2025-03-21 09:30:24 +08:00
Jyong
72191f5b13
add built-in field check when doing old metadata migrate (#16371) 2025-03-20 21:53:49 +08:00
Junjie.M
e324e59930
fix import DSL install Github plugin failed (#16362) 2025-03-20 21:37:45 +08:00
L8ng
727caccfc9
fix: knowledge base openapi cannot delete metadata (#16365) 2025-03-20 21:36:09 +08:00
-LAN-
85160b0487
chore: update version to 1.1.1 in packaging and docker configurations (#16301)
Signed-off-by: -LAN- <laipz8200@outlook.com>
2025-03-20 18:20:04 +08:00
Linh Nguyen
8996c1da29
fix removing member without permission (#16332)
Co-authored-by: crazywoola <427733928@qq.com>
2025-03-20 17:17:54 +08:00
Novice
437dcbdd68
fix: exclude additional unreachable nodes (#16329) 2025-03-20 16:53:56 +08:00
Jyong
3e84c77bbb
fix enable dataset metadata built-in field when dataset is empty (#16290) 2025-03-20 14:38:32 +08:00
Jyong
2c9af712a2
Fix/create document by api with metadata (#16307)
Co-authored-by: zxhlyh <jasonapring2015@outlook.com>
2025-03-20 14:33:32 +08:00
GuanMu
c1f3d968bf
fix: enhance React imports in LLM panel component #16282 (#16283) 2025-03-20 10:55:37 +08:00
Ning
79118f51c2
fix: dify-web docker MAX_TOOLS_NUM environment value not work (#16241) 2025-03-20 09:38:46 +08:00
リイノ Lin
285314da1c
fix: update workflow doc (#16251) 2025-03-20 09:28:42 +08:00
wyy-holding
daad5824bf
add kubernetes yaml for dify by docker-compose.yaml (#16246) 2025-03-20 09:28:09 +08:00
Jyong
d135677c25
add vdb document id index (#16244)
Co-authored-by: crazywoola <427733928@qq.com>
2025-03-20 01:38:15 +08:00
Panpan
cade0f65e2
fix: reset inputs when reset conversation (#16233) 2025-03-20 00:17:58 +08:00
GuanMu
106169ed7f
refactor: improve layout structure in StepOne component for better re… (#16209) 2025-03-19 20:44:43 +08:00
Jyong
a8879057c0
fix tidb metadata filter (#16237) 2025-03-19 19:44:56 +08:00
Xuetao Song
f6404f93ca
Add https://github.com/magicsong/ai-charts to recommended Helm charts… (#15848) 2025-03-19 18:44:25 +08:00
Jyong
81325df368
fix weaviate metadata filter (#16230) 2025-03-19 18:26:53 +08:00
Wu Tianwei
965bfc49dd
fix: update _dataset handling in knowledge retrieval config (#16218) 2025-03-19 17:48:49 +08:00
jimmyfen
425ea4e5b6
fix: model changed but completion params not change (#16156) 2025-03-19 17:48:18 +08:00
Jyong
b8ef3149ef
metadata expect value check error (#16210) 2025-03-19 17:48:01 +08:00
Yeuoly
c07af5a1a3
feat: tenant app invocations limiter (#16221) 2025-03-19 17:24:02 +08:00
Jyong
c3c957bb80
change recreate_collection function to create_collection (#16212) 2025-03-19 17:13:08 +08:00
Jyong
732c506e27
add metadata service api (#16211) 2025-03-19 17:12:48 +08:00
诗浓
0189a8b0f6
fix: agent node help link error in zh (#16194) 2025-03-19 16:04:17 +08:00
Jyong
3f44b690c2
check high-quality dataset with empty embedding model provider (#16181) 2025-03-19 15:16:05 +08:00
Jyong
e7572066a4
fix Weight rerank mode info missed when create dataset (#16190) 2025-03-19 15:10:00 +08:00
crazywoola
41dff175b3
Chore/update contributing (#16078) 2025-03-19 11:25:13 +08:00
Yongtao Huang
d339403e89
Chore: optimize the code of PromptTransform (#16143) 2025-03-19 11:24:57 +08:00
jiangbo721
e0cf55f5e9
fix: code lint (#16164)
Co-authored-by: 刘江波 <jiangbo721@163.com>
2025-03-19 11:23:51 +08:00
Wu Tianwei
411e332f1b
feat: dark mode for knowledge (#15236) 2025-03-19 11:19:57 +08:00
jiangbo721
97eadb867c
chore: use ConversationService.get_conversation instead of AgentChatA… (#16136)
Co-authored-by: 刘江波 <jiangbo721@163.com>
2025-03-19 11:16:23 +08:00
Yingchun Lai
e428628fcc
enhance: avoid to use transaction Redis commands in rate limiter (#15917)
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
2025-03-19 10:51:38 +08:00
cyflhn
1789437cc5
reopen PR for #14411 (#16148) 2025-03-19 10:24:35 +08:00
167 changed files with 1978 additions and 2461 deletions

View File

@ -9,7 +9,7 @@ body:
required: true
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
required: true
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:"
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:)"
required: true
- label: "Please do not modify this template :) and fill in all the required fields."
required: true

View File

@ -9,7 +9,7 @@ body:
required: true
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
required: true
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:"
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:)"
required: true
- label: "Please do not modify this template :) and fill in all the required fields."
required: true

View File

@ -9,7 +9,7 @@ body:
required: true
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
required: true
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:"
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:)"
required: true
- label: "Please do not modify this template :) and fill in all the required fields."
required: true

View File

@ -14,7 +14,7 @@ body:
required: true
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
required: true
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:"
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:)"
required: true
- label: "Please do not modify this template :) and fill in all the required fields."
required: true

View File

@ -12,7 +12,7 @@ body:
required: true
- label: I confirm that I am using English to submit report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
required: true
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:"
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:)"
required: true
- label: "Please do not modify this template :) and fill in all the required fields."
required: true

View File

@ -12,7 +12,7 @@ body:
required: true
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
required: true
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:"
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:)"
required: true
- label: "Please do not modify this template :) and fill in all the required fields."
required: true

View File

@ -1,5 +1,5 @@
name: "👾 Tracker"
description: For inner usages, please donot use this template.
description: For inner usages, please do not use this template.
title: "[Tracker] "
labels:
- tracker

View File

@ -1,5 +1,5 @@
name: "🌐 Localization/Translation issue"
description: Report incorrect translations. [please use English :]
description: Report incorrect translations. [please use English :)]
labels:
- translation
body:
@ -12,7 +12,7 @@ body:
required: true
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
required: true
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:"
- label: "[FOR CHINESE USERS] 请务必使用英文提交 Issue否则会被关闭。谢谢:)"
required: true
- label: "Please do not modify this template :) and fill in all the required fields."
required: true

View File

@ -10,28 +10,47 @@ In terms of licensing, please take a minute to read our short [License and Contr
## Before you jump in
[Find](https://github.com/langgenius/dify/issues?q=is:issue+is:open) an existing issue, or [open](https://github.com/langgenius/dify/issues/new/choose) a new one. We categorize issues into 2 types:
Looking for something to tackle? Browse our [good first issues](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) and pick one to get started!
Got a cool new model runtime or tool to add? Open a PR in our [plugin repo](https://github.com/langgenius/dify-plugins) and show us what you've built.
Need to update an existing model runtime, tool, or squash some bugs? Head over to our [official plugin repo](https://github.com/langgenius/dify-official-plugins) and make your magic happen!
Join the fun, contribute, and let's build something awesome together! 💡✨
Don't forget to link an existing issue or open an new issue in the PR's description.
### Bug reports
> [!IMPORTANT]
> Please make sure to include the following information when submitting a bug report:
- A clear and descriptive title
- A detailed description of the bug, including any error messages
- Steps to reproduce the bug
- Expected behavior
- **Logs**, if available, for backend issues, this is really important, you can find them in docker-compose logs
- Screenshots or videos, if applicable
How we prioritize:
| Issue Type | Priority |
| ------------------------------------------------------------ | --------------- |
| Bugs in core functions (cloud service, cannot login, applications not working, security loopholes) | Critical |
| Non-critical bugs, performance boosts | Medium Priority |
| Minor fixes (typos, confusing but working UI) | Low Priority |
### Feature requests
* If you're opening a new feature request, we'd like you to explain what the proposed feature achieves, and include as much context as possible. [@perzeusss](https://github.com/perzeuss) has made a solid [Feature Request Copilot](https://udify.app/chat/MK2kVSnw1gakVwMX) that helps you draft out your needs. Feel free to give it a try.
> [!NOTE]
> Please make sure to include the following information when submitting a feature request:
* If you want to pick one up from the existing issues, simply drop a comment below it saying so.
- A clear and descriptive title
- A detailed description of the feature
- A use case for the feature
- Any other context or screenshots about the feature request
A team member working in the related direction will be looped in. If all looks good, they will give the go-ahead for you to start coding. We ask that you hold off working on the feature until then, so none of your work goes to waste should we propose changes.
Depending on whichever area the proposed feature falls under, you might talk to different team members. Here's rundown of the areas each our team members are working on at the moment:
| Member | Scope |
| ------------------------------------------------------------ | ---------------------------------------------------- |
| [@yeuoly](https://github.com/Yeuoly) | Architecting Agents |
| [@jyong](https://github.com/JohnJyong) | RAG pipeline design |
| [@GarfieldDai](https://github.com/GarfieldDai) | Building workflow orchestrations |
| [@iamjoel](https://github.com/iamjoel) & [@zxhlyh](https://github.com/zxhlyh) | Making our frontend a breeze to use |
| [@guchenhe](https://github.com/guchenhe) & [@crazywoola](https://github.com/crazywoola) | Developer experience, points of contact for anything |
| [@takatost](https://github.com/takatost) | Overall product direction and architecture |
How we prioritize:
How we prioritize:
| Feature Type | Priority |
| ------------------------------------------------------------ | --------------- |
@ -39,119 +58,36 @@ In terms of licensing, please take a minute to read our short [License and Contr
| Popular feature requests from our [community feedback board](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Medium Priority |
| Non-core features and minor enhancements | Low Priority |
| Valuable but not immediate | Future-Feature |
### Anything else (e.g. bug report, performance optimization, typo correction)
* Start coding right away.
How we prioritize:
| Issue Type | Priority |
| ------------------------------------------------------------ | --------------- |
| Bugs in core functions (cannot login, applications not working, security loopholes) | Critical |
| Non-critical bugs, performance boosts | Medium Priority |
| Minor fixes (typos, confusing but working UI) | Low Priority |
## Installing
Here are the steps to set up Dify for development:
### 1. Fork this repository
### 2. Clone the repo
Clone the forked repository from your terminal:
```shell
git clone git@github.com:<github_username>/dify.git
```
### 3. Verify dependencies
Dify requires the following dependencies to build, make sure they're installed on your system:
* [Docker](https://www.docker.com/)
* [Docker Compose](https://docs.docker.com/compose/install/)
* [Node.js v18.x (LTS)](http://nodejs.org)
* [pnpm](https://pnpm.io/)
* [Python](https://www.python.org/) version 3.11.x or 3.12.x
### 4. Installations
Dify is composed of a backend and a frontend. Navigate to the backend directory by `cd api/`, then follow the [Backend README](api/README.md) to install it. In a separate terminal, navigate to the frontend directory by `cd web/`, then follow the [Frontend README](web/README.md) to install.
Check the [installation FAQ](https://docs.dify.ai/learn-more/faq/install-faq) for a list of common issues and steps to troubleshoot.
### 5. Visit dify in your browser
To validate your set up, head over to [http://localhost:3000](http://localhost:3000) (the default, or your self-configured URL and port) in your browser. You should now see Dify up and running.
## Developing
If you are adding a model provider, [this guide](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/README.md) is for you.
If you are adding a tool provider to Agent or Workflow, [this guide](./api/core/tools/README.md) is for you.
To help you quickly navigate where your contribution fits, a brief, annotated outline of Dify's backend & frontend is as follows:
### Backend
Difys backend is written in Python using [Flask](https://flask.palletsprojects.com/en/3.0.x/). It uses [SQLAlchemy](https://www.sqlalchemy.org/) for ORM and [Celery](https://docs.celeryq.dev/en/stable/getting-started/introduction.html) for task queueing. Authorization logic goes via Flask-login.
```text
[api/]
├── constants // Constant settings used throughout code base.
├── controllers // API route definitions and request handling logic.
├── core // Core application orchestration, model integrations, and tools.
├── docker // Docker & containerization related configurations.
├── events // Event handling and processing
├── extensions // Extensions with 3rd party frameworks/platforms.
├── fields // field definitions for serialization/marshalling.
├── libs // Reusable libraries and helpers.
├── migrations // Scripts for database migration.
├── models // Database models & schema definitions.
├── services // Specifies business logic.
├── storage // Private key storage.
├── tasks // Handling of async tasks and background jobs.
└── tests
```
### Frontend
The website is bootstrapped on [Next.js](https://nextjs.org/) boilerplate in Typescript and uses [Tailwind CSS](https://tailwindcss.com/) for styling. [React-i18next](https://react.i18next.com/) is used for internationalization.
```text
[web/]
├── app // layouts, pages, and components
│ ├── (commonLayout) // common layout used throughout the app
│ ├── (shareLayout) // layouts specifically shared across token-specific sessions
│ ├── activate // activate page
│ ├── components // shared by pages and layouts
│ ├── install // install page
│ ├── signin // signin page
│ └── styles // globally shared styles
├── assets // Static assets
├── bin // scripts ran at build step
├── config // adjustable settings and options
├── context // shared contexts used by different portions of the app
├── dictionaries // Language-specific translate files
├── docker // container configurations
├── hooks // Reusable hooks
├── i18n // Internationalization configuration
├── models // describes data models & shapes of API responses
├── public // meta assets like favicon
├── service // specifies shapes of API actions
├── test
├── types // descriptions of function params and return values
└── utils // Shared utility functions
```
## Submitting your PR
At last, time to open a pull request (PR) to our repo. For major features, we first merge them into the `deploy/dev` branch for testing, before they go into the `main` branch. If you run into issues like merge conflicts or don't know how to open a pull request, check out [GitHub's pull request tutorial](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests).
### Pull Request Process
And that's it! Once your PR is merged, you will be featured as a contributor in our [README](https://github.com/langgenius/dify/blob/main/README.md).
1. Fork the repository
2. Before you draft a PR, please create an issue to discuss the changes you want to make
3. Create a new branch for your changes
4. Please add tests for your changes accordingly
5. Ensure your code passes the existing tests
6. Please link the issue in the PR description, `fixes #<issue_number>`
7. Get merrged!
### Setup the project
#### Frontend
For setting up the frontend service, please refer to our comprehensive [guide](https://github.com/langgenius/dify/blob/main/web/README.md) in the `web/README.md` file. This document provides detailed instructions to help you set up the frontend environment properly.
#### Backend
For setting up the backend service, kindly refer to our detailed [instructions](https://github.com/langgenius/dify/blob/main/api/README.md) in the `api/README.md` file. This document contains step-by-step guidance to help you get the backend up and running smoothly.
#### Other things to note
We recommend reviewing this document carefully before proceeding with the setup, as it contains essential information about:
- Prerequisites and dependencies
- Installation steps
- Configuration details
- Common troubleshooting tips
Feel free to reach out if you encounter any issues during the setup process.
## Getting Help
If you ever get stuck or got a burning question while contributing, simply shoot your queries our way via the related GitHub issue, or hop onto our [Discord](https://discord.gg/8Tpq4AcN9c) for a quick chat.

View File

@ -1,154 +1,97 @@
所以你想为 Dify 做贡献 - 这太棒了,我们迫不及待地想看到你的贡献。作为一家人员和资金有限的初创公司,我们有着雄心勃勃的目标,希望设计出最直观的工作流程来构建和管理 LLM 应用程序。社区的任何帮助都是宝贵的。
# 贡献指南
考虑到我们的现状,我们需要灵活快速地交付,但我们也希望确保像你这样的贡献者在贡献过程中获得尽可能顺畅的体验。我们为此编写了这份贡献指南,旨在让你熟悉代码库和我们与贡献者的合作方式,以便你能快速进入有趣的部分
非常感谢你考虑为 Dify 做出贡献!作为一家资源有限的创业公司,我们希望打造最直观的 LLM 应用开发和管理工作流程。社区的每一份贡献对我们来说都弥足珍贵
这份指南,就像 Dify 本身一样,是一个不断改进的工作。如果有时它落后于实际项目,我们非常感谢你的理解,并欢迎提供任何反馈以供我们改进
我们需要保持敏捷和快速迭代,同时也希望确保贡献者能获得尽可能流畅的参与体验。这份贡献指南旨在帮助你熟悉代码库和我们的工作方式,让你可以尽快进入有趣的开发环节
在许可方面,请花一分钟阅读我们简短的 [许可证和贡献者协议](./LICENSE)。社区还遵守 [行为准则](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)
本指南和 Dify 一样在不断完善中。如果有任何滞后于项目实际情况的地方,恳请谅解,我们也欢迎任何改进建议
## 在开始之前
关于许可证,请花一分钟阅读我们简短的[许可和贡献者协议](./LICENSE)。社区同时也遵循[行为准则](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
[查找](https://github.com/langgenius/dify/issues?q=is:issue+is:open)现有问题,或 [创建](https://github.com/langgenius/dify/issues/new/choose) 一个新问题。我们将问题分为两类:
## 开始之前
### 功能请求:
想寻找可以着手的任务?浏览我们的[新手友好议题](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)并选择一个开始!
* 如果您要提出新的功能请求,请解释所提议的功能的目标,并尽可能提供详细的上下文。[@perzeusss](https://github.com/perzeuss) 制作了一个很好的 [功能请求助手](https://udify.app/chat/MK2kVSnw1gakVwMX),可以帮助您起草需求。随时尝试一下
有酷炫的新模型运行时或工具要添加?在我们的[插件仓库](https://github.com/langgenius/dify-plugins)开启 PR展示你的作品
* 如果您想从现有问题中选择一个,请在其下方留下评论表示您的意愿。
需要更新现有模型运行时、工具或修复 bug前往我们的[官方插件仓库](https://github.com/langgenius/dify-official-plugins)大展身手!
相关方向的团队成员将参与其中。如果一切顺利,他们将批准您开始编码。在此之前,请不要开始工作,以免我们提出更改导致您的工作付诸东流。
加入我们,一起贡献,共同打造精彩项目!💡✨
根据所提议的功能所属的领域不同,您可能需要与不同的团队成员交流。以下是我们团队成员目前正在从事的各个领域的概述:
请记得在 PR 描述中关联现有 issue 或创建新的 issue。
| 团队成员 | 工作范围 |
| ------------------------------------------------------------ | ---------------------------------------------------- |
| [@yeuoly](https://github.com/Yeuoly) | 架构 Agents |
| [@jyong](https://github.com/JohnJyong) | RAG 流水线设计 |
| [@GarfieldDai](https://github.com/GarfieldDai) | 构建 workflow 编排 |
| [@iamjoel](https://github.com/iamjoel) & [@zxhlyh](https://github.com/zxhlyh) | 让我们的前端更易用 |
| [@guchenhe](https://github.com/guchenhe) & [@crazywoola](https://github.com/crazywoola) | 开发人员体验,综合事项联系人 |
| [@takatost](https://github.com/takatost) | 产品整体方向和架构 |
### Bug 报告
事项优先级:
> [!IMPORTANT]
> 提交 bug 报告时请务必包含以下信息:
| 功能类型 | 优先级 |
| ------------------------------------------------------------ | --------------- |
| 被团队成员标记为高优先级的功能 | 高优先级 |
| 在 [community feedback board](https://github.com/langgenius/dify/discussions/categories/feedbacks) 内反馈的常见功能请求 | 中等优先级 |
| 非核心功能和小幅改进 | 低优先级 |
| 有价值但不紧急 | 未来功能 |
- 清晰描述性的标题
- 详细的 bug 描述,包括任何错误信息
- 复现步骤
- 预期行为
- **日志**,如果是后端问题,这点很重要,可以在 docker-compose 日志中找到
- 截图或视频(如果适用)
### 其他任何事情(例如 bug 报告、性能优化、拼写错误更正):
* 立即开始编码。
优先级划分:
事项优先级:
| 问题类型 | 优先级 |
| -------------------------------------------------- | ---------- |
| 核心功能 bug云服务、登录失败、应用无法使用、安全漏洞 | 紧急 |
| 非关键 bug、性能优化 | 中等优先级 |
| 小修复(拼写错误、界面混乱但可用) | 低优先级 |
| Issue 类型 | 优先级 |
| ------------------------------------------------------------ | --------------- |
| 核心功能的 Bugs例如无法登录、应用无法工作、安全漏洞 | 紧急 |
| 非紧急 bugs, 性能提升 | 中等优先级 |
| 小幅修复 (错别字,能正常工作但存在误导的 UI) | 低优先级 |
## 安装
### 功能请求
以下是设置 Dify 进行开发的步骤:
> [!NOTE]
> 提交功能请求时请务必包含以下信息:
### 1. Fork 该仓库
- 清晰描述性的标题
- 详细的功能描述
- 功能使用场景
- 其他相关上下文或截图
### 2. 克隆仓库
优先级划分:
从终端克隆代码仓库:
| 功能类型 | 优先级 |
| -------------------------------------------------- | ---------- |
| 被团队成员标记为高优先级的功能 | 高优先级 |
| 来自[社区反馈板](https://github.com/langgenius/dify/discussions/categories/feedbacks)的热门功能请求 | 中等优先级 |
| 非核心功能和小改进 | 低优先级 |
| 有价值但非紧急的功能 | 未来特性 |
```
git clone git@github.com:<github_username>/dify.git
```
## 提交 PR
### 3. 验证依赖
### 项目设置
Dify 依赖以下工具和库:
### PR 提交流程
- [Docker](https://www.docker.com/)
- [Docker Compose](https://docs.docker.com/compose/install/)
- [Node.js v18.x (LTS)](http://nodejs.org)
- [pnpm](https://pnpm.io/)
- [Python](https://www.python.org/) version 3.11.x or 3.12.x
1. Fork 本仓库
2. 在提交 PR 之前,请先创建 issue 讨论你想要做的修改
3. 为你的修改创建一个新的分支
4. 请为你的修改添加相应的测试
5. 确保你的代码能通过现有的测试
6. 请在 PR 描述中关联相关 issue格式为 `fixes #<issue编号>`
7. 等待合并!
### 4. 安装
#### 前端
Dify 由后端和前端组成。通过 `cd api/` 导航到后端目录,然后按照 [后端 README](api/README.md) 进行安装。在另一个终端中,通过 `cd web/` 导航到前端目录,然后按照 [前端 README](web/README.md) 进行安装
关于前端服务的设置,请参考 `web/README.md` 文件中的[详细指南](https://github.com/langgenius/dify/blob/main/web/README.md)。该文档提供了帮助你正确配置前端环境的详细说明
查看 [安装常见问题解答](https://docs.dify.ai/v/zh-hans/learn-more/faq/install-faq) 以获取常见问题列表和故障排除步骤。
#### 后端
### 5. 在浏览器中访问 Dify
关于后端服务的设置,请参考 `api/README.md` 文件中的[详细说明](https://github.com/langgenius/dify/blob/main/api/README.md)。该文档包含了帮助你顺利运行后端的步骤说明。
为了验证您的设置,打开浏览器并访问 [http://localhost:3000](http://localhost:3000)(默认或您自定义的 URL 和端口)。现在您应该看到 Dify 正在运行。
#### 其他注意事项
## 开发
我们建议在开始设置之前仔细阅读本文档,因为它包含以下重要信息:
- 前置条件和依赖项
- 安装步骤
- 配置细节
- 常见问题解决方案
如果您要添加模型提供程序,请参考 [此指南](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/README.md)。
如果您要向 Agent 或 Workflow 添加工具提供程序,请参考 [此指南](./api/core/tools/README.md)。
为了帮助您快速了解您的贡献在哪个部分,以下是 Dify 后端和前端的简要注释大纲:
### 后端
Dify 的后端使用 Python 编写,使用 [Flask](https://flask.palletsprojects.com/en/3.0.x/) 框架。它使用 [SQLAlchemy](https://www.sqlalchemy.org/) 作为 ORM使用 [Celery](https://docs.celeryq.dev/en/stable/getting-started/introduction.html) 作为任务队列。授权逻辑通过 Flask-login 进行处理。
```
[api/]
├── constants // 用于整个代码库的常量设置。
├── controllers // API 路由定义和请求处理逻辑。
├── core // 核心应用编排、模型集成和工具。
├── docker // Docker 和容器化相关配置。
├── events // 事件处理和处理。
├── extensions // 与第三方框架/平台的扩展。
├── fields // 用于序列化/封装的字段定义。
├── libs // 可重用的库和助手。
├── migrations // 数据库迁移脚本。
├── models // 数据库模型和架构定义。
├── services // 指定业务逻辑。
├── storage // 私钥存储。
├── tasks // 异步任务和后台作业的处理。
└── tests
```
### 前端
该网站使用基于 Typescript 的 [Next.js](https://nextjs.org/) 模板进行引导,并使用 [Tailwind CSS](https://tailwindcss.com/) 进行样式设计。[React-i18next](https://react.i18next.com/) 用于国际化。
```
[web/]
├── app // 布局、页面和组件
│ ├── (commonLayout) // 整个应用通用的布局
│ ├── (shareLayout) // 在特定会话中共享的布局
│ ├── activate // 激活页面
│ ├── components // 页面和布局共享的组件
│ ├── install // 安装页面
│ ├── signin // 登录页面
│ └── styles // 全局共享的样式
├── assets // 静态资源
├── bin // 构建步骤运行的脚本
├── config // 可调整的设置和选项
├── context // 应用中不同部分使用的共享上下文
├── dictionaries // 语言特定的翻译文件
├── docker // 容器配置
├── hooks // 可重用的钩子
├── i18n // 国际化配置
├── models // 描述数据模型和 API 响应的形状
├── public // 如 favicon 等元资源
├── service // 定义 API 操作的形状
├── test
├── types // 函数参数和返回值的描述
└── utils // 共享的实用函数
```
## 提交你的 PR
最后是时候向我们的仓库提交一个拉取请求PR了。对于重要的功能我们首先将它们合并到 `deploy/dev` 分支进行测试,然后再合并到 `main` 分支。如果你遇到合并冲突或者不知道如何提交拉取请求的问题,请查看 [GitHub 的拉取请求教程](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests)。
就是这样!一旦你的 PR 被合并,你将成为我们 [README](https://github.com/langgenius/dify/blob/main/README.md) 中的贡献者。
如果在设置过程中遇到任何问题,请随时联系我们。
## 获取帮助
如果你在贡献过程中遇到困难或者有任何问题,可以通过相关的 GitHub 问题提出你的疑问,或者加入我们的 [Discord](https://discord.gg/8Tpq4AcN9c) 进行快速交流。
如果你在贡献过程中遇到困难或有紧急问题,可以通过相关 GitHub issue 向我们提问,或加入我们的 [Discord](https://discord.gg/8Tpq4AcN9c) 进行快速交流。

View File

@ -1,155 +1,95 @@
# MITWIRKEN
So, du möchtest zu Dify beitragen das ist großartig, wir können es kaum erwarten, zu sehen, was du beisteuern wirst. Als ein Startup mit begrenzter Mitarbeiterzahl und Finanzierung haben wir große Ambitionen, den intuitivsten Workflow zum Aufbau und zur Verwaltung von LLM-Anwendungen zu entwickeln. Jede Unterstützung aus der Community zählt wirklich.
Sie möchten also zu Dify beitragen - das ist großartig, wir können es kaum erwarten zu sehen, was Sie entwickeln. Als Startup mit begrenztem Personal und Finanzierung haben wir große Ambitionen, den intuitivsten Workflow für die Entwicklung und Verwaltung von LLM-Anwendungen zu gestalten. Jede Hilfe aus der Community zählt wirklich.
Dieser Leitfaden, ebenso wie Dify selbst, ist ein ständig in Entwicklung befindliches Projekt. Wir schätzen Ihr Verständnis, falls er zeitweise hinter dem tatsächlichen Projekt zurückbleibt, und freuen uns über jegliches Feedback, das uns hilft, ihn zu verbessern.
Wir müssen wendig sein und schnell liefern, aber wir möchten auch sicherstellen, dass Mitwirkende wie Sie eine möglichst reibungslose Erfahrung beim Beitragen haben. Wir haben diesen Leitfaden zusammengestellt, damit Sie sich schnell mit der Codebasis und unserer Arbeitsweise mit Mitwirkenden vertraut machen können.
Bezüglich der Lizenzierung nehmen Sie sich bitte einen Moment Zeit, um unser kurzes [License and Contributor Agreement](./LICENSE) zu lesen. Die Community hält sich außerdem an den [Code of Conduct](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
Dieser Leitfaden ist, wie Dify selbst, in ständiger Entwicklung. Wir sind dankbar für Ihr Verständnis, falls er manchmal hinter dem eigentlichen Projekt zurückbleibt, und begrüßen jedes Feedback zur Verbesserung.
Bitte nehmen Sie sich einen Moment Zeit, um unsere [Lizenz- und Mitwirkungsvereinbarung](./LICENSE) zu lesen. Die Community hält sich außerdem an den [Verhaltenskodex](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Bevor Sie loslegen
[Finde](https://github.com/langgenius/dify/issues?q=is:issue+is:open) ein bestehendes Issue, oder [öffne](https://github.com/langgenius/dify/issues/new/choose) ein neues. Wir kategorisieren Issues in zwei Typen:
Suchen Sie nach einer Aufgabe? Durchstöbern Sie unsere [Einsteiger-Issues](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) und wählen Sie eines zum Einstieg!
Haben Sie eine neue Modell-Runtime oder ein Tool hinzuzufügen? Öffnen Sie einen PR in unserem [Plugin-Repository](https://github.com/langgenius/dify-plugins).
Möchten Sie eine bestehende Modell-Runtime oder ein Tool aktualisieren oder Bugs beheben? Besuchen Sie unser [offizielles Plugin-Repository](https://github.com/langgenius/dify-official-plugins)!
Vergessen Sie nicht, in der PR-Beschreibung ein bestehendes Issue zu verlinken oder ein neues zu erstellen.
### Fehlermeldungen
> [!WICHTIG]
> Bitte stellen Sie sicher, dass Sie folgende Informationen bei der Einreichung eines Fehlerberichts angeben:
- Ein klarer und beschreibender Titel
- Eine detaillierte Beschreibung des Fehlers, einschließlich Fehlermeldungen
- Schritte zur Reproduktion des Fehlers
- Erwartetes Verhalten
- **Logs** bei Backend-Problemen (sehr wichtig, zu finden in docker-compose logs)
- Screenshots oder Videos, falls zutreffend
Unsere Priorisierung:
| Fehlertyp | Priorität |
| ------------------------------------------------------------ | --------------- |
| Fehler in Kernfunktionen (Cloud-Service, Login nicht möglich, Anwendungen funktionieren nicht, Sicherheitslücken) | Kritisch |
| Nicht-kritische Fehler, Leistungsverbesserungen | Mittlere Priorität |
| Kleinere Korrekturen (Tippfehler, verwirrende aber funktionierende UI) | Niedrige Priorität |
### Feature-Anfragen
* Wenn Sie eine neue Feature-Anfrage stellen, bitten wir Sie zu erklären, was das vorgeschlagene Feature bewirken soll und so viel Kontext wie möglich bereitzustellen. [@perzeusss](https://github.com/perzeuss) hat einen soliden [Feature Request Copilot](https://udify.app/chat/MK2kVSnw1gakVwMX) entwickelt, der Ihnen dabei hilft, Ihre Anforderungen zu formulieren. Probieren Sie ihn gerne aus.
> [!HINWEIS]
> Bitte stellen Sie sicher, dass Sie folgende Informationen bei der Einreichung einer Feature-Anfrage angeben:
* Wenn Sie eines der bestehenden Issues übernehmen möchten, hinterlassen Sie einfach einen Kommentar darunter, in dem Sie uns dies mitteilen.
- Ein klarer und beschreibender Titel
- Eine detaillierte Beschreibung des Features
- Ein Anwendungsfall für das Feature
- Zusätzlicher Kontext oder Screenshots zur Feature-Anfrage
Ein Teammitglied, das in der entsprechenden Richtung arbeitet, wird hinzugezogen. Wenn alles in Ordnung ist, gibt es das Okay, mit der Codierung zu beginnen. Wir bitten Sie, mit der Umsetzung des Features zu warten, damit keine Ihrer Arbeiten verloren gehen sollte unsererseits Änderungen vorgeschlagen werden.
Unsere Priorisierung:
Je nachdem, in welchen Bereich das vorgeschlagene Feature fällt, können Sie mit verschiedenen Teammitgliedern sprechen. Hier ist eine Übersicht der Bereiche, an denen unsere Teammitglieder derzeit arbeiten:
| Member | Scope |
| ------------------------------------------------------------ | ---------------------------------------------------- |
| [@yeuoly](https://github.com/Yeuoly) | Architecting Agents |
| [@jyong](https://github.com/JohnJyong) | RAG pipeline design |
| [@GarfieldDai](https://github.com/GarfieldDai) | Building workflow orchestrations |
| [@iamjoel](https://github.com/iamjoel) & [@zxhlyh](https://github.com/zxhlyh) | Making our frontend a breeze to use |
| [@guchenhe](https://github.com/guchenhe) & [@crazywoola](https://github.com/crazywoola) | Developer experience, points of contact for anything |
| [@takatost](https://github.com/takatost) | Overall product direction and architecture |
Wie wir Prioritäten setzen:
| Feature Type | Priority |
| Feature-Typ | Priorität |
| ------------------------------------------------------------ | --------------- |
| Funktionen mit hoher Priorität, wie sie von einem Teammitglied gekennzeichnet wurden | High Priority |
| Beliebte Funktionsanfragen von unserem [Community-Feedback-Board](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Medium Priority |
| Nicht-Kernfunktionen und kleinere Verbesserungen | Low Priority |
| Wertvoll, aber nicht unmittelbar | Future-Feature |
| Hochprioritäre Features (durch Teammitglied gekennzeichnet) | Hohe Priorität |
| Beliebte Feature-Anfragen aus unserem [Community-Feedback-Board](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Mittlere Priorität |
| Nicht-Kernfunktionen und kleinere Verbesserungen | Niedrige Priorität |
| Wertvoll, aber nicht dringend | Zukunfts-Feature |
### Sonstiges (e.g. bug report, performance optimization, typo correction)
## Einreichen Ihres PRs
* Fangen Sie sofort an zu programmieren..
### Pull-Request-Prozess
Wie wir Prioritäten setzen:
1. Repository forken
2. Vor dem Erstellen eines PRs bitte ein Issue zur Diskussion der Änderungen erstellen
3. Einen neuen Branch für Ihre Änderungen erstellen
4. Tests für Ihre Änderungen hinzufügen
5. Sicherstellen, dass Ihr Code die bestehenden Tests besteht
6. Issue in der PR-Beschreibung verlinken (`fixes #<issue_number>`)
7. Auf den Merge warten!
| Issue Type | Priority |
| ------------------------------------------------------------ | --------------- |
| Fehler in Kernfunktionen (Anmeldung nicht möglich, Anwendungen funktionieren nicht, Sicherheitslücken) | Critical |
| Nicht-kritische Fehler, Leistungsverbesserungen | Medium Priority |
| Kleinere Fehlerkorrekturen (Schreibfehler, verwirrende, aber funktionierende Benutzeroberfläche) | Low Priority |
### Projekt einrichten
## Installieren
#### Frontend
Hier sind die Schritte, um Dify für die Entwicklung einzurichten:
Für die Einrichtung des Frontend-Service folgen Sie bitte unserer ausführlichen [Anleitung](https://github.com/langgenius/dify/blob/main/web/README.md) in der Datei `web/README.md`.
### 1. Fork dieses Repository
#### Backend
### 2. Clone das Repo
Für die Einrichtung des Backend-Service folgen Sie bitte unseren detaillierten [Anweisungen](https://github.com/langgenius/dify/blob/main/api/README.md) in der Datei `api/README.md`.
Klonen Sie das geforkte Repository von Ihrem Terminal aus:
#### Weitere Hinweise
```shell
git clone git@github.com:<github_username>/dify.git
```
Wir empfehlen, dieses Dokument sorgfältig zu lesen, da es wichtige Informationen enthält über:
- Voraussetzungen und Abhängigkeiten
- Installationsschritte
- Konfigurationsdetails
- Häufige Problemlösungen
### 3. Abhängigkeiten prüfen
Dify benötigt die folgenden Abhängigkeiten zum Bauen stellen Sie sicher, dass sie auf Ihrem System installiert sind:
* [Docker](https://www.docker.com/)
* [Docker Compose](https://docs.docker.com/compose/install/)
* [Node.js v18.x (LTS)](http://nodejs.org)
* [pnpm](https://pnpm.io/)
* [Python](https://www.python.org/) version 3.11.x or 3.12.x
### 4. Installationen
Dify setzt sich aus einem Backend und einem Frontend zusammen. Wechseln Sie in das Backend-Verzeichnis mit `cd api/` und folgen Sie der [Backend README](api/README.md) zur Installation. Öffnen Sie in einem separaten Terminal das Frontend-Verzeichnis mit `cd web/` und folgen Sie der [Frontend README](web/README.md) zur Installation.
Überprüfen Sie die [Installation FAQ](https://docs.dify.ai/learn-more/faq/install-faq) für eine Liste bekannter Probleme und Schritte zur Fehlerbehebung.
### 5. Besuchen Sie dify in Ihrem Browser
Um Ihre Einrichtung zu validieren, öffnen Sie Ihren Browser und navigieren Sie zu [http://localhost:3000](http://localhost:3000) (Standardwert oder Ihre selbst konfigurierte URL und Port). Sie sollten nun Dify im laufenden Betrieb sehen.
## Entwickeln
Wenn Sie einen Modellanbieter hinzufügen, ist [dieser Leitfaden](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/README.md) für Sie.
Wenn Sie einen Tool-Anbieter für Agent oder Workflow hinzufügen möchten, ist [dieser Leitfaden](./api/core/tools/README.md) für Sie.
Um Ihnen eine schnelle Orientierung zu bieten, wo Ihr Beitrag passt, folgt eine kurze, kommentierte Übersicht des Backends und Frontends von Dify:
### Backend
Difys Backend ist in Python geschrieben und nutzt [Flask](https://flask.palletsprojects.com/en/3.0.x/) als Web-Framework. Es verwendet [SQLAlchemy](https://www.sqlalchemy.org/) für ORM und [Celery](https://docs.celeryq.dev/en/stable/getting-started/introduction.html) für Task-Queueing. Die Autorisierungslogik erfolgt über Flask-login.
```text
[api/]
├── constants // Konstante Einstellungen, die in der gesamten Codebasis verwendet werden.
├── controllers // API-Routendefinitionen und Logik zur Bearbeitung von Anfragen.
├── core // Orchestrierung von Kernanwendungen, Modellintegrationen und Tools.
├── docker // Konfigurationen im Zusammenhang mit Docker und Containerisierung.
├── events // Ereignisbehandlung und -verarbeitung
├── extensions // Erweiterungen mit Frameworks/Plattformen von Drittanbietern.
├── fields // Felddefinitionen für die Serialisierung/Marshalling.
├── libs // Wiederverwendbare Bibliotheken und Hilfsprogramme
├── migrations // Skripte für die Datenbankmigration.
├── models // Datenbankmodelle und Schemadefinitionen.
├── services // Gibt die Geschäftslogik an.
├── storage // Speicherung privater Schlüssel.
├── tasks // Handhabung von asynchronen Aufgaben und Hintergrundaufträgen.
└── tests
```
### Frontend
Die Website basiert auf einem [Next.js](https://nextjs.org/)-Boilerplate in TypeScript und verwendet [Tailwind CSS](https://tailwindcss.com/) für das Styling. [React-i18next](https://react.i18next.com/) wird für die Internationalisierung genutzt.
```text
[web/]
├── app // Layouts, Seiten und Komponenten
│ ├── (commonLayout) // gemeinsames Layout für die gesamte Anwendung
│ ├── (shareLayout) // Layouts, die speziell für tokenspezifische Sitzungen gemeinsam genutzt werden
│ ├── activate // Seite aufrufen
│ ├── components // gemeinsam genutzt von Seiten und Layouts
│ ├── install // Seite installieren
│ ├── signin // Anmeldeseite
│ └── styles // global geteilte Stile
├── assets // Statische Vermögenswerte
├── bin // Skripte, die beim Build-Schritt ausgeführt werden
├── config // einstellbare Einstellungen und Optionen
├── context // gemeinsame Kontexte, die von verschiedenen Teilen der Anwendung verwendet werden
├── dictionaries // Sprachspezifische Übersetzungsdateien
├── docker // Container-Konfigurationen
├── hooks // Wiederverwendbare Haken
├── i18n // Konfiguration der Internationalisierung
├── models // beschreibt Datenmodelle und Formen von API-Antworten
├── public // Meta-Assets wie Favicon
├── service // legt Formen von API-Aktionen fest
├── test
├── types // Beschreibungen von Funktionsparametern und Rückgabewerten
└── utils // Gemeinsame Nutzenfunktionen
```
## Einreichung Ihrer PR
Am Ende ist es Zeit, einen Pull Request (PR) in unserem Repository zu eröffnen. Für wesentliche Features mergen wir diese zunächst in den `deploy/dev`-Branch zum Testen, bevor sie in den `main`-Branch übernommen werden. Falls Sie auf Probleme wie Merge-Konflikte stoßen oder nicht wissen, wie man einen Pull Request erstellt, schauen Sie sich [GitHub's Pull Request Tutorial](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) an.
Und das war's! Sobald Ihr PR gemerged wurde, werden Sie als Mitwirkender in unserem [README](https://github.com/langgenius/dify/blob/main/README.md) aufgeführt.
Bei Problemen während der Einrichtung können Sie sich gerne an uns wenden.
## Hilfe bekommen
Wenn Sie beim Beitragen jemals nicht weiter wissen oder eine brennende Frage haben, richten Sie Ihre Anfrage einfach über das entsprechende GitHub-Issue an uns oder besuchen Sie unseren [Discord](https://discord.gg/8Tpq4AcN9c) für ein kurzes Gespräch.
Wenn Sie beim Mitwirken Fragen haben oder nicht weiterkommen, stellen Sie Ihre Fragen einfach im entsprechenden GitHub Issue oder besuchen Sie unseren [Discord](https://discord.gg/8Tpq4AcN9c) für einen schnellen Austausch.

View File

@ -1,160 +1,97 @@
Dify にコントリビュートしたいとお考えなのですね。それは素晴らしいことです。
私たちは、LLM アプリケーションの構築と管理のための最も直感的なワークフローを設計するという壮大な野望を持っています。人数も資金も限られている新興企業として、コミュニティからの支援は本当に重要です。
# 貢献ガイド
私たちは現状を鑑み、機敏かつ迅速に開発をする必要がありますが、同時にあなた様のようなコントリビューターの方々に、可能な限りスムーズな貢献体験をしていただきたいと思っています。そのためにこのコントリビュートガイドを作成しました。
コードベースやコントリビュータの方々と私たちがどのように仕事をしているのかに慣れていただき、楽しいパートにすぐに飛び込めるようにすることが目的です。
Difyに貢献しようとお考えですか素晴らしいですね。私たちは、あなたがどのような貢献をしてくださるのか、とても楽しみにしています。スタートアップとして限られた人員と資金の中で、LLMアプリケーションの構築と管理のための最も直感的なワークフローを設計するという大きな目標を持っています。コミュニティからのあらゆる支援が、本当に重要な意味を持ちます。
このガイドは Dify そのものと同様に、継続的に改善されています。実際のプロジェクトに遅れをとることがあるかもしれませんが、ご理解のほどよろしくお願いいたします。
私たちは迅速に開発を進める必要がありますが、同時に貢献者の皆様にとってスムーズな経験を提供したいと考えています。このガイドは、コードベースと私たちの貢献者との協働方法を理解していただき、すぐに楽しい開発に取り掛かれるようにすることを目的としています。
ライセンスに関しては、私たちの短い[ライセンスおよびコントリビューター規約](./LICENSE)をお読みください。また、コミュニティは[行動規範](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)を遵守しています。
このガイドは、Dify自体と同様に、常に進化し続けています。実際のプロジェクトの進行状況と多少のずれが生じる場合もございますが、ご理解いただけますと幸いです。改善のためのフィードバックも歓迎いたします。
## 飛び込む前に
ライセンスについては、[ライセンスと貢献者同意書](./LICENSE)をご一読ください。また、コミュニティは[行動規範](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)に従っています。
[既存の Issue](https://github.com/langgenius/dify/issues?q=is:issue+is:open) を探すか、[新しい Issue](https://github.com/langgenius/dify/issues/new/choose) を作成してください。私たちは Issue を 2 つのタイプに分類しています。
## 始める前に
取り組むべき課題をお探しですか?[初心者向けの課題](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 | 低 |
### 機能リクエスト
* 新しい機能要望を出す場合は、提案する機能が何を実現するものなのかを説明し、可能な限り多くのコンテキストを含めてください。[@perzeusss](https://github.com/perzeuss)は、あなた様の要望を書き出すのに役立つ [Feature Request Copilot](https://udify.app/chat/MK2kVSnw1gakVwMX) を作ってくれました。気軽に試してみてください。
> [!NOTE]
> 機能リクエスト時には、以下の情報を必ず含めてください:
* 既存の課題から 1 つ選びたい場合は、その下にコメントを書いてください。
- 明確で分かりやすいタイトル
- 機能の詳細な説明
- 使用事例
- その他の文脈や画面のスクリーンショット
関連する方向で作業しているチームメンバーが参加します。すべてが良好であれば、コーディングを開始する許可が与えられます。私たちが変更を提案した場合にあなた様の作業が無駄になることがないよう、それまでこの機能の作業を控えていただくようお願いいたします。
優先順位の付け方:
提案された機能がどの分野に属するかによって、あなた様は異なるチーム・メンバーと話をするかもしれません。以下は、各チームメンバーが現在取り組んでいる分野の概要です。
| 機能の種類 | 優先度 |
| ------------------------------------------------------------ | --------- |
| チームメンバーによって高優先度とラベル付けされた機能 | 高 |
| [コミュニティフィードボード](https://github.com/langgenius/dify/discussions/categories/feedbacks)での人気の機能リクエスト | 中程度 |
| 非コア機能と軽微な改善 | 低 |
| 価値はあるが緊急性の低いもの | 将来対応 |
| Member | Scope |
| --------------------------------------------------------------------------------------- | ------------------------------------ |
| [@yeuoly](https://github.com/Yeuoly) | エージェントアーキテクチャ |
| [@jyong](https://github.com/JohnJyong) | RAG パイプライン設計 |
| [@GarfieldDai](https://github.com/GarfieldDai) | workflow orchestrations の構築 |
| [@iamjoel](https://github.com/iamjoel) & [@zxhlyh](https://github.com/zxhlyh) | フロントエンドを使いやすくする |
| [@guchenhe](https://github.com/guchenhe) & [@crazywoola](https://github.com/crazywoola) | 開発者体験、何でも相談できる窓口 |
| [@takatost](https://github.com/takatost) | 全体的な製品の方向性とアーキテクチャ |
## PRの提出
優先順位の付け方:
### プルリクエストのプロセス
| Feature Type | Priority |
| --------------------------------------------------------------------------------------------------------------------- | --------------- |
| チームメンバーによってラベル付けされた優先度の高い機能 | High Priority |
| [community feedback board](https://github.com/langgenius/dify/discussions/categories/feedbacks)の人気の機能リクエスト | Medium Priority |
| 非コア機能とマイナーな機能強化 | Low Priority |
| 価値はあるが即効性はない | Future-Feature |
1. リポジトリをフォークする
2. PRを作成する前に、変更内容についてイシューで議論する
3. 変更用の新しいブランチを作成する
4. 変更に応じたテストを追加する
5. 既存のテストをパスすることを確認する
6. PRの説明文にイシューをリンクする`fixes #<issue_number>`
7. マージ完了!
### その他 (バグレポート、パフォーマンスの最適化、誤字の修正など)
### プロジェクトのセットアップ
* すぐにコーディングを始めてください
#### フロントエンド
優先順位の付け方:
フロントエンドサービスのセットアップについては、`web/README.md`の[ガイド](https://github.com/langgenius/dify/blob/main/web/README.md)を参照してください。このドキュメントには、フロントエンド環境を適切にセットアップするための詳細な手順が記載されています。
| Issue Type | Priority |
| -------------------------------------------------------------------------------------- | --------------- |
| コア機能のバグ(ログインできない、アプリケーションが動作しない、セキュリティの抜け穴) | Critical |
| 致命的でないバグ、パフォーマンス向上 | Medium Priority |
| 細かな修正(誤字脱字、機能はするが分かりにくい UI | Low Priority |
#### バックエンド
## インストール
バックエンドサービスのセットアップについては、`api/README.md`の[手順](https://github.com/langgenius/dify/blob/main/api/README.md)を参照してください。このドキュメントには、バックエンドを正しく動作させるためのステップバイステップのガイドが含まれています。
以下の手順で 、Difyのセットアップをしてください。
#### その他の注意点
### 1. このリポジトリをフォークする
セットアップを進める前に、以下の重要な情報が含まれているため、このドキュメントを注意深く確認することをお勧めします:
- 前提条件と依存関係
- インストール手順
- 設定の詳細
- 一般的なトラブルシューティングのヒント
### 2. リポジトリをクローンする
セットアップ中に問題が発生した場合は、お気軽にお問い合わせください。
フォークしたリポジトリをターミナルからクローンします。
## サポートを受ける
```
git clone git@github.com:<github_username>/dify.git
```
貢献中に行き詰まったり、緊急の質問がある場合は、関連するGitHubイシューで質問するか、[Discord](https://discord.gg/8Tpq4AcN9c)で気軽にチャットしてください。
### 3. 依存関係の確認
Dify を構築するには次の依存関係が必要です。それらがシステムにインストールされていることを確認してください。
- [Docker](https://www.docker.com/)
- [Docker Compose](https://docs.docker.com/compose/install/)
- [Node.js v18.x (LTS)](http://nodejs.org)
- [pnpm](https://pnpm.io/)
- [Python](https://www.python.org/) version 3.11.x or 3.12.x
### 4. インストール
Dify はバックエンドとフロントエンドから構成されています。
まず`cd api/`でバックエンドのディレクトリに移動し、[Backend README](api/README.md)に従ってインストールします。
次に別のターミナルで、`cd web/`でフロントエンドのディレクトリに移動し、[Frontend README](web/README.md)に従ってインストールしてください。
よくある問題とトラブルシューティングの手順については、[installation FAQ](https://docs.dify.ai/v/japanese/learn-more/faq/install-faq) を確認してください。
### 5. ブラウザで dify にアクセスする
設定を確認するために、ブラウザで[http://localhost:3000](http://localhost:3000)(デフォルト、または自分で設定した URL とポート)にアクセスしてください。Dify が起動して実行中であることが確認できるはずです。
## 開発中
モデルプロバイダーを追加する場合は、[このガイド](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/README.md)が役立ちます。
Agent や Workflow にツールプロバイダーを追加する場合は、[このガイド](./api/core/tools/README.md)が役立ちます。
Dify のバックエンドとフロントエンドの概要を簡単に説明します。
### バックエンド
Dify のバックエンドは[Flask](https://flask.palletsprojects.com/en/3.0.x/)を使って Python で書かれています。ORM には[SQLAlchemy](https://www.sqlalchemy.org/)を、タスクキューには[Celery](https://docs.celeryq.dev/en/stable/getting-started/introduction.html)を使っています。認証ロジックは Flask-login 経由で行われます。
```
[api/]
├── constants // コードベース全体で使用される定数設定
├── controllers // APIルート定義とリクエスト処理ロジック
├── core // アプリケーションの中核的な管理、モデル統合、およびツール
├── docker // Dockerおよびコンテナ関連の設定
├── events // イベントのハンドリングと処理
├── extensions // 第三者のフレームワーク/プラットフォームとの拡張
├── fields // シリアライゼーション/マーシャリング用のフィールド定義
├── libs // 再利用可能なライブラリとヘルパー
├── migrations // データベースマイグレーションスクリプト
├── models // データベースモデルとスキーマ定義
├── services // ビジネスロジックの定義
├── storage // 秘密鍵の保存
├── tasks // 非同期タスクとバックグラウンドジョブの処理
└── tests // テスト関連のファイル
```
### フロントエンド
このウェブサイトは、Typescriptベースの[Next.js](https://nextjs.org/)テンプレートを使ってブートストラップされ、[Tailwind CSS](https://tailwindcss.com/)を使ってスタイリングされています。国際化には[React-i18next](https://react.i18next.com/)を使用しています。
```
[web/]
├── app // レイアウト、ページ、コンポーネント
│ ├── (commonLayout) // アプリ全体で共通のレイアウト
│ ├── (shareLayout) // トークン特有のセッションで共有されるレイアウト
│ ├── activate // アクティベートページ
│ ├── components // ページやレイアウトで共有されるコンポーネント
│ ├── install // インストールページ
│ ├── signin // サインインページ
│ └── styles // グローバルに共有されるスタイル
├── assets // 静的アセット
├── bin // ビルドステップで実行されるスクリプト
├── config // 調整可能な設定とオプション
├── context // アプリの異なる部分で使用される共有コンテキスト
├── dictionaries // 言語別の翻訳ファイル
├── docker // コンテナ設定
├── hooks // 再利用可能なフック
├── i18n // 国際化設定
├── models // データモデルとAPIレスポンスの形状を記述
├── public // ファビコンなどのメタアセット
├── service // APIアクションの形状を指定
├── test
├── types // 関数のパラメータと戻り値の記述
└── utils // 共有ユーティリティ関数
```
## PR を投稿する
いよいよ、私たちのリポジトリにプルリクエスト (PR) を提出する時が来ました。主要な機能については、まず `deploy/dev` ブランチにマージしてテストしてから `main` ブランチにマージします。
マージ競合などの問題が発生した場合、またはプル リクエストを開く方法がわからない場合は、[GitHub's pull request tutorial](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) をチェックしてみてください。
これで完了です!あなた様の PR がマージされると、[README](https://github.com/langgenius/dify/blob/main/README.md) にコントリビューターとして紹介されます。
## ヘルプを得る
コントリビュート中に行き詰まったり、疑問が生じたりした場合は、GitHub の関連する issue から質問していただくか、[Discord](https://discord.gg/8Tpq4AcN9c)でチャットしてください。

View File

@ -1,153 +1,97 @@
# 貢獻指南
# 參與貢獻
您想為 Dify 做出貢獻 - 這太棒了,我們迫不及待地想看看您的成果。作為一家人力和資金有限的初創公司,我們有宏大的抱負,希望設計出最直觀的工作流程來構建和管理 LLM 應用程式。來自社群的任何幫助都非常珍貴,真的
我們很高興你想要為 Dify 做出貢獻!作為一個資源有限的新創團隊,我們期望打造最直觀的 LLM 應用開發與管理工作流程。社群中的每一份貢獻對我們來說都非常重要
鑑於我們的現狀,我們需要靈活且快速地發展,但同時也希望確保像您這樣的貢獻者能夠獲得盡可能順暢的貢獻體驗。我們編寫了這份貢獻指南,目的是幫助您熟悉代碼庫以及我們如何與貢獻者合作,讓您可以更快地進入有趣的部分
作為一個快速發展的專案,我們需要保持敏捷並快速迭代,同時也希望能為貢獻者提供順暢的參與體驗。我們準備了這份貢獻指南,幫助你了解程式碼庫和我們與貢獻者合作的方式,讓你能夠盡快投入有趣的開發工作
這份指南,就像 Dify 本身一樣,是不斷發展的。如果有時它落後於實際項目,我們非常感謝您的理解,也歡迎任何改進的反饋
這份指南與 Dify 一樣,都在持續完善中。如果指南內容有落後於實際專案的情況,還請見諒,也歡迎提供改進建議
關於授權,請花一分鐘閱讀我們簡短的[授權和貢獻者協議](./LICENSE)。社群也遵守[行為準則](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
關於授權部分,請花點時間閱讀我們簡短的[授權和貢獻者協議](./LICENSE)。社群也遵守[行為準則](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
## 開始之前
## 開始之前
[尋找](https://github.com/langgenius/dify/issues?q=is:issue+is:open)現有的 issue或[創建](https://github.com/langgenius/dify/issues/new/choose)一個新的。我們將 issues 分為 2 種類型:
想找點事做?瀏覽我們的[新手友善議題](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 logs 中找到
- 截圖或影片(如適用)
優先順序評估:
| 議題類型 | 優先級 |
| -------- | ------ |
| 核心功能錯誤(雲端服務、無法登入、應用程式無法運作、安全漏洞) | 緊急 |
| 非緊急錯誤、效能優化 | 中等 |
| 次要修正(拼字錯誤、介面混淆但可運作) | 低 |
### 功能請求
- 如果您要開啟新的功能請求,我們希望您能解釋所提議的功能要達成什麼目標,並且盡可能包含更多的相關背景資訊。[@perzeusss](https://github.com/perzeuss) 已經製作了一個實用的[功能請求輔助工具](https://udify.app/chat/MK2kVSnw1gakVwMX),能幫助您草擬您的需求。歡迎試用。
> [!NOTE]
> 提交功能請求時,請務必包含以下資訊:
- 如果您想從現有問題中選擇一個來處理,只需在其下方留言表示即可。
- 清晰明確的標題
- 詳細的功能描述
- 功能的使用情境
- 其他相關背景說明或截圖
相關方向的團隊成員會加入討論。如果一切順利,他們會同意您開始編寫代碼。我們要求您在得到許可前先不要開始處理該功能,以免我們提出變更時您的工作成果被浪費。
優先順序評估:
根據所提議功能的領域不同,您可能會與不同的團隊成員討論。以下是目前每位團隊成員所負責的領域概述:
| 功能類型 | 優先級 |
| -------- | ------ |
| 團隊成員標記為高優先級的功能 | 高 |
| 來自[社群回饋板](https://github.com/langgenius/dify/discussions/categories/feedbacks)的熱門功能請求 | 中 |
| 非核心功能和小幅改進 | 低 |
| 有價值但非急迫的功能 | 未來功能 |
| 成員 | 負責領域 |
| --------------------------------------------------------------------------------------- | ------------------------------ |
| [@yeuoly](https://github.com/Yeuoly) | 設計 Agents 架構 |
| [@jyong](https://github.com/JohnJyong) | RAG 管道設計 |
| [@GarfieldDai](https://github.com/GarfieldDai) | 建構工作流程編排 |
| [@iamjoel](https://github.com/iamjoel) & [@zxhlyh](https://github.com/zxhlyh) | 打造易用的前端界面 |
| [@guchenhe](https://github.com/guchenhe) & [@crazywoola](https://github.com/crazywoola) | 開發者體驗,各類問題的聯絡窗口 |
| [@takatost](https://github.com/takatost) | 整體產品方向與架構 |
## 提交 PR
我們如何排定優先順序:
### PR 流程
| 功能類型 | 優先級 |
| ------------------------------------------------------------------------------------------------------- | -------- |
| 被團隊成員標記為高優先級的功能 | 高優先級 |
| 來自我們[社群回饋版](https://github.com/langgenius/dify/discussions/categories/feedbacks)的熱門功能請求 | 中優先級 |
| 非核心功能和次要增強 | 低優先級 |
| 有價值但非急迫的功能 | 未來功能 |
1. Fork 專案
2. 在開始撰寫 PR 前,請先建立議題討論你想做的更改
3. 為你的更改建立新分支
4. 請為你的更改新增相應的測試
5. 確保你的程式碼通過現有測試
6. 請在 PR 描述中連結相關議題,使用 `fixes #<issue_number>`
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)。此文件包含逐步指引,幫助你順利啟動後端服務。
以下是設置 Dify 開發環境的步驟:
#### 其他注意事項
### 1. 分叉此存儲庫
我們建議在開始設定前仔細閱讀此文件,因為它包含以下重要資訊:
- 前置需求和相依性
- 安裝步驟
- 設定細節
- 常見問題排解
### 2. 複製代碼庫
如果在設定過程中遇到任何問題,歡迎隨時詢問。
從您的終端機複製分叉的代碼庫:
## 尋求協助
```shell
git clone git@github.com:<github_username>/dify.git
```
如果你在貢獻過程中遇到困難或有急切的問題,可以透過相關的 GitHub 議題詢問,或加入我們的 [Discord](https://discord.gg/8Tpq4AcN9c) 進行即時交流。
- [Docker](https://www.docker.com/)
- [Docker Compose](https://docs.docker.com/compose/install/)
- [Node.js v18.x (LTS)](http://nodejs.org)
- [pnpm](https://pnpm.io/)
- [Python](https://www.python.org/) version 3.11.x or 3.12.x
### 4. 安裝
Dify 由後端和前端組成。透過 `cd api/` 導航至後端目錄,然後按照[後端 README](api/README.md)進行安裝。在另一個終端機視窗中,透過 `cd web/` 導航至前端目錄,然後按照[前端 README](web/README.md)進行安裝。
查閱[安裝常見問題](https://docs.dify.ai/learn-more/faq/install-faq)了解常見問題和故障排除步驟的列表。
### 5. 在瀏覽器中訪問 Dify
要驗證您的設置,請在瀏覽器中訪問 [http://localhost:3000](http://localhost:3000)(預設值,或您自行設定的 URL 和埠號)。現在您應該能看到 Dify 已啟動並運行。
## 開發
如果您要添加模型提供者,請參考[此指南](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/README.md)。
如果您要為 Agent 或工作流程添加工具提供者,請參考[此指南](./api/core/tools/README.md)。
為了幫助您快速找到您的貢獻適合的位置,以下是 Dify 後端和前端的簡要註解大綱:
### 後端
Dify 的後端使用 Python 的 [Flask](https://flask.palletsprojects.com/en/3.0.x/) 框架編寫。它使用 [SQLAlchemy](https://www.sqlalchemy.org/) 作為 ORM 工具,使用 [Celery](https://docs.celeryq.dev/en/stable/getting-started/introduction.html) 進行任務佇列處理。授權邏輯則透過 Flask-login 實現。
```text
[api/]
├── constants // 整個專案中使用的常數與設定值
├── controllers // API 路由定義與請求處理邏輯
├── core // 核心應用服務、模型整合與工具實現
├── docker // Docker 容器化相關設定檔案
├── events // 事件處理與流程管理機制
├── extensions // 與第三方框架或平台的整合擴充功能
├── fields // 資料序列化與結構定義欄位
├── libs // 可重複使用的共用程式庫與輔助工具
├── migrations // 資料庫結構變更與遷移腳本
├── models // 資料庫模型與資料結構定義
├── services // 核心業務邏輯與功能實現
├── storage // 私鑰與敏感資訊儲存機制
├── tasks // 非同步任務與背景作業處理器
└── tests
```
### 前端
網站基於 [Next.js](https://nextjs.org/) 的 Typescript 樣板,並使用 [Tailwind CSS](https://tailwindcss.com/) 進行樣式設計。[React-i18next](https://react.i18next.com/) 用於國際化。
```text
[web/]
├── app // 頁面佈局與介面元件
│ ├── (commonLayout) // 應用程式共用佈局結構
│ ├── (shareLayout) // Token 會話專用共享佈局
│ ├── activate // 帳號啟用頁面
│ ├── components // 頁面與佈局共用元件
│ ├── install // 系統安裝頁面
│ ├── signin // 使用者登入頁面
│ └── styles // 全域共用樣式定義
├── assets // 靜態資源檔案庫
├── bin // 建構流程執行腳本
├── config // 系統可調整設定與選項
├── context // 應用程式狀態共享上下文
├── dictionaries // 多語系翻譯詞彙庫
├── docker // Docker 容器設定檔
├── hooks // 可重複使用的 React Hooks
├── i18n // 國際化與本地化設定
├── models // 資料結構與 API 回應模型
├── public // 靜態資源與網站圖標
├── service // API 操作介面定義
├── test // 測試用例與測試框架
├── types // TypeScript 型別定義
└── utils // 共用輔助功能函式庫
```
## 提交您的 PR
最後是時候向我們的存儲庫開啟拉取請求PR了。對於主要功能我們會先將它們合併到 `deploy/dev` 分支進行測試,然後才會進入 `main` 分支。如果您遇到合併衝突或不知道如何開啟拉取請求等問題,請查看 [GitHub 的拉取請求教學](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests)。
就是這樣!一旦您的 PR 被合併,您將作為貢獻者出現在我們的 [README](https://github.com/langgenius/dify/blob/main/README.md) 中。
## 獲取幫助
如果您在貢獻過程中遇到困難或有迫切的問題,只需通過相關的 GitHub issue 向我們提問,或加入我們的 [Discord](https://discord.gg/8Tpq4AcN9c) 進行快速交流。

View File

@ -1,156 +1,97 @@
Thật tuyệt vời khi bạn muốn đóng góp cho Dify! Chúng tôi rất mong chờ được thấy những gì bạn sẽ làm. Là một startup với nguồn nhân lực và tài chính hạn chế, chúng tôi có tham vọng lớn là thiết kế quy trình trực quan nhất để xây dựng và quản lý các ứng dụng LLM. Mọi sự giúp đỡ từ cộng đồng đều rất quý giá đối với chúng tôi.
# ĐÓNG GÓP
Chúng tôi cần linh hoạt và làm việc nhanh chóng, nhưng đồng thời cũng muốn đảm bảo các cộng tác viên như bạn có trải nghiệm đóng góp thuận lợi nhất có thể. Chúng tôi đã tạo ra hướng dẫn đóng góp này nhằm giúp bạn làm quen với codebase và cách chúng tôi làm việc với các cộng tác viên, để bạn có thể nhanh chóng bắt tay vào phần thú vị.
Bạn đang muốn đóng góp cho Dify - thật tuyệt vời, chúng tôi rất mong được thấy những gì bạn sẽ làm. Là một startup với nguồn nhân lực và tài chính hạn chế, chúng tôi có tham vọng lớn trong việc thiết kế quy trình trực quan nhất để xây dựng và quản lý các ứng dụng LLM. Mọi sự giúp đỡ từ cộng đồng đều rất có ý nghĩa.
Hướng dẫn này, cũng như bản thân Dify, đang trong quá trình cải tiến liên tục. Chúng tôi rất cảm kích sự thông cảm của bạn nếu đôi khi nó không theo kịp dự án thực tế, và chúng tôi luôn hoan nghênh mọi phản hồi để cải thiện.
Chúng tôi cần phải nhanh nhẹn và triển khai nhanh chóng, nhưng cũng muốn đảm bảo những người đóng góp như bạn có trải nghiệm đóng góp thuận lợi nhất có thể. Chúng tôi đã tạo hướng dẫn đóng góp này nhằm giúp bạn làm quen với codebase và cách chúng tôi làm việc với người đóng góp, để bạn có thể nhanh chóng bắt đầu phần thú vị.
Về vấn đề cấp phép, xin vui lòng dành chút thời gian đọc qua [Thỏa thuận Cấp phép và Đóng góp](./LICENSE) ngắn gọn của chúng tôi. Cộng đồng cũng tuân thủ [quy tắc ứng xử](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
Hướng dẫn này, giống như Dify, đang được phát triển liên tục. Chúng tôi rất cảm kích sự thông cảm của bạn nếu đôi khi nó chưa theo kịp dự án thực tế, và hoan nghênh mọi phản hồi để cải thiện.
Về giấy phép, vui lòng dành chút thời gian đọc [Thỏa thuận Cấp phép và Người đóng góp](./LICENSE) ngắn gọn của chúng tôi. Cộng đồng cũng tuân theo [quy tắc ứng xử](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Trước khi bắt đầu
[Tìm kiếm](https://github.com/langgenius/dify/issues?q=is:issue+is:open) một vấn đề hiện có, hoặc [tạo mới](https://github.com/langgenius/dify/issues/new/choose) một vấn đề. Chúng tôi phân loại các vấn đề thành 2 loại:
Đang tìm việc để thực hiện? Hãy xem qua [các issue dành cho người mới](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) và chọn một để bắt đầu!
### Yêu cầu tính năng:
Bạn có một model runtime hoặc công cụ mới thú vị để thêm vào? Mở PR trong [repo plugin](https://github.com/langgenius/dify-plugins) của chúng tôi và cho chúng tôi thấy những gì bạn đã xây dựng.
* Nếu bạn đang tạo một yêu cầu tính năng mới, chúng tôi muốn bạn giải thích tính năng đề xuất sẽ đạt được điều gì và cung cấp càng nhiều thông tin chi tiết càng tốt. [@perzeusss](https://github.com/perzeuss) đã tạo một [Trợ lý Yêu cầu Tính năng](https://udify.app/chat/MK2kVSnw1gakVwMX) rất hữu ích để giúp bạn soạn thảo nhu cầu của mình. Hãy thử dùng nó nhé.
Cần cập nhật model runtime, công cụ hiện có hoặc sửa lỗi? Ghé thăm [repo plugin chính thức](https://github.com/langgenius/dify-official-plugins) và thực hiện phép màu của bạn!
* Nếu bạn muốn chọn một vấn đề từ danh sách hiện có, chỉ cần để lại bình luận dưới vấn đề đó nói rằng bạn sẽ làm.
Hãy tham gia, đóng góp và cùng nhau xây dựng điều tuyệt vời! 💡✨
Một thành viên trong nhóm làm việc trong lĩnh vực liên quan sẽ được thông báo. Nếu mọi thứ ổn, họ sẽ cho phép bạn bắt đầu code. Chúng tôi yêu cầu bạn chờ đợi cho đến lúc đó trước khi bắt tay vào làm tính năng, để không lãng phí công sức của bạn nếu chúng tôi đề xuất thay đổi.
Đừng quên liên kết đến issue hiện có hoặc mở issue mới trong mô tả PR.
Tùy thuộc vào lĩnh vực mà tính năng đề xuất thuộc về, bạn có thể nói chuyện với các thành viên khác nhau trong nhóm. Dưới đây là danh sách các lĩnh vực mà các thành viên trong nhóm chúng tôi đang làm việc hiện tại:
### Báo cáo lỗi
| Thành viên | Phạm vi |
| ------------------------------------------------------------ | ---------------------------------------------------- |
| [@yeuoly](https://github.com/Yeuoly) | Thiết kế kiến trúc Agents |
| [@jyong](https://github.com/JohnJyong) | Thiết kế quy trình RAG |
| [@GarfieldDai](https://github.com/GarfieldDai) | Xây dựng quy trình làm việc |
| [@iamjoel](https://github.com/iamjoel) & [@zxhlyh](https://github.com/zxhlyh) | Làm cho giao diện người dùng dễ sử dụng |
| [@guchenhe](https://github.com/guchenhe) & [@crazywoola](https://github.com/crazywoola) | Trải nghiệm nhà phát triển, đầu mối liên hệ cho mọi vấn đề |
| [@takatost](https://github.com/takatost) | Định hướng và kiến trúc tổng thể sản phẩm |
> [!QUAN TRỌNG]
> Vui lòng đảm bảo cung cấp các thông tin sau khi gửi báo cáo lỗi:
Cách chúng tôi ưu tiên:
- Tiêu đề rõ ràng và mô tả
- Mô tả chi tiết về lỗi, bao gồm các thông báo lỗi
- Các bước để tái hiện lỗi
- Hành vi mong đợi
- **Log**, nếu có, cho các vấn đề backend, điều này rất quan trọng, bạn có thể tìm thấy chúng trong docker-compose logs
- Ảnh chụp màn hình hoặc video, nếu có thể
| Loại tính năng | Mức độ ưu tiên |
| ------------------------------------------------------------ | -------------- |
| Tính năng ưu tiên cao được gắn nhãn bởi thành viên trong nhóm | Ưu tiên cao |
| Yêu cầu tính năng phổ biến từ [bảng phản hồi cộng đồng](https://github.com/langgenius/dify/discussions/categories/feedbacks) của chúng tôi | Ưu tiên trung bình |
| Tính năng không quan trọng và cải tiến nhỏ | Ưu tiên thấp |
| Có giá trị nhưng không cấp bách | Tính năng tương lai |
Cách chúng tôi ưu tiên:
### Những vấn đề khác (ví dụ: báo cáo lỗi, tối ưu hiệu suất, sửa lỗi chính tả):
| Loại vấn đề | Mức độ ưu tiên |
| ----------- | -------------- |
| Lỗi trong các chức năng cốt lõi (dịch vụ đám mây, không thể đăng nhập, ứng dụng không hoạt động, lỗ hổng bảo mật) | Quan trọng |
| Lỗi không nghiêm trọng, cải thiện hiệu suất | Ưu tiên trung bình |
| Sửa lỗi nhỏ (lỗi chính tả, UI gây nhầm lẫn nhưng vẫn hoạt động) | Ưu tiên thấp |
* Bắt đầu code ngay lập tức.
### Yêu cầu tính năng
Cách chúng tôi ưu tiên:
> [!LƯU Ý]
> Vui lòng đảm bảo cung cấp các thông tin sau khi gửi yêu cầu tính năng:
| Loại vấn đề | Mức độ ưu tiên |
| ------------------------------------------------------------ | -------------- |
| Lỗi trong các chức năng chính (không thể đăng nhập, ứng dụng không hoạt động, lỗ hổng bảo mật) | Nghiêm trọng |
| Lỗi không quan trọng, cải thiện hiệu suất | Ưu tiên trung bình |
| Sửa lỗi nhỏ (lỗi chính tả, giao diện người dùng gây nhầm lẫn nhưng vẫn hoạt động) | Ưu tiên thấp |
- Tiêu đề rõ ràng và mô tả
- Mô tả chi tiết về tính năng
- Trường hợp sử dụng cho tính năng
- Bất kỳ ngữ cảnh hoặc ảnh chụp màn hình nào về yêu cầu tính năng
Cách chúng tôi ưu tiên:
## Cài đặt
Dưới đây là các bước để thiết lập Dify cho việc phát triển:
### 1. Fork repository này
### 2. Clone repository
Clone repository đã fork từ terminal của bạn:
```
git clone git@github.com:<tên_người_dùng_github>/dify.git
```
### 3. Kiểm tra các phụ thuộc
Dify yêu cầu các phụ thuộc sau để build, hãy đảm bảo chúng đã được cài đặt trên hệ thống của bạn:
- [Docker](https://www.docker.com/)
- [Docker Compose](https://docs.docker.com/compose/install/)
- [Node.js v18.x (LTS)](http://nodejs.org)
- [pnpm](https://pnpm.io/)
- [Python](https://www.python.org/) phiên bản 3.11.x hoặc 3.12.x
### 4. Cài đặt
Dify bao gồm một backend và một frontend. Đi đến thư mục backend bằng lệnh `cd api/`, sau đó làm theo hướng dẫn trong [README của Backend](api/README.md) để cài đặt. Trong một terminal khác, đi đến thư mục frontend bằng lệnh `cd web/`, sau đó làm theo hướng dẫn trong [README của Frontend](web/README.md) để cài đặt.
Kiểm tra [FAQ về cài đặt](https://docs.dify.ai/learn-more/faq/install-faq) để xem danh sách các vấn đề thường gặp và các bước khắc phục.
### 5. Truy cập Dify trong trình duyệt của bạn
Để xác nhận cài đặt của bạn, hãy truy cập [http://localhost:3000](http://localhost:3000) (địa chỉ mặc định, hoặc URL và cổng bạn đã cấu hình) trong trình duyệt. Bạn sẽ thấy Dify đang chạy.
## Phát triển
Nếu bạn đang thêm một nhà cung cấp mô hình, [hướng dẫn này](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/README.md) dành cho bạn.
Nếu bạn đang thêm một nhà cung cấp công cụ cho Agent hoặc Workflow, [hướng dẫn này](./api/core/tools/README.md) dành cho bạn.
Để giúp bạn nhanh chóng định hướng phần đóng góp của mình, dưới đây là một bản phác thảo ngắn gọn về cấu trúc backend & frontend của Dify:
### Backend
Backend của Dify được viết bằng Python sử dụng [Flask](https://flask.palletsprojects.com/en/3.0.x/). Nó sử dụng [SQLAlchemy](https://www.sqlalchemy.org/) cho ORM và [Celery](https://docs.celeryq.dev/en/stable/getting-started/introduction.html) cho hàng đợi tác vụ. Logic xác thực được thực hiện thông qua Flask-login.
```
[api/]
├── constants // Các cài đặt hằng số được sử dụng trong toàn bộ codebase.
├── controllers // Định nghĩa các route API và logic xử lý yêu cầu.
├── core // Điều phối ứng dụng cốt lõi, tích hợp mô hình và công cụ.
├── docker // Cấu hình liên quan đến Docker & containerization.
├── events // Xử lý và xử lý sự kiện
├── extensions // Mở rộng với các framework/nền tảng bên thứ 3.
├── fields // Định nghĩa trường cho serialization/marshalling.
├── libs // Thư viện và tiện ích có thể tái sử dụng.
├── migrations // Script cho việc di chuyển cơ sở dữ liệu.
├── models // Mô hình cơ sở dữ liệu & định nghĩa schema.
├── services // Xác định logic nghiệp vụ.
├── storage // Lưu trữ khóa riêng tư.
├── tasks // Xử lý các tác vụ bất đồng bộ và công việc nền.
└── tests
```
### Frontend
Website được khởi tạo trên boilerplate [Next.js](https://nextjs.org/) bằng Typescript và sử dụng [Tailwind CSS](https://tailwindcss.com/) cho styling. [React-i18next](https://react.i18next.com/) được sử dụng cho việc quốc tế hóa.
```
[web/]
├── app // layouts, pages và components
│ ├── (commonLayout) // layout chung được sử dụng trong toàn bộ ứng dụng
│ ├── (shareLayout) // layouts được chia sẻ cụ thể cho các phiên dựa trên token
│ ├── activate // trang kích hoạt
│ ├── components // được chia sẻ bởi các trang và layouts
│ ├── install // trang cài đặt
│ ├── signin // trang đăng nhập
│ └── styles // styles được chia sẻ toàn cục
├── assets // Tài nguyên tĩnh
├── bin // scripts chạy ở bước build
├── config // cài đặt và tùy chọn có thể điều chỉnh
├── context // contexts được chia sẻ bởi các phần khác nhau của ứng dụng
├── dictionaries // File dịch cho từng ngôn ngữ
├── docker // cấu hình container
├── hooks // Hooks có thể tái sử dụng
├── i18n // Cấu hình quốc tế hóa
├── models // mô tả các mô hình dữ liệu & hình dạng của phản hồi API
├── public // tài nguyên meta như favicon
├── service // xác định hình dạng của các hành động API
├── test
├── types // mô tả các tham số hàm và giá trị trả về
└── utils // Các hàm tiện ích được chia sẻ
```
| Loại tính năng | Mức độ ưu tiên |
| -------------- | -------------- |
| Tính năng ưu tiên cao được gắn nhãn bởi thành viên nhóm | Ưu tiên cao |
| Yêu cầu tính năng phổ biến từ [bảng phản hồi cộng đồng](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Ưu tiên trung bình |
| Tính năng không cốt lõi và cải tiến nhỏ | Ưu tiên thấp |
| Có giá trị nhưng không cấp bách | Tính năng tương lai |
## Gửi PR của bạn
Cuối cùng, đã đến lúc mở một pull request (PR) đến repository của chúng tôi. Đối với các tính năng lớn, chúng tôi sẽ merge chúng vào nhánh `deploy/dev` để kiểm tra trước khi đưa vào nhánh `main`. Nếu bạn gặp vấn đề như xung đột merge hoặc không biết cách mở pull request, hãy xem [hướng dẫn về pull request của GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests).
### Quy trình tạo Pull Request
Và thế là xong! Khi PR của bạn được merge, bạn sẽ được giới thiệu là một người đóng góp trong [README](https://github.com/langgenius/dify/blob/main/README.md) của chúng tôi.
1. Fork repository
2. Trước khi soạn PR, vui lòng tạo issue để thảo luận về các thay đổi bạn muốn thực hiện
3. Tạo nhánh mới cho các thay đổi của bạn
4. Vui lòng thêm test cho các thay đổi tương ứng
5. Đảm bảo code của bạn vượt qua các test hiện có
6. Vui lòng liên kết issue trong mô tả PR, `fixes #<số_issue>`
7. Được merge!
### Thiết lập dự án
#### Frontend
Để thiết lập dịch vụ frontend, vui lòng tham khảo [hướng dẫn](https://github.com/langgenius/dify/blob/main/web/README.md) chi tiết của chúng tôi trong file `web/README.md`. Tài liệu này cung cấp hướng dẫn chi tiết để giúp bạn thiết lập môi trường frontend một cách đúng đắn.
#### Backend
Để thiết lập dịch vụ backend, vui lòng tham khảo [hướng dẫn](https://github.com/langgenius/dify/blob/main/api/README.md) chi tiết của chúng tôi trong file `api/README.md`. Tài liệu này chứa hướng dẫn từng bước để giúp bạn khởi chạy backend một cách suôn sẻ.
#### Các điểm cần lưu ý khác
Chúng tôi khuyến nghị xem xét kỹ tài liệu này trước khi tiến hành thiết lập, vì nó chứa thông tin thiết yếu về:
- Điều kiện tiên quyết và dependencies
- Các bước cài đặt
- Chi tiết cấu hình
- Các mẹo xử lý sự cố phổ biến
Đừng ngần ngại liên hệ nếu bạn gặp bất kỳ vấn đề nào trong quá trình thiết lập.
## Nhận trợ giúp
Nếu bạn gặp khó khăn hoặc có câu hỏi cấp bách trong quá trình đóng góp, hãy đặt câu hỏi của bạn trong vấn đề GitHub liên quan, hoặc tham gia [Discord](https://discord.gg/8Tpq4AcN9c) của chúng tôi để trò chuyện nhanh chóng.
Nếu bạn bị mắc kẹt hoặc có câu hỏi cấp bách trong quá trình đóng góp, chỉ cần gửi câu hỏi của bạn thông qua issue GitHub liên quan, hoặc tham gia [Discord](https://discord.gg/8Tpq4AcN9c) của chúng tôi để trò chuyện nhanh.

View File

@ -204,7 +204,9 @@ If you'd like to configure a highly-available setup, there are community-contrib
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Helm Chart by @magicsong](https://github.com/magicsong/ai-charts)
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Using Terraform for Deployment

View File

@ -187,7 +187,9 @@ docker compose up -d
- [رسم بياني Helm من قبل @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [رسم بياني Helm من قبل @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [رسم بياني Helm من قبل @magicsong](https://github.com/magicsong/ai-charts)
- [ملف YAML من قبل @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [ملف YAML من قبل @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### استخدام Terraform للتوزيع

View File

@ -203,7 +203,9 @@ GitHub-এ ডিফাইকে স্টার দিয়ে রাখুন
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Helm Chart by @magicsong](https://github.com/magicsong/ai-charts)
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### টেরাফর্ম ব্যবহার করে ডিপ্লয়

View File

@ -205,7 +205,9 @@ docker compose up -d
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Helm Chart by @magicsong](https://github.com/magicsong/ai-charts)
- [YAML 文件 by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### 使用 Terraform 部署

View File

@ -205,7 +205,9 @@ Falls Sie eine hochverfügbare Konfiguration einrichten möchten, gibt es von de
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Helm Chart by @magicsong](https://github.com/magicsong/ai-charts)
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Terraform für die Bereitstellung verwenden

View File

@ -77,9 +77,7 @@ Dify es una plataforma de desarrollo de aplicaciones de LLM de código abierto.
Amplias capacidades de RAG que cubren todo, desde la ingestión de documentos hasta la recuperación, con soporte listo para usar para la extracción de texto de PDF, PPT y otros formatos de documento comunes.
**5. Capacidades de agente**:
Puedes definir agent
es basados en LLM Function Calling o ReAct, y agregar herramientas preconstruidas o personalizadas para el agente. Dify proporciona más de 50 herramientas integradas para agentes de IA, como Búsqueda de Google, DALL·E, Difusión Estable y WolframAlpha.
Puedes definir agentes basados en LLM Function Calling o ReAct, y agregar herramientas preconstruidas o personalizadas para el agente. Dify proporciona más de 50 herramientas integradas para agentes de IA, como Búsqueda de Google, DALL·E, Difusión Estable y WolframAlpha.
**6. LLMOps**:
Supervisa y analiza registros de aplicaciones y rendimiento a lo largo del tiempo. Podrías mejorar continuamente prompts, conjuntos de datos y modelos basados en datos de producción y anotaciones.
@ -207,7 +205,9 @@ Si desea configurar una configuración de alta disponibilidad, la comunidad prop
- [Gráfico Helm por @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Gráfico Helm por @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Gráfico Helm por @magicsong](https://github.com/magicsong/ai-charts)
- [Ficheros YAML por @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [Ficheros YAML por @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Uso de Terraform para el despliegue

View File

@ -203,7 +203,9 @@ Si vous souhaitez configurer une configuration haute disponibilité, la communau
- [Helm Chart par @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart par @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Helm Chart par @magicsong](https://github.com/magicsong/ai-charts)
- [Fichier YAML par @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [Fichier YAML par @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Utilisation de Terraform pour le déploiement

View File

@ -204,7 +204,9 @@ docker compose up -d
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Helm Chart by @magicsong](https://github.com/magicsong/ai-charts)
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Terraformを使用したデプロイ

View File

@ -203,7 +203,9 @@ If you'd like to configure a highly-available setup, there are community-contrib
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Helm Chart by @magicsong](https://github.com/magicsong/ai-charts)
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Terraform atorlugu pilersitsineq

View File

@ -197,7 +197,9 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Helm Chart by @magicsong](https://github.com/magicsong/ai-charts)
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Terraform을 사용한 배포

View File

@ -203,7 +203,9 @@ Se deseja configurar uma instalação de alta disponibilidade, há [Helm Charts]
- [Helm Chart de @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart de @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Arquivo YAML de @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [Helm Chart de @magicsong](https://github.com/magicsong/ai-charts)
- [Arquivo YAML por @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [Arquivo YAML por @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Usando o Terraform para Implantação

View File

@ -205,6 +205,7 @@ Star Dify on GitHub and be instantly notified of new releases.
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Uporaba Terraform za uvajanje

View File

@ -198,6 +198,7 @@ Yüksek kullanılabilirliğe sahip bir kurulum yapılandırmak isterseniz, Dify'
- [@LeoQuote tarafından Helm Chart](https://github.com/douban/charts/tree/master/charts/dify)
- [@BorisPolonsky tarafından Helm Chart](https://github.com/BorisPolonsky/dify-helm)
- [@Winson-030 tarafından YAML dosyası](https://github.com/Winson-030/dify-kubernetes)
- [@wyy-holding tarafından YAML dosyası](https://github.com/wyy-holding/dify-k8s)
#### Dağıtım için Terraform Kullanımı

View File

@ -204,6 +204,7 @@ Dify 的所有功能都提供相應的 API因此您可以輕鬆地將 Dify
- [由 @LeoQuote 提供的 Helm Chart](https://github.com/douban/charts/tree/master/charts/dify)
- [由 @BorisPolonsky 提供的 Helm Chart](https://github.com/BorisPolonsky/dify-helm)
- [由 @Winson-030 提供的 YAML 文件](https://github.com/Winson-030/dify-kubernetes)
- [由 @wyy-holding 提供的 YAML 文件](https://github.com/wyy-holding/dify-k8s)
### 使用 Terraform 進行部署

View File

@ -200,6 +200,7 @@ Nếu bạn muốn cấu hình một cài đặt có độ sẵn sàng cao, có
- [Helm Chart bởi @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
- [Helm Chart bởi @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
- [Tệp YAML bởi @Winson-030](https://github.com/Winson-030/dify-kubernetes)
- [Tệp YAML bởi @wyy-holding](https://github.com/wyy-holding/dify-k8s)
#### Sử dụng Terraform để Triển khai

View File

@ -12,6 +12,7 @@ from configs import dify_config
from constants.languages import languages
from core.rag.datasource.vdb.vector_factory import Vector
from core.rag.datasource.vdb.vector_type import VectorType
from core.rag.index_processor.constant.built_in_field import BuiltInField
from core.rag.models.document import Document
from events.app_event import app_was_created
from extensions.ext_database import db
@ -20,7 +21,7 @@ from libs.helper import email as email_validate
from libs.password import hash_password, password_pattern, valid_password
from libs.rsa import generate_key_pair
from models import Tenant
from models.dataset import Dataset, DatasetCollectionBinding, DocumentSegment
from models.dataset import Dataset, DatasetCollectionBinding, DatasetMetadata, DatasetMetadataBinding, DocumentSegment
from models.dataset import Document as DatasetDocument
from models.model import Account, App, AppAnnotationSetting, AppMode, Conversation, MessageAnnotation
from models.provider import Provider, ProviderModel
@ -483,14 +484,11 @@ def convert_to_agent_apps():
click.echo(click.style("Conversion complete. Converted {} agent apps.".format(len(proceeded_app_ids)), fg="green"))
@click.command("add-qdrant-doc-id-index", help="Add Qdrant doc_id index.")
@click.command("add-qdrant-index", help="Add Qdrant index.")
@click.option("--field", default="metadata.doc_id", prompt=False, help="Index field , default is metadata.doc_id.")
def add_qdrant_doc_id_index(field: str):
click.echo(click.style("Starting Qdrant doc_id index creation.", fg="green"))
vector_type = dify_config.VECTOR_STORE
if vector_type != "qdrant":
click.echo(click.style("This command only supports Qdrant vector store.", fg="red"))
return
def add_qdrant_index(field: str):
click.echo(click.style("Starting Qdrant index creation.", fg="green"))
create_count = 0
try:
@ -539,6 +537,76 @@ def add_qdrant_doc_id_index(field: str):
click.echo(click.style(f"Index creation complete. Created {create_count} collection indexes.", fg="green"))
@click.command("old-metadata-migration", help="Old metadata migration.")
def old_metadata_migration():
"""
Old metadata migration.
"""
click.echo(click.style("Starting old metadata migration.", fg="green"))
page = 1
while True:
try:
documents = (
DatasetDocument.query.filter(DatasetDocument.doc_metadata is not None)
.order_by(DatasetDocument.created_at.desc())
.paginate(page=page, per_page=50)
)
except NotFound:
break
if not documents:
break
for document in documents:
if document.doc_metadata:
doc_metadata = document.doc_metadata
for key, value in doc_metadata.items():
for field in BuiltInField:
if field.value == key:
break
else:
dataset_metadata = (
db.session.query(DatasetMetadata)
.filter(DatasetMetadata.dataset_id == document.dataset_id, DatasetMetadata.name == key)
.first()
)
if not dataset_metadata:
dataset_metadata = DatasetMetadata(
tenant_id=document.tenant_id,
dataset_id=document.dataset_id,
name=key,
type="string",
created_by=document.created_by,
)
db.session.add(dataset_metadata)
db.session.flush()
dataset_metadata_binding = DatasetMetadataBinding(
tenant_id=document.tenant_id,
dataset_id=document.dataset_id,
metadata_id=dataset_metadata.id,
document_id=document.id,
created_by=document.created_by,
)
db.session.add(dataset_metadata_binding)
else:
dataset_metadata_binding = DatasetMetadataBinding.query.filter(
DatasetMetadataBinding.dataset_id == document.dataset_id,
DatasetMetadataBinding.document_id == document.id,
DatasetMetadataBinding.metadata_id == dataset_metadata.id,
).first()
if not dataset_metadata_binding:
dataset_metadata_binding = DatasetMetadataBinding(
tenant_id=document.tenant_id,
dataset_id=document.dataset_id,
metadata_id=dataset_metadata.id,
document_id=document.id,
created_by=document.created_by,
)
db.session.add(dataset_metadata_binding)
db.session.commit()
page += 1
click.echo(click.style("Old metadata migration completed.", fg="green"))
@click.command("create-tenant", help="Create account and tenant.")
@click.option("--email", prompt=True, help="Tenant account email.")
@click.option("--name", prompt=True, help="Workspace name.")

View File

@ -61,6 +61,10 @@ class AppExecutionConfig(BaseSettings):
description="Maximum number of concurrent active requests per app (0 for unlimited)",
default=0,
)
APP_DAILY_RATE_LIMIT: NonNegativeInt = Field(
description="Maximum number of requests per app per day",
default=5000,
)
class CodeExecutionSandboxConfig(BaseSettings):

View File

@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
CURRENT_VERSION: str = Field(
description="Dify version",
default="1.1.0",
default="1.1.1",
)
COMMIT_SHA: str = Field(

View File

@ -10,9 +10,14 @@ from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
import services
from configs import dify_config
from controllers.console import api
from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync
from controllers.console.app.error import (
ConversationCompletedError,
DraftWorkflowNotExist,
DraftWorkflowNotSync,
)
from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import account_initialization_required, setup_required
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
@ -27,6 +32,7 @@ from models.account import Account
from models.model import AppMode
from services.app_generate_service import AppGenerateService
from services.errors.app import WorkflowHashNotEqualError
from services.errors.llm import InvokeRateLimitError
from services.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError, WorkflowService
logger = logging.getLogger(__name__)
@ -168,6 +174,8 @@ class AdvancedChatDraftWorkflowRunApi(Resource):
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
raise ConversationCompletedError()
except InvokeRateLimitError as ex:
raise InvokeRateLimitHttpError(ex.description)
except ValueError as e:
raise e
except Exception:
@ -344,15 +352,18 @@ class DraftWorkflowRunApi(Resource):
parser.add_argument("files", type=list, required=False, location="json")
args = parser.parse_args()
response = AppGenerateService.generate(
app_model=app_model,
user=current_user,
args=args,
invoke_from=InvokeFrom.DEBUGGER,
streaming=True,
)
try:
response = AppGenerateService.generate(
app_model=app_model,
user=current_user,
args=args,
invoke_from=InvokeFrom.DEBUGGER,
streaming=True,
)
return helper.compact_generate_response(response)
return helper.compact_generate_response(response)
except InvokeRateLimitError as ex:
raise InvokeRateLimitHttpError(ex.description)
class WorkflowTaskStopApi(Resource):

View File

@ -79,7 +79,7 @@ class DatasetListApi(Resource):
data = marshal(datasets, dataset_detail_fields)
for item in data:
# convert embedding_model_provider to plugin standard format
if item["indexing_technique"] == "high_quality":
if item["indexing_technique"] == "high_quality" and item["embedding_model_provider"]:
item["embedding_model_provider"] = str(ModelProviderID(item["embedding_model_provider"]))
item_model = f"{item['embedding_model']}:{item['embedding_model_provider']}"
if item_model in model_names:

View File

@ -16,6 +16,7 @@ from controllers.console.app.error import (
)
from controllers.console.explore.error import NotChatAppError, NotCompletionAppError
from controllers.console.explore.wraps import InstalledAppResource
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import (
@ -29,6 +30,7 @@ from libs import helper
from libs.helper import uuid_value
from models.model import AppMode
from services.app_generate_service import AppGenerateService
from services.errors.llm import InvokeRateLimitError
# define completion api for user
@ -75,7 +77,7 @@ class CompletionApi(InstalledAppResource):
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception as e:
except Exception:
logging.exception("internal server error.")
raise InternalServerError()
@ -133,9 +135,11 @@ class ChatApi(InstalledAppResource):
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except InvokeRateLimitError as ex:
raise InvokeRateLimitHttpError(ex.description)
except ValueError as e:
raise e
except Exception as e:
except Exception:
logging.exception("internal server error.")
raise InternalServerError()

View File

@ -11,6 +11,7 @@ from controllers.console.app.error import (
)
from controllers.console.explore.error import NotWorkflowAppError
from controllers.console.explore.wraps import InstalledAppResource
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import (
@ -23,6 +24,7 @@ from libs import helper
from libs.login import current_user
from models.model import AppMode, InstalledApp
from services.app_generate_service import AppGenerateService
from services.errors.llm import InvokeRateLimitError
logger = logging.getLogger(__name__)
@ -56,9 +58,11 @@ class InstalledAppWorkflowRunApi(InstalledAppResource):
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except InvokeRateLimitError as ex:
raise InvokeRateLimitHttpError(ex.description)
except ValueError as e:
raise e
except Exception as e:
except Exception:
logging.exception("internal server error.")
raise InternalServerError()

View File

@ -7,4 +7,4 @@ api = ExternalApi(bp)
from . import index
from .app import app, audio, completion, conversation, file, message, workflow
from .dataset import dataset, document, hit_testing, segment, upload_file
from .dataset import dataset, document, hit_testing, metadata, segment, upload_file

View File

@ -15,6 +15,7 @@ from controllers.service_api.app.error import (
ProviderQuotaExceededError,
)
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import (
@ -27,6 +28,7 @@ from libs import helper
from libs.helper import uuid_value
from models.model import App, AppMode, EndUser
from services.app_generate_service import AppGenerateService
from services.errors.llm import InvokeRateLimitError
class CompletionApi(Resource):
@ -75,7 +77,7 @@ class CompletionApi(Resource):
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception as e:
except Exception:
logging.exception("internal server error.")
raise InternalServerError()
@ -130,11 +132,13 @@ class ChatApi(Resource):
raise ProviderQuotaExceededError()
except ModelCurrentlyNotSupportError:
raise ProviderModelCurrentlyNotSupportError()
except InvokeRateLimitError as ex:
raise InvokeRateLimitHttpError(ex.description)
except InvokeError as e:
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception as e:
except Exception:
logging.exception("internal server error.")
raise InternalServerError()

View File

@ -15,6 +15,7 @@ from controllers.service_api.app.error import (
ProviderQuotaExceededError,
)
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import (
@ -29,6 +30,7 @@ from libs import helper
from models.model import App, AppMode, EndUser
from models.workflow import WorkflowRun, WorkflowRunStatus
from services.app_generate_service import AppGenerateService
from services.errors.llm import InvokeRateLimitError
from services.workflow_app_service import WorkflowAppService
logger = logging.getLogger(__name__)
@ -93,11 +95,13 @@ class WorkflowRunApi(Resource):
raise ProviderQuotaExceededError()
except ModelCurrentlyNotSupportError:
raise ProviderModelCurrentlyNotSupportError()
except InvokeRateLimitError as ex:
raise InvokeRateLimitHttpError(ex.description)
except InvokeError as e:
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception as e:
except Exception:
logging.exception("internal server error.")
raise InternalServerError()

View File

@ -7,6 +7,7 @@ from controllers.service_api import api
from controllers.service_api.dataset.error import DatasetInUseError, DatasetNameDuplicateError
from controllers.service_api.wraps import DatasetApiResource
from core.model_runtime.entities.model_entities import ModelType
from core.plugin.entities.plugin import ModelProviderID
from core.provider_manager import ProviderManager
from fields.dataset_fields import dataset_detail_fields
from libs.login import current_user
@ -48,7 +49,8 @@ class DatasetListApi(DatasetApiResource):
data = marshal(datasets, dataset_detail_fields)
for item in data:
if item["indexing_technique"] == "high_quality":
if item["indexing_technique"] == "high_quality" and item["embedding_model_provider"]:
item["embedding_model_provider"] = str(ModelProviderID(item["embedding_model_provider"]))
item_model = f"{item['embedding_model']}:{item['embedding_model_provider']}"
if item_model in model_names:
item["embedding_available"] = True

View File

@ -18,7 +18,6 @@ from controllers.service_api.app.error import (
from controllers.service_api.dataset.error import (
ArchivedDocumentImmutableError,
DocumentIndexingError,
InvalidMetadataError,
)
from controllers.service_api.wraps import DatasetApiResource, cloud_edition_billing_resource_check
from core.errors.error import ProviderTokenNotInitError
@ -51,8 +50,6 @@ class DocumentAddByTextApi(DatasetApiResource):
"indexing_technique", type=str, choices=Dataset.INDEXING_TECHNIQUE_LIST, nullable=False, location="json"
)
parser.add_argument("retrieval_model", type=dict, required=False, nullable=False, location="json")
parser.add_argument("doc_type", type=str, required=False, nullable=True, location="json")
parser.add_argument("doc_metadata", type=dict, required=False, nullable=True, location="json")
args = parser.parse_args()
dataset_id = str(dataset_id)
@ -65,28 +62,6 @@ class DocumentAddByTextApi(DatasetApiResource):
if not dataset.indexing_technique and not args["indexing_technique"]:
raise ValueError("indexing_technique is required.")
# Validate metadata if provided
if args.get("doc_type") or args.get("doc_metadata"):
if not args.get("doc_type") or not args.get("doc_metadata"):
raise InvalidMetadataError("Both doc_type and doc_metadata must be provided when adding metadata")
if args["doc_type"] not in DocumentService.DOCUMENT_METADATA_SCHEMA:
raise InvalidMetadataError(
"Invalid doc_type. Must be one of: " + ", ".join(DocumentService.DOCUMENT_METADATA_SCHEMA.keys())
)
if not isinstance(args["doc_metadata"], dict):
raise InvalidMetadataError("doc_metadata must be a dictionary")
# Validate metadata schema based on doc_type
if args["doc_type"] != "others":
metadata_schema = DocumentService.DOCUMENT_METADATA_SCHEMA[args["doc_type"]]
for key, value in args["doc_metadata"].items():
if key in metadata_schema and not isinstance(value, metadata_schema[key]):
raise InvalidMetadataError(f"Invalid type for metadata field {key}")
# set to MetaDataConfig
args["metadata"] = {"doc_type": args["doc_type"], "doc_metadata": args["doc_metadata"]}
text = args.get("text")
name = args.get("name")
if text is None or name is None:
@ -133,8 +108,6 @@ class DocumentUpdateByTextApi(DatasetApiResource):
"doc_language", type=str, default="English", required=False, nullable=False, location="json"
)
parser.add_argument("retrieval_model", type=dict, required=False, nullable=False, location="json")
parser.add_argument("doc_type", type=str, required=False, nullable=True, location="json")
parser.add_argument("doc_metadata", type=dict, required=False, nullable=True, location="json")
args = parser.parse_args()
dataset_id = str(dataset_id)
tenant_id = str(tenant_id)
@ -146,29 +119,6 @@ class DocumentUpdateByTextApi(DatasetApiResource):
# indexing_technique is already set in dataset since this is an update
args["indexing_technique"] = dataset.indexing_technique
# Validate metadata if provided
if args.get("doc_type") or args.get("doc_metadata"):
if not args.get("doc_type") or not args.get("doc_metadata"):
raise InvalidMetadataError("Both doc_type and doc_metadata must be provided when adding metadata")
if args["doc_type"] not in DocumentService.DOCUMENT_METADATA_SCHEMA:
raise InvalidMetadataError(
"Invalid doc_type. Must be one of: " + ", ".join(DocumentService.DOCUMENT_METADATA_SCHEMA.keys())
)
if not isinstance(args["doc_metadata"], dict):
raise InvalidMetadataError("doc_metadata must be a dictionary")
# Validate metadata schema based on doc_type
if args["doc_type"] != "others":
metadata_schema = DocumentService.DOCUMENT_METADATA_SCHEMA[args["doc_type"]]
for key, value in args["doc_metadata"].items():
if key in metadata_schema and not isinstance(value, metadata_schema[key]):
raise InvalidMetadataError(f"Invalid type for metadata field {key}")
# set to MetaDataConfig
args["metadata"] = {"doc_type": args["doc_type"], "doc_metadata": args["doc_metadata"]}
if args["text"]:
text = args.get("text")
name = args.get("name")
@ -216,29 +166,6 @@ class DocumentAddByFileApi(DatasetApiResource):
if "doc_language" not in args:
args["doc_language"] = "English"
# Validate metadata if provided
if args.get("doc_type") or args.get("doc_metadata"):
if not args.get("doc_type") or not args.get("doc_metadata"):
raise InvalidMetadataError("Both doc_type and doc_metadata must be provided when adding metadata")
if args["doc_type"] not in DocumentService.DOCUMENT_METADATA_SCHEMA:
raise InvalidMetadataError(
"Invalid doc_type. Must be one of: " + ", ".join(DocumentService.DOCUMENT_METADATA_SCHEMA.keys())
)
if not isinstance(args["doc_metadata"], dict):
raise InvalidMetadataError("doc_metadata must be a dictionary")
# Validate metadata schema based on doc_type
if args["doc_type"] != "others":
metadata_schema = DocumentService.DOCUMENT_METADATA_SCHEMA[args["doc_type"]]
for key, value in args["doc_metadata"].items():
if key in metadata_schema and not isinstance(value, metadata_schema[key]):
raise InvalidMetadataError(f"Invalid type for metadata field {key}")
# set to MetaDataConfig
args["metadata"] = {"doc_type": args["doc_type"], "doc_metadata": args["doc_metadata"]}
# get dataset info
dataset_id = str(dataset_id)
tenant_id = str(tenant_id)
@ -306,29 +233,6 @@ class DocumentUpdateByFileApi(DatasetApiResource):
if "doc_language" not in args:
args["doc_language"] = "English"
# Validate metadata if provided
if args.get("doc_type") or args.get("doc_metadata"):
if not args.get("doc_type") or not args.get("doc_metadata"):
raise InvalidMetadataError("Both doc_type and doc_metadata must be provided when adding metadata")
if args["doc_type"] not in DocumentService.DOCUMENT_METADATA_SCHEMA:
raise InvalidMetadataError(
"Invalid doc_type. Must be one of: " + ", ".join(DocumentService.DOCUMENT_METADATA_SCHEMA.keys())
)
if not isinstance(args["doc_metadata"], dict):
raise InvalidMetadataError("doc_metadata must be a dictionary")
# Validate metadata schema based on doc_type
if args["doc_type"] != "others":
metadata_schema = DocumentService.DOCUMENT_METADATA_SCHEMA[args["doc_type"]]
for key, value in args["doc_metadata"].items():
if key in metadata_schema and not isinstance(value, metadata_schema[key]):
raise InvalidMetadataError(f"Invalid type for metadata field {key}")
# set to MetaDataConfig
args["metadata"] = {"doc_type": args["doc_type"], "doc_metadata": args["doc_metadata"]}
# get dataset info
dataset_id = str(dataset_id)
tenant_id = str(tenant_id)

View File

@ -0,0 +1,126 @@
from flask_login import current_user # type: ignore # type: ignore
from flask_restful import marshal, reqparse # type: ignore
from werkzeug.exceptions import NotFound
from controllers.service_api import api
from controllers.service_api.wraps import DatasetApiResource
from fields.dataset_fields import dataset_metadata_fields
from services.dataset_service import DatasetService
from services.entities.knowledge_entities.knowledge_entities import (
MetadataArgs,
MetadataOperationData,
)
from services.metadata_service import MetadataService
def _validate_name(name):
if not name or len(name) < 1 or len(name) > 40:
raise ValueError("Name must be between 1 to 40 characters.")
return name
def _validate_description_length(description):
if len(description) > 400:
raise ValueError("Description cannot exceed 400 characters.")
return description
class DatasetMetadataCreateServiceApi(DatasetApiResource):
def post(self, tenant_id, dataset_id):
parser = reqparse.RequestParser()
parser.add_argument("type", type=str, required=True, nullable=True, location="json")
parser.add_argument("name", type=str, required=True, nullable=True, location="json")
args = parser.parse_args()
metadata_args = MetadataArgs(**args)
dataset_id_str = str(dataset_id)
dataset = DatasetService.get_dataset(dataset_id_str)
if dataset is None:
raise NotFound("Dataset not found.")
DatasetService.check_dataset_permission(dataset, current_user)
metadata = MetadataService.create_metadata(dataset_id_str, metadata_args)
return marshal(metadata, dataset_metadata_fields), 201
def get(self, tenant_id, dataset_id):
dataset_id_str = str(dataset_id)
dataset = DatasetService.get_dataset(dataset_id_str)
if dataset is None:
raise NotFound("Dataset not found.")
return MetadataService.get_dataset_metadatas(dataset), 200
class DatasetMetadataServiceApi(DatasetApiResource):
def patch(self, tenant_id, dataset_id, metadata_id):
parser = reqparse.RequestParser()
parser.add_argument("name", type=str, required=True, nullable=True, location="json")
args = parser.parse_args()
dataset_id_str = str(dataset_id)
metadata_id_str = str(metadata_id)
dataset = DatasetService.get_dataset(dataset_id_str)
if dataset is None:
raise NotFound("Dataset not found.")
DatasetService.check_dataset_permission(dataset, current_user)
metadata = MetadataService.update_metadata_name(dataset_id_str, metadata_id_str, args.get("name"))
return marshal(metadata, dataset_metadata_fields), 200
def delete(self, tenant_id, dataset_id, metadata_id):
dataset_id_str = str(dataset_id)
metadata_id_str = str(metadata_id)
dataset = DatasetService.get_dataset(dataset_id_str)
if dataset is None:
raise NotFound("Dataset not found.")
DatasetService.check_dataset_permission(dataset, current_user)
MetadataService.delete_metadata(dataset_id_str, metadata_id_str)
return 200
class DatasetMetadataBuiltInFieldServiceApi(DatasetApiResource):
def get(self, tenant_id):
built_in_fields = MetadataService.get_built_in_fields()
return {"fields": built_in_fields}, 200
class DatasetMetadataBuiltInFieldActionServiceApi(DatasetApiResource):
def post(self, tenant_id, dataset_id, action):
dataset_id_str = str(dataset_id)
dataset = DatasetService.get_dataset(dataset_id_str)
if dataset is None:
raise NotFound("Dataset not found.")
DatasetService.check_dataset_permission(dataset, current_user)
if action == "enable":
MetadataService.enable_built_in_field(dataset)
elif action == "disable":
MetadataService.disable_built_in_field(dataset)
return 200
class DocumentMetadataEditServiceApi(DatasetApiResource):
def post(self, tenant_id, dataset_id):
dataset_id_str = str(dataset_id)
dataset = DatasetService.get_dataset(dataset_id_str)
if dataset is None:
raise NotFound("Dataset not found.")
DatasetService.check_dataset_permission(dataset, current_user)
parser = reqparse.RequestParser()
parser.add_argument("operation_data", type=list, required=True, nullable=True, location="json")
args = parser.parse_args()
metadata_args = MetadataOperationData(**args)
MetadataService.update_documents_metadata(dataset, metadata_args)
return 200
api.add_resource(DatasetMetadataCreateServiceApi, "/datasets/<uuid:dataset_id>/metadata")
api.add_resource(DatasetMetadataServiceApi, "/datasets/<uuid:dataset_id>/metadata/<uuid:metadata_id>")
api.add_resource(DatasetMetadataBuiltInFieldServiceApi, "/datasets/metadata/built-in")
api.add_resource(
DatasetMetadataBuiltInFieldActionServiceApi, "/datasets/<uuid:dataset_id>/metadata/built-in/<string:action>"
)
api.add_resource(DocumentMetadataEditServiceApi, "/datasets/<uuid:dataset_id>/documents/metadata")

View File

@ -11,6 +11,7 @@ from controllers.web.error import (
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
from controllers.web.wraps import WebApiResource
from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.entities.app_invoke_entities import InvokeFrom
@ -23,6 +24,7 @@ from core.model_runtime.errors.invoke import InvokeError
from libs import helper
from models.model import App, AppMode, EndUser
from services.app_generate_service import AppGenerateService
from services.errors.llm import InvokeRateLimitError
logger = logging.getLogger(__name__)
@ -55,9 +57,11 @@ class WorkflowRunApi(WebApiResource):
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except InvokeRateLimitError as ex:
raise InvokeRateLimitHttpError(ex.description)
except ValueError as e:
raise e
except Exception as e:
except Exception:
logging.exception("internal server error.")
raise InternalServerError()

View File

@ -29,6 +29,7 @@ from factories import file_factory
from models.account import Account
from models.model import App, Conversation, EndUser, Message
from models.workflow import Workflow
from services.conversation_service import ConversationService
from services.errors.message import MessageNotExistsError
logger = logging.getLogger(__name__)
@ -105,7 +106,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
conversation = None
conversation_id = args.get("conversation_id")
if conversation_id:
conversation = self._get_conversation_by_user(
conversation = ConversationService.get_conversation(
app_model=app_model, conversation_id=conversation_id, user=user
)

View File

@ -24,6 +24,7 @@ from core.ops.ops_trace_manager import TraceQueueManager
from extensions.ext_database import db
from factories import file_factory
from models import Account, App, EndUser
from services.conversation_service import ConversationService
from services.errors.message import MessageNotExistsError
logger = logging.getLogger(__name__)
@ -98,9 +99,11 @@ class AgentChatAppGenerator(MessageBasedAppGenerator):
# get conversation
conversation = None
if args.get("conversation_id"):
conversation = self._get_conversation_by_user(app_model, args.get("conversation_id", ""), user)
conversation_id = args.get("conversation_id")
if conversation_id:
conversation = ConversationService.get_conversation(
app_model=app_model, conversation_id=conversation_id, user=user
)
# get app model config
app_model_config = self._get_app_model_config(app_model=app_model, conversation=conversation)

View File

@ -24,6 +24,7 @@ from extensions.ext_database import db
from factories import file_factory
from models.account import Account
from models.model import App, EndUser
from services.conversation_service import ConversationService
from services.errors.message import MessageNotExistsError
logger = logging.getLogger(__name__)
@ -91,9 +92,11 @@ class ChatAppGenerator(MessageBasedAppGenerator):
# get conversation
conversation = None
if args.get("conversation_id"):
conversation = self._get_conversation_by_user(app_model, args.get("conversation_id", ""), user)
conversation_id = args.get("conversation_id")
if conversation_id:
conversation = ConversationService.get_conversation(
app_model=app_model, conversation_id=conversation_id, user=user
)
# get app model config
app_model_config = self._get_app_model_config(app_model=app_model, conversation=conversation)

View File

@ -4,8 +4,6 @@ from collections.abc import Generator
from datetime import UTC, datetime
from typing import Optional, Union, cast
from sqlalchemy import and_
from core.app.app_config.entities import EasyUIBasedAppConfig, EasyUIBasedAppModelConfigFrom
from core.app.apps.base_app_generator import BaseAppGenerator
from core.app.apps.base_app_queue_manager import AppQueueManager, GenerateTaskStoppedError
@ -30,7 +28,7 @@ from models import Account
from models.enums import CreatedByRole
from models.model import App, AppMode, AppModelConfig, Conversation, EndUser, Message, MessageFile
from services.errors.app_model_config import AppModelConfigBrokenError
from services.errors.conversation import ConversationCompletedError, ConversationNotExistsError
from services.errors.conversation import ConversationNotExistsError
logger = logging.getLogger(__name__)
@ -81,31 +79,6 @@ class MessageBasedAppGenerator(BaseAppGenerator):
logger.exception(f"Failed to handle response, conversation_id: {conversation.id}")
raise e
def _get_conversation_by_user(
self, app_model: App, conversation_id: str, user: Union[Account, EndUser]
) -> Conversation:
conversation_filter = [
Conversation.id == conversation_id,
Conversation.app_id == app_model.id,
Conversation.status == "normal",
Conversation.is_deleted.is_(False),
]
if isinstance(user, Account):
conversation_filter.append(Conversation.from_account_id == user.id)
else:
conversation_filter.append(Conversation.from_end_user_id == user.id if user else None)
conversation = db.session.query(Conversation).filter(and_(*conversation_filter)).first()
if not conversation:
raise ConversationNotExistsError()
if conversation.status != "normal":
raise ConversationCompletedError()
return conversation
def _get_app_model_config(self, app_model: App, conversation: Optional[Conversation] = None) -> AppModelConfig:
if conversation:
app_model_config = (

View File

@ -40,14 +40,10 @@ class RateLimit:
self.last_recalculate_time = time.time()
# flush max active requests
if use_local_value or not redis_client.exists(self.max_active_requests_key):
with redis_client.pipeline() as pipe:
pipe.set(self.max_active_requests_key, self.max_active_requests)
pipe.expire(self.max_active_requests_key, timedelta(days=1))
pipe.execute()
redis_client.setex(self.max_active_requests_key, timedelta(days=1), self.max_active_requests)
else:
with redis_client.pipeline() as pipe:
self.max_active_requests = int(redis_client.get(self.max_active_requests_key).decode("utf-8"))
redis_client.expire(self.max_active_requests_key, timedelta(days=1))
self.max_active_requests = int(redis_client.get(self.max_active_requests_key).decode("utf-8"))
redis_client.expire(self.max_active_requests_key, timedelta(days=1))
# flush max active requests (in-transit request list)
if not redis_client.exists(self.active_requests_key):

View File

@ -10,7 +10,7 @@
- 支持 5 种模型类型的能力调用
- `LLM` - LLM 文本补全、对话,预计算 tokens 能力
- `Text Embedidng Model` - 文本 Embedding ,预计算 tokens 能力
- `Text Embedding Model` - 文本 Embedding ,预计算 tokens 能力
- `Rerank Model` - 分段 Rerank 能力
- `Speech-to-text Model` - 语音转文本能力
- `Text-to-speech Model` - 文本转语音能力

View File

@ -93,7 +93,7 @@ class SimplePromptTransform(PromptTransform):
return prompt_messages, stops
def get_prompt_str_and_rules(
def _get_prompt_str_and_rules(
self,
app_mode: AppMode,
model_config: ModelConfigWithCredentialsEntity,
@ -184,7 +184,7 @@ class SimplePromptTransform(PromptTransform):
prompt_messages: list[PromptMessage] = []
# get prompt
prompt, _ = self.get_prompt_str_and_rules(
prompt, _ = self._get_prompt_str_and_rules(
app_mode=app_mode,
model_config=model_config,
pre_prompt=pre_prompt,
@ -209,9 +209,9 @@ class SimplePromptTransform(PromptTransform):
)
if query:
prompt_messages.append(self.get_last_user_message(query, files, image_detail_config))
prompt_messages.append(self._get_last_user_message(query, files, image_detail_config))
else:
prompt_messages.append(self.get_last_user_message(prompt, files, image_detail_config))
prompt_messages.append(self._get_last_user_message(prompt, files, image_detail_config))
return prompt_messages, None
@ -228,7 +228,7 @@ class SimplePromptTransform(PromptTransform):
image_detail_config: Optional[ImagePromptMessageContent.DETAIL] = None,
) -> tuple[list[PromptMessage], Optional[list[str]]]:
# get prompt
prompt, prompt_rules = self.get_prompt_str_and_rules(
prompt, prompt_rules = self._get_prompt_str_and_rules(
app_mode=app_mode,
model_config=model_config,
pre_prompt=pre_prompt,
@ -254,7 +254,7 @@ class SimplePromptTransform(PromptTransform):
)
# get prompt
prompt, prompt_rules = self.get_prompt_str_and_rules(
prompt, prompt_rules = self._get_prompt_str_and_rules(
app_mode=app_mode,
model_config=model_config,
pre_prompt=pre_prompt,
@ -268,9 +268,9 @@ class SimplePromptTransform(PromptTransform):
if stops is not None and len(stops) == 0:
stops = None
return [self.get_last_user_message(prompt, files, image_detail_config)], stops
return [self._get_last_user_message(prompt, files, image_detail_config)], stops
def get_last_user_message(
def _get_last_user_message(
self,
prompt: str,
files: Sequence["File"],

View File

@ -196,7 +196,8 @@ class ElasticSearchVector(BaseVector):
Field.METADATA_KEY.value: {
"type": "object",
"properties": {
"doc_id": {"type": "keyword"} # Map doc_id to keyword type
"doc_id": {"type": "keyword"}, # Map doc_id to keyword type
"document_id": {"type": "keyword"}, # Map doc_id to keyword type
},
},
}

View File

@ -11,3 +11,4 @@ class Field(Enum):
TEXT_KEY = "text"
PRIMARY_KEY = "id"
DOC_ID = "metadata.doc_id"
DOCUMENT_ID = "metadata.document_id"

View File

@ -119,7 +119,7 @@ class QdrantVector(BaseVector):
max_indexing_threads=0,
on_disk=False,
)
self._client.recreate_collection(
self._client.create_collection(
collection_name=collection_name,
vectors_config=vectors_config,
hnsw_config=hnsw_config,
@ -134,6 +134,10 @@ class QdrantVector(BaseVector):
self._client.create_payload_index(
collection_name, Field.DOC_ID.value, field_schema=PayloadSchemaType.KEYWORD
)
# create document_id payload index
self._client.create_payload_index(
collection_name, Field.DOCUMENT_ID.value, field_schema=PayloadSchemaType.KEYWORD
)
# create full text index
text_index_params = TextIndexParams(
type=TextIndexType.TEXT,

View File

@ -129,7 +129,7 @@ class TidbOnQdrantVector(BaseVector):
max_indexing_threads=0,
on_disk=False,
)
self._client.recreate_collection(
self._client.create_collection(
collection_name=collection_name,
vectors_config=vectors_config,
hnsw_config=hnsw_config,
@ -144,6 +144,10 @@ class TidbOnQdrantVector(BaseVector):
self._client.create_payload_index(
collection_name, Field.DOC_ID.value, field_schema=PayloadSchemaType.KEYWORD
)
# create document_id payload index
self._client.create_payload_index(
collection_name, Field.DOCUMENT_ID.value, field_schema=PayloadSchemaType.KEYWORD
)
# create full text index
text_index_params = TextIndexParams(
type=TextIndexType.TEXT,
@ -318,26 +322,17 @@ class TidbOnQdrantVector(BaseVector):
def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]:
from qdrant_client.http import models
filter = models.Filter(
must=[
models.FieldCondition(
key="group_id",
match=models.MatchValue(value=self._group_id),
),
],
)
filter = None
document_ids_filter = kwargs.get("document_ids_filter")
if document_ids_filter:
should_conditions = []
for document_id_filter in document_ids_filter:
should_conditions.append(
filter = models.Filter(
must=[
models.FieldCondition(
key="metadata.document_id",
match=models.MatchValue(value=document_id_filter),
match=models.MatchAny(any=document_ids_filter),
)
)
if should_conditions:
filter.should = should_conditions # type: ignore
],
)
results = self._client.search(
collection_name=self._collection_name,
query_vector=query_vector,
@ -372,26 +367,17 @@ class TidbOnQdrantVector(BaseVector):
"""
from qdrant_client.http import models
scroll_filter = models.Filter(
must=[
models.FieldCondition(
key="page_content",
match=models.MatchText(text=query),
)
]
)
scroll_filter = None
document_ids_filter = kwargs.get("document_ids_filter")
if document_ids_filter:
should_conditions = []
for document_id_filter in document_ids_filter:
should_conditions.append(
scroll_filter = models.Filter(
must=[
models.FieldCondition(
key="metadata.document_id",
match=models.MatchValue(value=document_id_filter),
match=models.MatchAny(any=document_ids_filter),
)
)
if should_conditions:
scroll_filter.should = should_conditions # type: ignore
]
)
response = self._client.scroll(
collection_name=self._collection_name,
scroll_filter=scroll_filter,

View File

@ -105,10 +105,12 @@ class TiDBVector(BaseVector):
text TEXT NOT NULL,
meta JSON NOT NULL,
doc_id VARCHAR(64) AS (JSON_UNQUOTE(JSON_EXTRACT(meta, '$.doc_id'))) STORED,
document_id VARCHAR(64) AS (JSON_UNQUOTE(JSON_EXTRACT(meta, '$.document_id'))) STORED,
vector VECTOR<FLOAT>({dimension}) NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY (doc_id),
KEY (document_id),
VECTOR INDEX idx_vector (({tidb_dist_func}(vector))) USING HNSW
);
""")

View File

@ -189,7 +189,10 @@ class WeaviateVector(BaseVector):
vector = {"vector": query_vector}
document_ids_filter = kwargs.get("document_ids_filter")
if document_ids_filter:
where_filter = {"operator": "ContainsAny", "path": ["document_id"], "valueTextArray": document_ids_filter}
operands = []
for document_id_filter in document_ids_filter:
operands.append({"path": ["document_id"], "operator": "Equal", "valueText": document_id_filter})
where_filter = {"operator": "Or", "operands": operands}
query_obj = query_obj.with_where(where_filter)
result = (
query_obj.with_near_vector(vector)
@ -237,7 +240,10 @@ class WeaviateVector(BaseVector):
query_obj = self._client.query.get(collection_name, properties)
document_ids_filter = kwargs.get("document_ids_filter")
if document_ids_filter:
where_filter = {"operator": "ContainsAny", "path": ["document_id"], "valueTextArray": document_ids_filter}
operands = []
for document_id_filter in document_ids_filter:
operands.append({"path": ["document_id"], "operator": "Equal", "valueText": document_id_filter})
where_filter = {"operator": "Or", "operands": operands}
query_obj = query_obj.with_where(where_filter)
query_obj = query_obj.with_additional(["vector"])
properties = ["text"]

View File

@ -870,7 +870,7 @@ class DatasetRetrieval:
for condition in metadata_filtering_conditions.conditions: # type: ignore
metadata_name = condition.name
expected_value = condition.value
if expected_value or condition.comparison_operator in ("empty", "not empty"):
if expected_value is not None or condition.comparison_operator in ("empty", "not empty"):
if isinstance(expected_value, str):
expected_value = self._replace_metadata_filter_value(expected_value, inputs)
filters = self._process_metadata_filter_func(

View File

@ -1,4 +1,4 @@
from ast import literal_eval
import json
from collections.abc import Generator, Mapping, Sequence
from typing import Any, cast
@ -143,15 +143,23 @@ class AgentNode(ToolNode):
raise ValueError(f"Variable {agent_input.value} does not exist")
parameter_value = variable.value
elif agent_input.type in {"mixed", "constant"}:
segment_group = variable_pool.convert_template(str(agent_input.value))
# variable_pool.convert_template expects a string template,
# but if passing a dict, convert to JSON string first before rendering
try:
parameter_value = json.dumps(agent_input.value, ensure_ascii=False)
except TypeError:
parameter_value = str(agent_input.value)
segment_group = variable_pool.convert_template(parameter_value)
parameter_value = segment_group.log if for_log else segment_group.text
# variable_pool.convert_template returns a string,
# so we need to convert it back to a dictionary
try:
parameter_value = json.loads(parameter_value)
except json.JSONDecodeError:
parameter_value = parameter_value
else:
raise ValueError(f"Unknown agent input type '{agent_input.type}'")
value = parameter_value.strip()
if (parameter_value.startswith("{") and parameter_value.endswith("}")) or (
parameter_value.startswith("[") and parameter_value.endswith("]")
):
value = literal_eval(parameter_value) # transform string to python object
value = parameter_value
if parameter.type == "array[tools]":
value = cast(list[dict[str, Any]], value)
value = [tool for tool in value if tool.get("enabled", False)]

View File

@ -65,7 +65,7 @@ class StreamProcessor(ABC):
# Issues: #13626
if (
finished_node_id in self.graph.node_parallel_mapping
and edge.target_node_id not in self.graph.parallel_mapping
and edge.target_node_id not in self.graph.node_parallel_mapping
):
continue
unreachable_first_node_ids.append(edge.target_node_id)

View File

@ -2,7 +2,6 @@ import csv
import io
import json
import logging
import operator
import os
import tempfile
from collections.abc import Mapping, Sequence
@ -12,6 +11,9 @@ import docx
import pandas as pd
import pypdfium2 # type: ignore
import yaml # type: ignore
from docx.document import Document
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P
from docx.table import Table
from docx.text.paragraph import Paragraph
@ -231,6 +233,13 @@ def _extract_text_from_doc(file_content: bytes) -> str:
raise TextExtractionError(f"Failed to extract text from DOC: {str(e)}") from e
def paser_docx_part(block, doc: Document, content_items, i):
if isinstance(block, CT_P):
content_items.append((i, "paragraph", Paragraph(block, doc)))
elif isinstance(block, CT_Tbl):
content_items.append((i, "table", Table(block, doc)))
def _extract_text_from_docx(file_content: bytes) -> str:
"""
Extract text from a DOCX file.
@ -244,16 +253,13 @@ def _extract_text_from_docx(file_content: bytes) -> str:
# Keep track of paragraph and table positions
content_items: list[tuple[int, str, Table | Paragraph]] = []
# Process paragraphs and tables
for i, paragraph in enumerate(doc.paragraphs):
if paragraph.text.strip():
content_items.append((i, "paragraph", paragraph))
for i, table in enumerate(doc.tables):
content_items.append((i, "table", table))
# Sort content items based on their original position
content_items.sort(key=operator.itemgetter(0))
it = iter(doc.element.body)
part = next(it, None)
i = 0
while part is not None:
paser_docx_part(part, doc, content_items, i)
i = i + 1
part = next(it, None)
# Process sorted content
for _, item_type, item in content_items:

View File

@ -356,7 +356,7 @@ class KnowledgeRetrievalNode(LLMNode):
for condition in node_data.metadata_filtering_conditions.conditions: # type: ignore
metadata_name = condition.name
expected_value = condition.value
if expected_value or condition.comparison_operator in ("empty", "not empty"):
if expected_value is not None or condition.comparison_operator in ("empty", "not empty"):
if isinstance(expected_value, str):
expected_value = self.graph_runtime_state.variable_pool.convert_template(
expected_value

View File

@ -3,7 +3,7 @@ from dify_app import DifyApp
def init_app(app: DifyApp):
from commands import (
add_qdrant_doc_id_index,
add_qdrant_index,
convert_to_agent_apps,
create_tenant,
extract_plugins,
@ -11,6 +11,7 @@ def init_app(app: DifyApp):
fix_app_site_missing,
install_plugins,
migrate_data_for_plugin,
old_metadata_migration,
reset_email,
reset_encrypt_key_pair,
reset_password,
@ -24,7 +25,7 @@ def init_app(app: DifyApp):
reset_encrypt_key_pair,
vdb_migrate,
convert_to_agent_apps,
add_qdrant_doc_id_index,
add_qdrant_index,
create_tenant,
upgrade_db,
fix_app_site_missing,
@ -32,6 +33,7 @@ def init_app(app: DifyApp):
extract_plugins,
extract_unique_plugins,
install_plugins,
old_metadata_migration,
]
for cmd in cmds_to_register:
app.cli.add_command(cmd)

View File

@ -785,9 +785,11 @@ class TenantService:
@staticmethod
def remove_member_from_tenant(tenant: Tenant, account: Account, operator: Account) -> None:
"""Remove member from tenant"""
if operator.id == account.id and TenantService.check_member_permission(tenant, operator, account, "remove"):
if operator.id == account.id:
raise CannotOperateSelfError("Cannot operate self.")
TenantService.check_member_permission(tenant, operator, account, "remove")
ta = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, account_id=account.id).first()
if not ta:
raise MemberNotInTenantError("Member not in tenant.")

View File

@ -11,13 +11,17 @@ from core.app.apps.completion.app_generator import CompletionAppGenerator
from core.app.apps.workflow.app_generator import WorkflowAppGenerator
from core.app.entities.app_invoke_entities import InvokeFrom
from core.app.features.rate_limiting import RateLimit
from libs.helper import RateLimiter
from models.model import Account, App, AppMode, EndUser
from models.workflow import Workflow
from services.billing_service import BillingService
from services.errors.llm import InvokeRateLimitError
from services.workflow_service import WorkflowService
class AppGenerateService:
system_rate_limiter = RateLimiter("app_daily_rate_limiter", dify_config.APP_DAILY_RATE_LIMIT, 86400)
@classmethod
def generate(
cls,
@ -36,6 +40,19 @@ class AppGenerateService:
:param streaming: streaming
:return:
"""
# system level rate limiter
if dify_config.BILLING_ENABLED:
# check if it's free plan
limit_info = BillingService.get_info(app_model.tenant_id)
if limit_info["subscription"]["plan"] == "sandbox":
if cls.system_rate_limiter.is_rate_limited(app_model.tenant_id):
raise InvokeRateLimitError(
"Rate limit exceeded, please upgrade your plan "
f"or your RPD was {dify_config.APP_DAILY_RATE_LIMIT} requests/day"
)
cls.system_rate_limiter.increment_rate_limit(app_model.tenant_id)
# app level rate limiter
max_active_request = AppGenerateService._get_max_active_requests(app_model)
rate_limit = RateLimit(app_model.id, max_active_request)
request_id = RateLimit.gen_request_key()

View File

@ -46,7 +46,6 @@ from models.source import DataSourceOauthBinding
from services.entities.knowledge_entities.knowledge_entities import (
ChildChunkUpdateArgs,
KnowledgeConfig,
MetaDataConfig,
RerankingModel,
RetrievalModel,
SegmentUpdateArgs,
@ -999,9 +998,6 @@ class DocumentService:
document.data_source_info = json.dumps(data_source_info)
document.batch = batch
document.indexing_status = "waiting"
if knowledge_config.metadata:
document.doc_type = knowledge_config.metadata.doc_type
document.metadata = knowledge_config.metadata.doc_metadata
db.session.add(document)
documents.append(document)
duplicate_document_ids.append(document.id)
@ -1018,7 +1014,6 @@ class DocumentService:
account,
file_name,
batch,
knowledge_config.metadata,
)
db.session.add(document)
db.session.flush()
@ -1076,7 +1071,6 @@ class DocumentService:
account,
truncated_page_name,
batch,
knowledge_config.metadata,
)
db.session.add(document)
db.session.flush()
@ -1117,7 +1111,6 @@ class DocumentService:
account,
document_name,
batch,
knowledge_config.metadata,
)
db.session.add(document)
db.session.flush()
@ -1155,7 +1148,6 @@ class DocumentService:
account: Account,
name: str,
batch: str,
metadata: Optional[MetaDataConfig] = None,
):
document = Document(
tenant_id=dataset.tenant_id,
@ -1180,9 +1172,6 @@ class DocumentService:
BuiltInField.last_update_date: datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%d %H:%M:%S"),
BuiltInField.source: data_source_type,
}
if metadata is not None:
doc_metadata.update(metadata.doc_metadata)
document.doc_type = metadata.doc_type
if doc_metadata:
document.doc_metadata = doc_metadata
return document
@ -1297,10 +1286,6 @@ class DocumentService:
# update document name
if document_data.name:
document.name = document_data.name
# update doc_type and doc_metadata if provided
if document_data.metadata is not None:
document.doc_metadata = document_data.metadata.doc_metadata
document.doc_type = document_data.metadata.doc_type
# update document to be waiting
document.indexing_status = "waiting"
document.completed_at = None

View File

@ -84,6 +84,22 @@ class RerankingModel(BaseModel):
reranking_model_name: Optional[str] = None
class WeightVectorSetting(BaseModel):
vector_weight: float
embedding_provider_name: str
embedding_model_name: str
class WeightKeywordSetting(BaseModel):
keyword_weight: float
class WeightModel(BaseModel):
weight_type: str
vector_setting: Optional[WeightVectorSetting] = None
keyword_setting: Optional[WeightKeywordSetting] = None
class RetrievalModel(BaseModel):
search_method: Literal["hybrid_search", "semantic_search", "full_text_search"]
reranking_enable: bool
@ -92,6 +108,7 @@ class RetrievalModel(BaseModel):
top_k: int
score_threshold_enabled: bool
score_threshold: Optional[float] = None
weights: Optional[WeightModel] = None
class MetaDataConfig(BaseModel):
@ -111,7 +128,6 @@ class KnowledgeConfig(BaseModel):
embedding_model: Optional[str] = None
embedding_model_provider: Optional[str] = None
name: Optional[str] = None
metadata: Optional[MetaDataConfig] = None
class SegmentUpdateArgs(BaseModel):

View File

@ -15,7 +15,6 @@ from libs.infinite_scroll_pagination import InfiniteScrollPagination
from models.account import Account
from models.model import App, AppMode, AppModelConfig, EndUser, Message, MessageFeedback
from services.conversation_service import ConversationService
from services.errors.conversation import ConversationCompletedError, ConversationNotExistsError
from services.errors.message import (
FirstMessageNotExistsError,
LastMessageNotExistsError,
@ -210,12 +209,6 @@ class MessageService:
app_model=app_model, conversation_id=message.conversation_id, user=user
)
if not conversation:
raise ConversationNotExistsError()
if conversation.status != "normal":
raise ConversationCompletedError()
model_manager = ModelManager()
if app_model.mode == AppMode.ADVANCED_CHAT.value:

View File

@ -137,7 +137,7 @@ class MetadataService:
doc_metadata[BuiltInField.source.value] = MetadataDataSource[document.data_source_type].value
document.doc_metadata = doc_metadata
db.session.add(document)
db.session.commit()
db.session.commit()
except Exception:
logging.exception("Enable built-in field failed")
finally:

View File

@ -24,10 +24,10 @@ class OpsService:
return None
# decrypt_token and obfuscated_token
tenant = db.session.query(App).filter(App.id == app_id).first()
if not tenant:
app = db.session.query(App).filter(App.id == app_id).first()
if not app:
return None
tenant_id = tenant.tenant_id
tenant_id = app.tenant_id
decrypt_tracing_config = OpsTraceManager.decrypt_tracing_config(
tenant_id, tracing_provider, trace_config_data.tracing_config
)
@ -117,10 +117,10 @@ class OpsService:
return None
# get tenant id
tenant = db.session.query(App).filter(App.id == app_id).first()
if not tenant:
app = db.session.query(App).filter(App.id == app_id).first()
if not app:
return None
tenant_id = tenant.tenant_id
tenant_id = app.tenant_id
tracing_config = OpsTraceManager.encrypt_tracing_config(tenant_id, tracing_provider, tracing_config)
if project_url:
tracing_config["project_url"] = project_url
@ -157,10 +157,10 @@ class OpsService:
return None
# get tenant id
tenant = db.session.query(App).filter(App.id == app_id).first()
if not tenant:
app = db.session.query(App).filter(App.id == app_id).first()
if not app:
return None
tenant_id = tenant.tenant_id
tenant_id = app.tenant_id
tracing_config = OpsTraceManager.encrypt_tracing_config(
tenant_id, tracing_provider, tracing_config, current_trace_config.tracing_config
)

View File

@ -64,12 +64,10 @@ def test_get_prompt():
transform._calculate_rest_token = MagicMock(return_value=max_token_limit)
result = transform.get_prompt()
assert len(result) <= max_token_limit
assert len(result) == 4
max_token_limit = 20
transform._calculate_rest_token = MagicMock(return_value=max_token_limit)
result = transform.get_prompt()
assert len(result) <= max_token_limit
assert len(result) == 12

View File

@ -84,7 +84,6 @@ def test_get_baichuan_completion_app_prompt_template_with_pcq():
query_in_prompt=True,
with_memory_prompt=False,
)
print(prompt_template["prompt_template"].template)
prompt_rules = prompt_template["prompt_rules"]
assert prompt_template["prompt_template"].template == (
prompt_rules["context_prompt"] + pre_prompt + "\n" + prompt_rules["query_prompt"]

View File

@ -1,5 +1,8 @@
from unittest.mock import patch
import pytest
from flask import Flask
from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.variable_pool import VariablePool
@ -17,12 +20,20 @@ from core.workflow.graph_engine.entities.event import (
from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.runtime_route_state import RouteNodeState
from core.workflow.graph_engine.graph_engine import GraphEngine
from core.workflow.nodes.code.code_node import CodeNode
from core.workflow.nodes.event import RunCompletedEvent, RunStreamChunkEvent
from core.workflow.nodes.llm.node import LLMNode
from core.workflow.nodes.question_classifier.question_classifier_node import QuestionClassifierNode
from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
@pytest.fixture
def app():
app = Flask(__name__)
return app
@patch("extensions.ext_database.db.session.remove")
@patch("extensions.ext_database.db.session.close")
def test_run_parallel_in_workflow(mock_close, mock_remove):
@ -502,3 +513,361 @@ def test_run_branch(mock_close, mock_remove):
assert isinstance(items[9], GraphRunSucceededEvent)
# print(graph_engine.graph_runtime_state.model_dump_json(indent=2))
@patch("extensions.ext_database.db.session.remove")
@patch("extensions.ext_database.db.session.close")
def test_condition_parallel_correct_output(mock_close, mock_remove, app):
"""issue #16238, workflow got unexpected additional output"""
graph_config = {
"edges": [
{
"data": {
"isInIteration": False,
"isInLoop": False,
"sourceType": "question-classifier",
"targetType": "question-classifier",
},
"id": "1742382406742-1-1742382480077-target",
"source": "1742382406742",
"sourceHandle": "1",
"target": "1742382480077",
"targetHandle": "target",
"type": "custom",
"zIndex": 0,
},
{
"data": {
"isInIteration": False,
"isInLoop": False,
"sourceType": "question-classifier",
"targetType": "answer",
},
"id": "1742382480077-1-1742382531085-target",
"source": "1742382480077",
"sourceHandle": "1",
"target": "1742382531085",
"targetHandle": "target",
"type": "custom",
"zIndex": 0,
},
{
"data": {
"isInIteration": False,
"isInLoop": False,
"sourceType": "question-classifier",
"targetType": "answer",
},
"id": "1742382480077-2-1742382534798-target",
"source": "1742382480077",
"sourceHandle": "2",
"target": "1742382534798",
"targetHandle": "target",
"type": "custom",
"zIndex": 0,
},
{
"data": {
"isInIteration": False,
"isInLoop": False,
"sourceType": "question-classifier",
"targetType": "answer",
},
"id": "1742382480077-1742382525856-1742382538517-target",
"source": "1742382480077",
"sourceHandle": "1742382525856",
"target": "1742382538517",
"targetHandle": "target",
"type": "custom",
"zIndex": 0,
},
{
"data": {"isInLoop": False, "sourceType": "start", "targetType": "question-classifier"},
"id": "1742382361944-source-1742382406742-target",
"source": "1742382361944",
"sourceHandle": "source",
"target": "1742382406742",
"targetHandle": "target",
"type": "custom",
"zIndex": 0,
},
{
"data": {
"isInIteration": False,
"isInLoop": False,
"sourceType": "question-classifier",
"targetType": "code",
},
"id": "1742382406742-1-1742451801533-target",
"source": "1742382406742",
"sourceHandle": "1",
"target": "1742451801533",
"targetHandle": "target",
"type": "custom",
"zIndex": 0,
},
{
"data": {"isInLoop": False, "sourceType": "code", "targetType": "answer"},
"id": "1742451801533-source-1742434464898-target",
"source": "1742451801533",
"sourceHandle": "source",
"target": "1742434464898",
"targetHandle": "target",
"type": "custom",
"zIndex": 0,
},
],
"nodes": [
{
"data": {"desc": "", "selected": False, "title": "开始", "type": "start", "variables": []},
"height": 54,
"id": "1742382361944",
"position": {"x": 30, "y": 286},
"positionAbsolute": {"x": 30, "y": 286},
"sourcePosition": "right",
"targetPosition": "left",
"type": "custom",
"width": 244,
},
{
"data": {
"classes": [{"id": "1", "name": "financial"}, {"id": "2", "name": "other"}],
"desc": "",
"instruction": "",
"instructions": "",
"model": {
"completion_params": {"temperature": 0.7},
"mode": "chat",
"name": "qwen-max-latest",
"provider": "langgenius/tongyi/tongyi",
},
"query_variable_selector": ["1742382361944", "sys.query"],
"selected": False,
"title": "qc",
"topics": [],
"type": "question-classifier",
"vision": {"enabled": False},
},
"height": 172,
"id": "1742382406742",
"position": {"x": 334, "y": 286},
"positionAbsolute": {"x": 334, "y": 286},
"selected": False,
"sourcePosition": "right",
"targetPosition": "left",
"type": "custom",
"width": 244,
},
{
"data": {
"classes": [
{"id": "1", "name": "VAT"},
{"id": "2", "name": "Stamp Duty"},
{"id": "1742382525856", "name": "other"},
],
"desc": "",
"instruction": "",
"instructions": "",
"model": {
"completion_params": {"temperature": 0.7},
"mode": "chat",
"name": "qwen-max-latest",
"provider": "langgenius/tongyi/tongyi",
},
"query_variable_selector": ["1742382361944", "sys.query"],
"selected": False,
"title": "qc 2",
"topics": [],
"type": "question-classifier",
"vision": {"enabled": False},
},
"height": 210,
"id": "1742382480077",
"position": {"x": 638, "y": 452},
"positionAbsolute": {"x": 638, "y": 452},
"selected": False,
"sourcePosition": "right",
"targetPosition": "left",
"type": "custom",
"width": 244,
},
{
"data": {
"answer": "VAT:{{#sys.query#}}\n",
"desc": "",
"selected": False,
"title": "answer 2",
"type": "answer",
"variables": [],
},
"height": 105,
"id": "1742382531085",
"position": {"x": 942, "y": 486.5},
"positionAbsolute": {"x": 942, "y": 486.5},
"selected": False,
"sourcePosition": "right",
"targetPosition": "left",
"type": "custom",
"width": 244,
},
{
"data": {
"answer": "Stamp Duty:{{#sys.query#}}\n",
"desc": "",
"selected": False,
"title": "answer 3",
"type": "answer",
"variables": [],
},
"height": 105,
"id": "1742382534798",
"position": {"x": 942, "y": 631.5},
"positionAbsolute": {"x": 942, "y": 631.5},
"selected": False,
"sourcePosition": "right",
"targetPosition": "left",
"type": "custom",
"width": 244,
},
{
"data": {
"answer": "other:{{#sys.query#}}\n",
"desc": "",
"selected": False,
"title": "answer 4",
"type": "answer",
"variables": [],
},
"height": 105,
"id": "1742382538517",
"position": {"x": 942, "y": 776.5},
"positionAbsolute": {"x": 942, "y": 776.5},
"selected": False,
"sourcePosition": "right",
"targetPosition": "left",
"type": "custom",
"width": 244,
},
{
"data": {
"answer": "{{#1742451801533.result#}}",
"desc": "",
"selected": False,
"title": "Answer 5",
"type": "answer",
"variables": [],
},
"height": 105,
"id": "1742434464898",
"position": {"x": 942, "y": 274.70425695336615},
"positionAbsolute": {"x": 942, "y": 274.70425695336615},
"selected": True,
"sourcePosition": "right",
"targetPosition": "left",
"type": "custom",
"width": 244,
},
{
"data": {
"code": '\ndef main(arg1: str, arg2: str) -> dict:\n return {\n "result": arg1 + arg2,\n }\n', # noqa: E501
"code_language": "python3",
"desc": "",
"outputs": {"result": {"children": None, "type": "string"}},
"selected": False,
"title": "Code",
"type": "code",
"variables": [
{"value_selector": ["sys", "query"], "variable": "arg1"},
{"value_selector": ["sys", "query"], "variable": "arg2"},
],
},
"height": 54,
"id": "1742451801533",
"position": {"x": 627.8839285786928, "y": 286},
"positionAbsolute": {"x": 627.8839285786928, "y": 286},
"selected": False,
"sourcePosition": "right",
"targetPosition": "left",
"type": "custom",
"width": 244,
},
],
}
graph = Graph.init(graph_config)
# construct variable pool
pool = VariablePool(
system_variables={
SystemVariableKey.QUERY: "dify",
SystemVariableKey.FILES: [],
SystemVariableKey.CONVERSATION_ID: "abababa",
SystemVariableKey.USER_ID: "1",
},
user_inputs={},
environment_variables=[],
)
pool.add(["pe", "list_output"], ["dify-1", "dify-2"])
variable_pool = VariablePool(
system_variables={SystemVariableKey.FILES: [], SystemVariableKey.USER_ID: "aaa"}, user_inputs={"query": "hi"}
)
graph_engine = GraphEngine(
tenant_id="111",
app_id="222",
workflow_type=WorkflowType.CHAT,
workflow_id="333",
graph_config=graph_config,
user_id="444",
user_from=UserFrom.ACCOUNT,
invoke_from=InvokeFrom.WEB_APP,
call_depth=0,
graph=graph,
variable_pool=variable_pool,
max_execution_steps=500,
max_execution_time=1200,
)
def qc_generator(self):
yield RunCompletedEvent(
run_result=NodeRunResult(
status=WorkflowNodeExecutionStatus.SUCCEEDED,
inputs={},
process_data={},
outputs={"class_name": "financial", "class_id": "1"},
metadata={
NodeRunMetadataKey.TOTAL_TOKENS: 1,
NodeRunMetadataKey.TOTAL_PRICE: 1,
NodeRunMetadataKey.CURRENCY: "USD",
},
edge_source_handle="1",
)
)
def code_generator(self):
yield RunCompletedEvent(
run_result=NodeRunResult(
status=WorkflowNodeExecutionStatus.SUCCEEDED,
inputs={},
process_data={},
outputs={"result": "dify 123"},
metadata={
NodeRunMetadataKey.TOTAL_TOKENS: 1,
NodeRunMetadataKey.TOTAL_PRICE: 1,
NodeRunMetadataKey.CURRENCY: "USD",
},
)
)
with patch.object(QuestionClassifierNode, "_run", new=qc_generator):
with app.app_context():
with patch.object(CodeNode, "_run", new=code_generator):
generator = graph_engine.run()
stream_content = ""
res_content = "VAT:\ndify 123"
for item in generator:
if isinstance(item, NodeRunStreamChunkEvent):
stream_content += f"{item.chunk_content}\n"
if isinstance(item, GraphRunSucceededEvent):
assert item.outputs == {"answer": res_content}
assert stream_content == res_content + "\n"

View File

@ -1,6 +1,7 @@
from unittest.mock import Mock, patch
import pytest
from docx.oxml.text.paragraph import CT_P
from core.file import File, FileTransferMethod
from core.variables import ArrayFileSegment
@ -169,7 +170,12 @@ def test_extract_text_from_docx(mock_document):
mock_paragraph2 = Mock()
mock_paragraph2.text = "Paragraph 2"
mock_document.return_value.paragraphs = [mock_paragraph1, mock_paragraph2]
mock_ct_p1 = Mock(spec=CT_P)
mock_ct_p1.text = "Paragraph 1"
mock_ct_p2 = Mock(spec=CT_P)
mock_ct_p2.text = "Paragraph 2"
mock_element = Mock(body=[mock_ct_p1, mock_ct_p2])
mock_document.return_value.element = mock_element
text = _extract_text_from_docx(b"PK\x03\x04")
assert text == "Paragraph 1\nParagraph 2"

View File

@ -68,7 +68,7 @@ DEBUG=false
# which is convenient for debugging.
FLASK_DEBUG=false
# A secretkey that is used for securely signing the session cookie
# A secret key that is used for securely signing the session cookie
# and encrypting sensitive information on the database.
# You can generate a strong key using `openssl rand -base64 42`.
SECRET_KEY=sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
@ -76,7 +76,7 @@ SECRET_KEY=sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U
# Password for admin user initialization.
# If left unset, admin user will not be prompted for a password
# when creating the initial admin account.
# The length of the password cannot exceed 30 charactors.
# The length of the password cannot exceed 30 characters.
INIT_PASSWORD=
# Deployment environment.

View File

@ -27,7 +27,7 @@ Welcome to the new `docker` directory for deploying Dify using Docker Compose. T
- Execute `docker compose up` from the `docker` directory to start the services.
- To specify a vector database, set the `VECTOR_STORE` variable in your `.env` file to your desired vector database service, such as `milvus`, `weaviate`, or `opensearch`.
4. **SSL Certificate Setup**:
- Rrefer `docker/certbot/README.md` to set up SSL certificates using Certbot.
- Refer `docker/certbot/README.md` to set up SSL certificates using Certbot.
### How to Deploy Middleware for Developing Dify
@ -54,7 +54,7 @@ For users migrating from the `docker-legacy` setup:
- **Vector Database Services**: Depending on the type of vector database used (`VECTOR_STORE`), users can set specific endpoints, ports, and authentication details.
- **Storage Services**: Depending on the storage type (`STORAGE_TYPE`), users can configure specific settings for S3, Azure Blob, Google Storage, etc.
- **API and Web Services**: Users can define URLs and other settings that affect how the API and web frontends operate.
- **API and Web Services**: Users can define URLs and other settings that affect how the API and web frontend operate.
#### Other notable variables

View File

@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env
services:
# API service
api:
image: langgenius/dify-api:1.1.0
image: langgenius/dify-api:1.1.1
restart: always
environment:
# Use the shared environment variables.
@ -29,7 +29,7 @@ services:
# worker service
# The Celery worker for processing the queue.
worker:
image: langgenius/dify-api:1.1.0
image: langgenius/dify-api:1.1.1
restart: always
environment:
# Use the shared environment variables.
@ -53,7 +53,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:1.1.0
image: langgenius/dify-web:1.1.1
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
@ -133,7 +133,7 @@ services:
# plugin daemon
plugin_daemon:
image: langgenius/dify-plugin-daemon:0.0.5-local
image: langgenius/dify-plugin-daemon:0.0.6-local
restart: always
environment:
# Use the shared environment variables.

View File

@ -66,7 +66,7 @@ services:
# plugin daemon
plugin_daemon:
image: langgenius/dify-plugin-daemon:0.0.5-local
image: langgenius/dify-plugin-daemon:0.0.6-local
restart: always
environment:
# Use the shared environment variables.

View File

@ -432,7 +432,7 @@ x-shared-env: &shared-api-worker-env
services:
# API service
api:
image: langgenius/dify-api:1.1.0
image: langgenius/dify-api:1.1.1
restart: always
environment:
# Use the shared environment variables.
@ -459,7 +459,7 @@ services:
# worker service
# The Celery worker for processing the queue.
worker:
image: langgenius/dify-api:1.1.0
image: langgenius/dify-api:1.1.1
restart: always
environment:
# Use the shared environment variables.
@ -483,7 +483,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:1.1.0
image: langgenius/dify-web:1.1.1
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
@ -563,7 +563,7 @@ services:
# plugin daemon
plugin_daemon:
image: langgenius/dify-plugin-daemon:0.0.5-local
image: langgenius/dify-plugin-daemon:0.0.6-local
restart: always
environment:
# Use the shared environment variables.

View File

@ -15,7 +15,7 @@ import {
// CommandLineIcon as CommandLineSolidIcon,
DocumentTextIcon as DocumentTextSolidIcon,
} from '@heroicons/react/24/solid'
import { RiApps2AddLine, RiInformation2Line } from '@remixicon/react'
import { RiApps2AddLine, RiBookOpenLine, RiInformation2Line } from '@remixicon/react'
import s from './style.module.css'
import classNames from '@/utils/classnames'
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
@ -58,13 +58,6 @@ const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
</svg>
}
const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
<path opacity="0.12" d="M1 3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7V10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1Z" fill="#155EEF" />
<path d="M6 10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7M6 10.5V4.7M6 10.5L6.05003 10.425C6.39735 9.90398 6.57101 9.64349 6.80045 9.45491C7.00357 9.28796 7.23762 9.1627 7.4892 9.0863C7.77337 9 8.08645 9 8.71259 9H9.4C9.96005 9 10.2401 9 10.454 8.89101C10.6422 8.79513 10.7951 8.64215 10.891 8.45399C11 8.24008 11 7.96005 11 7.4V3.1C11 2.53995 11 2.25992 10.891 2.04601C10.7951 1.85785 10.6422 1.70487 10.454 1.60899C10.2401 1.5 9.96005 1.5 9.4 1.5H9.2C8.07989 1.5 7.51984 1.5 7.09202 1.71799C6.71569 1.90973 6.40973 2.21569 6.21799 2.59202C6 3.01984 6 3.5799 6 4.7" stroke="#155EEF" strokeLinecap="round" strokeLinejoin="round" />
</svg>
}
type IExtraInfoProps = {
isMobile: boolean
relatedApps?: RelatedAppResponse
@ -131,7 +124,7 @@ const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
}
target='_blank' rel='noopener noreferrer'
>
<BookOpenIcon className='mr-1' />
<RiBookOpenLine className='mr-1 text-text-accent' />
{t('common.datasetMenus.viewDoc')}
</a>
</div>

View File

@ -47,44 +47,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Property name='text' type='string' key='text'>
Document content
</Property>
<Property name='doc_type' type='string' key='doc_type'>
Type of document (optional):
- <code>book</code> Book
- <code>web_page</code> Web page
- <code>paper</code> Academic paper/article
- <code>social_media_post</code> Social media post
- <code>wikipedia_entry</code> Wikipedia entry
- <code>personal_document</code> Personal document
- <code>business_document</code> Business document
- <code>im_chat_log</code> Chat log
- <code>synced_from_notion</code> Notion document
- <code>synced_from_github</code> GitHub document
- <code>others</code> Other document types
</Property>
<Property name='doc_metadata' type='object' key='doc_metadata'>
Document metadata (required if doc_type is provided). Fields vary by doc_type:
For <code>book</code>:
- <code>title</code> Book title
- <code>language</code> Book language
- <code>author</code> Book author
- <code>publisher</code> Publisher name
- <code>publication_date</code> Publication date
- <code>isbn</code> ISBN number
- <code>category</code> Book category
For <code>web_page</code>:
- <code>title</code> Page title
- <code>url</code> Page URL
- <code>language</code> Page language
- <code>publish_date</code> Publish date
- <code>author/publisher</code> Author or publisher
- <code>topic/keywords</code> Topic or keywords
- <code>description</code> Page description
Please check [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) for more details on the fields required for each doc_type.
For doc_type "others", any valid JSON object is accepted
</Property>
<Property name='indexing_technique' type='string' key='indexing_technique'>
Index mode
- <code>high_quality</code> High quality: embedding using embedding model, built as vector database index
@ -233,68 +195,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
- <code>hierarchical_model</code> Parent-child mode
- <code>qa_model</code> Q&A Mode: Generates Q&A pairs for segmented documents and then embeds the questions
- <code>doc_type</code> Type of document (optional)
- <code>book</code> Book
Document records a book or publication
- <code>web_page</code> Web page
Document records web page content
- <code>paper</code> Academic paper/article
Document records academic paper or research article
- <code>social_media_post</code> Social media post
Content from social media posts
- <code>wikipedia_entry</code> Wikipedia entry
Content from Wikipedia entries
- <code>personal_document</code> Personal document
Documents related to personal content
- <code>business_document</code> Business document
Documents related to business content
- <code>im_chat_log</code> Chat log
Records of instant messaging chats
- <code>synced_from_notion</code> Notion document
Documents synchronized from Notion
- <code>synced_from_github</code> GitHub document
Documents synchronized from GitHub
- <code>others</code> Other document types
Other document types not listed above
- <code>doc_metadata</code> Document metadata (required if doc_type is provided)
Fields vary by doc_type:
For <code>book</code>:
- <code>title</code> Book title
Title of the book
- <code>language</code> Book language
Language of the book
- <code>author</code> Book author
Author of the book
- <code>publisher</code> Publisher name
Name of the publishing house
- <code>publication_date</code> Publication date
Date when the book was published
- <code>isbn</code> ISBN number
International Standard Book Number
- <code>category</code> Book category
Category or genre of the book
For <code>web_page</code>:
- <code>title</code> Page title
Title of the web page
- <code>url</code> Page URL
URL address of the web page
- <code>language</code> Page language
Language of the web page
- <code>publish_date</code> Publish date
Date when the web page was published
- <code>author/publisher</code> Author or publisher
Author or publisher of the web page
- <code>topic/keywords</code> Topic or keywords
Topics or keywords of the web page
- <code>description</code> Page description
Description of the web page content
Please check [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) for more details on the fields required for each doc_type.
For doc_type "others", any valid JSON object is accepted
- <code>doc_language</code> In Q&A mode, specify the language of the document, for example: <code>English</code>, <code>Chinese</code>
- <code>process_rule</code> Processing rules
@ -407,44 +307,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Property name='description' type='string' key='description'>
Knowledge description (optional)
</Property>
<Property name='doc_type' type='string' key='doc_type'>
Type of document (optional):
- <code>book</code> Book
- <code>web_page</code> Web page
- <code>paper</code> Academic paper/article
- <code>social_media_post</code> Social media post
- <code>wikipedia_entry</code> Wikipedia entry
- <code>personal_document</code> Personal document
- <code>business_document</code> Business document
- <code>im_chat_log</code> Chat log
- <code>synced_from_notion</code> Notion document
- <code>synced_from_github</code> GitHub document
- <code>others</code> Other document types
</Property>
<Property name='doc_metadata' type='object' key='doc_metadata'>
Document metadata (required if doc_type is provided). Fields vary by doc_type:
For <code>book</code>:
- <code>title</code> Book title
- <code>language</code> Book language
- <code>author</code> Book author
- <code>publisher</code> Publisher name
- <code>publication_date</code> Publication date
- <code>isbn</code> ISBN number
- <code>category</code> Book category
For <code>web_page</code>:
- <code>title</code> Page title
- <code>url</code> Page URL
- <code>language</code> Page language
- <code>publish_date</code> Publish date
- <code>author/publisher</code> Author or publisher
- <code>topic/keywords</code> Topic or keywords
- <code>description</code> Page description
Please check [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) for more details on the fields required for each doc_type.
For doc_type "others", any valid JSON object is accepted
</Property>
<Property name='indexing_technique' type='string' key='indexing_technique'>
Index technique (optional)
- <code>high_quality</code> High quality
@ -762,67 +624,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
- <code>separator</code> Segmentation identifier. Currently, only one delimiter is allowed. The default is <code>***</code>
- <code>max_tokens</code> The maximum length (tokens) must be validated to be shorter than the length of the parent chunk
- <code>chunk_overlap</code> Define the overlap between adjacent chunks (optional)
- <code>doc_type</code> Type of document (optional)
- <code>book</code> Book
Document records a book or publication
- <code>web_page</code> Web page
Document records web page content
- <code>paper</code> Academic paper/article
Document records academic paper or research article
- <code>social_media_post</code> Social media post
Content from social media posts
- <code>wikipedia_entry</code> Wikipedia entry
Content from Wikipedia entries
- <code>personal_document</code> Personal document
Documents related to personal content
- <code>business_document</code> Business document
Documents related to business content
- <code>im_chat_log</code> Chat log
Records of instant messaging chats
- <code>synced_from_notion</code> Notion document
Documents synchronized from Notion
- <code>synced_from_github</code> GitHub document
Documents synchronized from GitHub
- <code>others</code> Other document types
Other document types not listed above
- <code>doc_metadata</code> Document metadata (required if doc_type is provided)
Fields vary by doc_type:
For <code>book</code>:
- <code>title</code> Book title
Title of the book
- <code>language</code> Book language
Language of the book
- <code>author</code> Book author
Author of the book
- <code>publisher</code> Publisher name
Name of the publishing house
- <code>publication_date</code> Publication date
Date when the book was published
- <code>isbn</code> ISBN number
International Standard Book Number
- <code>category</code> Book category
Category or genre of the book
For <code>web_page</code>:
- <code>title</code> Page title
Title of the web page
- <code>url</code> Page URL
URL address of the web page
- <code>language</code> Page language
Language of the web page
- <code>publish_date</code> Publish date
Date when the web page was published
- <code>author/publisher</code> Author or publisher
Author or publisher of the web page
- <code>topic/keywords</code> Topic or keywords
Topics or keywords of the web page
- <code>description</code> Page description
Description of the web page content
Please check [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) for more details on the fields required for each doc_type.
For doc_type "others", any valid JSON object is accepted
</Property>
</Properties>
</Col>
@ -1528,7 +1329,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
"id": "a8c6c36f-9f5d-4d7a-8472-f5d7b75d71d2",
"data_source_type": "upload_file",
"name": "readme.txt",
"doc_type": null
}
},
"score": 3.730463140527718e-05,

View File

@ -47,46 +47,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Property name='text' type='string' key='text'>
文档内容
</Property>
<Property name='doc_type' type='string' key='doc_type'>
文档类型(选填)
- <code>book</code> 图书 Book
- <code>web_page</code> 网页 Web page
- <code>paper</code> 学术论文/文章 Academic paper/article
- <code>social_media_post</code> 社交媒体帖子 Social media post
- <code>wikipedia_entry</code> 维基百科条目 Wikipedia entry
- <code>personal_document</code> 个人文档 Personal document
- <code>business_document</code> 商业文档 Business document
- <code>im_chat_log</code> 即时通讯记录 Chat log
- <code>synced_from_notion</code> Notion同步文档 Notion document
- <code>synced_from_github</code> GitHub同步文档 GitHub document
- <code>others</code> 其他文档类型 Other document types
</Property>
<Property name='doc_metadata' type='object' key='doc_metadata'>
文档元数据(如提供文档类型则必填)。字段因文档类型而异:
针对图书 For <code>book</code>:
- <code>title</code> 书名 Book title
- <code>language</code> 图书语言 Book language
- <code>author</code> 作者 Book author
- <code>publisher</code> 出版社 Publisher name
- <code>publication_date</code> 出版日期 Publication date
- <code>isbn</code> ISBN号码 ISBN number
- <code>category</code> 图书分类 Book category
针对网页 For <code>web_page</code>:
- <code>title</code> 页面标题 Page title
- <code>url</code> 页面网址 Page URL
- <code>language</code> 页面语言 Page language
- <code>publish_date</code> 发布日期 Publish date
- <code>author/publisher</code> 作者/发布者 Author or publisher
- <code>topic/keywords</code> 主题/关键词 Topic or keywords
- <code>description</code> 页面描述 Page description
请查看 [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) 了解各文档类型所需字段的详细信息。
针对"其他"类型文档接受任何有效的JSON对象
</Property>
<Property name='indexing_technique' type='string' key='indexing_technique'>
索引方式
- <code>high_quality</code> 高质量:使用 embedding 模型进行嵌入,构建为向量数据库索引
@ -234,68 +194,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
- <code>text_model</code> text 文档直接 embedding经济模式默认为该模式
- <code>hierarchical_model</code> parent-child 模式
- <code>qa_model</code> Q&A 模式:为分片文档生成 Q&A 对,然后对问题进行 embedding
- <code>doc_type</code> 文档类型选填Type of document (optional)
- <code>book</code> 图书
文档记录一本书籍或出版物
- <code>web_page</code> 网页
网页内容的文档记录
- <code>paper</code> 学术论文/文章
学术论文或研究文章的记录
- <code>social_media_post</code> 社交媒体帖子
社交媒体上的帖子内容
- <code>wikipedia_entry</code> 维基百科条目
维基百科的词条内容
- <code>personal_document</code> 个人文档
个人相关的文档记录
- <code>business_document</code> 商业文档
商业相关的文档记录
- <code>im_chat_log</code> 即时通讯记录
即时通讯的聊天记录
- <code>synced_from_notion</code> Notion同步文档
从Notion同步的文档内容
- <code>synced_from_github</code> GitHub同步文档
从GitHub同步的文档内容
- <code>others</code> 其他文档类型
其他未列出的文档类型
- <code>doc_metadata</code> 文档元数据(如提供文档类型则必填
字段因文档类型而异
针对图书类型 For <code>book</code>:
- <code>title</code> 书名
书籍的标题
- <code>language</code> 图书语言
书籍的语言
- <code>author</code> 作者
书籍的作者
- <code>publisher</code> 出版社
出版社的名称
- <code>publication_date</code> 出版日期
书籍的出版日期
- <code>isbn</code> ISBN号码
书籍的ISBN编号
- <code>category</code> 图书分类
书籍的分类类别
针对网页类型 For <code>web_page</code>:
- <code>title</code> 页面标题
网页的标题
- <code>url</code> 页面网址
网页的URL地址
- <code>language</code> 页面语言
网页的语言
- <code>publish_date</code> 发布日期
网页的发布日期
- <code>author/publisher</code> 作者/发布者
网页的作者或发布者
- <code>topic/keywords</code> 主题/关键词
网页的主题或关键词
- <code>description</code> 页面描述
网页的描述信息
请查看 [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) 了解各文档类型所需字段的详细信息。
针对"其他"类型文档接受任何有效的JSON对象
- <code>doc_language</code> 在 Q&A 模式下,指定文档的语言,例如:<code>English</code>、<code>Chinese</code>
@ -606,46 +504,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Property name='text' type='string' key='text'>
文档内容(选填)
</Property>
<Property name='doc_type' type='string' key='doc_type'>
文档类型(选填)
- <code>book</code> 图书 Book
- <code>web_page</code> 网页 Web page
- <code>paper</code> 学术论文/文章 Academic paper/article
- <code>social_media_post</code> 社交媒体帖子 Social media post
- <code>wikipedia_entry</code> 维基百科条目 Wikipedia entry
- <code>personal_document</code> 个人文档 Personal document
- <code>business_document</code> 商业文档 Business document
- <code>im_chat_log</code> 即时通讯记录 Chat log
- <code>synced_from_notion</code> Notion同步文档 Notion document
- <code>synced_from_github</code> GitHub同步文档 GitHub document
- <code>others</code> 其他文档类型 Other document types
</Property>
<Property name='doc_metadata' type='object' key='doc_metadata'>
文档元数据(如提供文档类型则必填)。字段因文档类型而异:
针对图书 For <code>book</code>:
- <code>title</code> 书名 Book title
- <code>language</code> 图书语言 Book language
- <code>author</code> 作者 Book author
- <code>publisher</code> 出版社 Publisher name
- <code>publication_date</code> 出版日期 Publication date
- <code>isbn</code> ISBN号码 ISBN number
- <code>category</code> 图书分类 Book category
针对网页 For <code>web_page</code>:
- <code>title</code> 页面标题 Page title
- <code>url</code> 页面网址 Page URL
- <code>language</code> 页面语言 Page language
- <code>publish_date</code> 发布日期 Publish date
- <code>author/publisher</code> 作者/发布者 Author or publisher
- <code>topic/keywords</code> 主题/关键词 Topic or keywords
- <code>description</code> 页面描述 Page description
请查看 [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) 了解各文档类型所需字段的详细信息。
针对"其他"类型文档接受任何有效的JSON对象
</Property>
<Property name='process_rule' type='object' key='process_rule'>
处理规则(选填)
- <code>mode</code> (string) 清洗、分段模式 automatic 自动 / custom 自定义
@ -766,68 +624,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
- <code>separator</code> 分段标识符,目前仅允许设置一个分隔符。默认为 <code>***</code>
- <code>max_tokens</code> 最大长度 (token) 需要校验小于父级的长度
- <code>chunk_overlap</code> 分段重叠指的是在对数据进行分段时,段与段之间存在一定的重叠部分(选填)
- <code>doc_type</code> 文档类型选填Type of document (optional)
- <code>book</code> 图书
文档记录一本书籍或出版物
- <code>web_page</code> 网页
网页内容的文档记录
- <code>paper</code> 学术论文/文章
学术论文或研究文章的记录
- <code>social_media_post</code> 社交媒体帖子
社交媒体上的帖子内容
- <code>wikipedia_entry</code> 维基百科条目
维基百科的词条内容
- <code>personal_document</code> 个人文档
个人相关的文档记录
- <code>business_document</code> 商业文档
商业相关的文档记录
- <code>im_chat_log</code> 即时通讯记录
即时通讯的聊天记录
- <code>synced_from_notion</code> Notion同步文档
从Notion同步的文档内容
- <code>synced_from_github</code> GitHub同步文档
从GitHub同步的文档内容
- <code>others</code> 其他文档类型
其他未列出的文档类型
- <code>doc_metadata</code> 文档元数据(如提供文档类型则必填
字段因文档类型而异
针对图书类型 For <code>book</code>:
- <code>title</code> 书名
书籍的标题
- <code>language</code> 图书语言
书籍的语言
- <code>author</code> 作者
书籍的作者
- <code>publisher</code> 出版社
出版社的名称
- <code>publication_date</code> 出版日期
书籍的出版日期
- <code>isbn</code> ISBN号码
书籍的ISBN编号
- <code>category</code> 图书分类
书籍的分类类别
针对网页类型 For <code>web_page</code>:
- <code>title</code> 页面标题
网页的标题
- <code>url</code> 页面网址
网页的URL地址
- <code>language</code> 页面语言
网页的语言
- <code>publish_date</code> 发布日期
网页的发布日期
- <code>author/publisher</code> 作者/发布者
网页的作者或发布者
- <code>topic/keywords</code> 主题/关键词
网页的主题或关键词
- <code>description</code> 页面描述
网页的描述信息
请查看 [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) 了解各文档类型所需字段的详细信息。
针对"其他"类型文档接受任何有效的JSON对象
</Property>
</Properties>
</Col>
@ -1534,7 +1330,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
"id": "a8c6c36f-9f5d-4d7a-8472-f5d7b75d71d2",
"data_source_type": "upload_file",
"name": "readme.txt",
"doc_type": null
}
},
"score": 3.730463140527718e-05,

View File

@ -44,7 +44,7 @@ export default function NavLink({
key={name}
href={href}
className={classNames(
isActive ? 'bg-state-accent-active text-text-accent font-semibold' : 'text-components-menu-item-text hover:bg-gray-100 hover:text-components-menu-item-text-hover',
isActive ? 'bg-state-accent-active text-text-accent font-semibold' : 'text-components-menu-item-text hover:bg-state-base-hover hover:text-components-menu-item-text-hover',
'group flex items-center h-9 rounded-md py-2 text-sm font-normal',
mode === 'expand' ? 'px-3' : 'px-2.5',
)}

View File

@ -34,10 +34,11 @@ export type ButtonProps = {
destructive?: boolean
loading?: boolean
styleCss?: CSSProperties
spinnerClassName?: string
} & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, destructive, loading, styleCss, children, ...props }, ref) => {
({ className, variant, size, destructive, loading, styleCss, children, spinnerClassName, ...props }, ref) => {
return (
<button
type='button'
@ -50,7 +51,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
{...props}
>
{children}
{loading && <Spinner loading={loading} className='!text-white !h-3 !w-3 !border-2 !ml-1' />}
{loading && <Spinner loading={loading} className={classNames('!text-white !h-3 !w-3 !border-2 !ml-1', spinnerClassName)} />}
</button>
)
},

View File

@ -183,7 +183,10 @@ export const useEmbeddedChatbot = () => {
useEffect(() => {
// init inputs from url params
setInitInputs(getProcessedInputsFromUrlParams())
(async () => {
const inputs = await getProcessedInputsFromUrlParams()
setInitInputs(inputs)
})()
}, [])
useEffect(() => {
const conversationInputs: Record<string, any> = {}
@ -288,11 +291,11 @@ export const useEmbeddedChatbot = () => {
if (conversationId)
setClearChatList(false)
}, [handleConversationIdInfoChange, setClearChatList])
const handleNewConversation = useCallback(() => {
const handleNewConversation = useCallback(async () => {
currentChatInstanceRef.current.handleStop()
setShowNewConversationItemInList(true)
handleChangeConversation('')
handleNewConversationInputsChange({})
handleNewConversationInputsChange(await getProcessedInputsFromUrlParams())
setClearChatList(true)
}, [handleChangeConversation, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList])

View File

@ -10,12 +10,14 @@ async function decodeBase64AndDecompress(base64String: string) {
return new TextDecoder().decode(decompressedArrayBuffer)
}
function getProcessedInputsFromUrlParams(): Record<string, any> {
async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> {
const urlParams = new URLSearchParams(window.location.search)
const inputs: Record<string, any> = {}
urlParams.forEach(async (value, key) => {
inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value))
})
await Promise.all(
urlParams.entries().map(async ([key, value]) => {
inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value))
}),
)
return inputs
}

View File

@ -20,7 +20,7 @@
"width": "16",
"height": "16",
"rx": "5",
"fill": "#F2F4F7"
"fill": "currentColor"
},
"children": []
},
@ -33,7 +33,7 @@
"width": "233",
"height": "10",
"rx": "3",
"fill": "#EAECF0"
"fill": "currentColor"
},
"children": []
},
@ -46,7 +46,7 @@
"width": "345",
"height": "6",
"rx": "3",
"fill": "#F2F4F7"
"fill": "currentColor"
},
"children": []
}

View File

@ -1,4 +0,0 @@
.setting-icon {
background: url(./assets/setting.svg) center center no-repeat;
background-size: 14px 14px;
}

View File

@ -1,10 +1,9 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import useSWR from 'swr'
import s from './base.module.css'
import { RiEqualizer2Line } from '@remixicon/react'
import WorkspaceSelector from './workspace-selector'
import SearchInput from './search-input'
import PageSelector from './page-selector'
import cn from '@/utils/classnames'
import { preImportNotionPages } from '@/service/datasets'
import { NotionConnector } from '@/app/components/datasets/create/step-one'
import type { DataSourceNotionPageMap, DataSourceNotionWorkspace, NotionPage } from '@/models/common'
@ -88,23 +87,24 @@ const NotionPageSelector = ({
}, [firstWorkspaceId])
return (
<div className='bg-gray-25 border border-gray-200 rounded-xl'>
<div className='bg-background-default-subtle border border-components-panel-border rounded-xl'>
{
data?.notion_info?.length
? (
<>
<div className='flex items-center pl-[10px] pr-2 h-11 bg-white border-b border-b-gray-200 rounded-t-xl'>
<WorkspaceSelector
value={currentWorkspaceId || firstWorkspaceId}
items={notionWorkspaces}
onSelect={handleSelectWorkspace}
/>
<div className='mx-1 w-[1px] h-3 bg-gray-200' />
<div
className={cn(s['setting-icon'], 'w-6 h-6 cursor-pointer')}
onClick={() => setShowAccountSettingModal({ payload: 'data-source', onCancelCallback: mutate })}
/>
<div className='grow' />
<div className='flex items-center gap-x-2 p-2 h-12 bg-components-panel-bg border-b border-b-divider-regular rounded-t-xl'>
<div className='grow flex items-center gap-x-1'>
<WorkspaceSelector
value={currentWorkspaceId || firstWorkspaceId}
items={notionWorkspaces}
onSelect={handleSelectWorkspace}
/>
<div className='mx-1 w-[1px] h-3 bg-divider-regular' />
<RiEqualizer2Line
className='w-4 h-4 cursor-pointer text-text-tertiary'
onClick={() => setShowAccountSettingModal({ payload: 'data-source', onCancelCallback: mutate })}
/>
</div>
<SearchInput
value={searchValue}
onChange={handleSearchValueChange}

View File

@ -1,17 +0,0 @@
.arrow {
width: 20px;
height: 20px;
background: url(../assets/down-arrow.svg) center center no-repeat;
background-size: 16px 16px;
transform: rotate(-90deg);
}
.arrow-expand {
transform: rotate(0);
}
.preview-item {
background-color: #eff4ff;
border: 1px solid #D1E0FF;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
}

View File

@ -2,9 +2,9 @@ import { memo, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FixedSizeList as List, areEqual } from 'react-window'
import type { ListChildComponentProps } from 'react-window'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import Checkbox from '../../checkbox'
import NotionIcon from '../../notion-icon'
import s from './index.module.css'
import cn from '@/utils/classnames'
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
@ -94,10 +94,16 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{
if (hasChild) {
return (
<div
className={cn(s.arrow, current.expand && s['arrow-expand'], 'shrink-0 mr-1 w-5 h-5 hover:bg-gray-200 rounded-md')}
className='flex items-center justify-center shrink-0 mr-1 w-5 h-5 hover:bg-components-button-ghost-bg-hover rounded-md'
style={{ marginLeft: current.depth * 8 }}
onClick={() => handleToggle(index)}
/>
>
{
current.expand
? <RiArrowDownSLine className='w-4 h-4 text-text-tertiary' />
: <RiArrowRightSLine className='w-4 h-4 text-text-tertiary' />
}
</div>
)
}
if (current.parent_id === 'root' || !pagesMap[current.parent_id]) {
@ -112,14 +118,12 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{
return (
<div
className={cn('group flex items-center pl-2 pr-[2px] rounded-md border border-transparent hover:bg-gray-100 cursor-pointer', previewPageId === current.page_id && s['preview-item'])}
className={cn('group flex items-center pl-2 pr-[2px] rounded-md hover:bg-state-base-hover cursor-pointer',
previewPageId === current.page_id && 'bg-state-base-hover')}
style={{ ...style, top: style.top as number + 8, left: 8, right: 8, width: 'calc(100% - 16px)' }}
>
<Checkbox
className={cn(
'shrink-0 mr-2 group-hover:border-primary-600 group-hover:border-[2px]',
disabled && 'group-hover:border-transparent',
)}
className='shrink-0 mr-2'
checked={checkedIds.has(current.page_id)}
disabled={disabled}
onCheck={() => {
@ -135,7 +139,7 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{
src={current.page_icon}
/>
<div
className='grow text-sm font-medium text-gray-700 truncate'
className='grow text-[13px] leading-4 font-medium text-text-secondary truncate'
title={current.page_name}
>
{current.page_name}
@ -143,7 +147,9 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{
{
canPreview && (
<div
className='shrink-0 hidden group-hover:flex items-center ml-1 px-2 h-6 rounded-md text-xs font-medium text-gray-500 cursor-pointer hover:bg-gray-50 hover:text-gray-700'
className='shrink-0 hidden group-hover:flex items-center ml-1 px-2 h-6 rounded-md text-xs leading-4 font-medium text-components-button-secondary-text
cursor-pointer bg-components-button-secondary-bg border-[0.5px] border-components-button-secondary-border shadow-xs shadow-shadow-shadow-3
backdrop-blur-[10px] hover:bg-components-button-secondary-bg-hover hover:border-components-button-secondary-border-hover'
onClick={() => handlePreview(index)}>
{t('common.dataSource.notion.selector.preview')}
</div>
@ -152,7 +158,7 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{
{
searchValue && (
<div
className='shrink-0 ml-1 max-w-[120px] text-xs text-gray-400 truncate'
className='shrink-0 ml-1 max-w-[120px] text-xs text-text-quaternary truncate'
title={breadCrumbs.join(' / ')}
>
{breadCrumbs.join(' / ')}
@ -278,7 +284,7 @@ const PageSelector = ({
if (!currentDataList.length) {
return (
<div className='flex items-center justify-center h-[296px] text-[13px] text-gray-500'>
<div className='flex items-center justify-center h-[296px] text-[13px] text-text-tertiary'>
{t('common.dataSource.notion.selector.noSearchResult')}
</div>
)

View File

@ -1,15 +0,0 @@
.search-icon {
background: url(../assets/search.svg) center center;
background-size: 14px 14px;
}
.clear-icon {
background: url(../assets/clear.svg) center center;
background-size: contain;
}
.input-wrapper {
flex-basis: 200px;
width: 0;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
}

View File

@ -1,7 +1,7 @@
import { useCallback } from 'react'
import type { ChangeEvent } from 'react'
import { useTranslation } from 'react-i18next'
import s from './index.module.css'
import { RiCloseCircleFill, RiSearchLine } from '@remixicon/react'
import cn from '@/utils/classnames'
type SearchInputProps = {
@ -19,18 +19,18 @@ const SearchInput = ({
}, [onChange])
return (
<div className={cn(s['input-wrapper'], 'flex items-center px-2 h-7 rounded-md', `${value ? 'bg-white' : 'bg-gray-100'}`)}>
<div className={cn(s['search-icon'], 'mr-[6px] w-4 h-4')} />
<div className={cn('w-[200px] flex items-center p-2 h-8 rounded-lg bg-components-input-bg-normal')}>
<RiSearchLine className={'w-4 h-4 mr-0.5 shrink-0 text-components-input-text-placeholder'} />
<input
className='grow text-[13px] bg-inherit border-0 outline-0 appearance-none'
className='min-w-0 grow px-1 text-[13px] leading-[16px] bg-transparent text-components-input-text-filled placeholder:text-components-input-text-placeholder border-0 outline-0 appearance-none'
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
placeholder={t('common.dataSource.notion.selector.searchPages') || ''}
/>
{
value && (
<div
className={cn(s['clear-icon'], 'ml-1 w-4 h-4 cursor-pointer')}
<RiCloseCircleFill
className={'w-4 h-4 shrink-0 cursor-pointer text-components-input-text-placeholder'}
onClick={handleClear}
/>
)

View File

@ -1,9 +0,0 @@
.down-arrow {
background: url(../assets/down-arrow.svg) center center no-repeat;
background-size: cover;
}
.popup {
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
z-index: 10;
}

View File

@ -2,9 +2,8 @@
import { useTranslation } from 'react-i18next'
import { Fragment } from 'react'
import { Menu, Transition } from '@headlessui/react'
import { RiArrowDownSLine } from '@remixicon/react'
import NotionIcon from '../../notion-icon'
import s from './index.module.css'
import cn from '@/utils/classnames'
import type { DataSourceNotionWorkspace } from '@/models/common'
type WorkspaceSelectorProps = {
@ -25,15 +24,15 @@ export default function WorkspaceSelector({
{
({ open }) => (
<>
<Menu.Button className={`flex items-center justify-center h-7 rounded-md hover:bg-gray-50 ${open && 'bg-gray-50'} cursor-pointer`}>
<Menu.Button className={`flex items-center justify-center h-7 p-1 pr-2 rounded-md hover:bg-state-base-hover ${open && 'bg-state-base-hover'} cursor-pointer`}>
<NotionIcon
className='ml-1 mr-2'
className='mr-2'
src={currentWorkspace?.workspace_icon}
name={currentWorkspace?.workspace_name}
/>
<div className='mr-1 w-[90px] text-left text-sm font-medium text-gray-700 truncate' title={currentWorkspace?.workspace_name}>{currentWorkspace?.workspace_name}</div>
<div className='mr-1 px-1 h-[18px] bg-primary-50 rounded-lg text-xs font-medium text-primary-600'>{currentWorkspace?.pages.length}</div>
<div className={cn(s['down-arrow'], 'mr-2 w-3 h-3')} />
<div className='mr-1 w-[90px] text-left text-sm font-medium text-text-secondary truncate' title={currentWorkspace?.workspace_name}>{currentWorkspace?.workspace_name}</div>
{/* <div className='mr-1 px-1 h-[18px] bg-primary-50 rounded-lg text-xs font-medium text-text-accent'>{currentWorkspace?.pages.length}</div> */}
<RiArrowDownSLine className='w-4 h-4 text-text-secondary' />
</Menu.Button>
<Transition
as={Fragment}
@ -45,19 +44,16 @@ export default function WorkspaceSelector({
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className={cn(
s.popup,
`absolute left-0 top-8 w-80
origin-top-right rounded-lg bg-white
border-[0.5px] border-gray-200`,
)}
className='absolute left-0 top-8 z-10 w-80
origin-top-right rounded-lg bg-components-panel-bg-blur
border-[0.5px] border-components-panel-border shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'
>
<div className="p-1 max-h-50 overflow-auto">
{
items.map(item => (
<Menu.Item key={item.workspace_id}>
<div
className='flex items-center px-3 h-9 hover:bg-gray-50 cursor-pointer'
className='flex items-center px-3 h-9 rounded-lg hover:bg-state-base-hover cursor-pointer'
onClick={() => onSelect(item.workspace_id)}
>
<NotionIcon
@ -65,8 +61,8 @@ export default function WorkspaceSelector({
src={item.workspace_icon}
name={item.workspace_name}
/>
<div className='grow mr-2 text-sm text-gray-700 truncate' title={item.workspace_name}>{item.workspace_name}</div>
<div className='shrink-0 text-xs font-medium text-primary-600'>
<div className='grow mr-2 system-sm-medium text-text-secondary truncate' title={item.workspace_name}>{item.workspace_name}</div>
<div className='shrink-0 system-xs-medium text-text-accent'>
{item.pages.length} {t('common.dataSource.notion.selector.pageSelected')}
</div>
</div>

View File

@ -1,9 +1,8 @@
import type { FC } from 'react'
import { useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiSearchLine } from '@remixicon/react'
import { RiCloseCircleFill, RiSearchLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
type SearchInputProps = {
placeholder?: string
@ -27,22 +26,19 @@ const SearchInput: FC<SearchInputProps> = ({
return (
<div className={cn(
'group flex items-center px-2 h-8 rounded-lg bg-gray-200 hover:bg-gray-300 border border-transparent overflow-hidden',
focus && '!bg-white hover:bg-white shadow-xs !border-gray-300',
!focus && value && 'hover:!bg-gray-200 hover:!shadow-xs hover:!border-black/5',
'group flex items-center px-2 h-8 rounded-lg bg-components-input-bg-normal hover:bg-components-input-bg-hover border-none overflow-hidden',
focus && '!bg-components-input-bg-active',
white && '!bg-white hover:!bg-white shadow-xs !border-gray-300 hover:!border-gray-300',
className,
)}>
<div className="pointer-events-none shrink-0 flex items-center mr-1.5 justify-center w-4 h-4">
<RiSearchLine className="h-3.5 w-3.5 text-gray-500" aria-hidden="true" />
<RiSearchLine className="h-4 w-4 text-components-input-text-placeholder" aria-hidden="true" />
</div>
<input
type="text"
name="query"
className={cn(
'grow block h-[18px] bg-gray-200 border-0 text-gray-700 text-[13px] placeholder:text-gray-500 appearance-none outline-none group-hover:bg-gray-300 caret-blue-600',
focus && '!bg-white hover:bg-white group-hover:bg-white placeholder:!text-gray-400',
!focus && value && 'hover:!bg-gray-200 group-hover:!bg-gray-200',
'grow block h-[18px] bg-transparent border-0 text-components-input-text-filled system-sm-regular placeholder:text-components-input-text-placeholder appearance-none outline-none caret-#295EFF',
white && '!bg-white hover:!bg-white group-hover:!bg-white placeholder:!text-gray-400',
)}
placeholder={placeholder || t('common.operation.search')!}
@ -71,7 +67,7 @@ const SearchInput: FC<SearchInputProps> = ({
setInternalValue('')
}}
>
<XCircle className='w-3.5 h-3.5 text-gray-400 group-hover/clear:text-gray-600' />
<RiCloseCircleFill className='w-4 h-4 text-text-quaternary group-hover/clear:text-text-tertiary' />
</div>
)}
</div>

View File

@ -234,8 +234,8 @@ const TagSelector: FC<TagSelectorProps> = ({
<div className={cn(
'group/tip relative w-full flex items-center gap-1 px-2 py-[7px] rounded-md cursor-pointer hover:bg-state-base-hover',
)}>
<Tag01 className='shrink-0 w-3 h-3' />
<div className='grow text-xs text-start leading-[18px] font-normal truncate'>
<Tag01 className='shrink-0 w-3 h-3 text-components-input-text-placeholder' />
<div className='grow text-start system-sm-regular text-components-input-text-placeholder truncate'>
{!triggerContent ? t('common.tag.addTag') : triggerContent}
</div>
</div>

View File

@ -78,7 +78,7 @@ const DocumentPicker: FC<Props> = ({
<FileIcon name={name} extension={extension} size='lg' />
<div className='flex flex-col items-start ml-1 mr-0.5'>
<div className='flex items-center space-x-0.5'>
<span className={cn('system-md-semibold')}> {name || '--'}</span>
<span className={cn('system-md-semibold text-text-primary')}> {name || '--'}</span>
<ArrowIcon className={'h-4 w-4 text-text-primary'} />
</div>
<div className='flex items-center h-3 text-text-tertiary space-x-0.5'>

View File

@ -253,9 +253,9 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
return (
<>
<div className="h-5 flex items-center mb-3">
<div className="flex items-center justify-between text-gray-900 font-medium text-sm mr-2">
<div className="flex items-center justify-between text-text-secondary font-medium text-sm mr-2">
{isEmbedding && <div className='flex items-center'>
<RiLoader2Fill className='size-4 mr-1 animate-spin' />
<RiLoader2Fill className='size-4 mr-1 animate-spin text-text-secondary' />
{t('datasetDocuments.embedding.processing')}
</div>}
{isEmbeddingCompleted && t('datasetDocuments.embedding.completed')}
@ -304,7 +304,7 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
/>
)}
<div className="grow flex items-center gap-1 w-0" title={getSourceName(indexingStatusDetail.id)}>
<div className="text-xs truncate">
<div className="system-xs-medium text-text-secondary truncate">
{getSourceName(indexingStatusDetail.id)}
</div>
{
@ -314,7 +314,7 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
}
</div>
{isSourceEmbedding(indexingStatusDetail) && (
<div className="shrink-0 text-xs">{`${getSourcePercent(indexingStatusDetail)}%`}</div>
<div className="shrink-0 text-xs text-text-secondary">{`${getSourcePercent(indexingStatusDetail)}%`}</div>
)}
{indexingStatusDetail.indexing_status === 'error' && (
<Tooltip

View File

@ -6,11 +6,10 @@
@apply flex items-center place-content-between h-8;
}
.modalHeader .title {
@apply grow;
@apply grow text-text-primary;
font-weight: 600;
font-size: 20px;
line-height: 32px;
color: #101828;
}
.modalHeader .close {
@apply shrink-0 h-4 w-4 bg-center bg-no-repeat cursor-pointer;
@ -19,20 +18,18 @@
}
.modal .tip {
@apply mt-1 mb-8;
@apply mt-1 mb-8 text-text-tertiary;
font-weight: 400;
font-size: 13px;
line-height: 18px;
color: #667085;
}
.form {
@apply mb-8;
}
.form .label {
@apply mb-2;
@apply mb-2 text-text-primary;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #101828;
}

View File

@ -1,43 +1,29 @@
.filePreview {
@apply flex flex-col border-l border-gray-200 shrink-0;
@apply flex flex-col border-l border-components-panel-border shrink-0 bg-background-default-lighter;
width: 100%;
background-color: #fcfcfd;
}
.previewHeader {
@apply border-b border-gray-200 shrink-0;
@apply border-b border-divider-subtle shrink-0;
margin: 42px 32px 0;
padding-bottom: 16px;
}
.previewHeader .title {
display: flex;
justify-content: space-between;
align-items: center;
color: #101828;
font-weight: 600;
font-size: 18px;
line-height: 28px;
@apply flex justify-between items-center text-text-primary;
}
.previewHeader .fileName {
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #1D2939;
@apply text-text-tertiary;
}
.previewHeader .filetype {
color: #667085;
@apply text-text-tertiary;
}
.previewContent {
@apply overflow-y-auto grow;
@apply overflow-y-auto grow text-text-secondary;
padding: 20px 32px;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: #344054;
}
.previewContent .loading {
@ -46,6 +32,7 @@
background: #f9fafb center no-repeat url(../assets/Loading.svg);
background-size: contain;
}
.fileContent {
white-space: pre-line;
word-break: break-all;

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