Merge branch 'fix/update-default-github-star' into deploy/dev

This commit is contained in:
NFish 2025-03-12 17:23:51 +08:00
commit a981bdc8e9
31 changed files with 139 additions and 120 deletions

View File

@ -325,8 +325,8 @@ class DatasetInitApi(Resource):
@cloud_edition_billing_resource_check("vector_space") @cloud_edition_billing_resource_check("vector_space")
@cloud_edition_billing_rate_limit_check("knowledge") @cloud_edition_billing_rate_limit_check("knowledge")
def post(self): def post(self):
# The role of the current user in the ta table must be admin, owner, or editor # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
parser = reqparse.RequestParser() parser = reqparse.RequestParser()
@ -704,8 +704,8 @@ class DocumentProcessingApi(DocumentResource):
document_id = str(document_id) document_id = str(document_id)
document = self.get_document(dataset_id, document_id) document = self.get_document(dataset_id, document_id)
# The role of the current user in the ta table must be admin, owner, or editor # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
if action == "pause": if action == "pause":
@ -769,8 +769,8 @@ class DocumentMetadataApi(DocumentResource):
doc_type = req_data.get("doc_type") doc_type = req_data.get("doc_type")
doc_metadata = req_data.get("doc_metadata") doc_metadata = req_data.get("doc_metadata")
# The role of the current user in the ta table must be admin, owner, or editor # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
if doc_type is None or doc_metadata is None: if doc_type is None or doc_metadata is None:

View File

@ -123,8 +123,8 @@ class DatasetDocumentSegmentListApi(Resource):
raise NotFound("Document not found.") raise NotFound("Document not found.")
segment_ids = request.args.getlist("segment_id") segment_ids = request.args.getlist("segment_id")
# The role of the current user in the ta table must be admin or owner # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
try: try:
DatasetService.check_dataset_permission(dataset, current_user) DatasetService.check_dataset_permission(dataset, current_user)
@ -151,8 +151,8 @@ class DatasetDocumentSegmentApi(Resource):
raise NotFound("Document not found.") raise NotFound("Document not found.")
# check user's model setting # check user's model setting
DatasetService.check_dataset_model_setting(dataset) DatasetService.check_dataset_model_setting(dataset)
# The role of the current user in the ta table must be admin, owner, or editor # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
try: try:
@ -206,7 +206,7 @@ class DatasetDocumentSegmentAddApi(Resource):
document = DocumentService.get_document(dataset_id, document_id) document = DocumentService.get_document(dataset_id, document_id)
if not document: if not document:
raise NotFound("Document not found.") raise NotFound("Document not found.")
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
# check embedding model setting # check embedding model setting
if dataset.indexing_technique == "high_quality": if dataset.indexing_technique == "high_quality":
@ -281,8 +281,8 @@ class DatasetDocumentSegmentUpdateApi(Resource):
).first() ).first()
if not segment: if not segment:
raise NotFound("Segment not found.") raise NotFound("Segment not found.")
# The role of the current user in the ta table must be admin, owner, or editor # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
try: try:
DatasetService.check_dataset_permission(dataset, current_user) DatasetService.check_dataset_permission(dataset, current_user)
@ -325,8 +325,8 @@ class DatasetDocumentSegmentUpdateApi(Resource):
).first() ).first()
if not segment: if not segment:
raise NotFound("Segment not found.") raise NotFound("Segment not found.")
# The role of the current user in the ta table must be admin or owner # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
try: try:
DatasetService.check_dataset_permission(dataset, current_user) DatasetService.check_dataset_permission(dataset, current_user)
@ -428,7 +428,7 @@ class ChildChunkAddApi(Resource):
).first() ).first()
if not segment: if not segment:
raise NotFound("Segment not found.") raise NotFound("Segment not found.")
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
# check embedding model setting # check embedding model setting
if dataset.indexing_technique == "high_quality": if dataset.indexing_technique == "high_quality":
@ -528,8 +528,8 @@ class ChildChunkAddApi(Resource):
).first() ).first()
if not segment: if not segment:
raise NotFound("Segment not found.") raise NotFound("Segment not found.")
# The role of the current user in the ta table must be admin, owner, or editor # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
try: try:
DatasetService.check_dataset_permission(dataset, current_user) DatasetService.check_dataset_permission(dataset, current_user)
@ -579,8 +579,8 @@ class ChildChunkUpdateApi(Resource):
).first() ).first()
if not child_chunk: if not child_chunk:
raise NotFound("Child chunk not found.") raise NotFound("Child chunk not found.")
# The role of the current user in the ta table must be admin or owner # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
try: try:
DatasetService.check_dataset_permission(dataset, current_user) DatasetService.check_dataset_permission(dataset, current_user)
@ -624,8 +624,8 @@ class ChildChunkUpdateApi(Resource):
).first() ).first()
if not child_chunk: if not child_chunk:
raise NotFound("Child chunk not found.") raise NotFound("Child chunk not found.")
# The role of the current user in the ta table must be admin or owner # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
if not current_user.is_editor: if not current_user.is_dataset_editor:
raise Forbidden() raise Forbidden()
try: try:
DatasetService.check_dataset_permission(dataset, current_user) DatasetService.check_dataset_permission(dataset, current_user)

View File

@ -10,7 +10,7 @@ from controllers.service_api.app.error import NotChatAppError
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from fields.conversation_fields import message_file_fields from fields.conversation_fields import message_file_fields
from fields.message_fields import feedback_fields, retriever_resource_fields from fields.message_fields import agent_thought_fields, feedback_fields, retriever_resource_fields
from fields.raws import FilesContainedField from fields.raws import FilesContainedField
from libs.helper import TimestampField, uuid_value from libs.helper import TimestampField, uuid_value
from models.model import App, AppMode, EndUser from models.model import App, AppMode, EndUser
@ -19,20 +19,6 @@ from services.message_service import MessageService
class MessageListApi(Resource): class MessageListApi(Resource):
agent_thought_fields = {
"id": fields.String,
"chain_id": fields.String,
"message_id": fields.String,
"position": fields.Integer,
"thought": fields.String,
"tool": fields.String,
"tool_labels": fields.Raw,
"tool_input": fields.String,
"created_at": TimestampField,
"observation": fields.String,
"message_files": fields.List(fields.Nested(message_file_fields)),
}
message_fields = { message_fields = {
"id": fields.String, "id": fields.String,
"conversation_id": fields.String, "conversation_id": fields.String,

View File

@ -80,7 +80,7 @@ class AIModel(BaseModel):
) )
) )
elif isinstance(invoke_error, InvokeError): elif isinstance(invoke_error, InvokeError):
return invoke_error(description=f"[{self.provider_name}] {invoke_error.description}, {str(error)}") return InvokeError(description=f"[{self.provider_name}] {invoke_error.description}, {str(error)}")
else: else:
return error return error

View File

@ -57,11 +57,19 @@ class StreamProcessor(ABC):
# The branch_identify parameter is added to ensure that # The branch_identify parameter is added to ensure that
# only nodes in the correct logical branch are included. # only nodes in the correct logical branch are included.
reachable_node_ids.append(edge.target_node_id)
ids = self._fetch_node_ids_in_reachable_branch(edge.target_node_id, run_result.edge_source_handle) ids = self._fetch_node_ids_in_reachable_branch(edge.target_node_id, run_result.edge_source_handle)
reachable_node_ids.extend(ids) reachable_node_ids.extend(ids)
else: else:
# if the condition edge in parallel, and the target node is not in parallel, we should not remove it
# Issues: #13626
if (
finished_node_id in self.graph.node_parallel_mapping
and edge.target_node_id not in self.graph.parallel_mapping
):
continue
unreachable_first_node_ids.append(edge.target_node_id) unreachable_first_node_ids.append(edge.target_node_id)
unreachable_first_node_ids = list(set(unreachable_first_node_ids) - set(reachable_node_ids))
for node_id in unreachable_first_node_ids: for node_id in unreachable_first_node_ids:
self._remove_node_ids_in_unreachable_branch(node_id, reachable_node_ids) self._remove_node_ids_in_unreachable_branch(node_id, reachable_node_ids)

View File

@ -71,15 +71,8 @@ class OpenDALStorage(BaseStorage):
logger.debug(f"file {filename} downloaded to {target_filepath}") logger.debug(f"file {filename} downloaded to {target_filepath}")
def exists(self, filename: str) -> bool: def exists(self, filename: str) -> bool:
# FIXME this is a workaround for opendal python-binding do not have a exists method and no better res: bool = self.op.exists(path=filename)
# error handler here when opendal python-binding has a exists method, we should use it return res
# more https://github.com/apache/opendal/blob/main/bindings/python/src/operator.rs
try:
res: bool = self.op.stat(path=filename).mode.is_file()
logger.debug(f"file {filename} checked")
return res
except Exception:
return False
def delete(self, filename: str): def delete(self, filename: str):
if self.exists(filename): if self.exists(filename):

View File

@ -5,7 +5,6 @@ from .account import (
InvitationCode, InvitationCode,
Tenant, Tenant,
TenantAccountJoin, TenantAccountJoin,
TenantAccountJoinRole,
TenantAccountRole, TenantAccountRole,
TenantStatus, TenantStatus,
) )
@ -156,7 +155,6 @@ __all__ = [
"TagBinding", "TagBinding",
"Tenant", "Tenant",
"TenantAccountJoin", "TenantAccountJoin",
"TenantAccountJoinRole",
"TenantAccountRole", "TenantAccountRole",
"TenantDefaultModel", "TenantDefaultModel",
"TenantPreferredModelProvider", "TenantPreferredModelProvider",

View File

@ -220,13 +220,6 @@ class Tenant(db.Model): # type: ignore[name-defined]
self.custom_config = json.dumps(value) self.custom_config = json.dumps(value)
class TenantAccountJoinRole(enum.Enum):
OWNER = "owner"
ADMIN = "admin"
NORMAL = "normal"
DATASET_OPERATOR = "dataset_operator"
class TenantAccountJoin(db.Model): # type: ignore[name-defined] class TenantAccountJoin(db.Model): # type: ignore[name-defined]
__tablename__ = "tenant_account_joins" __tablename__ = "tenant_account_joins"
__table_args__ = ( __table_args__ = (

2
api/poetry.lock generated
View File

@ -9922,4 +9922,4 @@ cffi = ["cffi (>=1.11)"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.11,<3.13" python-versions = ">=3.11,<3.13"
content-hash = "b22f8a8e798de0ca992b6fce0296081e90732dd30a00dbc3b3db24158dc857e9" content-hash = "13cbc3af42af0b3013c4a63024c372886ed88080579b1e2e5083e3dd191d080d"

View File

@ -105,7 +105,7 @@ bce-python-sdk = "~0.9.23"
cos-python-sdk-v5 = "1.9.30" cos-python-sdk-v5 = "1.9.30"
esdk-obs-python = "3.24.6.1" esdk-obs-python = "3.24.6.1"
google-cloud-storage = "2.16.0" google-cloud-storage = "2.16.0"
opendal = "~0.45.12" opendal = "~0.45.16"
oss2 = "2.18.5" oss2 = "2.18.5"
supabase = "~2.8.1" supabase = "~2.8.1"
tos = "~2.7.1" tos = "~2.7.1"

View File

@ -28,7 +28,6 @@ from models.account import (
AccountStatus, AccountStatus,
Tenant, Tenant,
TenantAccountJoin, TenantAccountJoin,
TenantAccountJoinRole,
TenantAccountRole, TenantAccountRole,
TenantStatus, TenantStatus,
) )
@ -625,8 +624,8 @@ class TenantService:
@staticmethod @staticmethod
def create_tenant_member(tenant: Tenant, account: Account, role: str = "normal") -> TenantAccountJoin: def create_tenant_member(tenant: Tenant, account: Account, role: str = "normal") -> TenantAccountJoin:
"""Create tenant member""" """Create tenant member"""
if role == TenantAccountJoinRole.OWNER.value: if role == TenantAccountRole.OWNER.value:
if TenantService.has_roles(tenant, [TenantAccountJoinRole.OWNER]): if TenantService.has_roles(tenant, [TenantAccountRole.OWNER]):
logging.error(f"Tenant {tenant.id} has already an owner.") logging.error(f"Tenant {tenant.id} has already an owner.")
raise Exception("Tenant already has an owner.") raise Exception("Tenant already has an owner.")
@ -734,10 +733,10 @@ class TenantService:
return updated_accounts return updated_accounts
@staticmethod @staticmethod
def has_roles(tenant: Tenant, roles: list[TenantAccountJoinRole]) -> bool: def has_roles(tenant: Tenant, roles: list[TenantAccountRole]) -> bool:
"""Check if user has any of the given roles for a tenant""" """Check if user has any of the given roles for a tenant"""
if not all(isinstance(role, TenantAccountJoinRole) for role in roles): if not all(isinstance(role, TenantAccountRole) for role in roles):
raise ValueError("all roles must be TenantAccountJoinRole") raise ValueError("all roles must be TenantAccountRole")
return ( return (
db.session.query(TenantAccountJoin) db.session.query(TenantAccountJoin)
@ -749,7 +748,7 @@ class TenantService:
) )
@staticmethod @staticmethod
def get_user_role(account: Account, tenant: Tenant) -> Optional[TenantAccountJoinRole]: def get_user_role(account: Account, tenant: Tenant) -> Optional[TenantAccountRole]:
"""Get the role of the current account for a given tenant""" """Get the role of the current account for a given tenant"""
join = ( join = (
db.session.query(TenantAccountJoin) db.session.query(TenantAccountJoin)

View File

@ -2,7 +2,7 @@ from flask_login import current_user # type: ignore
from configs import dify_config from configs import dify_config
from extensions.ext_database import db from extensions.ext_database import db
from models.account import Tenant, TenantAccountJoin, TenantAccountJoinRole from models.account import Tenant, TenantAccountJoin, TenantAccountRole
from services.account_service import TenantService from services.account_service import TenantService
from services.feature_service import FeatureService from services.feature_service import FeatureService
@ -18,7 +18,6 @@ class WorkspaceService:
"plan": tenant.plan, "plan": tenant.plan,
"status": tenant.status, "status": tenant.status,
"created_at": tenant.created_at, "created_at": tenant.created_at,
"in_trail": True,
"trial_end_reason": None, "trial_end_reason": None,
"role": "normal", "role": "normal",
} }
@ -34,9 +33,7 @@ class WorkspaceService:
can_replace_logo = FeatureService.get_features(tenant_info["id"]).can_replace_logo can_replace_logo = FeatureService.get_features(tenant_info["id"]).can_replace_logo
if can_replace_logo and TenantService.has_roles( if can_replace_logo and TenantService.has_roles(tenant, [TenantAccountRole.OWNER, TenantAccountRole.ADMIN]):
tenant, [TenantAccountJoinRole.OWNER, TenantAccountJoinRole.ADMIN]
):
base_url = dify_config.FILES_URL base_url = dify_config.FILES_URL
replace_webapp_logo = ( replace_webapp_logo = (
f"{base_url}/files/workspaces/{tenant.id}/webapp-logo" f"{base_url}/files/workspaces/{tenant.id}/webapp-logo"

View File

@ -723,6 +723,9 @@ SSRF_PROXY_HTTPS_URL=http://ssrf_proxy:3128
# Maximum loop count in the workflow # Maximum loop count in the workflow
LOOP_NODE_MAX_COUNT=100 LOOP_NODE_MAX_COUNT=100
# The maximum number of tools that can be used in the agent.
MAX_TOOLS_NUM=10
# ------------------------------ # ------------------------------
# Environment Variables for web Service # Environment Variables for web Service
# ------------------------------ # ------------------------------

View File

@ -68,6 +68,7 @@ services:
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-} INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
PM2_INSTANCES: ${PM2_INSTANCES:-2} PM2_INSTANCES: ${PM2_INSTANCES:-2}
LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100}
MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10}
# The postgres database. # The postgres database.
db: db:

View File

@ -311,6 +311,7 @@ x-shared-env: &shared-api-worker-env
SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128} SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128}
SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128} SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128}
LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100}
MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10}
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000} TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
PGUSER: ${PGUSER:-${DB_USERNAME}} PGUSER: ${PGUSER:-${DB_USERNAME}}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-${DB_PASSWORD}} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-${DB_PASSWORD}}
@ -486,6 +487,7 @@ services:
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-} INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
PM2_INSTANCES: ${PM2_INSTANCES:-2} PM2_INSTANCES: ${PM2_INSTANCES:-2}
LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100}
MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10}
# The postgres database. # The postgres database.
db: db:

View File

@ -40,3 +40,6 @@ NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000
# Maximum loop count in the workflow # Maximum loop count in the workflow
NEXT_PUBLIC_LOOP_NODE_MAX_COUNT=100 NEXT_PUBLIC_LOOP_NODE_MAX_COUNT=100
# Maximum number of tools in the agent/workflow
NEXT_PUBLIC_MAX_TOOLS_NUM=10

View File

@ -1,8 +1,9 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react' import { RiCloseLine } from '@remixicon/react'
import { useClickAway } from 'ahooks'
import ItemPanel from './item-panel' import ItemPanel from './item-panel'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' import { CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication'
@ -31,6 +32,18 @@ const AgentSetting: FC<Props> = ({
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [tempPayload, setTempPayload] = useState(payload) const [tempPayload, setTempPayload] = useState(payload)
const ref = useRef(null)
const [mounted, setMounted] = useState(false)
useClickAway(() => {
if (mounted)
onCancel()
}, ref)
useEffect(() => {
setMounted(true)
}, [])
const handleSave = () => { const handleSave = () => {
onSave(tempPayload) onSave(tempPayload)
} }
@ -42,6 +55,7 @@ const AgentSetting: FC<Props> = ({
}} }}
> >
<div <div
ref={ref}
className='w-[640px] flex flex-col h-full overflow-hidden bg-components-panel-bg border-[0.5px] border-components-panel-border rounded-xl shadow-xl' className='w-[640px] flex flex-col h-full overflow-hidden bg-components-panel-bg border-[0.5px] border-components-panel-border rounded-xl shadow-xl'
> >
<div className='shrink-0 flex justify-between items-center pl-6 pr-5 h-14 border-b border-divider-regular'> <div className='shrink-0 flex justify-between items-center pl-6 pr-5 h-14 border-b border-divider-regular'>

View File

@ -641,7 +641,7 @@ Chat applications support session persistence, allowing previous chat history to
"tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}", "tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}",
"created_at": 1705988186, "created_at": 1705988186,
"observation": "image has been created and sent to user already, you should tell user to check it now.", "observation": "image has been created and sent to user already, you should tell user to check it now.",
"message_files": [ "files": [
"976990d2-5294-47e6-8f14-7356ba9d2d76" "976990d2-5294-47e6-8f14-7356ba9d2d76"
] ]
}, },
@ -655,7 +655,7 @@ Chat applications support session persistence, allowing previous chat history to
"tool_input": "", "tool_input": "",
"created_at": 1705988199, "created_at": 1705988199,
"observation": "", "observation": "",
"message_files": [] "files": []
} }
] ]
} }

View File

@ -641,7 +641,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
"tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}", "tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}",
"created_at": 1705988186, "created_at": 1705988186,
"observation": "画像はすでに作成され、ユーザーに送信されました。今すぐユーザーに確認するように伝えてください。", "observation": "画像はすでに作成され、ユーザーに送信されました。今すぐユーザーに確認するように伝えてください。",
"message_files": [ "files": [
"976990d2-5294-47e6-8f14-7356ba9d2d76" "976990d2-5294-47e6-8f14-7356ba9d2d76"
] ]
}, },
@ -655,7 +655,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
"tool_input": "", "tool_input": "",
"created_at": 1705988199, "created_at": 1705988199,
"observation": "", "observation": "",
"message_files": [] "files": []
} }
] ]
} }

View File

@ -657,7 +657,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
"tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}", "tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}",
"created_at": 1705988186, "created_at": 1705988186,
"observation": "image has been created and sent to user already, you should tell user to check it now.", "observation": "image has been created and sent to user already, you should tell user to check it now.",
"message_files": [ "files": [
"976990d2-5294-47e6-8f14-7356ba9d2d76" "976990d2-5294-47e6-8f14-7356ba9d2d76"
] ]
}, },
@ -671,7 +671,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
"tool_input": "", "tool_input": "",
"created_at": 1705988199, "created_at": 1705988199,
"observation": "", "observation": "",
"message_files": [] "files": []
} }
] ]
} }

View File

@ -17,7 +17,7 @@ const GithubStar: FC<{ className: string }> = (props) => {
queryKey: ['github-star'], queryKey: ['github-star'],
queryFn: getStar, queryFn: getStar,
enabled: process.env.NODE_ENV !== 'development', enabled: process.env.NODE_ENV !== 'development',
initialData: { stargazers_count: 6000 }, initialData: { stargazers_count: 81204 },
}) })
if (isFetching) if (isFetching)
return null return null

View File

@ -28,9 +28,6 @@ const nodeDefault: NodeDefault<LoopNodeType> = {
checkValid(payload: LoopNodeType, t: any) { checkValid(payload: LoopNodeType, t: any) {
let errorMessages = '' let errorMessages = ''
if (!errorMessages && (!payload.break_conditions || payload.break_conditions.length === 0))
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.loop.breakCondition') })
payload.break_conditions!.forEach((condition) => { payload.break_conditions!.forEach((condition) => {
if (!errorMessages && (!condition.variable_selector || condition.variable_selector.length === 0)) if (!errorMessages && (!condition.variable_selector || condition.variable_selector.length === 0))
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variable`) }) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variable`) })

View File

@ -55,6 +55,7 @@ const Panel: FC<NodePanelProps<LoopNodeType>> = ({
<div> <div>
<Field <Field
title={<div className='pl-3'>{t(`${i18nPrefix}.breakCondition`)}</div>} title={<div className='pl-3'>{t(`${i18nPrefix}.breakCondition`)}</div>}
tooltip={t(`${i18nPrefix}.breakConditionTip`)}
> >
<ConditionWrap <ConditionWrap
nodeId={id} nodeId={id}

View File

@ -17,12 +17,14 @@ import { getOperators } from './utils'
import { LogicalOperator } from './types' import { LogicalOperator } from './types'
import type { HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, LoopNodeType } from './types' import type { HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, LoopNodeType } from './types'
import useIsVarFileAttribute from './use-is-var-file-attribute' import useIsVarFileAttribute from './use-is-var-file-attribute'
import { useStore } from '@/app/components/workflow/store'
const DELIMITER = '@@@@@' const DELIMITER = '@@@@@'
const useConfig = (id: string, payload: LoopNodeType) => { const useConfig = (id: string, payload: LoopNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly() const { nodesReadOnly: readOnly } = useNodesReadOnly()
const { isNodeInLoop } = useIsNodeInLoop(id) const { isNodeInLoop } = useIsNodeInLoop(id)
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const conversationVariables = useStore(s => s.conversationVariables)
const { inputs, setInputs } = useNodeCrud<LoopNodeType>(id, payload) const { inputs, setInputs } = useNodeCrud<LoopNodeType>(id, payload)
@ -35,7 +37,7 @@ const useConfig = (id: string, payload: LoopNodeType) => {
const beforeNodes = getBeforeNodesInSameBranch(id) const beforeNodes = getBeforeNodesInSameBranch(id)
const loopChildrenNodes = getLoopNodeChildren(id) const loopChildrenNodes = getLoopNodeChildren(id)
const canChooseVarNodes = [...beforeNodes, ...loopChildrenNodes] const canChooseVarNodes = [...beforeNodes, ...loopChildrenNodes]
const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode) const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables)
// single run // single run
const loopInputKey = `${id}.input_selector` const loopInputKey = `${id}.input_selector`

View File

@ -1,6 +1,7 @@
'use client' 'use client'
import React, { useEffect } from 'react' import React, { useCallback, useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
@ -39,7 +40,7 @@ const InstallForm = () => {
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors, isSubmitting },
} = useForm<AccountFormValues>({ } = useForm<AccountFormValues>({
resolver: zodResolver(accountFormSchema), resolver: zodResolver(accountFormSchema),
defaultValues: { defaultValues: {
@ -59,9 +60,22 @@ const InstallForm = () => {
} }
const handleSetting = async () => { const handleSetting = async () => {
if (isSubmitting) return
handleSubmit(onSubmit)() handleSubmit(onSubmit)()
} }
const { run: debouncedHandleKeyDown } = useDebounceFn(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault()
handleSetting()
}
},
{ wait: 200 },
)
const handleKeyDown = useCallback(debouncedHandleKeyDown, [debouncedHandleKeyDown])
useEffect(() => { useEffect(() => {
fetchSetupStatus().then((res: SetupStatusResponse) => { fetchSetupStatus().then((res: SetupStatusResponse) => {
if (res.step === 'finished') { if (res.step === 'finished') {
@ -90,7 +104,7 @@ const InstallForm = () => {
</div> </div>
<div className="grow mt-8 sm:mx-auto sm:w-full sm:max-w-md"> <div className="grow mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white "> <div className="bg-white ">
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)} onKeyDown={handleKeyDown}>
<div className='mb-5'> <div className='mb-5'>
<label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900"> <label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
{t('login.email')} {t('login.email')}
@ -99,7 +113,7 @@ const InstallForm = () => {
<input <input
{...register('email')} {...register('email')}
placeholder={t('login.emailPlaceholder') || ''} placeholder={t('login.emailPlaceholder') || ''}
className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm'} className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder:text-gray-400 caret-primary-600 sm:text-sm'}
/> />
{errors.email && <span className='text-red-400 text-sm'>{t(`${errors.email?.message}`)}</span>} {errors.email && <span className='text-red-400 text-sm'>{t(`${errors.email?.message}`)}</span>}
</div> </div>
@ -114,7 +128,7 @@ const InstallForm = () => {
<input <input
{...register('name')} {...register('name')}
placeholder={t('login.namePlaceholder') || ''} placeholder={t('login.namePlaceholder') || ''}
className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'} className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder:text-gray-400 caret-primary-600 sm:text-sm pr-10'}
/> />
</div> </div>
{errors.name && <span className='text-red-400 text-sm'>{t(`${errors.name.message}`)}</span>} {errors.name && <span className='text-red-400 text-sm'>{t(`${errors.name.message}`)}</span>}
@ -129,7 +143,7 @@ const InstallForm = () => {
{...register('password')} {...register('password')}
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
placeholder={t('login.passwordPlaceholder') || ''} placeholder={t('login.passwordPlaceholder') || ''}
className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'} className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder:text-gray-400 caret-primary-600 sm:text-sm pr-10'}
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-3"> <div className="absolute inset-y-0 right-0 flex items-center pr-3">

View File

@ -47,6 +47,7 @@ const LocaleLayout = ({
data-public-maintenance-notice={process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE} data-public-maintenance-notice={process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE}
data-public-site-about={process.env.NEXT_PUBLIC_SITE_ABOUT} data-public-site-about={process.env.NEXT_PUBLIC_SITE_ABOUT}
data-public-text-generation-timeout-ms={process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS} data-public-text-generation-timeout-ms={process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS}
data-public-max-tools-num={process.env.NEXT_PUBLIC_MAX_TOOLS_NUM}
data-public-top-k-max-value={process.env.NEXT_PUBLIC_TOP_K_MAX_VALUE} data-public-top-k-max-value={process.env.NEXT_PUBLIC_TOP_K_MAX_VALUE}
data-public-indexing-max-segmentation-tokens-length={process.env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH} data-public-indexing-max-segmentation-tokens-length={process.env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}
data-public-loop-node-max-count={process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT} data-public-loop-node-max-count={process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT}

View File

@ -162,7 +162,14 @@ export const ANNOTATION_DEFAULT = {
score_threshold: 0.9, score_threshold: 0.9,
} }
export const MAX_TOOLS_NUM = 10 export let maxToolsNum = 10
if (process.env.NEXT_PUBLIC_MAX_TOOLS_NUM && process.env.NEXT_PUBLIC_MAX_TOOLS_NUM !== '')
maxToolsNum = Number.parseInt(process.env.NEXT_PUBLIC_MAX_TOOLS_NUM)
else if (globalThis.document?.body?.getAttribute('data-public-max-tools-num') && globalThis.document.body.getAttribute('data-public-max-tools-num') !== '')
maxToolsNum = Number.parseInt(globalThis.document.body.getAttribute('data-public-max-tools-num') as string)
export const MAX_TOOLS_NUM = maxToolsNum
export const DEFAULT_AGENT_SETTING = { export const DEFAULT_AGENT_SETTING = {
enabled: false, enabled: false,

View File

@ -49,7 +49,6 @@ const initialWorkspaceInfo: ICurrentWorkspace = {
created_at: 0, created_at: 0,
role: 'normal', role: 'normal',
providers: [], providers: [],
in_trail: true,
} }
const AppContext = createContext<AppContextValue>({ const AppContext = createContext<AppContextValue>({

View File

@ -703,6 +703,7 @@ const translation = {
loop_other: '{{count}} Loops', loop_other: '{{count}} Loops',
currentLoop: 'Current Loop', currentLoop: 'Current Loop',
breakCondition: 'Loop Termination Condition', breakCondition: 'Loop Termination Condition',
breakConditionTip: 'Only variables within loops with termination conditions and conversation variables can be referenced.',
loopMaxCount: 'Maximum Loop Count', loopMaxCount: 'Maximum Loop Count',
loopMaxCountError: 'Please enter a valid maximum loop count, ranging from 1 to {{maxCount}}', loopMaxCountError: 'Please enter a valid maximum loop count, ranging from 1 to {{maxCount}}',
errorResponseMethod: 'Error Response Method', errorResponseMethod: 'Error Response Method',

View File

@ -704,6 +704,7 @@ const translation = {
loop_other: '{{count}} 个循环', loop_other: '{{count}} 个循环',
currentLoop: '当前循环', currentLoop: '当前循环',
breakCondition: '循环终止条件', breakCondition: '循环终止条件',
breakConditionTip: '支持引用终止条件循环内的变量和会话变量。',
loopMaxCount: '最大循环次数', loopMaxCount: '最大循环次数',
loopMaxCountError: '请输入正确的 最大循环次数,范围为 1 到 {{maxCount}}', loopMaxCountError: '请输入正确的 最大循环次数,范围为 1 到 {{maxCount}}',
errorResponseMethod: '错误响应方法', errorResponseMethod: '错误响应方法',

View File

@ -1,23 +1,23 @@
import type { I18nText } from '@/i18n/language' import type { I18nText } from '@/i18n/language'
export interface CommonResponse { export type CommonResponse = {
result: 'success' | 'fail' result: 'success' | 'fail'
} }
export interface OauthResponse { export type OauthResponse = {
redirect_url: string redirect_url: string
} }
export interface SetupStatusResponse { export type SetupStatusResponse = {
step: 'finished' | 'not_started' step: 'finished' | 'not_started'
setup_at?: Date setup_at?: Date
} }
export interface InitValidateStatusResponse { export type InitValidateStatusResponse = {
status: 'finished' | 'not_started' status: 'finished' | 'not_started'
} }
export interface UserProfileResponse { export type UserProfileResponse = {
id: string id: string
name: string name: string
email: string email: string
@ -33,13 +33,13 @@ export interface UserProfileResponse {
created_at?: string created_at?: string
} }
export interface UserProfileOriginResponse { export type UserProfileOriginResponse = {
json: () => Promise<UserProfileResponse> json: () => Promise<UserProfileResponse>
bodyUsed: boolean bodyUsed: boolean
headers: any headers: any
} }
export interface LangGeniusVersionResponse { export type LangGeniusVersionResponse = {
current_version: string current_version: string
latest_version: string latest_version: string
version: string version: string
@ -49,7 +49,7 @@ export interface LangGeniusVersionResponse {
current_env: string current_env: string
} }
export interface TenantInfoResponse { export type TenantInfoResponse = {
name: string name: string
created_at: string created_at: string
providers: Array<{ providers: Array<{
@ -80,14 +80,14 @@ export enum ProviderName {
Tongyi = 'tongyi', Tongyi = 'tongyi',
ChatGLM = 'chatglm', ChatGLM = 'chatglm',
} }
export interface ProviderAzureToken { export type ProviderAzureToken = {
openai_api_base?: string openai_api_base?: string
openai_api_key?: string openai_api_key?: string
} }
export interface ProviderAnthropicToken { export type ProviderAnthropicToken = {
anthropic_api_key?: string anthropic_api_key?: string
} }
export interface ProviderTokenType { export type ProviderTokenType = {
[ProviderName.OPENAI]: string [ProviderName.OPENAI]: string
[ProviderName.AZURE_OPENAI]: ProviderAzureToken [ProviderName.AZURE_OPENAI]: ProviderAzureToken
[ProviderName.ANTHROPIC]: ProviderAnthropicToken [ProviderName.ANTHROPIC]: ProviderAnthropicToken
@ -110,14 +110,14 @@ export type ProviderHosted = Provider & {
quota_used: number quota_used: number
} }
export interface AccountIntegrate { export type AccountIntegrate = {
provider: 'google' | 'github' provider: 'google' | 'github'
created_at: number created_at: number
is_bound: boolean is_bound: boolean
link: string link: string
} }
export interface IWorkspace { export type IWorkspace = {
id: string id: string
name: string name: string
plan: string plan: string
@ -129,7 +129,6 @@ export interface IWorkspace {
export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & { export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & {
role: 'owner' | 'admin' | 'editor' | 'dataset_operator' | 'normal' role: 'owner' | 'admin' | 'editor' | 'dataset_operator' | 'normal'
providers: Provider[] providers: Provider[]
in_trail: boolean
trial_end_reason?: string trial_end_reason?: string
custom_config?: { custom_config?: {
remove_webapp_brand?: boolean remove_webapp_brand?: boolean
@ -137,7 +136,7 @@ export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & {
} }
} }
export interface DataSourceNotionPage { export type DataSourceNotionPage = {
page_icon: null | { page_icon: null | {
type: string | null type: string | null
url: string | null url: string | null
@ -156,7 +155,7 @@ export type NotionPage = DataSourceNotionPage & {
export type DataSourceNotionPageMap = Record<string, DataSourceNotionPage & { workspace_id: string }> export type DataSourceNotionPageMap = Record<string, DataSourceNotionPage & { workspace_id: string }>
export interface DataSourceNotionWorkspace { export type DataSourceNotionWorkspace = {
workspace_name: string workspace_name: string
workspace_id: string workspace_id: string
workspace_icon: string | null workspace_icon: string | null
@ -166,7 +165,7 @@ export interface DataSourceNotionWorkspace {
export type DataSourceNotionWorkspaceMap = Record<string, DataSourceNotionWorkspace> export type DataSourceNotionWorkspaceMap = Record<string, DataSourceNotionWorkspace>
export interface DataSourceNotion { export type DataSourceNotion = {
id: string id: string
provider: string provider: string
is_bound: boolean is_bound: boolean
@ -181,12 +180,12 @@ export enum DataSourceProvider {
jinaReader = 'jinareader', jinaReader = 'jinareader',
} }
export interface FirecrawlConfig { export type FirecrawlConfig = {
api_key: string api_key: string
base_url: string base_url: string
} }
export interface DataSourceItem { export type DataSourceItem = {
id: string id: string
category: DataSourceCategory category: DataSourceCategory
provider: DataSourceProvider provider: DataSourceProvider
@ -195,15 +194,15 @@ export interface DataSourceItem {
updated_at: number updated_at: number
} }
export interface DataSources { export type DataSources = {
sources: DataSourceItem[] sources: DataSourceItem[]
} }
export interface GithubRepo { export type GithubRepo = {
stargazers_count: number stargazers_count: number
} }
export interface PluginProvider { export type PluginProvider = {
tool_name: string tool_name: string
is_enabled: boolean is_enabled: boolean
credentials: { credentials: {
@ -211,7 +210,7 @@ export interface PluginProvider {
} | null } | null
} }
export interface FileUploadConfigResponse { export type FileUploadConfigResponse = {
batch_count_limit: number batch_count_limit: number
image_file_size_limit?: number | string // default is 10MB image_file_size_limit?: number | string // default is 10MB
file_size_limit: number // default is 15MB file_size_limit: number // default is 15MB
@ -234,14 +233,14 @@ export type InvitationResponse = CommonResponse & {
invitation_results: InvitationResult[] invitation_results: InvitationResult[]
} }
export interface ApiBasedExtension { export type ApiBasedExtension = {
id?: string id?: string
name?: string name?: string
api_endpoint?: string api_endpoint?: string
api_key?: string api_key?: string
} }
export interface CodeBasedExtensionForm { export type CodeBasedExtensionForm = {
type: string type: string
label: I18nText label: I18nText
variable: string variable: string
@ -252,17 +251,17 @@ export interface CodeBasedExtensionForm {
max_length?: number max_length?: number
} }
export interface CodeBasedExtensionItem { export type CodeBasedExtensionItem = {
name: string name: string
label: any label: any
form_schema: CodeBasedExtensionForm[] form_schema: CodeBasedExtensionForm[]
} }
export interface CodeBasedExtension { export type CodeBasedExtension = {
module: string module: string
data: CodeBasedExtensionItem[] data: CodeBasedExtensionItem[]
} }
export interface ExternalDataTool { export type ExternalDataTool = {
type?: string type?: string
label?: string label?: string
icon?: string icon?: string
@ -274,7 +273,7 @@ export interface ExternalDataTool {
} & Partial<Record<string, any>> } & Partial<Record<string, any>>
} }
export interface ModerateResponse { export type ModerateResponse = {
flagged: boolean flagged: boolean
text: string text: string
} }