mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 09:06:56 +08:00
Merge remote-tracking branch 'origin/deploy/rag-dev' into deploy/rag-dev
This commit is contained in:
commit
0a6dbf6ee2
@ -226,6 +226,11 @@ Deploy Dify to AWS with [CDK](https://aws.amazon.com/cdk/)
|
|||||||
|
|
||||||
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Using Alibaba Cloud Computing Nest
|
||||||
|
|
||||||
|
Quickly deploy Dify to Alibaba cloud with [Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
|
|||||||
@ -209,6 +209,9 @@ docker compose up -d
|
|||||||
|
|
||||||
- [AWS CDK بواسطة @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK بواسطة @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### استخدام Alibaba Cloud للنشر
|
||||||
|
[بسرعة نشر Dify إلى سحابة علي بابا مع عش الحوسبة السحابية علي بابا](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
## المساهمة
|
## المساهمة
|
||||||
|
|
||||||
لأولئك الذين يرغبون في المساهمة، انظر إلى [دليل المساهمة](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) لدينا.
|
لأولئك الذين يرغبون في المساهمة، انظر إلى [دليل المساهمة](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) لدينا.
|
||||||
|
|||||||
@ -225,6 +225,11 @@ GitHub-এ ডিফাইকে স্টার দিয়ে রাখুন
|
|||||||
|
|
||||||
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud ব্যবহার করে ডিপ্লয়
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
যারা কোড অবদান রাখতে চান, তাদের জন্য আমাদের [অবদান নির্দেশিকা] দেখুন (https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)।
|
যারা কোড অবদান রাখতে চান, তাদের জন্য আমাদের [অবদান নির্দেশিকা] দেখুন (https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)।
|
||||||
|
|||||||
@ -221,6 +221,11 @@ docker compose up -d
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### 使用 阿里云计算巢 部署
|
||||||
|
|
||||||
|
使用 [阿里云计算巢](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88) 将 Dify 一键部署到 阿里云
|
||||||
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#langgenius/dify&Date)
|
[](https://star-history.com/#langgenius/dify&Date)
|
||||||
|
|||||||
@ -221,6 +221,11 @@ Bereitstellung von Dify auf AWS mit [CDK](https://aws.amazon.com/cdk/)
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Falls Sie Code beitragen möchten, lesen Sie bitte unseren [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). Gleichzeitig bitten wir Sie, Dify zu unterstützen, indem Sie es in den sozialen Medien teilen und auf Veranstaltungen und Konferenzen präsentieren.
|
Falls Sie Code beitragen möchten, lesen Sie bitte unseren [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). Gleichzeitig bitten wir Sie, Dify zu unterstützen, indem Sie es in den sozialen Medien teilen und auf Veranstaltungen und Konferenzen präsentieren.
|
||||||
|
|||||||
@ -221,6 +221,10 @@ Despliegue Dify en AWS usando [CDK](https://aws.amazon.com/cdk/)
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK por @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK por @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
## Contribuir
|
## Contribuir
|
||||||
|
|
||||||
Para aquellos que deseen contribuir con código, consulten nuestra [Guía de contribución](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
Para aquellos que deseen contribuir con código, consulten nuestra [Guía de contribución](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
|
|||||||
@ -219,6 +219,11 @@ Déployez Dify sur AWS en utilisant [CDK](https://aws.amazon.com/cdk/)
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK par @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK par @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Contribuer
|
## Contribuer
|
||||||
|
|
||||||
Pour ceux qui souhaitent contribuer du code, consultez notre [Guide de contribution](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
Pour ceux qui souhaitent contribuer du code, consultez notre [Guide de contribution](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
|
|||||||
@ -220,6 +220,10 @@ docker compose up -d
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [@KevinZhaoによるAWS CDK](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [@KevinZhaoによるAWS CDK](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## 貢献
|
## 貢献
|
||||||
|
|
||||||
コードに貢献したい方は、[Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)を参照してください。
|
コードに貢献したい方は、[Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)を参照してください。
|
||||||
|
|||||||
@ -219,6 +219,11 @@ wa'logh nIqHom neH ghun deployment toy'wI' [CDK](https://aws.amazon.com/cdk/) lo
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK qachlot @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK qachlot @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
|
|||||||
@ -213,6 +213,11 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [KevinZhao의 AWS CDK](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [KevinZhao의 AWS CDK](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## 기여
|
## 기여
|
||||||
|
|
||||||
코드에 기여하고 싶은 분들은 [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)를 참조하세요.
|
코드에 기여하고 싶은 분들은 [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)를 참조하세요.
|
||||||
|
|||||||
@ -218,6 +218,11 @@ Implante o Dify na AWS usando [CDK](https://aws.amazon.com/cdk/)
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK por @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK por @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Contribuindo
|
## Contribuindo
|
||||||
|
|
||||||
Para aqueles que desejam contribuir com código, veja nosso [Guia de Contribuição](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
Para aqueles que desejam contribuir com código, veja nosso [Guia de Contribuição](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
|
|||||||
@ -219,6 +219,11 @@ Uvedite Dify v AWS z uporabo [CDK](https://aws.amazon.com/cdk/)
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Prispevam
|
## Prispevam
|
||||||
|
|
||||||
Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah.
|
Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah.
|
||||||
|
|||||||
@ -212,6 +212,11 @@ Dify'ı bulut platformuna tek tıklamayla dağıtın [terraform](https://www.ter
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK tarafından @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK tarafından @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Katkıda Bulunma
|
## Katkıda Bulunma
|
||||||
|
|
||||||
Kod katkısında bulunmak isteyenler için [Katkı Kılavuzumuza](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) bakabilirsiniz.
|
Kod katkısında bulunmak isteyenler için [Katkı Kılavuzumuza](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) bakabilirsiniz.
|
||||||
|
|||||||
@ -224,6 +224,11 @@ Dify 的所有功能都提供相應的 API,因此您可以輕鬆地將 Dify
|
|||||||
|
|
||||||
- [由 @KevinZhao 提供的 AWS CDK](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [由 @KevinZhao 提供的 AWS CDK](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
#### 使用 阿里云计算巢進行部署
|
||||||
|
|
||||||
|
[阿里云](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## 貢獻
|
## 貢獻
|
||||||
|
|
||||||
對於想要貢獻程式碼的開發者,請參閱我們的[貢獻指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)。
|
對於想要貢獻程式碼的開發者,請參閱我們的[貢獻指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)。
|
||||||
|
|||||||
@ -214,6 +214,12 @@ Triển khai Dify trên AWS bằng [CDK](https://aws.amazon.com/cdk/)
|
|||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK bởi @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK bởi @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
|
|
||||||
|
#### Alibaba Cloud
|
||||||
|
|
||||||
|
[Alibaba Cloud Computing Nest](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Dify%E7%A4%BE%E5%8C%BA%E7%89%88)
|
||||||
|
|
||||||
|
|
||||||
## Đóng góp
|
## Đóng góp
|
||||||
|
|
||||||
Đối với những người muốn đóng góp mã, xem [Hướng dẫn Đóng góp](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) của chúng tôi.
|
Đối với những người muốn đóng góp mã, xem [Hướng dẫn Đóng góp](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) của chúng tôi.
|
||||||
|
|||||||
@ -56,8 +56,7 @@ class InsertExploreAppListApi(Resource):
|
|||||||
parser.add_argument("position", type=int, required=True, nullable=False, location="json")
|
parser.add_argument("position", type=int, required=True, nullable=False, location="json")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
with Session(db.engine) as session:
|
app = db.session.execute(select(App).filter(App.id == args["app_id"])).scalar_one_or_none()
|
||||||
app = session.execute(select(App).filter(App.id == args["app_id"])).scalar_one_or_none()
|
|
||||||
if not app:
|
if not app:
|
||||||
raise NotFound(f"App '{args['app_id']}' is not found")
|
raise NotFound(f"App '{args['app_id']}' is not found")
|
||||||
|
|
||||||
@ -78,38 +77,38 @@ class InsertExploreAppListApi(Resource):
|
|||||||
select(RecommendedApp).filter(RecommendedApp.app_id == args["app_id"])
|
select(RecommendedApp).filter(RecommendedApp.app_id == args["app_id"])
|
||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
|
|
||||||
if not recommended_app:
|
if not recommended_app:
|
||||||
recommended_app = RecommendedApp(
|
recommended_app = RecommendedApp(
|
||||||
app_id=app.id,
|
app_id=app.id,
|
||||||
description=desc,
|
description=desc,
|
||||||
copyright=copy_right,
|
copyright=copy_right,
|
||||||
privacy_policy=privacy_policy,
|
privacy_policy=privacy_policy,
|
||||||
custom_disclaimer=custom_disclaimer,
|
custom_disclaimer=custom_disclaimer,
|
||||||
language=args["language"],
|
language=args["language"],
|
||||||
category=args["category"],
|
category=args["category"],
|
||||||
position=args["position"],
|
position=args["position"],
|
||||||
)
|
)
|
||||||
|
|
||||||
db.session.add(recommended_app)
|
db.session.add(recommended_app)
|
||||||
|
|
||||||
app.is_public = True
|
app.is_public = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"result": "success"}, 201
|
return {"result": "success"}, 201
|
||||||
else:
|
else:
|
||||||
recommended_app.description = desc
|
recommended_app.description = desc
|
||||||
recommended_app.copyright = copy_right
|
recommended_app.copyright = copy_right
|
||||||
recommended_app.privacy_policy = privacy_policy
|
recommended_app.privacy_policy = privacy_policy
|
||||||
recommended_app.custom_disclaimer = custom_disclaimer
|
recommended_app.custom_disclaimer = custom_disclaimer
|
||||||
recommended_app.language = args["language"]
|
recommended_app.language = args["language"]
|
||||||
recommended_app.category = args["category"]
|
recommended_app.category = args["category"]
|
||||||
recommended_app.position = args["position"]
|
recommended_app.position = args["position"]
|
||||||
|
|
||||||
app.is_public = True
|
app.is_public = True
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 200
|
||||||
|
|
||||||
|
|
||||||
class InsertExploreAppApi(Resource):
|
class InsertExploreAppApi(Resource):
|
||||||
|
|||||||
@ -17,6 +17,8 @@ from libs.login import login_required
|
|||||||
from models import Account
|
from models import Account
|
||||||
from models.model import App
|
from models.model import App
|
||||||
from services.app_dsl_service import AppDslService, ImportStatus
|
from services.app_dsl_service import AppDslService, ImportStatus
|
||||||
|
from services.enterprise.enterprise_service import EnterpriseService
|
||||||
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
|
|
||||||
class AppImportApi(Resource):
|
class AppImportApi(Resource):
|
||||||
@ -60,7 +62,9 @@ class AppImportApi(Resource):
|
|||||||
app_id=args.get("app_id"),
|
app_id=args.get("app_id"),
|
||||||
)
|
)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
if result.app_id and FeatureService.get_system_features().webapp_auth.enabled:
|
||||||
|
# update web app setting as private
|
||||||
|
EnterpriseService.WebAppAuth.update_app_access_mode(result.app_id, "private")
|
||||||
# Return appropriate status code based on result
|
# Return appropriate status code based on result
|
||||||
status = result.status
|
status = result.status
|
||||||
if status == ImportStatus.FAILED.value:
|
if status == ImportStatus.FAILED.value:
|
||||||
|
|||||||
@ -44,7 +44,6 @@ from core.model_runtime.errors.invoke import InvokeAuthorizationError
|
|||||||
from core.plugin.impl.exc import PluginDaemonClientSideError
|
from core.plugin.impl.exc import PluginDaemonClientSideError
|
||||||
from core.rag.extractor.entity.extract_setting import ExtractSetting
|
from core.rag.extractor.entity.extract_setting import ExtractSetting
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from extensions.ext_redis import redis_client
|
|
||||||
from fields.document_fields import (
|
from fields.document_fields import (
|
||||||
dataset_and_document_fields,
|
dataset_and_document_fields,
|
||||||
document_fields,
|
document_fields,
|
||||||
@ -56,8 +55,6 @@ from models import Dataset, DatasetProcessRule, Document, DocumentSegment, Uploa
|
|||||||
from models.dataset import DocumentPipelineExecutionLog
|
from models.dataset import DocumentPipelineExecutionLog
|
||||||
from services.dataset_service import DatasetService, DocumentService
|
from services.dataset_service import DatasetService, DocumentService
|
||||||
from services.entities.knowledge_entities.knowledge_entities import KnowledgeConfig
|
from services.entities.knowledge_entities.knowledge_entities import KnowledgeConfig
|
||||||
from tasks.add_document_to_index_task import add_document_to_index_task
|
|
||||||
from tasks.remove_document_from_index_task import remove_document_from_index_task
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentResource(Resource):
|
class DocumentResource(Resource):
|
||||||
@ -864,77 +861,16 @@ class DocumentStatusApi(DocumentResource):
|
|||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
DatasetService.check_dataset_permission(dataset, current_user)
|
||||||
|
|
||||||
document_ids = request.args.getlist("document_id")
|
document_ids = request.args.getlist("document_id")
|
||||||
for document_id in document_ids:
|
|
||||||
document = self.get_document(dataset_id, document_id)
|
|
||||||
|
|
||||||
indexing_cache_key = "document_{}_indexing".format(document.id)
|
try:
|
||||||
cache_result = redis_client.get(indexing_cache_key)
|
DocumentService.batch_update_document_status(dataset, document_ids, action, current_user)
|
||||||
if cache_result is not None:
|
except services.errors.document.DocumentIndexingError as e:
|
||||||
raise InvalidActionError(f"Document:{document.name} is being indexed, please try again later")
|
raise InvalidActionError(str(e))
|
||||||
|
except ValueError as e:
|
||||||
|
raise InvalidActionError(str(e))
|
||||||
|
except NotFound as e:
|
||||||
|
raise NotFound(str(e))
|
||||||
|
|
||||||
if action == "enable":
|
|
||||||
if document.enabled:
|
|
||||||
continue
|
|
||||||
document.enabled = True
|
|
||||||
document.disabled_at = None
|
|
||||||
document.disabled_by = None
|
|
||||||
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Set cache to prevent indexing the same document multiple times
|
|
||||||
redis_client.setex(indexing_cache_key, 600, 1)
|
|
||||||
|
|
||||||
add_document_to_index_task.delay(document_id)
|
|
||||||
|
|
||||||
elif action == "disable":
|
|
||||||
if not document.completed_at or document.indexing_status != "completed":
|
|
||||||
raise InvalidActionError(f"Document: {document.name} is not completed.")
|
|
||||||
if not document.enabled:
|
|
||||||
continue
|
|
||||||
|
|
||||||
document.enabled = False
|
|
||||||
document.disabled_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
document.disabled_by = current_user.id
|
|
||||||
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Set cache to prevent indexing the same document multiple times
|
|
||||||
redis_client.setex(indexing_cache_key, 600, 1)
|
|
||||||
|
|
||||||
remove_document_from_index_task.delay(document_id)
|
|
||||||
|
|
||||||
elif action == "archive":
|
|
||||||
if document.archived:
|
|
||||||
continue
|
|
||||||
|
|
||||||
document.archived = True
|
|
||||||
document.archived_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
document.archived_by = current_user.id
|
|
||||||
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
if document.enabled:
|
|
||||||
# Set cache to prevent indexing the same document multiple times
|
|
||||||
redis_client.setex(indexing_cache_key, 600, 1)
|
|
||||||
|
|
||||||
remove_document_from_index_task.delay(document_id)
|
|
||||||
|
|
||||||
elif action == "un_archive":
|
|
||||||
if not document.archived:
|
|
||||||
continue
|
|
||||||
document.archived = False
|
|
||||||
document.archived_at = None
|
|
||||||
document.archived_by = None
|
|
||||||
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Set cache to prevent indexing the same document multiple times
|
|
||||||
redis_client.setex(indexing_cache_key, 600, 1)
|
|
||||||
|
|
||||||
add_document_to_index_task.delay(document_id)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise InvalidActionError()
|
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 200
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ class LoadBalancingCredentialsValidateApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
def post(self, provider: str):
|
def post(self, provider: str):
|
||||||
if not TenantAccountRole.is_privileged_role(current_user.current_tenant.current_role):
|
if not TenantAccountRole.is_privileged_role(current_user.current_role):
|
||||||
raise Forbidden()
|
raise Forbidden()
|
||||||
|
|
||||||
tenant_id = current_user.current_tenant_id
|
tenant_id = current_user.current_tenant_id
|
||||||
@ -64,7 +64,7 @@ class LoadBalancingConfigCredentialsValidateApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
def post(self, provider: str, config_id: str):
|
def post(self, provider: str, config_id: str):
|
||||||
if not TenantAccountRole.is_privileged_role(current_user.current_tenant.current_role):
|
if not TenantAccountRole.is_privileged_role(current_user.current_role):
|
||||||
raise Forbidden()
|
raise Forbidden()
|
||||||
|
|
||||||
tenant_id = current_user.current_tenant_id
|
tenant_id = current_user.current_tenant_id
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from werkzeug.exceptions import Forbidden, NotFound
|
|||||||
|
|
||||||
import services.dataset_service
|
import services.dataset_service
|
||||||
from controllers.service_api import api
|
from controllers.service_api import api
|
||||||
from controllers.service_api.dataset.error import DatasetInUseError, DatasetNameDuplicateError
|
from controllers.service_api.dataset.error import DatasetInUseError, DatasetNameDuplicateError, InvalidActionError
|
||||||
from controllers.service_api.wraps import (
|
from controllers.service_api.wraps import (
|
||||||
DatasetApiResource,
|
DatasetApiResource,
|
||||||
cloud_edition_billing_rate_limit_check,
|
cloud_edition_billing_rate_limit_check,
|
||||||
@ -17,7 +17,7 @@ from fields.dataset_fields import dataset_detail_fields
|
|||||||
from fields.tag_fields import tag_fields
|
from fields.tag_fields import tag_fields
|
||||||
from libs.login import current_user
|
from libs.login import current_user
|
||||||
from models.dataset import Dataset, DatasetPermissionEnum
|
from models.dataset import Dataset, DatasetPermissionEnum
|
||||||
from services.dataset_service import DatasetPermissionService, DatasetService
|
from services.dataset_service import DatasetPermissionService, DatasetService, DocumentService
|
||||||
from services.entities.knowledge_entities.knowledge_entities import RetrievalModel
|
from services.entities.knowledge_entities.knowledge_entities import RetrievalModel
|
||||||
from services.tag_service import TagService
|
from services.tag_service import TagService
|
||||||
|
|
||||||
@ -329,6 +329,56 @@ class DatasetApi(DatasetApiResource):
|
|||||||
raise DatasetInUseError()
|
raise DatasetInUseError()
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentStatusApi(DatasetApiResource):
|
||||||
|
"""Resource for batch document status operations."""
|
||||||
|
|
||||||
|
def patch(self, tenant_id, dataset_id, action):
|
||||||
|
"""
|
||||||
|
Batch update document status.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id: tenant id
|
||||||
|
dataset_id: dataset id
|
||||||
|
action: action to perform (enable, disable, archive, un_archive)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary with a key 'result' and a value 'success'
|
||||||
|
int: HTTP status code 200 indicating that the operation was successful.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
NotFound: If the dataset with the given ID does not exist.
|
||||||
|
Forbidden: If the user does not have permission.
|
||||||
|
InvalidActionError: If the action is invalid or cannot be performed.
|
||||||
|
"""
|
||||||
|
dataset_id_str = str(dataset_id)
|
||||||
|
dataset = DatasetService.get_dataset(dataset_id_str)
|
||||||
|
|
||||||
|
if dataset is None:
|
||||||
|
raise NotFound("Dataset not found.")
|
||||||
|
|
||||||
|
# Check user's permission
|
||||||
|
try:
|
||||||
|
DatasetService.check_dataset_permission(dataset, current_user)
|
||||||
|
except services.errors.account.NoPermissionError as e:
|
||||||
|
raise Forbidden(str(e))
|
||||||
|
|
||||||
|
# Check dataset model setting
|
||||||
|
DatasetService.check_dataset_model_setting(dataset)
|
||||||
|
|
||||||
|
# Get document IDs from request body
|
||||||
|
data = request.get_json()
|
||||||
|
document_ids = data.get("document_ids", [])
|
||||||
|
|
||||||
|
try:
|
||||||
|
DocumentService.batch_update_document_status(dataset, document_ids, action, current_user)
|
||||||
|
except services.errors.document.DocumentIndexingError as e:
|
||||||
|
raise InvalidActionError(str(e))
|
||||||
|
except ValueError as e:
|
||||||
|
raise InvalidActionError(str(e))
|
||||||
|
|
||||||
|
return {"result": "success"}, 200
|
||||||
|
|
||||||
|
|
||||||
class DatasetTagsApi(DatasetApiResource):
|
class DatasetTagsApi(DatasetApiResource):
|
||||||
@validate_dataset_token
|
@validate_dataset_token
|
||||||
@marshal_with(tag_fields)
|
@marshal_with(tag_fields)
|
||||||
@ -457,6 +507,7 @@ class DatasetTagsBindingStatusApi(DatasetApiResource):
|
|||||||
|
|
||||||
api.add_resource(DatasetListApi, "/datasets")
|
api.add_resource(DatasetListApi, "/datasets")
|
||||||
api.add_resource(DatasetApi, "/datasets/<uuid:dataset_id>")
|
api.add_resource(DatasetApi, "/datasets/<uuid:dataset_id>")
|
||||||
|
api.add_resource(DocumentStatusApi, "/datasets/<uuid:dataset_id>/documents/status/<string:action>")
|
||||||
api.add_resource(DatasetTagsApi, "/datasets/tags")
|
api.add_resource(DatasetTagsApi, "/datasets/tags")
|
||||||
api.add_resource(DatasetTagBindingApi, "/datasets/tags/binding")
|
api.add_resource(DatasetTagBindingApi, "/datasets/tags/binding")
|
||||||
api.add_resource(DatasetTagUnbindingApi, "/datasets/tags/unbinding")
|
api.add_resource(DatasetTagUnbindingApi, "/datasets/tags/unbinding")
|
||||||
|
|||||||
@ -68,22 +68,17 @@ class MarkdownExtractor(BaseExtractor):
|
|||||||
continue
|
continue
|
||||||
header_match = re.match(r"^#+\s", line)
|
header_match = re.match(r"^#+\s", line)
|
||||||
if header_match:
|
if header_match:
|
||||||
if current_header is not None:
|
markdown_tups.append((current_header, current_text))
|
||||||
markdown_tups.append((current_header, current_text))
|
|
||||||
|
|
||||||
current_header = line
|
current_header = line
|
||||||
current_text = ""
|
current_text = ""
|
||||||
else:
|
else:
|
||||||
current_text += line + "\n"
|
current_text += line + "\n"
|
||||||
markdown_tups.append((current_header, current_text))
|
markdown_tups.append((current_header, current_text))
|
||||||
|
|
||||||
if current_header is not None:
|
markdown_tups = [
|
||||||
# pass linting, assert keys are defined
|
(re.sub(r"#", "", cast(str, key)).strip() if key else None, re.sub(r"<.*?>", "", value))
|
||||||
markdown_tups = [
|
for key, value in markdown_tups
|
||||||
(re.sub(r"#", "", cast(str, key)).strip(), re.sub(r"<.*?>", "", value)) for key, value in markdown_tups
|
]
|
||||||
]
|
|
||||||
else:
|
|
||||||
markdown_tups = [(key, re.sub("\n", "", value)) for key, value in markdown_tups]
|
|
||||||
|
|
||||||
return markdown_tups
|
return markdown_tups
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.engine import Engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
@ -146,20 +146,7 @@ class SQLAlchemyWorkflowExecutionRepository(WorkflowExecutionRepository):
|
|||||||
db_model.workflow_id = domain_model.workflow_id
|
db_model.workflow_id = domain_model.workflow_id
|
||||||
db_model.triggered_from = self._triggered_from
|
db_model.triggered_from = self._triggered_from
|
||||||
|
|
||||||
# Check if this is a new record
|
# No sequence number generation needed anymore
|
||||||
with self._session_factory() as session:
|
|
||||||
existing = session.scalar(select(WorkflowRun).where(WorkflowRun.id == domain_model.id_))
|
|
||||||
if not existing:
|
|
||||||
# For new records, get the next sequence number
|
|
||||||
stmt = select(func.max(WorkflowRun.sequence_number)).where(
|
|
||||||
WorkflowRun.app_id == self._app_id,
|
|
||||||
WorkflowRun.tenant_id == self._tenant_id,
|
|
||||||
)
|
|
||||||
max_sequence = session.scalar(stmt)
|
|
||||||
db_model.sequence_number = (max_sequence or 0) + 1
|
|
||||||
else:
|
|
||||||
# For updates, keep the existing sequence number
|
|
||||||
db_model.sequence_number = existing.sequence_number
|
|
||||||
|
|
||||||
db_model.type = domain_model.workflow_type
|
db_model.type = domain_model.workflow_type
|
||||||
db_model.version = domain_model.workflow_version
|
db_model.version = domain_model.workflow_version
|
||||||
|
|||||||
@ -8,5 +8,4 @@ EMPTY_VALUE_MAPPING = {
|
|||||||
SegmentType.ARRAY_STRING: [],
|
SegmentType.ARRAY_STRING: [],
|
||||||
SegmentType.ARRAY_NUMBER: [],
|
SegmentType.ARRAY_NUMBER: [],
|
||||||
SegmentType.ARRAY_OBJECT: [],
|
SegmentType.ARRAY_OBJECT: [],
|
||||||
SegmentType.ARRAY_FILE: [],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from core.file import File
|
|
||||||
from core.variables import SegmentType
|
from core.variables import SegmentType
|
||||||
|
|
||||||
from .enums import Operation
|
from .enums import Operation
|
||||||
@ -86,8 +85,6 @@ def is_input_value_valid(*, variable_type: SegmentType, operation: Operation, va
|
|||||||
return isinstance(value, int | float)
|
return isinstance(value, int | float)
|
||||||
case SegmentType.ARRAY_OBJECT if operation == Operation.APPEND:
|
case SegmentType.ARRAY_OBJECT if operation == Operation.APPEND:
|
||||||
return isinstance(value, dict)
|
return isinstance(value, dict)
|
||||||
case SegmentType.ARRAY_FILE if operation == Operation.APPEND:
|
|
||||||
return isinstance(value, File)
|
|
||||||
|
|
||||||
# Array & Extend / Overwrite
|
# Array & Extend / Overwrite
|
||||||
case SegmentType.ARRAY_ANY if operation in {Operation.EXTEND, Operation.OVER_WRITE}:
|
case SegmentType.ARRAY_ANY if operation in {Operation.EXTEND, Operation.OVER_WRITE}:
|
||||||
@ -98,8 +95,6 @@ def is_input_value_valid(*, variable_type: SegmentType, operation: Operation, va
|
|||||||
return isinstance(value, list) and all(isinstance(item, int | float) for item in value)
|
return isinstance(value, list) and all(isinstance(item, int | float) for item in value)
|
||||||
case SegmentType.ARRAY_OBJECT if operation in {Operation.EXTEND, Operation.OVER_WRITE}:
|
case SegmentType.ARRAY_OBJECT if operation in {Operation.EXTEND, Operation.OVER_WRITE}:
|
||||||
return isinstance(value, list) and all(isinstance(item, dict) for item in value)
|
return isinstance(value, list) and all(isinstance(item, dict) for item in value)
|
||||||
case SegmentType.ARRAY_FILE if operation in {Operation.EXTEND, Operation.OVER_WRITE}:
|
|
||||||
return isinstance(value, list) and all(isinstance(item, File) for item in value)
|
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -110,8 +110,6 @@ def _build_variable_from_mapping(*, mapping: Mapping[str, Any], selector: Sequen
|
|||||||
result = ArrayNumberVariable.model_validate(mapping)
|
result = ArrayNumberVariable.model_validate(mapping)
|
||||||
case SegmentType.ARRAY_OBJECT if isinstance(value, list):
|
case SegmentType.ARRAY_OBJECT if isinstance(value, list):
|
||||||
result = ArrayObjectVariable.model_validate(mapping)
|
result = ArrayObjectVariable.model_validate(mapping)
|
||||||
case SegmentType.ARRAY_FILE if isinstance(value, list):
|
|
||||||
result = ArrayFileVariable.model_validate(mapping)
|
|
||||||
case _:
|
case _:
|
||||||
raise VariableError(f"not supported value type {value_type}")
|
raise VariableError(f"not supported value type {value_type}")
|
||||||
if result.size > dify_config.MAX_VARIABLE_SIZE:
|
if result.size > dify_config.MAX_VARIABLE_SIZE:
|
||||||
|
|||||||
@ -19,7 +19,6 @@ workflow_run_for_log_fields = {
|
|||||||
|
|
||||||
workflow_run_for_list_fields = {
|
workflow_run_for_list_fields = {
|
||||||
"id": fields.String,
|
"id": fields.String,
|
||||||
"sequence_number": fields.Integer,
|
|
||||||
"version": fields.String,
|
"version": fields.String,
|
||||||
"status": fields.String,
|
"status": fields.String,
|
||||||
"elapsed_time": fields.Float,
|
"elapsed_time": fields.Float,
|
||||||
@ -36,7 +35,6 @@ advanced_chat_workflow_run_for_list_fields = {
|
|||||||
"id": fields.String,
|
"id": fields.String,
|
||||||
"conversation_id": fields.String,
|
"conversation_id": fields.String,
|
||||||
"message_id": fields.String,
|
"message_id": fields.String,
|
||||||
"sequence_number": fields.Integer,
|
|
||||||
"version": fields.String,
|
"version": fields.String,
|
||||||
"status": fields.String,
|
"status": fields.String,
|
||||||
"elapsed_time": fields.Float,
|
"elapsed_time": fields.Float,
|
||||||
@ -63,7 +61,6 @@ workflow_run_pagination_fields = {
|
|||||||
|
|
||||||
workflow_run_detail_fields = {
|
workflow_run_detail_fields = {
|
||||||
"id": fields.String,
|
"id": fields.String,
|
||||||
"sequence_number": fields.Integer,
|
|
||||||
"version": fields.String,
|
"version": fields.String,
|
||||||
"graph": fields.Raw(attribute="graph_dict"),
|
"graph": fields.Raw(attribute="graph_dict"),
|
||||||
"inputs": fields.Raw(attribute="inputs_dict"),
|
"inputs": fields.Raw(attribute="inputs_dict"),
|
||||||
|
|||||||
@ -35,7 +35,10 @@ class SendGridClient:
|
|||||||
logging.exception("SendGridClient Timeout occurred while sending email")
|
logging.exception("SendGridClient Timeout occurred while sending email")
|
||||||
raise
|
raise
|
||||||
except (UnauthorizedError, ForbiddenError) as e:
|
except (UnauthorizedError, ForbiddenError) as e:
|
||||||
logging.exception("SendGridClient Authentication failed. Verify that your credentials and the 'from")
|
logging.exception(
|
||||||
|
"SendGridClient Authentication failed. "
|
||||||
|
"Verify that your credentials and the 'from' email address are correct"
|
||||||
|
)
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(f"SendGridClient Unexpected error occurred while sending email to {_to}")
|
logging.exception(f"SendGridClient Unexpected error occurred while sending email to {_to}")
|
||||||
|
|||||||
@ -0,0 +1,66 @@
|
|||||||
|
"""remove sequence_number from workflow_runs
|
||||||
|
|
||||||
|
Revision ID: 0ab65e1cc7fa
|
||||||
|
Revises: 4474872b0ee6
|
||||||
|
Create Date: 2025-06-19 16:33:13.377215
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import models as models
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0ab65e1cc7fa'
|
||||||
|
down_revision = '4474872b0ee6'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('workflow_runs', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('workflow_run_tenant_app_sequence_idx'))
|
||||||
|
batch_op.drop_column('sequence_number')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
|
||||||
|
# WARNING: This downgrade CANNOT recover the original sequence_number values!
|
||||||
|
# The original sequence numbers are permanently lost after the upgrade.
|
||||||
|
# This downgrade will regenerate sequence numbers based on created_at order,
|
||||||
|
# which may result in different values than the original sequence numbers.
|
||||||
|
#
|
||||||
|
# If you need to preserve original sequence numbers, use the alternative
|
||||||
|
# migration approach that creates a backup table before removal.
|
||||||
|
|
||||||
|
# Step 1: Add sequence_number column as nullable first
|
||||||
|
with op.batch_alter_table('workflow_runs', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('sequence_number', sa.INTEGER(), autoincrement=False, nullable=True))
|
||||||
|
|
||||||
|
# Step 2: Populate sequence_number values based on created_at order within each app
|
||||||
|
# NOTE: This recreates sequence numbering logic but values will be different
|
||||||
|
# from the original sequence numbers that were removed in the upgrade
|
||||||
|
connection = op.get_bind()
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
UPDATE workflow_runs
|
||||||
|
SET sequence_number = subquery.row_num
|
||||||
|
FROM (
|
||||||
|
SELECT id, ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY tenant_id, app_id
|
||||||
|
ORDER BY created_at, id
|
||||||
|
) as row_num
|
||||||
|
FROM workflow_runs
|
||||||
|
) subquery
|
||||||
|
WHERE workflow_runs.id = subquery.id
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# Step 3: Make the column NOT NULL and add the index
|
||||||
|
with op.batch_alter_table('workflow_runs', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('sequence_number', nullable=False)
|
||||||
|
batch_op.create_index(batch_op.f('workflow_run_tenant_app_sequence_idx'), ['tenant_id', 'app_id', 'sequence_number'], unique=False)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -410,7 +410,7 @@ class WorkflowRun(Base):
|
|||||||
- id (uuid) Run ID
|
- id (uuid) Run ID
|
||||||
- tenant_id (uuid) Workspace ID
|
- tenant_id (uuid) Workspace ID
|
||||||
- app_id (uuid) App ID
|
- app_id (uuid) App ID
|
||||||
- sequence_number (int) Auto-increment sequence number, incremented within the App, starting from 1
|
|
||||||
- workflow_id (uuid) Workflow ID
|
- workflow_id (uuid) Workflow ID
|
||||||
- type (string) Workflow type
|
- type (string) Workflow type
|
||||||
- triggered_from (string) Trigger source
|
- triggered_from (string) Trigger source
|
||||||
@ -443,13 +443,12 @@ class WorkflowRun(Base):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
db.PrimaryKeyConstraint("id", name="workflow_run_pkey"),
|
db.PrimaryKeyConstraint("id", name="workflow_run_pkey"),
|
||||||
db.Index("workflow_run_triggerd_from_idx", "tenant_id", "app_id", "triggered_from"),
|
db.Index("workflow_run_triggerd_from_idx", "tenant_id", "app_id", "triggered_from"),
|
||||||
db.Index("workflow_run_tenant_app_sequence_idx", "tenant_id", "app_id", "sequence_number"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()"))
|
id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()"))
|
||||||
tenant_id: Mapped[str] = mapped_column(StringUUID)
|
tenant_id: Mapped[str] = mapped_column(StringUUID)
|
||||||
app_id: Mapped[str] = mapped_column(StringUUID)
|
app_id: Mapped[str] = mapped_column(StringUUID)
|
||||||
sequence_number: Mapped[int] = mapped_column()
|
|
||||||
workflow_id: Mapped[str] = mapped_column(StringUUID)
|
workflow_id: Mapped[str] = mapped_column(StringUUID)
|
||||||
type: Mapped[str] = mapped_column(db.String(255))
|
type: Mapped[str] = mapped_column(db.String(255))
|
||||||
triggered_from: Mapped[str] = mapped_column(db.String(255))
|
triggered_from: Mapped[str] = mapped_column(db.String(255))
|
||||||
@ -509,7 +508,6 @@ class WorkflowRun(Base):
|
|||||||
"id": self.id,
|
"id": self.id,
|
||||||
"tenant_id": self.tenant_id,
|
"tenant_id": self.tenant_id,
|
||||||
"app_id": self.app_id,
|
"app_id": self.app_id,
|
||||||
"sequence_number": self.sequence_number,
|
|
||||||
"workflow_id": self.workflow_id,
|
"workflow_id": self.workflow_id,
|
||||||
"type": self.type,
|
"type": self.type,
|
||||||
"triggered_from": self.triggered_from,
|
"triggered_from": self.triggered_from,
|
||||||
@ -535,7 +533,6 @@ class WorkflowRun(Base):
|
|||||||
id=data.get("id"),
|
id=data.get("id"),
|
||||||
tenant_id=data.get("tenant_id"),
|
tenant_id=data.get("tenant_id"),
|
||||||
app_id=data.get("app_id"),
|
app_id=data.get("app_id"),
|
||||||
sequence_number=data.get("sequence_number"),
|
|
||||||
workflow_id=data.get("workflow_id"),
|
workflow_id=data.get("workflow_id"),
|
||||||
type=data.get("type"),
|
type=data.get("type"),
|
||||||
triggered_from=data.get("triggered_from"),
|
triggered_from=data.get("triggered_from"),
|
||||||
|
|||||||
@ -64,6 +64,7 @@ from services.external_knowledge_service import ExternalDatasetService
|
|||||||
from services.feature_service import FeatureModel, FeatureService
|
from services.feature_service import FeatureModel, FeatureService
|
||||||
from services.tag_service import TagService
|
from services.tag_service import TagService
|
||||||
from services.vector_service import VectorService
|
from services.vector_service import VectorService
|
||||||
|
from tasks.add_document_to_index_task import add_document_to_index_task
|
||||||
from tasks.batch_clean_document_task import batch_clean_document_task
|
from tasks.batch_clean_document_task import batch_clean_document_task
|
||||||
from tasks.clean_notion_document_task import clean_notion_document_task
|
from tasks.clean_notion_document_task import clean_notion_document_task
|
||||||
from tasks.deal_dataset_index_update_task import deal_dataset_index_update_task
|
from tasks.deal_dataset_index_update_task import deal_dataset_index_update_task
|
||||||
@ -76,6 +77,7 @@ from tasks.document_indexing_update_task import document_indexing_update_task
|
|||||||
from tasks.duplicate_document_indexing_task import duplicate_document_indexing_task
|
from tasks.duplicate_document_indexing_task import duplicate_document_indexing_task
|
||||||
from tasks.enable_segments_to_index_task import enable_segments_to_index_task
|
from tasks.enable_segments_to_index_task import enable_segments_to_index_task
|
||||||
from tasks.recover_document_indexing_task import recover_document_indexing_task
|
from tasks.recover_document_indexing_task import recover_document_indexing_task
|
||||||
|
from tasks.remove_document_from_index_task import remove_document_from_index_task
|
||||||
from tasks.retry_document_indexing_task import retry_document_indexing_task
|
from tasks.retry_document_indexing_task import retry_document_indexing_task
|
||||||
from tasks.sync_website_document_indexing_task import sync_website_document_indexing_task
|
from tasks.sync_website_document_indexing_task import sync_website_document_indexing_task
|
||||||
|
|
||||||
@ -490,7 +492,7 @@ class DatasetService:
|
|||||||
raise ValueError(ex.description)
|
raise ValueError(ex.description)
|
||||||
|
|
||||||
filtered_data["updated_by"] = user.id
|
filtered_data["updated_by"] = user.id
|
||||||
filtered_data["updated_at"] = datetime.datetime.now()
|
filtered_data["updated_at"] = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
|
||||||
|
|
||||||
# update Retrieval model
|
# update Retrieval model
|
||||||
filtered_data["retrieval_model"] = data["retrieval_model"]
|
filtered_data["retrieval_model"] = data["retrieval_model"]
|
||||||
@ -1157,12 +1159,17 @@ class DocumentService:
|
|||||||
process_rule = knowledge_config.process_rule
|
process_rule = knowledge_config.process_rule
|
||||||
if process_rule:
|
if process_rule:
|
||||||
if process_rule.mode in ("custom", "hierarchical"):
|
if process_rule.mode in ("custom", "hierarchical"):
|
||||||
dataset_process_rule = DatasetProcessRule(
|
if process_rule.rules:
|
||||||
dataset_id=dataset.id,
|
dataset_process_rule = DatasetProcessRule(
|
||||||
mode=process_rule.mode,
|
dataset_id=dataset.id,
|
||||||
rules=process_rule.rules.model_dump_json() if process_rule.rules else None,
|
mode=process_rule.mode,
|
||||||
created_by=account.id,
|
rules=process_rule.rules.model_dump_json() if process_rule.rules else None,
|
||||||
)
|
created_by=account.id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
dataset_process_rule = dataset.latest_process_rule
|
||||||
|
if not dataset_process_rule:
|
||||||
|
raise ValueError("No process rule found.")
|
||||||
elif process_rule.mode == "automatic":
|
elif process_rule.mode == "automatic":
|
||||||
dataset_process_rule = DatasetProcessRule(
|
dataset_process_rule = DatasetProcessRule(
|
||||||
dataset_id=dataset.id,
|
dataset_id=dataset.id,
|
||||||
@ -2061,6 +2068,191 @@ class DocumentService:
|
|||||||
if not isinstance(args["process_rule"]["rules"]["segmentation"]["max_tokens"], int):
|
if not isinstance(args["process_rule"]["rules"]["segmentation"]["max_tokens"], int):
|
||||||
raise ValueError("Process rule segmentation max_tokens is invalid")
|
raise ValueError("Process rule segmentation max_tokens is invalid")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def batch_update_document_status(dataset: Dataset, document_ids: list[str], action: str, user):
|
||||||
|
"""
|
||||||
|
Batch update document status.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dataset (Dataset): The dataset object
|
||||||
|
document_ids (list[str]): List of document IDs to update
|
||||||
|
action (str): Action to perform (enable, disable, archive, un_archive)
|
||||||
|
user: Current user performing the action
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
DocumentIndexingError: If document is being indexed or not in correct state
|
||||||
|
ValueError: If action is invalid
|
||||||
|
"""
|
||||||
|
if not document_ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Early validation of action parameter
|
||||||
|
valid_actions = ["enable", "disable", "archive", "un_archive"]
|
||||||
|
if action not in valid_actions:
|
||||||
|
raise ValueError(f"Invalid action: {action}. Must be one of {valid_actions}")
|
||||||
|
|
||||||
|
documents_to_update = []
|
||||||
|
|
||||||
|
# First pass: validate all documents and prepare updates
|
||||||
|
for document_id in document_ids:
|
||||||
|
document = DocumentService.get_document(dataset.id, document_id)
|
||||||
|
if not document:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if document is being indexed
|
||||||
|
indexing_cache_key = f"document_{document.id}_indexing"
|
||||||
|
cache_result = redis_client.get(indexing_cache_key)
|
||||||
|
if cache_result is not None:
|
||||||
|
raise DocumentIndexingError(f"Document:{document.name} is being indexed, please try again later")
|
||||||
|
|
||||||
|
# Prepare update based on action
|
||||||
|
update_info = DocumentService._prepare_document_status_update(document, action, user)
|
||||||
|
if update_info:
|
||||||
|
documents_to_update.append(update_info)
|
||||||
|
|
||||||
|
# Second pass: apply all updates in a single transaction
|
||||||
|
if documents_to_update:
|
||||||
|
try:
|
||||||
|
for update_info in documents_to_update:
|
||||||
|
document = update_info["document"]
|
||||||
|
updates = update_info["updates"]
|
||||||
|
|
||||||
|
# Apply updates to the document
|
||||||
|
for field, value in updates.items():
|
||||||
|
setattr(document, field, value)
|
||||||
|
|
||||||
|
db.session.add(document)
|
||||||
|
|
||||||
|
# Batch commit all changes
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
# Rollback on any error
|
||||||
|
db.session.rollback()
|
||||||
|
raise e
|
||||||
|
# Execute async tasks and set Redis cache after successful commit
|
||||||
|
# propagation_error is used to capture any errors for submitting async task execution
|
||||||
|
propagation_error = None
|
||||||
|
for update_info in documents_to_update:
|
||||||
|
try:
|
||||||
|
# Execute async tasks after successful commit
|
||||||
|
if update_info["async_task"]:
|
||||||
|
task_info = update_info["async_task"]
|
||||||
|
task_func = task_info["function"]
|
||||||
|
task_args = task_info["args"]
|
||||||
|
task_func.delay(*task_args)
|
||||||
|
except Exception as e:
|
||||||
|
# Log the error but do not rollback the transaction
|
||||||
|
logging.exception(f"Error executing async task for document {update_info['document'].id}")
|
||||||
|
# don't raise the error immediately, but capture it for later
|
||||||
|
propagation_error = e
|
||||||
|
try:
|
||||||
|
# Set Redis cache if needed after successful commit
|
||||||
|
if update_info["set_cache"]:
|
||||||
|
document = update_info["document"]
|
||||||
|
indexing_cache_key = f"document_{document.id}_indexing"
|
||||||
|
redis_client.setex(indexing_cache_key, 600, 1)
|
||||||
|
except Exception as e:
|
||||||
|
# Log the error but do not rollback the transaction
|
||||||
|
logging.exception(f"Error setting cache for document {update_info['document'].id}")
|
||||||
|
# Raise any propagation error after all updates
|
||||||
|
if propagation_error:
|
||||||
|
raise propagation_error
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _prepare_document_status_update(document, action: str, user):
|
||||||
|
"""
|
||||||
|
Prepare document status update information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
document: Document object to update
|
||||||
|
action: Action to perform
|
||||||
|
user: Current user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Update information or None if no update needed
|
||||||
|
"""
|
||||||
|
now = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
|
||||||
|
|
||||||
|
if action == "enable":
|
||||||
|
return DocumentService._prepare_enable_update(document, now)
|
||||||
|
elif action == "disable":
|
||||||
|
return DocumentService._prepare_disable_update(document, user, now)
|
||||||
|
elif action == "archive":
|
||||||
|
return DocumentService._prepare_archive_update(document, user, now)
|
||||||
|
elif action == "un_archive":
|
||||||
|
return DocumentService._prepare_unarchive_update(document, now)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _prepare_enable_update(document, now):
|
||||||
|
"""Prepare updates for enabling a document."""
|
||||||
|
if document.enabled:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"document": document,
|
||||||
|
"updates": {"enabled": True, "disabled_at": None, "disabled_by": None, "updated_at": now},
|
||||||
|
"async_task": {"function": add_document_to_index_task, "args": [document.id]},
|
||||||
|
"set_cache": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _prepare_disable_update(document, user, now):
|
||||||
|
"""Prepare updates for disabling a document."""
|
||||||
|
if not document.completed_at or document.indexing_status != "completed":
|
||||||
|
raise DocumentIndexingError(f"Document: {document.name} is not completed.")
|
||||||
|
|
||||||
|
if not document.enabled:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"document": document,
|
||||||
|
"updates": {"enabled": False, "disabled_at": now, "disabled_by": user.id, "updated_at": now},
|
||||||
|
"async_task": {"function": remove_document_from_index_task, "args": [document.id]},
|
||||||
|
"set_cache": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _prepare_archive_update(document, user, now):
|
||||||
|
"""Prepare updates for archiving a document."""
|
||||||
|
if document.archived:
|
||||||
|
return None
|
||||||
|
|
||||||
|
update_info = {
|
||||||
|
"document": document,
|
||||||
|
"updates": {"archived": True, "archived_at": now, "archived_by": user.id, "updated_at": now},
|
||||||
|
"async_task": None,
|
||||||
|
"set_cache": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only set async task and cache if document is currently enabled
|
||||||
|
if document.enabled:
|
||||||
|
update_info["async_task"] = {"function": remove_document_from_index_task, "args": [document.id]}
|
||||||
|
update_info["set_cache"] = True
|
||||||
|
|
||||||
|
return update_info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _prepare_unarchive_update(document, now):
|
||||||
|
"""Prepare updates for unarchiving a document."""
|
||||||
|
if not document.archived:
|
||||||
|
return None
|
||||||
|
|
||||||
|
update_info = {
|
||||||
|
"document": document,
|
||||||
|
"updates": {"archived": False, "archived_at": None, "archived_by": None, "updated_at": now},
|
||||||
|
"async_task": None,
|
||||||
|
"set_cache": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only re-index if the document is currently enabled
|
||||||
|
if document.enabled:
|
||||||
|
update_info["async_task"] = {"function": add_document_to_index_task, "args": [document.id]}
|
||||||
|
update_info["set_cache"] = True
|
||||||
|
|
||||||
|
return update_info
|
||||||
|
|
||||||
|
|
||||||
class SegmentService:
|
class SegmentService:
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
@ -11,6 +12,24 @@ PROJECT_DIR = os.path.abspath(os.path.join(ABS_PATH, os.pardir, os.pardir))
|
|||||||
|
|
||||||
CACHED_APP = Flask(__name__)
|
CACHED_APP = Flask(__name__)
|
||||||
|
|
||||||
|
# set global mock for Redis client
|
||||||
|
redis_mock = MagicMock()
|
||||||
|
redis_mock.get = MagicMock(return_value=None)
|
||||||
|
redis_mock.setex = MagicMock()
|
||||||
|
redis_mock.setnx = MagicMock()
|
||||||
|
redis_mock.delete = MagicMock()
|
||||||
|
redis_mock.lock = MagicMock()
|
||||||
|
redis_mock.exists = MagicMock(return_value=False)
|
||||||
|
redis_mock.set = MagicMock()
|
||||||
|
redis_mock.expire = MagicMock()
|
||||||
|
redis_mock.hgetall = MagicMock(return_value={})
|
||||||
|
redis_mock.hdel = MagicMock()
|
||||||
|
redis_mock.incr = MagicMock(return_value=1)
|
||||||
|
|
||||||
|
# apply the mock to the Redis client in the Flask app
|
||||||
|
redis_patcher = patch("extensions.ext_redis.redis_client", redis_mock)
|
||||||
|
redis_patcher.start()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app() -> Flask:
|
def app() -> Flask:
|
||||||
@ -21,3 +40,19 @@ def app() -> Flask:
|
|||||||
def _provide_app_context(app: Flask):
|
def _provide_app_context(app: Flask):
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def reset_redis_mock():
|
||||||
|
"""reset the Redis mock before each test"""
|
||||||
|
redis_mock.reset_mock()
|
||||||
|
redis_mock.get.return_value = None
|
||||||
|
redis_mock.setex.return_value = None
|
||||||
|
redis_mock.setnx.return_value = None
|
||||||
|
redis_mock.delete.return_value = None
|
||||||
|
redis_mock.exists.return_value = False
|
||||||
|
redis_mock.set.return_value = None
|
||||||
|
redis_mock.expire.return_value = None
|
||||||
|
redis_mock.hgetall.return_value = {}
|
||||||
|
redis_mock.hdel.return_value = None
|
||||||
|
redis_mock.incr.return_value = 1
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
from core.rag.extractor.markdown_extractor import MarkdownExtractor
|
||||||
|
|
||||||
|
|
||||||
|
def test_markdown_to_tups():
|
||||||
|
markdown = """
|
||||||
|
this is some text without header
|
||||||
|
|
||||||
|
# title 1
|
||||||
|
this is balabala text
|
||||||
|
|
||||||
|
## title 2
|
||||||
|
this is more specific text.
|
||||||
|
"""
|
||||||
|
extractor = MarkdownExtractor(file_path="dummy_path")
|
||||||
|
updated_output = extractor.markdown_to_tups(markdown)
|
||||||
|
assert len(updated_output) == 3
|
||||||
|
key, header_value = updated_output[0]
|
||||||
|
assert key == None
|
||||||
|
assert header_value.strip() == "this is some text without header"
|
||||||
|
title_1, value = updated_output[1]
|
||||||
|
assert title_1.strip() == "title 1"
|
||||||
|
assert value.strip() == "this is balabala text"
|
||||||
@ -163,7 +163,6 @@ def real_workflow_run():
|
|||||||
workflow_run.tenant_id = "test-tenant-id"
|
workflow_run.tenant_id = "test-tenant-id"
|
||||||
workflow_run.app_id = "test-app-id"
|
workflow_run.app_id = "test-app-id"
|
||||||
workflow_run.workflow_id = "test-workflow-id"
|
workflow_run.workflow_id = "test-workflow-id"
|
||||||
workflow_run.sequence_number = 1
|
|
||||||
workflow_run.type = "chat"
|
workflow_run.type = "chat"
|
||||||
workflow_run.triggered_from = "app-run"
|
workflow_run.triggered_from = "app-run"
|
||||||
workflow_run.version = "1.0"
|
workflow_run.version = "1.0"
|
||||||
|
|||||||
1238
api/tests/unit_tests/services/test_dataset_service.py
Normal file
1238
api/tests/unit_tests/services/test_dataset_service.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,17 +8,17 @@ import { useRouter } from 'next/navigation'
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
export default function DatasetsLayout({ children }: { children: React.ReactNode }) {
|
export default function DatasetsLayout({ children }: { children: React.ReactNode }) {
|
||||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, currentWorkspace, isLoadingCurrentWorkspace } = useAppContext()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof isCurrentWorkspaceEditor !== 'boolean' || typeof isCurrentWorkspaceDatasetOperator !== 'boolean')
|
if (isLoadingCurrentWorkspace || !currentWorkspace.id)
|
||||||
return
|
return
|
||||||
if (!isCurrentWorkspaceEditor && !isCurrentWorkspaceDatasetOperator)
|
if (!(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator))
|
||||||
router.replace('/apps')
|
router.replace('/apps')
|
||||||
}, [isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, router])
|
}, [isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace, currentWorkspace, router])
|
||||||
|
|
||||||
if (!isCurrentWorkspaceEditor && !isCurrentWorkspaceDatasetOperator)
|
if (isLoadingCurrentWorkspace || !(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator))
|
||||||
return <Loading type='app' />
|
return <Loading type='app' />
|
||||||
return (
|
return (
|
||||||
<ExternalKnowledgeApiProvider>
|
<ExternalKnowledgeApiProvider>
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap'
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import type { PromptVariable } from '@/models/debug'
|
import type { PromptVariable } from '@/models/debug'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import type { CompletionParams } from '@/types/app'
|
|
||||||
import { AppType } from '@/types/app'
|
import { AppType } from '@/types/app'
|
||||||
import { getNewVar, getVars } from '@/utils/var'
|
import { getNewVar, getVars } from '@/utils/var'
|
||||||
import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn'
|
import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn'
|
||||||
@ -63,7 +62,6 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||||||
const { eventEmitter } = useEventEmitterContextContext()
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
const {
|
const {
|
||||||
modelConfig,
|
modelConfig,
|
||||||
completionParams,
|
|
||||||
dataSets,
|
dataSets,
|
||||||
setModelConfig,
|
setModelConfig,
|
||||||
setPrevPromptConfig,
|
setPrevPromptConfig,
|
||||||
@ -264,14 +262,6 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||||||
{showAutomatic && (
|
{showAutomatic && (
|
||||||
<GetAutomaticResModal
|
<GetAutomaticResModal
|
||||||
mode={mode as AppType}
|
mode={mode as AppType}
|
||||||
model={
|
|
||||||
{
|
|
||||||
provider: modelConfig.provider,
|
|
||||||
name: modelConfig.model_id,
|
|
||||||
mode: modelConfig.mode,
|
|
||||||
completion_params: completionParams as CompletionParams,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isShow={showAutomatic}
|
isShow={showAutomatic}
|
||||||
onClose={showAutomaticFalse}
|
onClose={showAutomaticFalse}
|
||||||
onFinished={handleAutomaticRes}
|
onFinished={handleAutomaticRes}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import {
|
import {
|
||||||
@ -22,7 +22,7 @@ import Textarea from '@/app/components/base/textarea'
|
|||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import { generateRule } from '@/service/debug'
|
import { generateRule } from '@/service/debug'
|
||||||
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
||||||
import type { Model } from '@/types/app'
|
import type { CompletionParams, Model } from '@/types/app'
|
||||||
import { AppType } from '@/types/app'
|
import { AppType } from '@/types/app'
|
||||||
import ConfigVar from '@/app/components/app/configuration/config-var'
|
import ConfigVar from '@/app/components/app/configuration/config-var'
|
||||||
import GroupName from '@/app/components/app/configuration/base/group-name'
|
import GroupName from '@/app/components/app/configuration/base/group-name'
|
||||||
@ -33,14 +33,15 @@ import { LoveMessage } from '@/app/components/base/icons/src/vender/features'
|
|||||||
// type
|
// type
|
||||||
import type { AutomaticRes } from '@/service/debug'
|
import type { AutomaticRes } from '@/service/debug'
|
||||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
|
import type { ModelModeType } from '@/types/app'
|
||||||
|
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
|
|
||||||
export type IGetAutomaticResProps = {
|
export type IGetAutomaticResProps = {
|
||||||
mode: AppType
|
mode: AppType
|
||||||
model: Model
|
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onFinished: (res: AutomaticRes) => void
|
onFinished: (res: AutomaticRes) => void
|
||||||
@ -65,16 +66,23 @@ const TryLabel: FC<{
|
|||||||
|
|
||||||
const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||||
mode,
|
mode,
|
||||||
model,
|
|
||||||
isShow,
|
isShow,
|
||||||
onClose,
|
onClose,
|
||||||
isInLLMNode,
|
isInLLMNode,
|
||||||
onFinished,
|
onFinished,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const localModel = localStorage.getItem('auto-gen-model')
|
||||||
|
? JSON.parse(localStorage.getItem('auto-gen-model') as string) as Model
|
||||||
|
: null
|
||||||
|
const [model, setModel] = React.useState<Model>(localModel || {
|
||||||
|
name: '',
|
||||||
|
provider: '',
|
||||||
|
mode: mode as unknown as ModelModeType.chat,
|
||||||
|
completion_params: {} as CompletionParams,
|
||||||
|
})
|
||||||
const {
|
const {
|
||||||
currentProvider,
|
defaultModel,
|
||||||
currentModel,
|
|
||||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
||||||
const tryList = [
|
const tryList = [
|
||||||
{
|
{
|
||||||
@ -115,7 +123,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const [instruction, setInstruction] = React.useState<string>('')
|
const [instruction, setInstruction] = useState<string>('')
|
||||||
const handleChooseTemplate = useCallback((key: string) => {
|
const handleChooseTemplate = useCallback((key: string) => {
|
||||||
return () => {
|
return () => {
|
||||||
const template = t(`appDebug.generate.template.${key}.instruction`)
|
const template = t(`appDebug.generate.template.${key}.instruction`)
|
||||||
@ -135,7 +143,25 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
|
||||||
const [res, setRes] = React.useState<AutomaticRes | null>(null)
|
const [res, setRes] = useState<AutomaticRes | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultModel) {
|
||||||
|
const localModel = localStorage.getItem('auto-gen-model')
|
||||||
|
? JSON.parse(localStorage.getItem('auto-gen-model') || '')
|
||||||
|
: null
|
||||||
|
if (localModel) {
|
||||||
|
setModel(localModel)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setModel(prev => ({
|
||||||
|
...prev,
|
||||||
|
name: defaultModel.model,
|
||||||
|
provider: defaultModel.provider.provider,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [defaultModel])
|
||||||
|
|
||||||
const renderLoading = (
|
const renderLoading = (
|
||||||
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3'>
|
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3'>
|
||||||
@ -154,6 +180,26 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleModelChange = useCallback((newValue: { modelId: string; provider: string; mode?: string; features?: string[] }) => {
|
||||||
|
const newModel = {
|
||||||
|
...model,
|
||||||
|
provider: newValue.provider,
|
||||||
|
name: newValue.modelId,
|
||||||
|
mode: newValue.mode as ModelModeType,
|
||||||
|
}
|
||||||
|
setModel(newModel)
|
||||||
|
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||||
|
}, [model, setModel])
|
||||||
|
|
||||||
|
const handleCompletionParamsChange = useCallback((newParams: FormValue) => {
|
||||||
|
const newModel = {
|
||||||
|
...model,
|
||||||
|
completion_params: newParams as CompletionParams,
|
||||||
|
}
|
||||||
|
setModel(newModel)
|
||||||
|
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||||
|
}, [model, setModel])
|
||||||
|
|
||||||
const onGenerate = async () => {
|
const onGenerate = async () => {
|
||||||
if (!isValid())
|
if (!isValid())
|
||||||
return
|
return
|
||||||
@ -198,17 +244,18 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
|||||||
<div className={`text-lg font-bold leading-[28px] ${s.textGradient}`}>{t('appDebug.generate.title')}</div>
|
<div className={`text-lg font-bold leading-[28px] ${s.textGradient}`}>{t('appDebug.generate.title')}</div>
|
||||||
<div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.generate.description')}</div>
|
<div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.generate.description')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mb-8 flex items-center'>
|
<div className='mb-8'>
|
||||||
<ModelIcon
|
<ModelParameterModal
|
||||||
className='mr-1.5 shrink-0 '
|
popupClassName='!w-[520px]'
|
||||||
provider={currentProvider}
|
portalToFollowElemContentClassName='z-[1000]'
|
||||||
modelName={currentModel?.model}
|
isAdvancedMode={true}
|
||||||
/>
|
provider={model.provider}
|
||||||
<ModelName
|
mode={model.mode}
|
||||||
className='grow'
|
completionParams={model.completion_params}
|
||||||
modelItem={currentModel!}
|
modelId={model.name}
|
||||||
showMode
|
setModel={handleModelChange}
|
||||||
showFeatures
|
onCompletionParamsChange={handleCompletionParamsChange}
|
||||||
|
hideDebugWithMultipleModel
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div >
|
<div >
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import React, { useCallback, useEffect } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import useBoolean from 'ahooks/lib/useBoolean'
|
import useBoolean from 'ahooks/lib/useBoolean'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -7,8 +7,10 @@ import ConfigPrompt from '../../config-prompt'
|
|||||||
import { languageMap } from '../../../../workflow/nodes/_base/components/editor/code-editor/index'
|
import { languageMap } from '../../../../workflow/nodes/_base/components/editor/code-editor/index'
|
||||||
import { generateRuleCode } from '@/service/debug'
|
import { generateRuleCode } from '@/service/debug'
|
||||||
import type { CodeGenRes } from '@/service/debug'
|
import type { CodeGenRes } from '@/service/debug'
|
||||||
import { type AppType, type Model, ModelModeType } from '@/types/app'
|
import type { ModelModeType } from '@/types/app'
|
||||||
|
import type { AppType, CompletionParams, Model } from '@/types/app'
|
||||||
import Modal from '@/app/components/base/modal'
|
import Modal from '@/app/components/base/modal'
|
||||||
|
import Textarea from '@/app/components/base/textarea'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
@ -17,8 +19,9 @@ import Confirm from '@/app/components/base/confirm'
|
|||||||
import type { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
import type { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
|
|
||||||
export type IGetCodeGeneratorResProps = {
|
export type IGetCodeGeneratorResProps = {
|
||||||
mode: AppType
|
mode: AppType
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
@ -36,11 +39,28 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
onFinished,
|
onFinished,
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const {
|
|
||||||
currentProvider,
|
|
||||||
currentModel,
|
|
||||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const defaultCompletionParams = {
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 0,
|
||||||
|
top_p: 0,
|
||||||
|
echo: false,
|
||||||
|
stop: [],
|
||||||
|
presence_penalty: 0,
|
||||||
|
frequency_penalty: 0,
|
||||||
|
}
|
||||||
|
const localModel = localStorage.getItem('auto-gen-model')
|
||||||
|
? JSON.parse(localStorage.getItem('auto-gen-model') as string) as Model
|
||||||
|
: null
|
||||||
|
const [model, setModel] = React.useState<Model>(localModel || {
|
||||||
|
name: '',
|
||||||
|
provider: '',
|
||||||
|
mode: mode as unknown as ModelModeType.chat,
|
||||||
|
completion_params: defaultCompletionParams,
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
defaultModel,
|
||||||
|
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
||||||
const [instruction, setInstruction] = React.useState<string>('')
|
const [instruction, setInstruction] = React.useState<string>('')
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
|
||||||
const [res, setRes] = React.useState<CodeGenRes | null>(null)
|
const [res, setRes] = React.useState<CodeGenRes | null>(null)
|
||||||
@ -56,21 +76,27 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const model: Model = {
|
|
||||||
provider: currentProvider?.provider || '',
|
const handleModelChange = useCallback((newValue: { modelId: string; provider: string; mode?: string; features?: string[] }) => {
|
||||||
name: currentModel?.model || '',
|
const newModel = {
|
||||||
mode: ModelModeType.chat,
|
...model,
|
||||||
// This is a fixed parameter
|
provider: newValue.provider,
|
||||||
completion_params: {
|
name: newValue.modelId,
|
||||||
temperature: 0.7,
|
mode: newValue.mode as ModelModeType,
|
||||||
max_tokens: 0,
|
}
|
||||||
top_p: 0,
|
setModel(newModel)
|
||||||
echo: false,
|
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||||
stop: [],
|
}, [model, setModel])
|
||||||
presence_penalty: 0,
|
|
||||||
frequency_penalty: 0,
|
const handleCompletionParamsChange = useCallback((newParams: FormValue) => {
|
||||||
},
|
const newModel = {
|
||||||
}
|
...model,
|
||||||
|
completion_params: newParams as CompletionParams,
|
||||||
|
}
|
||||||
|
setModel(newModel)
|
||||||
|
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||||
|
}, [model, setModel])
|
||||||
|
|
||||||
const isInLLMNode = true
|
const isInLLMNode = true
|
||||||
const onGenerate = async () => {
|
const onGenerate = async () => {
|
||||||
if (!isValid())
|
if (!isValid())
|
||||||
@ -99,16 +125,40 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
}
|
}
|
||||||
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
|
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultModel) {
|
||||||
|
const localModel = localStorage.getItem('auto-gen-model')
|
||||||
|
? JSON.parse(localStorage.getItem('auto-gen-model') || '')
|
||||||
|
: null
|
||||||
|
if (localModel) {
|
||||||
|
setModel({
|
||||||
|
...localModel,
|
||||||
|
completion_params: {
|
||||||
|
...defaultCompletionParams,
|
||||||
|
...localModel.completion_params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setModel(prev => ({
|
||||||
|
...prev,
|
||||||
|
name: defaultModel.model,
|
||||||
|
provider: defaultModel.provider.provider,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [defaultModel])
|
||||||
|
|
||||||
const renderLoading = (
|
const renderLoading = (
|
||||||
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3'>
|
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3'>
|
||||||
<Loading />
|
<Loading />
|
||||||
<div className='text-[13px] text-gray-400'>{t('appDebug.codegen.loading')}</div>
|
<div className='text-[13px] text-text-tertiary'>{t('appDebug.codegen.loading')}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
const renderNoData = (
|
const renderNoData = (
|
||||||
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'>
|
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'>
|
||||||
<Generator className='h-14 w-14 text-gray-300' />
|
<Generator className='h-14 w-14 text-text-tertiary' />
|
||||||
<div className='text-center text-[13px] font-normal leading-5 text-gray-400'>
|
<div className='text-center text-[13px] font-normal leading-5 text-text-tertiary'>
|
||||||
<div>{t('appDebug.codegen.noDataLine1')}</div>
|
<div>{t('appDebug.codegen.noDataLine1')}</div>
|
||||||
<div>{t('appDebug.codegen.noDataLine2')}</div>
|
<div>{t('appDebug.codegen.noDataLine2')}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -123,29 +173,30 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
closable
|
closable
|
||||||
>
|
>
|
||||||
<div className='relative flex h-[680px] flex-wrap'>
|
<div className='relative flex h-[680px] flex-wrap'>
|
||||||
<div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-gray-100 p-8'>
|
<div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-8'>
|
||||||
<div className='mb-8'>
|
<div className='mb-8'>
|
||||||
<div className={'text-lg font-bold leading-[28px]'}>{t('appDebug.codegen.title')}</div>
|
<div className={'text-lg font-bold leading-[28px] text-text-primary'}>{t('appDebug.codegen.title')}</div>
|
||||||
<div className='mt-1 text-[13px] font-normal text-gray-500'>{t('appDebug.codegen.description')}</div>
|
<div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.codegen.description')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center'>
|
<div className='mb-8'>
|
||||||
<ModelIcon
|
<ModelParameterModal
|
||||||
className='mr-1.5 shrink-0'
|
popupClassName='!w-[520px]'
|
||||||
provider={currentProvider}
|
portalToFollowElemContentClassName='z-[1000]'
|
||||||
modelName={currentModel?.model}
|
isAdvancedMode={true}
|
||||||
/>
|
provider={model.provider}
|
||||||
<ModelName
|
mode={model.mode}
|
||||||
className='grow'
|
completionParams={model.completion_params}
|
||||||
modelItem={currentModel!}
|
modelId={model.name}
|
||||||
showMode
|
setModel={handleModelChange}
|
||||||
showFeatures
|
onCompletionParamsChange={handleCompletionParamsChange}
|
||||||
|
hideDebugWithMultipleModel
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-6'>
|
<div>
|
||||||
<div className='text-[0px]'>
|
<div className='text-[0px]'>
|
||||||
<div className='mb-2 text-sm font-medium leading-5 text-gray-900'>{t('appDebug.codegen.instruction')}</div>
|
<div className='mb-2 text-sm font-medium leading-5 text-text-primary'>{t('appDebug.codegen.instruction')}</div>
|
||||||
<textarea
|
<Textarea
|
||||||
className="h-[200px] w-full overflow-y-auto rounded-lg bg-gray-50 px-3 py-2 text-sm"
|
className="h-[200px] resize-none"
|
||||||
placeholder={t('appDebug.codegen.instructionPlaceholder') || ''}
|
placeholder={t('appDebug.codegen.instructionPlaceholder') || ''}
|
||||||
value={instruction}
|
value={instruction}
|
||||||
onChange={e => setInstruction(e.target.value)}
|
onChange={e => setInstruction(e.target.value)}
|
||||||
@ -169,7 +220,7 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
{!isLoading && !res && renderNoData}
|
{!isLoading && !res && renderNoData}
|
||||||
{(!isLoading && res) && (
|
{(!isLoading && res) && (
|
||||||
<div className='h-full w-0 grow p-6 pb-0'>
|
<div className='h-full w-0 grow p-6 pb-0'>
|
||||||
<div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-gray-800'>{t('appDebug.codegen.resTitle')}</div>
|
<div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.codegen.resTitle')}</div>
|
||||||
<div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}>
|
<div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}>
|
||||||
<ConfigPrompt
|
<ConfigPrompt
|
||||||
mode={mode}
|
mode={mode}
|
||||||
@ -185,7 +236,7 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
<>
|
<>
|
||||||
{res?.code && (
|
{res?.code && (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
<h3 className='mb-2 text-sm font-medium text-gray-900'>{t('appDebug.codegen.generatedCode')}</h3>
|
<h3 className='mb-2 text-sm font-medium text-text-primary'>{t('appDebug.codegen.generatedCode')}</h3>
|
||||||
<pre className='overflow-x-auto rounded-lg bg-gray-50 p-4'>
|
<pre className='overflow-x-auto rounded-lg bg-gray-50 p-4'>
|
||||||
<code className={`language-${res.language}`}>
|
<code className={`language-${res.language}`}>
|
||||||
{res.code}
|
{res.code}
|
||||||
@ -202,7 +253,7 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex justify-end bg-white py-4'>
|
<div className='flex justify-end bg-background-default py-4'>
|
||||||
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||||
<Button variant='primary' className='ml-2' onClick={() => {
|
<Button variant='primary' className='ml-2' onClick={() => {
|
||||||
setShowConfirmOverwrite(true)
|
setShowConfirmOverwrite(true)
|
||||||
|
|||||||
@ -271,9 +271,7 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
|
|||||||
const content = String(children).replace(/\n$/, '')
|
const content = String(children).replace(/\n$/, '')
|
||||||
switch (language) {
|
switch (language) {
|
||||||
case 'mermaid':
|
case 'mermaid':
|
||||||
if (isSVG)
|
return <Flowchart PrimitiveCode={content} theme={theme as 'light' | 'dark'} />
|
||||||
return <Flowchart PrimitiveCode={content} />
|
|
||||||
break
|
|
||||||
case 'echarts': {
|
case 'echarts': {
|
||||||
// Loading state: show loading indicator
|
// Loading state: show loading indicator
|
||||||
if (chartState === 'loading') {
|
if (chartState === 'loading') {
|
||||||
@ -428,7 +426,7 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
|
|||||||
<div className='flex h-8 items-center justify-between rounded-t-[10px] border-b border-divider-subtle bg-components-input-bg-normal p-1 pl-3'>
|
<div className='flex h-8 items-center justify-between rounded-t-[10px] border-b border-divider-subtle bg-components-input-bg-normal p-1 pl-3'>
|
||||||
<div className='system-xs-semibold-uppercase text-text-secondary'>{languageShowName}</div>
|
<div className='system-xs-semibold-uppercase text-text-secondary'>{languageShowName}</div>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
{(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}
|
{language === 'svg' && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}
|
||||||
<ActionButton>
|
<ActionButton>
|
||||||
<CopyIcon content={String(children).replace(/\n$/, '')} />
|
<CopyIcon content={String(children).replace(/\n$/, '')} />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const Link = ({ node, children, ...props }: any) => {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const href = props.href || node.properties?.href
|
const href = props.href || node.properties?.href
|
||||||
if(!isValidUrl(href))
|
if(!href || !isValidUrl(href))
|
||||||
return <span>{children}</span>
|
return <span>{children}</span>
|
||||||
|
|
||||||
return <a href={href} target="_blank" className="cursor-pointer underline !decoration-primary-700 decoration-dashed">{children || 'Download'}</a>
|
return <a href={href} target="_blank" className="cursor-pointer underline !decoration-primary-700 decoration-dashed">{children || 'Download'}</a>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import mermaid from 'mermaid'
|
import mermaid, { type MermaidConfig } from 'mermaid'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
|
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
|
||||||
import { MoonIcon, SunIcon } from '@heroicons/react/24/solid'
|
import { MoonIcon, SunIcon } from '@heroicons/react/24/solid'
|
||||||
@ -68,14 +68,13 @@ const THEMES = {
|
|||||||
const initMermaid = () => {
|
const initMermaid = () => {
|
||||||
if (typeof window !== 'undefined' && !isMermaidInitialized) {
|
if (typeof window !== 'undefined' && !isMermaidInitialized) {
|
||||||
try {
|
try {
|
||||||
mermaid.initialize({
|
const config: MermaidConfig = {
|
||||||
startOnLoad: false,
|
startOnLoad: false,
|
||||||
fontFamily: 'sans-serif',
|
fontFamily: 'sans-serif',
|
||||||
securityLevel: 'loose',
|
securityLevel: 'loose',
|
||||||
flowchart: {
|
flowchart: {
|
||||||
htmlLabels: true,
|
htmlLabels: true,
|
||||||
useMaxWidth: true,
|
useMaxWidth: true,
|
||||||
diagramPadding: 10,
|
|
||||||
curve: 'basis',
|
curve: 'basis',
|
||||||
nodeSpacing: 50,
|
nodeSpacing: 50,
|
||||||
rankSpacing: 70,
|
rankSpacing: 70,
|
||||||
@ -94,10 +93,10 @@ const initMermaid = () => {
|
|||||||
mindmap: {
|
mindmap: {
|
||||||
useMaxWidth: true,
|
useMaxWidth: true,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
diagramPadding: 20,
|
|
||||||
},
|
},
|
||||||
maxTextSize: 50000,
|
maxTextSize: 50000,
|
||||||
})
|
}
|
||||||
|
mermaid.initialize(config)
|
||||||
isMermaidInitialized = true
|
isMermaidInitialized = true
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@ -113,7 +112,7 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
theme?: 'light' | 'dark'
|
theme?: 'light' | 'dark'
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [svgCode, setSvgCode] = useState<string | null>(null)
|
const [svgString, setSvgString] = useState<string | null>(null)
|
||||||
const [look, setLook] = useState<'classic' | 'handDrawn'>('classic')
|
const [look, setLook] = useState<'classic' | 'handDrawn'>('classic')
|
||||||
const [isInitialized, setIsInitialized] = useState(false)
|
const [isInitialized, setIsInitialized] = useState(false)
|
||||||
const [currentTheme, setCurrentTheme] = useState<'light' | 'dark'>(props.theme || 'light')
|
const [currentTheme, setCurrentTheme] = useState<'light' | 'dark'>(props.theme || 'light')
|
||||||
@ -125,6 +124,7 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
|
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
|
||||||
const [isCodeComplete, setIsCodeComplete] = useState(false)
|
const [isCodeComplete, setIsCodeComplete] = useState(false)
|
||||||
const codeCompletionCheckRef = useRef<NodeJS.Timeout>()
|
const codeCompletionCheckRef = useRef<NodeJS.Timeout>()
|
||||||
|
const prevCodeRef = useRef<string>()
|
||||||
|
|
||||||
// Create cache key from code, style and theme
|
// Create cache key from code, style and theme
|
||||||
const cacheKey = useMemo(() => {
|
const cacheKey = useMemo(() => {
|
||||||
@ -169,50 +169,18 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
*/
|
*/
|
||||||
const handleRenderError = (error: any) => {
|
const handleRenderError = (error: any) => {
|
||||||
console.error('Mermaid rendering error:', error)
|
console.error('Mermaid rendering error:', error)
|
||||||
const errorMsg = (error as Error).message
|
|
||||||
|
|
||||||
if (errorMsg.includes('getAttribute')) {
|
// On any render error, assume the mermaid state is corrupted and force a re-initialization.
|
||||||
diagramCache.clear()
|
try {
|
||||||
mermaid.initialize({
|
diagramCache.clear() // Clear cache to prevent using potentially corrupted SVGs
|
||||||
startOnLoad: false,
|
isMermaidInitialized = false // <-- THE FIX: Force re-initialization
|
||||||
securityLevel: 'loose',
|
initMermaid() // Re-initialize with the default safe configuration
|
||||||
})
|
|
||||||
}
|
}
|
||||||
else {
|
catch (reinitError) {
|
||||||
setErrMsg(`Rendering chart failed, please refresh and try again ${look === 'handDrawn' ? 'Or try using classic mode' : ''}`)
|
console.error('Failed to re-initialize Mermaid after error:', reinitError)
|
||||||
}
|
|
||||||
|
|
||||||
if (look === 'handDrawn') {
|
|
||||||
try {
|
|
||||||
// Clear possible cache issues
|
|
||||||
diagramCache.delete(`${props.PrimitiveCode}-handDrawn-${currentTheme}`)
|
|
||||||
|
|
||||||
// Reset mermaid configuration
|
|
||||||
mermaid.initialize({
|
|
||||||
startOnLoad: false,
|
|
||||||
securityLevel: 'loose',
|
|
||||||
theme: 'default',
|
|
||||||
maxTextSize: 50000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Try rendering with standard mode
|
|
||||||
setLook('classic')
|
|
||||||
setErrMsg('Hand-drawn mode is not supported for this diagram. Switched to classic mode.')
|
|
||||||
|
|
||||||
// Delay error clearing
|
|
||||||
setTimeout(() => {
|
|
||||||
if (containerRef.current) {
|
|
||||||
// Try rendering again with standard mode, but can't call renderFlowchart directly due to circular dependency
|
|
||||||
// Instead set state to trigger re-render
|
|
||||||
setIsCodeComplete(true) // This will trigger useEffect re-render
|
|
||||||
}
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error('Reset after handDrawn error failed:', e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setErrMsg(`Rendering failed: ${(error as Error).message || 'Unknown error. Please check the console.'}`)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,51 +191,23 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
setIsInitialized(true)
|
setIsInitialized(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Update theme when prop changes
|
// Update theme when prop changes, but allow internal override.
|
||||||
|
const prevThemeRef = useRef<string>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.theme)
|
// Only react if the theme prop from the outside has actually changed.
|
||||||
|
if (props.theme && props.theme !== prevThemeRef.current) {
|
||||||
|
// When the global theme prop changes, it should act as the source of truth,
|
||||||
|
// overriding any local theme selection.
|
||||||
|
diagramCache.clear()
|
||||||
|
setSvgString(null)
|
||||||
setCurrentTheme(props.theme)
|
setCurrentTheme(props.theme)
|
||||||
|
// Reset look to classic for a consistent state after a global change.
|
||||||
|
setLook('classic')
|
||||||
|
}
|
||||||
|
// Update the ref to the current prop value for the next render.
|
||||||
|
prevThemeRef.current = props.theme
|
||||||
}, [props.theme])
|
}, [props.theme])
|
||||||
|
|
||||||
// Validate mermaid code and check for completeness
|
|
||||||
useEffect(() => {
|
|
||||||
if (codeCompletionCheckRef.current)
|
|
||||||
clearTimeout(codeCompletionCheckRef.current)
|
|
||||||
|
|
||||||
// Reset code complete status when code changes
|
|
||||||
setIsCodeComplete(false)
|
|
||||||
|
|
||||||
// If no code or code is extremely short, don't proceed
|
|
||||||
if (!props.PrimitiveCode || props.PrimitiveCode.length < 10)
|
|
||||||
return
|
|
||||||
|
|
||||||
// Check if code already in cache - if so we know it's valid
|
|
||||||
if (diagramCache.has(cacheKey)) {
|
|
||||||
setIsCodeComplete(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial check using the extracted isMermaidCodeComplete function
|
|
||||||
const isComplete = isMermaidCodeComplete(props.PrimitiveCode)
|
|
||||||
if (isComplete) {
|
|
||||||
setIsCodeComplete(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a delay to check again in case code is still being generated
|
|
||||||
codeCompletionCheckRef.current = setTimeout(() => {
|
|
||||||
setIsCodeComplete(isMermaidCodeComplete(props.PrimitiveCode))
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (codeCompletionCheckRef.current)
|
|
||||||
clearTimeout(codeCompletionCheckRef.current)
|
|
||||||
}
|
|
||||||
}, [props.PrimitiveCode, cacheKey])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders flowchart based on provided code
|
|
||||||
*/
|
|
||||||
const renderFlowchart = useCallback(async (primitiveCode: string) => {
|
const renderFlowchart = useCallback(async (primitiveCode: string) => {
|
||||||
if (!isInitialized || !containerRef.current) {
|
if (!isInitialized || !containerRef.current) {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@ -275,15 +215,11 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't render if code is not complete yet
|
|
||||||
if (!isCodeComplete) {
|
|
||||||
setIsLoading(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return cached result if available
|
// Return cached result if available
|
||||||
|
const cacheKey = `${primitiveCode}-${look}-${currentTheme}`
|
||||||
if (diagramCache.has(cacheKey)) {
|
if (diagramCache.has(cacheKey)) {
|
||||||
setSvgCode(diagramCache.get(cacheKey) || null)
|
setErrMsg('')
|
||||||
|
setSvgString(diagramCache.get(cacheKey) || null)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -294,17 +230,45 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
try {
|
try {
|
||||||
let finalCode: string
|
let finalCode: string
|
||||||
|
|
||||||
// Check if it's a gantt chart or mindmap
|
const trimmedCode = primitiveCode.trim()
|
||||||
const isGanttChart = primitiveCode.trim().startsWith('gantt')
|
const isGantt = trimmedCode.startsWith('gantt')
|
||||||
const isMindMap = primitiveCode.trim().startsWith('mindmap')
|
const isMindMap = trimmedCode.startsWith('mindmap')
|
||||||
|
const isSequence = trimmedCode.startsWith('sequenceDiagram')
|
||||||
|
|
||||||
if (isGanttChart || isMindMap) {
|
if (isGantt || isMindMap || isSequence) {
|
||||||
// For gantt charts and mindmaps, ensure each task is on its own line
|
if (isGantt) {
|
||||||
// and preserve exact whitespace/format
|
finalCode = trimmedCode
|
||||||
finalCode = primitiveCode.trim()
|
.split('\n')
|
||||||
|
.map((line) => {
|
||||||
|
// Gantt charts have specific syntax needs.
|
||||||
|
const taskMatch = line.match(/^\s*([^:]+?)\s*:\s*(.*)/)
|
||||||
|
if (!taskMatch)
|
||||||
|
return line // Not a task line, return as is.
|
||||||
|
|
||||||
|
const taskName = taskMatch[1].trim()
|
||||||
|
let paramsStr = taskMatch[2].trim()
|
||||||
|
|
||||||
|
// Rule 1: Correct multiple "after" dependencies ONLY if they exist.
|
||||||
|
// This is a common mistake, e.g., "..., after task1, after task2, ..."
|
||||||
|
const afterCount = (paramsStr.match(/after /g) || []).length
|
||||||
|
if (afterCount > 1)
|
||||||
|
paramsStr = paramsStr.replace(/,\s*after\s+/g, ' ')
|
||||||
|
|
||||||
|
// Rule 2: Normalize spacing between parameters for consistency.
|
||||||
|
const finalParams = paramsStr.replace(/\s*,\s*/g, ', ').trim()
|
||||||
|
return `${taskName} :${finalParams}`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// For mindmap and sequence charts, which are sensitive to syntax,
|
||||||
|
// pass the code through directly.
|
||||||
|
finalCode = trimmedCode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Step 1: Clean and prepare Mermaid code using the extracted prepareMermaidCode function
|
// Step 1: Clean and prepare Mermaid code using the extracted prepareMermaidCode function
|
||||||
|
// This function handles flowcharts appropriately.
|
||||||
finalCode = prepareMermaidCode(primitiveCode, look)
|
finalCode = prepareMermaidCode(primitiveCode, look)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,13 +283,12 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
THEMES,
|
THEMES,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Step 4: Clean SVG code and convert to base64 using the extracted functions
|
// Step 4: Clean up SVG code
|
||||||
const cleanedSvg = cleanUpSvgCode(processedSvg)
|
const cleanedSvg = cleanUpSvgCode(processedSvg)
|
||||||
const base64Svg = await svgToBase64(cleanedSvg)
|
|
||||||
|
|
||||||
if (base64Svg && typeof base64Svg === 'string') {
|
if (cleanedSvg && typeof cleanedSvg === 'string') {
|
||||||
diagramCache.set(cacheKey, base64Svg)
|
diagramCache.set(cacheKey, cleanedSvg)
|
||||||
setSvgCode(base64Svg)
|
setSvgString(cleanedSvg)
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@ -334,12 +297,9 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
// Error handling
|
// Error handling
|
||||||
handleRenderError(error)
|
handleRenderError(error)
|
||||||
}
|
}
|
||||||
}, [chartId, isInitialized, cacheKey, isCodeComplete, look, currentTheme, t])
|
}, [chartId, isInitialized, look, currentTheme, t])
|
||||||
|
|
||||||
/**
|
const configureMermaid = useCallback((primitiveCode: string) => {
|
||||||
* Configure mermaid based on selected style and theme
|
|
||||||
*/
|
|
||||||
const configureMermaid = useCallback(() => {
|
|
||||||
if (typeof window !== 'undefined' && isInitialized) {
|
if (typeof window !== 'undefined' && isInitialized) {
|
||||||
const themeVars = THEMES[currentTheme]
|
const themeVars = THEMES[currentTheme]
|
||||||
const config: any = {
|
const config: any = {
|
||||||
@ -361,23 +321,37 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
mindmap: {
|
mindmap: {
|
||||||
useMaxWidth: true,
|
useMaxWidth: true,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
diagramPadding: 20,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFlowchart = primitiveCode.trim().startsWith('graph') || primitiveCode.trim().startsWith('flowchart')
|
||||||
|
|
||||||
if (look === 'classic') {
|
if (look === 'classic') {
|
||||||
config.theme = currentTheme === 'dark' ? 'dark' : 'neutral'
|
config.theme = currentTheme === 'dark' ? 'dark' : 'neutral'
|
||||||
config.flowchart = {
|
|
||||||
htmlLabels: true,
|
if (isFlowchart) {
|
||||||
useMaxWidth: true,
|
config.flowchart = {
|
||||||
diagramPadding: 12,
|
htmlLabels: true,
|
||||||
nodeSpacing: 60,
|
useMaxWidth: true,
|
||||||
rankSpacing: 80,
|
nodeSpacing: 60,
|
||||||
curve: 'linear',
|
rankSpacing: 80,
|
||||||
ranker: 'tight-tree',
|
curve: 'linear',
|
||||||
|
ranker: 'tight-tree',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTheme === 'dark') {
|
||||||
|
config.themeVariables = {
|
||||||
|
background: themeVars.background,
|
||||||
|
primaryColor: themeVars.primaryColor,
|
||||||
|
primaryBorderColor: themeVars.primaryBorderColor,
|
||||||
|
primaryTextColor: themeVars.primaryTextColor,
|
||||||
|
secondaryColor: themeVars.secondaryColor,
|
||||||
|
tertiaryColor: themeVars.tertiaryColor,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else { // look === 'handDrawn'
|
||||||
config.theme = 'default'
|
config.theme = 'default'
|
||||||
config.themeCSS = `
|
config.themeCSS = `
|
||||||
.node rect { fill-opacity: 0.85; }
|
.node rect { fill-opacity: 0.85; }
|
||||||
@ -389,27 +363,17 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
config.themeVariables = {
|
config.themeVariables = {
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
fontFamily: 'sans-serif',
|
fontFamily: 'sans-serif',
|
||||||
|
primaryBorderColor: currentTheme === 'dark' ? THEMES.dark.connectionColor : THEMES.light.connectionColor,
|
||||||
}
|
}
|
||||||
config.flowchart = {
|
|
||||||
htmlLabels: true,
|
|
||||||
useMaxWidth: true,
|
|
||||||
diagramPadding: 10,
|
|
||||||
nodeSpacing: 40,
|
|
||||||
rankSpacing: 60,
|
|
||||||
curve: 'basis',
|
|
||||||
}
|
|
||||||
config.themeVariables.primaryBorderColor = currentTheme === 'dark' ? THEMES.dark.connectionColor : THEMES.light.connectionColor
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentTheme === 'dark' && !config.themeVariables) {
|
if (isFlowchart) {
|
||||||
config.themeVariables = {
|
config.flowchart = {
|
||||||
background: themeVars.background,
|
htmlLabels: true,
|
||||||
primaryColor: themeVars.primaryColor,
|
useMaxWidth: true,
|
||||||
primaryBorderColor: themeVars.primaryBorderColor,
|
nodeSpacing: 40,
|
||||||
primaryTextColor: themeVars.primaryTextColor,
|
rankSpacing: 60,
|
||||||
secondaryColor: themeVars.secondaryColor,
|
curve: 'basis',
|
||||||
tertiaryColor: themeVars.tertiaryColor,
|
}
|
||||||
fontFamily: 'sans-serif',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,44 +389,50 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
return false
|
return false
|
||||||
}, [currentTheme, isInitialized, look])
|
}, [currentTheme, isInitialized, look])
|
||||||
|
|
||||||
// Effect for theme and style configuration
|
// This is the main rendering effect.
|
||||||
|
// It triggers whenever the code, theme, or style changes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (diagramCache.has(cacheKey)) {
|
if (!isInitialized)
|
||||||
setSvgCode(diagramCache.get(cacheKey) || null)
|
return
|
||||||
setIsLoading(false)
|
|
||||||
return
|
// Don't render if code is too short
|
||||||
}
|
if (!props.PrimitiveCode || props.PrimitiveCode.length < 10) {
|
||||||
|
|
||||||
if (configureMermaid() && containerRef.current && isCodeComplete)
|
|
||||||
renderFlowchart(props.PrimitiveCode)
|
|
||||||
}, [look, props.PrimitiveCode, renderFlowchart, isInitialized, cacheKey, currentTheme, isCodeComplete, configureMermaid])
|
|
||||||
|
|
||||||
// Effect for rendering with debounce
|
|
||||||
useEffect(() => {
|
|
||||||
if (diagramCache.has(cacheKey)) {
|
|
||||||
setSvgCode(diagramCache.get(cacheKey) || null)
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
setSvgString(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use a timeout to handle streaming code and debounce rendering
|
||||||
if (renderTimeoutRef.current)
|
if (renderTimeoutRef.current)
|
||||||
clearTimeout(renderTimeoutRef.current)
|
clearTimeout(renderTimeoutRef.current)
|
||||||
|
|
||||||
if (isCodeComplete) {
|
setIsLoading(true)
|
||||||
renderTimeoutRef.current = setTimeout(() => {
|
|
||||||
if (isInitialized)
|
renderTimeoutRef.current = setTimeout(() => {
|
||||||
renderFlowchart(props.PrimitiveCode)
|
// Final validation before rendering
|
||||||
}, 300)
|
if (!isMermaidCodeComplete(props.PrimitiveCode)) {
|
||||||
}
|
setIsLoading(false)
|
||||||
else {
|
setErrMsg('Diagram code is not complete or invalid.')
|
||||||
setIsLoading(true)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cacheKey = `${props.PrimitiveCode}-${look}-${currentTheme}`
|
||||||
|
if (diagramCache.has(cacheKey)) {
|
||||||
|
setErrMsg('')
|
||||||
|
setSvgString(diagramCache.get(cacheKey) || null)
|
||||||
|
setIsLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configureMermaid(props.PrimitiveCode))
|
||||||
|
renderFlowchart(props.PrimitiveCode)
|
||||||
|
}, 300) // 300ms debounce
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (renderTimeoutRef.current)
|
if (renderTimeoutRef.current)
|
||||||
clearTimeout(renderTimeoutRef.current)
|
clearTimeout(renderTimeoutRef.current)
|
||||||
}
|
}
|
||||||
}, [props.PrimitiveCode, renderFlowchart, isInitialized, cacheKey, isCodeComplete])
|
}, [props.PrimitiveCode, look, currentTheme, isInitialized, configureMermaid, renderFlowchart])
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -471,14 +441,22 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
containerRef.current.innerHTML = ''
|
containerRef.current.innerHTML = ''
|
||||||
if (renderTimeoutRef.current)
|
if (renderTimeoutRef.current)
|
||||||
clearTimeout(renderTimeoutRef.current)
|
clearTimeout(renderTimeoutRef.current)
|
||||||
if (codeCompletionCheckRef.current)
|
|
||||||
clearTimeout(codeCompletionCheckRef.current)
|
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handlePreviewClick = async () => {
|
||||||
|
if (svgString) {
|
||||||
|
const base64 = await svgToBase64(svgString)
|
||||||
|
setImagePreviewUrl(base64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
setCurrentTheme(prevTheme => prevTheme === 'light' ? Theme.dark : Theme.light)
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light'
|
||||||
|
// Ensure a full, clean re-render cycle, consistent with global theme change.
|
||||||
diagramCache.clear()
|
diagramCache.clear()
|
||||||
|
setSvgString(null)
|
||||||
|
setCurrentTheme(newTheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style classes for theme-dependent elements
|
// Style classes for theme-dependent elements
|
||||||
@ -527,14 +505,26 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
<div
|
<div
|
||||||
key='classic'
|
key='classic'
|
||||||
className={getLookButtonClass('classic')}
|
className={getLookButtonClass('classic')}
|
||||||
onClick={() => setLook('classic')}
|
onClick={() => {
|
||||||
|
if (look !== 'classic') {
|
||||||
|
diagramCache.clear()
|
||||||
|
setSvgString(null)
|
||||||
|
setLook('classic')
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="msh-segmented-item-label">{t('app.mermaid.classic')}</div>
|
<div className="msh-segmented-item-label">{t('app.mermaid.classic')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
key='handDrawn'
|
key='handDrawn'
|
||||||
className={getLookButtonClass('handDrawn')}
|
className={getLookButtonClass('handDrawn')}
|
||||||
onClick={() => setLook('handDrawn')}
|
onClick={() => {
|
||||||
|
if (look !== 'handDrawn') {
|
||||||
|
diagramCache.clear()
|
||||||
|
setSvgString(null)
|
||||||
|
setLook('handDrawn')
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="msh-segmented-item-label">{t('app.mermaid.handDrawn')}</div>
|
<div className="msh-segmented-item-label">{t('app.mermaid.handDrawn')}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -544,7 +534,7 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
|
|
||||||
<div ref={containerRef} style={{ position: 'absolute', visibility: 'hidden', height: 0, overflow: 'hidden' }} />
|
<div ref={containerRef} style={{ position: 'absolute', visibility: 'hidden', height: 0, overflow: 'hidden' }} />
|
||||||
|
|
||||||
{isLoading && !svgCode && (
|
{isLoading && !svgString && (
|
||||||
<div className='px-[26px] py-4'>
|
<div className='px-[26px] py-4'>
|
||||||
<LoadingAnim type='text'/>
|
<LoadingAnim type='text'/>
|
||||||
{!isCodeComplete && (
|
{!isCodeComplete && (
|
||||||
@ -555,8 +545,8 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{svgCode && (
|
{svgString && (
|
||||||
<div className={themeClasses.mermaidDiv} style={{ objectFit: 'cover' }} onClick={() => setImagePreviewUrl(svgCode)}>
|
<div className={themeClasses.mermaidDiv} style={{ objectFit: 'cover' }} onClick={handlePreviewClick}>
|
||||||
<div className="absolute bottom-2 left-2 z-[100]">
|
<div className="absolute bottom-2 left-2 z-[100]">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -571,11 +561,9 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img
|
<div
|
||||||
src={svgCode}
|
|
||||||
alt="mermaid_chart"
|
|
||||||
style={{ maxWidth: '100%' }}
|
style={{ maxWidth: '100%' }}
|
||||||
onError={() => { setErrMsg('Chart rendering failed, please refresh and retry') }}
|
dangerouslySetInnerHTML={{ __html: svgString }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -3,52 +3,31 @@ export function cleanUpSvgCode(svgCode: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preprocesses mermaid code to fix common syntax issues
|
* Prepares mermaid code for rendering by sanitizing common syntax issues.
|
||||||
|
* @param {string} mermaidCode - The mermaid code to prepare
|
||||||
|
* @param {'classic' | 'handDrawn'} style - The rendering style
|
||||||
|
* @returns {string} - The prepared mermaid code
|
||||||
*/
|
*/
|
||||||
export function preprocessMermaidCode(code: string): string {
|
export const prepareMermaidCode = (mermaidCode: string, style: 'classic' | 'handDrawn'): string => {
|
||||||
if (!code || typeof code !== 'string')
|
if (!mermaidCode || typeof mermaidCode !== 'string')
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
// First check if this is a gantt chart
|
let code = mermaidCode.trim()
|
||||||
if (code.trim().startsWith('gantt')) {
|
|
||||||
// For gantt charts, we need to ensure each task is on its own line
|
|
||||||
// Split the code into lines and process each line separately
|
|
||||||
const lines = code.split('\n').map(line => line.trim())
|
|
||||||
return lines.join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
return code
|
// Security: Sanitize against javascript: protocol in click events (XSS vector)
|
||||||
// Replace English colons with Chinese colons in section nodes to avoid parsing issues
|
code = code.replace(/(\bclick\s+\w+\s+")javascript:[^"]*(")/g, '$1#$2')
|
||||||
.replace(/section\s+([^:]+):/g, (match, sectionName) => `section ${sectionName}:`)
|
|
||||||
// Fix common syntax issues
|
|
||||||
.replace(/fifopacket/g, 'rect')
|
|
||||||
// Ensure graph has direction
|
|
||||||
.replace(/^graph\s+((?:TB|BT|RL|LR)*)/, (match, direction) => {
|
|
||||||
return direction ? match : 'graph TD'
|
|
||||||
})
|
|
||||||
// Clean up empty lines and extra spaces
|
|
||||||
.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Convenience: Basic BR replacement. This is a common and safe operation.
|
||||||
* Prepares mermaid code based on selected style
|
code = code.replace(/<br\s*\/?>/g, '\n')
|
||||||
*/
|
|
||||||
export function prepareMermaidCode(code: string, style: 'classic' | 'handDrawn'): string {
|
|
||||||
let finalCode = preprocessMermaidCode(code)
|
|
||||||
|
|
||||||
// Special handling for gantt charts and mindmaps
|
let finalCode = code
|
||||||
if (finalCode.trim().startsWith('gantt') || finalCode.trim().startsWith('mindmap')) {
|
|
||||||
// For gantt charts and mindmaps, preserve the structure exactly as is
|
|
||||||
return finalCode
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Hand-drawn style requires some specific clean-up.
|
||||||
if (style === 'handDrawn') {
|
if (style === 'handDrawn') {
|
||||||
finalCode = finalCode
|
finalCode = finalCode
|
||||||
// Remove style definitions that interfere with hand-drawn style
|
|
||||||
.replace(/style\s+[^\n]+/g, '')
|
.replace(/style\s+[^\n]+/g, '')
|
||||||
.replace(/linkStyle\s+[^\n]+/g, '')
|
.replace(/linkStyle\s+[^\n]+/g, '')
|
||||||
.replace(/^flowchart/, 'graph')
|
.replace(/^flowchart/, 'graph')
|
||||||
// Remove any styles that might interfere with hand-drawn style
|
|
||||||
.replace(/class="[^"]*"/g, '')
|
.replace(/class="[^"]*"/g, '')
|
||||||
.replace(/fill="[^"]*"/g, '')
|
.replace(/fill="[^"]*"/g, '')
|
||||||
.replace(/stroke="[^"]*"/g, '')
|
.replace(/stroke="[^"]*"/g, '')
|
||||||
@ -82,7 +61,6 @@ export function svgToBase64(svgGraph: string): Promise<string> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('Error converting SVG to base64:', error)
|
|
||||||
return Promise.resolve('')
|
return Promise.resolve('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,13 +93,11 @@ export function processSvgForTheme(
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let i = 0
|
let i = 0
|
||||||
themes.dark.nodeColors.forEach(() => {
|
const nodeColorRegex = /fill="#[a-fA-F0-9]{6}"[^>]*class="node-[^"]*"/g
|
||||||
const regex = /fill="#[a-fA-F0-9]{6}"[^>]*class="node-[^"]*"/g
|
processedSvg = processedSvg.replace(nodeColorRegex, (match: string) => {
|
||||||
processedSvg = processedSvg.replace(regex, (match: string) => {
|
const colorIndex = i % themes.dark.nodeColors.length
|
||||||
const colorIndex = i % themes.dark.nodeColors.length
|
i++
|
||||||
i++
|
return match.replace(/fill="#[a-fA-F0-9]{6}"/, `fill="${themes.dark.nodeColors[colorIndex].bg}"`)
|
||||||
return match.replace(/fill="#[a-fA-F0-9]{6}"/, `fill="${themes.dark.nodeColors[colorIndex].bg}"`)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
processedSvg = processedSvg
|
processedSvg = processedSvg
|
||||||
@ -139,14 +115,12 @@ export function processSvgForTheme(
|
|||||||
.replace(/stroke-width="1"/g, 'stroke-width="1.5"')
|
.replace(/stroke-width="1"/g, 'stroke-width="1.5"')
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
themes.light.nodeColors.forEach(() => {
|
let i = 0
|
||||||
const regex = /fill="#[a-fA-F0-9]{6}"[^>]*class="node-[^"]*"/g
|
const nodeColorRegex = /fill="#[a-fA-F0-9]{6}"[^>]*class="node-[^"]*"/g
|
||||||
let i = 0
|
processedSvg = processedSvg.replace(nodeColorRegex, (match: string) => {
|
||||||
processedSvg = processedSvg.replace(regex, (match: string) => {
|
const colorIndex = i % themes.light.nodeColors.length
|
||||||
const colorIndex = i % themes.light.nodeColors.length
|
i++
|
||||||
i++
|
return match.replace(/fill="#[a-fA-F0-9]{6}"/, `fill="${themes.light.nodeColors[colorIndex].bg}"`)
|
||||||
return match.replace(/fill="#[a-fA-F0-9]{6}"/, `fill="${themes.light.nodeColors[colorIndex].bg}"`)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
processedSvg = processedSvg
|
processedSvg = processedSvg
|
||||||
@ -187,24 +161,10 @@ export function isMermaidCodeComplete(code: string): boolean {
|
|||||||
// Check for basic syntax structure
|
// Check for basic syntax structure
|
||||||
const hasValidStart = /^(graph|flowchart|sequenceDiagram|classDiagram|classDef|class|stateDiagram|gantt|pie|er|journey|requirementDiagram|mindmap)/.test(trimmedCode)
|
const hasValidStart = /^(graph|flowchart|sequenceDiagram|classDiagram|classDef|class|stateDiagram|gantt|pie|er|journey|requirementDiagram|mindmap)/.test(trimmedCode)
|
||||||
|
|
||||||
// Check for balanced brackets and parentheses
|
// The balanced bracket check was too strict and produced false negatives for valid
|
||||||
const isBalanced = (() => {
|
// mermaid syntax like the asymmetric shape `A>B]`. Relying on Mermaid's own
|
||||||
const stack = []
|
// parser is more robust.
|
||||||
const pairs = { '{': '}', '[': ']', '(': ')' }
|
const isBalanced = true
|
||||||
|
|
||||||
for (const char of trimmedCode) {
|
|
||||||
if (char in pairs) {
|
|
||||||
stack.push(char)
|
|
||||||
}
|
|
||||||
else if (Object.values(pairs).includes(char)) {
|
|
||||||
const last = stack.pop()
|
|
||||||
if (pairs[last as keyof typeof pairs] !== char)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stack.length === 0
|
|
||||||
})()
|
|
||||||
|
|
||||||
// Check for common syntax errors
|
// Check for common syntax errors
|
||||||
const hasNoSyntaxErrors = !trimmedCode.includes('undefined')
|
const hasNoSyntaxErrors = !trimmedCode.includes('undefined')
|
||||||
@ -215,7 +175,7 @@ export function isMermaidCodeComplete(code: string): boolean {
|
|||||||
return hasValidStart && isBalanced && hasNoSyntaxErrors
|
return hasValidStart && isBalanced && hasNoSyntaxErrors
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.debug('Mermaid code validation error:', error)
|
console.error('Mermaid code validation error:', error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,7 +162,9 @@ const StepTwo = ({
|
|||||||
|
|
||||||
const isInCreatePage = !datasetId || (datasetId && !currentDataset?.data_source_type)
|
const isInCreatePage = !datasetId || (datasetId && !currentDataset?.data_source_type)
|
||||||
const dataSourceType = isInCreatePage ? inCreatePageDataSourceType : currentDataset?.data_source_type
|
const dataSourceType = isInCreatePage ? inCreatePageDataSourceType : currentDataset?.data_source_type
|
||||||
const [segmentationType, setSegmentationType] = useState<ProcessMode>(ProcessMode.general)
|
const [segmentationType, setSegmentationType] = useState<ProcessMode>(
|
||||||
|
currentDataset?.doc_form === ChunkingMode.parentChild ? ProcessMode.parentChild : ProcessMode.general,
|
||||||
|
)
|
||||||
const [segmentIdentifier, doSetSegmentIdentifier] = useState(DEFAULT_SEGMENT_IDENTIFIER)
|
const [segmentIdentifier, doSetSegmentIdentifier] = useState(DEFAULT_SEGMENT_IDENTIFIER)
|
||||||
const setSegmentIdentifier = useCallback((value: string, canEmpty?: boolean) => {
|
const setSegmentIdentifier = useCallback((value: string, canEmpty?: boolean) => {
|
||||||
doSetSegmentIdentifier(value ? escape(value) : (canEmpty ? '' : DEFAULT_SEGMENT_IDENTIFIER))
|
doSetSegmentIdentifier(value ? escape(value) : (canEmpty ? '' : DEFAULT_SEGMENT_IDENTIFIER))
|
||||||
@ -208,7 +210,14 @@ const StepTwo = ({
|
|||||||
}
|
}
|
||||||
if (value === ChunkingMode.parentChild && indexType === IndexingType.ECONOMICAL)
|
if (value === ChunkingMode.parentChild && indexType === IndexingType.ECONOMICAL)
|
||||||
setIndexType(IndexingType.QUALIFIED)
|
setIndexType(IndexingType.QUALIFIED)
|
||||||
|
|
||||||
setDocForm(value)
|
setDocForm(value)
|
||||||
|
|
||||||
|
if (value === ChunkingMode.parentChild)
|
||||||
|
setSegmentationType(ProcessMode.parentChild)
|
||||||
|
else
|
||||||
|
setSegmentationType(ProcessMode.general)
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
currentEstimateMutation.reset()
|
currentEstimateMutation.reset()
|
||||||
}
|
}
|
||||||
@ -504,6 +513,20 @@ const StepTwo = ({
|
|||||||
setOverlap(overlap!)
|
setOverlap(overlap!)
|
||||||
setRules(rules.pre_processing_rules)
|
setRules(rules.pre_processing_rules)
|
||||||
setDefaultConfig(rules)
|
setDefaultConfig(rules)
|
||||||
|
|
||||||
|
if (documentDetail.dataset_process_rule.mode === 'hierarchical') {
|
||||||
|
setParentChildConfig({
|
||||||
|
chunkForContext: rules.parent_mode || 'paragraph',
|
||||||
|
parent: {
|
||||||
|
delimiter: escape(rules.segmentation.separator),
|
||||||
|
maxLength: rules.segmentation.max_tokens,
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
delimiter: escape(rules.subchunk_segmentation.separator),
|
||||||
|
maxLength: rules.subchunk_segmentation.max_tokens,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -966,8 +989,8 @@ const StepTwo = ({
|
|||||||
<div className='system-md-semibold mb-0.5 text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
<div className='system-md-semibold mb-0.5 text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||||
<div className='body-xs-regular text-text-tertiary'>
|
<div className='body-xs-regular text-text-tertiary'>
|
||||||
<a target='_blank' rel='noopener noreferrer'
|
<a target='_blank' rel='noopener noreferrer'
|
||||||
href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents')}
|
href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents')}
|
||||||
className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
|
className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
|
||||||
{t('datasetSettings.form.retrievalSetting.longDescription')}
|
{t('datasetSettings.form.retrievalSetting.longDescription')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1131,7 +1154,7 @@ const StepTwo = ({
|
|||||||
const indexForLabel = index + 1
|
const indexForLabel = index + 1
|
||||||
return (
|
return (
|
||||||
<PreviewSlice
|
<PreviewSlice
|
||||||
key={child}
|
key={`C-${indexForLabel}-${child}`}
|
||||||
label={`C-${indexForLabel}`}
|
label={`C-${indexForLabel}`}
|
||||||
text={child}
|
text={child}
|
||||||
tooltip={`Child-chunk-${indexForLabel} · ${child.length} Characters`}
|
tooltip={`Child-chunk-${indexForLabel} · ${child.length} Characters`}
|
||||||
|
|||||||
@ -1124,6 +1124,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr className='ml-0 mr-0' />
|
||||||
|
|
||||||
|
<Heading
|
||||||
|
url='/datasets/{dataset_id}/documents/status/{action}'
|
||||||
|
method='PATCH'
|
||||||
|
title='Update Document Status'
|
||||||
|
name='#batch_document_status'
|
||||||
|
/>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
### Path
|
||||||
|
<Properties>
|
||||||
|
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||||
|
Knowledge ID
|
||||||
|
</Property>
|
||||||
|
<Property name='action' type='string' key='action'>
|
||||||
|
- `enable` - Enable document
|
||||||
|
- `disable` - Disable document
|
||||||
|
- `archive` - Archive document
|
||||||
|
- `un_archive` - Unarchive document
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
### Request Body
|
||||||
|
<Properties>
|
||||||
|
<Property name='document_ids' type='array[string]' key='document_ids'>
|
||||||
|
List of document IDs
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Col>
|
||||||
|
<Col sticky>
|
||||||
|
<CodeGroup
|
||||||
|
title="Request"
|
||||||
|
tag="PATCH"
|
||||||
|
label="/datasets/{dataset_id}/documents/status/{action}"
|
||||||
|
targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/status/{action}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "document_ids": ["doc-id-1", "doc-id-2"]\n}'`}
|
||||||
|
>
|
||||||
|
```bash {{ title: 'cURL' }}
|
||||||
|
curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/status/{action}' \
|
||||||
|
--header 'Authorization: Bearer {api_key}' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"document_ids": ["doc-id-1", "doc-id-2"]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
<CodeGroup title="Response">
|
||||||
|
```json {{ title: 'Response' }}
|
||||||
|
{
|
||||||
|
"result": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<hr className='ml-0 mr-0' />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
method='POST'
|
method='POST'
|
||||||
|
|||||||
@ -881,6 +881,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr className='ml-0 mr-0' />
|
||||||
|
|
||||||
|
<Heading
|
||||||
|
url='/datasets/{dataset_id}/documents/status/{action}'
|
||||||
|
method='PATCH'
|
||||||
|
title='ドキュメントステータスの更新'
|
||||||
|
name='#batch_document_status'
|
||||||
|
/>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
### パス
|
||||||
|
<Properties>
|
||||||
|
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||||
|
ナレッジ ID
|
||||||
|
</Property>
|
||||||
|
<Property name='action' type='string' key='action'>
|
||||||
|
- `enable` - ドキュメントを有効化
|
||||||
|
- `disable` - ドキュメントを無効化
|
||||||
|
- `archive` - ドキュメントをアーカイブ
|
||||||
|
- `un_archive` - ドキュメントのアーカイブを解除
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
### リクエストボディ
|
||||||
|
<Properties>
|
||||||
|
<Property name='document_ids' type='array[string]' key='document_ids'>
|
||||||
|
ドキュメントIDのリスト
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Col>
|
||||||
|
<Col sticky>
|
||||||
|
<CodeGroup
|
||||||
|
title="リクエスト"
|
||||||
|
tag="PATCH"
|
||||||
|
label="/datasets/{dataset_id}/documents/status/{action}"
|
||||||
|
targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/status/{action}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "document_ids": ["doc-id-1", "doc-id-2"]\n}'`}
|
||||||
|
>
|
||||||
|
```bash {{ title: 'cURL' }}
|
||||||
|
curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/status/{action}' \
|
||||||
|
--header 'Authorization: Bearer {api_key}' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"document_ids": ["doc-id-1", "doc-id-2"]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
<CodeGroup title="レスポンス">
|
||||||
|
```json {{ title: 'Response' }}
|
||||||
|
{
|
||||||
|
"result": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<hr className='ml-0 mr-0' />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
method='POST'
|
method='POST'
|
||||||
@ -2413,3 +2470,4 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="pb-4" />
|
<div className="pb-4" />
|
||||||
|
|
||||||
|
|||||||
@ -1131,6 +1131,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr className='ml-0 mr-0' />
|
||||||
|
|
||||||
|
<Heading
|
||||||
|
url='/datasets/{dataset_id}/documents/status/{action}'
|
||||||
|
method='PATCH'
|
||||||
|
title='更新文档状态'
|
||||||
|
name='#batch_document_status'
|
||||||
|
/>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
### Path
|
||||||
|
<Properties>
|
||||||
|
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||||
|
知识库 ID
|
||||||
|
</Property>
|
||||||
|
<Property name='action' type='string' key='action'>
|
||||||
|
- `enable` - 启用文档
|
||||||
|
- `disable` - 禁用文档
|
||||||
|
- `archive` - 归档文档
|
||||||
|
- `un_archive` - 取消归档文档
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
### Request Body
|
||||||
|
<Properties>
|
||||||
|
<Property name='document_ids' type='array[string]' key='document_ids'>
|
||||||
|
文档ID列表
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Col>
|
||||||
|
<Col sticky>
|
||||||
|
<CodeGroup
|
||||||
|
title="Request"
|
||||||
|
tag="PATCH"
|
||||||
|
label="/datasets/{dataset_id}/documents/status/{action}"
|
||||||
|
targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/status/{action}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "document_ids": ["doc-id-1", "doc-id-2"]\n}'`}
|
||||||
|
>
|
||||||
|
```bash {{ title: 'cURL' }}
|
||||||
|
curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/status/{action}' \
|
||||||
|
--header 'Authorization: Bearer {api_key}' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"document_ids": ["doc-id-1", "doc-id-2"]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
<CodeGroup title="Response">
|
||||||
|
```json {{ title: 'Response' }}
|
||||||
|
{
|
||||||
|
"result": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<hr className='ml-0 mr-0' />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
method='POST'
|
method='POST'
|
||||||
|
|||||||
@ -152,7 +152,6 @@ Chat applications support session persistence, allowing previous chat history to
|
|||||||
- `data` (object) detail
|
- `data` (object) detail
|
||||||
- `id` (string) Unique ID of workflow execution
|
- `id` (string) Unique ID of workflow execution
|
||||||
- `workflow_id` (string) ID of related workflow
|
- `workflow_id` (string) ID of related workflow
|
||||||
- `sequence_number` (int) Self-increasing serial number, self-increasing in the App, starting from 1
|
|
||||||
- `created_at` (timestamp) Creation timestamp, e.g., 1705395332
|
- `created_at` (timestamp) Creation timestamp, e.g., 1705395332
|
||||||
- `event: node_started` node execution started
|
- `event: node_started` node execution started
|
||||||
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
|
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
|
||||||
@ -287,7 +286,7 @@ Chat applications support session persistence, allowing previous chat history to
|
|||||||
### Streaming Mode
|
### Streaming Mode
|
||||||
<CodeGroup title="Response">
|
<CodeGroup title="Response">
|
||||||
```streaming {{ title: 'Response' }}
|
```streaming {{ title: 'Response' }}
|
||||||
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
|
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
|
||||||
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
||||||
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
||||||
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
||||||
|
|||||||
@ -152,7 +152,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
|||||||
- `data` (object) 詳細
|
- `data` (object) 詳細
|
||||||
- `id` (string) ワークフロー実行の一意ID
|
- `id` (string) ワークフロー実行の一意ID
|
||||||
- `workflow_id` (string) 関連ワークフローのID
|
- `workflow_id` (string) 関連ワークフローのID
|
||||||
- `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1から始まります
|
|
||||||
- `created_at` (timestamp) 作成タイムスタンプ、例:1705395332
|
- `created_at` (timestamp) 作成タイムスタンプ、例:1705395332
|
||||||
- `event: node_started` ノード実行が開始
|
- `event: node_started` ノード実行が開始
|
||||||
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
|
- `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用
|
||||||
@ -287,7 +286,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
|||||||
### ストリーミングモード
|
### ストリーミングモード
|
||||||
<CodeGroup title="応答">
|
<CodeGroup title="応答">
|
||||||
```streaming {{ title: '応答' }}
|
```streaming {{ title: '応答' }}
|
||||||
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
|
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
|
||||||
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
||||||
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
||||||
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
||||||
|
|||||||
@ -153,7 +153,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
|||||||
- `data` (object) 详细内容
|
- `data` (object) 详细内容
|
||||||
- `id` (string) workflow 执行 ID
|
- `id` (string) workflow 执行 ID
|
||||||
- `workflow_id` (string) 关联 Workflow ID
|
- `workflow_id` (string) 关联 Workflow ID
|
||||||
- `sequence_number` (int) 自增序号,App 内自增,从 1 开始
|
|
||||||
- `created_at` (timestamp) 开始时间
|
- `created_at` (timestamp) 开始时间
|
||||||
- `event: node_started` node 开始执行
|
- `event: node_started` node 开始执行
|
||||||
- `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
- `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||||
@ -297,7 +296,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
|||||||
### 流式模式
|
### 流式模式
|
||||||
<CodeGroup title="Response">
|
<CodeGroup title="Response">
|
||||||
```streaming {{ title: 'Response' }}
|
```streaming {{ title: 'Response' }}
|
||||||
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
|
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
|
||||||
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
||||||
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
||||||
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
||||||
|
|||||||
@ -82,9 +82,9 @@ Chat applications support session persistence, allowing previous chat history to
|
|||||||
|
|
||||||
### ChatCompletionResponse
|
### ChatCompletionResponse
|
||||||
Returns the complete App result, `Content-Type` is `application/json`.
|
Returns the complete App result, `Content-Type` is `application/json`.
|
||||||
- `event` (string) 事件类型,固定为 `message`
|
- `event` (string) Event type, always `message` in blocking mode.
|
||||||
- `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
|
||||||
- `id` (string) 唯一ID
|
- `id` (string) Unique ID, same as `message_id`
|
||||||
- `message_id` (string) Unique message ID
|
- `message_id` (string) Unique message ID
|
||||||
- `conversation_id` (string) Conversation ID
|
- `conversation_id` (string) Conversation ID
|
||||||
- `mode` (string) App mode, fixed as `chat`
|
- `mode` (string) App mode, fixed as `chat`
|
||||||
|
|||||||
@ -103,7 +103,6 @@ Workflow applications offers non-session support and is ideal for translation, a
|
|||||||
- `data` (object) detail
|
- `data` (object) detail
|
||||||
- `id` (string) Unique ID of workflow execution
|
- `id` (string) Unique ID of workflow execution
|
||||||
- `workflow_id` (string) ID of related workflow
|
- `workflow_id` (string) ID of related workflow
|
||||||
- `sequence_number` (int) Self-increasing serial number, self-increasing in the App, starting from 1
|
|
||||||
- `created_at` (timestamp) Creation timestamp, e.g., 1705395332
|
- `created_at` (timestamp) Creation timestamp, e.g., 1705395332
|
||||||
- `event: node_started` node execution started
|
- `event: node_started` node execution started
|
||||||
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
|
- `task_id` (string) Task ID, used for request tracking and the below Stop Generate API
|
||||||
@ -241,7 +240,7 @@ Workflow applications offers non-session support and is ideal for translation, a
|
|||||||
### Streaming Mode
|
### Streaming Mode
|
||||||
<CodeGroup title="Response">
|
<CodeGroup title="Response">
|
||||||
```streaming {{ title: 'Response' }}
|
```streaming {{ title: 'Response' }}
|
||||||
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
|
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
|
||||||
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
||||||
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
||||||
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
||||||
|
|||||||
@ -104,7 +104,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
|||||||
- `data` (object) 詳細
|
- `data` (object) 詳細
|
||||||
- `id` (string) ワークフロー実行の一意の ID
|
- `id` (string) ワークフロー実行の一意の ID
|
||||||
- `workflow_id` (string) 関連するワークフローの ID
|
- `workflow_id` (string) 関連するワークフローの ID
|
||||||
- `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1 から始まります
|
|
||||||
- `created_at` (timestamp) 作成タイムスタンプ、例:1705395332
|
- `created_at` (timestamp) 作成タイムスタンプ、例:1705395332
|
||||||
- `event: node_started` ノード実行開始
|
- `event: node_started` ノード実行開始
|
||||||
- `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用
|
- `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用
|
||||||
@ -242,7 +241,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
|||||||
### ストリーミングモード
|
### ストリーミングモード
|
||||||
<CodeGroup title="応答">
|
<CodeGroup title="応答">
|
||||||
```streaming {{ title: '応答' }}
|
```streaming {{ title: '応答' }}
|
||||||
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
|
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
|
||||||
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
||||||
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
||||||
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
||||||
|
|||||||
@ -98,7 +98,6 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
|
|||||||
- `data` (object) 详细内容
|
- `data` (object) 详细内容
|
||||||
- `id` (string) workflow 执行 ID
|
- `id` (string) workflow 执行 ID
|
||||||
- `workflow_id` (string) 关联 Workflow ID
|
- `workflow_id` (string) 关联 Workflow ID
|
||||||
- `sequence_number` (int) 自增序号,App 内自增,从 1 开始
|
|
||||||
- `created_at` (timestamp) 开始时间
|
- `created_at` (timestamp) 开始时间
|
||||||
- `event: node_started` node 开始执行
|
- `event: node_started` node 开始执行
|
||||||
- `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
- `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||||
@ -232,7 +231,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
|
|||||||
### Streaming Mode
|
### Streaming Mode
|
||||||
<CodeGroup title="Response">
|
<CodeGroup title="Response">
|
||||||
```streaming {{ title: 'Response' }}
|
```streaming {{ title: 'Response' }}
|
||||||
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
|
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
|
||||||
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
||||||
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
||||||
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
PortalToFollowElemTrigger,
|
PortalToFollowElemTrigger,
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
source: PluginSource
|
source: PluginSource
|
||||||
@ -40,6 +41,8 @@ const OperationDropdown: FC<Props> = ({
|
|||||||
setOpen(!openRef.current)
|
setOpen(!openRef.current)
|
||||||
}, [setOpen])
|
}, [setOpen])
|
||||||
|
|
||||||
|
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PortalToFollowElem
|
<PortalToFollowElem
|
||||||
open={open}
|
open={open}
|
||||||
@ -77,13 +80,13 @@ const OperationDropdown: FC<Props> = ({
|
|||||||
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
|
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
|
||||||
>{t('plugin.detailPanel.operation.checkUpdate')}</div>
|
>{t('plugin.detailPanel.operation.checkUpdate')}</div>
|
||||||
)}
|
)}
|
||||||
{(source === PluginSource.marketplace || source === PluginSource.github) && (
|
{(source === PluginSource.marketplace || source === PluginSource.github) && enable_marketplace && (
|
||||||
<a href={detailUrl} target='_blank' className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
|
<a href={detailUrl} target='_blank' className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
|
||||||
<span className='grow'>{t('plugin.detailPanel.operation.viewDetail')}</span>
|
<span className='grow'>{t('plugin.detailPanel.operation.viewDetail')}</span>
|
||||||
<RiArrowRightUpLine className='h-3.5 w-3.5 shrink-0 text-text-tertiary' />
|
<RiArrowRightUpLine className='h-3.5 w-3.5 shrink-0 text-text-tertiary' />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{(source === PluginSource.marketplace || source === PluginSource.github) && (
|
{(source === PluginSource.marketplace || source === PluginSource.github) && enable_marketplace && (
|
||||||
<div className='my-1 h-px bg-divider-subtle'></div>
|
<div className='my-1 h-px bg-divider-subtle'></div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { useAppContext } from '@/context/app-context'
|
|||||||
import { gte } from 'semver'
|
import { gte } from 'semver'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import { getMarketplaceUrl } from '@/utils/var'
|
import { getMarketplaceUrl } from '@/utils/var'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -75,6 +76,7 @@ const PluginItem: FC<Props> = ({
|
|||||||
const getValueFromI18nObject = useRenderI18nObject()
|
const getValueFromI18nObject = useRenderI18nObject()
|
||||||
const title = getValueFromI18nObject(label)
|
const title = getValueFromI18nObject(label)
|
||||||
const descriptionText = getValueFromI18nObject(description)
|
const descriptionText = getValueFromI18nObject(description)
|
||||||
|
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -165,7 +167,7 @@ const PluginItem: FC<Props> = ({
|
|||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
{source === PluginSource.marketplace
|
{source === PluginSource.marketplace && enable_marketplace
|
||||||
&& <>
|
&& <>
|
||||||
<a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target='_blank' className='flex items-center gap-0.5'>
|
<a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target='_blank' className='flex items-center gap-0.5'>
|
||||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{t('plugin.from')} <span className='text-text-secondary'>marketplace</span></div>
|
<div className='system-2xs-medium-uppercase text-text-tertiary'>{t('plugin.from')} <span className='text-text-secondary'>marketplace</span></div>
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
|||||||
import { DSL_EXPORT_CHECK } from '@/app/components/workflow/constants'
|
import { DSL_EXPORT_CHECK } from '@/app/components/workflow/constants'
|
||||||
import { useStore } from '@/app/components/workflow/store'
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
import Features from '@/app/components/workflow/features'
|
import Features from '@/app/components/workflow/features'
|
||||||
import PluginDependency from '@/app/components/workflow/plugin-dependency'
|
|
||||||
import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal'
|
import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal'
|
||||||
import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
|
import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
|
||||||
import {
|
import {
|
||||||
@ -38,7 +37,6 @@ const WorkflowChildren = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PluginDependency />
|
|
||||||
{
|
{
|
||||||
showFeaturesPanel && <Features />
|
showFeaturesPanel && <Features />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
useWorkflowRun,
|
useWorkflowRun,
|
||||||
useWorkflowStartRun,
|
useWorkflowStartRun,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
|
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
|
|
||||||
type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
||||||
const WorkflowMain = ({
|
const WorkflowMain = ({
|
||||||
@ -23,14 +24,28 @@ const WorkflowMain = ({
|
|||||||
viewport,
|
viewport,
|
||||||
}: WorkflowMainProps) => {
|
}: WorkflowMainProps) => {
|
||||||
const featuresStore = useFeaturesStore()
|
const featuresStore = useFeaturesStore()
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
|
||||||
const handleWorkflowDataUpdate = useCallback((payload: any) => {
|
const handleWorkflowDataUpdate = useCallback((payload: any) => {
|
||||||
if (payload.features && featuresStore) {
|
const {
|
||||||
|
features,
|
||||||
|
conversation_variables,
|
||||||
|
environment_variables,
|
||||||
|
} = payload
|
||||||
|
if (features && featuresStore) {
|
||||||
const { setFeatures } = featuresStore.getState()
|
const { setFeatures } = featuresStore.getState()
|
||||||
|
|
||||||
setFeatures(payload.features)
|
setFeatures(features)
|
||||||
}
|
}
|
||||||
}, [featuresStore])
|
if (conversation_variables) {
|
||||||
|
const { setConversationVariables } = workflowStore.getState()
|
||||||
|
setConversationVariables(conversation_variables)
|
||||||
|
}
|
||||||
|
if (environment_variables) {
|
||||||
|
const { setEnvironmentVariables } = workflowStore.getState()
|
||||||
|
setEnvironmentVariables(environment_variables)
|
||||||
|
}
|
||||||
|
}, [featuresStore, workflowStore])
|
||||||
|
|
||||||
const {
|
const {
|
||||||
doSyncWorkflowDraft,
|
doSyncWorkflowDraft,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { memo } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useIsChatMode } from '../hooks'
|
import { useIsChatMode } from '../hooks'
|
||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
|
import { formatWorkflowRunIdentifier } from '../utils'
|
||||||
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
|
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
|
||||||
|
|
||||||
const RunningTitle = () => {
|
const RunningTitle = () => {
|
||||||
@ -12,7 +13,7 @@ const RunningTitle = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='flex h-[18px] items-center text-xs text-gray-500'>
|
<div className='flex h-[18px] items-center text-xs text-gray-500'>
|
||||||
<ClockPlay className='mr-1 h-3 w-3 text-gray-500' />
|
<ClockPlay className='mr-1 h-3 w-3 text-gray-500' />
|
||||||
<span>{isChatMode ? `Test Chat#${historyWorkflowData?.sequence_number}` : `Test Run#${historyWorkflowData?.sequence_number}`}</span>
|
<span>{isChatMode ? `Test Chat${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}` : `Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}</span>
|
||||||
<span className='mx-1'>·</span>
|
<span className='mx-1'>·</span>
|
||||||
<span className='ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600'>
|
<span className='ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600'>
|
||||||
{t('workflow.common.viewOnly')}
|
{t('workflow.common.viewOnly')}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
useWorkflowRun,
|
useWorkflowRun,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
import { ControlMode, WorkflowRunningStatus } from '../types'
|
import { ControlMode, WorkflowRunningStatus } from '../types'
|
||||||
|
import { formatWorkflowRunIdentifier } from '../utils'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
@ -196,7 +197,7 @@ const ViewHistory = ({
|
|||||||
item.id === historyWorkflowData?.id && 'text-text-accent',
|
item.id === historyWorkflowData?.id && 'text-text-accent',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{`Test ${isChatMode ? 'Chat' : 'Run'} #${item.sequence_number}`}
|
{`Test ${isChatMode ? 'Chat' : 'Run'}${formatWorkflowRunIdentifier(item.finished_at)}`}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center text-xs leading-[18px] text-text-tertiary'>
|
<div className='flex items-center text-xs leading-[18px] text-text-tertiary'>
|
||||||
{item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)}
|
{item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)}
|
||||||
|
|||||||
@ -80,6 +80,7 @@ import Confirm from '@/app/components/base/confirm'
|
|||||||
import DatasetsDetailProvider from './datasets-detail-store/provider'
|
import DatasetsDetailProvider from './datasets-detail-store/provider'
|
||||||
import { HooksStoreContextProvider } from './hooks-store'
|
import { HooksStoreContextProvider } from './hooks-store'
|
||||||
import type { Shape as HooksStoreShape } from './hooks-store'
|
import type { Shape as HooksStoreShape } from './hooks-store'
|
||||||
|
import PluginDependency from './plugin-dependency'
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
[CUSTOM_NODE]: CustomNode,
|
[CUSTOM_NODE]: CustomNode,
|
||||||
@ -282,6 +283,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
<LimitTips />
|
<LimitTips />
|
||||||
|
<PluginDependency />
|
||||||
{children}
|
{children}
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { ModelModeType } from '@/types/app'
|
|||||||
import { Theme } from '@/types/app'
|
import { Theme } from '@/types/app'
|
||||||
import { SchemaGeneratorDark, SchemaGeneratorLight } from './assets'
|
import { SchemaGeneratorDark, SchemaGeneratorLight } from './assets'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import type { ModelInfo } from './prompt-editor'
|
|
||||||
import PromptEditor from './prompt-editor'
|
import PromptEditor from './prompt-editor'
|
||||||
import GeneratedResult from './generated-result'
|
import GeneratedResult from './generated-result'
|
||||||
import { useGenerateStructuredOutputRules } from '@/service/use-common'
|
import { useGenerateStructuredOutputRules } from '@/service/use-common'
|
||||||
@ -19,7 +18,6 @@ import Toast from '@/app/components/base/toast'
|
|||||||
import { type FormValue, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { type FormValue, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import { useVisualEditorStore } from '../visual-editor/store'
|
import { useVisualEditorStore } from '../visual-editor/store'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { useMittContext } from '../visual-editor/context'
|
import { useMittContext } from '../visual-editor/context'
|
||||||
|
|
||||||
type JsonSchemaGeneratorProps = {
|
type JsonSchemaGeneratorProps = {
|
||||||
@ -36,10 +34,12 @@ export const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({
|
|||||||
onApply,
|
onApply,
|
||||||
crossAxisOffset,
|
crossAxisOffset,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const localModel = localStorage.getItem('auto-gen-model')
|
||||||
|
? JSON.parse(localStorage.getItem('auto-gen-model') as string) as Model
|
||||||
|
: null
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [view, setView] = useState(GeneratorView.promptEditor)
|
const [view, setView] = useState(GeneratorView.promptEditor)
|
||||||
const [model, setModel] = useState<Model>({
|
const [model, setModel] = useState<Model>(localModel || {
|
||||||
name: '',
|
name: '',
|
||||||
provider: '',
|
provider: '',
|
||||||
mode: ModelModeType.completion,
|
mode: ModelModeType.completion,
|
||||||
@ -58,11 +58,19 @@ export const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultModel) {
|
if (defaultModel) {
|
||||||
setModel(prev => ({
|
const localModel = localStorage.getItem('auto-gen-model')
|
||||||
...prev,
|
? JSON.parse(localStorage.getItem('auto-gen-model') || '')
|
||||||
name: defaultModel.model,
|
: null
|
||||||
provider: defaultModel.provider.provider,
|
if (localModel) {
|
||||||
}))
|
setModel(localModel)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setModel(prev => ({
|
||||||
|
...prev,
|
||||||
|
name: defaultModel.model,
|
||||||
|
provider: defaultModel.provider.provider,
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [defaultModel])
|
}, [defaultModel])
|
||||||
|
|
||||||
@ -77,22 +85,25 @@ export const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({
|
|||||||
setOpen(false)
|
setOpen(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleModelChange = useCallback((model: ModelInfo) => {
|
const handleModelChange = useCallback((newValue: { modelId: string; provider: string; mode?: string; features?: string[] }) => {
|
||||||
setModel(prev => ({
|
const newModel = {
|
||||||
...prev,
|
...model,
|
||||||
provider: model.provider,
|
provider: newValue.provider,
|
||||||
name: model.modelId,
|
name: newValue.modelId,
|
||||||
mode: model.mode as ModelModeType,
|
mode: newValue.mode as ModelModeType,
|
||||||
}))
|
}
|
||||||
}, [])
|
setModel(newModel)
|
||||||
|
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||||
|
}, [model, setModel])
|
||||||
|
|
||||||
const handleCompletionParamsChange = useCallback((newParams: FormValue) => {
|
const handleCompletionParamsChange = useCallback((newParams: FormValue) => {
|
||||||
setModel(prev => ({
|
const newModel = {
|
||||||
...prev,
|
...model,
|
||||||
completion_params: newParams as CompletionParams,
|
completion_params: newParams as CompletionParams,
|
||||||
}),
|
}
|
||||||
)
|
setModel(newModel)
|
||||||
}, [])
|
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||||
|
}, [model, setModel])
|
||||||
|
|
||||||
const { mutateAsync: generateStructuredOutputRules, isPending: isGenerating } = useGenerateStructuredOutputRules()
|
const { mutateAsync: generateStructuredOutputRules, isPending: isGenerating } = useGenerateStructuredOutputRules()
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import GetAutomaticResModal from '@/app/components/app/configuration/config/auto
|
|||||||
import { AppType } from '@/types/app'
|
import { AppType } from '@/types/app'
|
||||||
import type { AutomaticRes } from '@/service/debug'
|
import type { AutomaticRes } from '@/service/debug'
|
||||||
import type { ModelConfig } from '@/app/components/workflow/types'
|
import type { ModelConfig } from '@/app/components/workflow/types'
|
||||||
import type { Model } from '@/types/app'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -20,7 +19,6 @@ type Props = {
|
|||||||
const PromptGeneratorBtn: FC<Props> = ({
|
const PromptGeneratorBtn: FC<Props> = ({
|
||||||
className,
|
className,
|
||||||
onGenerated,
|
onGenerated,
|
||||||
modelConfig,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
|
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
|
||||||
const handleAutomaticRes = useCallback((res: AutomaticRes) => {
|
const handleAutomaticRes = useCallback((res: AutomaticRes) => {
|
||||||
@ -40,7 +38,6 @@ const PromptGeneratorBtn: FC<Props> = ({
|
|||||||
isShow={showAutomatic}
|
isShow={showAutomatic}
|
||||||
onClose={showAutomaticFalse}
|
onClose={showAutomaticFalse}
|
||||||
onFinished={handleAutomaticRes}
|
onFinished={handleAutomaticRes}
|
||||||
model={modelConfig as Model}
|
|
||||||
isInLLMNode
|
isInLLMNode
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
useWorkflowStore,
|
useWorkflowStore,
|
||||||
} from '../../store'
|
} from '../../store'
|
||||||
import { useWorkflowRun } from '../../hooks'
|
import { useWorkflowRun } from '../../hooks'
|
||||||
|
import { formatWorkflowRunIdentifier } from '../../utils'
|
||||||
import UserInput from './user-input'
|
import UserInput from './user-input'
|
||||||
import Chat from '@/app/components/base/chat/chat'
|
import Chat from '@/app/components/base/chat/chat'
|
||||||
import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types'
|
import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types'
|
||||||
@ -99,7 +100,7 @@ const ChatRecord = () => {
|
|||||||
{fetched && (
|
{fetched && (
|
||||||
<>
|
<>
|
||||||
<div className='flex shrink-0 items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'>
|
<div className='flex shrink-0 items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'>
|
||||||
{`TEST CHAT#${historyWorkflowData?.sequence_number}`}
|
{`TEST CHAT${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}
|
||||||
<div
|
<div
|
||||||
className='flex h-6 w-6 cursor-pointer items-center justify-center'
|
className='flex h-6 w-6 cursor-pointer items-center justify-center'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@ -37,7 +37,6 @@ const typeList = [
|
|||||||
ChatVarType.ArrayString,
|
ChatVarType.ArrayString,
|
||||||
ChatVarType.ArrayNumber,
|
ChatVarType.ArrayNumber,
|
||||||
ChatVarType.ArrayObject,
|
ChatVarType.ArrayObject,
|
||||||
ChatVarType.ArrayFile,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const objectPlaceholder = `# example
|
const objectPlaceholder = `# example
|
||||||
@ -128,7 +127,6 @@ const ChatVariableModal = ({
|
|||||||
case ChatVarType.ArrayString:
|
case ChatVarType.ArrayString:
|
||||||
case ChatVarType.ArrayNumber:
|
case ChatVarType.ArrayNumber:
|
||||||
case ChatVarType.ArrayObject:
|
case ChatVarType.ArrayObject:
|
||||||
case ChatVarType.ArrayFile:
|
|
||||||
return value?.filter(Boolean) || []
|
return value?.filter(Boolean) || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,86 +294,84 @@ const ChatVariableModal = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* default value */}
|
{/* default value */}
|
||||||
{type !== ChatVarType.ArrayFile && (
|
<div className='mb-4'>
|
||||||
<div className='mb-4'>
|
<div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'>
|
||||||
<div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'>
|
<div>{t('workflow.chatVariable.modal.value')}</div>
|
||||||
<div>{t('workflow.chatVariable.modal.value')}</div>
|
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && (
|
||||||
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && (
|
<Button
|
||||||
<Button
|
variant='ghost'
|
||||||
variant='ghost'
|
size='small'
|
||||||
size='small'
|
className='text-text-tertiary'
|
||||||
className='text-text-tertiary'
|
onClick={() => handleEditorChange(!editInJSON)}
|
||||||
onClick={() => handleEditorChange(!editInJSON)}
|
>
|
||||||
>
|
{editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
|
||||||
{editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
|
{editInJSON ? t('workflow.chatVariable.modal.oneByOne') : t('workflow.chatVariable.modal.editInJSON')}
|
||||||
{editInJSON ? t('workflow.chatVariable.modal.oneByOne') : t('workflow.chatVariable.modal.editInJSON')}
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
)}
|
{type === ChatVarType.Object && (
|
||||||
{type === ChatVarType.Object && (
|
<Button
|
||||||
<Button
|
variant='ghost'
|
||||||
variant='ghost'
|
size='small'
|
||||||
size='small'
|
className='text-text-tertiary'
|
||||||
className='text-text-tertiary'
|
onClick={() => handleEditorChange(!editInJSON)}
|
||||||
onClick={() => handleEditorChange(!editInJSON)}
|
>
|
||||||
>
|
{editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
|
||||||
{editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
|
{editInJSON ? t('workflow.chatVariable.modal.editInForm') : t('workflow.chatVariable.modal.editInJSON')}
|
||||||
{editInJSON ? t('workflow.chatVariable.modal.editInForm') : t('workflow.chatVariable.modal.editInJSON')}
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className='flex'>
|
|
||||||
{type === ChatVarType.String && (
|
|
||||||
// Input will remove \n\r, so use Textarea just like description area
|
|
||||||
<textarea
|
|
||||||
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
|
|
||||||
value={value}
|
|
||||||
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
|
||||||
onChange={e => setValue(e.target.value)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{type === ChatVarType.Number && (
|
|
||||||
<Input
|
|
||||||
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
|
||||||
value={value}
|
|
||||||
onChange={e => setValue(Number(e.target.value))}
|
|
||||||
type='number'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{type === ChatVarType.Object && !editInJSON && (
|
|
||||||
<ObjectValueList
|
|
||||||
list={objectValue}
|
|
||||||
onChange={setObjectValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{type === ChatVarType.ArrayString && !editInJSON && (
|
|
||||||
<ArrayValueList
|
|
||||||
isString
|
|
||||||
list={value || [undefined]}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{type === ChatVarType.ArrayNumber && !editInJSON && (
|
|
||||||
<ArrayValueList
|
|
||||||
isString={false}
|
|
||||||
list={value || [undefined]}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{editInJSON && (
|
|
||||||
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
|
|
||||||
<CodeEditor
|
|
||||||
isExpand
|
|
||||||
noWrapper
|
|
||||||
language={CodeLanguage.json}
|
|
||||||
value={editorContent}
|
|
||||||
placeholder={<div className='whitespace-pre'>{placeholder}</div>}
|
|
||||||
onChange={handleEditorValueChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className='flex'>
|
||||||
|
{type === ChatVarType.String && (
|
||||||
|
// Input will remove \n\r, so use Textarea just like description area
|
||||||
|
<textarea
|
||||||
|
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
|
||||||
|
value={value}
|
||||||
|
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
||||||
|
onChange={e => setValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === ChatVarType.Number && (
|
||||||
|
<Input
|
||||||
|
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
||||||
|
value={value}
|
||||||
|
onChange={e => setValue(Number(e.target.value))}
|
||||||
|
type='number'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === ChatVarType.Object && !editInJSON && (
|
||||||
|
<ObjectValueList
|
||||||
|
list={objectValue}
|
||||||
|
onChange={setObjectValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === ChatVarType.ArrayString && !editInJSON && (
|
||||||
|
<ArrayValueList
|
||||||
|
isString
|
||||||
|
list={value || [undefined]}
|
||||||
|
onChange={setValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === ChatVarType.ArrayNumber && !editInJSON && (
|
||||||
|
<ArrayValueList
|
||||||
|
isString={false}
|
||||||
|
list={value || [undefined]}
|
||||||
|
onChange={setValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{editInJSON && (
|
||||||
|
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
|
||||||
|
<CodeEditor
|
||||||
|
isExpand
|
||||||
|
noWrapper
|
||||||
|
language={CodeLanguage.json}
|
||||||
|
value={editorContent}
|
||||||
|
placeholder={<div className='whitespace-pre'>{placeholder}</div>}
|
||||||
|
onChange={handleEditorValueChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/* description */}
|
{/* description */}
|
||||||
<div className=''>
|
<div className=''>
|
||||||
<div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.description')}</div>
|
<div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.description')}</div>
|
||||||
|
|||||||
@ -5,5 +5,4 @@ export enum ChatVarType {
|
|||||||
ArrayString = 'array[string]',
|
ArrayString = 'array[string]',
|
||||||
ArrayNumber = 'array[number]',
|
ArrayNumber = 'array[number]',
|
||||||
ArrayObject = 'array[object]',
|
ArrayObject = 'array[object]',
|
||||||
ArrayFile = 'array[file]',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Run from '../run'
|
|||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
import { useWorkflowUpdate } from '../hooks'
|
import { useWorkflowUpdate } from '../hooks'
|
||||||
import { useHooksStore } from '../hooks-store'
|
import { useHooksStore } from '../hooks-store'
|
||||||
|
import { formatWorkflowRunIdentifier } from '../utils'
|
||||||
|
|
||||||
const Record = () => {
|
const Record = () => {
|
||||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||||
@ -22,7 +23,7 @@ const Record = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='flex h-full w-[400px] flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'>
|
<div className='flex h-full w-[400px] flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'>
|
||||||
<div className='system-xl-semibold flex items-center justify-between p-4 pb-0 text-text-primary'>
|
<div className='system-xl-semibold flex items-center justify-between p-4 pb-0 text-text-primary'>
|
||||||
{`Test Run#${historyWorkflowData?.sequence_number}`}
|
{`Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}
|
||||||
</div>
|
</div>
|
||||||
<Run
|
<Run
|
||||||
runDetailUrl={getWorkflowRunAndTraceUrl(historyWorkflowData?.id).runUrl}
|
runDetailUrl={getWorkflowRunAndTraceUrl(historyWorkflowData?.id).runUrl}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { useStore } from '../store'
|
|||||||
import {
|
import {
|
||||||
WorkflowRunningStatus,
|
WorkflowRunningStatus,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
import { formatWorkflowRunIdentifier } from '../utils'
|
||||||
import Toast from '../../base/toast'
|
import Toast from '../../base/toast'
|
||||||
import InputsPanel from './inputs-panel'
|
import InputsPanel from './inputs-panel'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
@ -88,7 +89,7 @@ const WorkflowPreview = () => {
|
|||||||
onMouseDown={startResizing}
|
onMouseDown={startResizing}
|
||||||
/>
|
/>
|
||||||
<div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'>
|
<div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'>
|
||||||
{`Test Run${!workflowRunningData?.result.sequence_number ? '' : `#${workflowRunningData?.result.sequence_number}`}`}
|
{`Test Run${formatWorkflowRunIdentifier(workflowRunningData?.result.finished_at)}`}
|
||||||
<div className='cursor-pointer p-1' onClick={() => handleCancelDebugAndPreviewPanel()}>
|
<div className='cursor-pointer p-1' onClick={() => handleCancelDebugAndPreviewPanel()}>
|
||||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -376,7 +376,6 @@ export type WorkflowRunningData = {
|
|||||||
message_id?: string
|
message_id?: string
|
||||||
conversation_id?: string
|
conversation_id?: string
|
||||||
result: {
|
result: {
|
||||||
sequence_number?: number
|
|
||||||
workflow_id?: string
|
workflow_id?: string
|
||||||
inputs?: string
|
inputs?: string
|
||||||
process_data?: string
|
process_data?: string
|
||||||
@ -399,9 +398,9 @@ export type WorkflowRunningData = {
|
|||||||
|
|
||||||
export type HistoryWorkflowData = {
|
export type HistoryWorkflowData = {
|
||||||
id: string
|
id: string
|
||||||
sequence_number: number
|
|
||||||
status: string
|
status: string
|
||||||
conversation_id?: string
|
conversation_id?: string
|
||||||
|
finished_at?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ChangeType {
|
export enum ChangeType {
|
||||||
|
|||||||
@ -86,6 +86,8 @@ const UpdateDSLModal = ({
|
|||||||
graph,
|
graph,
|
||||||
features,
|
features,
|
||||||
hash,
|
hash,
|
||||||
|
conversation_variables,
|
||||||
|
environment_variables,
|
||||||
} = await fetchWorkflowDraft(`/apps/${app_id}/workflows/draft`)
|
} = await fetchWorkflowDraft(`/apps/${app_id}/workflows/draft`)
|
||||||
|
|
||||||
const { nodes, edges, viewport } = graph
|
const { nodes, edges, viewport } = graph
|
||||||
@ -122,6 +124,8 @@ const UpdateDSLModal = ({
|
|||||||
viewport,
|
viewport,
|
||||||
features: newFeatures,
|
features: newFeatures,
|
||||||
hash,
|
hash,
|
||||||
|
conversation_variables: conversation_variables || [],
|
||||||
|
environment_variables: environment_variables || [],
|
||||||
},
|
},
|
||||||
} as any)
|
} as any)
|
||||||
}, [eventEmitter])
|
}, [eventEmitter])
|
||||||
|
|||||||
@ -33,3 +33,22 @@ export const isEventTargetInputArea = (target: HTMLElement) => {
|
|||||||
if (target.contentEditable === 'true')
|
if (target.contentEditable === 'true')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format workflow run identifier using finished_at timestamp
|
||||||
|
* @param finishedAt - Unix timestamp in seconds
|
||||||
|
* @param fallbackText - Text to show when finishedAt is not available (default: 'Running')
|
||||||
|
* @returns Formatted string like " (14:30:25)" or " (Running)"
|
||||||
|
*/
|
||||||
|
export const formatWorkflowRunIdentifier = (finishedAt?: number, fallbackText = 'Running'): string => {
|
||||||
|
if (!finishedAt)
|
||||||
|
return ` (${fallbackText})`
|
||||||
|
|
||||||
|
const date = new Date(finishedAt * 1000)
|
||||||
|
const timeStr = date.toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
})
|
||||||
|
return ` (${timeStr})`
|
||||||
|
}
|
||||||
|
|||||||
@ -314,14 +314,22 @@ else if (globalThis.document?.body?.getAttribute('data-public-max-iterations-num
|
|||||||
|
|
||||||
export const MAX_ITERATIONS_NUM = maxIterationsNum
|
export const MAX_ITERATIONS_NUM = maxIterationsNum
|
||||||
|
|
||||||
export const ENABLE_WEBSITE_JINAREADER = process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER !== undefined
|
let enableWebsiteJinaReader = true
|
||||||
? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER === 'true'
|
let enableWebsiteFireCrawl = true
|
||||||
: globalThis.document?.body?.getAttribute('data-public-enable-website-jinareader') === 'true' || true
|
let enableWebsiteWaterCrawl = false
|
||||||
|
|
||||||
export const ENABLE_WEBSITE_FIRECRAWL = process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL !== undefined
|
const getBooleanConfig = (envVar: string | undefined, attr: string) => {
|
||||||
? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL === 'true'
|
if (envVar !== undefined && envVar !== '')
|
||||||
: globalThis.document?.body?.getAttribute('data-public-enable-website-firecrawl') === 'true' || true
|
return envVar === 'true'
|
||||||
|
const attrValue = globalThis.document?.body?.getAttribute(attr)
|
||||||
|
if (attrValue !== undefined && attrValue !== '')
|
||||||
|
return attrValue === 'true'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
export const ENABLE_WEBSITE_WATERCRAWL = process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL !== undefined
|
enableWebsiteJinaReader = getBooleanConfig(process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER, 'data-public-enable-website-jinareader')
|
||||||
? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL === 'true'
|
enableWebsiteFireCrawl = getBooleanConfig(process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL, 'data-public-enable-website-firecrawl')
|
||||||
: globalThis.document?.body?.getAttribute('data-public-enable-website-watercrawl') === 'true' || true
|
enableWebsiteWaterCrawl = getBooleanConfig(process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL, 'data-public-enable-website-watercrawl')
|
||||||
|
export const ENABLE_WEBSITE_JINAREADER = enableWebsiteJinaReader
|
||||||
|
export const ENABLE_WEBSITE_FIRECRAWL = enableWebsiteFireCrawl
|
||||||
|
export const ENABLE_WEBSITE_WATERCRAWL = enableWebsiteWaterCrawl
|
||||||
|
|||||||
@ -154,10 +154,6 @@ export const ProviderContextProvider = ({
|
|||||||
setIsFetchedPlan(true)
|
setIsFetchedPlan(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.model_load_balancing_enabled)
|
|
||||||
setModelLoadBalancingEnabled(true)
|
|
||||||
if (data.dataset_operator_enabled)
|
|
||||||
setDatasetOperatorEnabled(true)
|
|
||||||
if (data.model_load_balancing_enabled)
|
if (data.model_load_balancing_enabled)
|
||||||
setModelLoadBalancingEnabled(true)
|
setModelLoadBalancingEnabled(true)
|
||||||
if (data.dataset_operator_enabled)
|
if (data.dataset_operator_enabled)
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
const translation = {
|
const translation = {
|
||||||
daysInWeek: {
|
daysInWeek: {
|
||||||
Tue: '星期二',
|
Sun: '日',
|
||||||
Wed: '星期三',
|
Mon: '一',
|
||||||
Fri: '自由',
|
Tue: '二',
|
||||||
Mon: '懷念',
|
Wed: '三',
|
||||||
Sun: '太陽',
|
Thu: '四',
|
||||||
Sat: '星期六',
|
Fri: '五',
|
||||||
Thu: '星期四',
|
Sat: '六',
|
||||||
},
|
},
|
||||||
months: {
|
months: {
|
||||||
January: '一月',
|
January: '一月',
|
||||||
|
|||||||
@ -278,7 +278,6 @@ export type WorkflowLogsRequest = {
|
|||||||
|
|
||||||
export type WorkflowRunDetailResponse = {
|
export type WorkflowRunDetailResponse = {
|
||||||
id: string
|
id: string
|
||||||
sequence_number: number
|
|
||||||
version: string
|
version: string
|
||||||
graph: {
|
graph: {
|
||||||
nodes: Node[]
|
nodes: Node[]
|
||||||
|
|||||||
@ -386,7 +386,7 @@ html[data-theme="dark"] {
|
|||||||
--color-background-gradient-bg-fill-chat-bg-2: #1d1d20;
|
--color-background-gradient-bg-fill-chat-bg-2: #1d1d20;
|
||||||
--color-background-gradient-bg-fill-chat-bubble-bg-1: #c8ceda14;
|
--color-background-gradient-bg-fill-chat-bubble-bg-1: #c8ceda14;
|
||||||
--color-background-gradient-bg-fill-chat-bubble-bg-2: #c8ceda05;
|
--color-background-gradient-bg-fill-chat-bubble-bg-2: #c8ceda05;
|
||||||
--color-background-gradient-bg-fill-chat-bubble-bg-3: #a5bddb;
|
--color-background-gradient-bg-fill-chat-bubble-bg-3: #27314d;
|
||||||
--color-background-gradient-bg-fill-debug-bg-1: #c8ceda14;
|
--color-background-gradient-bg-fill-debug-bg-1: #c8ceda14;
|
||||||
--color-background-gradient-bg-fill-debug-bg-2: #18181b0a;
|
--color-background-gradient-bg-fill-debug-bg-2: #18181b0a;
|
||||||
|
|
||||||
|
|||||||
@ -154,7 +154,6 @@ export type WorkflowStartedResponse = {
|
|||||||
data: {
|
data: {
|
||||||
id: string
|
id: string
|
||||||
workflow_id: string
|
workflow_id: string
|
||||||
sequence_number: number
|
|
||||||
created_at: number
|
created_at: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,7 +290,6 @@ export type AgentLogResponse = {
|
|||||||
|
|
||||||
export type WorkflowRunHistory = {
|
export type WorkflowRunHistory = {
|
||||||
id: string
|
id: string
|
||||||
sequence_number: number
|
|
||||||
version: string
|
version: string
|
||||||
conversation_id?: string
|
conversation_id?: string
|
||||||
message_id?: string
|
message_id?: string
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user