Merge branch 'main' into feat/knowledge-dark-mode

pull/13379/head
twwu 1 year ago
commit 26bd253c2d

@ -498,6 +498,11 @@ class AuthConfig(BaseSettings):
default=86400, default=86400,
) )
FORGOT_PASSWORD_LOCKOUT_DURATION: PositiveInt = Field(
description="Time (in seconds) a user must wait before retrying password reset after exceeding the rate limit.",
default=86400,
)
class ModerationConfig(BaseSettings): class ModerationConfig(BaseSettings):
""" """

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

@ -59,3 +59,9 @@ class EmailCodeAccountDeletionRateLimitExceededError(BaseHTTPException):
error_code = "email_code_account_deletion_rate_limit_exceeded" error_code = "email_code_account_deletion_rate_limit_exceeded"
description = "Too many account deletion emails have been sent. Please try again in 5 minutes." description = "Too many account deletion emails have been sent. Please try again in 5 minutes."
code = 429 code = 429
class EmailPasswordResetLimitError(BaseHTTPException):
error_code = "email_password_reset_limit"
description = "Too many failed password reset attempts. Please try again in 24 hours."
code = 429

@ -6,7 +6,13 @@ from flask_restful import Resource, reqparse # type: ignore
from constants.languages import languages from constants.languages import languages
from controllers.console import api from controllers.console import api
from controllers.console.auth.error import EmailCodeError, InvalidEmailError, InvalidTokenError, PasswordMismatchError from controllers.console.auth.error import (
EmailCodeError,
EmailPasswordResetLimitError,
InvalidEmailError,
InvalidTokenError,
PasswordMismatchError,
)
from controllers.console.error import AccountInFreezeError, AccountNotFound, EmailSendIpLimitError from controllers.console.error import AccountInFreezeError, AccountNotFound, EmailSendIpLimitError
from controllers.console.wraps import setup_required from controllers.console.wraps import setup_required
from events.tenant_event import tenant_was_created from events.tenant_event import tenant_was_created
@ -62,6 +68,10 @@ class ForgotPasswordCheckApi(Resource):
user_email = args["email"] user_email = args["email"]
is_forgot_password_error_rate_limit = AccountService.is_forgot_password_error_rate_limit(args["email"])
if is_forgot_password_error_rate_limit:
raise EmailPasswordResetLimitError()
token_data = AccountService.get_reset_password_data(args["token"]) token_data = AccountService.get_reset_password_data(args["token"])
if token_data is None: if token_data is None:
raise InvalidTokenError() raise InvalidTokenError()
@ -70,8 +80,10 @@ class ForgotPasswordCheckApi(Resource):
raise InvalidEmailError() raise InvalidEmailError()
if args["code"] != token_data.get("code"): if args["code"] != token_data.get("code"):
AccountService.add_forgot_password_error_rate_limit(args["email"])
raise EmailCodeError() raise EmailCodeError()
AccountService.reset_forgot_password_error_rate_limit(args["email"])
return {"is_valid": True, "email": token_data.get("email")} return {"is_valid": True, "email": token_data.get("email")}

@ -1,4 +1,4 @@
from .llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage from .llm_entities import LLMMode, LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
from .message_entities import ( from .message_entities import (
AssistantPromptMessage, AssistantPromptMessage,
AudioPromptMessageContent, AudioPromptMessageContent,
@ -23,6 +23,7 @@ __all__ = [
"AudioPromptMessageContent", "AudioPromptMessageContent",
"DocumentPromptMessageContent", "DocumentPromptMessageContent",
"ImagePromptMessageContent", "ImagePromptMessageContent",
"LLMMode",
"LLMResult", "LLMResult",
"LLMResultChunk", "LLMResultChunk",
"LLMResultChunkDelta", "LLMResultChunkDelta",

@ -1,5 +1,5 @@
from decimal import Decimal from decimal import Decimal
from enum import Enum from enum import StrEnum
from typing import Optional from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
@ -8,7 +8,7 @@ from core.model_runtime.entities.message_entities import AssistantPromptMessage,
from core.model_runtime.entities.model_entities import ModelUsage, PriceInfo from core.model_runtime.entities.model_entities import ModelUsage, PriceInfo
class LLMMode(Enum): class LLMMode(StrEnum):
""" """
Enum class for large language model mode. Enum class for large language model mode.
""" """

@ -51,6 +51,40 @@ model_credential_schema:
show_on: show_on:
- variable: __model_type - variable: __model_type
value: llm value: llm
- variable: mode
show_on:
- variable: __model_type
value: llm
label:
en_US: Completion mode
type: select
required: false
default: chat
placeholder:
zh_Hans: 选择对话类型
en_US: Select completion mode
options:
- value: completion
label:
en_US: Completion
zh_Hans: 补全
- value: chat
label:
en_US: Chat
zh_Hans: 对话
- variable: context_size
label:
zh_Hans: 模型上下文长度
en_US: Model context size
required: true
show_on:
- variable: __model_type
value: llm
type: text-input
default: "4096"
placeholder:
zh_Hans: 在此输入您的模型上下文长度
en_US: Enter your Model context size
- variable: jwt_token - variable: jwt_token
required: true required: true
label: label:

@ -20,7 +20,7 @@ from azure.core.exceptions import (
) )
from core.model_runtime.callbacks.base_callback import Callback from core.model_runtime.callbacks.base_callback import Callback
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage from core.model_runtime.entities.llm_entities import LLMMode, LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
from core.model_runtime.entities.message_entities import ( from core.model_runtime.entities.message_entities import (
AssistantPromptMessage, AssistantPromptMessage,
PromptMessage, PromptMessage,
@ -30,6 +30,7 @@ from core.model_runtime.entities.model_entities import (
AIModelEntity, AIModelEntity,
FetchFrom, FetchFrom,
I18nObject, I18nObject,
ModelPropertyKey,
ModelType, ModelType,
ParameterRule, ParameterRule,
ParameterType, ParameterType,
@ -334,7 +335,10 @@ class AzureAIStudioLargeLanguageModel(LargeLanguageModel):
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
model_type=ModelType.LLM, model_type=ModelType.LLM,
features=[], features=[],
model_properties={}, model_properties={
ModelPropertyKey.CONTEXT_SIZE: int(credentials.get("context_size", "4096")),
ModelPropertyKey.MODE: credentials.get("mode", LLMMode.CHAT),
},
parameter_rules=rules, parameter_rules=rules,
) )

@ -314,7 +314,6 @@ class OllamaLargeLanguageModel(LargeLanguageModel):
""" """
full_text = "" full_text = ""
chunk_index = 0 chunk_index = 0
is_reasoning_started = False
def create_final_llm_result_chunk( def create_final_llm_result_chunk(
index: int, message: AssistantPromptMessage, finish_reason: str index: int, message: AssistantPromptMessage, finish_reason: str
@ -368,14 +367,7 @@ class OllamaLargeLanguageModel(LargeLanguageModel):
# transform assistant message to prompt message # transform assistant message to prompt message
text = chunk_json["response"] text = chunk_json["response"]
if "<think>" in text: text = self._wrap_thinking_by_tag(text)
is_reasoning_started = True
text = text.replace("<think>", "> 💭 ")
elif "</think>" in text:
is_reasoning_started = False
text = text.replace("</think>", "") + "\n\n"
elif is_reasoning_started:
text = text.replace("\n", "\n> ")
assistant_prompt_message = AssistantPromptMessage(content=text) assistant_prompt_message = AssistantPromptMessage(content=text)

@ -17,6 +17,13 @@
- deepseek-ai/DeepSeek-V2.5 - deepseek-ai/DeepSeek-V2.5
- deepseek-ai/DeepSeek-V3 - deepseek-ai/DeepSeek-V3
- deepseek-ai/DeepSeek-Coder-V2-Instruct - deepseek-ai/DeepSeek-Coder-V2-Instruct
- deepseek-ai/DeepSeek-R1-Distill-Llama-8B
- deepseek-ai/DeepSeek-R1-Distill-Llama-70B
- deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B
- deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
- deepseek-ai/DeepSeek-R1-Distill-Qwen-14B
- deepseek-ai/DeepSeek-R1-Distill-Qwen-32B
- deepseek-ai/Janus-Pro-7B
- THUDM/glm-4-9b-chat - THUDM/glm-4-9b-chat
- 01-ai/Yi-1.5-34B-Chat-16K - 01-ai/Yi-1.5-34B-Chat-16K
- 01-ai/Yi-1.5-9B-Chat-16K - 01-ai/Yi-1.5-9B-Chat-16K

@ -0,0 +1,21 @@
model: deepseek-ai/DeepSeek-R1-Distill-Llama-70B
label:
zh_Hans: deepseek-ai/DeepSeek-R1-Distill-Llama-70B
en_US: deepseek-ai/DeepSeek-R1-Distill-Llama-70B
model_type: llm
features:
- agent-thought
model_properties:
mode: chat
context_size: 32000
parameter_rules:
- name: max_tokens
use_template: max_tokens
min: 1
max: 8192
default: 4096
pricing:
input: "0.00"
output: "4.3"
unit: "0.000001"
currency: RMB

@ -0,0 +1,21 @@
model: deepseek-ai/DeepSeek-R1-Distill-Llama-8B
label:
zh_Hans: deepseek-ai/DeepSeek-R1-Distill-Llama-8B
en_US: deepseek-ai/DeepSeek-R1-Distill-Llama-8B
model_type: llm
features:
- agent-thought
model_properties:
mode: chat
context_size: 32000
parameter_rules:
- name: max_tokens
use_template: max_tokens
min: 1
max: 8192
default: 4096
pricing:
input: "0.00"
output: "0.00"
unit: "0.000001"
currency: RMB

@ -0,0 +1,21 @@
model: deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B
label:
zh_Hans: deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B
en_US: deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B
model_type: llm
features:
- agent-thought
model_properties:
mode: chat
context_size: 32000
parameter_rules:
- name: max_tokens
use_template: max_tokens
min: 1
max: 8192
default: 4096
pricing:
input: "0.00"
output: "1.26"
unit: "0.000001"
currency: RMB

@ -0,0 +1,21 @@
model: deepseek-ai/DeepSeek-R1-Distill-Qwen-14B
label:
zh_Hans: deepseek-ai/DeepSeek-R1-Distill-Qwen-14B
en_US: deepseek-ai/DeepSeek-R1-Distill-Qwen-14B
model_type: llm
features:
- agent-thought
model_properties:
mode: chat
context_size: 32000
parameter_rules:
- name: max_tokens
use_template: max_tokens
min: 1
max: 8192
default: 4096
pricing:
input: "0.00"
output: "0.70"
unit: "0.000001"
currency: RMB

@ -0,0 +1,21 @@
model: deepseek-ai/DeepSeek-R1-Distill-Qwen-32B
label:
zh_Hans: deepseek-ai/DeepSeek-R1-Distill-Qwen-32B
en_US: deepseek-ai/DeepSeek-R1-Distill-Qwen-32B
model_type: llm
features:
- agent-thought
model_properties:
mode: chat
context_size: 32000
parameter_rules:
- name: max_tokens
use_template: max_tokens
min: 1
max: 8192
default: 4096
pricing:
input: "0.00"
output: "1.26"
unit: "0.000001"
currency: RMB

@ -0,0 +1,21 @@
model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
label:
zh_Hans: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
en_US: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
model_type: llm
features:
- agent-thought
model_properties:
mode: chat
context_size: 32000
parameter_rules:
- name: max_tokens
use_template: max_tokens
min: 1
max: 8192
default: 4096
pricing:
input: "0.00"
output: "0.00"
unit: "0.000001"
currency: RMB

@ -0,0 +1,22 @@
model: deepseek-ai/Janus-Pro-7B
label:
zh_Hans: deepseek-ai/Janus-Pro-7B
en_US: deepseek-ai/Janus-Pro-7B
model_type: llm
features:
- agent-thought
- vision
model_properties:
mode: chat
context_size: 32000
parameter_rules:
- name: max_tokens
use_template: max_tokens
min: 1
max: 8192
default: 4096
pricing:
input: "0.00"
output: "0.00"
unit: "0.000001"
currency: RMB

@ -69,6 +69,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -69,6 +69,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -69,6 +69,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -69,6 +69,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -68,6 +68,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -69,6 +69,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -69,6 +69,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -69,6 +69,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -68,6 +68,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -69,6 +69,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -67,6 +67,15 @@ parameter_rules:
help: help:
zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。
en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment.
- name: enable_search
type: boolean
default: false
label:
zh_Hans: 联网搜索
en_US: Web Search
help:
zh_Hans: 模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
en_US: The model has a built-in Internet search service. This parameter controls whether the model refers to Internet search results when generating text. When Internet search is enabled, the model will use the search results as reference information in the text generation process, but the model will "judge" whether to use Internet search results based on its internal logic.
- name: response_format - name: response_format
use_template: response_format use_template: response_format
pricing: pricing:

@ -1,4 +1,3 @@
import re
from collections.abc import Generator, Iterator from collections.abc import Generator, Iterator
from typing import Optional, cast from typing import Optional, cast
@ -636,16 +635,13 @@ class XinferenceAILargeLanguageModel(LargeLanguageModel):
handle stream chat generate response handle stream chat generate response
""" """
full_response = "" full_response = ""
is_reasoning_started_tag = False
for chunk in resp: for chunk in resp:
if len(chunk.choices) == 0: if len(chunk.choices) == 0:
continue continue
delta = chunk.choices[0] delta = chunk.choices[0]
if delta.finish_reason is None and (delta.delta.content is None or delta.delta.content == ""): if delta.finish_reason is None and (delta.delta.content is None or delta.delta.content == ""):
continue continue
delta_content = delta.delta.content delta_content = delta.delta.content or ""
if not delta_content:
delta_content = ""
# check if there is a tool call in the response # check if there is a tool call in the response
function_call = None function_call = None
tool_calls = [] tool_calls = []
@ -658,15 +654,7 @@ class XinferenceAILargeLanguageModel(LargeLanguageModel):
if function_call: if function_call:
assistant_message_tool_calls += [self._extract_response_function_call(function_call)] assistant_message_tool_calls += [self._extract_response_function_call(function_call)]
if not is_reasoning_started_tag and "<think>" in delta_content: delta_content = self._wrap_thinking_by_tag(delta_content)
is_reasoning_started_tag = True
delta_content = "> 💭 " + delta_content.replace("<think>", "")
elif is_reasoning_started_tag and "</think>" in delta_content:
delta_content = delta_content.replace("</think>", "") + "\n\n"
is_reasoning_started_tag = False
elif is_reasoning_started_tag:
if "\n" in delta_content:
delta_content = re.sub(r"\n(?!(>|\n))", "\n> ", delta_content)
# transform assistant message to prompt message # transform assistant message to prompt message
assistant_prompt_message = AssistantPromptMessage( assistant_prompt_message = AssistantPromptMessage(
content=delta_content or "", tool_calls=assistant_message_tool_calls content=delta_content or "", tool_calls=assistant_message_tool_calls

@ -3,7 +3,7 @@ from typing import Any, Optional
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field, field_validator
from core.model_runtime.entities import ImagePromptMessageContent from core.model_runtime.entities import ImagePromptMessageContent, LLMMode
from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, CompletionModelPromptTemplate, MemoryConfig from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, CompletionModelPromptTemplate, MemoryConfig
from core.workflow.entities.variable_entities import VariableSelector from core.workflow.entities.variable_entities import VariableSelector
from core.workflow.nodes.base import BaseNodeData from core.workflow.nodes.base import BaseNodeData
@ -12,7 +12,7 @@ from core.workflow.nodes.base import BaseNodeData
class ModelConfig(BaseModel): class ModelConfig(BaseModel):
provider: str provider: str
name: str name: str
mode: str mode: LLMMode
completion_params: dict[str, Any] = {} completion_params: dict[str, Any] = {}

@ -32,7 +32,11 @@ class AwsS3Storage(BaseStorage):
aws_access_key_id=dify_config.S3_ACCESS_KEY, aws_access_key_id=dify_config.S3_ACCESS_KEY,
endpoint_url=dify_config.S3_ENDPOINT, endpoint_url=dify_config.S3_ENDPOINT,
region_name=dify_config.S3_REGION, region_name=dify_config.S3_REGION,
config=Config(s3={"addressing_style": dify_config.S3_ADDRESS_STYLE}), config=Config(
s3={"addressing_style": dify_config.S3_ADDRESS_STYLE},
request_checksum_calculation="when_required",
response_checksum_validation="when_required",
),
) )
# create bucket # create bucket
try: try:

@ -77,6 +77,7 @@ class AccountService:
prefix="email_code_account_deletion_rate_limit", max_attempts=1, time_window=60 * 1 prefix="email_code_account_deletion_rate_limit", max_attempts=1, time_window=60 * 1
) )
LOGIN_MAX_ERROR_LIMITS = 5 LOGIN_MAX_ERROR_LIMITS = 5
FORGOT_PASSWORD_MAX_ERROR_LIMITS = 5
@staticmethod @staticmethod
def _get_refresh_token_key(refresh_token: str) -> str: def _get_refresh_token_key(refresh_token: str) -> str:
@ -503,6 +504,32 @@ class AccountService:
key = f"login_error_rate_limit:{email}" key = f"login_error_rate_limit:{email}"
redis_client.delete(key) redis_client.delete(key)
@staticmethod
def add_forgot_password_error_rate_limit(email: str) -> None:
key = f"forgot_password_error_rate_limit:{email}"
count = redis_client.get(key)
if count is None:
count = 0
count = int(count) + 1
redis_client.setex(key, dify_config.FORGOT_PASSWORD_LOCKOUT_DURATION, count)
@staticmethod
def is_forgot_password_error_rate_limit(email: str) -> bool:
key = f"forgot_password_error_rate_limit:{email}"
count = redis_client.get(key)
if count is None:
return False
count = int(count)
if count > AccountService.FORGOT_PASSWORD_MAX_ERROR_LIMITS:
return True
return False
@staticmethod
def reset_forgot_password_error_rate_limit(email: str):
key = f"forgot_password_error_rate_limit:{email}"
redis_client.delete(key)
@staticmethod @staticmethod
def is_email_send_ip_limit(ip_address: str): def is_email_send_ip_limit(ip_address: str):
minute_key = f"email_send_ip_limit_minute:{ip_address}" minute_key = f"email_send_ip_limit_minute:{ip_address}"

@ -2,7 +2,7 @@ version: '3'
services: services:
# API service # API service
api: api:
image: langgenius/dify-api:0.15.2 image: langgenius/dify-api:0.15.3
restart: always restart: always
environment: environment:
# Startup mode, 'api' starts the API server. # Startup mode, 'api' starts the API server.
@ -227,7 +227,7 @@ services:
# worker service # worker service
# The Celery worker for processing the queue. # The Celery worker for processing the queue.
worker: worker:
image: langgenius/dify-api:0.15.2 image: langgenius/dify-api:0.15.3
restart: always restart: always
environment: environment:
CONSOLE_WEB_URL: '' CONSOLE_WEB_URL: ''
@ -397,7 +397,7 @@ services:
# Frontend web application. # Frontend web application.
web: web:
image: langgenius/dify-web:0.15.2 image: langgenius/dify-web:0.15.3
restart: always restart: always
environment: environment:
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is # The base URL of console application api server, refers to the Console base URL of WEB service if console domain is

@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env
services: services:
# API service # API service
api: api:
image: langgenius/dify-api:0.15.2 image: langgenius/dify-api:0.15.3
restart: always restart: always
environment: environment:
# Use the shared environment variables. # Use the shared environment variables.
@ -25,7 +25,7 @@ services:
# worker service # worker service
# The Celery worker for processing the queue. # The Celery worker for processing the queue.
worker: worker:
image: langgenius/dify-api:0.15.2 image: langgenius/dify-api:0.15.3
restart: always restart: always
environment: environment:
# Use the shared environment variables. # Use the shared environment variables.
@ -47,7 +47,7 @@ services:
# Frontend web application. # Frontend web application.
web: web:
image: langgenius/dify-web:0.15.2 image: langgenius/dify-web:0.15.3
restart: always restart: always
environment: environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-} CONSOLE_API_URL: ${CONSOLE_API_URL:-}

@ -393,7 +393,7 @@ x-shared-env: &shared-api-worker-env
services: services:
# API service # API service
api: api:
image: langgenius/dify-api:0.15.2 image: langgenius/dify-api:0.15.3
restart: always restart: always
environment: environment:
# Use the shared environment variables. # Use the shared environment variables.
@ -416,7 +416,7 @@ services:
# worker service # worker service
# The Celery worker for processing the queue. # The Celery worker for processing the queue.
worker: worker:
image: langgenius/dify-api:0.15.2 image: langgenius/dify-api:0.15.3
restart: always restart: always
environment: environment:
# Use the shared environment variables. # Use the shared environment variables.
@ -438,7 +438,7 @@ services:
# Frontend web application. # Frontend web application.
web: web:
image: langgenius/dify-web:0.15.2 image: langgenius/dify-web:0.15.3
restart: always restart: always
environment: environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-} CONSOLE_API_URL: ${CONSOLE_API_URL:-}

@ -161,9 +161,9 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
} }
return ( return (
<div className={cn(s.app, 'flex', 'overflow-hidden')}> <div className={cn(s.app, 'flex relative', 'overflow-hidden')}>
{appDetail && ( {appDetail && (
<AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background} desc={appDetail.mode} navigation={navigation} /> <AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background as string} desc={appDetail.mode} navigation={navigation} />
)} )}
<div className="bg-components-panel-bg grow overflow-hidden"> <div className="bg-components-panel-bg grow overflow-hidden">
{children} {children}

@ -24,9 +24,11 @@ import AppContext from '@/context/app-context'
export type ICardViewProps = { export type ICardViewProps = {
appId: string appId: string
isInPanel?: boolean
className?: string
} }
const CardView: FC<ICardViewProps> = ({ appId }) => { const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const appDetail = useAppStore(state => state.appDetail) const appDetail = useAppStore(state => state.appDetail)
@ -120,10 +122,11 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
return <Loading /> return <Loading />
return ( return (
<div className="grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6"> <div className={className || 'grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'}>
<AppCard <AppCard
appInfo={appDetail} appInfo={appDetail}
cardType="webapp" cardType="webapp"
isInPanel={isInPanel}
onChangeStatus={onChangeSiteStatus} onChangeStatus={onChangeSiteStatus}
onGenerateCode={onGenerateCode} onGenerateCode={onGenerateCode}
onSaveSiteConfig={onSaveSiteConfig} onSaveSiteConfig={onSaveSiteConfig}
@ -131,6 +134,7 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
<AppCard <AppCard
cardType="api" cardType="api"
appInfo={appDetail} appInfo={appDetail}
isInPanel={isInPanel}
onChangeStatus={onChangeApiStatus} onChangeStatus={onChangeApiStatus}
/> />
</div> </div>

@ -31,8 +31,6 @@ const ApiServer: FC<ApiServerProps> = ({
</div> </div>
<SecretKeyButton <SecretKeyButton
className='flex-shrink-0 !h-8 bg-white' className='flex-shrink-0 !h-8 bg-white'
textCls='!text-gray-700 font-medium'
iconCls='stroke-[1.2px]'
/> />
</div> </div>
) )

@ -1,18 +1,18 @@
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useContext, useContextSelector } from 'use-context-selector' import { useContext, useContextSelector } from 'use-context-selector'
import { RiArrowDownSLine } from '@remixicon/react'
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import {
RiDeleteBinLine,
RiEditLine,
RiEqualizer2Line,
RiFileCopy2Line,
RiFileDownloadLine,
RiFileUploadLine,
} from '@remixicon/react'
import AppIcon from '../base/app-icon' import AppIcon from '../base/app-icon'
import SwitchAppModal from '../app/switch-app-modal' import SwitchAppModal from '../app/switch-app-modal'
import s from './style.module.css'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Divider from '@/app/components/base/divider'
import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
@ -22,8 +22,6 @@ import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/ap
import DuplicateAppModal from '@/app/components/app/duplicate-modal' import DuplicateAppModal from '@/app/components/app/duplicate-modal'
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
import CreateAppModal from '@/app/components/explore/create-app-modal' import CreateAppModal from '@/app/components/explore/create-app-modal'
import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication'
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { getRedirection } from '@/utils/app-redirection' import { getRedirection } from '@/utils/app-redirection'
@ -31,6 +29,9 @@ import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal'
import type { EnvironmentVariable } from '@/app/components/workflow/types' import type { EnvironmentVariable } from '@/app/components/workflow/types'
import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal' import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
import { fetchWorkflowDraft } from '@/service/workflow' import { fetchWorkflowDraft } from '@/service/workflow'
import ContentDialog from '@/app/components/base/content-dialog'
import Button from '@/app/components/base/button'
import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView'
export type IAppInfoProps = { export type IAppInfoProps = {
expand: boolean expand: boolean
@ -47,7 +48,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
const [showEditModal, setShowEditModal] = useState(false) const [showEditModal, setShowEditModal] = useState(false)
const [showDuplicateModal, setShowDuplicateModal] = useState(false) const [showDuplicateModal, setShowDuplicateModal] = useState(false)
const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showSwitchTip, setShowSwitchTip] = useState<string>('')
const [showSwitchModal, setShowSwitchModal] = useState<boolean>(false) const [showSwitchModal, setShowSwitchModal] = useState<boolean>(false)
const [showImportDSLModal, setShowImportDSLModal] = useState<boolean>(false) const [showImportDSLModal, setShowImportDSLModal] = useState<boolean>(false)
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([]) const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
@ -183,291 +183,199 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
return null return null
return ( return (
<PortalToFollowElem <div>
open={open} <button
onOpenChange={setOpen} onClick={() => {
placement='bottom-start' if (isCurrentWorkspaceEditor)
offset={4} setOpen(v => !v)
> }}
<div className='relative'> className='block w-full'
<PortalToFollowElemTrigger >
onClick={() => { <div className={cn('flex rounded-lg', expand ? 'p-2 pb-2.5 flex-col gap-2' : 'p-1 gap-1 justify-center items-start', open && 'bg-state-base-hover', isCurrentWorkspaceEditor && 'hover:bg-state-base-hover cursor-pointer')}>
if (isCurrentWorkspaceEditor) <div className={`flex items-center self-stretch ${expand ? 'justify-between' : 'flex-col gap-1'}`}>
setOpen(v => !v) <AppIcon
}} size={expand ? 'large' : 'small'}
className='block' iconType={appDetail.icon_type}
> icon={appDetail.icon}
<div className={cn('flex p-1 rounded-lg', open && 'bg-gray-100', isCurrentWorkspaceEditor && 'hover:bg-gray-100 cursor-pointer')}> background={appDetail.icon_background}
<div className='relative shrink-0 mr-2'> imageUrl={appDetail.icon_url}
<AppIcon />
size={expand ? 'large' : 'small'} <div className='flex p-0.5 justify-center items-center rounded-md'>
iconType={appDetail.icon_type} <div className='flex w-5 h-5 justify-center items-center'>
icon={appDetail.icon} <RiEqualizer2Line className='w-4 h-4 text-text-tertiary' />
background={appDetail.icon_background}
imageUrl={appDetail.icon_url}
/>
<span className={cn(
'absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm',
!expand && '!w-3.5 !h-3.5 !bottom-[-2px] !right-[-2px]',
)}>
{appDetail.mode === 'advanced-chat' && (
<ChatBot className={cn('w-3 h-3 text-[#1570EF]', !expand && '!w-2.5 !h-2.5')} />
)}
{appDetail.mode === 'agent-chat' && (
<CuteRobot className={cn('w-3 h-3 text-indigo-600', !expand && '!w-2.5 !h-2.5')} />
)}
{appDetail.mode === 'chat' && (
<ChatBot className={cn('w-3 h-3 text-[#1570EF]', !expand && '!w-2.5 !h-2.5')} />
)}
{appDetail.mode === 'completion' && (
<AiText className={cn('w-3 h-3 text-[#0E9384]', !expand && '!w-2.5 !h-2.5')} />
)}
{appDetail.mode === 'workflow' && (
<Route className={cn('w-3 h-3 text-[#f79009]', !expand && '!w-2.5 !h-2.5')} />
)}
</span>
</div>
{expand && (
<div className="grow w-0">
<div className='flex justify-between items-center text-sm leading-5 font-medium text-text-secondary'>
<div className='truncate' title={appDetail.name}>{appDetail.name}</div>
{isCurrentWorkspaceEditor && <RiArrowDownSLine className='shrink-0 ml-[2px] w-3 h-3 text-gray-500' />}
</div>
<div className='flex items-center text-[10px] leading-[18px] font-medium text-gray-500 gap-1'>
{appDetail.mode === 'advanced-chat' && (
<>
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
<div title={t('app.types.advanced') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.advanced').toUpperCase()}</div>
</>
)}
{appDetail.mode === 'agent-chat' && (
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.agent').toUpperCase()}</div>
)}
{appDetail.mode === 'chat' && (
<>
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
<div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
</>
)}
{appDetail.mode === 'completion' && (
<>
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.completion').toUpperCase()}</div>
<div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
</>
)}
{appDetail.mode === 'workflow' && (
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.workflow').toUpperCase()}</div>
)}
</div>
</div> </div>
)} </div>
</div> </div>
</PortalToFollowElemTrigger> {
<PortalToFollowElemContent className='z-[1002]'> expand && (
<div className='relative w-[320px] bg-white rounded-2xl shadow-xl'> <div className='flex flex-col items-start gap-1'>
{/* header */} <div className='flex w-full'>
<div className={cn('flex pl-4 pt-3 pr-3', !appDetail.description && 'pb-2')}> <div className='text-text-secondary system-md-semibold truncate'>{appDetail.name}</div>
<div className='relative shrink-0 mr-2'>
<AppIcon
size="large"
iconType={appDetail.icon_type}
icon={appDetail.icon}
background={appDetail.icon_background}
imageUrl={appDetail.icon_url}
/>
<span className='absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
{appDetail.mode === 'advanced-chat' && (
<ChatBot className='w-3 h-3 text-[#1570EF]' />
)}
{appDetail.mode === 'agent-chat' && (
<CuteRobot className='w-3 h-3 text-indigo-600' />
)}
{appDetail.mode === 'chat' && (
<ChatBot className='w-3 h-3 text-[#1570EF]' />
)}
{appDetail.mode === 'completion' && (
<AiText className='w-3 h-3 text-[#0E9384]' />
)}
{appDetail.mode === 'workflow' && (
<Route className='w-3 h-3 text-[#f79009]' />
)}
</span>
</div>
<div className='grow w-0'>
<div title={appDetail.name} className='flex justify-between items-center text-sm leading-5 font-medium text-gray-900 truncate'>{appDetail.name}</div>
<div className='flex items-center text-[10px] leading-[18px] font-medium text-gray-500 gap-1'>
{appDetail.mode === 'advanced-chat' && (
<>
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
<div title={t('app.types.advanced') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.advanced').toUpperCase()}</div>
</>
)}
{appDetail.mode === 'agent-chat' && (
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.agent').toUpperCase()}</div>
)}
{appDetail.mode === 'chat' && (
<>
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
<div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
</>
)}
{appDetail.mode === 'completion' && (
<>
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.completion').toUpperCase()}</div>
<div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
</>
)}
{appDetail.mode === 'workflow' && (
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.workflow').toUpperCase()}</div>
)}
</div> </div>
<div className='text-text-tertiary system-2xs-medium-uppercase'>{appDetail.mode === 'advanced-chat' ? t('app.types.chatbot') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
</div> </div>
)
}
</div>
</button>
<ContentDialog
show={open}
onClose={() => setOpen(false)}
className='!p-0 flex flex-col absolute left-2 top-2 bottom-2 w-[420px] rounded-2xl'
>
<div className='flex p-4 flex-col justify-center items-start gap-3 self-stretch shrink-0'>
<div className='flex items-center gap-3 self-stretch'>
<AppIcon
size="large"
iconType={appDetail.icon_type}
icon={appDetail.icon}
background={appDetail.icon_background}
imageUrl={appDetail.icon_url}
/>
<div className='flex flex-col justify-center items-start grow w-full'>
<div className='text-text-secondary system-md-semibold truncate w-full'>{appDetail.name}</div>
<div className='text-text-tertiary system-2xs-medium-uppercase'>{appDetail.mode === 'advanced-chat' ? t('app.types.chatbot') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
</div> </div>
{/* description */} </div>
{appDetail.description && ( {/* description */}
<div className='px-4 py-2 text-gray-500 text-xs leading-[18px]'>{appDetail.description}</div> {appDetail.description && (
)} <div className='text-text-tertiary system-xs-regular'>{appDetail.description}</div>
{/* operations */} )}
<Divider className="!my-1" /> {/* operations */}
<div className="w-full py-1"> <div className='flex items-center gap-1 self-stretch'>
<div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={() => { <Button
size={'small'}
variant={'secondary'}
className='gap-[1px]'
onClick={() => {
setOpen(false) setOpen(false)
setShowEditModal(true) setShowEditModal(true)
}}> }}
<span className='text-gray-700 text-sm leading-5'>{t('app.editApp')}</span> >
</div> <RiEditLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
<div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={() => { <span className='text-components-button-secondary-text system-xs-medium'>{t('app.editApp')}</span>
</Button>
<Button
size={'small'}
variant={'secondary'}
className='gap-[1px]'
onClick={() => {
setOpen(false) setOpen(false)
setShowDuplicateModal(true) setShowDuplicateModal(true)
}}> }}
<span className='text-gray-700 text-sm leading-5'>{t('app.duplicate')}</span>
</div>
{(appDetail.mode === 'completion' || appDetail.mode === 'chat') && (
<>
<Divider className="!my-1" />
<div
className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer'
onMouseEnter={() => setShowSwitchTip(appDetail.mode)}
onMouseLeave={() => setShowSwitchTip('')}
onClick={() => {
setOpen(false)
setShowSwitchModal(true)
}}
>
<span className='text-gray-700 text-sm leading-5'>{t('app.switch')}</span>
</div>
</>
)}
<Divider className="!my-1" />
<div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={exportCheck}>
<span className='text-gray-700 text-sm leading-5'>{t('app.export')}</span>
</div>
{
(appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (
<div
className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer'
onClick={() => {
setOpen(false)
setShowImportDSLModal(true)
}}>
<span className='text-gray-700 text-sm leading-5'>{t('workflow.common.importDSL')}</span>
</div>
)
}
<Divider className="!my-1" />
<div className='group h-9 py-2 px-3 mx-1 flex items-center hover:bg-red-50 rounded-lg cursor-pointer' onClick={() => {
setOpen(false)
setShowConfirmDelete(true)
}}>
<span className='text-gray-700 text-sm leading-5 group-hover:text-red-500'>
{t('common.operation.delete')}
</span>
</div>
</div>
{/* switch tip */}
<div
className={cn(
'hidden absolute left-[324px] top-0 w-[376px] rounded-xl bg-white border-[0.5px] border-[rgba(0,0,0,0.05)] shadow-lg',
showSwitchTip && '!block',
)}
> >
<div className={cn( <RiFileCopy2Line className='w-3.5 h-3.5 text-components-button-secondary-text' />
'w-full h-[256px] bg-center bg-no-repeat bg-contain rounded-xl', <span className='text-components-button-secondary-text system-xs-medium'>{t('app.duplicate')}</span>
showSwitchTip === 'chat' && s.expertPic, </Button>
showSwitchTip === 'completion' && s.completionPic, <Button
)} /> size={'small'}
<div className='px-4 pb-2'> variant={'secondary'}
<div className='flex items-center gap-1 text-gray-700 text-md leading-6 font-semibold'> className='gap-[1px]'
{showSwitchTip === 'chat' ? t('app.types.advanced') : t('app.types.workflow')} onClick={exportCheck}
<span className='px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span> >
</div> <RiFileDownloadLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
<div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div> <span className='text-components-button-secondary-text system-xs-medium'>{t('app.export')}</span>
<div className='mt-1 text-gray-500 text-sm leading-5'>{t('app.newApp.advancedDescription')}</div> </Button>
</div> {
</div> (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (
<Button
size={'small'}
variant={'secondary'}
className='gap-[1px]'
onClick={() => {
setOpen(false)
setShowImportDSLModal(true)
}}
>
<RiFileUploadLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
<span className='text-components-button-secondary-text system-xs-medium'>{t('workflow.common.importDSL')}</span>
</Button>
)
}
</div> </div>
</PortalToFollowElemContent> </div>
{showSwitchModal && ( <div className='flex flex-1'>
<SwitchAppModal <CardView
inAppDetail appId={appDetail.id}
show={showSwitchModal} isInPanel={true}
appDetail={appDetail} className='flex flex-col px-2 py-1 gap-2 grow overflow-auto'
onClose={() => setShowSwitchModal(false)}
onSuccess={() => setShowSwitchModal(false)}
/>
)}
{showEditModal && (
<CreateAppModal
isEditModal
appName={appDetail.name}
appIconType={appDetail.icon_type}
appIcon={appDetail.icon}
appIconBackground={appDetail.icon_background}
appIconUrl={appDetail.icon_url}
appDescription={appDetail.description}
appMode={appDetail.mode}
appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon}
show={showEditModal}
onConfirm={onEdit}
onHide={() => setShowEditModal(false)}
/>
)}
{showDuplicateModal && (
<DuplicateAppModal
appName={appDetail.name}
icon_type={appDetail.icon_type}
icon={appDetail.icon}
icon_background={appDetail.icon_background}
icon_url={appDetail.icon_url}
show={showDuplicateModal}
onConfirm={onCopy}
onHide={() => setShowDuplicateModal(false)}
/>
)}
{showConfirmDelete && (
<Confirm
title={t('app.deleteAppConfirmTitle')}
content={t('app.deleteAppConfirmContent')}
isShow={showConfirmDelete}
onConfirm={onConfirmDelete}
onCancel={() => setShowConfirmDelete(false)}
/>
)}
{showImportDSLModal && (
<UpdateDSLModal
onCancel={() => setShowImportDSLModal(false)}
onBackup={exportCheck}
/>
)}
{secretEnvList.length > 0 && (
<DSLExportConfirmModal
envList={secretEnvList}
onConfirm={onExport}
onClose={() => setSecretEnvList([])}
/> />
)} </div>
</div> <div className='flex p-2 flex-col justify-center items-start gap-3 self-stretch border-t-[0.5px] border-divider-subtle shrink-0 min-h-fit'>
</PortalToFollowElem> <Button
size={'medium'}
variant={'ghost'}
className='gap-0.5'
onClick={() => {
setOpen(false)
setShowConfirmDelete(true)
}}
>
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary' />
<span className='text-text-tertiary system-sm-medium'>{t('common.operation.deleteApp')}</span>
</Button>
</div>
</ContentDialog>
{showSwitchModal && (
<SwitchAppModal
inAppDetail
show={showSwitchModal}
appDetail={appDetail}
onClose={() => setShowSwitchModal(false)}
onSuccess={() => setShowSwitchModal(false)}
/>
)}
{showEditModal && (
<CreateAppModal
isEditModal
appName={appDetail.name}
appIconType={appDetail.icon_type}
appIcon={appDetail.icon}
appIconBackground={appDetail.icon_background}
appIconUrl={appDetail.icon_url}
appDescription={appDetail.description}
appMode={appDetail.mode}
appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon}
show={showEditModal}
onConfirm={onEdit}
onHide={() => setShowEditModal(false)}
/>
)}
{showDuplicateModal && (
<DuplicateAppModal
appName={appDetail.name}
icon_type={appDetail.icon_type}
icon={appDetail.icon}
icon_background={appDetail.icon_background}
icon_url={appDetail.icon_url}
show={showDuplicateModal}
onConfirm={onCopy}
onHide={() => setShowDuplicateModal(false)}
/>
)}
{showConfirmDelete && (
<Confirm
title={t('app.deleteAppConfirmTitle')}
content={t('app.deleteAppConfirmContent')}
isShow={showConfirmDelete}
onConfirm={onConfirmDelete}
onCancel={() => setShowConfirmDelete(false)}
/>
)}
{showImportDSLModal && (
<UpdateDSLModal
onCancel={() => setShowImportDSLModal(false)}
onBackup={exportCheck}
/>
)}
{secretEnvList.length > 0 && (
<DSLExportConfirmModal
envList={secretEnvList}
onConfirm={onExport}
onClose={() => setSecretEnvList([])}
/>
)}
</div>
) )
} }

@ -58,7 +58,7 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<div className="flex items-start p-1"> <div className="flex items-center grow">
{icon && icon_background && iconType === 'app' && ( {icon && icon_background && iconType === 'app' && (
<div className='flex-shrink-0 mr-3'> <div className='flex-shrink-0 mr-3'>
<AppIcon icon={icon} background={icon_background} /> <AppIcon icon={icon} background={icon_background} />
@ -71,8 +71,10 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
} }
{mode === 'expand' && <div className="group"> {mode === 'expand' && <div className="group">
<div className={`flex flex-row items-center text-sm font-semibold text-gray-700 group-hover:text-gray-900 break-all ${textStyle?.main ?? ''}`}> <div className={`flex flex-row items-center system-md-semibold text-text-secondary group-hover:text-text-primary ${textStyle?.main ?? ''}`}>
{name} <div className="max-w-[180px] truncate">
{name}
</div>
{hoverTip {hoverTip
&& <Tooltip && <Tooltip
popupContent={ popupContent={
@ -86,7 +88,6 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
/> />
} }
</div> </div>
<div className={`text-xs font-normal text-gray-500 group-hover:text-gray-700 break-all ${textStyle?.extra ?? ''}`}>{type}</div>
<div className='text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? t('dataset.externalTag') : ''}</div> <div className='text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? t('dataset.externalTag') : ''}</div>
</div>} </div>}
</div> </div>

@ -57,7 +57,7 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
<div <div
className={` className={`
shrink-0 shrink-0
${expand ? 'p-3' : 'p-2'} ${expand ? 'p-2' : 'p-1'}
`} `}
> >
{iconType === 'app' && ( {iconType === 'app' && (

@ -1,14 +1,14 @@
'use client' 'use client'
import type { HTMLProps } from 'react'
import React, { useMemo, useState } from 'react' import React, { useMemo, useState } from 'react'
import {
Cog8ToothIcon,
DocumentTextIcon,
PaintBrushIcon,
RocketLaunchIcon,
} from '@heroicons/react/24/outline'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import {
RiBookOpenLine,
RiEqualizer2Line,
RiExternalLinkLine,
RiPaintBrushLine,
RiWindowLine,
} from '@remixicon/react'
import SettingsModal from './settings' import SettingsModal from './settings'
import EmbeddedModal from './embedded' import EmbeddedModal from './embedded'
import CustomizeModal from './customize' import CustomizeModal from './customize'
@ -18,7 +18,6 @@ import Tooltip from '@/app/components/base/tooltip'
import AppBasic from '@/app/components/app-sidebar/basic' import AppBasic from '@/app/components/app-sidebar/basic'
import { asyncRunSafe, randomString } from '@/utils' import { asyncRunSafe, randomString } from '@/utils'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Tag from '@/app/components/base/tag'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import CopyFeedback from '@/app/components/base/copy-feedback' import CopyFeedback from '@/app/components/base/copy-feedback'
@ -28,10 +27,12 @@ import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-butt
import type { AppDetailResponse } from '@/models/app' import type { AppDetailResponse } from '@/models/app'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import type { AppSSO } from '@/types/app' import type { AppSSO } from '@/types/app'
import Indicator from '@/app/components/header/indicator'
export type IAppCardProps = { export type IAppCardProps = {
className?: string className?: string
appInfo: AppDetailResponse & Partial<AppSSO> appInfo: AppDetailResponse & Partial<AppSSO>
isInPanel?: boolean
cardType?: 'api' | 'webapp' cardType?: 'api' | 'webapp'
customBgColor?: string customBgColor?: string
onChangeStatus: (val: boolean) => Promise<void> onChangeStatus: (val: boolean) => Promise<void>
@ -39,12 +40,9 @@ export type IAppCardProps = {
onGenerateCode?: () => Promise<void> onGenerateCode?: () => Promise<void>
} }
const EmbedIcon = ({ className = '' }: HTMLProps<HTMLDivElement>) => {
return <div className={`${style.codeBrowserIcon} ${className}`}></div>
}
function AppCard({ function AppCard({
appInfo, appInfo,
isInPanel,
cardType = 'webapp', cardType = 'webapp',
customBgColor, customBgColor,
onChangeStatus, onChangeStatus,
@ -66,17 +64,18 @@ function AppCard({
const OPERATIONS_MAP = useMemo(() => { const OPERATIONS_MAP = useMemo(() => {
const operationsMap = { const operationsMap = {
webapp: [ webapp: [
{ opName: t('appOverview.overview.appInfo.preview'), opIcon: RocketLaunchIcon }, { opName: t('appOverview.overview.appInfo.launch'), opIcon: RiExternalLinkLine },
{ opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: PaintBrushIcon },
] as { opName: string; opIcon: any }[], ] as { opName: string; opIcon: any }[],
api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: DocumentTextIcon }], api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: RiBookOpenLine }],
app: [], app: [],
} }
if (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') if (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow')
operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: EmbedIcon }) operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: RiWindowLine })
operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: RiPaintBrushLine })
if (isCurrentWorkspaceEditor) if (isCurrentWorkspaceEditor)
operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: Cog8ToothIcon }) operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: RiEqualizer2Line })
return operationsMap return operationsMap
}, [isCurrentWorkspaceEditor, appInfo, t]) }, [isCurrentWorkspaceEditor, appInfo, t])
@ -92,13 +91,9 @@ function AppCard({
const appUrl = `${app_base_url}/${appMode}/${access_token}` const appUrl = `${app_base_url}/${appMode}/${access_token}`
const apiUrl = appInfo?.api_base_url const apiUrl = appInfo?.api_base_url
let bgColor = 'bg-primary-50 bg-opacity-40'
if (cardType === 'api')
bgColor = 'bg-purple-50'
const genClickFuncByName = (opName: string) => { const genClickFuncByName = (opName: string) => {
switch (opName) { switch (opName) {
case t('appOverview.overview.appInfo.preview'): case t('appOverview.overview.appInfo.launch'):
return () => { return () => {
window.open(appUrl, '_blank') window.open(appUrl, '_blank')
} }
@ -135,49 +130,50 @@ function AppCard({
return ( return (
<div <div
className={ className={
`shadow-xs border-[0.5px] rounded-lg border-gray-200 ${className ?? ''}`} `${isInPanel ? 'border-l-[0.5px] border-t' : 'shadow-xs border-[0.5px]'} rounded-xl border-effects-highlight w-full max-w-full ${className ?? ''}`}
> >
<div className={`px-6 py-5 ${customBgColor ?? bgColor} rounded-lg`}> <div className={`${customBgColor ?? 'bg-background-default'} rounded-xl`}>
<div className="mb-2.5 flex flex-row items-start justify-between"> <div className='flex flex-col p-3 justify-center items-start gap-3 self-stretch border-b-[0.5px] border-divider-subtle w-full'>
<AppBasic <div className='flex items-center gap-3 self-stretch w-full'>
iconType={cardType} <AppBasic
icon={appInfo.icon} iconType={cardType}
icon_background={appInfo.icon_background} icon={appInfo.icon}
name={basicName} icon_background={appInfo.icon_background}
type={ name={basicName}
isApp type={
? t('appOverview.overview.appInfo.explanation') isApp
: t('appOverview.overview.apiInfo.explanation') ? t('appOverview.overview.appInfo.explanation')
} : t('appOverview.overview.apiInfo.explanation')
/> }
<div className="flex flex-row items-center h-9"> />
<Tag className="mr-2" color={runningStatus ? 'green' : 'yellow'}> <div className='flex items-center gap-1'>
{runningStatus <Indicator color={runningStatus ? 'green' : 'yellow'} />
? t('appOverview.overview.status.running') <div className={`${runningStatus ? 'text-text-success' : 'text-text-warning'} system-xs-semibold-uppercase`}>
: t('appOverview.overview.status.disable')} {runningStatus
</Tag> ? t('appOverview.overview.status.running')
: t('appOverview.overview.status.disable')}
</div>
</div>
<Switch defaultValue={runningStatus} onChange={onChangeStatus} disabled={toggleDisabled} /> <Switch defaultValue={runningStatus} onChange={onChangeStatus} disabled={toggleDisabled} />
</div> </div>
</div> <div className='flex flex-col justify-center items-start self-stretch'>
<div className="flex flex-col justify-center py-2"> <div className="pb-1 system-xs-medium text-text-tertiary">
<div className="py-1">
<div className="pb-1 text-xs text-gray-500">
{isApp {isApp
? t('appOverview.overview.appInfo.accessibleAddress') ? t('appOverview.overview.appInfo.accessibleAddress')
: t('appOverview.overview.apiInfo.accessibleAddress')} : t('appOverview.overview.apiInfo.accessibleAddress')}
</div> </div>
<div className="w-full h-9 pl-2 pr-0.5 py-0.5 bg-black bg-opacity-2 rounded-lg border border-black border-opacity-5 justify-start items-center inline-flex"> <div className="w-full h-9 pl-2 p-1 bg-components-input-bg-normal rounded-lg items-center inline-flex gap-0.5">
<div className="h-4 px-2 justify-start items-start gap-2 flex flex-1 min-w-0"> <div className="h-4 px-1 justify-start items-start gap-2 flex flex-1 min-w-0">
<div className="text-gray-700 text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap"> <div className="text-text-secondary text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap">
{isApp ? appUrl : apiUrl} {isApp ? appUrl : apiUrl}
</div> </div>
</div> </div>
<Divider type="vertical" className="!h-3.5 shrink-0 !mx-0.5" />
{isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} selectorId={randomString(8)} className={'hover:bg-gray-200'} />}
<CopyFeedback <CopyFeedback
content={isApp ? appUrl : apiUrl} content={isApp ? appUrl : apiUrl}
className={'hover:bg-gray-200'} className={'!size-6'}
/> />
{isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} className='z-50 !size-6 hover:bg-state-base-hover rounded-md' selectorId={randomString(8)} />}
{isApp && <Divider type="vertical" className="!h-3.5 shrink-0 !mx-0.5" />}
{/* button copy link/ button regenerate */} {/* button copy link/ button regenerate */}
{showConfirmDelete && ( {showConfirmDelete && (
<Confirm <Confirm
@ -197,7 +193,7 @@ function AppCard({
popupContent={t('appOverview.overview.appInfo.regenerate') || ''} popupContent={t('appOverview.overview.appInfo.regenerate') || ''}
> >
<div <div
className="w-8 h-8 ml-0.5 cursor-pointer hover:bg-gray-200 rounded-lg" className="w-6 h-6 cursor-pointer hover:bg-state-base-hover rounded-md"
onClick={() => setShowConfirmDelete(true)} onClick={() => setShowConfirmDelete(true)}
> >
<div <div
@ -210,8 +206,8 @@ function AppCard({
</div> </div>
</div> </div>
</div> </div>
<div className={'pt-2 flex flex-row items-center flex-wrap gap-y-2'}> <div className={'flex p-3 items-center gap-1 self-stretch'}>
{!isApp && <SecretKeyButton className='flex-shrink-0 !h-8 bg-white mr-2' textCls='!text-gray-700 font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />} {!isApp && <SecretKeyButton appId={appInfo.id} />}
{OPERATIONS_MAP[cardType].map((op) => { {OPERATIONS_MAP[cardType].map((op) => {
const disabled const disabled
= op.opName === t('appOverview.overview.appInfo.settings.entry') = op.opName === t('appOverview.overview.appInfo.settings.entry')
@ -219,7 +215,9 @@ function AppCard({
: !runningStatus : !runningStatus
return ( return (
<Button <Button
className="mr-2" className="mr-1 min-w-[88px]"
size="small"
variant={'ghost'}
key={op.opName} key={op.opName}
onClick={genClickFuncByName(op.opName)} onClick={genClickFuncByName(op.opName)}
disabled={disabled} disabled={disabled}
@ -230,9 +228,9 @@ function AppCard({
} }
popupClassName={disabled ? 'mt-[-8px]' : '!hidden'} popupClassName={disabled ? 'mt-[-8px]' : '!hidden'}
> >
<div className="flex flex-row items-center"> <div className="flex items-center justify-center gap-[1px]">
<op.opIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" /> <op.opIcon className="h-3.5 w-3.5" />
<span className="text-[13px]">{op.opName}</span> <div className={`${runningStatus ? 'text-text-tertiary' : 'text-components-button-ghost-text-disabled'} system-xs-medium px-[3px]`}>{op.opName}</div>
</div> </div>
</Tooltip> </Tooltip>
</Button> </Button>

@ -0,0 +1,59 @@
import { Fragment, type ReactNode } from 'react'
import { Transition } from '@headlessui/react'
import classNames from '@/utils/classnames'
type ContentDialogProps = {
className?: string
show: boolean
onClose?: () => void
children: ReactNode
}
const ContentDialog = ({
className,
show,
onClose,
children,
}: ContentDialogProps) => {
return (
<Transition
show={show}
as="div"
className="absolute left-0 top-0 w-full h-full z-20 p-2 box-border"
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
className="absolute left-0 inset-0 w-full bg-app-detail-overlay-bg"
onClick={onClose}
/>
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transform transition ease-out duration-300"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in duration-200"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>
<div className={classNames(
'absolute left-0 w-full bg-app-detail-bg border-r border-divider-burn',
className,
)}>
{children}
</div>
</Transition.Child>
</Transition>
)
}
export default ContentDialog

@ -35,7 +35,7 @@ const CopyFeedback = ({ content, className }: Props) => {
} }
> >
<div <div
className={`w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg ${ className={`w-8 h-8 cursor-pointer hover:bg-state-base-hover rounded-md ${
className ?? '' className ?? ''
}`} }`}
> >

@ -1,7 +1,7 @@
'use client' 'use client'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import QRCode from 'qrcode.react' import { QRCodeSVG } from 'qrcode.react'
import QrcodeStyle from './style.module.css' import QrcodeStyle from './style.module.css'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
@ -54,20 +54,20 @@ const ShareQRCode = ({ content, selectorId, className }: Props) => {
popupContent={t(`${prefixEmbedded}`) || ''} popupContent={t(`${prefixEmbedded}`) || ''}
> >
<div <div
className={`w-8 h-8 cursor-pointer rounded-lg ${className ?? ''}`} className={`w-8 h-8 cursor-pointer rounded-lg relative ${className ?? ''}`}
onClick={toggleQRCode} onClick={toggleQRCode}
> >
<div className={`w-full h-full ${QrcodeStyle.QrcodeIcon} ${isShow ? QrcodeStyle.show : ''}`} /> <div className={`w-full h-full ${QrcodeStyle.QrcodeIcon} ${isShow ? QrcodeStyle.show : ''}`} />
{isShow && ( {isShow && (
<div <div
ref={qrCodeRef} ref={qrCodeRef}
className={QrcodeStyle.qrcodeform} className={`${QrcodeStyle.qrcodeform} !absolute right-0 top-0`}
onClick={handlePanelClick} onClick={handlePanelClick}
> >
<QRCode size={160} value={content} className={QrcodeStyle.qrcodeimage}/> <QRCodeSVG size={160} value={content} className={QrcodeStyle.qrcodeimage}/>
<div className={QrcodeStyle.text}> <div className={QrcodeStyle.text}>
<div className={`text-gray-500 ${QrcodeStyle.scan}`}>{t('appOverview.overview.appInfo.qrcode.scan')}</div> <div className={`text-text-tertiary ${QrcodeStyle.scan}`}>{t('appOverview.overview.appInfo.qrcode.scan')}</div>
<div className={`text-gray-500 ${QrcodeStyle.scan}`}>·</div> <div className={`text-text-tertiary ${QrcodeStyle.scan}`}>·</div>
<div className={QrcodeStyle.download} onClick={downloadQR}>{t('appOverview.overview.appInfo.qrcode.download')}</div> <div className={QrcodeStyle.download} onClick={downloadQR}>{t('appOverview.overview.appInfo.qrcode.download')}</div>
</div> </div>
</div> </div>

@ -1,29 +1,31 @@
'use client' 'use client'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiKey2Line } from '@remixicon/react'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import SecretKeyModal from '@/app/components/develop/secret-key/secret-key-modal' import SecretKeyModal from '@/app/components/develop/secret-key/secret-key-modal'
// import { KeyIcon } from '@heroicons/react/20/solid'
type ISecretKeyButtonProps = { type ISecretKeyButtonProps = {
className?: string className?: string
appId?: string appId?: string
iconCls?: string
textCls?: string textCls?: string
} }
const SecretKeyButton = ({ className, appId, iconCls, textCls }: ISecretKeyButtonProps) => { const SecretKeyButton = ({ className, appId, textCls }: ISecretKeyButtonProps) => {
const [isVisible, setVisible] = useState(false) const [isVisible, setVisible] = useState(false)
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<> <>
<Button className={`px-3 ${className}`} onClick={() => setVisible(true)}> <Button
<div className={'flex items-center justify-center w-4 h-4 mr-2'}> className={`px-3 ${className}`}
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" className={iconCls}> onClick={() => setVisible(true)}
<path d="M9 3.66672C9.35362 3.66672 9.69276 3.80719 9.94281 4.05724C10.1929 4.30729 10.3333 4.64643 10.3333 5.00005M13 5.00005C13.0002 5.62483 12.854 6.24097 12.5732 6.79908C12.2924 7.3572 11.8847 7.84177 11.3829 8.21397C10.8811 8.58617 10.2991 8.83564 9.68347 8.94239C9.06788 9.04915 8.43584 9.01022 7.838 8.82872L6.33333 10.3334H5V11.6667H3.66667V13.0001H1.66667C1.48986 13.0001 1.32029 12.9298 1.19526 12.8048C1.07024 12.6798 1 12.5102 1 12.3334V10.6094C1.00004 10.4326 1.0703 10.263 1.19533 10.1381L5.17133 6.16205C5.00497 5.61206 4.95904 5.03268 5.0367 4.46335C5.11435 3.89402 5.31375 3.3481 5.62133 2.86275C5.92891 2.3774 6.33744 1.96401 6.81913 1.65073C7.30082 1.33745 7.84434 1.13162 8.41272 1.04725C8.9811 0.96289 9.56098 1.00197 10.1129 1.16184C10.6648 1.32171 11.1758 1.59861 11.6111 1.97369C12.0464 2.34878 12.3958 2.81324 12.6354 3.33548C12.8751 3.85771 12.9994 4.42545 13 5.00005Z" stroke="#1F2A37" strokeLinecap="round" strokeLinejoin="round" /> size='small'
</svg> variant='ghost'
>
<div className={'flex items-center justify-center w-3.5 h-3.5'}>
<RiKey2Line className='w-3.5 h-3.5 text-text-tertiary' />
</div> </div>
<div className={`text-[13px] text-gray-800 ${textCls}`}>{t('appApi.apiKey')}</div> <div className={`text-text-tertiary system-xs-medium px-[3px] ${textCls}`}>{t('appApi.apiKey')}</div>
</Button> </Button>
<SecretKeyModal isShow={isVisible} onClose={() => setVisible(false)} appId={appId} /> <SecretKeyModal isShow={isVisible} onClose={() => setVisible(false)} appId={appId} />
</> </>

@ -112,6 +112,7 @@ const translation = {
operation: 'Dokumentation', operation: 'Dokumentation',
}, },
}, },
launch: 'Abschießen',
}, },
apiInfo: { apiInfo: {
title: 'Backend-Service-API', title: 'Backend-Service-API',

@ -50,6 +50,7 @@ const translation = {
submit: 'Senden', submit: 'Senden',
skip: 'Schiff', skip: 'Schiff',
imageCopied: 'Kopiertes Bild', imageCopied: 'Kopiertes Bild',
deleteApp: 'App löschen',
}, },
placeholder: { placeholder: {
input: 'Bitte eingeben', input: 'Bitte eingeben',

@ -33,6 +33,7 @@ const translation = {
explanation: 'Ready-to-use AI WebApp', explanation: 'Ready-to-use AI WebApp',
accessibleAddress: 'Public URL', accessibleAddress: 'Public URL',
preview: 'Preview', preview: 'Preview',
launch: 'Launch',
regenerate: 'Regenerate', regenerate: 'Regenerate',
regenerateNotice: 'Do you want to regenerate the public URL?', regenerateNotice: 'Do you want to regenerate the public URL?',
preUseReminder: 'Please enable WebApp before continuing.', preUseReminder: 'Please enable WebApp before continuing.',

@ -27,6 +27,7 @@ const translation = {
sure: 'I\'m sure', sure: 'I\'m sure',
download: 'Download', download: 'Download',
delete: 'Delete', delete: 'Delete',
deleteApp: 'Delete App',
settings: 'Settings', settings: 'Settings',
setup: 'Setup', setup: 'Setup',
getForFree: 'Get for free', getForFree: 'Get for free',

@ -112,6 +112,7 @@ const translation = {
operation: 'Documentación', operation: 'Documentación',
}, },
}, },
launch: 'Lanzar',
}, },
apiInfo: { apiInfo: {
title: 'API del servicio backend', title: 'API del servicio backend',

@ -50,6 +50,7 @@ const translation = {
submit: 'Enviar', submit: 'Enviar',
skip: 'Navío', skip: 'Navío',
imageCopied: 'Imagen copiada', imageCopied: 'Imagen copiada',
deleteApp: 'Eliminar aplicación',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} es requerido', fieldRequired: '{{field}} es requerido',

@ -112,6 +112,7 @@ const translation = {
operation: 'مستندات', operation: 'مستندات',
}, },
}, },
launch: 'راه اندازی',
}, },
apiInfo: { apiInfo: {
title: 'API سرویس بک‌اند', title: 'API سرویس بک‌اند',

@ -50,6 +50,7 @@ const translation = {
submit: 'ارسال', submit: 'ارسال',
skip: 'کشتی', skip: 'کشتی',
imageCopied: 'تصویر کپی شده', imageCopied: 'تصویر کپی شده',
deleteApp: 'حذف برنامه',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} الزامی است', fieldRequired: '{{field}} الزامی است',

@ -112,6 +112,7 @@ const translation = {
operation: 'Documentation', operation: 'Documentation',
}, },
}, },
launch: 'Lancer',
}, },
apiInfo: { apiInfo: {
title: 'API de service Backend', title: 'API de service Backend',

@ -50,6 +50,7 @@ const translation = {
submit: 'Envoyer', submit: 'Envoyer',
skip: 'Bateau', skip: 'Bateau',
imageCopied: 'Image copied', imageCopied: 'Image copied',
deleteApp: 'Supprimer lapplication',
}, },
placeholder: { placeholder: {
input: 'Veuillez entrer', input: 'Veuillez entrer',

@ -123,6 +123,7 @@ const translation = {
operation: 'प्रलेखन', operation: 'प्रलेखन',
}, },
}, },
launch: 'लॉन्च',
}, },
apiInfo: { apiInfo: {
title: 'बैकएंड सेवा एपीआई', title: 'बैकएंड सेवा एपीआई',

@ -50,6 +50,7 @@ const translation = {
skip: 'जहाज़', skip: 'जहाज़',
submit: 'जमा करें', submit: 'जमा करें',
imageCopied: 'कॉपी की गई छवि', imageCopied: 'कॉपी की गई छवि',
deleteApp: 'ऐप हटाएं',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} आवश्यक है', fieldRequired: '{{field}} आवश्यक है',

@ -125,6 +125,7 @@ const translation = {
operation: 'Documentazione', operation: 'Documentazione',
}, },
}, },
launch: 'Lanciare',
}, },
apiInfo: { apiInfo: {
title: 'API del servizio backend', title: 'API del servizio backend',

@ -50,6 +50,7 @@ const translation = {
submit: 'Invia', submit: 'Invia',
skip: 'Nave', skip: 'Nave',
imageCopied: 'Immagine copiata', imageCopied: 'Immagine copiata',
deleteApp: 'Elimina app',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} è obbligatorio', fieldRequired: '{{field}} è obbligatorio',

@ -112,6 +112,7 @@ const translation = {
operation: 'ドキュメント', operation: 'ドキュメント',
}, },
}, },
launch: '発射',
}, },
apiInfo: { apiInfo: {
title: 'バックエンドサービスAPI', title: 'バックエンドサービスAPI',

@ -50,6 +50,7 @@ const translation = {
submit: '送信', submit: '送信',
skip: 'スキップ', skip: 'スキップ',
imageCopied: 'コピーした画像', imageCopied: 'コピーした画像',
deleteApp: 'アプリを削除',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}}は必要です', fieldRequired: '{{field}}は必要です',

@ -112,6 +112,7 @@ const translation = {
operation: '문서', operation: '문서',
}, },
}, },
launch: '발사',
}, },
apiInfo: { apiInfo: {
title: '백엔드 서비스 API', title: '백엔드 서비스 API',

@ -50,6 +50,7 @@ const translation = {
submit: '전송', submit: '전송',
skip: '배', skip: '배',
imageCopied: '복사된 이미지', imageCopied: '복사된 이미지',
deleteApp: '앱 삭제',
}, },
placeholder: { placeholder: {
input: '입력해주세요', input: '입력해주세요',

@ -123,6 +123,7 @@ const translation = {
operation: 'Dokumentacja', operation: 'Dokumentacja',
}, },
}, },
launch: 'Uruchomić',
}, },
apiInfo: { apiInfo: {
title: 'API usługi w tle', title: 'API usługi w tle',

@ -50,6 +50,7 @@ const translation = {
submit: 'Prześlij', submit: 'Prześlij',
skip: 'Statek', skip: 'Statek',
imageCopied: 'Skopiowany obraz', imageCopied: 'Skopiowany obraz',
deleteApp: 'Usuń aplikację',
}, },
placeholder: { placeholder: {
input: 'Proszę wprowadzić', input: 'Proszę wprowadzić',

@ -112,6 +112,7 @@ const translation = {
operation: 'Documentação', operation: 'Documentação',
}, },
}, },
launch: 'Lançar',
}, },
apiInfo: { apiInfo: {
title: 'API de Serviço de Back-end', title: 'API de Serviço de Back-end',

@ -50,6 +50,7 @@ const translation = {
submit: 'Enviar', submit: 'Enviar',
skip: 'Navio', skip: 'Navio',
imageCopied: 'Imagem copiada', imageCopied: 'Imagem copiada',
deleteApp: 'Excluir aplicativo',
}, },
placeholder: { placeholder: {
input: 'Por favor, insira', input: 'Por favor, insira',

@ -112,6 +112,7 @@ const translation = {
operation: 'Documentație', operation: 'Documentație',
}, },
}, },
launch: 'Lansa',
}, },
apiInfo: { apiInfo: {
title: 'API serviciu backend', title: 'API serviciu backend',

@ -50,6 +50,7 @@ const translation = {
submit: 'Prezinte', submit: 'Prezinte',
skip: 'Navă', skip: 'Navă',
imageCopied: 'Imagine copiată', imageCopied: 'Imagine copiată',
deleteApp: 'Ștergeți aplicația',
}, },
placeholder: { placeholder: {
input: 'Vă rugăm să introduceți', input: 'Vă rugăm să introduceți',

@ -112,6 +112,7 @@ const translation = {
operation: 'Документация', operation: 'Документация',
}, },
}, },
launch: 'Баркас',
}, },
apiInfo: { apiInfo: {
title: 'API серверной части', title: 'API серверной части',

@ -50,6 +50,7 @@ const translation = {
submit: 'Отправить', submit: 'Отправить',
skip: 'Корабль', skip: 'Корабль',
imageCopied: 'Скопированное изображение', imageCopied: 'Скопированное изображение',
deleteApp: 'Удалить приложение',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} обязательно', fieldRequired: '{{field}} обязательно',

@ -112,6 +112,7 @@ const translation = {
operation: 'Dokumentacija', operation: 'Dokumentacija',
}, },
}, },
launch: 'Začetek',
}, },
apiInfo: { apiInfo: {
title: 'API storitev v ozadju', title: 'API storitev v ozadju',

@ -50,6 +50,7 @@ const translation = {
submit: 'Predložiti', submit: 'Predložiti',
skip: 'Ladja', skip: 'Ladja',
imageCopied: 'Kopirana slika', imageCopied: 'Kopirana slika',
deleteApp: 'Izbriši aplikacijo',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} je obvezno', fieldRequired: '{{field}} je obvezno',

@ -112,6 +112,7 @@ const translation = {
operation: 'เอกสาร', operation: 'เอกสาร',
}, },
}, },
launch: 'เรือยนต์',
}, },
apiInfo: { apiInfo: {
title: 'API บริการแบ็กเอนด์', title: 'API บริการแบ็กเอนด์',

@ -50,6 +50,7 @@ const translation = {
skip: 'เรือ', skip: 'เรือ',
submit: 'ส่ง', submit: 'ส่ง',
imageCopied: 'ภาพที่คัดลอก', imageCopied: 'ภาพที่คัดลอก',
deleteApp: 'ลบแอพ',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} เป็นสิ่งจําเป็น', fieldRequired: '{{field}} เป็นสิ่งจําเป็น',

@ -112,6 +112,7 @@ const translation = {
operation: 'Dokümantasyon', operation: 'Dokümantasyon',
}, },
}, },
launch: 'Başlat',
}, },
apiInfo: { apiInfo: {
title: 'Arka Uç Servis API\'si', title: 'Arka Uç Servis API\'si',

@ -50,6 +50,7 @@ const translation = {
submit: 'Gönder', submit: 'Gönder',
skip: 'Gemi', skip: 'Gemi',
imageCopied: 'Kopyalanan görüntü', imageCopied: 'Kopyalanan görüntü',
deleteApp: 'Uygulamayı Sil',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} gereklidir', fieldRequired: '{{field}} gereklidir',

@ -112,6 +112,7 @@ const translation = {
operation: 'Документація', operation: 'Документація',
}, },
}, },
launch: 'Запуску',
}, },
apiInfo: { apiInfo: {
title: 'API сервісу Backend', title: 'API сервісу Backend',

@ -50,6 +50,7 @@ const translation = {
submit: 'Представити', submit: 'Представити',
skip: 'Корабель', skip: 'Корабель',
imageCopied: 'Скопійоване зображення', imageCopied: 'Скопійоване зображення',
deleteApp: 'Видалити програму',
}, },
placeholder: { placeholder: {
input: 'Будь ласка, введіть текст', input: 'Будь ласка, введіть текст',

@ -112,6 +112,7 @@ const translation = {
operation: 'Tài liệu', operation: 'Tài liệu',
}, },
}, },
launch: 'Phóng',
}, },
apiInfo: { apiInfo: {
title: 'API dịch vụ backend', title: 'API dịch vụ backend',

@ -50,6 +50,7 @@ const translation = {
submit: 'Trình', submit: 'Trình',
skip: 'Tàu', skip: 'Tàu',
imageCopied: 'Hình ảnh sao chép', imageCopied: 'Hình ảnh sao chép',
deleteApp: 'Xóa ứng dụng',
}, },
placeholder: { placeholder: {
input: 'Vui lòng nhập', input: 'Vui lòng nhập',

@ -33,6 +33,7 @@ const translation = {
explanation: '开箱即用的 AI WebApp', explanation: '开箱即用的 AI WebApp',
accessibleAddress: '公开访问 URL', accessibleAddress: '公开访问 URL',
preview: '预览', preview: '预览',
launch: '启动',
regenerate: '重新生成', regenerate: '重新生成',
regenerateNotice: '您是否要重新生成公开访问 URL', regenerateNotice: '您是否要重新生成公开访问 URL',
preUseReminder: '使用前请先打开开关', preUseReminder: '使用前请先打开开关',

@ -27,6 +27,7 @@ const translation = {
sure: '我确定', sure: '我确定',
download: '下载', download: '下载',
delete: '删除', delete: '删除',
deleteApp: '删除应用',
settings: '设置', settings: '设置',
setup: '设置', setup: '设置',
getForFree: '免费获取', getForFree: '免费获取',

@ -112,6 +112,7 @@ const translation = {
operation: '檢視文件', operation: '檢視文件',
}, },
}, },
launch: '發射',
}, },
apiInfo: { apiInfo: {
title: '後端服務 API', title: '後端服務 API',

@ -50,6 +50,7 @@ const translation = {
submit: '提交', submit: '提交',
skip: '船', skip: '船',
imageCopied: '複製的圖片', imageCopied: '複製的圖片',
deleteApp: '刪除應用程式',
}, },
placeholder: { placeholder: {
input: '請輸入', input: '請輸入',

@ -1,6 +1,6 @@
{ {
"name": "dify-web", "name": "dify-web",
"version": "0.15.2", "version": "0.15.3",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=18.17.0" "node": ">=18.17.0"

@ -99,6 +99,8 @@ const config = {
'chatbot-bg': 'var(--color-chatbot-bg)', 'chatbot-bg': 'var(--color-chatbot-bg)',
'chat-bubble-bg': 'var(--color-chat-bubble-bg)', 'chat-bubble-bg': 'var(--color-chat-bubble-bg)',
'workflow-process-bg': 'var(--color-workflow-process-bg)', 'workflow-process-bg': 'var(--color-workflow-process-bg)',
'app-detail-bg': 'var(--color-app-detail-bg)',
'app-detail-overlay-bg': 'var(--color-app-detail-overlay-bg)',
'dataset-chunk-process-success-bg': 'var(--color-dataset-chunk-process-success-bg)', 'dataset-chunk-process-success-bg': 'var(--color-dataset-chunk-process-success-bg)',
'dataset-chunk-process-error-bg': 'var(--color-dataset-chunk-process-error-bg)', 'dataset-chunk-process-error-bg': 'var(--color-dataset-chunk-process-error-bg)',
'dataset-chunk-detail-card-hover-bg': 'var(--color-dataset-chunk-detail-card-hover-bg)', 'dataset-chunk-detail-card-hover-bg': 'var(--color-dataset-chunk-detail-card-hover-bg)',

@ -19,6 +19,17 @@ html[data-theme="dark"] {
rgba(34, 34, 37, 0.9) -0.1%, rgba(34, 34, 37, 0.9) -0.1%,
rgba(29, 29, 32, 0.9) 98.26% rgba(29, 29, 32, 0.9) 98.26%
); );
--color-app-detail-bg: linear-gradient(
169deg,
#1D1D20 1.18%,
#222225 99.52%
);
--color-app-detail-overlay-bg: linear-gradient(
270deg,
rgba(0, 0, 0, 0.00) 0%,
rgba(24, 24, 27, 0.02) 8%,
rgba(24, 24, 27, 0.54) 100%
);
--color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%); --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%);
--color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%); --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%);
--color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #1D1D20 0%, #222225 100%); --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #1D1D20 0%, #222225 100%);

@ -19,6 +19,17 @@ html[data-theme="light"] {
rgba(249, 250, 251, 0.9) -0.1%, rgba(249, 250, 251, 0.9) -0.1%,
rgba(242, 244, 247, 0.9) 98.26% rgba(242, 244, 247, 0.9) 98.26%
); );
--color-app-detail-bg: linear-gradient(
169deg,
#F2F4F7 1.18%,
#F9FAFB 99.52%
);
--color-app-detail-overlay-bg: linear-gradient(
270deg,
rgba(0, 0, 0, 0.00) 0%,
rgba(16, 24, 40, 0.01) 8%,
rgba(16, 24, 40, 0.18) 100%
);
--color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%); --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%);
--color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%); --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%);
--color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%); --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%);

Loading…
Cancel
Save