Compare commits

...
Author SHA1 Message Date
jyong 8d34db912f update tidb batch get endpoint to basic mode 2024-12-06 14:46:38 +08:00
shirochanandGitHub 5093337de1 FEAT: cohere rerank 3.5 model added (#11289) 2024-12-06 09:58:55 +08:00
MatsudaandGitHub f54225568c fix(model_runtime): add vision to Amazon Nova Lite and Pro (#11398) 2024-12-06 09:15:32 +08:00
crazywoolaandGitHub 255ff446ba use md table systax in pr template (#11412) 2024-12-06 09:14:15 +08:00
kurokoboandGitHub 9a0dc4bfdc fix: add elkjs (#11404) 2024-12-06 09:00:48 +08:00
huayaoyue6andGitHub 9d975750bc fix: update DocumentIsPausedError (#11405) 2024-12-06 08:59:23 +08:00
Charlie.WeiGitHubluowei <glpat-EjySCyNjWiLqAED-YmwM>crazywoolacrazywoola
7c979e6490 Update mermaid (#11356)
Co-authored-by: luowei <glpat-EjySCyNjWiLqAED-YmwM>
Co-authored-by: crazywoola <[email protected]>
Co-authored-by: crazywoola <[email protected]>
2024-12-05 20:43:42 +08:00
d60ca1661c chore: translate i18n files (#11389)
Co-authored-by: JzoNgKVO <[email protected]>
2024-12-05 17:55:44 +08:00
euxandGitHub bb62391a4c fix: broken link to knowledge base guide (#11387) 2024-12-05 17:47:11 +08:00
KVOJJJinandGitHub 0b25c0b677 Fix: support file download in workflow result (#11338) 2024-12-05 16:58:39 +08:00
-LAN-andGitHub a5d6082418 chore: bump version to 0.13.1 (#11382)
Signed-off-by: -LAN- <[email protected]>
2024-12-05 15:11:55 +08:00
Warren ChenandGitHub 631cbcd781 [fix] rename yaml files to fit windows (#11379) 2024-12-05 14:38:12 +08:00
Yi XiaoandGitHub 20c4633d2a fix: empty object (conversation variable) editable (#11352) 2024-12-05 13:59:59 +08:00
yihongandGitHub 5669cac16d fix: some typos using typos (#11374)
Signed-off-by: yihong0618 <[email protected]>
2024-12-05 13:24:06 +08:00
Yi XiaoandGitHub 6180762160 fix: bg typo in variable aggregator node (#11376) 2024-12-05 11:46:12 +08:00
376726cf90 [feat] Add AWS Bedrock rerank (#11349)
Co-authored-by: crazywoola <[email protected]>
2024-12-05 11:31:43 +08:00
284bb7ac71 fix: ref attribute in markdown causes page crash (#11369)
Co-authored-by: crazywoola <[email protected]>
2024-12-05 10:15:21 +08:00
Akira NodaandGitHub eca466bdaa chore: fix typo (#11359) 2024-12-05 09:04:30 +08:00
crazywoolaandGitHub d56abec195 Revert "Fix: iteration not in main thread pool" (#11358) 2024-12-04 21:22:22 +08:00
yihongandGitHub 961e25f608 fix: better bedrock message handler close #10976 (#11317)
Signed-off-by: yihong0618 <[email protected]>
2024-12-04 19:46:40 +08:00
138bf698b0 chore: translate i18n files (#11353)
Co-authored-by: douxc <[email protected]>
2024-12-04 19:24:03 +08:00
NFishandGitHub e5bb4cca12 fix: Correct category of 'Workflow' used in Explore Apps. (#11351) 2024-12-04 18:19:12 +08:00
AkaraChenandGitHub 5e2cb0e3a8 feat: add base skeleton component (#11339) 2024-12-04 17:34:55 +08:00
Hash BrownandGitHub 16a65cb367 fix: cannot send message when debug with multiple model with conversa… (#11333) 2024-12-04 16:17:11 +08:00
1bae9b8ff7 update pricing for bedrock nova LLM models (#11336)
Co-authored-by: Yuanbo Li <[email protected]>
2024-12-04 16:16:41 +08:00
JyongandGitHub d7c1f43b49 fix tidb full-text-search vector missed (#11337) 2024-12-04 16:13:23 +08:00
Yi XiaoandGitHub f933af9f57 fix: check valid for number variable (#11334) 2024-12-04 15:46:54 +08:00
非法操作andGitHub 91e1ff5e30 chore: improve zhipu LLM (#11321) 2024-12-04 15:14:30 +08:00
5908e10549 integrate amazon nove llms to dify (#11324)
Co-authored-by: Yuanbo Li <[email protected]>
2024-12-04 15:13:08 +08:00
-LAN-andGitHub 464e6354c5 feat: correct the prompt grammar. (#11328)
Signed-off-by: -LAN- <[email protected]>
2024-12-04 15:12:47 +08:00
非法操作andGitHub d470e55f8c fix: http node download file always image type (#11319) 2024-12-04 12:15:26 +08:00
zxhlyhandGitHub 98a1b01b0c fix: file download in chat (#11322) 2024-12-04 11:10:56 +08:00
JoelandGitHub e240424be5 fix: number variable can not input constant type value in tool config form (#11320) 2024-12-04 10:46:03 +08:00
DDDDD12138andGitHub 1cb5a12abb fix: resolve scrolling issue in workflow-log table (#11302) 2024-12-03 21:29:42 +08:00
KVOJJJinandGitHub ff2a4a6fcd Fix: model params in logs (#11298) 2024-12-03 21:17:55 +08:00
119 changed files with 2385 additions and 1342 deletions
+3 -10
View File
@@ -8,16 +8,9 @@ Please include a summary of the change and which issue is fixed. Please also inc
# Screenshots
<table>
<tr>
<td>Before: </td>
<td>After: </td>
</tr>
<tr>
<td>...</td>
<td>...</td>
</tr>
</table>
| Before | After |
|--------|-------|
| ... | ... |
# Checklist
+4 -4
View File
@@ -259,7 +259,7 @@ def migrate_knowledge_vector_database():
skipped_count = 0
total_count = 0
vector_type = dify_config.VECTOR_STORE
upper_colletion_vector_types = {
upper_collection_vector_types = {
VectorType.MILVUS,
VectorType.PGVECTOR,
VectorType.RELYT,
@@ -267,7 +267,7 @@ def migrate_knowledge_vector_database():
VectorType.ORACLE,
VectorType.ELASTICSEARCH,
}
lower_colletion_vector_types = {
lower_collection_vector_types = {
VectorType.ANALYTICDB,
VectorType.CHROMA,
VectorType.MYSCALE,
@@ -307,7 +307,7 @@ def migrate_knowledge_vector_database():
continue
collection_name = ""
dataset_id = dataset.id
if vector_type in upper_colletion_vector_types:
if vector_type in upper_collection_vector_types:
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
elif vector_type == VectorType.QDRANT:
if dataset.collection_binding_id:
@@ -323,7 +323,7 @@ def migrate_knowledge_vector_database():
else:
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
elif vector_type in lower_colletion_vector_types:
elif vector_type in lower_collection_vector_types:
collection_name = Dataset.gen_collection_name_by_id(dataset_id).lower()
else:
raise ValueError(f"Vector store {vector_type} is not supported.")
+1 -1
View File
@@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
CURRENT_VERSION: str = Field(
description="Dify version",
default="0.13.0",
default="0.13.1",
)
COMMIT_SHA: str = Field(
+1 -1
View File
@@ -2,7 +2,7 @@
Due to the presence of tasks in App Runner that require long execution times, such as LLM generation and external requests, Flask-Sqlalchemy's strategy for database connection pooling is to allocate one connection (transaction) per request. This approach keeps a connection occupied even during non-DB tasks, leading to the inability to acquire new connections during high concurrency requests due to multiple long-running tasks.
Therefore, the database operations in App Runner and Task Pipeline must ensure connections are closed immediately after use, and it's better to pass IDs rather than Model objects to avoid deattach errors.
Therefore, the database operations in App Runner and Task Pipeline must ensure connections are closed immediately after use, and it's better to pass IDs rather than Model objects to avoid detach errors.
Examples:
@@ -91,7 +91,7 @@ class XinferenceProvider(Provider):
"""
```
也可以直接抛出对应Erros,并做如下定义,这样在之后的调用中可以直接抛出`InvokeConnectionError`等异常。
也可以直接抛出对应 Errors,并做如下定义,这样在之后的调用中可以直接抛出`InvokeConnectionError`等异常。
```python
@property
@@ -16,6 +16,7 @@ help:
supported_model_types:
- llm
- text-embedding
- rerank
configurate_methods:
- predefined-model
provider_credential_schema:
@@ -0,0 +1,53 @@
model: amazon.nova-lite-v1:0
label:
en_US: Nova Lite V1
model_type: llm
features:
- agent-thought
- tool-call
- stream-tool-call
- vision
model_properties:
mode: chat
context_size: 300000
parameter_rules:
- name: max_new_tokens
use_template: max_tokens
required: true
default: 2048
min: 1
max: 5000
- name: temperature
use_template: temperature
required: false
type: float
default: 1
min: 0.0
max: 1.0
help:
zh_Hans: 生成内容的随机性。
en_US: The amount of randomness injected into the response.
- name: top_p
required: false
type: float
default: 0.999
min: 0.000
max: 1.000
help:
zh_Hans: 在核采样中,Anthropic Claude 按概率递减顺序计算每个后续标记的所有选项的累积分布,并在达到 top_p 指定的特定概率时将其切断。您应该更改温度或top_p,但不能同时更改两者。
en_US: In nucleus sampling, Anthropic Claude computes the cumulative distribution over all the options for each subsequent token in decreasing probability order and cuts it off once it reaches a particular probability specified by top_p. You should alter either temperature or top_p, but not both.
- name: top_k
required: false
type: int
default: 0
min: 0
# tip docs from aws has error, max value is 500
max: 500
help:
zh_Hans: 对于每个后续标记,仅从前 K 个选项中进行采样。使用 top_k 删除长尾低概率响应。
en_US: Only sample from the top K options for each subsequent token. Use top_k to remove long tail low probability responses.
pricing:
input: '0.00006'
output: '0.00024'
unit: '0.001'
currency: USD
@@ -0,0 +1,52 @@
model: amazon.nova-micro-v1:0
label:
en_US: Nova Micro V1
model_type: llm
features:
- agent-thought
- tool-call
- stream-tool-call
model_properties:
mode: chat
context_size: 128000
parameter_rules:
- name: max_new_tokens
use_template: max_tokens
required: true
default: 2048
min: 1
max: 5000
- name: temperature
use_template: temperature
required: false
type: float
default: 1
min: 0.0
max: 1.0
help:
zh_Hans: 生成内容的随机性。
en_US: The amount of randomness injected into the response.
- name: top_p
required: false
type: float
default: 0.999
min: 0.000
max: 1.000
help:
zh_Hans: 在核采样中,Anthropic Claude 按概率递减顺序计算每个后续标记的所有选项的累积分布,并在达到 top_p 指定的特定概率时将其切断。您应该更改温度或top_p,但不能同时更改两者。
en_US: In nucleus sampling, Anthropic Claude computes the cumulative distribution over all the options for each subsequent token in decreasing probability order and cuts it off once it reaches a particular probability specified by top_p. You should alter either temperature or top_p, but not both.
- name: top_k
required: false
type: int
default: 0
min: 0
# tip docs from aws has error, max value is 500
max: 500
help:
zh_Hans: 对于每个后续标记,仅从前 K 个选项中进行采样。使用 top_k 删除长尾低概率响应。
en_US: Only sample from the top K options for each subsequent token. Use top_k to remove long tail low probability responses.
pricing:
input: '0.000035'
output: '0.00014'
unit: '0.001'
currency: USD
@@ -0,0 +1,53 @@
model: amazon.nova-pro-v1:0
label:
en_US: Nova Pro V1
model_type: llm
features:
- agent-thought
- tool-call
- stream-tool-call
- vision
model_properties:
mode: chat
context_size: 300000
parameter_rules:
- name: max_new_tokens
use_template: max_tokens
required: true
default: 2048
min: 1
max: 5000
- name: temperature
use_template: temperature
required: false
type: float
default: 1
min: 0.0
max: 1.0
help:
zh_Hans: 生成内容的随机性。
en_US: The amount of randomness injected into the response.
- name: top_p
required: false
type: float
default: 0.999
min: 0.000
max: 1.000
help:
zh_Hans: 在核采样中,Anthropic Claude 按概率递减顺序计算每个后续标记的所有选项的累积分布,并在达到 top_p 指定的特定概率时将其切断。您应该更改温度或top_p,但不能同时更改两者。
en_US: In nucleus sampling, Anthropic Claude computes the cumulative distribution over all the options for each subsequent token in decreasing probability order and cuts it off once it reaches a particular probability specified by top_p. You should alter either temperature or top_p, but not both.
- name: top_k
required: false
type: int
default: 0
min: 0
# tip docs from aws has error, max value is 500
max: 500
help:
zh_Hans: 对于每个后续标记,仅从前 K 个选项中进行采样。使用 top_k 删除长尾低概率响应。
en_US: Only sample from the top K options for each subsequent token. Use top_k to remove long tail low probability responses.
pricing:
input: '0.0008'
output: '0.0032'
unit: '0.001'
currency: USD
@@ -70,6 +70,8 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
{"prefix": "cohere.command-r", "support_system_prompts": True, "support_tool_use": True},
{"prefix": "amazon.titan", "support_system_prompts": False, "support_tool_use": False},
{"prefix": "ai21.jamba-1-5", "support_system_prompts": True, "support_tool_use": False},
{"prefix": "amazon.nova", "support_system_prompts": True, "support_tool_use": False},
{"prefix": "us.amazon.nova", "support_system_prompts": True, "support_tool_use": False},
]
@staticmethod
@@ -194,6 +196,13 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
if model_info["support_tool_use"] and tools:
parameters["toolConfig"] = self._convert_converse_tool_config(tools=tools)
try:
# for issue #10976
conversations_list = parameters["messages"]
# if two consecutive user messages found, combine them into one message
for i in range(len(conversations_list) - 2, -1, -1):
if conversations_list[i]["role"] == conversations_list[i + 1]["role"]:
conversations_list[i]["content"].extend(conversations_list.pop(i + 1)["content"])
if stream:
response = bedrock_client.converse_stream(**parameters)
return self._handle_converse_stream_response(
@@ -0,0 +1,53 @@
model: us.amazon.nova-lite-v1:0
label:
en_US: Nova Lite V1 (US.Cross Region Inference)
model_type: llm
features:
- agent-thought
- tool-call
- stream-tool-call
- vision
model_properties:
mode: chat
context_size: 300000
parameter_rules:
- name: max_new_tokens
use_template: max_tokens
required: true
default: 2048
min: 1
max: 5000
- name: temperature
use_template: temperature
required: false
type: float
default: 1
min: 0.0
max: 1.0
help:
zh_Hans: 生成内容的随机性。
en_US: The amount of randomness injected into the response.
- name: top_p
required: false
type: float
default: 0.999
min: 0.000
max: 1.000
help:
zh_Hans: 在核采样中,Anthropic Claude 按概率递减顺序计算每个后续标记的所有选项的累积分布,并在达到 top_p 指定的特定概率时将其切断。您应该更改温度或top_p,但不能同时更改两者。
en_US: In nucleus sampling, Anthropic Claude computes the cumulative distribution over all the options for each subsequent token in decreasing probability order and cuts it off once it reaches a particular probability specified by top_p. You should alter either temperature or top_p, but not both.
- name: top_k
required: false
type: int
default: 0
min: 0
# tip docs from aws has error, max value is 500
max: 500
help:
zh_Hans: 对于每个后续标记,仅从前 K 个选项中进行采样。使用 top_k 删除长尾低概率响应。
en_US: Only sample from the top K options for each subsequent token. Use top_k to remove long tail low probability responses.
pricing:
input: '0.00006'
output: '0.00024'
unit: '0.001'
currency: USD
@@ -0,0 +1,52 @@
model: us.amazon.nova-micro-v1:0
label:
en_US: Nova Micro V1 (US.Cross Region Inference)
model_type: llm
features:
- agent-thought
- tool-call
- stream-tool-call
model_properties:
mode: chat
context_size: 128000
parameter_rules:
- name: max_new_tokens
use_template: max_tokens
required: true
default: 2048
min: 1
max: 5000
- name: temperature
use_template: temperature
required: false
type: float
default: 1
min: 0.0
max: 1.0
help:
zh_Hans: 生成内容的随机性。
en_US: The amount of randomness injected into the response.
- name: top_p
required: false
type: float
default: 0.999
min: 0.000
max: 1.000
help:
zh_Hans: 在核采样中,Anthropic Claude 按概率递减顺序计算每个后续标记的所有选项的累积分布,并在达到 top_p 指定的特定概率时将其切断。您应该更改温度或top_p,但不能同时更改两者。
en_US: In nucleus sampling, Anthropic Claude computes the cumulative distribution over all the options for each subsequent token in decreasing probability order and cuts it off once it reaches a particular probability specified by top_p. You should alter either temperature or top_p, but not both.
- name: top_k
required: false
type: int
default: 0
min: 0
# tip docs from aws has error, max value is 500
max: 500
help:
zh_Hans: 对于每个后续标记,仅从前 K 个选项中进行采样。使用 top_k 删除长尾低概率响应。
en_US: Only sample from the top K options for each subsequent token. Use top_k to remove long tail low probability responses.
pricing:
input: '0.000035'
output: '0.00014'
unit: '0.001'
currency: USD
@@ -0,0 +1,53 @@
model: us.amazon.nova-pro-v1:0
label:
en_US: Nova Pro V1 (US.Cross Region Inference)
model_type: llm
features:
- agent-thought
- tool-call
- stream-tool-call
- vision
model_properties:
mode: chat
context_size: 300000
parameter_rules:
- name: max_new_tokens
use_template: max_tokens
required: true
default: 2048
min: 1
max: 5000
- name: temperature
use_template: temperature
required: false
type: float
default: 1
min: 0.0
max: 1.0
help:
zh_Hans: 生成内容的随机性。
en_US: The amount of randomness injected into the response.
- name: top_p
required: false
type: float
default: 0.999
min: 0.000
max: 1.000
help:
zh_Hans: 在核采样中,Anthropic Claude 按概率递减顺序计算每个后续标记的所有选项的累积分布,并在达到 top_p 指定的特定概率时将其切断。您应该更改温度或top_p,但不能同时更改两者。
en_US: In nucleus sampling, Anthropic Claude computes the cumulative distribution over all the options for each subsequent token in decreasing probability order and cuts it off once it reaches a particular probability specified by top_p. You should alter either temperature or top_p, but not both.
- name: top_k
required: false
type: int
default: 0
min: 0
# tip docs from aws has error, max value is 500
max: 500
help:
zh_Hans: 对于每个后续标记,仅从前 K 个选项中进行采样。使用 top_k 删除长尾低概率响应。
en_US: Only sample from the top K options for each subsequent token. Use top_k to remove long tail low probability responses.
pricing:
input: '0.0008'
output: '0.0032'
unit: '0.001'
currency: USD
@@ -0,0 +1,2 @@
- amazon.rerank-v1
- cohere.rerank-v3-5
@@ -0,0 +1,4 @@
model: amazon.rerank-v1:0
model_type: rerank
model_properties:
context_size: 5120
@@ -0,0 +1,4 @@
model: cohere.rerank-v3-5:0
model_type: rerank
model_properties:
context_size: 5120
@@ -0,0 +1,147 @@
from typing import Optional
import boto3
from botocore.config import Config
from core.model_runtime.entities.rerank_entities import RerankDocument, RerankResult
from core.model_runtime.errors.invoke import (
InvokeAuthorizationError,
InvokeBadRequestError,
InvokeConnectionError,
InvokeError,
InvokeRateLimitError,
InvokeServerUnavailableError,
)
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.__base.rerank_model import RerankModel
class BedrockRerankModel(RerankModel):
"""
Model class for Cohere rerank model.
"""
def _invoke(
self,
model: str,
credentials: dict,
query: str,
docs: list[str],
score_threshold: Optional[float] = None,
top_n: Optional[int] = None,
user: Optional[str] = None,
) -> RerankResult:
"""
Invoke rerank model
:param model: model name
:param credentials: model credentials
:param query: search query
:param docs: docs for reranking
:param score_threshold: score threshold
:param top_n: top n
:param user: unique user id
:return: rerank result
"""
if len(docs) == 0:
return RerankResult(model=model, docs=docs)
# initialize client
client_config = Config(region_name=credentials["aws_region"])
bedrock_runtime = boto3.client(
service_name="bedrock-agent-runtime",
config=client_config,
aws_access_key_id=credentials.get("aws_access_key_id", ""),
aws_secret_access_key=credentials.get("aws_secret_access_key"),
)
queries = [{"type": "TEXT", "textQuery": {"text": query}}]
text_sources = []
for text in docs:
text_sources.append(
{
"type": "INLINE",
"inlineDocumentSource": {
"type": "TEXT",
"textDocument": {
"text": text,
},
},
}
)
modelId = model
region = credentials["aws_region"]
model_package_arn = f"arn:aws:bedrock:{region}::foundation-model/{modelId}"
rerankingConfiguration = {
"type": "BEDROCK_RERANKING_MODEL",
"bedrockRerankingConfiguration": {
"numberOfResults": top_n,
"modelConfiguration": {
"modelArn": model_package_arn,
},
},
}
response = bedrock_runtime.rerank(
queries=queries, sources=text_sources, rerankingConfiguration=rerankingConfiguration
)
rerank_documents = []
for idx, result in enumerate(response["results"]):
# format document
index = result["index"]
rerank_document = RerankDocument(
index=index,
text=docs[index],
score=result["relevanceScore"],
)
# score threshold check
if score_threshold is not None:
if rerank_document.score >= score_threshold:
rerank_documents.append(rerank_document)
else:
rerank_documents.append(rerank_document)
return RerankResult(model=model, docs=rerank_documents)
def validate_credentials(self, model: str, credentials: dict) -> None:
"""
Validate model credentials
:param model: model name
:param credentials: model credentials
:return:
"""
try:
self.invoke(
model=model,
credentials=credentials,
query="What is the capital of the United States?",
docs=[
"Carson City is the capital city of the American state of Nevada. At the 2010 United States "
"Census, Carson City had a population of 55,274.",
"The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that "
"are a political division controlled by the United States. Its capital is Saipan.",
],
score_threshold=0.8,
)
except Exception as ex:
raise CredentialsValidateFailedError(str(ex))
@property
def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
"""
Map model invoke error to unified error
The key is the ermd = genai.GenerativeModel(model) error type thrown to the caller
The value is the md = genai.GenerativeModel(model) error type thrown by the model,
which needs to be converted into a unified error type for the caller.
:return: Invoke emd = genai.GenerativeModel(model) error mapping
"""
return {
InvokeConnectionError: [],
InvokeServerUnavailableError: [],
InvokeRateLimitError: [],
InvokeAuthorizationError: [],
InvokeBadRequestError: [],
}
@@ -2,3 +2,4 @@
- rerank-english-v3.0
- rerank-multilingual-v2.0
- rerank-multilingual-v3.0
- rerank-v3.5
@@ -0,0 +1,4 @@
model: rerank-v3.5
model_type: rerank
model_properties:
context_size: 5120
@@ -8,6 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 131072
parameter_rules:
- name: temperature
use_template: temperature
@@ -8,6 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 131072
parameter_rules:
- name: temperature
use_template: temperature
@@ -8,6 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 8192
parameter_rules:
- name: temperature
use_template: temperature
@@ -8,6 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 131072
parameter_rules:
- name: temperature
use_template: temperature
@@ -8,6 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 131072
parameter_rules:
- name: temperature
use_template: temperature
@@ -8,6 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 131072
parameter_rules:
- name: temperature
use_template: temperature
@@ -8,6 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 131072
parameter_rules:
- name: temperature
use_template: temperature
@@ -8,7 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 10240
context_size: 1048576
parameter_rules:
- name: temperature
use_template: temperature
@@ -8,6 +8,7 @@ features:
- stream-tool-call
model_properties:
mode: chat
context_size: 131072
parameter_rules:
- name: temperature
use_template: temperature
@@ -4,6 +4,7 @@ label:
model_type: llm
model_properties:
mode: chat
context_size: 2048
features:
- vision
parameter_rules:
@@ -4,6 +4,7 @@ label:
model_type: llm
model_properties:
mode: chat
context_size: 8192
features:
- vision
- video
@@ -22,18 +22,6 @@ from core.model_runtime.model_providers.__base.large_language_model import Large
from core.model_runtime.model_providers.zhipuai._common import _CommonZhipuaiAI
from core.model_runtime.utils import helper
GLM_JSON_MODE_PROMPT = """You should always follow the instructions and output a valid JSON object.
The structure of the JSON object you can found in the instructions, use {"answer": "$your_answer"} as the default structure
if you are not sure about the structure.
And you should always end the block with a "```" to indicate the end of the JSON object.
<instructions>
{{instructions}}
</instructions>
```JSON""" # noqa: E501
class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
def _invoke(
@@ -64,42 +52,8 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
credentials_kwargs = self._to_credential_kwargs(credentials)
# invoke model
# stop = stop or []
# self._transform_json_prompts(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
return self._generate(model, credentials_kwargs, prompt_messages, model_parameters, tools, stop, stream, user)
# def _transform_json_prompts(self, model: str, credentials: dict,
# prompt_messages: list[PromptMessage], model_parameters: dict,
# tools: list[PromptMessageTool] | None = None, stop: list[str] | None = None,
# stream: bool = True, user: str | None = None) \
# -> None:
# """
# Transform json prompts to model prompts
# """
# if "}\n\n" not in stop:
# stop.append("}\n\n")
# # check if there is a system message
# if len(prompt_messages) > 0 and isinstance(prompt_messages[0], SystemPromptMessage):
# # override the system message
# prompt_messages[0] = SystemPromptMessage(
# content=GLM_JSON_MODE_PROMPT.replace("{{instructions}}", prompt_messages[0].content)
# )
# else:
# # insert the system message
# prompt_messages.insert(0, SystemPromptMessage(
# content=GLM_JSON_MODE_PROMPT.replace("{{instructions}}", "Please output a valid JSON object.")
# ))
# # check if the last message is a user message
# if len(prompt_messages) > 0 and isinstance(prompt_messages[-1], UserPromptMessage):
# # add ```JSON\n to the last message
# prompt_messages[-1].content += "\n```JSON\n"
# else:
# # append a user message
# prompt_messages.append(UserPromptMessage(
# content="```JSON\n"
# ))
def get_num_tokens(
self,
model: str,
@@ -170,7 +124,7 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
:return: full response or stream response chunk generator result
"""
extra_model_kwargs = {}
# request to glm-4v-plus with stop words will always response "finish_reason":"network_error"
# request to glm-4v-plus with stop words will always respond "finish_reason":"network_error"
if stop and model != "glm-4v-plus":
extra_model_kwargs["stop"] = stop
@@ -186,7 +140,7 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
# resolve zhipuai model not support system message and user message, assistant message must be in sequence
new_prompt_messages: list[PromptMessage] = []
for prompt_message in prompt_messages:
copy_prompt_message = prompt_message.copy()
copy_prompt_message = prompt_message.model_copy()
if copy_prompt_message.role in {PromptMessageRole.USER, PromptMessageRole.SYSTEM, PromptMessageRole.TOOL}:
if isinstance(copy_prompt_message.content, list):
# check if model is 'glm-4v'
@@ -238,59 +192,38 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
params = self._construct_glm_4v_parameter(model, new_prompt_messages, model_parameters)
else:
params = {"model": model, "messages": [], **model_parameters}
# glm model
if not model.startswith("chatglm"):
for prompt_message in new_prompt_messages:
if prompt_message.role == PromptMessageRole.TOOL:
for prompt_message in new_prompt_messages:
if prompt_message.role == PromptMessageRole.TOOL:
params["messages"].append(
{
"role": "tool",
"content": prompt_message.content,
"tool_call_id": prompt_message.tool_call_id,
}
)
elif isinstance(prompt_message, AssistantPromptMessage):
if prompt_message.tool_calls:
params["messages"].append(
{
"role": "tool",
"role": "assistant",
"content": prompt_message.content,
"tool_call_id": prompt_message.tool_call_id,
"tool_calls": [
{
"id": tool_call.id,
"type": tool_call.type,
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments,
},
}
for tool_call in prompt_message.tool_calls
],
}
)
elif isinstance(prompt_message, AssistantPromptMessage):
if prompt_message.tool_calls:
params["messages"].append(
{
"role": "assistant",
"content": prompt_message.content,
"tool_calls": [
{
"id": tool_call.id,
"type": tool_call.type,
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments,
},
}
for tool_call in prompt_message.tool_calls
],
}
)
else:
params["messages"].append({"role": "assistant", "content": prompt_message.content})
else:
params["messages"].append(
{"role": prompt_message.role.value, "content": prompt_message.content}
)
else:
# chatglm model
for prompt_message in new_prompt_messages:
# merge system message to user message
if prompt_message.role in {
PromptMessageRole.SYSTEM,
PromptMessageRole.TOOL,
PromptMessageRole.USER,
}:
if len(params["messages"]) > 0 and params["messages"][-1]["role"] == "user":
params["messages"][-1]["content"] += "\n\n" + prompt_message.content
else:
params["messages"].append({"role": "user", "content": prompt_message.content})
else:
params["messages"].append(
{"role": prompt_message.role.value, "content": prompt_message.content}
)
params["messages"].append({"role": "assistant", "content": prompt_message.content})
else:
params["messages"].append({"role": prompt_message.role.value, "content": prompt_message.content})
if tools and len(tools) > 0:
params["tools"] = [{"type": "function", "function": helper.dump_model(tool)} for tool in tools]
@@ -406,7 +339,7 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
Handle llm stream response
:param model: model name
:param response: response
:param responses: response
:param prompt_messages: prompt messages
:return: llm response chunk generator result
"""
@@ -505,7 +438,7 @@ class ZhipuAILargeLanguageModel(_CommonZhipuaiAI, LargeLanguageModel):
if tools and len(tools) > 0:
text += "\n\nTools:"
for tool in tools:
text += f"\n{tool.json()}"
text += f"\n{tool.model_dump_json()}"
# trim off the trailing ' ' that might come from the "Assistant: "
return text.rstrip()
@@ -5,7 +5,7 @@ BAICHUAN_CONTEXT = "用户在与一个客观的助手对话。助手会尊重找
CHAT_APP_COMPLETION_PROMPT_CONFIG = {
"completion_prompt_config": {
"prompt": {
"text": "{{#pre_prompt#}}\nHere is the chat histories between human and assistant, inside <histories></histories> XML tags.\n\n<histories>\n{{#histories#}}\n</histories>\n\n\nHuman: {{#query#}}\n\nAssistant: " # noqa: E501
"text": "{{#pre_prompt#}}\nHere are the chat histories between human and assistant, inside <histories></histories> XML tags.\n\n<histories>\n{{#histories#}}\n</histories>\n\n\nHuman: {{#query#}}\n\nAssistant: " # noqa: E501
},
"conversation_histories_role": {"user_prefix": "Human", "assistant_prefix": "Assistant"},
},
@@ -375,7 +375,6 @@ class TidbOnQdrantVector(BaseVector):
for result in results:
if result:
document = self._document_from_scored_point(result, Field.CONTENT_KEY.value, Field.METADATA_KEY.value)
document.metadata["vector"] = result.vector
documents.append(document)
return documents
@@ -394,6 +393,7 @@ class TidbOnQdrantVector(BaseVector):
) -> Document:
return Document(
page_content=scored_point.payload.get(content_payload_key),
vector=scored_point.vector,
metadata=scored_point.payload.get(metadata_payload_key) or {},
)
@@ -162,7 +162,7 @@ class TidbService:
clusters = []
tidb_serverless_list_map = {item.cluster_id: item for item in tidb_serverless_list}
cluster_ids = [item.cluster_id for item in tidb_serverless_list]
params = {"clusterIds": cluster_ids, "view": "FULL"}
params = {"clusterIds": cluster_ids, "view": "BASIC"}
response = requests.get(
f"{api_url}/clusters:batchGet", params=params, auth=HTTPDigestAuth(public_key, private_key)
)
@@ -6,9 +6,9 @@ identity:
zh_Hans: GitLab 合并请求查询
description:
human:
en_US: A tool for query GitLab merge requests, Input should be a exists reposity or branch.
en_US: A tool for query GitLab merge requests, Input should be a exists repository or branch.
zh_Hans: 一个用于查询 GitLab 代码合并请求的工具,输入的内容应该是一个已存在的仓库名或者分支。
llm: A tool for query GitLab merge requests, Input should be a exists reposity or branch.
llm: A tool for query GitLab merge requests, Input should be a exists repository or branch.
parameters:
- name: repository
type: string
@@ -61,7 +61,7 @@ class WolframAlphaTool(BuiltinTool):
params["input"] = query
else:
finished = True
if "souces" in response_data["queryresult"]:
if "sources" in response_data["queryresult"]:
return self.create_link_message(response_data["queryresult"]["sources"]["url"])
elif "pods" in response_data["queryresult"]:
result = response_data["queryresult"]["pods"][0]["subpods"][0]["plaintext"]
+1 -9
View File
@@ -1,11 +1,9 @@
import logging
from collections.abc import Mapping, Sequence
from mimetypes import guess_extension
from os import path
from typing import Any
from configs import dify_config
from core.file import File, FileTransferMethod, FileType
from core.file import File, FileTransferMethod
from core.tools.tool_file_manager import ToolFileManager
from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_entities import VariableSelector
@@ -150,11 +148,6 @@ class HttpRequestNode(BaseNode[HttpRequestNodeData]):
content = response.content
if is_file and content_type:
# extract filename from url
filename = path.basename(url)
# extract extension if possible
extension = guess_extension(content_type) or ".bin"
tool_file = ToolFileManager.create_file_by_raw(
user_id=self.user_id,
tenant_id=self.tenant_id,
@@ -165,7 +158,6 @@ class HttpRequestNode(BaseNode[HttpRequestNodeData]):
mapping = {
"tool_file_id": tool_file.id,
"type": FileType.IMAGE.value,
"transfer_method": FileTransferMethod.TOOL_FILE.value,
}
file = file_factory.build_from_mapping(
@@ -24,7 +24,7 @@ class IfElseNode(BaseNode[IfElseNodeData]):
"""
node_inputs: dict[str, list] = {"conditions": []}
process_datas: dict[str, list] = {"condition_results": []}
process_data: dict[str, list] = {"condition_results": []}
input_conditions = []
final_result = False
@@ -40,7 +40,7 @@ class IfElseNode(BaseNode[IfElseNodeData]):
operator=case.logical_operator,
)
process_datas["condition_results"].append(
process_data["condition_results"].append(
{
"group": case.model_dump(),
"results": group_result,
@@ -65,7 +65,7 @@ class IfElseNode(BaseNode[IfElseNodeData]):
selected_case_id = "true" if final_result else "false"
process_datas["condition_results"].append(
process_data["condition_results"].append(
{"group": "default", "results": group_result, "final_result": final_result}
)
@@ -73,7 +73,7 @@ class IfElseNode(BaseNode[IfElseNodeData]):
except Exception as e:
return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED, inputs=node_inputs, process_data=process_datas, error=str(e)
status=WorkflowNodeExecutionStatus.FAILED, inputs=node_inputs, process_data=process_data, error=str(e)
)
outputs = {"result": final_result, "selected_case_id": selected_case_id}
@@ -81,7 +81,7 @@ class IfElseNode(BaseNode[IfElseNodeData]):
data = NodeRunResult(
status=WorkflowNodeExecutionStatus.SUCCEEDED,
inputs=node_inputs,
process_data=process_datas,
process_data=process_data,
edge_source_handle=selected_case_id or "false", # Use case ID or 'default'
outputs=outputs,
)
@@ -116,7 +116,7 @@ class IterationNode(BaseNode[IterationNodeData]):
variable_pool.add([self.node_id, "item"], iterator_list_value[0])
# init graph engine
from core.workflow.graph_engine.graph_engine import GraphEngine
from core.workflow.graph_engine.graph_engine import GraphEngine, GraphEngineThreadPool
graph_engine = GraphEngine(
tenant_id=self.tenant_id,
@@ -162,8 +162,7 @@ class IterationNode(BaseNode[IterationNodeData]):
if self.node_data.is_parallel:
futures: list[Future] = []
q = Queue()
thread_pool = graph_engine.workflow_thread_pool_mapping[graph_engine.thread_pool_id]
thread_pool._max_workers = self.node_data.parallel_nums
thread_pool = GraphEngineThreadPool(max_workers=self.node_data.parallel_nums, max_submit_count=100)
for index, item in enumerate(iterator_list_value):
future: Future = thread_pool.submit(
self._run_single_iter_parallel,
+1 -1
View File
@@ -815,7 +815,7 @@ class LLMNode(BaseNode[LLMNodeData]):
"completion_model": {
"conversation_histories_role": {"user_prefix": "Human", "assistant_prefix": "Assistant"},
"prompt": {
"text": "Here is the chat histories between human and assistant, inside "
"text": "Here are the chat histories between human and assistant, inside "
"<histories></histories> XML tags.\n\n<histories>\n{{"
"#histories#}}\n</histories>\n\n\nHuman: {{#sys.query#}}\n\nAssistant:",
"edition_type": "basic",
@@ -98,7 +98,7 @@ Step 3: Structure the extracted parameters to JSON object as specified in <struc
Step 4: Ensure that the JSON object is properly formatted and valid. The output should not contain any XML tags. Only the JSON object should be outputted.
### Memory
Here is the chat histories between human and assistant, inside <histories></histories> XML tags.
Here are the chat histories between human and assistant, inside <histories></histories> XML tags.
<histories>
{histories}
</histories>
@@ -125,7 +125,7 @@ CHAT_GENERATE_JSON_PROMPT = """You should always follow the instructions and out
The structure of the JSON object you can found in the instructions.
### Memory
Here is the chat histories between human and assistant, inside <histories></histories> XML tags.
Here are the chat histories between human and assistant, inside <histories></histories> XML tags.
<histories>
{histories}
</histories>
@@ -8,7 +8,7 @@ QUESTION_CLASSIFIER_SYSTEM_PROMPT = """
### Constraint
DO NOT include anything other than the JSON array in your response.
### Memory
Here is the chat histories between human and assistant, inside <histories></histories> XML tags.
Here are the chat histories between human and assistant, inside <histories></histories> XML tags.
<histories>
{histories}
</histories>
@@ -66,7 +66,7 @@ User:{{"input_text": ["bad service, slow to bring the food"], "categories": [{{"
Assistant:{{"keywords": ["bad service", "slow", "food", "tip", "terrible", "waitresses"],"category_id": "f6ff5bc3-aca0-4e4a-8627-e760d0aca78f","category_name": "Experience"}}
</example>
### Memory
Here is the chat histories between human and assistant, inside <histories></histories> XML tags.
Here are the chat histories between human and assistant, inside <histories></histories> XML tags.
<histories>
{histories}
</histories>
@@ -7,8 +7,8 @@ from .enums import InputType, Operation
class OperationNotSupportedError(VariableOperatorNodeError):
def __init__(self, *, operation: Operation, varialbe_type: str):
super().__init__(f"Operation {operation} is not supported for type {varialbe_type}")
def __init__(self, *, operation: Operation, variable_type: str):
super().__init__(f"Operation {operation} is not supported for type {variable_type}")
class InputTypeNotSupportedError(VariableOperatorNodeError):
@@ -45,7 +45,7 @@ class VariableAssignerNode(BaseNode[VariableAssignerNodeData]):
# Check if operation is supported
if not helpers.is_operation_supported(variable_type=variable.value_type, operation=item.operation):
raise OperationNotSupportedError(operation=item.operation, varialbe_type=variable.value_type)
raise OperationNotSupportedError(operation=item.operation, variable_type=variable.value_type)
# Check if variable input is supported
if item.input_type == InputType.VARIABLE and not helpers.is_variable_input_supported(
@@ -156,4 +156,4 @@ class VariableAssignerNode(BaseNode[VariableAssignerNodeData]):
case Operation.DIVIDE:
return variable.value / value
case _:
raise OperationNotSupportedError(operation=operation, varialbe_type=variable.value_type)
raise OperationNotSupportedError(operation=operation, variable_type=variable.value_type)
+970 -897
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -20,7 +20,7 @@ azure-ai-inference = "~1.0.0b3"
azure-ai-ml = "~1.20.0"
azure-identity = "1.16.1"
beautifulsoup4 = "4.12.2"
boto3 = "1.35.17"
boto3 = "1.35.74"
bs4 = "~0.0.1"
cachetools = "~5.3.0"
celery = "~5.4.0"
+2 -2
View File
@@ -5,7 +5,7 @@ import time
import click
from celery import shared_task
from core.indexing_runner import DocumentIsPausedException
from core.indexing_runner import DocumentIsPausedError
from extensions.ext_database import db
from extensions.ext_storage import storage
from models.dataset import Dataset, ExternalKnowledgeApis
@@ -86,7 +86,7 @@ def external_document_indexing_task(
fg="green",
)
)
except DocumentIsPausedException as ex:
except DocumentIsPausedError as ex:
logging.info(click.style(str(ex), fg="yellow"))
except Exception:
+3 -3
View File
@@ -2,7 +2,7 @@ version: '3'
services:
# API service
api:
image: langgenius/dify-api:0.13.0
image: langgenius/dify-api:0.13.1
restart: always
environment:
# Startup mode, 'api' starts the API server.
@@ -227,7 +227,7 @@ services:
# worker service
# The Celery worker for processing the queue.
worker:
image: langgenius/dify-api:0.13.0
image: langgenius/dify-api:0.13.1
restart: always
environment:
CONSOLE_WEB_URL: ''
@@ -397,7 +397,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:0.13.0
image: langgenius/dify-web:0.13.1
restart: always
environment:
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
+3 -3
View File
@@ -292,7 +292,7 @@ x-shared-env: &shared-api-worker-env
services:
# API service
api:
image: langgenius/dify-api:0.13.0
image: langgenius/dify-api:0.13.1
restart: always
environment:
# Use the shared environment variables.
@@ -312,7 +312,7 @@ services:
# worker service
# The Celery worker for processing the queue.
worker:
image: langgenius/dify-api:0.13.0
image: langgenius/dify-api:0.13.1
restart: always
environment:
# Use the shared environment variables.
@@ -331,7 +331,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:0.13.0
image: langgenius/dify-web:0.13.1
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
@@ -166,7 +166,7 @@ const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
href={
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate_knowledge_within_application'
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
}
target='_blank' rel='noopener noreferrer'
@@ -29,6 +29,7 @@ import { useAppContext } from '@/context/app-context'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useFeatures } from '@/app/components/base/features/hooks'
import type { InputForm } from '@/app/components/base/chat/chat/type'
import { getLastAnswer } from '@/app/components/base/chat/utils'
type ChatItemProps = {
modelAndParameter: ModelAndParameter
@@ -101,7 +102,7 @@ const ChatItem: FC<ChatItemProps> = ({
query: message,
inputs,
model_config: configData,
parent_message_id: chatListRef.current.at(-1)?.id || null,
parent_message_id: getLastAnswer(chatListRef.current)?.id || null,
}
if ((config.file_upload as any).enabled && files?.length && supportVision)
+1 -1
View File
@@ -318,7 +318,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
const targetTone = TONE_LIST.find((item: any) => {
let res = true
validatedParams.forEach((param) => {
res = item.config?.[param] === detail.model_config?.configs?.completion_params?.[param]
res = item.config?.[param] === detail?.model_config.model?.completion_params?.[param]
})
return res
})?.name ?? 'custom'
@@ -334,7 +334,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
</SimpleBtn>
)
}
{(currentTab === 'RESULT' || !isWorkflow) && (
{((currentTab === 'RESULT' && workflowProcessData?.resultText) || !isWorkflow) && (
<SimpleBtn
isDisabled={isError || !messageId}
className={cn(isMobile && '!px-1.5', 'space-x-1')}
@@ -27,15 +27,15 @@ const ResultTab = ({
onCurrentTabChange(tab)
}
useEffect(() => {
if (data?.resultText)
if (data?.resultText || !!data?.files?.length)
switchTab('RESULT')
else
switchTab('DETAIL')
}, [data?.resultText])
}, [data?.files?.length, data?.resultText])
return (
<div className='grow relative flex flex-col'>
{data?.resultText && (
{(data?.resultText || !!data?.files?.length) && (
<div className='shrink-0 flex items-center mb-2 border-b-[0.5px] border-[rgba(0,0,0,0.05)]'>
<div
className={cn(
@@ -56,14 +56,21 @@ const ResultTab = ({
<div className={cn('grow bg-white')}>
{currentTab === 'RESULT' && (
<>
<Markdown content={data?.resultText || ''} />
{data?.resultText && <Markdown content={data?.resultText || ''} />}
{!!data?.files?.length && (
<FileList
files={data?.files}
showDeleteAction={false}
showDownloadAction
canPreview
/>
<div className='flex flex-col gap-2'>
{data?.files.map((item: any) => (
<div key={item.varName} className='flex flex-col gap-1 system-xs-regular'>
<div className='py-1 text-text-tertiary '>{item.varName}</div>
<FileList
files={item.list}
showDeleteAction={false}
showDownloadAction
canPreview
/>
</div>
))}
</div>
)}
</>
)}
@@ -76,7 +76,7 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
<div className='flex flex-col h-full'>
<h1 className='text-text-primary system-xl-semibold'>{t('appLog.workflowTitle')}</h1>
<p className='text-text-tertiary system-sm-regular'>{t('appLog.workflowSubtitle')}</p>
<div className='flex flex-col py-4 flex-1'>
<div className='flex flex-col py-4 flex-1 max-h-[calc(100%-16px)]'>
<Filter queryParams={queryParams} setQueryParams={setQueryParams} />
{/* workflow log */}
{total === undefined
@@ -1,4 +1,5 @@
import React, { useState } from 'react'
import React, { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiArrowRightSLine } from '@remixicon/react'
import FileImageRender from './file-image-render'
import FileTypeIcon from './file-type-icon'
@@ -12,23 +13,36 @@ import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
type Props = {
fileList: FileEntity[]
fileList: {
varName: string
list: FileEntity[]
}[]
isExpanded?: boolean
noBorder?: boolean
noPadding?: boolean
}
const FileListInLog = ({ fileList }: Props) => {
const [expanded, setExpanded] = useState(false)
const FileListInLog = ({ fileList, isExpanded = false, noBorder = false, noPadding = false }: Props) => {
const { t } = useTranslation()
const [expanded, setExpanded] = useState(isExpanded)
const fullList = useMemo(() => {
return fileList.reduce((acc: FileEntity[], { list }) => {
return [...acc, ...list]
}, [])
}, [fileList])
if (!fileList.length)
return null
return (
<div className={cn('border-t border-divider-subtle px-3 py-2', expanded && 'py-3')}>
<div className={cn('px-3 py-2', expanded && 'py-3', !noBorder && 'border-t border-divider-subtle', noPadding && '!p-0')}>
<div className='flex justify-between gap-1'>
{expanded && (
<div></div>
<div className='grow py-1 text-text-secondary system-xs-semibold-uppercase cursor-pointer' onClick={() => setExpanded(!expanded)}>{t('appLog.runDetail.fileListLabel')}</div>
)}
{!expanded && (
<div className='flex'>
{fileList.map((file) => {
<div className='flex gap-1'>
{fullList.map((file) => {
const { id, name, type, supportFileType, base64Url, url } = file
const isImageFile = supportFileType === SupportUploadFileTypes.image
return (
@@ -63,19 +77,25 @@ const FileListInLog = ({ fileList }: Props) => {
</div>
)}
<div className='flex items-center gap-1 cursor-pointer' onClick={() => setExpanded(!expanded)}>
{!expanded && <div className='text-text-tertiary system-xs-medium-uppercase'>DETAIL</div>}
{!expanded && <div className='text-text-tertiary system-xs-medium-uppercase'>{t('appLog.runDetail.fileListDetail')}</div>}
<RiArrowRightSLine className={cn('w-4 h-4 text-text-tertiary', expanded && 'rotate-90')} />
</div>
</div>
{expanded && (
<div className='flex flex-col gap-1'>
{fileList.map(file => (
<FileItem
key={file.id}
file={file}
showDeleteAction={false}
showDownloadAction
/>
<div className='flex flex-col gap-3'>
{fileList.map(item => (
<div key={item.varName} className='flex flex-col gap-1 system-xs-regular'>
<div className='py-1 text-text-tertiary '>{item.varName}</div>
{item.list.map(file => (
<FileItem
key={file.id}
file={file}
showDeleteAction={false}
showDownloadAction
canPreview
/>
))}
</div>
))}
</div>
)}
@@ -1,12 +1,15 @@
import {
memo,
useState,
} from 'react'
import {
RiDeleteBinLine,
RiDownloadLine,
RiEyeLine,
} from '@remixicon/react'
import FileTypeIcon from '../file-type-icon'
import {
downloadFile,
fileIsUploaded,
getFileAppearanceType,
getFileExtension,
@@ -19,6 +22,7 @@ import { formatFileSize } from '@/utils/format'
import cn from '@/utils/classnames'
import { ReplayLine } from '@/app/components/base/icons/src/vender/other'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
type FileInAttachmentItemProps = {
file: FileEntity
@@ -26,6 +30,7 @@ type FileInAttachmentItemProps = {
showDownloadAction?: boolean
onRemove?: (fileId: string) => void
onReUpload?: (fileId: string) => void
canPreview?: boolean
}
const FileInAttachmentItem = ({
file,
@@ -33,96 +38,116 @@ const FileInAttachmentItem = ({
showDownloadAction = true,
onRemove,
onReUpload,
canPreview,
}: FileInAttachmentItemProps) => {
const { id, name, type, progress, supportFileType, base64Url, url, isRemote } = file
const ext = getFileExtension(name, type, isRemote)
const isImageFile = supportFileType === SupportUploadFileTypes.image
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
return (
<div className={cn(
'flex items-center pr-3 h-12 rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs',
progress === -1 && 'bg-state-destructive-hover border-state-destructive-border',
)}>
<div className='flex items-center justify-center w-12 h-12'>
{
isImageFile && (
<FileImageRender
className='w-8 h-8'
imageUrl={base64Url || url || ''}
/>
)
}
{
!isImageFile && (
<FileTypeIcon
type={getFileAppearanceType(name, type)}
size='lg'
/>
)
}
</div>
<div className='grow w-0 mr-1'>
<div
className='flex items-center mb-0.5 system-xs-medium text-text-secondary truncate'
title={file.name}
>
<div className='truncate'>{name}</div>
<>
<div className={cn(
'flex items-center pr-3 h-12 rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs',
progress === -1 && 'bg-state-destructive-hover border-state-destructive-border',
)}>
<div className='flex items-center justify-center w-12 h-12'>
{
isImageFile && (
<FileImageRender
className='w-8 h-8'
imageUrl={base64Url || url || ''}
/>
)
}
{
!isImageFile && (
<FileTypeIcon
type={getFileAppearanceType(name, type)}
size='lg'
/>
)
}
</div>
<div className='flex items-center system-2xs-medium-uppercase text-text-tertiary'>
<div className='grow w-0 mr-1'>
<div
className='flex items-center mb-0.5 system-xs-medium text-text-secondary truncate'
title={file.name}
>
<div className='truncate'>{name}</div>
</div>
<div className='flex items-center system-2xs-medium-uppercase text-text-tertiary'>
{
ext && (
<span>{ext.toLowerCase()}</span>
)
}
{
ext && (
<span className='mx-1 system-2xs-medium'></span>
)
}
{
!!file.size && (
<span>{formatFileSize(file.size)}</span>
)
}
</div>
</div>
<div className='shrink-0 flex items-center'>
{
ext && (
<span>{ext.toLowerCase()}</span>
progress >= 0 && !fileIsUploaded(file) && (
<ProgressCircle
className='mr-2.5'
percentage={progress}
/>
)
}
{
ext && (
<span className='mx-1 system-2xs-medium'></span>
progress === -1 && (
<ActionButton
className='mr-1'
onClick={() => onReUpload?.(id)}
>
<ReplayLine className='w-4 h-4 text-text-tertiary' />
</ActionButton>
)
}
{
!!file.size && (
<span>{formatFileSize(file.size)}</span>
showDeleteAction && (
<ActionButton onClick={() => onRemove?.(id)}>
<RiDeleteBinLine className='w-4 h-4' />
</ActionButton>
)
}
{
canPreview && isImageFile && (
<ActionButton className='mr-1' onClick={() => setImagePreviewUrl(url || '')}>
<RiEyeLine className='w-4 h-4' />
</ActionButton>
)
}
{
showDownloadAction && (
<ActionButton onClick={(e) => {
e.stopPropagation()
downloadFile(url || base64Url || '', name)
}}>
<RiDownloadLine className='w-4 h-4' />
</ActionButton>
)
}
</div>
</div>
<div className='shrink-0 flex items-center'>
{
progress >= 0 && !fileIsUploaded(file) && (
<ProgressCircle
className='mr-2.5'
percentage={progress}
/>
)
}
{
progress === -1 && (
<ActionButton
className='mr-1'
onClick={() => onReUpload?.(id)}
>
<ReplayLine className='w-4 h-4 text-text-tertiary' />
</ActionButton>
)
}
{
showDeleteAction && (
<ActionButton onClick={() => onRemove?.(id)}>
<RiDeleteBinLine className='w-4 h-4' />
</ActionButton>
)
}
{
showDownloadAction && (
<ActionButton
size='xs'
>
<RiDownloadLine className='w-3.5 h-3.5 text-text-tertiary' />
</ActionButton>
)
}
</div>
</div>
{
imagePreviewUrl && canPreview && (
<ImagePreview
title={name}
url={imagePreviewUrl}
onCancel={() => setImagePreviewUrl('')}
/>
)
}
</>
)
}
@@ -84,7 +84,7 @@ const FileImageItem = ({
className='absolute bottom-0.5 right-0.5 flex items-center justify-center w-6 h-6 rounded-lg bg-components-actionbar-bg shadow-md'
onClick={(e) => {
e.stopPropagation()
downloadFile(url || '', name)
downloadFile(url || base64Url || '', name)
}}
>
<RiDownloadLine className='w-4 h-4 text-text-tertiary' />
@@ -31,7 +31,7 @@ const FileItem = ({
onRemove,
onReUpload,
}: FileItemProps) => {
const { id, name, type, progress, url, isRemote } = file
const { id, name, type, progress, url, base64Url, isRemote } = file
const ext = getFileExtension(name, type, isRemote)
const uploadError = progress === -1
@@ -80,13 +80,13 @@ const FileItem = ({
}
</div>
{
showDownloadAction && (
showDownloadAction && url && (
<ActionButton
size='m'
className='hidden group-hover/file-item:flex absolute -right-1 -top-1'
onClick={(e) => {
e.stopPropagation()
downloadFile(url || '', name)
downloadFile(url || base64Url || '', name)
}}
>
<RiDownloadLine className='w-3.5 h-3.5 text-text-tertiary' />
+15 -6
View File
@@ -1,5 +1,4 @@
import mime from 'mime'
import { flatten } from 'lodash-es'
import { FileAppearanceTypeEnum } from './types'
import type { FileEntity } from './types'
import { upload } from '@/service/base'
@@ -158,12 +157,22 @@ export const isAllowedFileExtension = (fileName: string, fileMimetype: string, a
}
export const getFilesInLogs = (rawData: any) => {
const originalFiles = flatten(Object.keys(rawData || {}).map((key) => {
if (typeof rawData[key] === 'object' || Array.isArray(rawData[key]))
return rawData[key]
const result = Object.keys(rawData || {}).map((key) => {
if (typeof rawData[key] === 'object' && rawData[key].dify_model_identity === '__dify__file__') {
return {
varName: key,
list: getProcessedFilesFromResponse([rawData[key]]),
}
}
if (Array.isArray(rawData[key]) && rawData[key].some(item => item.dify_model_identity === '__dify__file__')) {
return {
varName: key,
list: getProcessedFilesFromResponse(rawData[key]),
}
}
return undefined
}).filter(Boolean)).filter(item => item?.model_identity === '__dify__file__')
return getProcessedFilesFromResponse(originalFiles)
}).filter(Boolean)
return result
}
export const fileIsUploaded = (file: FileEntity) => {
@@ -53,8 +53,7 @@ const ImageGallery: FC<Props> = ({
imagePreviewUrl && (
<ImagePreview
url={imagePreviewUrl}
onCancel={() => setImagePreviewUrl('')}
/>
onCancel={() => setImagePreviewUrl('')} title={''} />
)
}
</div>
+4 -28
View File
@@ -8,8 +8,7 @@ import RemarkGfm from 'remark-gfm'
import RehypeRaw from 'rehype-raw'
import SyntaxHighlighter from 'react-syntax-highlighter'
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
import type { RefObject } from 'react'
import { Component, memo, useEffect, useMemo, useRef, useState } from 'react'
import { Component, memo, useMemo, useRef, useState } from 'react'
import type { CodeComponent } from 'react-markdown/lib/ast-to-react'
import cn from '@/utils/classnames'
import CopyBtn from '@/app/components/base/copy-btn'
@@ -77,29 +76,6 @@ export function PreCode(props: { children: any }) {
)
}
// eslint-disable-next-line unused-imports/no-unused-vars
const useLazyLoad = (ref: RefObject<Element>): boolean => {
const [isIntersecting, setIntersecting] = useState<boolean>(false)
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setIntersecting(true)
observer.disconnect()
}
})
if (ref.current)
observer.observe(ref.current)
return () => {
observer.disconnect()
}
}, [ref])
return isIntersecting
}
// **Add code block
// Avoid error #185 (Maximum update depth exceeded.
// This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
@@ -123,7 +99,7 @@ const CodeBlock: CodeComponent = memo(({ inline, className, children, ...props }
try {
return JSON.parse(String(children).replace(/\n$/, ''))
}
catch (error) {}
catch (error) { }
}
return JSON.parse('{"title":{"text":"ECharts error - Wrong JSON format."}}')
}, [language, children])
@@ -181,7 +157,7 @@ const CodeBlock: CodeComponent = memo(({ inline, className, children, ...props }
>
<div className='text-[13px] text-gray-500 font-normal'>{languageShowName}</div>
<div style={{ display: 'flex' }}>
{(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG}/>}
{(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}
<CopyBtn
className='mr-1'
value={String(children).replace(/\n$/, '')}
@@ -261,7 +237,7 @@ export function Markdown(props: { content: string; className?: string }) {
() => {
return (tree) => {
const iterate = (node: any) => {
if (node.type === 'element' && !node.properties?.src && node.properties?.ref && node.properties.ref.startsWith('{') && node.properties.ref.endsWith('}'))
if (node.type === 'element' && node.properties?.ref)
delete node.properties.ref
if (node.children)
+70 -24
View File
@@ -1,24 +1,18 @@
import React, { useEffect, useRef, useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import mermaid from 'mermaid'
import { usePrevious } from 'ahooks'
import CryptoJS from 'crypto-js'
import { useTranslation } from 'react-i18next'
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
import cn from '@/utils/classnames'
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
let mermaidAPI: any
mermaidAPI = null
if (typeof window !== 'undefined') {
mermaid.initialize({
startOnLoad: true,
theme: 'default',
flowchart: {
htmlLabels: true,
useMaxWidth: true,
},
})
if (typeof window !== 'undefined')
mermaidAPI = mermaid.mermaidAPI
}
const style = {
minWidth: '480px',
@@ -40,14 +34,21 @@ const svgToBase64 = (svgGraph: string) => {
const Flowchart = React.forwardRef((props: {
PrimitiveCode: string
}, ref) => {
const { t } = useTranslation()
const [svgCode, setSvgCode] = useState(null)
const [look, setLook] = useState<'classic' | 'handDrawn'>('classic')
const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`)
const prevPrimitiveCode = usePrevious(props.PrimitiveCode)
const [isLoading, setIsLoading] = useState(true)
const timeRef = useRef<NodeJS.Timeout>()
const [errMsg, setErrMsg] = useState('')
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
const renderFlowchart = useCallback(async (PrimitiveCode: string) => {
setSvgCode(null)
setIsLoading(true)
const renderFlowchart = async (PrimitiveCode: string) => {
try {
if (typeof window !== 'undefined' && mermaidAPI) {
const svgGraph = await mermaidAPI.render(chartId.current, PrimitiveCode)
@@ -64,10 +65,28 @@ const Flowchart = React.forwardRef((props: {
setErrMsg((error as Error).message)
}
}
}
}, [props.PrimitiveCode])
useEffect(() => {
if (typeof window !== 'undefined') {
mermaid.initialize({
startOnLoad: true,
theme: 'neutral',
look,
flowchart: {
htmlLabels: true,
useMaxWidth: true,
},
})
localStorage.removeItem(chartId.current)
renderFlowchart(props.PrimitiveCode)
}
}, [look])
useEffect(() => {
const cachedSvg: any = localStorage.getItem(chartId.current)
if (cachedSvg) {
setSvgCode(cachedSvg)
setIsLoading(false)
@@ -85,24 +104,51 @@ const Flowchart = React.forwardRef((props: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
<div ref={ref}>
<div className="msh-segmented msh-segmented-sm css-23bs09 css-var-r1">
<div className="msh-segmented-group">
<label className="msh-segmented-item flex items-center space-x-1 m-2 w-[200px]">
<div key='classic'
className={cn('flex items-center justify-center mb-4 w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary',
look === 'classic' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary',
)}
onClick={() => setLook('classic')}
>
<div className="msh-segmented-item-label">{t('app.mermaid.classic')}</div>
</div>
<div key='handDrawn'
className={cn(
'flex items-center justify-center mb-4 w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary',
look === 'handDrawn' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary',
)}
onClick={() => setLook('handDrawn')}
>
<div className="msh-segmented-item-label">{t('app.mermaid.handDrawn')}</div>
</div>
</label>
</div>
</div>
{
svgCode
&& <div className="mermaid" style={style}>
{svgCode && <img src={svgCode} style={{ width: '100%', height: 'auto' }} alt="Mermaid chart" />}
</div>
&& <div className="mermaid cursor-pointer" style={style} onClick={() => setImagePreviewUrl(svgCode)}>
{svgCode && <img src={svgCode} style={{ width: '100%', height: 'auto' }} alt="mermaid_chart" />}
</div>
}
{isLoading
&& <div className='py-4 px-[26px]'>
<LoadingAnim type='text' />
</div>
&& <div className='py-4 px-[26px]'>
<LoadingAnim type='text'/>
</div>
}
{
errMsg
&& <div className='py-4 px-[26px]'>
<ExclamationTriangleIcon className='w-6 h-6 text-red-500' />
&nbsp;
{errMsg}
</div>
&& <div className='py-4 px-[26px]'>
<ExclamationTriangleIcon className='w-6 h-6 text-red-500'/>
&nbsp;
{errMsg}
</div>
}
{
imagePreviewUrl && (<ImagePreview title='mermaid_chart' url={imagePreviewUrl} onCancel={() => setImagePreviewUrl('')} />)
}
</div>
)
@@ -0,0 +1,47 @@
import type { ComponentProps, FC } from 'react'
import classNames from '@/utils/classnames'
type SkeletonProps = ComponentProps<'div'>
export const SkeletonContanier: FC<SkeletonProps> = (props) => {
const { className, children, ...rest } = props
return (
<div className={classNames('flex flex-col gap-1', className)} {...rest}>
{children}
</div>
)
}
export const SkeletonRow: FC<SkeletonProps> = (props) => {
const { className, children, ...rest } = props
return (
<div className={classNames('flex items-center gap-2', className)} {...rest}>
{children}
</div>
)
}
export const SkeletonRectangle: FC<SkeletonProps> = (props) => {
const { className, children, ...rest } = props
return (
<div className={classNames('h-2 rounded-sm opacity-20 bg-text-tertiary my-1', className)} {...rest}>
{children}
</div>
)
}
export const SkeletonPoint: FC = () =>
<div className='text-text-quaternary text-xs font-medium'>·</div>
/** Usage
* <SkeletonContanier>
* <SkeletonRow>
* <SkeletonRectangle className="w-96" />
* <SkeletonPoint />
* <SkeletonRectangle className="w-96" />
* </SkeletonRow>
* <SkeletonRow>
* <SkeletonRectangle className="w-96" />
* </SkeletonRow>
* <SkeletonRow>
*/
@@ -21,7 +21,7 @@ import { sleep } from '@/utils'
import type { SiteInfo } from '@/models/share'
import { TEXT_GENERATION_TIMEOUT_MS } from '@/config'
import {
getProcessedFilesFromResponse,
getFilesInLogs,
} from '@/app/components/base/file-uploader/utils'
export type IResultProps = {
@@ -288,7 +288,7 @@ const Result: FC<IResultProps> = ({
}
setWorkflowProcessData(produce(getWorkflowProcessData()!, (draft) => {
draft.status = WorkflowRunningStatus.Succeeded
draft.files = getProcessedFilesFromResponse(data.files || [])
draft.files = getFilesInLogs(data.outputs || []) as any[]
}))
if (!data.outputs) {
setCompletionRes('')
@@ -26,7 +26,7 @@ import {
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
import {
getProcessedFilesFromResponse,
getFilesInLogs,
} from '@/app/components/base/file-uploader/utils'
export const useWorkflowRun = () => {
@@ -213,7 +213,7 @@ export const useWorkflowRun = () => {
draft.result = {
...draft.result,
...data,
files: getProcessedFilesFromResponse(data.files || []),
files: getFilesInLogs(data.outputs),
} as any
if (isStringOutput) {
draft.resultTabActive = true
@@ -27,7 +27,10 @@ type Props = {
isInNode?: boolean
onGenerated?: (prompt: string) => void
codeLanguages?: CodeLanguage
fileList?: FileEntity[]
fileList?: {
varName: string
list: FileEntity[]
}[]
showFileList?: boolean
showCodeGenerator?: boolean
}
@@ -208,7 +208,7 @@ const CodeEditor: FC<Props> = ({
isInNode={isInNode}
onGenerated={onGenerated}
codeLanguages={language}
fileList={fileList}
fileList={fileList as any}
showFileList={showFileList}
showCodeGenerator={showCodeGenerator}
>
@@ -30,7 +30,9 @@ const nodeDefault: NodeDefault<AssignerNodeType> = {
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.assigner.assignedVariable') })
if (!errorMessages && value.operation !== WriteMode.clear) {
if (value.operation === WriteMode.set) {
if (value.operation === WriteMode.set || value.operation === WriteMode.increment
|| value.operation === WriteMode.decrement || value.operation === WriteMode.multiply
|| value.operation === WriteMode.divide) {
if (!value.value && typeof value.value !== 'number')
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.assigner.variable') })
}
@@ -173,7 +173,8 @@ const InputVarList: FC<Props> = ({
value={varInput?.type === VarKindType.constant ? (varInput?.value || '') : (varInput?.value || [])}
onChange={handleNotMixedTypeChange(variable)}
onOpen={handleOpen(index)}
defaultVarKindType={VarKindType.variable}
defaultVarKindType={isNumber ? VarKindType.constant : VarKindType.variable}
isSupportConstantValue={isSupportConstantValue}
filterVar={isNumber ? filterVar : undefined}
availableVars={isSelect ? availableVars : undefined}
schema={schema}
@@ -33,7 +33,7 @@ const NodeVariableItem = ({
const { t } = useTranslation()
return (
<div className={cn(
'relative flex items-center p-[3px] pl-[5px] gap-1 self-stretch rounded-md bg-workflow-block-param-bg',
'relative flex items-center p-[3px] pl-[5px] gap-1 self-stretch rounded-md bg-workflow-block-parma-bg',
showBorder && '!bg-black/[0.02]',
className,
)}>
@@ -97,8 +97,9 @@ const ChatVariableModal = ({
return objectPlaceholder
}, [type])
const getObjectValue = useCallback(() => {
if (!chatVar)
if (!chatVar || Object.keys(chatVar.value).length === 0)
return [DEFAULT_OBJECT_VALUE]
return Object.keys(chatVar.value).map((key) => {
return {
key,
@@ -48,7 +48,7 @@ const WorkflowPreview = () => {
}, [showDebugAndPreviewPanel, showInputsPanel])
useEffect(() => {
if ((workflowRunningData?.result.status === WorkflowRunningStatus.Succeeded || workflowRunningData?.result.status === WorkflowRunningStatus.Failed) && !workflowRunningData.resultText)
if ((workflowRunningData?.result.status === WorkflowRunningStatus.Succeeded || workflowRunningData?.result.status === WorkflowRunningStatus.Failed) && !workflowRunningData.resultText && !workflowRunningData.result.files?.length)
switchTab('DETAIL')
}, [workflowRunningData])
@@ -1,10 +1,13 @@
'use client'
import type { FC } from 'react'
import { useMemo } from 'react'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { Markdown } from '@/app/components/base/markdown'
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
import { FileList } from '@/app/components/base/file-uploader'
import StatusContainer from '@/app/components/workflow/run/status-container'
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
type OutputPanelProps = {
isRunning?: boolean
@@ -19,6 +22,30 @@ const OutputPanel: FC<OutputPanelProps> = ({
error,
height,
}) => {
const isTextOutput = useMemo(() => {
return outputs && Object.keys(outputs).length === 1 && typeof outputs[Object.keys(outputs)[0]] === 'string'
}, [outputs])
const fileList = useMemo(() => {
const fileList: any[] = []
if (!outputs)
return fileList
if (Object.keys(outputs).length > 1)
return fileList
for (const key in outputs) {
if (Array.isArray(outputs[key])) {
outputs[key].map((output: any) => {
if (output.dify_model_identity === '__dify__file__')
fileList.push(output)
return null
})
}
else if (outputs[key].dify_model_identity === '__dify__file__') {
fileList.push(outputs[key])
}
}
return getProcessedFilesFromResponse(fileList)
}, [outputs])
return (
<div className='py-2'>
{isRunning && (
@@ -36,20 +63,31 @@ const OutputPanel: FC<OutputPanelProps> = ({
<Markdown content='No Output' />
</div>
)}
{outputs && Object.keys(outputs).length === 1 && (
{isTextOutput && (
<div className='px-4 py-2'>
<Markdown content={outputs[Object.keys(outputs)[0]] || ''} />
</div>
)}
{fileList.length > 0 && (
<div className='px-4 py-2'>
<FileList
files={fileList}
showDeleteAction={false}
showDownloadAction
canPreview
/>
</div>
)}
{outputs && Object.keys(outputs).length > 1 && height! > 0 && (
<div className='px-4 py-2 flex flex-col gap-2'>
<div className='flex flex-col gap-2'>
<CodeEditor
showFileList
readOnly
title={<div></div>}
language={CodeLanguage.json}
value={outputs}
isJSONStringifyBeauty
height={height}
height={height ? (height - 16) / 2 : undefined}
/>
</div>
)}
+22 -16
View File
@@ -6,14 +6,13 @@ import { Markdown } from '@/app/components/base/markdown'
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
import StatusContainer from '@/app/components/workflow/run/status-container'
import { FileList } from '@/app/components/base/file-uploader'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
type ResultTextProps = {
isRunning?: boolean
outputs?: any
error?: string
onClick?: () => void
allFiles?: FileEntity[]
allFiles?: any[]
}
const ResultText: FC<ResultTextProps> = ({
@@ -25,20 +24,20 @@ const ResultText: FC<ResultTextProps> = ({
}) => {
const { t } = useTranslation()
return (
<div className='bg-background-section-burn py-2'>
<div className='bg-background-section-burn'>
{isRunning && !outputs && (
<div className='pt-4 pl-[26px]'>
<LoadingAnim type='text' />
</div>
)}
{!isRunning && error && (
<div className='px-4'>
<div className='px-4 py-2'>
<StatusContainer status='failed'>
{error}
</StatusContainer>
</div>
)}
{!isRunning && !outputs && !error && (
{!isRunning && !outputs && !error && !allFiles?.length && (
<div className='mt-[120px] px-4 py-2 flex flex-col items-center text-[13px] leading-[18px] text-gray-500'>
<ImageIndentLeft className='w-6 h-6 text-gray-400' />
<div className='mr-2'>{t('runLog.resultEmpty.title')}</div>
@@ -49,18 +48,25 @@ const ResultText: FC<ResultTextProps> = ({
</div>
</div>
)}
{outputs && (
<div className='px-4 py-2'>
<Markdown content={outputs} />
{!!allFiles?.length && (
<FileList
files={allFiles}
showDeleteAction={false}
showDownloadAction
canPreview
/>
{(outputs || !!allFiles?.length) && (
<>
{outputs && (
<div className='px-4 py-2'>
<Markdown content={outputs} />
</div>
)}
</div>
{!!allFiles?.length && allFiles.map(item => (
<div key={item.varName} className='px-4 py-2 flex flex-col gap-1 system-xs-regular'>
<div className='py-1 text-text-tertiary '>{item.varName}</div>
<FileList
files={item.list}
showDeleteAction={false}
showDownloadAction
canPreview
/>
</div>
))}
</>
)}
</div>
)
+2
View File
@@ -79,6 +79,8 @@ const translation = {
runDetail: {
title: 'Konversationsprotokoll',
workflowTitle: 'Protokolldetail',
fileListLabel: 'Details zur Datei',
fileListDetail: 'Detail',
},
promptLog: 'Prompt-Protokoll',
agentLog: 'Agentenprotokoll',
+2
View File
@@ -35,6 +35,8 @@ const translation = {
Translate: 'Übersetzen',
Programming: 'Programmieren',
HR: 'Personalwesen',
Agent: 'Agent',
Workflow: 'Arbeitsablauf',
},
}
+2
View File
@@ -79,6 +79,8 @@ const translation = {
runDetail: {
title: 'Conversation Log',
workflowTitle: 'Log Detail',
fileListLabel: 'File Details',
fileListDetail: 'Detail',
},
promptLog: 'Prompt Log',
agentLog: 'Agent Log',
+4
View File
@@ -8,6 +8,10 @@ const translation = {
completion: 'Completion',
},
duplicate: 'Duplicate',
mermaid: {
handDrawn: 'Hand Drawn',
classic: 'Classic',
},
duplicateTitle: 'Duplicate App',
export: 'Export DSL',
exportFailed: 'Export DSL failed.',
+2
View File
@@ -30,11 +30,13 @@ const translation = {
nameRequired: 'App name is required',
},
category: {
Agent: 'Agent',
Assistant: 'Assistant',
Writing: 'Writing',
Translate: 'Translate',
Programming: 'Programming',
HR: 'HR',
Workflow: 'Workflow',
},
}
+2
View File
@@ -79,6 +79,8 @@ const translation = {
runDetail: {
title: 'Registro de Conversación',
workflowTitle: 'Detalle del Registro',
fileListLabel: 'Detalles del archivo',
fileListDetail: 'Detalle',
},
promptLog: 'Registro de Indicación',
agentLog: 'Registro de Agente',
+2
View File
@@ -35,6 +35,8 @@ const translation = {
Translate: 'Traducción',
Programming: 'Programación',
HR: 'Recursos Humanos',
Agent: 'Agente',
Workflow: 'Flujo de trabajo',
},
}
+2
View File
@@ -79,6 +79,8 @@ const translation = {
runDetail: {
title: 'لاگ مکالمه',
workflowTitle: 'جزئیات لاگ',
fileListLabel: 'جزئیات فایل',
fileListDetail: 'جزئیات',
},
promptLog: 'لاگ درخواست',
agentLog: 'لاگ عامل',
+2
View File
@@ -35,6 +35,8 @@ const translation = {
Translate: 'ترجمه',
Programming: 'برنامه‌نویسی',
HR: 'منابع انسانی',
Agent: 'عامل',
Workflow: 'گردش',
},
}
+2
View File
@@ -79,6 +79,8 @@ const translation = {
runDetail: {
title: 'Journal de conversation',
workflowTitle: 'Détail du journal',
fileListDetail: 'Détail',
fileListLabel: 'Détails du fichier',
},
promptLog: 'Journal de consigne',
agentLog: 'Journal des agents',
+2
View File
@@ -35,6 +35,8 @@ const translation = {
Translate: 'Traduire',
Programming: 'Programmation',
HR: 'RH',
Agent: 'Agent',
Workflow: 'Flux de travail',
},
}
+2
View File
@@ -81,6 +81,8 @@ const translation = {
runDetail: {
title: 'बातचीत लॉग',
workflowTitle: 'लॉग विवरण',
fileListDetail: 'विस्तार',
fileListLabel: 'फ़ाइल विवरण',
},
promptLog: 'प्रॉम्प्ट लॉग',
agentLog: 'एजेंट लॉग',
+2
View File
@@ -36,6 +36,8 @@ const translation = {
Translate: 'अनुवाद',
Programming: 'प्रोग्रामिंग',
HR: 'मानव संसाधन',
Workflow: 'कार्यप्रवाह',
Agent: 'आढ़तिया',
},
}
+2
View File
@@ -83,6 +83,8 @@ const translation = {
runDetail: {
title: 'Registro Conversazione',
workflowTitle: 'Dettagli Registro',
fileListDetail: 'Dettaglio',
fileListLabel: 'Dettagli del file',
},
promptLog: 'Registro Prompt',
agentLog: 'Registro Agente',
+2
View File
@@ -36,6 +36,8 @@ const translation = {
Translate: 'Traduzione',
Programming: 'Programmazione',
HR: 'Risorse Umane',
Workflow: 'Flusso di lavoro',
Agent: 'Agente',
},
}
+2
View File
@@ -79,6 +79,8 @@ const translation = {
runDetail: {
title: '会話ログ',
workflowTitle: 'ログの詳細',
fileListLabel: 'ファイルの詳細',
fileListDetail: 'ディテール',
},
promptLog: 'プロンプトログ',
agentLog: 'エージェントログ',
+2
View File
@@ -35,6 +35,8 @@ const translation = {
Translate: '翻訳',
Programming: 'プログラミング',
HR: '人事',
Workflow: 'ワークフロー',
Agent: 'エージェント',
},
}
+2
View File
@@ -80,6 +80,8 @@ const translation = {
runDetail: {
title: '대화 로그',
workflowTitle: '로그 세부 정보',
fileListDetail: '세부',
fileListLabel: '파일 세부 정보',
},
promptLog: '프롬프트 로그',
agentLog: '에이전트 로그',
+2
View File
@@ -83,6 +83,8 @@ const translation = {
runDetail: {
title: 'Dziennik rozmowy',
workflowTitle: 'Szczegół dziennika',
fileListDetail: 'Detal',
fileListLabel: 'Szczegóły pliku',
},
promptLog: 'Dziennik monitów',
agentLog: 'Dziennik agenta',
+2
View File
@@ -36,6 +36,8 @@ const translation = {
Translate: 'Tłumaczenie',
Programming: 'Programowanie',
HR: 'HR',
Agent: 'Agent',
Workflow: 'Przepływ pracy',
},
}
+2
View File
@@ -79,6 +79,8 @@ const translation = {
runDetail: {
title: 'Registro de Conversa',
workflowTitle: 'Detalhes do Registro',
fileListLabel: 'Detalhes do arquivo',
fileListDetail: 'Detalhe',
},
promptLog: 'Registro de Prompt',
agentLog: 'Registro do agente',
+2
View File
@@ -35,6 +35,8 @@ const translation = {
Translate: 'Traduzir',
Programming: 'Programação',
HR: 'RH',
Workflow: 'Fluxo de trabalho',
Agent: 'Agente',
},
}
+2
View File
@@ -79,6 +79,8 @@ const translation = {
runDetail: {
title: 'Jurnal de conversație',
workflowTitle: 'Detalii jurnal',
fileListDetail: 'Amănunt',
fileListLabel: 'Detalii fișier',
},
promptLog: 'Jurnal prompt',
agentLog: 'Jurnal agent',
+2
View File
@@ -35,6 +35,8 @@ const translation = {
Translate: 'Traducere',
Programming: 'Programare',
HR: 'Resurse Umane',
Agent: 'Agent',
Workflow: 'Flux de lucru',
},
}

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