Merge branch 'main' into feat/plugin-readme

This commit is contained in:
Stream
2025-08-27 11:35:04 +08:00
308 changed files with 6229 additions and 2729 deletions

View File

@ -425,7 +425,7 @@ class AccountService:
cls,
account: Optional[Account] = None,
email: Optional[str] = None,
language: Optional[str] = "en-US",
language: str = "en-US",
):
account_email = account.email if account else email
if account_email is None:
@ -452,12 +452,14 @@ class AccountService:
account: Optional[Account] = None,
email: Optional[str] = None,
old_email: Optional[str] = None,
language: Optional[str] = "en-US",
language: str = "en-US",
phase: Optional[str] = None,
):
account_email = account.email if account else email
if account_email is None:
raise ValueError("Email must be provided.")
if not phase:
raise ValueError("phase must be provided.")
if cls.change_email_rate_limiter.is_rate_limited(account_email):
from controllers.console.auth.error import EmailChangeRateLimitExceededError
@ -480,7 +482,7 @@ class AccountService:
cls,
account: Optional[Account] = None,
email: Optional[str] = None,
language: Optional[str] = "en-US",
language: str = "en-US",
):
account_email = account.email if account else email
if account_email is None:
@ -496,7 +498,7 @@ class AccountService:
cls,
account: Optional[Account] = None,
email: Optional[str] = None,
language: Optional[str] = "en-US",
language: str = "en-US",
workspace_name: Optional[str] = "",
):
account_email = account.email if account else email
@ -509,6 +511,7 @@ class AccountService:
raise OwnerTransferRateLimitExceededError()
code, token = cls.generate_owner_transfer_token(account_email, account)
workspace_name = workspace_name or ""
send_owner_transfer_confirm_task.delay(
language=language,
@ -524,13 +527,14 @@ class AccountService:
cls,
account: Optional[Account] = None,
email: Optional[str] = None,
language: Optional[str] = "en-US",
language: str = "en-US",
workspace_name: Optional[str] = "",
new_owner_email: Optional[str] = "",
new_owner_email: str = "",
):
account_email = account.email if account else email
if account_email is None:
raise ValueError("Email must be provided.")
workspace_name = workspace_name or ""
send_old_owner_transfer_notify_email_task.delay(
language=language,
@ -544,12 +548,13 @@ class AccountService:
cls,
account: Optional[Account] = None,
email: Optional[str] = None,
language: Optional[str] = "en-US",
language: str = "en-US",
workspace_name: Optional[str] = "",
):
account_email = account.email if account else email
if account_email is None:
raise ValueError("Email must be provided.")
workspace_name = workspace_name or ""
send_new_owner_transfer_notify_email_task.delay(
language=language,
@ -633,7 +638,10 @@ class AccountService:
@classmethod
def send_email_code_login_email(
cls, account: Optional[Account] = None, email: Optional[str] = None, language: Optional[str] = "en-US"
cls,
account: Optional[Account] = None,
email: Optional[str] = None,
language: str = "en-US",
):
email = account.email if account else email
if email is None:
@ -1260,10 +1268,11 @@ class RegisterService:
raise AccountAlreadyInTenantError("Account already in tenant.")
token = cls.generate_invite_token(tenant, account)
language = account.interface_language or "en-US"
# send email
send_invite_member_mail_task.delay(
language=account.interface_language,
language=language,
to=email,
token=token,
inviter_name=inviter.name if inviter else "Dify",

View File

@ -1,4 +1,3 @@
import datetime
import uuid
from typing import cast
@ -10,6 +9,7 @@ from werkzeug.exceptions import NotFound
from extensions.ext_database import db
from extensions.ext_redis import redis_client
from libs.datetime_utils import naive_utc_now
from models.model import App, AppAnnotationHitHistory, AppAnnotationSetting, Message, MessageAnnotation
from services.feature_service import FeatureService
from tasks.annotation.add_annotation_to_index_task import add_annotation_to_index_task
@ -473,7 +473,7 @@ class AppAnnotationService:
raise NotFound("App annotation not found")
annotation_setting.score_threshold = args["score_threshold"]
annotation_setting.updated_user_id = current_user.id
annotation_setting.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
annotation_setting.updated_at = naive_utc_now()
db.session.add(annotation_setting)
db.session.commit()

View File

@ -1,4 +1,5 @@
import contextlib
import logging
from collections.abc import Callable, Sequence
from typing import Any, Optional, Union
@ -23,6 +24,9 @@ from services.errors.conversation import (
LastConversationNotExistsError,
)
from services.errors.message import MessageNotExistsError
from tasks.delete_conversation_task import delete_conversation_related_data
logger = logging.getLogger(__name__)
class ConversationService:
@ -175,11 +179,21 @@ class ConversationService:
@classmethod
def delete(cls, app_model: App, conversation_id: str, user: Optional[Union[Account, EndUser]]):
conversation = cls.get_conversation(app_model, conversation_id, user)
try:
logger.info(
"Initiating conversation deletion for app_name %s, conversation_id: %s",
app_model.name,
conversation_id,
)
conversation.is_deleted = True
conversation.updated_at = naive_utc_now()
db.session.commit()
db.session.query(Conversation).where(Conversation.id == conversation_id).delete(synchronize_session=False)
db.session.commit()
delete_conversation_related_data.delay(conversation_id)
except Exception as e:
db.session.rollback()
raise e
@classmethod
def get_conversational_variable(

View File

@ -1234,7 +1234,7 @@ class DocumentService:
)
if document:
document.dataset_process_rule_id = dataset_process_rule.id # type: ignore
document.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
document.updated_at = naive_utc_now()
document.created_from = created_from
document.doc_form = knowledge_config.doc_form
document.doc_language = knowledge_config.doc_language
@ -1552,7 +1552,7 @@ class DocumentService:
document.parsing_completed_at = None
document.cleaning_completed_at = None
document.splitting_completed_at = None
document.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
document.updated_at = naive_utc_now()
document.created_from = created_from
document.doc_form = document_data.doc_form
db.session.add(document)
@ -1912,7 +1912,7 @@ class DocumentService:
Returns:
dict: Update information or None if no update needed
"""
now = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
now = naive_utc_now()
if action == "enable":
return DocumentService._prepare_enable_update(document, now)
@ -2040,8 +2040,8 @@ class SegmentService:
word_count=len(content),
tokens=tokens,
status="completed",
indexing_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None),
completed_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None),
indexing_at=naive_utc_now(),
completed_at=naive_utc_now(),
created_by=current_user.id,
)
if document.doc_form == "qa_model":
@ -2061,7 +2061,7 @@ class SegmentService:
except Exception as e:
logging.exception("create segment index failed")
segment_document.enabled = False
segment_document.disabled_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
segment_document.disabled_at = naive_utc_now()
segment_document.status = "error"
segment_document.error = str(e)
db.session.commit()
@ -2117,8 +2117,8 @@ class SegmentService:
tokens=tokens,
keywords=segment_item.get("keywords", []),
status="completed",
indexing_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None),
completed_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None),
indexing_at=naive_utc_now(),
completed_at=naive_utc_now(),
created_by=current_user.id,
)
if document.doc_form == "qa_model":
@ -2145,7 +2145,7 @@ class SegmentService:
logging.exception("create segment index failed")
for segment_document in segment_data_list:
segment_document.enabled = False
segment_document.disabled_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
segment_document.disabled_at = naive_utc_now()
segment_document.status = "error"
segment_document.error = str(e)
db.session.commit()
@ -2162,7 +2162,7 @@ class SegmentService:
if segment.enabled != action:
if not action:
segment.enabled = action
segment.disabled_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
segment.disabled_at = naive_utc_now()
segment.disabled_by = current_user.id
db.session.add(segment)
db.session.commit()
@ -2260,10 +2260,10 @@ class SegmentService:
segment.word_count = len(content)
segment.tokens = tokens
segment.status = "completed"
segment.indexing_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
segment.completed_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
segment.indexing_at = naive_utc_now()
segment.completed_at = naive_utc_now()
segment.updated_by = current_user.id
segment.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
segment.updated_at = naive_utc_now()
segment.enabled = True
segment.disabled_at = None
segment.disabled_by = None
@ -2316,7 +2316,7 @@ class SegmentService:
except Exception as e:
logging.exception("update segment index failed")
segment.enabled = False
segment.disabled_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
segment.disabled_at = naive_utc_now()
segment.status = "error"
segment.error = str(e)
db.session.commit()
@ -2344,13 +2344,9 @@ class SegmentService:
@classmethod
def delete_segments(cls, segment_ids: list, document: Document, dataset: Dataset):
# Check if segment_ids is not empty to avoid WHERE false condition
if not segment_ids or len(segment_ids) == 0:
return
index_node_ids = (
db.session.query(DocumentSegment)
.with_entities(DocumentSegment.index_node_id)
.where(
segments = (
db.session.query(DocumentSegment.index_node_id, DocumentSegment.word_count)
.filter(
DocumentSegment.id.in_(segment_ids),
DocumentSegment.dataset_id == dataset.id,
DocumentSegment.document_id == document.id,
@ -2358,7 +2354,15 @@ class SegmentService:
)
.all()
)
index_node_ids = [index_node_id[0] for index_node_id in index_node_ids]
if not segments:
return
index_node_ids = [seg.index_node_id for seg in segments]
total_words = sum(seg.word_count for seg in segments)
document.word_count -= total_words
db.session.add(document)
delete_segment_from_index_task.delay(index_node_ids, dataset.id, document.id)
db.session.query(DocumentSegment).where(DocumentSegment.id.in_(segment_ids)).delete()
@ -2418,7 +2422,7 @@ class SegmentService:
if cache_result is not None:
continue
segment.enabled = False
segment.disabled_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
segment.disabled_at = naive_utc_now()
segment.disabled_by = current_user.id
db.session.add(segment)
real_deal_segment_ids.append(segment.id)
@ -2508,7 +2512,7 @@ class SegmentService:
child_chunk.content = child_chunk_update_args.content
child_chunk.word_count = len(child_chunk.content)
child_chunk.updated_by = current_user.id
child_chunk.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
child_chunk.updated_at = naive_utc_now()
child_chunk.type = "customized"
update_child_chunks.append(child_chunk)
else:
@ -2565,7 +2569,7 @@ class SegmentService:
child_chunk.content = content
child_chunk.word_count = len(content)
child_chunk.updated_by = current_user.id
child_chunk.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
child_chunk.updated_at = naive_utc_now()
child_chunk.type = "customized"
db.session.add(child_chunk)
VectorService.update_child_chunk_vector([], [child_chunk], [], dataset)

View File

@ -1,4 +1,3 @@
import datetime
import hashlib
import os
import uuid
@ -18,6 +17,7 @@ from core.file import helpers as file_helpers
from core.rag.extractor.extract_processor import ExtractProcessor
from extensions.ext_database import db
from extensions.ext_storage import storage
from libs.datetime_utils import naive_utc_now
from libs.helper import extract_tenant_id
from models.account import Account
from models.enums import CreatorUserRole
@ -80,7 +80,7 @@ class FileService:
mime_type=mimetype,
created_by_role=(CreatorUserRole.ACCOUNT if isinstance(user, Account) else CreatorUserRole.END_USER),
created_by=user.id,
created_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None),
created_at=naive_utc_now(),
used=False,
hash=hashlib.sha3_256(content).hexdigest(),
source_url=source_url,
@ -131,10 +131,10 @@ class FileService:
mime_type="text/plain",
created_by=current_user.id,
created_by_role=CreatorUserRole.ACCOUNT,
created_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None),
created_at=naive_utc_now(),
used=True,
used_by=current_user.id,
used_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None),
used_at=naive_utc_now(),
)
db.session.add(upload_file)

View File

@ -1,5 +1,4 @@
import copy
import datetime
import logging
from typing import Optional
@ -8,6 +7,7 @@ from flask_login import current_user
from core.rag.index_processor.constant.built_in_field import BuiltInField, MetadataDataSource
from extensions.ext_database import db
from extensions.ext_redis import redis_client
from libs.datetime_utils import naive_utc_now
from models.dataset import Dataset, DatasetMetadata, DatasetMetadataBinding
from services.dataset_service import DocumentService
from services.entities.knowledge_entities.knowledge_entities import (
@ -69,7 +69,7 @@ class MetadataService:
old_name = metadata.name
metadata.name = name
metadata.updated_by = current_user.id
metadata.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
metadata.updated_at = naive_utc_now()
# update related documents
dataset_metadata_bindings = (

View File

@ -1,4 +1,3 @@
import datetime
import json
import logging
from json import JSONDecodeError
@ -17,6 +16,7 @@ from core.model_runtime.entities.provider_entities import (
from core.model_runtime.model_providers.model_provider_factory import ModelProviderFactory
from core.provider_manager import ProviderManager
from extensions.ext_database import db
from libs.datetime_utils import naive_utc_now
from models.provider import LoadBalancingModelConfig
logger = logging.getLogger(__name__)
@ -371,7 +371,7 @@ class ModelLoadBalancingService:
load_balancing_config.name = name
load_balancing_config.enabled = enabled
load_balancing_config.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
load_balancing_config.updated_at = naive_utc_now()
db.session.commit()
self._clear_credentials_cache(tenant_id, config_id)

View File

@ -63,7 +63,7 @@ class WebAppAuthService:
@classmethod
def send_email_code_login_email(
cls, account: Optional[Account] = None, email: Optional[str] = None, language: Optional[str] = "en-US"
cls, account: Optional[Account] = None, email: Optional[str] = None, language: str = "en-US"
):
email = account.email if account else email
if email is None:

View File

@ -1,5 +1,4 @@
import dataclasses
import datetime
import logging
from collections.abc import Mapping, Sequence
from enum import StrEnum
@ -23,6 +22,7 @@ from core.workflow.nodes.variable_assigner.common.helpers import get_updated_var
from core.workflow.variable_loader import VariableLoader
from factories.file_factory import StorageKeyLoader
from factories.variable_factory import build_segment, segment_to_variable
from libs.datetime_utils import naive_utc_now
from models import App, Conversation
from models.enums import DraftVariableType
from models.workflow import Workflow, WorkflowDraftVariable, is_system_variable_editable
@ -231,7 +231,7 @@ class WorkflowDraftVariableService:
variable.set_name(name)
if value is not None:
variable.set_value(value)
variable.last_edited_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
variable.last_edited_at = naive_utc_now()
self._session.flush()
return variable