mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-16 13:45:55 +08:00
Merge branch 'refs/heads/main' into feat/workflow-parallel-support
# Conflicts: # api/core/workflow/nodes/code/code_node.py # api/core/workflow/nodes/end/end_node.py # api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py # api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py # api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py
This commit is contained in:
commit
0fe516568a
24
.github/DISCUSSION_TEMPLATE/general.yml
vendored
Normal file
24
.github/DISCUSSION_TEMPLATE/general.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
title: "General Discussion"
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Self Checks
|
||||
description: "To make sure we get to you in time, please check the following :)"
|
||||
options:
|
||||
- label: I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
|
||||
required: true
|
||||
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
|
||||
required: true
|
||||
- label: "请务必使用英文提交 Issue,否则会被关闭。谢谢!:)"
|
||||
required: true
|
||||
- label: "Please do not modify this template :) and fill in all the required fields."
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Content
|
||||
placeholder: Please describe the content you would like to discuss.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please limit one request per issue.
|
30
.github/DISCUSSION_TEMPLATE/help.yml
vendored
Normal file
30
.github/DISCUSSION_TEMPLATE/help.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
title: "Help"
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Self Checks
|
||||
description: "To make sure we get to you in time, please check the following :)"
|
||||
options:
|
||||
- label: I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
|
||||
required: true
|
||||
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
|
||||
required: true
|
||||
- label: "请务必使用英文提交 Issue,否则会被关闭。谢谢!:)"
|
||||
required: true
|
||||
- label: "Please do not modify this template :) and fill in all the required fields."
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 1. Is this request related to a challenge you're experiencing? Tell me about your story.
|
||||
placeholder: Please describe the specific scenario or problem you're facing as clearly as possible. For instance "I was trying to use [feature] for [specific task], and [what happened]... It was frustrating because...."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 2. Additional context or comments
|
||||
placeholder: (Any other information, comments, documentations, links, or screenshots that would provide more clarity. This is the place to add anything else not covered above.)
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please limit one request per issue.
|
37
.github/DISCUSSION_TEMPLATE/suggestion.yml
vendored
Normal file
37
.github/DISCUSSION_TEMPLATE/suggestion.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
title: Suggestions for New Features
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Self Checks
|
||||
description: "To make sure we get to you in time, please check the following :)"
|
||||
options:
|
||||
- label: I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
|
||||
required: true
|
||||
- label: I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)).
|
||||
required: true
|
||||
- label: "请务必使用英文提交 Issue,否则会被关闭。谢谢!:)"
|
||||
required: true
|
||||
- label: "Please do not modify this template :) and fill in all the required fields."
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 1. Is this request related to a challenge you're experiencing? Tell me about your story.
|
||||
placeholder: Please describe the specific scenario or problem you're facing as clearly as possible. For instance "I was trying to use [feature] for [specific task], and [what happened]... It was frustrating because...."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 2. Additional context or comments
|
||||
placeholder: (Any other information, comments, documentations, links, or screenshots that would provide more clarity. This is the place to add anything else not covered above.)
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 3. Can you help us with this feature?
|
||||
description: Let us know! This is not a commitment, but a starting point for collaboration.
|
||||
options:
|
||||
- label: I am interested in contributing to this feature.
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please limit one request per issue.
|
@ -216,7 +216,6 @@ At the same time, please consider supporting Dify by sharing it on social media
|
||||
|
||||
* [Github Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and asking questions.
|
||||
* [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||
* [Email](mailto:support@dify.ai?subject=[GitHub]Questions%20About%20Dify). Best for: questions you have about using Dify.AI.
|
||||
* [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
|
||||
* [Twitter](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
|
||||
|
||||
|
@ -199,7 +199,6 @@ docker compose up -d
|
||||
## المجتمع والاتصال
|
||||
* [مناقشة Github](https://github.com/langgenius/dify/discussions). الأفضل لـ: مشاركة التعليقات وطرح الأسئلة.
|
||||
* [المشكلات على GitHub](https://github.com/langgenius/dify/issues). الأفضل لـ: الأخطاء التي تواجهها في استخدام Dify.AI، واقتراحات الميزات. انظر [دليل المساهمة](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||
* [البريد الإلكتروني](mailto:support@dify.ai?subject=[GitHub]Questions%20About%20Dify). الأفضل لـ: الأسئلة التي تتعلق باستخدام Dify.AI.
|
||||
* [Discord](https://discord.gg/FngNHpbcY7). الأفضل لـ: مشاركة تطبيقاتك والترفيه مع المجتمع.
|
||||
* [تويتر](https://twitter.com/dify_ai). الأفضل لـ: مشاركة تطبيقاتك والترفيه مع المجتمع.
|
||||
|
||||
|
@ -224,7 +224,6 @@ Al mismo tiempo, considera apoyar a Dify compartiéndolo en redes sociales y en
|
||||
|
||||
* [Discusión en GitHub](https://github.com/langgenius/dify/discussions). Lo mejor para: compartir comentarios y hacer preguntas.
|
||||
* [Reporte de problemas en GitHub](https://github.com/langgenius/dify/issues). Lo mejor para: errores que encuentres usando Dify.AI y propuestas de características. Consulta nuestra [Guía de contribución](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||
* [Correo electrónico](mailto:support@dify.ai?subject=[GitHub]Questions%20About%20Dify). Lo mejor para: preguntas que tengas sobre el uso de Dify.AI.
|
||||
* [Discord](https://discord.gg/FngNHpbcY7). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.
|
||||
* [Twitter](https://twitter.com/dify_ai). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.
|
||||
|
||||
|
@ -222,7 +222,6 @@ Dans le même temps, veuillez envisager de soutenir Dify en le partageant sur le
|
||||
|
||||
* [Discussion GitHub](https://github.com/langgenius/dify/discussions). Meilleur pour: partager des commentaires et poser des questions.
|
||||
* [Problèmes GitHub](https://github.com/langgenius/dify/issues). Meilleur pour: les bogues que vous rencontrez en utilisant Dify.AI et les propositions de fonctionnalités. Consultez notre [Guide de contribution](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||
* [E-mail](mailto:support@dify.ai?subject=[GitHub]Questions%20About%20Dify). Meilleur pour: les questions que vous avez sur l'utilisation de Dify.AI.
|
||||
* [Discord](https://discord.gg/FngNHpbcY7). Meilleur pour: partager vos applications et passer du temps avec la communauté.
|
||||
* [Twitter](https://twitter.com/dify_ai). Meilleur pour: partager vos applications et passer du temps avec la communauté.
|
||||
|
||||
|
@ -221,7 +221,6 @@ docker compose up -d
|
||||
|
||||
* [Github Discussion](https://github.com/langgenius/dify/discussions). 主に: フィードバックの共有や質問。
|
||||
* [GitHub Issues](https://github.com/langgenius/dify/issues). 主に: Dify.AIを使用する際に発生するエラーや問題については、[貢献ガイド](CONTRIBUTING_JA.md)を参照してください
|
||||
* [Email](mailto:support@dify.ai?subject=[GitHub]Questions%20About%20Dify). 主に: Dify.AIの使用に関する質問。
|
||||
* [Discord](https://discord.gg/FngNHpbcY7). 主に: アプリケーションの共有やコミュニティとの交流。
|
||||
* [Twitter](https://twitter.com/dify_ai). 主に: アプリケーションの共有やコミュニティとの交流。
|
||||
|
||||
@ -239,7 +238,7 @@ docker compose up -d
|
||||
<td>無料の30分間のミーティングをスケジュール</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href='mailto:support@dify.ai?subject=[GitHub]Technical%20Support'>技術サポート</a></td>
|
||||
<td><a href='https://github.com/langgenius/dify/issues'>技術サポート</a></td>
|
||||
<td>技術的な問題やサポートに関する質問</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -224,7 +224,6 @@ At the same time, please consider supporting Dify by sharing it on social media
|
||||
|
||||
). Best for: sharing feedback and asking questions.
|
||||
* [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||
* [Email](mailto:support@dify.ai?subject=[GitHub]Questions%20About%20Dify). Best for: questions you have about using Dify.AI.
|
||||
* [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
|
||||
* [Twitter](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
|
||||
|
||||
|
@ -214,7 +214,6 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했
|
||||
|
||||
* [Github 토론](https://github.com/langgenius/dify/discussions). 피드백 공유 및 질문하기에 적합합니다.
|
||||
* [GitHub 이슈](https://github.com/langgenius/dify/issues). Dify.AI 사용 중 발견한 버그와 기능 제안에 적합합니다. [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)를 참조하세요.
|
||||
* [이메일](mailto:support@dify.ai?subject=[GitHub]Questions%20About%20Dify). Dify.AI 사용에 대한 질문하기에 적합합니다.
|
||||
* [디스코드](https://discord.gg/FngNHpbcY7). 애플리케이션 공유 및 커뮤니티와 소통하기에 적합합니다.
|
||||
* [트위터](https://twitter.com/dify_ai). 애플리케이션 공유 및 커뮤니티와 소통하기에 적합합니다.
|
||||
|
||||
|
@ -543,13 +543,13 @@ class DatasetRetrievalSettingApi(Resource):
|
||||
def get(self):
|
||||
vector_type = dify_config.VECTOR_STORE
|
||||
match vector_type:
|
||||
case VectorType.MILVUS | VectorType.RELYT | VectorType.PGVECTOR | VectorType.TIDB_VECTOR | VectorType.CHROMA | VectorType.TENCENT | VectorType.ORACLE:
|
||||
case VectorType.MILVUS | VectorType.RELYT | VectorType.PGVECTOR | VectorType.TIDB_VECTOR | VectorType.CHROMA | VectorType.TENCENT:
|
||||
return {
|
||||
'retrieval_method': [
|
||||
RetrievalMethod.SEMANTIC_SEARCH.value
|
||||
]
|
||||
}
|
||||
case VectorType.QDRANT | VectorType.WEAVIATE | VectorType.OPENSEARCH | VectorType.ANALYTICDB | VectorType.MYSCALE:
|
||||
case VectorType.QDRANT | VectorType.WEAVIATE | VectorType.OPENSEARCH | VectorType.ANALYTICDB | VectorType.MYSCALE | VectorType.ORACLE:
|
||||
return {
|
||||
'retrieval_method': [
|
||||
RetrievalMethod.SEMANTIC_SEARCH.value,
|
||||
@ -567,13 +567,13 @@ class DatasetRetrievalSettingMockApi(Resource):
|
||||
@account_initialization_required
|
||||
def get(self, vector_type):
|
||||
match vector_type:
|
||||
case VectorType.MILVUS | VectorType.RELYT | VectorType.PGVECTOR | VectorType.TIDB_VECTOR | VectorType.CHROMA | VectorType.TENCENT | VectorType.ORACLE:
|
||||
case VectorType.MILVUS | VectorType.RELYT | VectorType.PGVECTOR | VectorType.TIDB_VECTOR | VectorType.CHROMA | VectorType.TENCENT:
|
||||
return {
|
||||
'retrieval_method': [
|
||||
RetrievalMethod.SEMANTIC_SEARCH.value
|
||||
]
|
||||
}
|
||||
case VectorType.QDRANT | VectorType.WEAVIATE | VectorType.OPENSEARCH| VectorType.ANALYTICDB | VectorType.MYSCALE:
|
||||
case VectorType.QDRANT | VectorType.WEAVIATE | VectorType.OPENSEARCH| VectorType.ANALYTICDB | VectorType.MYSCALE | VectorType.ORACLE:
|
||||
return {
|
||||
'retrieval_method': [
|
||||
RetrievalMethod.SEMANTIC_SEARCH.value,
|
||||
|
@ -410,10 +410,9 @@ class LBModelManager:
|
||||
self._model = model
|
||||
self._load_balancing_configs = load_balancing_configs
|
||||
|
||||
for load_balancing_config in self._load_balancing_configs:
|
||||
for load_balancing_config in self._load_balancing_configs[:]: # Iterate over a shallow copy of the list
|
||||
if load_balancing_config.name == "__inherit__":
|
||||
if not managed_credentials:
|
||||
# FIXME: Mutation to loop iterable `self._load_balancing_configs` during iteration
|
||||
# remove __inherit__ if managed credentials is not provided
|
||||
self._load_balancing_configs.remove(load_balancing_config)
|
||||
else:
|
||||
|
@ -38,11 +38,10 @@ class BaseKeyword(ABC):
|
||||
raise NotImplementedError
|
||||
|
||||
def _filter_duplicate_texts(self, texts: list[Document]) -> list[Document]:
|
||||
for text in texts:
|
||||
for text in texts[:]:
|
||||
doc_id = text.metadata['doc_id']
|
||||
exists_duplicate_node = self.text_exists(doc_id)
|
||||
if exists_duplicate_node:
|
||||
# FIXME: Mutation to loop iterable `texts` during iteration
|
||||
texts.remove(text)
|
||||
|
||||
return texts
|
||||
|
@ -110,7 +110,7 @@ class RetrievalService:
|
||||
)
|
||||
|
||||
documents = keyword.search(
|
||||
query,
|
||||
cls.escape_query_for_search(query),
|
||||
top_k=top_k
|
||||
)
|
||||
all_documents.extend(documents)
|
||||
@ -132,7 +132,7 @@ class RetrievalService:
|
||||
)
|
||||
|
||||
documents = vector.search_by_vector(
|
||||
query,
|
||||
cls.escape_query_for_search(query),
|
||||
search_type='similarity_score_threshold',
|
||||
top_k=top_k,
|
||||
score_threshold=score_threshold,
|
||||
@ -170,7 +170,7 @@ class RetrievalService:
|
||||
)
|
||||
|
||||
documents = vector_processor.search_by_full_text(
|
||||
query,
|
||||
cls.escape_query_for_search(query),
|
||||
top_k=top_k
|
||||
)
|
||||
if documents:
|
||||
@ -186,3 +186,7 @@ class RetrievalService:
|
||||
all_documents.extend(documents)
|
||||
except Exception as e:
|
||||
exceptions.append(str(e))
|
||||
|
||||
@staticmethod
|
||||
def escape_query_for_search(query: str) -> str:
|
||||
return query.replace('"', '\\"')
|
@ -115,7 +115,8 @@ class MilvusVector(BaseVector):
|
||||
uri = "https://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
else:
|
||||
uri = "http://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password,
|
||||
db_name=self._client_config.database)
|
||||
|
||||
from pymilvus import utility
|
||||
if utility.has_collection(self._collection_name, using=alias):
|
||||
@ -130,7 +131,8 @@ class MilvusVector(BaseVector):
|
||||
uri = "https://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
else:
|
||||
uri = "http://" + str(self._client_config.host) + ":" + str(self._client_config.port)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password)
|
||||
connections.connect(alias=alias, uri=uri, user=self._client_config.user, password=self._client_config.password,
|
||||
db_name=self._client_config.database)
|
||||
|
||||
from pymilvus import utility
|
||||
if utility.has_collection(self._collection_name, using=alias):
|
||||
|
@ -1,11 +1,15 @@
|
||||
import array
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
from contextlib import contextmanager
|
||||
from typing import Any
|
||||
|
||||
import jieba.posseg as pseg
|
||||
import nltk
|
||||
import numpy
|
||||
import oracledb
|
||||
from nltk.corpus import stopwords
|
||||
from pydantic import BaseModel, model_validator
|
||||
|
||||
from configs import dify_config
|
||||
@ -50,6 +54,11 @@ CREATE TABLE IF NOT EXISTS {table_name} (
|
||||
,embedding vector NOT NULL
|
||||
)
|
||||
"""
|
||||
SQL_CREATE_INDEX = """
|
||||
CREATE INDEX idx_docs_{table_name} ON {table_name}(text)
|
||||
INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS
|
||||
('FILTER CTXSYS.NULL_FILTER SECTION GROUP CTXSYS.HTML_SECTION_GROUP LEXER sys.my_chinese_vgram_lexer')
|
||||
"""
|
||||
|
||||
|
||||
class OracleVector(BaseVector):
|
||||
@ -188,7 +197,53 @@ class OracleVector(BaseVector):
|
||||
return docs
|
||||
|
||||
def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]:
|
||||
# do not support bm25 search
|
||||
top_k = kwargs.get("top_k", 5)
|
||||
# just not implement fetch by score_threshold now, may be later
|
||||
score_threshold = kwargs.get("score_threshold") if kwargs.get("score_threshold") else 0.0
|
||||
if len(query) > 0:
|
||||
# Check which language the query is in
|
||||
zh_pattern = re.compile('[\u4e00-\u9fa5]+')
|
||||
match = zh_pattern.search(query)
|
||||
entities = []
|
||||
# match: query condition maybe is a chinese sentence, so using Jieba split,else using nltk split
|
||||
if match:
|
||||
words = pseg.cut(query)
|
||||
current_entity = ""
|
||||
for word, pos in words:
|
||||
if pos == 'nr' or pos == 'Ng' or pos == 'eng' or pos == 'nz' or pos == 'n' or pos == 'ORG' or pos == 'v': # nr: 人名, ns: 地名, nt: 机构名
|
||||
current_entity += word
|
||||
else:
|
||||
if current_entity:
|
||||
entities.append(current_entity)
|
||||
current_entity = ""
|
||||
if current_entity:
|
||||
entities.append(current_entity)
|
||||
else:
|
||||
try:
|
||||
nltk.data.find('tokenizers/punkt')
|
||||
nltk.data.find('corpora/stopwords')
|
||||
except LookupError:
|
||||
nltk.download('punkt')
|
||||
nltk.download('stopwords')
|
||||
print("run download")
|
||||
e_str = re.sub(r'[^\w ]', '', query)
|
||||
all_tokens = nltk.word_tokenize(e_str)
|
||||
stop_words = stopwords.words('english')
|
||||
for token in all_tokens:
|
||||
if token not in stop_words:
|
||||
entities.append(token)
|
||||
with self._get_cursor() as cur:
|
||||
cur.execute(
|
||||
f"select meta, text FROM {self.table_name} WHERE CONTAINS(text, :1, 1) > 0 order by score(1) desc fetch first {top_k} rows only",
|
||||
[" ACCUM ".join(entities)]
|
||||
)
|
||||
docs = []
|
||||
for record in cur:
|
||||
metadata, text = record
|
||||
docs.append(Document(page_content=text, metadata=metadata))
|
||||
return docs
|
||||
else:
|
||||
return [Document(page_content="", metadata="")]
|
||||
return []
|
||||
|
||||
def delete(self) -> None:
|
||||
@ -206,6 +261,8 @@ class OracleVector(BaseVector):
|
||||
with self._get_cursor() as cur:
|
||||
cur.execute(SQL_CREATE_TABLE.format(table_name=self.table_name))
|
||||
redis_client.set(collection_exist_cache_key, 1, ex=3600)
|
||||
with self._get_cursor() as cur:
|
||||
cur.execute(SQL_CREATE_INDEX.format(table_name=self.table_name))
|
||||
|
||||
|
||||
class OracleVectorFactory(AbstractVectorFactory):
|
||||
|
@ -57,11 +57,10 @@ class BaseVector(ABC):
|
||||
raise NotImplementedError
|
||||
|
||||
def _filter_duplicate_texts(self, texts: list[Document]) -> list[Document]:
|
||||
for text in texts:
|
||||
for text in texts[:]:
|
||||
doc_id = text.metadata['doc_id']
|
||||
exists_duplicate_node = self.text_exists(doc_id)
|
||||
if exists_duplicate_node:
|
||||
# FIXME: Mutation to loop iterable `texts` during iteration
|
||||
texts.remove(text)
|
||||
|
||||
return texts
|
||||
|
@ -153,11 +153,10 @@ class Vector:
|
||||
return CacheEmbedding(embedding_model)
|
||||
|
||||
def _filter_duplicate_texts(self, texts: list[Document]) -> list[Document]:
|
||||
for text in texts:
|
||||
for text in texts[:]:
|
||||
doc_id = text.metadata['doc_id']
|
||||
exists_duplicate_node = self.text_exists(doc_id)
|
||||
if exists_duplicate_node:
|
||||
# FIXME: Mutation to loop iterable `texts` during iteration
|
||||
texts.remove(text)
|
||||
|
||||
return texts
|
||||
|
16
api/core/tools/provider/builtin/hap/_assets/icon.svg
Normal file
16
api/core/tools/provider/builtin/hap/_assets/icon.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80" viewBox="0 0 80 80">
|
||||
<defs>
|
||||
<clipPath id="clip-画板_16">
|
||||
<rect width="80" height="80"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="画板_16" data-name="画板 – 16" clip-path="url(#clip-画板_16)">
|
||||
<g id="组_27" data-name="组 27">
|
||||
<circle id="椭圆_13" data-name="椭圆 13" cx="40" cy="40" r="40" fill="#3e96f3"/>
|
||||
<g id="组_26" data-name="组 26" transform="translate(16.361 16.674)">
|
||||
<path id="路径_63230" data-name="路径 63230" d="M92.52,179.361c5.974,0,8.592-2.307,8.592-8.2,0-6.428.067-32.5.067-32.5h-.738a5.221,5.221,0,0,0-4.005,1.344H86.076A5.3,5.3,0,0,0,82,138.6h-.582v32.855c0,5.487,2.461,7.906,8.413,7.906Zm-5.348-33.93h8.189c-.022,4.457-.022,8.04-.022,10.952H87.15C87.15,153.471,87.173,149.888,87.173,145.431Zm-.067,25.823c0-2.016,0-3.315.022-9.451h8.189c-.022,6.316-.022,7.547-.022,9.429,0,1.792-1.052,2.71-2.842,2.71H89.7A2.447,2.447,0,0,1,87.106,171.253Z" transform="translate(-81.4 -137.973)" fill="#fff"/>
|
||||
<path id="路径_63231" data-name="路径 63231" d="M172.508,168.341h12.373v6.495c0,2.508-.873,3.27-3.468,3.27H178.84v4.972h4.027c5.437,0,7.988-2.419,7.988-7.637V143.549c0-5.33-2.17-7.749-8.323-7.749h-7.719c-6.22,0-8.323,2.441-8.323,7.7v27.413c0,3.942-1.611,6.293-4.99,7.525v4.614c7.652-.851,10.986-4.838,10.986-12.027C172.508,170.178,172.508,169.26,172.508,168.341Zm0-24.255c0-2.038.85-2.844,3.132-2.844h6.332c1.991,0,2.909.9,2.909,2.934v4.994H172.508Zm0,10.5h12.373v8.331H172.508Z" transform="translate(-143.578 -135.8)" fill="#fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
8
api/core/tools/provider/builtin/hap/hap.py
Normal file
8
api/core/tools/provider/builtin/hap/hap.py
Normal file
@ -0,0 +1,8 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class HapProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
pass
|
15
api/core/tools/provider/builtin/hap/hap.yaml
Normal file
15
api/core/tools/provider/builtin/hap/hap.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
identity:
|
||||
author: Mingdao
|
||||
name: hap
|
||||
label:
|
||||
en_US: HAP
|
||||
zh_Hans: HAP
|
||||
pt_BR: HAP
|
||||
description:
|
||||
en_US: "Hyper application platform that is particularly friendly to AI"
|
||||
zh_Hans: "对 AI 特别友好的超级应用平台"
|
||||
pt_BR: "Plataforma de aplicação hiper que é particularmente amigável à IA"
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- productivity
|
||||
credentials_for_provider:
|
@ -0,0 +1,53 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class AddWorksheetRecordTool(BuiltinTool):
|
||||
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
appkey = tool_parameters.get('appkey', '')
|
||||
if not appkey:
|
||||
return self.create_text_message('Invalid parameter App Key')
|
||||
sign = tool_parameters.get('sign', '')
|
||||
if not sign:
|
||||
return self.create_text_message('Invalid parameter Sign')
|
||||
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||
if not worksheet_id:
|
||||
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||
record_data = tool_parameters.get('record_data', '')
|
||||
if not record_data:
|
||||
return self.create_text_message('Invalid parameter Record Row Data')
|
||||
|
||||
host = tool_parameters.get('host', '')
|
||||
if not host:
|
||||
host = 'https://api.mingdao.com'
|
||||
elif not host.startswith(("http://", "https://")):
|
||||
return self.create_text_message('Invalid parameter Host Address')
|
||||
else:
|
||||
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||
|
||||
url = f"{host}/v2/open/worksheet/addRow"
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
|
||||
|
||||
try:
|
||||
payload['controls'] = json.loads(record_data)
|
||||
res = httpx.post(url, headers=headers, json=payload, timeout=60)
|
||||
res.raise_for_status()
|
||||
res_json = res.json()
|
||||
if res_json.get('error_code') != 1:
|
||||
return self.create_text_message(f"Failed to add the new record. {res_json['error_msg']}")
|
||||
return self.create_text_message(f"New record added successfully. The record ID is {res_json['data']}.")
|
||||
except httpx.RequestError as e:
|
||||
return self.create_text_message(f"Failed to add the new record, request error: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
return self.create_text_message(f"Failed to parse JSON response: {e}")
|
||||
except Exception as e:
|
||||
return self.create_text_message(f"Failed to add the new record, unexpected error: {e}")
|
@ -0,0 +1,78 @@
|
||||
identity:
|
||||
name: add_worksheet_record
|
||||
author: Ryan Tian
|
||||
label:
|
||||
en_US: Add Worksheet Record
|
||||
zh_Hans: 新增一条工作表记录
|
||||
description:
|
||||
human:
|
||||
en_US: Adds a new record to the specified worksheet
|
||||
zh_Hans: 向指定的工作表新增一条记录数据
|
||||
llm: A tool to append a new data entry into a specified worksheet.
|
||||
parameters:
|
||||
- name: appkey
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: App Key
|
||||
zh_Hans: App Key
|
||||
human_description:
|
||||
en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
|
||||
zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
|
||||
llm_description: the AppKey parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: sign
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Sign
|
||||
zh_Hans: Sign
|
||||
human_description:
|
||||
en_US: The Sign parameter for the HAP application
|
||||
zh_Hans: HAP 应用的 Sign 参数
|
||||
llm_description: the Sign parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: worksheet_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Worksheet ID
|
||||
zh_Hans: 工作表 ID
|
||||
human_description:
|
||||
en_US: The ID of the specified worksheet
|
||||
zh_Hans: 要获取字段信息的工作表 ID
|
||||
llm_description: The ID of the specified worksheet which to get the fields information.
|
||||
form: llm
|
||||
|
||||
- name: record_data
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Record Row Data
|
||||
zh_Hans: 记录数据
|
||||
human_description:
|
||||
en_US: The fields with data of the specified record
|
||||
zh_Hans: 要新增的记录数据,JSON 对象数组格式。数组元素属性:controlId-字段ID,value-字段值
|
||||
llm_description: |
|
||||
The fields with data of the specified record which to be created. It is in the format of an array of JSON objects, and the structure is defined as follows:
|
||||
```
|
||||
type RowData = {
|
||||
controlId: string; // Field ID to be updated
|
||||
value: string; // Field value to be updated
|
||||
}[];
|
||||
```
|
||||
form: llm
|
||||
|
||||
- name: host
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Host Address
|
||||
zh_Hans: 服务器地址
|
||||
human_description:
|
||||
en_US: The address for the privately deployed HAP server.
|
||||
zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
|
||||
llm_description: the address for the privately deployed HAP server.
|
||||
form: form
|
@ -0,0 +1,49 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class DeleteWorksheetRecordTool(BuiltinTool):
|
||||
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
appkey = tool_parameters.get('appkey', '')
|
||||
if not appkey:
|
||||
return self.create_text_message('Invalid parameter App Key')
|
||||
sign = tool_parameters.get('sign', '')
|
||||
if not sign:
|
||||
return self.create_text_message('Invalid parameter Sign')
|
||||
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||
if not worksheet_id:
|
||||
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||
row_id = tool_parameters.get('row_id', '')
|
||||
if not row_id:
|
||||
return self.create_text_message('Invalid parameter Record Row ID')
|
||||
|
||||
host = tool_parameters.get('host', '')
|
||||
if not host:
|
||||
host = 'https://api.mingdao.com'
|
||||
elif not host.startswith(("http://", "https://")):
|
||||
return self.create_text_message('Invalid parameter Host Address')
|
||||
else:
|
||||
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||
|
||||
url = f"{host}/v2/open/worksheet/deleteRow"
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "rowId": row_id}
|
||||
|
||||
try:
|
||||
res = httpx.post(url, headers=headers, json=payload, timeout=30)
|
||||
res.raise_for_status()
|
||||
res_json = res.json()
|
||||
if res_json.get('error_code') != 1:
|
||||
return self.create_text_message(f"Failed to delete the record. {res_json['error_msg']}")
|
||||
return self.create_text_message("Successfully deleted the record.")
|
||||
except httpx.RequestError as e:
|
||||
return self.create_text_message(f"Failed to delete the record, request error: {e}")
|
||||
except Exception as e:
|
||||
return self.create_text_message(f"Failed to delete the record, unexpected error: {e}")
|
@ -0,0 +1,71 @@
|
||||
identity:
|
||||
name: delete_worksheet_record
|
||||
author: Ryan Tian
|
||||
label:
|
||||
en_US: Delete Worksheet Record
|
||||
zh_Hans: 删除指定的一条工作表记录
|
||||
description:
|
||||
human:
|
||||
en_US: Deletes a single record from a worksheet based on the specified record row ID
|
||||
zh_Hans: 根据指定的记录ID删除一条工作表记录数据
|
||||
llm: A tool to remove a particular record from a worksheet by specifying its unique record identifier.
|
||||
parameters:
|
||||
- name: appkey
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: App Key
|
||||
zh_Hans: App Key
|
||||
human_description:
|
||||
en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
|
||||
zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
|
||||
llm_description: the AppKey parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: sign
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Sign
|
||||
zh_Hans: Sign
|
||||
human_description:
|
||||
en_US: The Sign parameter for the HAP application
|
||||
zh_Hans: HAP 应用的 Sign 参数
|
||||
llm_description: the Sign parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: worksheet_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Worksheet ID
|
||||
zh_Hans: 工作表 ID
|
||||
human_description:
|
||||
en_US: The ID of the specified worksheet
|
||||
zh_Hans: 要获取字段信息的工作表 ID
|
||||
llm_description: The ID of the specified worksheet which to get the fields information.
|
||||
form: llm
|
||||
|
||||
- name: row_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Record Row ID
|
||||
zh_Hans: 记录 ID
|
||||
human_description:
|
||||
en_US: The row ID of the specified record
|
||||
zh_Hans: 要删除的记录 ID
|
||||
llm_description: The row ID of the specified record which to be deleted.
|
||||
form: llm
|
||||
|
||||
- name: host
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Host Address
|
||||
zh_Hans: 服务器地址
|
||||
human_description:
|
||||
en_US: The address for the privately deployed HAP server.
|
||||
zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
|
||||
llm_description: the address for the privately deployed HAP server.
|
||||
form: form
|
@ -0,0 +1,148 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class GetWorksheetFieldsTool(BuiltinTool):
|
||||
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
appkey = tool_parameters.get('appkey', '')
|
||||
if not appkey:
|
||||
return self.create_text_message('Invalid parameter App Key')
|
||||
sign = tool_parameters.get('sign', '')
|
||||
if not sign:
|
||||
return self.create_text_message('Invalid parameter Sign')
|
||||
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||
if not worksheet_id:
|
||||
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||
|
||||
host = tool_parameters.get('host', '')
|
||||
if not host:
|
||||
host = 'https://api.mingdao.com'
|
||||
elif not host.startswith(("http://", "https://")):
|
||||
return self.create_text_message('Invalid parameter Host Address')
|
||||
else:
|
||||
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||
|
||||
url = f"{host}/v2/open/worksheet/getWorksheetInfo"
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
|
||||
|
||||
try:
|
||||
res = httpx.post(url, headers=headers, json=payload, timeout=60)
|
||||
res.raise_for_status()
|
||||
res_json = res.json()
|
||||
if res_json.get('error_code') != 1:
|
||||
return self.create_text_message(f"Failed to get the worksheet information. {res_json['error_msg']}")
|
||||
|
||||
fields_json, fields_table = self.get_controls(res_json['data']['controls'])
|
||||
result_type = tool_parameters.get('result_type', 'table')
|
||||
return self.create_text_message(
|
||||
text=json.dumps(fields_json, ensure_ascii=False) if result_type == 'json' else fields_table
|
||||
)
|
||||
except httpx.RequestError as e:
|
||||
return self.create_text_message(f"Failed to get the worksheet information, request error: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
return self.create_text_message(f"Failed to parse JSON response: {e}")
|
||||
except Exception as e:
|
||||
return self.create_text_message(f"Failed to get the worksheet information, unexpected error: {e}")
|
||||
|
||||
def get_field_type_by_id(self, field_type_id: int) -> str:
|
||||
field_type_map = {
|
||||
2: "Text",
|
||||
3: "Text-Phone",
|
||||
4: "Text-Phone",
|
||||
5: "Text-Email",
|
||||
6: "Number",
|
||||
7: "Text",
|
||||
8: "Number",
|
||||
9: "Option-Single Choice",
|
||||
10: "Option-Multiple Choices",
|
||||
11: "Option-Single Choice",
|
||||
15: "Date",
|
||||
16: "Date",
|
||||
24: "Option-Region",
|
||||
25: "Text",
|
||||
26: "Option-Member",
|
||||
27: "Option-Department",
|
||||
28: "Number",
|
||||
29: "Option-Linked Record",
|
||||
30: "Unknown Type",
|
||||
31: "Number",
|
||||
32: "Text",
|
||||
33: "Text",
|
||||
35: "Option-Linked Record",
|
||||
36: "Number-Yes1/No0",
|
||||
37: "Number",
|
||||
38: "Date",
|
||||
40: "Location",
|
||||
41: "Text",
|
||||
46: "Time",
|
||||
48: "Option-Organizational Role",
|
||||
50: "Text",
|
||||
51: "Query Record",
|
||||
}
|
||||
return field_type_map.get(field_type_id, '')
|
||||
|
||||
def get_controls(self, controls: list) -> dict:
|
||||
fields = []
|
||||
fields_list = ['|fieldId|fieldName|fieldType|fieldTypeId|description|options|','|'+'---|'*6]
|
||||
for control in controls:
|
||||
if control['type'] in self._get_ignore_types():
|
||||
continue
|
||||
field_type_id = control['type']
|
||||
field_type = self.get_field_type_by_id(control['type'])
|
||||
if field_type_id == 30:
|
||||
source_type = control['sourceControl']['type']
|
||||
if source_type in self._get_ignore_types():
|
||||
continue
|
||||
else:
|
||||
field_type_id = source_type
|
||||
field_type = self.get_field_type_by_id(source_type)
|
||||
field = {
|
||||
'id': control['controlId'],
|
||||
'name': control['controlName'],
|
||||
'type': field_type,
|
||||
'typeId': field_type_id,
|
||||
'description': control['remark'].replace('\n', ' ').replace('\t', ' '),
|
||||
'options': self._extract_options(control),
|
||||
}
|
||||
fields.append(field)
|
||||
fields_list.append(f"|{field['id']}|{field['name']}|{field['type']}|{field['typeId']}|{field['description']}|{field['options'] if field['options'] else ''}|")
|
||||
|
||||
fields.append({
|
||||
'id': 'ctime',
|
||||
'name': 'Created Time',
|
||||
'type': self.get_field_type_by_id(16),
|
||||
'typeId': 16,
|
||||
'description': '',
|
||||
'options': []
|
||||
})
|
||||
fields_list.append("|ctime|Created Time|Date|16|||")
|
||||
return fields, '\n'.join(fields_list)
|
||||
|
||||
def _extract_options(self, control: dict) -> list:
|
||||
options = []
|
||||
if control['type'] in [9, 10, 11]:
|
||||
options.extend([{"key": opt['key'], "value": opt['value']} for opt in control.get('options', [])])
|
||||
elif control['type'] in [28, 36]:
|
||||
itemnames = control['advancedSetting'].get('itemnames')
|
||||
if itemnames and itemnames.startswith('[{'):
|
||||
try:
|
||||
options = json.loads(itemnames)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
elif control['type'] == 30:
|
||||
source_type = control['sourceControl']['type']
|
||||
if source_type not in self._get_ignore_types():
|
||||
options.extend([{"key": opt['key'], "value": opt['value']} for opt in control.get('options', [])])
|
||||
return options
|
||||
|
||||
def _get_ignore_types(self):
|
||||
return {14, 21, 22, 34, 42, 43, 45, 47, 49, 10010}
|
@ -0,0 +1,80 @@
|
||||
identity:
|
||||
name: get_worksheet_fields
|
||||
author: Ryan Tian
|
||||
label:
|
||||
en_US: Get Worksheet Fields
|
||||
zh_Hans: 获取工作表字段结构
|
||||
description:
|
||||
human:
|
||||
en_US: Get fields information of the worksheet
|
||||
zh_Hans: 获取指定工作表的所有字段结构信息
|
||||
llm: A tool to get fields information of the specific worksheet.
|
||||
parameters:
|
||||
- name: appkey
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: App Key
|
||||
zh_Hans: App Key
|
||||
human_description:
|
||||
en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
|
||||
zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
|
||||
llm_description: the AppKey parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: sign
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Sign
|
||||
zh_Hans: Sign
|
||||
human_description:
|
||||
en_US: The Sign parameter for the HAP application
|
||||
zh_Hans: HAP 应用的 Sign 参数
|
||||
llm_description: the Sign parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: worksheet_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Worksheet ID
|
||||
zh_Hans: 工作表 ID
|
||||
human_description:
|
||||
en_US: The ID of the specified worksheet
|
||||
zh_Hans: 要获取字段信息的工作表 ID
|
||||
llm_description: The ID of the specified worksheet which to get the fields information.
|
||||
form: llm
|
||||
|
||||
- name: host
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Host Address
|
||||
zh_Hans: 服务器地址
|
||||
human_description:
|
||||
en_US: The address for the privately deployed HAP server.
|
||||
zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
|
||||
llm_description: the address for the privately deployed HAP server.
|
||||
form: form
|
||||
|
||||
- name: result_type
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: table
|
||||
label:
|
||||
en_US: table text
|
||||
zh_Hans: 表格文本
|
||||
- value: json
|
||||
label:
|
||||
en_US: json text
|
||||
zh_Hans: JSON文本
|
||||
default: table
|
||||
label:
|
||||
en_US: Result type
|
||||
zh_Hans: 结果类型
|
||||
human_description:
|
||||
en_US: used for selecting the result type, table styled text or json text
|
||||
zh_Hans: 用于选择结果类型,使用表格格式文本还是JSON格式文本
|
||||
form: form
|
@ -0,0 +1,130 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class GetWorksheetPivotDataTool(BuiltinTool):
|
||||
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
appkey = tool_parameters.get('appkey', '')
|
||||
if not appkey:
|
||||
return self.create_text_message('Invalid parameter App Key')
|
||||
sign = tool_parameters.get('sign', '')
|
||||
if not sign:
|
||||
return self.create_text_message('Invalid parameter Sign')
|
||||
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||
if not worksheet_id:
|
||||
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||
x_column_fields = tool_parameters.get('x_column_fields', '')
|
||||
if not x_column_fields or not x_column_fields.startswith('['):
|
||||
return self.create_text_message('Invalid parameter Column Fields')
|
||||
y_row_fields = tool_parameters.get('y_row_fields', '')
|
||||
if y_row_fields and not y_row_fields.strip().startswith('['):
|
||||
return self.create_text_message('Invalid parameter Row Fields')
|
||||
elif not y_row_fields:
|
||||
y_row_fields = '[]'
|
||||
value_fields = tool_parameters.get('value_fields', '')
|
||||
if not value_fields or not value_fields.strip().startswith('['):
|
||||
return self.create_text_message('Invalid parameter Value Fields')
|
||||
|
||||
host = tool_parameters.get('host', '')
|
||||
if not host:
|
||||
host = 'https://api.mingdao.com'
|
||||
elif not host.startswith(("http://", "https://")):
|
||||
return self.create_text_message('Invalid parameter Host Address')
|
||||
else:
|
||||
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||
|
||||
url = f"{host}/report/getPivotData"
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "options": {"showTotal": True}}
|
||||
|
||||
try:
|
||||
x_column_fields = json.loads(x_column_fields)
|
||||
payload['columns'] = x_column_fields
|
||||
y_row_fields = json.loads(y_row_fields)
|
||||
if y_row_fields: payload['rows'] = y_row_fields
|
||||
value_fields = json.loads(value_fields)
|
||||
payload['values'] = value_fields
|
||||
sort_fields = tool_parameters.get('sort_fields', '')
|
||||
if not sort_fields: sort_fields = '[]'
|
||||
sort_fields = json.loads(sort_fields)
|
||||
if sort_fields: payload['options']['sort'] = sort_fields
|
||||
res = httpx.post(url, headers=headers, json=payload, timeout=60)
|
||||
res.raise_for_status()
|
||||
res_json = res.json()
|
||||
if res_json.get('status') != 1:
|
||||
return self.create_text_message(f"Failed to get the worksheet pivot data. {res_json['msg']}")
|
||||
|
||||
pivot_json = self.generate_pivot_json(res_json['data'])
|
||||
pivot_table = self.generate_pivot_table(res_json['data'])
|
||||
result_type = tool_parameters.get('result_type', '')
|
||||
text = pivot_table if result_type == 'table' else json.dumps(pivot_json, ensure_ascii=False)
|
||||
return self.create_text_message(text)
|
||||
except httpx.RequestError as e:
|
||||
return self.create_text_message(f"Failed to get the worksheet pivot data, request error: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
return self.create_text_message(f"Failed to parse JSON response: {e}")
|
||||
except Exception as e:
|
||||
return self.create_text_message(f"Failed to get the worksheet pivot data, unexpected error: {e}")
|
||||
|
||||
def generate_pivot_table(self, data: dict[str, Any]) -> str:
|
||||
columns = data['metadata']['columns']
|
||||
rows = data['metadata']['rows']
|
||||
values = data['metadata']['values']
|
||||
|
||||
rows_data = data['data']
|
||||
|
||||
header = ([row['displayName'] for row in rows] if rows else []) + [column['displayName'] for column in columns] + [value['displayName'] for value in values]
|
||||
line = (['---'] * len(rows) if rows else []) + ['---'] * len(columns) + ['--:'] * len(values)
|
||||
|
||||
table = [header, line]
|
||||
for row in rows_data:
|
||||
row_data = [self.replace_pipe(row['rows'][r['controlId']]) for r in rows] if rows else []
|
||||
row_data.extend([self.replace_pipe(row['columns'][column['controlId']]) for column in columns])
|
||||
row_data.extend([self.replace_pipe(str(row['values'][value['controlId']])) for value in values])
|
||||
table.append(row_data)
|
||||
|
||||
return '\n'.join([('|'+'|'.join(row) +'|') for row in table])
|
||||
|
||||
def replace_pipe(self, text: str) -> str:
|
||||
return text.replace('|', '▏').replace('\n', ' ')
|
||||
|
||||
def generate_pivot_json(self, data: dict[str, Any]) -> dict:
|
||||
fields = {
|
||||
"x-axis": [
|
||||
{"fieldId": column["controlId"], "fieldName": column["displayName"]}
|
||||
for column in data["metadata"]["columns"]
|
||||
],
|
||||
"y-axis": [
|
||||
{"fieldId": row["controlId"], "fieldName": row["displayName"]}
|
||||
for row in data["metadata"]["rows"]
|
||||
] if data["metadata"]["rows"] else [],
|
||||
"values": [
|
||||
{"fieldId": value["controlId"], "fieldName": value["displayName"]}
|
||||
for value in data["metadata"]["values"]
|
||||
]
|
||||
}
|
||||
# fields = ([
|
||||
# {"fieldId": row["controlId"], "fieldName": row["displayName"]}
|
||||
# for row in data["metadata"]["rows"]
|
||||
# ] if data["metadata"]["rows"] else []) + [
|
||||
# {"fieldId": column["controlId"], "fieldName": column["displayName"]}
|
||||
# for column in data["metadata"]["columns"]
|
||||
# ] + [
|
||||
# {"fieldId": value["controlId"], "fieldName": value["displayName"]}
|
||||
# for value in data["metadata"]["values"]
|
||||
# ]
|
||||
rows = []
|
||||
for row in data["data"]:
|
||||
row_data = row["rows"] if row["rows"] else {}
|
||||
row_data.update(row["columns"])
|
||||
row_data.update(row["values"])
|
||||
rows.append(row_data)
|
||||
return {"fields": fields, "rows": rows, "summary": data["metadata"]["totalRow"]}
|
@ -0,0 +1,248 @@
|
||||
identity:
|
||||
name: get_worksheet_pivot_data
|
||||
author: Ryan Tian
|
||||
label:
|
||||
en_US: Get Worksheet Pivot Data
|
||||
zh_Hans: 获取工作表统计透视数据
|
||||
description:
|
||||
human:
|
||||
en_US: Retrieve statistical pivot table data from a specified worksheet
|
||||
zh_Hans: 从指定的工作表中检索统计透视表数据
|
||||
llm: A tool for extracting statistical pivot table data from a specific worksheet, providing summarized information for analysis and reporting purposes.
|
||||
parameters:
|
||||
- name: appkey
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: App Key
|
||||
zh_Hans: App Key
|
||||
human_description:
|
||||
en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
|
||||
zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
|
||||
llm_description: the AppKey parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: sign
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Sign
|
||||
zh_Hans: Sign
|
||||
human_description:
|
||||
en_US: The Sign parameter for the HAP application
|
||||
zh_Hans: HAP 应用的 Sign 参数
|
||||
llm_description: the Sign parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: worksheet_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Worksheet ID
|
||||
zh_Hans: 工作表 ID
|
||||
human_description:
|
||||
en_US: The ID of the specified worksheet
|
||||
zh_Hans: 要获取字段信息的工作表 ID
|
||||
llm_description: The ID of the specified worksheet which to get the fields information.
|
||||
form: llm
|
||||
|
||||
- name: x_column_fields
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Columns (X-axis)
|
||||
zh_Hans: 统计列字段(X轴)
|
||||
human_description:
|
||||
en_US: The column fields that make up the pivot table's X-axis groups or other dimensions for the X-axis in pivot charts
|
||||
zh_Hans: 组成透视表的统计列或者统计图表的X轴分组及X轴其它维度。JSON 对象数组格式,数组元素属性:controlId-列ID,displayName-显示名称,particleSize(可选)-字段类型是日期或者地区时,通过此参数设置统计维度(日期时间:1-日,2-周,3-月;地区:1-全国,2-省,3-市)
|
||||
llm_description: |
|
||||
This parameter allows you to specify the columns that make up the pivot table's X-axis groups or other dimensions for the X-axis in pivot charts. It is formatted as a JSON array, with its structure defined as follows:
|
||||
```
|
||||
type XColumnFields = { // X-axis or column object array
|
||||
controlId: string; // fieldId
|
||||
displayName: string; // displayName
|
||||
particleSize?: number; // field type is date or area, set the statistical dimension (date time: 1-day, 2-week, 3-month; area: 1-nation, 2-province, 3-city)
|
||||
}[];
|
||||
```
|
||||
form: llm
|
||||
|
||||
- name: y_row_fields
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Rows (Y-axis)
|
||||
zh_Hans: 统计行字段(Y轴)
|
||||
human_description:
|
||||
en_US: The row fields that make up the pivot table's Y-axis groups or other dimensions for the Y-axis in pivot charts
|
||||
zh_Hans: 组成透视表的统计行或者统计图表的Y轴分组及Y轴其它维度。JSON 对象数组格式,数组元素属性:controlId-列ID,displayName-显示名称,particleSize(可选)-字段类型是日期或者地区时,通过此参数设置统计维度(日期时间:1-日,2-周,3-月;地区:1-全国,2-省,3-市)
|
||||
llm_description: |
|
||||
This parameter allows you to specify the rows that make up the pivot table's Y-axis groups or other dimensions for the Y-axis in pivot charts. It is formatted as a JSON array, with its structure defined as follows:
|
||||
```
|
||||
type YRowFields = { // Y-axis or row object array
|
||||
controlId: string; // fieldId
|
||||
displayName: string; // displayName
|
||||
particleSize?: number; // field type is date or area, set the statistical dimension (date time: 1-day, 2-week, 3-month; area: 1-nation, 2-province, 3-city)
|
||||
}[];
|
||||
```
|
||||
form: llm
|
||||
|
||||
- name: value_fields
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Aggregated Values
|
||||
zh_Hans: 统计值字段
|
||||
human_description:
|
||||
en_US: The aggregated value fields in the pivot table
|
||||
zh_Hans: 透视表中经过聚合计算后的统计值字段。JSON 对象数组格式,数组元素属性:controlId-列ID,displayName-显示名称,aggregation-聚合方式(SUM,AVG,MIN,MAX,COUNT)
|
||||
llm_description: |
|
||||
This parameter allows you to specify the aggregated value fields in the pivot table. It is formatted as a JSON array, with its structure defined as follows:
|
||||
```
|
||||
type ValueFields = { // aggregated value object array
|
||||
controlId: string; // fieldId
|
||||
displayName: string; // displayName
|
||||
aggregation: string; // aggregation method, e.g.: SUM, AVG, MIN, MAX, COUNT
|
||||
}[];
|
||||
```
|
||||
form: llm
|
||||
|
||||
- name: filters
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Filter Set
|
||||
zh_Hans: 筛选器组合
|
||||
human_description:
|
||||
en_US: A combination of filters applied to query records, formatted as a JSON array. See the application's API documentation for details on its structure and usage.
|
||||
zh_Hans: 查询记录的筛选条件组合,格式为 JSON 数组,可以从应用 API 文档中了解参数结构详情
|
||||
llm_description: |
|
||||
This parameter allows you to specify a set of conditions that records must meet to be included in the result set. It is formatted as a JSON array, with its structure defined as follows:
|
||||
```
|
||||
type Filters = { // filter object array
|
||||
controlId: string; // fieldId
|
||||
dataType: number; // fieldTypeId
|
||||
spliceType: number; // condition concatenation method, 1: And, 2: Or
|
||||
filterType: number; // expression type, refer to the <FilterTypeEnum Reference> for enumerable values
|
||||
values?: string[]; // values in the condition, for option-type fields, multiple values can be passed
|
||||
value?: string; // value in the condition, a single value can be passed according to the field type
|
||||
dateRange?: number; // date range, mandatory when filterType is 17 or 18, refer to the <DateRangeEnum Reference> for enumerable values
|
||||
minValue?: string; // minimum value for custom range
|
||||
maxValue?: string; // maximum value for custom range
|
||||
isAsc?: boolean; // ascending order, false: descending, true: ascending
|
||||
}[];
|
||||
```
|
||||
For option-type fields, if this option field has `options`, then you need to get the corresponding `key` value from the `options` in the current field information via `value`, and pass it into `values` in array format. Do not use the `options` value of other fields as input conditions.
|
||||
|
||||
### FilterTypeEnum Reference
|
||||
```
|
||||
Enum Value, Enum Character, Description
|
||||
1, Like, Contains
|
||||
2, Eq, Is (Equal)
|
||||
3, Start, Starts With
|
||||
4, End, Ends With
|
||||
5, NotLike, Does Not Contain
|
||||
6, Ne, Is Not (Not Equal)
|
||||
7, IsEmpty, Empty
|
||||
8, HasValue, Not Empty
|
||||
11, Between, Within Range
|
||||
12, NotBetween, Outside Range
|
||||
13, Gt, Greater Than
|
||||
14, Gte, Greater Than or Equal To
|
||||
15, Lt, Less Than
|
||||
16, Lte, Less Than or Equal To
|
||||
17, DateEnum, Date Is
|
||||
18, NotDateEnum, Date Is Not
|
||||
21, MySelf, Owned by Me
|
||||
22, UnRead, Unread
|
||||
23, Sub, Owned by Subordinate
|
||||
24, RCEq, Associated Field Is
|
||||
25, RCNe, Associated Field Is Not
|
||||
26, ArrEq, Array Equals
|
||||
27, ArrNe, Array Does Not Equal
|
||||
31, DateBetween, Date Within Range (can only be used with minValue and maxValue)
|
||||
32, DateNotBetween, Date Not Within Range (can only be used with minValue and maxValue)
|
||||
33, DateGt, Date Later Than
|
||||
34, DateGte, Date Later Than or Equal To
|
||||
35, DateLt, Date Earlier Than
|
||||
36, DateLte, Date Earlier Than or Equal To
|
||||
```
|
||||
|
||||
### DateRangeEnum Reference
|
||||
```
|
||||
Enum Value, Enum Character, Description
|
||||
1, Today, Today
|
||||
2, Yesterday, Yesterday
|
||||
3, Tomorrow, Tomorrow
|
||||
4, ThisWeek, This Week
|
||||
5, LastWeek, Last Week
|
||||
6, NextWeek, Next Week
|
||||
7, ThisMonth, This Month
|
||||
8, LastMonth, Last Month
|
||||
9, NextMonth, Next Month
|
||||
12, ThisQuarter, This Quarter
|
||||
13, LastQuarter, Last Quarter
|
||||
14, NextQuarter, Next Quarter
|
||||
15, ThisYear, This Year
|
||||
16, LastYear, Last Year
|
||||
17, NextYear, Next Year
|
||||
18, Customize, Custom
|
||||
21, Last7Day, Past 7 Days
|
||||
22, Last14Day, Past 14 Days
|
||||
23, Last30Day, Past 30 Days
|
||||
31, Next7Day, Next 7 Days
|
||||
32, Next14Day, Next 14 Days
|
||||
33, Next33Day, Next 33 Days
|
||||
```
|
||||
form: llm
|
||||
|
||||
- name: sort_fields
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Sort Fields
|
||||
zh_Hans: 排序字段
|
||||
human_description:
|
||||
en_US: The fields to used for sorting
|
||||
zh_Hans: 用于确定排序的字段,不超过3个
|
||||
llm_description: |
|
||||
This optional parameter specifies the unique identifier of the fields that will be used to sort the results. It is in the format of an array of JSON objects, and its structure is defined as follows:
|
||||
```
|
||||
type SortByFields = {
|
||||
controlId: string; // Field ID used for sorting
|
||||
isAsc: boolean; // Sorting direction, true indicates ascending order, false indicates descending order
|
||||
}[];
|
||||
```
|
||||
form: llm
|
||||
|
||||
- name: host
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Host Address
|
||||
zh_Hans: 服务器地址
|
||||
human_description:
|
||||
en_US: The address for the privately deployed HAP server.
|
||||
zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
|
||||
llm_description: the address for the privately deployed HAP server.
|
||||
form: form
|
||||
|
||||
- name: result_type
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: table
|
||||
label:
|
||||
en_US: table text
|
||||
zh_Hans: 表格文本
|
||||
- value: json
|
||||
label:
|
||||
en_US: json text
|
||||
zh_Hans: JSON文本
|
||||
default: table
|
||||
label:
|
||||
en_US: Result type
|
||||
zh_Hans: 结果类型
|
||||
human_description:
|
||||
en_US: used for selecting the result type, table styled text or json text
|
||||
zh_Hans: 用于选择结果类型,使用表格格式文本还是JSON格式文本
|
||||
form: form
|
@ -0,0 +1,209 @@
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class ListWorksheetRecordsTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
appkey = tool_parameters.get('appkey', '')
|
||||
if not appkey:
|
||||
return self.create_text_message('Invalid parameter App Key')
|
||||
|
||||
sign = tool_parameters.get('sign', '')
|
||||
if not sign:
|
||||
return self.create_text_message('Invalid parameter Sign')
|
||||
|
||||
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||
if not worksheet_id:
|
||||
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||
|
||||
host = tool_parameters.get('host', '')
|
||||
if not host:
|
||||
host = 'https://api.mingdao.com'
|
||||
elif not (host.startswith("http://") or host.startswith("https://")):
|
||||
return self.create_text_message('Invalid parameter Host Address')
|
||||
else:
|
||||
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||
|
||||
url_fields = f"{host}/v2/open/worksheet/getWorksheetInfo"
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
|
||||
|
||||
field_ids = tool_parameters.get('field_ids', '')
|
||||
|
||||
try:
|
||||
res = httpx.post(url_fields, headers=headers, json=payload, timeout=30)
|
||||
res_json = res.json()
|
||||
if res.is_success:
|
||||
if res_json['error_code'] != 1:
|
||||
return self.create_text_message("Failed to get the worksheet information. {}".format(res_json['error_msg']))
|
||||
else:
|
||||
worksheet_name = res_json['data']['name']
|
||||
fields, schema, table_header = self.get_schema(res_json['data']['controls'], field_ids)
|
||||
else:
|
||||
return self.create_text_message(
|
||||
f"Failed to get the worksheet information, status code: {res.status_code}, response: {res.text}")
|
||||
except Exception as e:
|
||||
return self.create_text_message("Failed to get the worksheet information, something went wrong: {}".format(e))
|
||||
|
||||
if field_ids:
|
||||
payload['controls'] = [v.strip() for v in field_ids.split(',')] if field_ids else []
|
||||
filters = tool_parameters.get('filters', '')
|
||||
if filters:
|
||||
payload['filters'] = json.loads(filters)
|
||||
sort_id = tool_parameters.get('sort_id', '')
|
||||
sort_is_asc = tool_parameters.get('sort_is_asc', False)
|
||||
if sort_id:
|
||||
payload['sortId'] = sort_id
|
||||
payload['isAsc'] = sort_is_asc
|
||||
limit = tool_parameters.get('limit', 50)
|
||||
payload['pageSize'] = limit
|
||||
page_index = tool_parameters.get('page_index', 1)
|
||||
payload['pageIndex'] = page_index
|
||||
payload['useControlId'] = True
|
||||
payload['listType'] = 1
|
||||
|
||||
url = f"{host}/v2/open/worksheet/getFilterRows"
|
||||
try:
|
||||
res = httpx.post(url, headers=headers, json=payload, timeout=90)
|
||||
res_json = res.json()
|
||||
if res.is_success:
|
||||
if res_json['error_code'] != 1:
|
||||
return self.create_text_message("Failed to get the records. {}".format(res_json['error_msg']))
|
||||
else:
|
||||
result = {
|
||||
"fields": fields,
|
||||
"rows": [],
|
||||
"total": res_json.get("data", {}).get("total"),
|
||||
"payload": {key: payload[key] for key in ['worksheetId', 'controls', 'filters', 'sortId', 'isAsc', 'pageSize', 'pageIndex'] if key in payload}
|
||||
}
|
||||
rows = res_json.get("data", {}).get("rows", [])
|
||||
result_type = tool_parameters.get('result_type', '')
|
||||
if not result_type: result_type = 'table'
|
||||
if result_type == 'json':
|
||||
for row in rows:
|
||||
result['rows'].append(self.get_row_field_value(row, schema))
|
||||
return self.create_text_message(json.dumps(result, ensure_ascii=False))
|
||||
else:
|
||||
result_text = f"Found {result['total']} rows in worksheet \"{worksheet_name}\"."
|
||||
if result['total'] > 0:
|
||||
result_text += f" The following are {result['total'] if result['total'] < limit else limit} pieces of data presented in a table format:\n\n{table_header}"
|
||||
for row in rows:
|
||||
result_values = []
|
||||
for f in fields:
|
||||
result_values.append(self.handle_value_type(row[f['fieldId']], schema[f['fieldId']]))
|
||||
result_text += '\n|'+'|'.join(result_values)+'|'
|
||||
return self.create_text_message(result_text)
|
||||
else:
|
||||
return self.create_text_message(
|
||||
f"Failed to get the records, status code: {res.status_code}, response: {res.text}")
|
||||
except Exception as e:
|
||||
return self.create_text_message("Failed to get the records, something went wrong: {}".format(e))
|
||||
|
||||
|
||||
def get_row_field_value(self, row: dict, schema: dict):
|
||||
row_value = {"rowid": row["rowid"]}
|
||||
for field in schema:
|
||||
row_value[field] = self.handle_value_type(row[field], schema[field])
|
||||
return row_value
|
||||
|
||||
|
||||
def get_schema(self, controls: list, fieldids: str):
|
||||
allow_fields = {v.strip() for v in fieldids.split(',')} if fieldids else set()
|
||||
fields = []
|
||||
schema = {}
|
||||
field_names = []
|
||||
for control in controls:
|
||||
control_type_id = self.get_real_type_id(control)
|
||||
if (control_type_id in self._get_ignore_types()) or (allow_fields and not control['controlId'] in allow_fields):
|
||||
continue
|
||||
else:
|
||||
fields.append({'fieldId': control['controlId'], 'fieldName': control['controlName']})
|
||||
schema[control['controlId']] = {'typeId': control_type_id, 'options': self.set_option(control)}
|
||||
field_names.append(control['controlName'])
|
||||
if (not allow_fields or ('ctime' in allow_fields)):
|
||||
fields.append({'fieldId': 'ctime', 'fieldName': 'Created Time'})
|
||||
schema['ctime'] = {'typeId': 16, 'options': {}}
|
||||
field_names.append("Created Time")
|
||||
fields.append({'fieldId':'rowid', 'fieldName': 'Record Row ID'})
|
||||
schema['rowid'] = {'typeId': 2, 'options': {}}
|
||||
field_names.append("Record Row ID")
|
||||
return fields, schema, '|'+'|'.join(field_names)+'|\n|'+'---|'*len(field_names)
|
||||
|
||||
def get_real_type_id(self, control: dict) -> int:
|
||||
return control['sourceControlType'] if control['type'] == 30 else control['type']
|
||||
|
||||
def set_option(self, control: dict) -> dict:
|
||||
options = {}
|
||||
if control.get('options'):
|
||||
options = {option['key']: option['value'] for option in control['options']}
|
||||
elif control.get('advancedSetting', {}).get('itemnames'):
|
||||
try:
|
||||
itemnames = json.loads(control['advancedSetting']['itemnames'])
|
||||
options = {item['key']: item['value'] for item in itemnames}
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return options
|
||||
|
||||
def _get_ignore_types(self):
|
||||
return {14, 21, 22, 34, 42, 43, 45, 47, 49, 10010}
|
||||
|
||||
def handle_value_type(self, value, field):
|
||||
type_id = field.get("typeId")
|
||||
if type_id == 10:
|
||||
value = value if isinstance(value, str) else "、".join(value)
|
||||
elif type_id in [28, 36]:
|
||||
value = field.get("options", {}).get(value, value)
|
||||
elif type_id in [26, 27, 48, 14]:
|
||||
value = self.process_value(value)
|
||||
elif type_id in [35, 29]:
|
||||
value = self.parse_cascade_or_associated(field, value)
|
||||
elif type_id == 40:
|
||||
value = self.parse_location(value)
|
||||
return self.rich_text_to_plain_text(value) if value else ''
|
||||
|
||||
def process_value(self, value):
|
||||
if isinstance(value, str):
|
||||
if value.startswith("[{\"accountId\""):
|
||||
value = json.loads(value)
|
||||
value = ', '.join([item['fullname'] for item in value])
|
||||
elif value.startswith("[{\"departmentId\""):
|
||||
value = json.loads(value)
|
||||
value = '、'.join([item['departmentName'] for item in value])
|
||||
elif value.startswith("[{\"organizeId\""):
|
||||
value = json.loads(value)
|
||||
value = '、'.join([item['organizeName'] for item in value])
|
||||
elif value.startswith("[{\"file_id\""):
|
||||
value = ''
|
||||
elif value == '[]':
|
||||
value = ''
|
||||
elif hasattr(value, 'accountId'):
|
||||
value = value['fullname']
|
||||
return value
|
||||
|
||||
def parse_cascade_or_associated(self, field, value):
|
||||
if (field['typeId'] == 35 and value.startswith('[')) or (field['typeId'] == 29 and value.startswith('[{')):
|
||||
value = json.loads(value)
|
||||
value = value[0]['name'] if len(value) > 0 else ''
|
||||
else:
|
||||
value = ''
|
||||
return value
|
||||
|
||||
def parse_location(self, value):
|
||||
if len(value) > 10:
|
||||
parsed_value = json.loads(value)
|
||||
value = parsed_value.get("address", "")
|
||||
else:
|
||||
value = ""
|
||||
return value
|
||||
|
||||
def rich_text_to_plain_text(self, rich_text):
|
||||
text = re.sub(r'<[^>]+>', '', rich_text) if '<' in rich_text else rich_text
|
||||
return text.replace("|", "▏").replace("\n", " ")
|
@ -0,0 +1,226 @@
|
||||
identity:
|
||||
name: list_worksheet_records
|
||||
author: Ryan Tian
|
||||
label:
|
||||
en_US: List Worksheet Records
|
||||
zh_Hans: 查询工作表记录数据
|
||||
description:
|
||||
human:
|
||||
en_US: List records from the worksheet
|
||||
zh_Hans: 查询工作表的记录列表数据,一次最多1000行,可分页获取
|
||||
llm: A tool to retrieve record data from the specific worksheet.
|
||||
parameters:
|
||||
- name: appkey
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: App Key
|
||||
zh_Hans: App Key
|
||||
human_description:
|
||||
en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
|
||||
zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
|
||||
llm_description: the AppKey parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: sign
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Sign
|
||||
zh_Hans: Sign
|
||||
human_description:
|
||||
en_US: The Sign parameter for the HAP application
|
||||
zh_Hans: HAP 应用的 Sign 参数
|
||||
llm_description: the Sign parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: worksheet_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Worksheet ID
|
||||
zh_Hans: 工作表 ID
|
||||
human_description:
|
||||
en_US: The ID of the worksheet from which to retrieve record data
|
||||
zh_Hans: 要获取记录数据的工作表 ID
|
||||
llm_description: This parameter specifies the ID of the worksheet where the records are stored.
|
||||
form: llm
|
||||
|
||||
- name: field_ids
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Field IDs
|
||||
zh_Hans: 字段 ID 列表
|
||||
human_description:
|
||||
en_US: A comma-separated list of field IDs whose data to retrieve. If not provided, all fields' data will be fetched
|
||||
zh_Hans: 要获取记录数据的字段 ID,多个 ID 间用英文逗号隔开,不传此参数则将获取所有字段的数据
|
||||
llm_description: This optional parameter lets you specify a comma-separated list of field IDs. Unless the user explicitly requests to output the specified field in the question, this parameter should usually be omitted. If this parameter is omitted, the API will return data for all fields by default. When provided, only the data associated with these fields will be included in the response.
|
||||
form: llm
|
||||
|
||||
- name: filters
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Filter Set
|
||||
zh_Hans: 筛选器组合
|
||||
human_description:
|
||||
en_US: A combination of filters applied to query records, formatted as a JSON array. See the application's API documentation for details on its structure and usage.
|
||||
zh_Hans: 查询记录的筛选条件组合,格式为 JSON 数组,可以从应用 API 文档中了解参数结构详情
|
||||
llm_description: |
|
||||
This parameter allows you to specify a set of conditions that records must meet to be included in the result set. It is formatted as a JSON array, with its structure defined as follows:
|
||||
```
|
||||
type Filters = { // filter object array
|
||||
controlId: string; // fieldId
|
||||
dataType: number; // fieldTypeId
|
||||
spliceType: number; // condition concatenation method, 1: And, 2: Or
|
||||
filterType: number; // expression type, refer to the <FilterTypeEnum Reference> for enumerable values
|
||||
values?: string[]; // values in the condition, for option-type fields, multiple values can be passed
|
||||
value?: string; // value in the condition, a single value can be passed according to the field type
|
||||
dateRange?: number; // date range, mandatory when filterType is 17 or 18, refer to the <DateRangeEnum Reference> for enumerable values
|
||||
minValue?: string; // minimum value for custom range
|
||||
maxValue?: string; // maximum value for custom range
|
||||
isAsc?: boolean; // ascending order, false: descending, true: ascending
|
||||
}[];
|
||||
```
|
||||
For option-type fields, if this option field has `options`, then you need to get the corresponding `key` value from the `options` in the current field information via `value`, and pass it into `values` in array format. Do not use the `options` value of other fields as input conditions.
|
||||
|
||||
### FilterTypeEnum Reference
|
||||
```
|
||||
Enum Value, Enum Character, Description
|
||||
1, Like, Contains(Include)
|
||||
2, Eq, Is (Equal)
|
||||
3, Start, Starts With
|
||||
4, End, Ends With
|
||||
5, NotLike, Does Not Contain(Not Include)
|
||||
6, Ne, Is Not (Not Equal)
|
||||
7, IsEmpty, Empty
|
||||
8, HasValue, Not Empty
|
||||
11, Between, Within Range(Belong to)
|
||||
12, NotBetween, Outside Range(Not belong to)
|
||||
13, Gt, Greater Than
|
||||
14, Gte, Greater Than or Equal To
|
||||
15, Lt, Less Than
|
||||
16, Lte, Less Than or Equal To
|
||||
17, DateEnum, Date Is
|
||||
18, NotDateEnum, Date Is Not
|
||||
24, RCEq, Associated Field Is
|
||||
25, RCNe, Associated Field Is Not
|
||||
26, ArrEq, Array Equals
|
||||
27, ArrNe, Array Does Not Equal
|
||||
31, DateBetween, Date Within Range (can only be used with minValue and maxValue)
|
||||
32, DateNotBetween, Date Not Within Range (can only be used with minValue and maxValue)
|
||||
33, DateGt, Date Later Than
|
||||
34, DateGte, Date Later Than or Equal To
|
||||
35, DateLt, Date Earlier Than
|
||||
36, DateLte, Date Earlier Than or Equal To
|
||||
```
|
||||
|
||||
### DateRangeEnum Reference
|
||||
```
|
||||
Enum Value, Enum Character, Description
|
||||
1, Today, Today
|
||||
2, Yesterday, Yesterday
|
||||
3, Tomorrow, Tomorrow
|
||||
4, ThisWeek, This Week
|
||||
5, LastWeek, Last Week
|
||||
6, NextWeek, Next Week
|
||||
7, ThisMonth, This Month
|
||||
8, LastMonth, Last Month
|
||||
9, NextMonth, Next Month
|
||||
12, ThisQuarter, This Quarter
|
||||
13, LastQuarter, Last Quarter
|
||||
14, NextQuarter, Next Quarter
|
||||
15, ThisYear, This Year
|
||||
16, LastYear, Last Year
|
||||
17, NextYear, Next Year
|
||||
18, Customize, Custom
|
||||
21, Last7Day, Past 7 Days
|
||||
22, Last14Day, Past 14 Days
|
||||
23, Last30Day, Past 30 Days
|
||||
31, Next7Day, Next 7 Days
|
||||
32, Next14Day, Next 14 Days
|
||||
33, Next33Day, Next 33 Days
|
||||
```
|
||||
form: llm
|
||||
|
||||
- name: sort_id
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Sort Field ID
|
||||
zh_Hans: 排序字段 ID
|
||||
human_description:
|
||||
en_US: The ID of the field used for sorting
|
||||
zh_Hans: 用以排序的字段 ID
|
||||
llm_description: This optional parameter specifies the unique identifier of the field that will be used to sort the results. It should be set to the ID of an existing field within your data structure.
|
||||
form: llm
|
||||
|
||||
- name: sort_is_asc
|
||||
type: boolean
|
||||
required: false
|
||||
label:
|
||||
en_US: Ascending Order
|
||||
zh_Hans: 是否升序排列
|
||||
human_description:
|
||||
en_US: Determines whether the sorting is in ascending (true) or descending (false) order
|
||||
zh_Hans: 排序字段的排序方式:true-升序,false-降序
|
||||
llm_description: This optional parameter controls the direction of the sort. If set to true, the results will be sorted in ascending order; if false, they will be sorted in descending order.
|
||||
form: llm
|
||||
|
||||
- name: limit
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Record Limit
|
||||
zh_Hans: 记录数量限制
|
||||
human_description:
|
||||
en_US: The maximum number of records to retrieve
|
||||
zh_Hans: 要获取的记录数量限制条数
|
||||
llm_description: This optional parameter allows you to specify the maximum number of records that should be returned in the result set. When retrieving paginated record data, this parameter indicates the number of rows to fetch per page, and must be used in conjunction with the `page_index` parameter.
|
||||
form: llm
|
||||
|
||||
- name: page_index
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Page Index
|
||||
zh_Hans: 页码
|
||||
human_description:
|
||||
en_US: The page number when paginating through a list of records
|
||||
zh_Hans: 分页读取记录列表时的页码
|
||||
llm_description: This parameter is used when you need to paginate through a large set of records. The default value is 1, which refers to the first page. When it is used, the meaning of the `limit` parameter becomes the number of records per page.
|
||||
form: llm
|
||||
|
||||
- name: host
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Host Address
|
||||
zh_Hans: 服务器地址
|
||||
human_description:
|
||||
en_US: The address for the privately deployed HAP server.
|
||||
zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
|
||||
llm_description: the address for the privately deployed HAP server.
|
||||
form: form
|
||||
|
||||
- name: result_type
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: table
|
||||
label:
|
||||
en_US: table text
|
||||
zh_Hans: 表格文本
|
||||
- value: json
|
||||
label:
|
||||
en_US: json text
|
||||
zh_Hans: JSON文本
|
||||
default: table
|
||||
label:
|
||||
en_US: Result type
|
||||
zh_Hans: 结果类型
|
||||
human_description:
|
||||
en_US: used for selecting the result type, table styled text or json text
|
||||
zh_Hans: 用于选择结果类型,使用表格格式文本还是JSON格式文本
|
||||
form: form
|
82
api/core/tools/provider/builtin/hap/tools/list_worksheets.py
Normal file
82
api/core/tools/provider/builtin/hap/tools/list_worksheets.py
Normal file
@ -0,0 +1,82 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class ListWorksheetsTool(BuiltinTool):
|
||||
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
appkey = tool_parameters.get('appkey', '')
|
||||
if not appkey:
|
||||
return self.create_text_message('Invalid parameter App Key')
|
||||
sign = tool_parameters.get('sign', '')
|
||||
if not sign:
|
||||
return self.create_text_message('Invalid parameter Sign')
|
||||
|
||||
host = tool_parameters.get('host', '')
|
||||
if not host:
|
||||
host = 'https://api.mingdao.com'
|
||||
elif not (host.startswith("http://") or host.startswith("https://")):
|
||||
return self.create_text_message('Invalid parameter Host Address')
|
||||
else:
|
||||
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||
url = f"{host}/v1/open/app/get"
|
||||
|
||||
result_type = tool_parameters.get('result_type', '')
|
||||
if not result_type:
|
||||
result_type = 'table'
|
||||
|
||||
headers = { 'Content-Type': 'application/json' }
|
||||
params = { "appKey": appkey, "sign": sign, }
|
||||
try:
|
||||
res = httpx.get(url, headers=headers, params=params, timeout=30)
|
||||
res_json = res.json()
|
||||
if res.is_success:
|
||||
if res_json['error_code'] != 1:
|
||||
return self.create_text_message("Failed to access the application. {}".format(res_json['error_msg']))
|
||||
else:
|
||||
if result_type == 'json':
|
||||
worksheets = []
|
||||
for section in res_json['data']['sections']:
|
||||
worksheets.extend(self._extract_worksheets(section, result_type))
|
||||
return self.create_text_message(text=json.dumps(worksheets, ensure_ascii=False))
|
||||
else:
|
||||
worksheets = '|worksheetId|worksheetName|description|\n|---|---|---|'
|
||||
for section in res_json['data']['sections']:
|
||||
worksheets += self._extract_worksheets(section, result_type)
|
||||
return self.create_text_message(worksheets)
|
||||
|
||||
else:
|
||||
return self.create_text_message(
|
||||
f"Failed to list worksheets, status code: {res.status_code}, response: {res.text}")
|
||||
except Exception as e:
|
||||
return self.create_text_message("Failed to list worksheets, something went wrong: {}".format(e))
|
||||
|
||||
def _extract_worksheets(self, section, type):
|
||||
items = []
|
||||
tables = ''
|
||||
for item in section.get('items', []):
|
||||
if item.get('type') == 0 and (not 'notes' in item or item.get('notes') != 'NO'):
|
||||
if type == 'json':
|
||||
filtered_item = {
|
||||
'id': item['id'],
|
||||
'name': item['name'],
|
||||
'notes': item.get('notes', '')
|
||||
}
|
||||
items.append(filtered_item)
|
||||
else:
|
||||
tables += f"\n|{item['id']}|{item['name']}|{item.get('notes', '')}|"
|
||||
|
||||
for child_section in section.get('childSections', []):
|
||||
if type == 'json':
|
||||
items.extend(self._extract_worksheets(child_section, 'json'))
|
||||
else:
|
||||
tables += self._extract_worksheets(child_section, 'table')
|
||||
|
||||
return items if type == 'json' else tables
|
@ -0,0 +1,68 @@
|
||||
identity:
|
||||
name: list_worksheets
|
||||
author: Ryan Tian
|
||||
label:
|
||||
en_US: List Worksheets
|
||||
zh_Hans: 获取应用下所有工作表
|
||||
description:
|
||||
human:
|
||||
en_US: List worksheets within an application
|
||||
zh_Hans: 获取应用下的所有工作表和说明信息
|
||||
llm: A tool to list worksheets info within an application, imported parameter is AppKey and Sign of the application.
|
||||
parameters:
|
||||
- name: appkey
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: App Key
|
||||
zh_Hans: App Key
|
||||
human_description:
|
||||
en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
|
||||
zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
|
||||
llm_description: the AppKey parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: sign
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Sign
|
||||
zh_Hans: Sign
|
||||
human_description:
|
||||
en_US: The Sign parameter for the HAP application
|
||||
zh_Hans: HAP 应用的 Sign 参数
|
||||
llm_description: the Sign parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: host
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Host Address
|
||||
zh_Hans: 服务器地址
|
||||
human_description:
|
||||
en_US: The address for the privately deployed HAP server.
|
||||
zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
|
||||
llm_description: the address for the privately deployed HAP server.
|
||||
form: form
|
||||
|
||||
- name: result_type
|
||||
type: select
|
||||
required: true
|
||||
options:
|
||||
- value: table
|
||||
label:
|
||||
en_US: table text
|
||||
zh_Hans: 表格文本
|
||||
- value: json
|
||||
label:
|
||||
en_US: json text
|
||||
zh_Hans: JSON文本
|
||||
default: table
|
||||
label:
|
||||
en_US: Result type
|
||||
zh_Hans: 结果类型
|
||||
human_description:
|
||||
en_US: used for selecting the result type, table styled text or json text
|
||||
zh_Hans: 用于选择结果类型,使用表格格式文本还是JSON格式文本
|
||||
form: form
|
@ -0,0 +1,56 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class UpdateWorksheetRecordTool(BuiltinTool):
|
||||
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
|
||||
appkey = tool_parameters.get('appkey', '')
|
||||
if not appkey:
|
||||
return self.create_text_message('Invalid parameter App Key')
|
||||
sign = tool_parameters.get('sign', '')
|
||||
if not sign:
|
||||
return self.create_text_message('Invalid parameter Sign')
|
||||
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||
if not worksheet_id:
|
||||
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||
row_id = tool_parameters.get('row_id', '')
|
||||
if not row_id:
|
||||
return self.create_text_message('Invalid parameter Record Row ID')
|
||||
record_data = tool_parameters.get('record_data', '')
|
||||
if not record_data:
|
||||
return self.create_text_message('Invalid parameter Record Row Data')
|
||||
|
||||
host = tool_parameters.get('host', '')
|
||||
if not host:
|
||||
host = 'https://api.mingdao.com'
|
||||
elif not host.startswith(("http://", "https://")):
|
||||
return self.create_text_message('Invalid parameter Host Address')
|
||||
else:
|
||||
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||
|
||||
url = f"{host}/v2/open/worksheet/editRow"
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "rowId": row_id}
|
||||
|
||||
try:
|
||||
payload['controls'] = json.loads(record_data)
|
||||
res = httpx.post(url, headers=headers, json=payload, timeout=60)
|
||||
res.raise_for_status()
|
||||
res_json = res.json()
|
||||
if res_json.get('error_code') != 1:
|
||||
return self.create_text_message(f"Failed to update the record. {res_json['error_msg']}")
|
||||
return self.create_text_message("Record updated successfully.")
|
||||
except httpx.RequestError as e:
|
||||
return self.create_text_message(f"Failed to update the record, request error: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
return self.create_text_message(f"Failed to parse JSON response: {e}")
|
||||
except Exception as e:
|
||||
return self.create_text_message(f"Failed to update the record, unexpected error: {e}")
|
@ -0,0 +1,90 @@
|
||||
identity:
|
||||
name: update_worksheet_record
|
||||
author: Ryan Tian
|
||||
label:
|
||||
en_US: Update Worksheet Record
|
||||
zh_Hans: 更新指定的一条工作表记录
|
||||
description:
|
||||
human:
|
||||
en_US: Updates a single record in a worksheet based on the specified record row ID
|
||||
zh_Hans: 根据指定的记录ID更新一条工作表记录数据
|
||||
llm: A tool to modify existing information within a particular record of a worksheet by referencing its unique identifier.
|
||||
parameters:
|
||||
- name: appkey
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: App Key
|
||||
zh_Hans: App Key
|
||||
human_description:
|
||||
en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
|
||||
zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
|
||||
llm_description: the AppKey parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: sign
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: Sign
|
||||
zh_Hans: Sign
|
||||
human_description:
|
||||
en_US: The Sign parameter for the HAP application
|
||||
zh_Hans: HAP 应用的 Sign 参数
|
||||
llm_description: the Sign parameter for the HAP application
|
||||
form: form
|
||||
|
||||
- name: worksheet_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Worksheet ID
|
||||
zh_Hans: 工作表 ID
|
||||
human_description:
|
||||
en_US: The ID of the specified worksheet
|
||||
zh_Hans: 要获取字段信息的工作表 ID
|
||||
llm_description: The ID of the specified worksheet which to get the fields information.
|
||||
form: llm
|
||||
|
||||
- name: row_id
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Record Row ID
|
||||
zh_Hans: 记录 ID
|
||||
human_description:
|
||||
en_US: The row ID of the specified record
|
||||
zh_Hans: 要更新的记录 ID
|
||||
llm_description: The row ID of the specified record which to be updated.
|
||||
form: llm
|
||||
|
||||
- name: record_data
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Record Row Data
|
||||
zh_Hans: 记录数据
|
||||
human_description:
|
||||
en_US: The fields with data of the specified record
|
||||
zh_Hans: 要更新的记录数据,JSON 对象数组格式。数组元素属性:controlId-字段ID,value-字段值
|
||||
llm_description: |
|
||||
The fields with data of the specified record which to be updated. It is in the format of an array of JSON objects, and the structure is defined as follows:
|
||||
```
|
||||
type RowData = {
|
||||
controlId: string; // Field ID to be updated
|
||||
value: string; // Field value to be updated
|
||||
}[];
|
||||
```
|
||||
form: llm
|
||||
|
||||
- name: host
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Host Address
|
||||
zh_Hans: 服务器地址
|
||||
human_description:
|
||||
en_US: The address for the privately deployed HAP server.
|
||||
zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
|
||||
llm_description: the address for the privately deployed HAP server.
|
||||
form: form
|
@ -57,8 +57,9 @@ class CodeNode(BaseNode):
|
||||
variables = {}
|
||||
for variable_selector in node_data.variables:
|
||||
variable = variable_selector.variable
|
||||
value = self.graph_runtime_state.variable_pool.get(variable_selector.value_selector)
|
||||
variables[variable] = value.value if value else None
|
||||
value = variable_pool.get_any(variable_selector.value_selector)
|
||||
|
||||
variables[variable] = value
|
||||
# Run code
|
||||
try:
|
||||
result = CodeExecutor.execute_workflow_code_template(
|
||||
|
@ -22,8 +22,8 @@ class EndNode(BaseNode):
|
||||
|
||||
outputs = {}
|
||||
for variable_selector in output_variables:
|
||||
value = self.graph_runtime_state.variable_pool.get(variable_selector.value_selector)
|
||||
outputs[variable_selector.variable] = value.value if value else None
|
||||
value = self.graph_runtime_state.variable_pool.get_any(variable_selector.value_selector)
|
||||
outputs[variable_selector.variable] = value
|
||||
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.SUCCEEDED,
|
||||
|
@ -333,13 +333,13 @@ class HttpExecutor:
|
||||
if variable_pool:
|
||||
variable_value_mapping = {}
|
||||
for variable_selector in variable_selectors:
|
||||
variable = variable_pool.get(variable_selector.value_selector)
|
||||
variable = variable_pool.get_any(variable_selector.value_selector)
|
||||
if variable is None:
|
||||
raise ValueError(f'Variable {variable_selector.variable} not found')
|
||||
if escape_quotes and isinstance(variable.value, str):
|
||||
value = variable.value.replace('"', '\\"')
|
||||
if escape_quotes and isinstance(variable, str):
|
||||
value = variable.replace('"', '\\"')
|
||||
else:
|
||||
value = variable.value
|
||||
value = variable
|
||||
variable_value_mapping[variable_selector.variable] = value
|
||||
|
||||
return variable_template_parser.format(variable_value_mapping), variable_selectors
|
||||
|
@ -40,8 +40,8 @@ class KnowledgeRetrievalNode(BaseNode):
|
||||
node_data = cast(KnowledgeRetrievalNodeData, self.node_data)
|
||||
|
||||
# extract variables
|
||||
variable = self.graph_runtime_state.variable_pool.get(node_data.query_variable_selector)
|
||||
query = variable.value if variable else None
|
||||
variable = self.graph_runtime_state.variable_pool.get_any(node_data.query_variable_selector)
|
||||
query = variable
|
||||
variables = {
|
||||
'query': query
|
||||
}
|
||||
|
@ -405,7 +405,7 @@ class LLMNode(BaseNode):
|
||||
if 'content' not in item:
|
||||
raise ValueError(f'Invalid context structure: {item}')
|
||||
|
||||
context_str += item['content'] + '\n'
|
||||
context_str += item['content'].text + '\n'
|
||||
|
||||
retriever_resource = self._convert_to_original_retriever_resource(item)
|
||||
if retriever_resource:
|
||||
|
@ -71,10 +71,10 @@ class ParameterExtractorNode(LLMNode):
|
||||
Run the node.
|
||||
"""
|
||||
node_data = cast(ParameterExtractorNodeData, self.node_data)
|
||||
variable = self.graph_runtime_state.variable_pool.get(node_data.query)
|
||||
variable = self.graph_runtime_state.variable_pool.get_any(node_data.query)
|
||||
if not variable:
|
||||
raise ValueError("Input variable content not found or is empty")
|
||||
query = variable.value
|
||||
query = variable
|
||||
|
||||
inputs = {
|
||||
'query': query,
|
||||
@ -568,8 +568,8 @@ class ParameterExtractorNode(LLMNode):
|
||||
variable_template_parser = VariableTemplateParser(instruction)
|
||||
inputs = {}
|
||||
for selector in variable_template_parser.extract_variable_selectors():
|
||||
variable = variable_pool.get(selector.value_selector)
|
||||
inputs[selector.variable] = variable.value if variable else None
|
||||
variable = variable_pool.get_any(selector.value_selector)
|
||||
inputs[selector.variable] = variable
|
||||
|
||||
return variable_template_parser.format(inputs)
|
||||
|
||||
|
@ -19,26 +19,26 @@ class VariableAggregatorNode(BaseNode):
|
||||
|
||||
if not node_data.advanced_settings or not node_data.advanced_settings.group_enabled:
|
||||
for selector in node_data.variables:
|
||||
variable = self.graph_runtime_state.variable_pool.get(selector)
|
||||
variable = self.graph_runtime_state.variable_pool.get_any(selector)
|
||||
if variable is not None:
|
||||
outputs = {
|
||||
"output": variable.value
|
||||
"output": variable
|
||||
}
|
||||
|
||||
inputs = {
|
||||
'.'.join(selector[1:]): variable.value
|
||||
'.'.join(selector[1:]): variable
|
||||
}
|
||||
break
|
||||
else:
|
||||
for group in node_data.advanced_settings.groups:
|
||||
for selector in group.variables:
|
||||
variable = self.graph_runtime_state.variable_pool.get(selector)
|
||||
variable = self.graph_runtime_state.variable_pool.get_any(selector)
|
||||
|
||||
if variable is not None:
|
||||
outputs[group.group_name] = {
|
||||
'output': variable.value
|
||||
'output': variable
|
||||
}
|
||||
inputs['.'.join(selector[1:])] = variable.value
|
||||
inputs['.'.join(selector[1:])] = variable
|
||||
break
|
||||
|
||||
return NodeRunResult(
|
||||
|
@ -15,6 +15,7 @@ from zoneinfo import available_timezones
|
||||
from flask import Response, current_app, stream_with_context
|
||||
from flask_restful import fields
|
||||
|
||||
from core.app.features.rate_limiting.rate_limit import RateLimitGenerator
|
||||
from extensions.ext_redis import redis_client
|
||||
from models.account import Account
|
||||
|
||||
@ -159,7 +160,7 @@ def generate_text_hash(text: str) -> str:
|
||||
return sha256(hash_text.encode()).hexdigest()
|
||||
|
||||
|
||||
def compact_generate_response(response: Union[dict, Generator]) -> Response:
|
||||
def compact_generate_response(response: Union[dict, RateLimitGenerator]) -> Response:
|
||||
if isinstance(response, dict):
|
||||
return Response(response=json.dumps(response), status=200, mimetype='application/json')
|
||||
else:
|
||||
|
25
api/poetry.lock
generated
25
api/poetry.lock
generated
@ -7548,13 +7548,13 @@ torch = ["safetensors[numpy]", "torch (>=1.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "1.39.2"
|
||||
version = "2.8.0"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "sentry-sdk-1.39.2.tar.gz", hash = "sha256:24c83b0b41c887d33328a9166f5950dc37ad58f01c9f2fbff6b87a6f1094170c"},
|
||||
{file = "sentry_sdk-1.39.2-py2.py3-none-any.whl", hash = "sha256:acaf597b30258fc7663063b291aa99e58f3096e91fe1e6634f4b79f9c1943e8e"},
|
||||
{file = "sentry_sdk-2.8.0-py2.py3-none-any.whl", hash = "sha256:6051562d2cfa8087bb8b4b8b79dc44690f8a054762a29c07e22588b1f619bfb5"},
|
||||
{file = "sentry_sdk-2.8.0.tar.gz", hash = "sha256:aa4314f877d9cd9add5a0c9ba18e3f27f99f7de835ce36bd150e48a41c7c646f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -7562,28 +7562,33 @@ blinker = {version = ">=1.1", optional = true, markers = "extra == \"flask\""}
|
||||
certifi = "*"
|
||||
flask = {version = ">=0.11", optional = true, markers = "extra == \"flask\""}
|
||||
markupsafe = {version = "*", optional = true, markers = "extra == \"flask\""}
|
||||
urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""}
|
||||
urllib3 = ">=1.26.11"
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp (>=3.5)"]
|
||||
anthropic = ["anthropic (>=0.16)"]
|
||||
arq = ["arq (>=0.23)"]
|
||||
asyncpg = ["asyncpg (>=0.23)"]
|
||||
beam = ["apache-beam (>=2.12)"]
|
||||
bottle = ["bottle (>=0.12.13)"]
|
||||
celery = ["celery (>=3)"]
|
||||
celery-redbeat = ["celery-redbeat (>=2)"]
|
||||
chalice = ["chalice (>=1.16.0)"]
|
||||
clickhouse-driver = ["clickhouse-driver (>=0.2.0)"]
|
||||
django = ["django (>=1.8)"]
|
||||
falcon = ["falcon (>=1.4)"]
|
||||
fastapi = ["fastapi (>=0.79.0)"]
|
||||
flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"]
|
||||
grpcio = ["grpcio (>=1.21.1)"]
|
||||
grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"]
|
||||
httpx = ["httpx (>=0.16.0)"]
|
||||
huey = ["huey (>=2)"]
|
||||
huggingface-hub = ["huggingface-hub (>=0.22)"]
|
||||
langchain = ["langchain (>=0.0.210)"]
|
||||
loguru = ["loguru (>=0.5)"]
|
||||
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
|
||||
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
|
||||
opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"]
|
||||
pure-eval = ["asttokens", "executing", "pure_eval"]
|
||||
opentelemetry-experimental = ["opentelemetry-instrumentation-aio-pika (==0.46b0)", "opentelemetry-instrumentation-aiohttp-client (==0.46b0)", "opentelemetry-instrumentation-aiopg (==0.46b0)", "opentelemetry-instrumentation-asgi (==0.46b0)", "opentelemetry-instrumentation-asyncio (==0.46b0)", "opentelemetry-instrumentation-asyncpg (==0.46b0)", "opentelemetry-instrumentation-aws-lambda (==0.46b0)", "opentelemetry-instrumentation-boto (==0.46b0)", "opentelemetry-instrumentation-boto3sqs (==0.46b0)", "opentelemetry-instrumentation-botocore (==0.46b0)", "opentelemetry-instrumentation-cassandra (==0.46b0)", "opentelemetry-instrumentation-celery (==0.46b0)", "opentelemetry-instrumentation-confluent-kafka (==0.46b0)", "opentelemetry-instrumentation-dbapi (==0.46b0)", "opentelemetry-instrumentation-django (==0.46b0)", "opentelemetry-instrumentation-elasticsearch (==0.46b0)", "opentelemetry-instrumentation-falcon (==0.46b0)", "opentelemetry-instrumentation-fastapi (==0.46b0)", "opentelemetry-instrumentation-flask (==0.46b0)", "opentelemetry-instrumentation-grpc (==0.46b0)", "opentelemetry-instrumentation-httpx (==0.46b0)", "opentelemetry-instrumentation-jinja2 (==0.46b0)", "opentelemetry-instrumentation-kafka-python (==0.46b0)", "opentelemetry-instrumentation-logging (==0.46b0)", "opentelemetry-instrumentation-mysql (==0.46b0)", "opentelemetry-instrumentation-mysqlclient (==0.46b0)", "opentelemetry-instrumentation-pika (==0.46b0)", "opentelemetry-instrumentation-psycopg (==0.46b0)", "opentelemetry-instrumentation-psycopg2 (==0.46b0)", "opentelemetry-instrumentation-pymemcache (==0.46b0)", "opentelemetry-instrumentation-pymongo (==0.46b0)", "opentelemetry-instrumentation-pymysql (==0.46b0)", "opentelemetry-instrumentation-pyramid (==0.46b0)", "opentelemetry-instrumentation-redis (==0.46b0)", "opentelemetry-instrumentation-remoulade (==0.46b0)", "opentelemetry-instrumentation-requests (==0.46b0)", "opentelemetry-instrumentation-sklearn (==0.46b0)", "opentelemetry-instrumentation-sqlalchemy (==0.46b0)", "opentelemetry-instrumentation-sqlite3 (==0.46b0)", "opentelemetry-instrumentation-starlette (==0.46b0)", "opentelemetry-instrumentation-system-metrics (==0.46b0)", "opentelemetry-instrumentation-threading (==0.46b0)", "opentelemetry-instrumentation-tornado (==0.46b0)", "opentelemetry-instrumentation-tortoiseorm (==0.46b0)", "opentelemetry-instrumentation-urllib (==0.46b0)", "opentelemetry-instrumentation-urllib3 (==0.46b0)", "opentelemetry-instrumentation-wsgi (==0.46b0)"]
|
||||
pure-eval = ["asttokens", "executing", "pure-eval"]
|
||||
pymongo = ["pymongo (>=3.1)"]
|
||||
pyspark = ["pyspark (>=2.4.4)"]
|
||||
quart = ["blinker (>=1.1)", "quart (>=0.16.1)"]
|
||||
@ -7592,7 +7597,7 @@ sanic = ["sanic (>=0.8)"]
|
||||
sqlalchemy = ["sqlalchemy (>=1.2)"]
|
||||
starlette = ["starlette (>=0.19.1)"]
|
||||
starlite = ["starlite (>=1.48)"]
|
||||
tornado = ["tornado (>=5)"]
|
||||
tornado = ["tornado (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
@ -9438,4 +9443,4 @@ cffi = ["cffi (>=1.11)"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "9b1821b6e5d6d44947cc011c2d635a366557582b4540b99e0ff53a3078a989e5"
|
||||
content-hash = "ae278035ed74936930da140681218a2f0762c4bbbc6bd985d53924a56df595cf"
|
||||
|
@ -163,7 +163,7 @@ redis = { version = "~5.0.3", extras = ["hiredis"] }
|
||||
replicate = "~0.22.0"
|
||||
resend = "~0.7.0"
|
||||
safetensors = "~0.4.3"
|
||||
sentry-sdk = { version = "~1.39.2", extras = ["flask"] }
|
||||
sentry-sdk = { version = "~2.8.0", extras = ["flask"] }
|
||||
sqlalchemy = "~2.0.29"
|
||||
tencentcloud-sdk-python-hunyuan = "~3.0.1158"
|
||||
tiktoken = "~0.7.0"
|
||||
|
@ -21,7 +21,7 @@ class AppGenerateService:
|
||||
args: Any,
|
||||
invoke_from: InvokeFrom,
|
||||
streaming: bool = True,
|
||||
) -> Union[dict, Generator[dict, None, None]]:
|
||||
):
|
||||
"""
|
||||
App Content Generate
|
||||
:param app_model: app model
|
||||
|
@ -40,7 +40,7 @@ class HitTestingService:
|
||||
|
||||
all_documents = RetrievalService.retrieve(retrival_method=retrieval_model['search_method'],
|
||||
dataset_id=dataset.id,
|
||||
query=query,
|
||||
query=cls.escape_query_for_search(query),
|
||||
top_k=retrieval_model['top_k'],
|
||||
score_threshold=retrieval_model['score_threshold']
|
||||
if retrieval_model['score_threshold_enabled'] else None,
|
||||
@ -104,3 +104,7 @@ class HitTestingService:
|
||||
|
||||
if not query or len(query) > 250:
|
||||
raise ValueError('Query is required and cannot exceed 250 characters')
|
||||
|
||||
@staticmethod
|
||||
def escape_query_for_search(query: str) -> str:
|
||||
return query.replace('"', '\\"')
|
||||
|
@ -131,9 +131,8 @@ class ModelLoadBalancingService:
|
||||
load_balancing_configs.insert(0, inherit_config)
|
||||
else:
|
||||
# move the inherit configuration to the first
|
||||
for i, load_balancing_config in enumerate(load_balancing_configs):
|
||||
for i, load_balancing_config in enumerate(load_balancing_configs[:]):
|
||||
if load_balancing_config.name == '__inherit__':
|
||||
# FIXME: Mutation to loop iterable `load_balancing_configs` during iteration
|
||||
inherit_config = load_balancing_configs.pop(i)
|
||||
load_balancing_configs.insert(0, inherit_config)
|
||||
|
||||
|
13
docker/startupscripts/init.sh
Executable file
13
docker/startupscripts/init.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DB_INITIALISED="/opt/oracle/oradata/dbinit"
|
||||
#[ -f ${DB_INITIALISED} ] && exit
|
||||
#touch ${DB_INITIALISED}
|
||||
if [ -f ${DB_INITIALISED} ]; then
|
||||
echo 'File exists. Standards for have been Init'
|
||||
exit
|
||||
else
|
||||
echo 'File does not exist. Standards for first time Strart up this DB'
|
||||
"$ORACLE_HOME"/bin/sqlplus -s "/ as sysdba" @"/opt/oracle/scripts/startup/init_user.script";
|
||||
touch ${DB_INITIALISED}
|
||||
fi
|
@ -3,3 +3,8 @@ ALTER SYSTEM SET PROCESSES=500 SCOPE=SPFILE;
|
||||
alter session set container= freepdb1;
|
||||
create user dify identified by dify DEFAULT TABLESPACE users quota unlimited on users;
|
||||
grant DB_DEVELOPER_ROLE to dify;
|
||||
|
||||
BEGIN
|
||||
CTX_DDL.CREATE_PREFERENCE('my_chinese_vgram_lexer','CHINESE_VGRAM_LEXER');
|
||||
END;
|
||||
/
|
@ -19,9 +19,6 @@ export type IPromptProps = {
|
||||
promptTemplate: string
|
||||
promptVariables: PromptVariable[]
|
||||
readonly?: boolean
|
||||
noTitle?: boolean
|
||||
gradientBorder?: boolean
|
||||
editorHeight?: number
|
||||
onChange?: (prompt: string, promptVariables: PromptVariable[]) => void
|
||||
}
|
||||
|
||||
@ -29,10 +26,7 @@ const Prompt: FC<IPromptProps> = ({
|
||||
mode,
|
||||
promptTemplate,
|
||||
promptVariables,
|
||||
noTitle,
|
||||
gradientBorder,
|
||||
readonly = false,
|
||||
editorHeight,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@ -105,9 +99,6 @@ const Prompt: FC<IPromptProps> = ({
|
||||
promptVariables={promptVariables}
|
||||
readonly={readonly}
|
||||
onChange={onChange}
|
||||
noTitle={noTitle}
|
||||
gradientBorder={gradientBorder}
|
||||
editorHeight={editorHeight}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var'
|
||||
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
|
||||
import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '@/app/components/base/prompt-editor/plugins/update-block'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
export type ISimplePromptInput = {
|
||||
mode: AppType
|
||||
@ -36,9 +35,6 @@ export type ISimplePromptInput = {
|
||||
promptVariables: PromptVariable[]
|
||||
readonly?: boolean
|
||||
onChange?: (promp: string, promptVariables: PromptVariable[]) => void
|
||||
noTitle?: boolean
|
||||
gradientBorder?: boolean
|
||||
editorHeight?: number
|
||||
}
|
||||
|
||||
const Prompt: FC<ISimplePromptInput> = ({
|
||||
@ -47,14 +43,8 @@ const Prompt: FC<ISimplePromptInput> = ({
|
||||
promptVariables,
|
||||
readonly = false,
|
||||
onChange,
|
||||
noTitle,
|
||||
gradientBorder,
|
||||
editorHeight: initEditorHeight,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const {
|
||||
modelConfig,
|
||||
@ -126,11 +116,6 @@ const Prompt: FC<ISimplePromptInput> = ({
|
||||
|
||||
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
|
||||
const handleAutomaticRes = (res: AutomaticRes) => {
|
||||
// put eventEmitter in first place to prevent overwrite the configs.prompt_variables.But another problem is that prompt won't hight the prompt_variables.
|
||||
eventEmitter?.emit({
|
||||
type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER,
|
||||
payload: res.prompt,
|
||||
} as any)
|
||||
const newModelConfig = produce(modelConfig, (draft) => {
|
||||
draft.configs.prompt_template = res.prompt
|
||||
draft.configs.prompt_variables = res.variables.map(key => ({ key, name: key, type: 'string', required: true }))
|
||||
@ -140,35 +125,36 @@ const Prompt: FC<ISimplePromptInput> = ({
|
||||
if (mode !== AppType.completion)
|
||||
setIntroduction(res.opening_statement)
|
||||
showAutomaticFalse()
|
||||
eventEmitter?.emit({
|
||||
type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER,
|
||||
payload: res.prompt,
|
||||
} as any)
|
||||
}
|
||||
const minHeight = initEditorHeight || 228
|
||||
const minHeight = 228
|
||||
const [editorHeight, setEditorHeight] = useState(minHeight)
|
||||
|
||||
return (
|
||||
<div className={cn((!readonly || gradientBorder) ? `${s.gradientBorder}` : 'bg-gray-50', ' relative shadow-md')}>
|
||||
<div className={cn(!readonly ? `${s.gradientBorder}` : 'bg-gray-50', ' relative shadow-md')}>
|
||||
<div className='rounded-xl bg-[#EEF4FF]'>
|
||||
{!noTitle && (
|
||||
<div className="flex justify-between items-center h-11 px-3">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className='h2'>{mode !== AppType.completion ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}</div>
|
||||
{!readonly && (
|
||||
<Tooltip
|
||||
htmlContent={<div className='w-[180px]'>
|
||||
{t('appDebug.promptTip')}
|
||||
</div>}
|
||||
selector='config-prompt-tooltip'>
|
||||
<RiQuestionLine className='w-[14px] h-[14px] text-indigo-400' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
{!isAgent && !readonly && !isMobile && (
|
||||
<AutomaticBtn onClick={showAutomaticTrue} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between items-center h-11 px-3">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className='h2'>{mode !== AppType.completion ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}</div>
|
||||
{!readonly && (
|
||||
<Tooltip
|
||||
htmlContent={<div className='w-[180px]'>
|
||||
{t('appDebug.promptTip')}
|
||||
</div>}
|
||||
selector='config-prompt-tooltip'>
|
||||
<RiQuestionLine className='w-[14px] h-[14px] text-indigo-400' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='flex items-center'>
|
||||
{!isAgent && !readonly && (
|
||||
<AutomaticBtn onClick={showAutomaticTrue} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<PromptEditorHeightResizeWrap
|
||||
className='px-4 pt-2 min-h-[228px] bg-white rounded-t-xl text-sm text-gray-700'
|
||||
height={editorHeight}
|
||||
@ -230,7 +216,6 @@ const Prompt: FC<ISimplePromptInput> = ({
|
||||
onBlur={() => {
|
||||
handleChange(promptTemplate, getVars(promptTemplate))
|
||||
}}
|
||||
editable={!readonly}
|
||||
/>
|
||||
</PromptEditorHeightResizeWrap>
|
||||
</div>
|
||||
|
@ -2,21 +2,29 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
|
||||
export type IAutomaticBtnProps = {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const leftIcon = (
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.31346 0.905711C4.21464 0.708087 4.01266 0.583252 3.79171 0.583252C3.57076 0.583252 3.36877 0.708087 3.26996 0.905711L2.81236 1.82091C2.64757 2.15048 2.59736 2.24532 2.53635 2.32447C2.47515 2.40386 2.40398 2.47503 2.32459 2.53623C2.24544 2.59724 2.1506 2.64745 1.82103 2.81224L0.905833 3.26984C0.708209 3.36865 0.583374 3.57064 0.583374 3.79159C0.583374 4.01254 0.708209 4.21452 0.905833 4.31333L1.82103 4.77094C2.1506 4.93572 2.24544 4.98593 2.32459 5.04694C2.40398 5.10814 2.47515 5.17931 2.53635 5.2587C2.59736 5.33785 2.64758 5.43269 2.81236 5.76226L3.26996 6.67746C3.36877 6.87508 3.57076 6.99992 3.79171 6.99992C4.01266 6.99992 4.21465 6.87508 4.31346 6.67746L4.77106 5.76226C4.93584 5.43269 4.98605 5.33786 5.04707 5.2587C5.10826 5.17931 5.17943 5.10814 5.25883 5.04694C5.33798 4.98593 5.43282 4.93572 5.76238 4.77094L6.67758 4.31333C6.87521 4.21452 7.00004 4.01254 7.00004 3.79159C7.00004 3.57064 6.87521 3.36865 6.67758 3.26984L5.76238 2.81224C5.43282 2.64745 5.33798 2.59724 5.25883 2.53623C5.17943 2.47503 5.10826 2.40386 5.04707 2.32447C4.98605 2.24532 4.93584 2.15048 4.77106 1.82091L4.31346 0.905711Z" fill="#444CE7" />
|
||||
<path d="M11.375 1.74992C11.375 1.42775 11.1139 1.16659 10.7917 1.16659C10.4695 1.16659 10.2084 1.42775 10.2084 1.74992V2.62492H9.33337C9.01121 2.62492 8.75004 2.88609 8.75004 3.20825C8.75004 3.53042 9.01121 3.79159 9.33337 3.79159H10.2084V4.66659C10.2084 4.98875 10.4695 5.24992 10.7917 5.24992C11.1139 5.24992 11.375 4.98875 11.375 4.66659V3.79159H12.25C12.5722 3.79159 12.8334 3.53042 12.8334 3.20825C12.8334 2.88609 12.5722 2.62492 12.25 2.62492H11.375V1.74992Z" fill="#444CE7" />
|
||||
<path d="M3.79171 9.33325C3.79171 9.01109 3.53054 8.74992 3.20837 8.74992C2.88621 8.74992 2.62504 9.01109 2.62504 9.33325V10.2083H1.75004C1.42787 10.2083 1.16671 10.4694 1.16671 10.7916C1.16671 11.1138 1.42787 11.3749 1.75004 11.3749H2.62504V12.2499C2.62504 12.5721 2.88621 12.8333 3.20837 12.8333C3.53054 12.8333 3.79171 12.5721 3.79171 12.2499V11.3749H4.66671C4.98887 11.3749 5.25004 11.1138 5.25004 10.7916C5.25004 10.4694 4.98887 10.2083 4.66671 10.2083H3.79171V9.33325Z" fill="#444CE7" />
|
||||
<path d="M10.4385 6.73904C10.3396 6.54142 10.1377 6.41659 9.91671 6.41659C9.69576 6.41659 9.49377 6.54142 9.39496 6.73904L8.84014 7.84869C8.67535 8.17826 8.62514 8.27309 8.56413 8.35225C8.50293 8.43164 8.43176 8.50281 8.35237 8.56401C8.27322 8.62502 8.17838 8.67523 7.84881 8.84001L6.73917 9.39484C6.54154 9.49365 6.41671 9.69564 6.41671 9.91659C6.41671 10.1375 6.54154 10.3395 6.73917 10.4383L7.84881 10.9932C8.17838 11.1579 8.27322 11.2082 8.35237 11.2692C8.43176 11.3304 8.50293 11.4015 8.56413 11.4809C8.62514 11.5601 8.67535 11.6549 8.84014 11.9845L9.39496 13.0941C9.49377 13.2918 9.69576 13.4166 9.91671 13.4166C10.1377 13.4166 10.3396 13.2918 10.4385 13.0941L10.9933 11.9845C11.1581 11.6549 11.2083 11.5601 11.2693 11.4809C11.3305 11.4015 11.4017 11.3304 11.481 11.2692C11.5602 11.2082 11.655 11.1579 11.9846 10.9932L13.0942 10.4383C13.2919 10.3395 13.4167 10.1375 13.4167 9.91659C13.4167 9.69564 13.2919 9.49365 13.0942 9.39484L11.9846 8.84001C11.655 8.67523 11.5602 8.62502 11.481 8.56401C11.4017 8.50281 11.3305 8.43164 11.2693 8.35225C11.2083 8.27309 11.1581 8.17826 10.9933 7.84869L10.4385 6.73904Z" fill="#444CE7" />
|
||||
</svg>
|
||||
)
|
||||
const AutomaticBtn: FC<IAutomaticBtnProps> = ({
|
||||
onClick,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex space-x-1 items-center !h-8 cursor-pointer'
|
||||
<div className='flex px-3 space-x-2 items-center !h-8 cursor-pointer'
|
||||
onClick={onClick}
|
||||
>
|
||||
<Generator className='w-3.5 h-3.5 text-indigo-600' />
|
||||
{leftIcon}
|
||||
<span className='text-xs font-semibold text-indigo-600'>{t('appDebug.operation.automatic')}</span>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,20 +1,8 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
RiDatabase2Line,
|
||||
RiFileExcel2Line,
|
||||
RiGitCommitLine,
|
||||
RiNewspaperLine,
|
||||
RiPresentationLine,
|
||||
RiRoadMapLine,
|
||||
RiTerminalBoxLine,
|
||||
RiTranslate,
|
||||
RiUser2Line,
|
||||
} from '@remixicon/react'
|
||||
import s from './style.module.css'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
@ -26,97 +14,57 @@ import OpeningStatement from '@/app/components/app/configuration/features/chat-g
|
||||
import GroupName from '@/app/components/app/configuration/base/group-name'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
|
||||
// type
|
||||
import type { AutomaticRes } from '@/service/debug'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
|
||||
const noDataIcon = (
|
||||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.4998 51.3333V39.6666M10.4998 16.3333V4.66663M4.6665 10.5H16.3332M4.6665 45.5H16.3332M30.3332 6.99996L26.2868 17.5206C25.6287 19.2315 25.2997 20.0869 24.7881 20.8065C24.3346 21.4442 23.7774 22.0014 23.1397 22.4549C22.4202 22.9665 21.5647 23.2955 19.8538 23.9535L9.33317 28L19.8539 32.0464C21.5647 32.7044 22.4202 33.0334 23.1397 33.5451C23.7774 33.9985 24.3346 34.5557 24.7881 35.1934C25.2997 35.913 25.6287 36.7684 26.2868 38.4793L30.3332 49L34.3796 38.4793C35.0376 36.7684 35.3666 35.913 35.8783 35.1934C36.3317 34.5557 36.8889 33.9985 37.5266 33.5451C38.2462 33.0334 39.1016 32.7044 40.8125 32.0464L51.3332 28L40.8125 23.9535C39.1016 23.2955 38.2462 22.9665 37.5266 22.4549C36.8889 22.0014 36.3317 21.4442 35.8783 20.8065C35.3666 20.0869 35.0376 19.2315 34.3796 17.5206L30.3332 6.99996Z" stroke="#EAECF0" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export type IGetAutomaticResProps = {
|
||||
mode: AppType
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
onFinished: (res: AutomaticRes) => void
|
||||
isInLLMNode?: boolean
|
||||
}
|
||||
|
||||
const TryLabel: FC<{
|
||||
Icon: any
|
||||
text: string
|
||||
onClick: () => void
|
||||
}> = ({ Icon, text, onClick }) => {
|
||||
return (
|
||||
<div
|
||||
className='mt-2 mr-1 shrink-0 flex h-7 items-center px-2 bg-gray-100 rounded-lg cursor-pointer'
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon className='w-4 h-4 text-gray-500'></Icon>
|
||||
<div className='ml-1 text-xs font-medium text-gray-700'>{text}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const genIcon = (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.6665 1.33332C3.6665 0.965133 3.36803 0.666656 2.99984 0.666656C2.63165 0.666656 2.33317 0.965133 2.33317 1.33332V2.33332H1.33317C0.964981 2.33332 0.666504 2.6318 0.666504 2.99999C0.666504 3.36818 0.964981 3.66666 1.33317 3.66666H2.33317V4.66666C2.33317 5.03485 2.63165 5.33332 2.99984 5.33332C3.36803 5.33332 3.6665 5.03485 3.6665 4.66666V3.66666H4.6665C5.03469 3.66666 5.33317 3.36818 5.33317 2.99999C5.33317 2.6318 5.03469 2.33332 4.6665 2.33332H3.6665V1.33332Z" fill="white" />
|
||||
<path d="M3.6665 11.3333C3.6665 10.9651 3.36803 10.6667 2.99984 10.6667C2.63165 10.6667 2.33317 10.9651 2.33317 11.3333V12.3333H1.33317C0.964981 12.3333 0.666504 12.6318 0.666504 13C0.666504 13.3682 0.964981 13.6667 1.33317 13.6667H2.33317V14.6667C2.33317 15.0348 2.63165 15.3333 2.99984 15.3333C3.36803 15.3333 3.6665 15.0348 3.6665 14.6667V13.6667H4.6665C5.03469 13.6667 5.33317 13.3682 5.33317 13C5.33317 12.6318 5.03469 12.3333 4.6665 12.3333H3.6665V11.3333Z" fill="white" />
|
||||
<path d="M9.28873 1.76067C9.18971 1.50321 8.94235 1.33332 8.6665 1.33332C8.39066 1.33332 8.1433 1.50321 8.04427 1.76067L6.88815 4.76658C6.68789 5.28727 6.62495 5.43732 6.53887 5.55838C6.4525 5.67986 6.34637 5.78599 6.2249 5.87236C6.10384 5.95844 5.95379 6.02137 5.43309 6.22164L2.42718 7.37776C2.16972 7.47678 1.99984 7.72414 1.99984 7.99999C1.99984 8.27584 2.16972 8.5232 2.42718 8.62222L5.43309 9.77834C5.95379 9.97861 6.10384 10.0415 6.2249 10.1276C6.34637 10.214 6.4525 10.3201 6.53887 10.4416C6.62495 10.5627 6.68789 10.7127 6.88816 11.2334L8.04427 14.2393C8.1433 14.4968 8.39066 14.6667 8.6665 14.6667C8.94235 14.6667 9.18971 14.4968 9.28873 14.2393L10.4449 11.2334C10.6451 10.7127 10.7081 10.5627 10.7941 10.4416C10.8805 10.3201 10.9866 10.214 11.1081 10.1276C11.2292 10.0415 11.3792 9.97861 11.8999 9.77834L14.9058 8.62222C15.1633 8.5232 15.3332 8.27584 15.3332 7.99999C15.3332 7.72414 15.1633 7.47678 14.9058 7.37776L11.8999 6.22164C11.3792 6.02137 11.2292 5.95844 11.1081 5.87236C10.9866 5.78599 10.8805 5.67986 10.7941 5.55838C10.7081 5.43732 10.6451 5.28727 10.4449 4.76658L9.28873 1.76067Z" fill="white" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
mode,
|
||||
isShow,
|
||||
onClose,
|
||||
isInLLMNode,
|
||||
// appId,
|
||||
onFinished,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tryList = [
|
||||
{
|
||||
icon: RiTerminalBoxLine,
|
||||
key: 'pythonDebugger',
|
||||
},
|
||||
{
|
||||
icon: RiTranslate,
|
||||
key: 'translation',
|
||||
},
|
||||
{
|
||||
icon: RiPresentationLine,
|
||||
key: 'meetingTakeaways',
|
||||
},
|
||||
{
|
||||
icon: RiNewspaperLine,
|
||||
key: 'writingsPolisher',
|
||||
},
|
||||
{
|
||||
icon: RiUser2Line,
|
||||
key: 'professionalAnalyst',
|
||||
},
|
||||
{
|
||||
icon: RiFileExcel2Line,
|
||||
key: 'excelFormulaExpert',
|
||||
},
|
||||
{
|
||||
icon: RiRoadMapLine,
|
||||
key: 'travelPlanning',
|
||||
},
|
||||
{
|
||||
icon: RiDatabase2Line,
|
||||
key: 'SQLSorcerer',
|
||||
},
|
||||
{
|
||||
icon: RiGitCommitLine,
|
||||
key: 'GitGud',
|
||||
},
|
||||
]
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const [instruction, setInstruction] = React.useState<string>('')
|
||||
const handleChooseTemplate = useCallback((key: string) => {
|
||||
return () => {
|
||||
const template = t(`appDebug.generate.template.${key}.instruction`)
|
||||
setInstruction(template)
|
||||
}
|
||||
}, [t])
|
||||
const [audiences, setAudiences] = React.useState<string>('')
|
||||
const [hopingToSolve, setHopingToSolve] = React.useState<string>('')
|
||||
const isValid = () => {
|
||||
if (instruction.trim() === '') {
|
||||
if (audiences.trim() === '') {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('common.errorMsg.fieldRequired', {
|
||||
field: t('appDebug.generate.instruction'),
|
||||
}),
|
||||
message: t('appDebug.automatic.audiencesRequired'),
|
||||
})
|
||||
return false
|
||||
}
|
||||
if (hopingToSolve.trim() === '') {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('appDebug.automatic.problemRequired'),
|
||||
})
|
||||
return false
|
||||
}
|
||||
@ -128,17 +76,14 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
const renderLoading = (
|
||||
<div className='w-0 grow flex flex-col items-center justify-center h-full space-y-3'>
|
||||
<Loading />
|
||||
<div className='text-[13px] text-gray-400'>{t('appDebug.generate.loading')}</div>
|
||||
<div className='text-[13px] text-gray-400'>{t('appDebug.automatic.loading')}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderNoData = (
|
||||
<div className='w-0 grow flex flex-col items-center px-8 justify-center h-full space-y-3'>
|
||||
<Generator className='w-14 h-14 text-gray-300' />
|
||||
<div className='leading-5 text-center text-[13px] font-normal text-gray-400'>
|
||||
<div>{t('appDebug.generate.noDataLine1')}</div>
|
||||
<div>{t('appDebug.generate.noDataLine2')}</div>
|
||||
</div>
|
||||
{noDataIcon}
|
||||
<div className='text-[13px] text-gray-400'>{t('appDebug.automatic.noData')}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -150,7 +95,8 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
setLoadingTrue()
|
||||
try {
|
||||
const res = await generateRule({
|
||||
instruction,
|
||||
audiences,
|
||||
hoping_to_solve: hopingToSolve,
|
||||
})
|
||||
setRes(res)
|
||||
}
|
||||
@ -161,7 +107,24 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
|
||||
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
|
||||
|
||||
const isShowAutoPromptInput = () => {
|
||||
if (isMobile) {
|
||||
// hide prompt panel on mobile if it is loading or has had result
|
||||
if (isLoading || res)
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
// always display prompt panel on desktop mode
|
||||
return true
|
||||
}
|
||||
|
||||
const isShowAutoPromptResPlaceholder = () => {
|
||||
if (isMobile) {
|
||||
// hide placeholder panel on mobile
|
||||
return false
|
||||
}
|
||||
|
||||
return !isLoading && !res
|
||||
}
|
||||
|
||||
@ -169,96 +132,75 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className='!p-0 min-w-[1140px]'
|
||||
className='!p-0 sm:min-w-[768px] xl:min-w-[1120px]'
|
||||
closable
|
||||
>
|
||||
<div className='flex h-[680px] flex-wrap'>
|
||||
<div className='w-[570px] shrink-0 p-6 h-full overflow-y-auto border-r border-gray-100'>
|
||||
<div className='mb-8'>
|
||||
<div className={`leading-[28px] text-lg font-bold ${s.textGradient}`}>{t('appDebug.generate.title')}</div>
|
||||
<div className='mt-1 text-[13px] font-normal text-gray-500'>{t('appDebug.generate.description')}</div>
|
||||
</div>
|
||||
<div >
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-3 shrink-0 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>{t('appDebug.generate.tryIt')}</div>
|
||||
<div className='grow h-px' style={{
|
||||
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))',
|
||||
}}></div>
|
||||
</div>
|
||||
<div className='flex flex-wrap'>
|
||||
{tryList.map(item => (
|
||||
<TryLabel
|
||||
key={item.key}
|
||||
Icon={item.icon}
|
||||
text={t(`appDebug.generate.template.${item.key}.name`)}
|
||||
onClick={handleChooseTemplate(item.key)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className='flex h-[680px] flex-wrap gap-y-4 overflow-y-auto'>
|
||||
{isShowAutoPromptInput() && <div className='w-full sm:w-[360px] xl:w-[480px] shrink-0 px-8 py-6 h-full overflow-y-auto border-r border-gray-100'>
|
||||
<div>
|
||||
<div className='mb-1 text-xl font-semibold text-primary-600'>{t('appDebug.automatic.title')}</div>
|
||||
<div className='text-[13px] font-normal text-gray-500'>{t('appDebug.automatic.description')}</div>
|
||||
</div>
|
||||
{/* inputs */}
|
||||
<div className='mt-6'>
|
||||
<div className='text-[0px]'>
|
||||
<div className='mb-2 leading-5 text-sm font-medium text-gray-900'>{t('appDebug.generate.instruction')}</div>
|
||||
<textarea className="w-full h-[200px] overflow-y-auto px-3 py-2 text-sm bg-gray-50 rounded-lg" placeholder={t('appDebug.generate.instructionPlaceHolder') as string} value={instruction} onChange={e => setInstruction(e.target.value)} />
|
||||
<div className='mt-2 space-y-5'>
|
||||
<div className='space-y-2'>
|
||||
<div className='text-[13px] font-medium text-gray-900'>{t('appDebug.automatic.intendedAudience')}</div>
|
||||
<input className="w-full h-8 px-3 text-[13px] font-normal bg-gray-50 rounded-lg" placeholder={t('appDebug.automatic.intendedAudiencePlaceHolder') as string} value={audiences} onChange={e => setAudiences(e.target.value)} />
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<div className='text-[13px] font-medium text-gray-900'>{t('appDebug.automatic.solveProblem')}</div>
|
||||
<textarea className="w-full h-[200px] overflow-y-auto p-3 text-[13px] font-normal bg-gray-50 rounded-lg" placeholder={t('appDebug.automatic.solveProblemPlaceHolder') as string} value={hopingToSolve} onChange={e => setHopingToSolve(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className='mt-5 flex justify-end'>
|
||||
<div className='mt-6 flex justify-end'>
|
||||
<Button
|
||||
className='flex space-x-1'
|
||||
className='flex space-x-2'
|
||||
variant='primary'
|
||||
onClick={onGenerate}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Generator className='w-4 h-4 text-white' />
|
||||
<span className='text-xs font-semibold text-white'>{t('appDebug.generate.generate')}</span>
|
||||
{genIcon}
|
||||
<span className='text-xs font-semibold text-white uppercase'>{t('appDebug.automatic.generate')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
{(!isLoading && res) && (
|
||||
<div className='w-0 grow p-6 pb-0 h-full'>
|
||||
<div className='shrink-0 mb-3 leading-[160%] text-base font-semibold text-gray-800'>{t('appDebug.generate.resTitle')}</div>
|
||||
<div className='max-h-[560px] pb-2 overflow-y-auto'>
|
||||
<ConfigPrompt
|
||||
mode={mode}
|
||||
promptTemplate={res?.prompt || ''}
|
||||
promptVariables={[]}
|
||||
readonly
|
||||
noTitle={isInLLMNode}
|
||||
gradientBorder
|
||||
editorHeight={isInLLMNode ? 524 : 0}
|
||||
/>
|
||||
{!isInLLMNode && (
|
||||
<>
|
||||
{(res?.variables?.length && res?.variables?.length > 0)
|
||||
? (
|
||||
<ConfigVar
|
||||
promptVariables={res?.variables.map(key => ({ key, name: key, type: 'string', required: true })) || []}
|
||||
readonly
|
||||
/>
|
||||
)
|
||||
: ''}
|
||||
<div className='w-0 grow px-8 pt-6 h-full overflow-y-auto'>
|
||||
<div className='mb-4 text-lg font-medium text-gray-900'>{t('appDebug.automatic.resTitle')}</div>
|
||||
|
||||
{(mode !== AppType.completion && res?.opening_statement) && (
|
||||
<div className='mt-7'>
|
||||
<GroupName name={t('appDebug.feature.groupChat.title')} />
|
||||
<OpeningStatement
|
||||
value={res?.opening_statement || ''}
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ConfigPrompt
|
||||
mode={mode}
|
||||
promptTemplate={res?.prompt || ''}
|
||||
promptVariables={[]}
|
||||
readonly
|
||||
/>
|
||||
|
||||
<div className='flex justify-end py-4 pr-6 bg-white'>
|
||||
{(res?.variables?.length && res?.variables?.length > 0)
|
||||
? (
|
||||
<ConfigVar
|
||||
promptVariables={res?.variables.map(key => ({ key, name: key, type: 'string', required: true })) || []}
|
||||
readonly
|
||||
/>
|
||||
)
|
||||
: ''}
|
||||
|
||||
{(mode !== AppType.completion && res?.opening_statement) && (
|
||||
<div className='mt-7'>
|
||||
<GroupName name={t('appDebug.feature.groupChat.title')} />
|
||||
<OpeningStatement
|
||||
value={res?.opening_statement || ''}
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='sticky bottom-0 flex justify-end right-0 py-4 bg-white'>
|
||||
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' className='ml-2' onClick={() => {
|
||||
setShowConfirmOverwrite(true)
|
||||
}}>{t('appDebug.generate.apply')}</Button>
|
||||
}}>{t('appDebug.automatic.apply')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -266,8 +208,8 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
{isShowAutoPromptResPlaceholder() && renderNoData}
|
||||
{showConfirmOverwrite && (
|
||||
<Confirm
|
||||
title={t('appDebug.generate.overwriteTitle')}
|
||||
content={t('appDebug.generate.overwriteMessage')}
|
||||
title={t('appDebug.automatic.overwriteTitle')}
|
||||
content={t('appDebug.automatic.overwriteMessage')}
|
||||
isShow={showConfirmOverwrite}
|
||||
onClose={() => setShowConfirmOverwrite(false)}
|
||||
onConfirm={() => {
|
||||
|
@ -1,7 +0,0 @@
|
||||
.textGradient {
|
||||
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.5" d="M10.5402 2.95679L10.5402 2.95685C10.4455 3.05146 10.3424 3.13459 10.2314 3.2072C10.3429 3.27923 10.4468 3.36165 10.5422 3.45535L10.5402 2.95679ZM10.5402 2.95679C10.6348 2.86217 10.718 2.75907 10.7906 2.64807C10.8626 2.75955 10.945 2.86339 11.0387 2.95881L11.0388 2.95888C11.1304 3.05224 11.2302 3.13482 11.3377 3.20717C11.2297 3.27895 11.1292 3.36081 11.0367 3.45327L11.0366 3.45333C10.9442 3.5458 10.8623 3.64635 10.7905 3.75431M10.5402 2.95679L10.7905 3.75431M10.7905 3.75431C10.7182 3.64686 10.6356 3.54707 10.5422 3.45538L10.7905 3.75431Z" stroke="#155EEF" stroke-width="1.25"/>
|
||||
<path d="M6.99659 2.85105C6.96323 2.55641 6.71414 2.33368 6.41758 2.33337C6.12107 2.33307 5.87146 2.55529 5.83751 2.84987C5.67932 4.2213 5.27205 5.16213 4.6339 5.80028C3.99575 6.43841 3.05492 6.84569 1.68349 7.00389C1.3889 7.03784 1.16669 7.28745 1.16699 7.58396C1.1673 7.88052 1.39002 8.12961 1.68467 8.16297C3.03291 8.31569 3.99517 8.72292 4.64954 9.36546C5.30035 10.0045 5.71535 10.944 5.83593 12.3017C5.86271 12.6029 6.11523 12.8337 6.41763 12.8334C6.72009 12.833 6.97209 12.6016 6.99817 12.3003C7.11367 10.9656 7.52836 10.005 8.18344 9.34982C8.83858 8.69474 9.79922 8.28005 11.1339 8.16455C11.4352 8.13847 11.6666 7.88647 11.667 7.58402C11.6673 7.28162 11.4365 7.02909 11.1353 7.00232C9.77758 6.88174 8.83812 6.46676 8.19908 5.81592C7.55653 5.16155 7.14931 4.19929 6.99659 2.85105Z" fill="#155EEF"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,37 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "14",
|
||||
"height": "14",
|
||||
"viewBox": "0 0 14 14",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"opacity": "0.5",
|
||||
"d": "M10.5402 2.95679L10.5402 2.95685C10.4455 3.05146 10.3424 3.13459 10.2314 3.2072C10.3429 3.27923 10.4468 3.36165 10.5422 3.45535L10.5402 2.95679ZM10.5402 2.95679C10.6348 2.86217 10.718 2.75907 10.7906 2.64807C10.8626 2.75955 10.945 2.86339 11.0387 2.95881L11.0388 2.95888C11.1304 3.05224 11.2302 3.13482 11.3377 3.20717C11.2297 3.27895 11.1292 3.36081 11.0367 3.45327L11.0366 3.45333C10.9442 3.5458 10.8623 3.64635 10.7905 3.75431M10.5402 2.95679L10.7905 3.75431M10.7905 3.75431C10.7182 3.64686 10.6356 3.54707 10.5422 3.45538L10.7905 3.75431Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "1.25"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M6.99659 2.85105C6.96323 2.55641 6.71414 2.33368 6.41758 2.33337C6.12107 2.33307 5.87146 2.55529 5.83751 2.84987C5.67932 4.2213 5.27205 5.16213 4.6339 5.80028C3.99575 6.43841 3.05492 6.84569 1.68349 7.00389C1.3889 7.03784 1.16669 7.28745 1.16699 7.58396C1.1673 7.88052 1.39002 8.12961 1.68467 8.16297C3.03291 8.31569 3.99517 8.72292 4.64954 9.36546C5.30035 10.0045 5.71535 10.944 5.83593 12.3017C5.86271 12.6029 6.11523 12.8337 6.41763 12.8334C6.72009 12.833 6.97209 12.6016 6.99817 12.3003C7.11367 10.9656 7.52836 10.005 8.18344 9.34982C8.83858 8.69474 9.79922 8.28005 11.1339 8.16455C11.4352 8.13847 11.6666 7.88647 11.667 7.58402C11.6673 7.28162 11.4365 7.02909 11.1353 7.00232C9.77758 6.88174 8.83812 6.46676 8.19908 5.81592C7.55653 5.16155 7.14931 4.19929 6.99659 2.85105Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Generator"
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Generator.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Generator'
|
||||
|
||||
export default Icon
|
@ -1 +0,0 @@
|
||||
export { default as Generator } from './Generator'
|
@ -33,7 +33,7 @@
|
||||
"attributes": {
|
||||
"d": "M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z",
|
||||
"fill": "currentColor",
|
||||
"fill-opacity": "0.5"
|
||||
"fill-opacity": "0"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import Link from 'next/link'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import Indicator from '../indicator'
|
||||
import AccountAbout from '../account-about'
|
||||
import { mailToSupport } from '../utils/util'
|
||||
import WorkplaceSelector from './workplace-selector'
|
||||
import classNames from '@/utils/classnames'
|
||||
import I18n from '@/context/i18n'
|
||||
@ -18,6 +19,9 @@ import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows
|
||||
import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { LanguagesSupported } from '@/i18n/language'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
|
||||
export type IAppSelecotr = {
|
||||
isMobile: boolean
|
||||
}
|
||||
@ -34,6 +38,8 @@ export default function AppSelector({ isMobile }: IAppSelecotr) {
|
||||
const { t } = useTranslation()
|
||||
const { userProfile, langeniusVersionInfo } = useAppContext()
|
||||
const { setShowAccountSettingModal } = useModalContext()
|
||||
const { plan } = useProviderContext()
|
||||
const canEmailSupport = plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout({
|
||||
@ -105,6 +111,15 @@ export default function AppSelector({ isMobile }: IAppSelecotr) {
|
||||
<div>{t('common.userProfile.settings')}</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
{canEmailSupport && <Menu.Item>
|
||||
<a
|
||||
className={classNames(itemClassName, 'group justify-between')}
|
||||
href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
|
||||
target='_blank' rel='noopener noreferrer'>
|
||||
<div>{t('common.userProfile.emailSupport')}</div>
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
|
||||
</a>
|
||||
</Menu.Item>}
|
||||
<Menu.Item>
|
||||
<Link
|
||||
className={classNames(itemClassName, 'group justify-between')}
|
||||
|
25
web/app/components/header/utils/util.ts
Normal file
25
web/app/components/header/utils/util.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export const generateMailToLink = (email: string, subject?: string, body?: string): string => {
|
||||
let mailtoLink = `mailto:${email}`
|
||||
|
||||
if (subject)
|
||||
mailtoLink += `?subject=${encodeURIComponent(subject)}`
|
||||
|
||||
if (body)
|
||||
mailtoLink += `&body=${encodeURIComponent(body)}`
|
||||
|
||||
return mailtoLink
|
||||
}
|
||||
|
||||
export const mailToSupport = (account: string, plan: string, version: string) => {
|
||||
const subject = `Technical Support Request ${plan} ${account}`
|
||||
const body = `
|
||||
Please do not remove the following information:
|
||||
-----------------------------------------------
|
||||
Current Plan: ${plan}
|
||||
Account: ${account}
|
||||
Version: ${version}
|
||||
Platform:
|
||||
Problem Description:
|
||||
`
|
||||
return generateMailToLink('support@dify.ai', subject, body)
|
||||
}
|
@ -16,7 +16,6 @@ import type {
|
||||
|
||||
import Wrap from '../editor/wrap'
|
||||
import { CodeLanguage } from '../../../code/types'
|
||||
import PromptGeneratorBtn from '../../../llm/components/prompt-generator-btn'
|
||||
import cn from '@/utils/classnames'
|
||||
import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn'
|
||||
import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend'
|
||||
@ -56,8 +55,6 @@ type Props = {
|
||||
}
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
isSupportPromptGenerator?: boolean
|
||||
onGenerated?: (prompt: string) => void
|
||||
// for jinja
|
||||
isSupportJinja?: boolean
|
||||
editionType?: EditionType
|
||||
@ -83,13 +80,11 @@ const Editor: FC<Props> = ({
|
||||
hasSetBlockStatus,
|
||||
nodesOutputVars,
|
||||
availableNodes = [],
|
||||
isSupportPromptGenerator,
|
||||
isSupportJinja,
|
||||
editionType,
|
||||
onEditionTypeChange,
|
||||
varList = [],
|
||||
handleAddVariable,
|
||||
onGenerated,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
@ -129,10 +124,6 @@ const Editor: FC<Props> = ({
|
||||
<div className='leading-4 text-xs font-semibold text-gray-700 uppercase'>{title}</div>
|
||||
<div className='flex items-center'>
|
||||
<div className='leading-[18px] text-xs font-medium text-gray-500'>{value?.length || 0}</div>
|
||||
{isSupportPromptGenerator && (
|
||||
<PromptGeneratorBtn className='ml-[5px]' onGenerated={onGenerated} />
|
||||
)}
|
||||
|
||||
<div className='w-px h-3 ml-2 mr-2 bg-gray-200'></div>
|
||||
{/* Operations */}
|
||||
<div className='flex items-center space-x-2'>
|
||||
|
@ -1,12 +1,11 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { uniqueId } from 'lodash-es'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiQuestionLine } from '@remixicon/react'
|
||||
import type { PromptItem, Variable } from '../../../types'
|
||||
import { EditionType } from '../../../types'
|
||||
import { useWorkflowStore } from '../../../store'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
@ -79,20 +78,11 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
handleAddVariable,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const {
|
||||
setControlPromptEditorRerenderKey,
|
||||
} = workflowStore.getState()
|
||||
const [instanceId, setInstanceId] = useState(uniqueId())
|
||||
useEffect(() => {
|
||||
setInstanceId(`${id}-${uniqueId()}`)
|
||||
}, [id])
|
||||
|
||||
const handleGenerated = useCallback((prompt: string) => {
|
||||
onPromptChange(prompt)
|
||||
setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
|
||||
}, [onPromptChange, setControlPromptEditorRerenderKey])
|
||||
|
||||
return (
|
||||
<Editor
|
||||
className={className}
|
||||
@ -136,8 +126,6 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodes}
|
||||
isSupportPromptGenerator={payload.role === PromptRole.system}
|
||||
onGenerated={handleGenerated}
|
||||
isSupportJinja
|
||||
editionType={payload.edition_type}
|
||||
onEditionTypeChange={onEditionTypeChange}
|
||||
|
@ -8,7 +8,6 @@ import { v4 as uuid4 } from 'uuid'
|
||||
import type { PromptItem, ValueSelector, Var, Variable } from '../../../types'
|
||||
import { EditionType, PromptRole } from '../../../types'
|
||||
import useAvailableVarList from '../../_base/hooks/use-available-var-list'
|
||||
import { useWorkflowStore } from '../../../store'
|
||||
import ConfigPromptItem from './config-prompt-item'
|
||||
import cn from '@/utils/classnames'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
@ -49,10 +48,6 @@ const ConfigPrompt: FC<Props> = ({
|
||||
handleAddVariable,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const {
|
||||
setControlPromptEditorRerenderKey,
|
||||
} = workflowStore.getState()
|
||||
const payloadWithIds = (isChatModel && Array.isArray(payload))
|
||||
? payload.map((item) => {
|
||||
const id = uuid4()
|
||||
@ -129,11 +124,6 @@ const ConfigPrompt: FC<Props> = ({
|
||||
onChange(newPrompt)
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleGenerated = useCallback((prompt: string) => {
|
||||
handleCompletionPromptChange(prompt)
|
||||
setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
|
||||
}, [handleCompletionPromptChange, setControlPromptEditorRerenderKey])
|
||||
|
||||
const handleCompletionEditionTypeChange = useCallback((editionType: EditionType) => {
|
||||
const newPrompt = produce(payload as PromptItem, (draft) => {
|
||||
draft.edition_type = editionType
|
||||
@ -201,8 +191,10 @@ const ConfigPrompt: FC<Props> = ({
|
||||
handleAddVariable={handleAddVariable}
|
||||
/>
|
||||
</div>
|
||||
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
</ReactSortable>
|
||||
</div>
|
||||
@ -227,13 +219,11 @@ const ConfigPrompt: FC<Props> = ({
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
isSupportPromptGenerator
|
||||
isSupportJinja
|
||||
editionType={(payload as PromptItem).edition_type}
|
||||
varList={varList}
|
||||
onEditionTypeChange={handleCompletionEditionTypeChange}
|
||||
handleAddVariable={handleAddVariable}
|
||||
onGenerated={handleGenerated}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,42 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import cn from 'classnames'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
|
||||
import { AppType } from '@/types/app'
|
||||
import type { AutomaticRes } from '@/service/debug'
|
||||
type Props = {
|
||||
className?: string
|
||||
onGenerated?: (prompt: string) => void
|
||||
}
|
||||
|
||||
const PromptGeneratorBtn: FC<Props> = ({
|
||||
className,
|
||||
onGenerated,
|
||||
}) => {
|
||||
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
|
||||
const handleAutomaticRes = useCallback((res: AutomaticRes) => {
|
||||
onGenerated?.(res.prompt)
|
||||
showAutomaticFalse()
|
||||
}, [onGenerated, showAutomaticFalse])
|
||||
return (
|
||||
<div className={cn(className)}>
|
||||
<div className='p-[5px] rounded-md hover:bg-[#155EEF]/8 cursor-pointer' onClick={showAutomaticTrue}>
|
||||
<Generator className='w-3.5 h-3.5 text-primary-600' />
|
||||
</div>
|
||||
{showAutomatic && (
|
||||
<GetAutomaticResModal
|
||||
mode={AppType.chat}
|
||||
isShow={showAutomatic}
|
||||
onClose={showAutomaticFalse}
|
||||
onFinished={handleAutomaticRes}
|
||||
isInLLMNode
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(PromptGeneratorBtn)
|
@ -24,7 +24,7 @@ const translation = {
|
||||
resetConfig: 'Zurücksetzen',
|
||||
debugConfig: 'Debuggen',
|
||||
addFeature: 'Funktion hinzufügen',
|
||||
automatic: 'Generieren',
|
||||
automatic: 'Automatisch',
|
||||
stopResponding: 'Antworten stoppen',
|
||||
agree: 'gefällt mir',
|
||||
disagree: 'gefällt mir nicht',
|
||||
@ -199,6 +199,23 @@ const translation = {
|
||||
},
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: 'Automatisierte Anwendungsorchestrierung',
|
||||
description: 'Beschreiben Sie Ihr Szenario, Dify wird eine Anwendung für Sie orchestrieren.',
|
||||
intendedAudience: 'Wer ist die Zielgruppe?',
|
||||
intendedAudiencePlaceHolder: 'z.B. Student',
|
||||
solveProblem: 'Welche Probleme hoffen sie, dass KI für sie lösen kann?',
|
||||
solveProblemPlaceHolder: 'z.B. Erkenntnisse extrahieren und Informationen aus langen Berichten und Artikeln zusammenfassen',
|
||||
generate: 'Generieren',
|
||||
audiencesRequired: 'Zielgruppe erforderlich',
|
||||
problemRequired: 'Problem erforderlich',
|
||||
resTitle: 'Wir haben die folgende Anwendung für Sie orchestriert.',
|
||||
apply: 'Diese Orchestrierung anwenden',
|
||||
noData: 'Beschreiben Sie Ihren Anwendungsfall links, die Orchestrierungsvorschau wird hier angezeigt.',
|
||||
loading: 'Orchestrieren der Anwendung für Sie...',
|
||||
overwriteTitle: 'Bestehende Konfiguration überschreiben?',
|
||||
overwriteMessage: 'Das Anwenden dieser Orchestrierung wird die bestehende Konfiguration überschreiben.',
|
||||
},
|
||||
resetConfig: {
|
||||
title: 'Zurücksetzen bestätigen?',
|
||||
message:
|
||||
|
@ -119,6 +119,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Einstellungen',
|
||||
emailSupport: 'E-Mail-Support',
|
||||
workspace: 'Arbeitsbereich',
|
||||
createWorkspace: 'Arbeitsbereich erstellen',
|
||||
helpCenter: 'Hilfe',
|
||||
|
@ -24,7 +24,7 @@ const translation = {
|
||||
resetConfig: 'Reset',
|
||||
debugConfig: 'Debug',
|
||||
addFeature: 'Add Feature',
|
||||
automatic: 'Generate',
|
||||
automatic: 'Automatic',
|
||||
stopResponding: 'Stop responding',
|
||||
agree: 'like',
|
||||
disagree: 'dislike',
|
||||
@ -199,58 +199,22 @@ const translation = {
|
||||
},
|
||||
},
|
||||
},
|
||||
generate: {
|
||||
title: 'Prompt Generator',
|
||||
description: 'The prompt generator can convert input task instructions into high-quality, structured prompts. Please write clear and specific instructions as much detail as possible. The quality of the generated prompts depends on the inference model you choose.',
|
||||
tryIt: 'Try it',
|
||||
instruction: 'Instructions',
|
||||
instructionPlaceHolder: 'Write clear and specific instructions.',
|
||||
automatic: {
|
||||
title: 'Automated application orchestration',
|
||||
description: 'Describe your scenario, Dify will orchestrate an application for you.',
|
||||
intendedAudience: 'Who is the intended audience?',
|
||||
intendedAudiencePlaceHolder: 'e.g. Student',
|
||||
solveProblem: 'What problems do they hope AI can solve for them?',
|
||||
solveProblemPlaceHolder: 'e.g. Extract insights and summarize information from long reports and articles',
|
||||
generate: 'Generate',
|
||||
resTitle: 'Generated Prompt',
|
||||
noDataLine1: 'Describe your use case on the left,',
|
||||
noDataLine2: 'the orchestration preview will show here.',
|
||||
apply: 'Apply',
|
||||
audiencesRequired: 'Audiences required',
|
||||
problemRequired: 'Problem required',
|
||||
resTitle: 'We have orchestrated the following application for you.',
|
||||
apply: 'Apply this orchestration',
|
||||
noData: 'Describe your use case on the left, the orchestration preview will show here.',
|
||||
loading: 'Orchestrating the application for you...',
|
||||
overwriteTitle: 'Override existing configuration?',
|
||||
overwriteMessage: 'Applying this prompt will override existing configuration.',
|
||||
template: {
|
||||
pythonDebugger: {
|
||||
name: 'Python debugger',
|
||||
instruction: 'A bot that can generate and debug your code based on your instruction',
|
||||
},
|
||||
translation: {
|
||||
name: 'Translation',
|
||||
instruction: 'A translator that can translate multiple languages',
|
||||
},
|
||||
professionalAnalyst: {
|
||||
name: 'Professional analyst',
|
||||
instruction: 'Extract insights, identify risk and distill key information from long reports into single memo',
|
||||
},
|
||||
excelFormulaExpert: {
|
||||
name: 'Excel formula expert',
|
||||
instruction: 'A chatbot that can help novice users understand, use and create Excel formulas based on user instructions',
|
||||
},
|
||||
travelPlanning: {
|
||||
name: 'Travel planning',
|
||||
instruction: 'The Travel Planning Assistant is an intelligent tool designed to help users effortlessly plan their trips',
|
||||
},
|
||||
SQLSorcerer: {
|
||||
name: 'SQL sorcerer',
|
||||
instruction: 'Transform everyday language into SQL queries',
|
||||
},
|
||||
GitGud: {
|
||||
name: 'Git gud',
|
||||
instruction: 'Generate appropriate Git commands based on user described version control actions',
|
||||
},
|
||||
meetingTakeaways: {
|
||||
name: 'Meeting takeaways',
|
||||
instruction: 'Distill meetings into concise summaries including discussion topics, key takeaways, and action items',
|
||||
},
|
||||
writingsPolisher: {
|
||||
name: 'Writing polisher',
|
||||
instruction: 'Use advanced copyediting techniques to improve your writings',
|
||||
},
|
||||
},
|
||||
overwriteMessage: 'Applying this orchestration will override existing configuration.',
|
||||
},
|
||||
resetConfig: {
|
||||
title: 'Confirm reset?',
|
||||
|
@ -124,6 +124,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Settings',
|
||||
emailSupport: 'Email Support',
|
||||
workspace: 'Workspace',
|
||||
createWorkspace: 'Create Workspace',
|
||||
helpCenter: 'Help',
|
||||
|
@ -124,6 +124,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Configuraciones',
|
||||
emailSupport: 'Soporte de Correo Electrónico',
|
||||
workspace: 'Espacio de trabajo',
|
||||
createWorkspace: 'Crear espacio de trabajo',
|
||||
helpCenter: 'Ayuda',
|
||||
|
@ -24,7 +24,7 @@ const translation = {
|
||||
resetConfig: 'Réinitialiser',
|
||||
debugConfig: 'Déboguer',
|
||||
addFeature: 'Ajouter une fonctionnalité',
|
||||
automatic: 'Générer',
|
||||
automatic: 'Automatique',
|
||||
stopResponding: 'Arrêtez de répondre',
|
||||
agree: 'comme',
|
||||
disagree: 'déteste',
|
||||
@ -199,6 +199,23 @@ const translation = {
|
||||
},
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: 'Orchestration automatique d\'application',
|
||||
description: 'Décrivez votre scénario, Dify orchestrera une application pour vous.',
|
||||
intendedAudience: 'Qui est le public cible ?',
|
||||
intendedAudiencePlaceHolder: 'par exemple. Étudiant',
|
||||
solveProblem: 'Quels problèmes espèrent-ils que l\'IA peut résoudre pour eux ?',
|
||||
solveProblemPlaceHolder: 'par exemple, Évaluation des performances académiques',
|
||||
generate: 'Générer',
|
||||
audiencesRequired: 'Audiences requises',
|
||||
problemRequired: 'Problème requis',
|
||||
resTitle: 'Nous avons orchestré l\'application suivante pour vous.',
|
||||
apply: 'Appliquez cette orchestration',
|
||||
noData: 'Décrivez votre cas d\'utilisation sur la gauche, l\'aperçu de l\'orchestration s\'affichera ici.',
|
||||
loading: 'Orchestration de l\'application pour vous...',
|
||||
overwriteTitle: 'Remplacer la configuration existante ?',
|
||||
overwriteMessage: 'L\'application de cette orchestration remplacera la configuration existante.',
|
||||
},
|
||||
resetConfig: {
|
||||
title: 'Confirmer la réinitialisation ?',
|
||||
message:
|
||||
|
@ -119,6 +119,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Paramètres',
|
||||
emailSupport: 'Support par courriel',
|
||||
workspace: 'Espace de travail',
|
||||
createWorkspace: 'Créer un Espace de Travail',
|
||||
helpCenter: 'Aide',
|
||||
|
@ -128,6 +128,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'सेटिंग्स',
|
||||
emailSupport: 'सहायता',
|
||||
workspace: 'वर्कस्पेस',
|
||||
createWorkspace: 'वर्कस्पेस बनाएं',
|
||||
helpCenter: 'सहायता',
|
||||
|
@ -129,6 +129,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Impostazioni',
|
||||
emailSupport: 'Supporto Email',
|
||||
workspace: 'Workspace',
|
||||
createWorkspace: 'Crea Workspace',
|
||||
helpCenter: 'Aiuto',
|
||||
|
@ -202,6 +202,23 @@ const translation = {
|
||||
},
|
||||
|
||||
},
|
||||
automatic: {
|
||||
title: '自動アプリケーションオーケストレーション',
|
||||
description: 'シナリオを説明してください。Difyがアプリケーションをあなたのためにオーケストレートします。',
|
||||
intendedAudience: '誰が想定されるターゲットですか?',
|
||||
intendedAudiencePlaceHolder: '例:学生',
|
||||
solveProblem: 'どのような問題をAIが解決できると期待していますか?',
|
||||
solveProblemPlaceHolder: '例:学業成績の評価',
|
||||
generate: '生成',
|
||||
audiencesRequired: 'ターゲットが必要です',
|
||||
problemRequired: '問題が必要です',
|
||||
resTitle: '次のアプリケーションをあなたのためにオーケストレートしました。',
|
||||
apply: 'このオーケストレーションを適用する',
|
||||
noData: '左側にユースケースを記述し、オーケストレーションプレビューがここに表示されます。',
|
||||
loading: 'アプリケーションのオーケストレーションを実行しています...',
|
||||
overwriteTitle: '既存の構成を上書きしますか?',
|
||||
overwriteMessage: 'このオーケストレーションを適用すると、既存の構成が上書きされます。',
|
||||
},
|
||||
resetConfig: {
|
||||
title: 'リセットを確認しますか?',
|
||||
message: '変更が破棄され、最後に公開された構成が復元されます。',
|
||||
|
@ -119,6 +119,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: '設定',
|
||||
emailSupport: 'サポート',
|
||||
workspace: 'ワークスペース',
|
||||
createWorkspace: 'ワークスペースを作成',
|
||||
helpCenter: 'ヘルプ',
|
||||
|
@ -115,6 +115,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: '설정',
|
||||
emailSupport: '이메일 지원',
|
||||
workspace: '작업 공간',
|
||||
createWorkspace: '작업 공간 만들기',
|
||||
helpCenter: '도움말 센터',
|
||||
|
@ -124,6 +124,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Ustawienia',
|
||||
emailSupport: 'Wsparcie e-mail',
|
||||
workspace: 'Przestrzeń robocza',
|
||||
createWorkspace: 'Utwórz przestrzeń roboczą',
|
||||
helpCenter: 'Pomoc',
|
||||
|
@ -119,6 +119,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Configurações',
|
||||
emailSupport: 'Suporte por e-mail',
|
||||
workspace: 'Espaço de trabalho',
|
||||
createWorkspace: 'Criar Espaço de Trabalho',
|
||||
helpCenter: 'Ajuda',
|
||||
|
@ -118,6 +118,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Setări',
|
||||
emailSupport: 'Suport prin email',
|
||||
workspace: 'Spațiu de lucru',
|
||||
createWorkspace: 'Creează Spațiu de lucru',
|
||||
helpCenter: 'Ajutor',
|
||||
|
@ -119,6 +119,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Налаштування',
|
||||
emailSupport: 'Підтримка по електронній пошті',
|
||||
workspace: 'Робочий простір',
|
||||
createWorkspace: 'Створити робочий простір',
|
||||
helpCenter: 'Довідковий центр',
|
||||
|
@ -118,6 +118,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: 'Cài đặt',
|
||||
emailSupport: 'Hỗ trợ qua Email',
|
||||
workspace: 'Không gian làm việc',
|
||||
createWorkspace: 'Tạo Không gian làm việc',
|
||||
helpCenter: 'Trung tâm trợ giúp',
|
||||
|
@ -24,7 +24,7 @@ const translation = {
|
||||
resetConfig: '重置',
|
||||
debugConfig: '调试',
|
||||
addFeature: '添加功能',
|
||||
automatic: '生成',
|
||||
automatic: '自动编排',
|
||||
stopResponding: '停止响应',
|
||||
agree: '赞同',
|
||||
disagree: '反对',
|
||||
@ -199,59 +199,22 @@ const translation = {
|
||||
},
|
||||
},
|
||||
},
|
||||
generate: {
|
||||
title: '提示生成器',
|
||||
description: '提示生成器可以将输入的任务指令转换为高质量、结构化的提示。请尽可能详细地编写清晰、具体的指令。生成的提示的质量取决于您选择的推理模型。',
|
||||
tryIt: '试一试',
|
||||
instruction: '指令',
|
||||
instructionPlaceHolder: '写下清晰、具体的说明。',
|
||||
automatic: {
|
||||
title: '自动编排',
|
||||
description: '描述您的场景,Dify 将为您编排一个应用。',
|
||||
intendedAudience: '目标用户是谁?',
|
||||
intendedAudiencePlaceHolder: '例如:学生',
|
||||
solveProblem: '希望 AI 为他们解决什么问题?',
|
||||
solveProblemPlaceHolder: '例如:评估学业水平',
|
||||
generate: '生成',
|
||||
resTitle: '生成的提示词',
|
||||
noDataLine1: '在左侧描述您的用例,',
|
||||
noDataLine2: '编排预览将在此处显示。',
|
||||
audiencesRequired: '目标用户必填',
|
||||
problemRequired: '解决问题必填',
|
||||
resTitle: '我们为您编排了以下应用程序',
|
||||
apply: '应用',
|
||||
noData: '在左侧描述您的用例,编排预览将在此处显示。',
|
||||
loading: '为您编排应用程序中…',
|
||||
overwriteTitle: '覆盖现有配置?',
|
||||
overwriteMessage: '应用此提示将覆盖现有配置。',
|
||||
template: {
|
||||
pythonDebugger: {
|
||||
name: 'Python 代码助手',
|
||||
instruction: '一个帮你写和纠错程序的机器人',
|
||||
},
|
||||
translation: {
|
||||
name: '翻译机器人',
|
||||
instruction: '一个可以翻译多种语言的翻译器',
|
||||
},
|
||||
professionalAnalyst: {
|
||||
name: '职业分析师',
|
||||
instruction: ' 从长篇报告中提取洞察、识别风险并提炼关键信息',
|
||||
},
|
||||
excelFormulaExpert: {
|
||||
name: 'Excel 公式专家',
|
||||
instruction: '一个可以让小白用户理解、使用和创建 Excel 公式的对话机器人',
|
||||
},
|
||||
travelPlanning: {
|
||||
name: '旅行规划助手',
|
||||
instruction: '旅行规划助手是一个智能工具,旨在帮助用户轻松规划他们的旅行',
|
||||
},
|
||||
SQLSorcerer: {
|
||||
name: 'SQL 生成',
|
||||
instruction: '把自然语言转换成 SQL 查询语句',
|
||||
},
|
||||
GitGud: {
|
||||
name: 'Git 大师',
|
||||
instruction: '从用户提出的版本管理需求生成合适的 Git 命令',
|
||||
},
|
||||
meetingTakeaways: {
|
||||
name: '总结会议纪要',
|
||||
instruction: '将会议内容提炼总结,包括讨论主题、关键要点和待办事项',
|
||||
},
|
||||
writingsPolisher: {
|
||||
name: '润色文章',
|
||||
instruction: '用地道的编辑技巧改进我的文章',
|
||||
},
|
||||
},
|
||||
overwriteMessage: '应用此编排将覆盖现有配置。',
|
||||
},
|
||||
resetConfig: {
|
||||
title: '确认重置?',
|
||||
|
@ -124,6 +124,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: '设置',
|
||||
emailSupport: '邮件支持',
|
||||
workspace: '工作空间',
|
||||
createWorkspace: '创建工作空间',
|
||||
helpCenter: '帮助文档',
|
||||
|
@ -24,7 +24,7 @@ const translation = {
|
||||
resetConfig: '重置',
|
||||
debugConfig: '除錯',
|
||||
addFeature: '新增功能',
|
||||
automatic: '產生',
|
||||
automatic: '自動編排',
|
||||
stopResponding: '停止響應',
|
||||
agree: '贊同',
|
||||
disagree: '反對',
|
||||
@ -199,6 +199,23 @@ const translation = {
|
||||
},
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: '自動編排',
|
||||
description: '描述您的場景,Dify 將為您編排一個應用。',
|
||||
intendedAudience: '目標使用者是誰?',
|
||||
intendedAudiencePlaceHolder: '例如:學生',
|
||||
solveProblem: '希望 AI 為他們解決什麼問題?',
|
||||
solveProblemPlaceHolder: '例如:評估學業水平',
|
||||
generate: '生成',
|
||||
audiencesRequired: '目標使用者必填',
|
||||
problemRequired: '解決問題必填',
|
||||
resTitle: '我們為您編排了以下應用程式',
|
||||
apply: '應用',
|
||||
noData: '在左側描述您的用例,編排預覽將在此處顯示。',
|
||||
loading: '為您編排應用程式中…',
|
||||
overwriteTitle: '覆蓋現有配置?',
|
||||
overwriteMessage: '應用此編排將覆蓋現有配置。',
|
||||
},
|
||||
resetConfig: {
|
||||
title: '確認重置?',
|
||||
message: '重置將丟失當前頁面所有修改,恢復至上次釋出時的配置',
|
||||
|
@ -119,6 +119,7 @@ const translation = {
|
||||
},
|
||||
userProfile: {
|
||||
settings: '設定',
|
||||
emailSupport: '電子郵件支援',
|
||||
workspace: '工作空間',
|
||||
createWorkspace: '建立工作空間',
|
||||
helpCenter: '幫助文件',
|
||||
|
Loading…
x
Reference in New Issue
Block a user