mirror of
https://github.com/langgenius/dify.git
synced 2026-02-15 10:00:13 -05:00
Compare commits
9 Commits
refactor/t
...
deploy/dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a7edd2ae0 | ||
|
|
b3faa34a03 | ||
|
|
5c05f09e23 | ||
|
|
ff8d7855b1 | ||
|
|
02206cc64b | ||
|
|
cba157038f | ||
|
|
d606de26f1 | ||
|
|
49f46a05e7 | ||
|
|
30b73f2765 |
@@ -136,7 +136,6 @@ ignore_imports =
|
|||||||
core.workflow.nodes.llm.llm_utils -> models.provider
|
core.workflow.nodes.llm.llm_utils -> models.provider
|
||||||
core.workflow.nodes.llm.llm_utils -> services.credit_pool_service
|
core.workflow.nodes.llm.llm_utils -> services.credit_pool_service
|
||||||
core.workflow.nodes.llm.node -> core.tools.signature
|
core.workflow.nodes.llm.node -> core.tools.signature
|
||||||
core.workflow.nodes.template_transform.template_transform_node -> configs
|
|
||||||
core.workflow.nodes.tool.tool_node -> core.callback_handler.workflow_tool_callback_handler
|
core.workflow.nodes.tool.tool_node -> core.callback_handler.workflow_tool_callback_handler
|
||||||
core.workflow.nodes.tool.tool_node -> core.tools.tool_engine
|
core.workflow.nodes.tool.tool_node -> core.tools.tool_engine
|
||||||
core.workflow.nodes.tool.tool_node -> core.tools.tool_manager
|
core.workflow.nodes.tool.tool_node -> core.tools.tool_manager
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from . import (
|
|||||||
extension,
|
extension,
|
||||||
feature,
|
feature,
|
||||||
init_validate,
|
init_validate,
|
||||||
|
notification,
|
||||||
ping,
|
ping,
|
||||||
setup,
|
setup,
|
||||||
spec,
|
spec,
|
||||||
@@ -182,6 +183,7 @@ __all__ = [
|
|||||||
"model_config",
|
"model_config",
|
||||||
"model_providers",
|
"model_providers",
|
||||||
"models",
|
"models",
|
||||||
|
"notification",
|
||||||
"oauth",
|
"oauth",
|
||||||
"oauth_server",
|
"oauth_server",
|
||||||
"ops_trace",
|
"ops_trace",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import csv
|
||||||
|
import io
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import ParamSpec, TypeVar
|
from typing import ParamSpec, TypeVar
|
||||||
@@ -6,7 +8,7 @@ from flask import request
|
|||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from werkzeug.exceptions import NotFound, Unauthorized
|
from werkzeug.exceptions import BadRequest, NotFound, Unauthorized
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from constants.languages import supported_language
|
from constants.languages import supported_language
|
||||||
@@ -16,6 +18,7 @@ from core.db.session_factory import session_factory
|
|||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.token import extract_access_token
|
from libs.token import extract_access_token
|
||||||
from models.model import App, ExporleBanner, InstalledApp, RecommendedApp, TrialApp
|
from models.model import App, ExporleBanner, InstalledApp, RecommendedApp, TrialApp
|
||||||
|
from services.billing_service import BillingService
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
R = TypeVar("R")
|
R = TypeVar("R")
|
||||||
@@ -277,3 +280,113 @@ class DeleteExploreBannerApi(Resource):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"result": "success"}, 204
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
|
class SaveNotificationContentPayload(BaseModel):
|
||||||
|
content: str = Field(...)
|
||||||
|
|
||||||
|
|
||||||
|
class SaveNotificationUserPayload(BaseModel):
|
||||||
|
user_email: list[str] = Field(...)
|
||||||
|
|
||||||
|
|
||||||
|
console_ns.schema_model(
|
||||||
|
SaveNotificationContentPayload.__name__,
|
||||||
|
SaveNotificationContentPayload.model_json_schema(ref_template=DEFAULT_REF_TEMPLATE_SWAGGER_2_0),
|
||||||
|
)
|
||||||
|
|
||||||
|
console_ns.schema_model(
|
||||||
|
SaveNotificationUserPayload.__name__,
|
||||||
|
SaveNotificationUserPayload.model_json_schema(ref_template=DEFAULT_REF_TEMPLATE_SWAGGER_2_0),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/admin/save_notification_content")
|
||||||
|
class SaveNotificationContentApi(Resource):
|
||||||
|
@console_ns.doc("save_notification_content")
|
||||||
|
@console_ns.doc(description="Save a notification content")
|
||||||
|
@console_ns.expect(console_ns.models[SaveNotificationContentPayload.__name__])
|
||||||
|
@console_ns.response(200, "Notification content saved successfully")
|
||||||
|
@only_edition_cloud
|
||||||
|
@admin_required
|
||||||
|
def post(self):
|
||||||
|
payload = SaveNotificationContentPayload.model_validate(console_ns.payload)
|
||||||
|
BillingService.save_notification_content(payload.content)
|
||||||
|
return {"result": "success"}, 200
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/admin/save_notification_user")
|
||||||
|
class SaveNotificationUserApi(Resource):
|
||||||
|
@console_ns.doc("save_notification_user")
|
||||||
|
@console_ns.doc(description="Save notification users via JSON body or file upload. "
|
||||||
|
"JSON: {\"user_email\": [\"a@example.com\", ...]}. "
|
||||||
|
"File: multipart/form-data with a 'file' field (CSV or TXT, one email per line).")
|
||||||
|
@console_ns.response(200, "Notification users saved successfully")
|
||||||
|
@only_edition_cloud
|
||||||
|
@admin_required
|
||||||
|
def post(self):
|
||||||
|
# Determine input mode: file upload or JSON body
|
||||||
|
if "file" in request.files:
|
||||||
|
emails = self._parse_emails_from_file()
|
||||||
|
else:
|
||||||
|
payload = SaveNotificationUserPayload.model_validate(console_ns.payload)
|
||||||
|
emails = payload.user_email
|
||||||
|
|
||||||
|
if not emails:
|
||||||
|
raise BadRequest("No valid email addresses provided.")
|
||||||
|
|
||||||
|
# Use batch API for bulk insert (chunks of 1000 per request to billing service)
|
||||||
|
result = BillingService.save_notification_users_batch(emails)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"result": "success",
|
||||||
|
"total": len(emails),
|
||||||
|
"succeeded": result["succeeded"],
|
||||||
|
"failed_chunks": result["failed_chunks"],
|
||||||
|
}, 200
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_emails_from_file() -> list[str]:
|
||||||
|
"""Parse email addresses from an uploaded CSV or TXT file."""
|
||||||
|
file = request.files["file"]
|
||||||
|
|
||||||
|
if not file.filename:
|
||||||
|
raise BadRequest("Uploaded file has no filename.")
|
||||||
|
|
||||||
|
filename_lower = file.filename.lower()
|
||||||
|
if not filename_lower.endswith((".csv", ".txt")):
|
||||||
|
raise BadRequest("Invalid file type. Only CSV (.csv) and TXT (.txt) files are allowed.")
|
||||||
|
|
||||||
|
# Read file content
|
||||||
|
try:
|
||||||
|
content = file.read().decode("utf-8")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
try:
|
||||||
|
file.seek(0)
|
||||||
|
content = file.read().decode("gbk")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
raise BadRequest("Unable to decode the file. Please use UTF-8 or GBK encoding.")
|
||||||
|
|
||||||
|
emails: list[str] = []
|
||||||
|
if filename_lower.endswith(".csv"):
|
||||||
|
reader = csv.reader(io.StringIO(content))
|
||||||
|
for row in reader:
|
||||||
|
for cell in row:
|
||||||
|
cell = cell.strip()
|
||||||
|
emails.append(cell)
|
||||||
|
else:
|
||||||
|
# TXT file: one email per line
|
||||||
|
for line in content.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
emails.append(line)
|
||||||
|
|
||||||
|
# Deduplicate while preserving order
|
||||||
|
seen: set[str] = set()
|
||||||
|
unique_emails: list[str] = []
|
||||||
|
for email in emails:
|
||||||
|
email_lower = email.lower()
|
||||||
|
if email_lower not in seen:
|
||||||
|
seen.add(email_lower)
|
||||||
|
unique_emails.append(email)
|
||||||
|
|
||||||
|
return unique_emails
|
||||||
26
api/controllers/console/notification.py
Normal file
26
api/controllers/console/notification.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from flask_restx import Resource
|
||||||
|
|
||||||
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.wraps import account_initialization_required, only_edition_cloud, setup_required
|
||||||
|
from libs.login import current_account_with_tenant, login_required
|
||||||
|
from services.billing_service import BillingService
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/notification")
|
||||||
|
class NotificationApi(Resource):
|
||||||
|
@console_ns.doc("get_notification")
|
||||||
|
@console_ns.doc(description="Get notification for the current user")
|
||||||
|
@console_ns.doc(
|
||||||
|
responses={
|
||||||
|
200: "Success",
|
||||||
|
401: "Unauthorized",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@only_edition_cloud
|
||||||
|
def get(self):
|
||||||
|
current_user, _ = current_account_with_tenant()
|
||||||
|
notification = BillingService.read_notification(current_user.email)
|
||||||
|
return notification
|
||||||
@@ -47,6 +47,7 @@ class DifyNodeFactory(NodeFactory):
|
|||||||
code_providers: Sequence[type[CodeNodeProvider]] | None = None,
|
code_providers: Sequence[type[CodeNodeProvider]] | None = None,
|
||||||
code_limits: CodeNodeLimits | None = None,
|
code_limits: CodeNodeLimits | None = None,
|
||||||
template_renderer: Jinja2TemplateRenderer | None = None,
|
template_renderer: Jinja2TemplateRenderer | None = None,
|
||||||
|
template_transform_max_output_length: int | None = None,
|
||||||
http_request_http_client: HttpClientProtocol | None = None,
|
http_request_http_client: HttpClientProtocol | None = None,
|
||||||
http_request_tool_file_manager_factory: Callable[[], ToolFileManager] = ToolFileManager,
|
http_request_tool_file_manager_factory: Callable[[], ToolFileManager] = ToolFileManager,
|
||||||
http_request_file_manager: FileManagerProtocol | None = None,
|
http_request_file_manager: FileManagerProtocol | None = None,
|
||||||
@@ -68,6 +69,9 @@ class DifyNodeFactory(NodeFactory):
|
|||||||
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
|
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
|
||||||
)
|
)
|
||||||
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer()
|
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer()
|
||||||
|
self._template_transform_max_output_length = (
|
||||||
|
template_transform_max_output_length or dify_config.TEMPLATE_TRANSFORM_MAX_LENGTH
|
||||||
|
)
|
||||||
self._http_request_http_client = http_request_http_client or ssrf_proxy
|
self._http_request_http_client = http_request_http_client or ssrf_proxy
|
||||||
self._http_request_tool_file_manager_factory = http_request_tool_file_manager_factory
|
self._http_request_tool_file_manager_factory = http_request_tool_file_manager_factory
|
||||||
self._http_request_file_manager = http_request_file_manager or file_manager
|
self._http_request_file_manager = http_request_file_manager or file_manager
|
||||||
@@ -122,6 +126,7 @@ class DifyNodeFactory(NodeFactory):
|
|||||||
graph_init_params=self.graph_init_params,
|
graph_init_params=self.graph_init_params,
|
||||||
graph_runtime_state=self.graph_runtime_state,
|
graph_runtime_state=self.graph_runtime_state,
|
||||||
template_renderer=self._template_renderer,
|
template_renderer=self._template_renderer,
|
||||||
|
max_output_length=self._template_transform_max_output_length,
|
||||||
)
|
)
|
||||||
|
|
||||||
if node_type == NodeType.HTTP_REQUEST:
|
if node_type == NodeType.HTTP_REQUEST:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from configs import dify_config
|
|
||||||
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
|
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
|
||||||
from core.workflow.node_events import NodeRunResult
|
from core.workflow.node_events import NodeRunResult
|
||||||
from core.workflow.nodes.base.node import Node
|
from core.workflow.nodes.base.node import Node
|
||||||
@@ -16,12 +15,13 @@ if TYPE_CHECKING:
|
|||||||
from core.workflow.entities import GraphInitParams
|
from core.workflow.entities import GraphInitParams
|
||||||
from core.workflow.runtime import GraphRuntimeState
|
from core.workflow.runtime import GraphRuntimeState
|
||||||
|
|
||||||
MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH = dify_config.TEMPLATE_TRANSFORM_MAX_LENGTH
|
DEFAULT_TEMPLATE_TRANSFORM_MAX_OUTPUT_LENGTH = 400_000
|
||||||
|
|
||||||
|
|
||||||
class TemplateTransformNode(Node[TemplateTransformNodeData]):
|
class TemplateTransformNode(Node[TemplateTransformNodeData]):
|
||||||
node_type = NodeType.TEMPLATE_TRANSFORM
|
node_type = NodeType.TEMPLATE_TRANSFORM
|
||||||
_template_renderer: Jinja2TemplateRenderer
|
_template_renderer: Jinja2TemplateRenderer
|
||||||
|
_max_output_length: int
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -31,6 +31,7 @@ class TemplateTransformNode(Node[TemplateTransformNodeData]):
|
|||||||
graph_runtime_state: "GraphRuntimeState",
|
graph_runtime_state: "GraphRuntimeState",
|
||||||
*,
|
*,
|
||||||
template_renderer: Jinja2TemplateRenderer | None = None,
|
template_renderer: Jinja2TemplateRenderer | None = None,
|
||||||
|
max_output_length: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
id=id,
|
id=id,
|
||||||
@@ -40,6 +41,10 @@ class TemplateTransformNode(Node[TemplateTransformNodeData]):
|
|||||||
)
|
)
|
||||||
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer()
|
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer()
|
||||||
|
|
||||||
|
if max_output_length is not None and max_output_length <= 0:
|
||||||
|
raise ValueError("max_output_length must be a positive integer")
|
||||||
|
self._max_output_length = max_output_length or DEFAULT_TEMPLATE_TRANSFORM_MAX_OUTPUT_LENGTH
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_config(cls, filters: Mapping[str, object] | None = None) -> Mapping[str, object]:
|
def get_default_config(cls, filters: Mapping[str, object] | None = None) -> Mapping[str, object]:
|
||||||
"""
|
"""
|
||||||
@@ -69,11 +74,11 @@ class TemplateTransformNode(Node[TemplateTransformNodeData]):
|
|||||||
except TemplateRenderError as e:
|
except TemplateRenderError as e:
|
||||||
return NodeRunResult(inputs=variables, status=WorkflowNodeExecutionStatus.FAILED, error=str(e))
|
return NodeRunResult(inputs=variables, status=WorkflowNodeExecutionStatus.FAILED, error=str(e))
|
||||||
|
|
||||||
if len(rendered) > MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH:
|
if len(rendered) > self._max_output_length:
|
||||||
return NodeRunResult(
|
return NodeRunResult(
|
||||||
inputs=variables,
|
inputs=variables,
|
||||||
status=WorkflowNodeExecutionStatus.FAILED,
|
status=WorkflowNodeExecutionStatus.FAILED,
|
||||||
error=f"Output length exceeds {MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH} characters",
|
error=f"Output length exceeds {self._max_output_length} characters",
|
||||||
)
|
)
|
||||||
|
|
||||||
return NodeRunResult(
|
return NodeRunResult(
|
||||||
|
|||||||
@@ -393,3 +393,35 @@ class BillingService:
|
|||||||
for item in data:
|
for item in data:
|
||||||
tenant_whitelist.append(item["tenant_id"])
|
tenant_whitelist.append(item["tenant_id"])
|
||||||
return tenant_whitelist
|
return tenant_whitelist
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def read_notification(cls, user_email: str):
|
||||||
|
params = {"user_email": user_email}
|
||||||
|
return cls._send_request("GET", "/notification/read", params=params)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save_notification_user(cls, user_email: str):
|
||||||
|
json = {"user_email": user_email}
|
||||||
|
return cls._send_request("POST", "/notification/new-notification-user", json=json)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save_notification_users_batch(cls, user_emails: list[str]) -> dict:
|
||||||
|
"""Batch save notification users in chunks of 1000."""
|
||||||
|
chunk_size = 1000
|
||||||
|
total_succeeded = 0
|
||||||
|
failed_chunks: list[dict] = []
|
||||||
|
|
||||||
|
for i in range(0, len(user_emails), chunk_size):
|
||||||
|
chunk = user_emails[i : i + chunk_size]
|
||||||
|
try:
|
||||||
|
resp = cls._send_request("POST", "/notification/batch-notification-users", json={"user_emails": chunk})
|
||||||
|
total_succeeded += resp.get("count", len(chunk))
|
||||||
|
except Exception as e:
|
||||||
|
failed_chunks.append({"offset": i, "count": len(chunk), "error": str(e)})
|
||||||
|
|
||||||
|
return {"succeeded": total_succeeded, "failed_chunks": failed_chunks}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save_notification_content(cls, content: str):
|
||||||
|
json = {"content": content}
|
||||||
|
return cls._send_request("POST", "/notification/new-notification", json=json)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
from enums.cloud_plan import CloudPlan
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from models.account import Tenant, TenantAccountJoin, TenantAccountRole
|
from models.account import Tenant, TenantAccountJoin, TenantAccountRole
|
||||||
from services.account_service import TenantService
|
from services.account_service import TenantService
|
||||||
@@ -53,7 +54,12 @@ class WorkspaceService:
|
|||||||
from services.credit_pool_service import CreditPoolService
|
from services.credit_pool_service import CreditPoolService
|
||||||
|
|
||||||
paid_pool = CreditPoolService.get_pool(tenant_id=tenant.id, pool_type="paid")
|
paid_pool = CreditPoolService.get_pool(tenant_id=tenant.id, pool_type="paid")
|
||||||
if paid_pool:
|
# if the tenant is not on the sandbox plan and the paid pool is not full, use the paid pool
|
||||||
|
if (
|
||||||
|
feature.billing.subscription.plan != CloudPlan.SANDBOX
|
||||||
|
and paid_pool is not None
|
||||||
|
and paid_pool.quota_limit > paid_pool.quota_used
|
||||||
|
):
|
||||||
tenant_info["trial_credits"] = paid_pool.quota_limit
|
tenant_info["trial_credits"] = paid_pool.quota_limit
|
||||||
tenant_info["trial_credits_used"] = paid_pool.quota_used
|
tenant_info["trial_credits_used"] = paid_pool.quota_used
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -217,7 +217,6 @@ class TestTemplateTransformNode:
|
|||||||
@patch(
|
@patch(
|
||||||
"core.workflow.nodes.template_transform.template_transform_node.CodeExecutorJinja2TemplateRenderer.render_template"
|
"core.workflow.nodes.template_transform.template_transform_node.CodeExecutorJinja2TemplateRenderer.render_template"
|
||||||
)
|
)
|
||||||
@patch("core.workflow.nodes.template_transform.template_transform_node.MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH", 10)
|
|
||||||
def test_run_output_length_exceeds_limit(
|
def test_run_output_length_exceeds_limit(
|
||||||
self, mock_execute, basic_node_data, mock_graph, mock_graph_runtime_state, graph_init_params
|
self, mock_execute, basic_node_data, mock_graph, mock_graph_runtime_state, graph_init_params
|
||||||
):
|
):
|
||||||
@@ -231,6 +230,7 @@ class TestTemplateTransformNode:
|
|||||||
graph_init_params=graph_init_params,
|
graph_init_params=graph_init_params,
|
||||||
graph=mock_graph,
|
graph=mock_graph,
|
||||||
graph_runtime_state=mock_graph_runtime_state,
|
graph_runtime_state=mock_graph_runtime_state,
|
||||||
|
max_output_length=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = node._run()
|
result = node._run()
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{
|
{
|
||||||
showSummaryIndexSetting && (
|
showSummaryIndexSetting && IS_CE_EDITION && (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<SummaryIndexSetting
|
<SummaryIndexSetting
|
||||||
entry="create-document"
|
entry="create-document"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Divider from '@/app/components/base/divider'
|
|||||||
import { ParentChildChunk } from '@/app/components/base/icons/src/vender/knowledge'
|
import { ParentChildChunk } from '@/app/components/base/icons/src/vender/knowledge'
|
||||||
import RadioCard from '@/app/components/base/radio-card'
|
import RadioCard from '@/app/components/base/radio-card'
|
||||||
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
|
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
|
||||||
|
import { IS_CE_EDITION } from '@/config'
|
||||||
import { ChunkingMode } from '@/models/datasets'
|
import { ChunkingMode } from '@/models/datasets'
|
||||||
import FileList from '../../assets/file-list-3-fill.svg'
|
import FileList from '../../assets/file-list-3-fill.svg'
|
||||||
import Note from '../../assets/note-mod.svg'
|
import Note from '../../assets/note-mod.svg'
|
||||||
@@ -191,7 +192,7 @@ export const ParentChildOptions: FC<ParentChildOptionsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{
|
{
|
||||||
showSummaryIndexSetting && (
|
showSummaryIndexSetting && IS_CE_EDITION && (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<SummaryIndexSetting
|
<SummaryIndexSetting
|
||||||
entry="create-document"
|
entry="create-document"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import CustomPopover from '@/app/components/base/popover'
|
|||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import { IS_CE_EDITION } from '@/config'
|
||||||
import { DataSourceType, DocumentActionType } from '@/models/datasets'
|
import { DataSourceType, DocumentActionType } from '@/models/datasets'
|
||||||
import {
|
import {
|
||||||
useDocumentArchive,
|
useDocumentArchive,
|
||||||
@@ -263,10 +264,14 @@ const Operations = ({
|
|||||||
<span className={s.actionName}>{t('list.action.sync', { ns: 'datasetDocuments' })}</span>
|
<span className={s.actionName}>{t('list.action.sync', { ns: 'datasetDocuments' })}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={s.actionItem} onClick={() => onOperate('summary')}>
|
{
|
||||||
<SearchLinesSparkle className="h-4 w-4 text-text-tertiary" />
|
IS_CE_EDITION && (
|
||||||
<span className={s.actionName}>{t('list.action.summary', { ns: 'datasetDocuments' })}</span>
|
<div className={s.actionItem} onClick={() => onOperate('summary')}>
|
||||||
</div>
|
<SearchLinesSparkle className="h-4 w-4 text-text-tertiary" />
|
||||||
|
<span className={s.actionName}>{t('list.action.summary', { ns: 'datasetDocuments' })}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
<Divider className="my-1" />
|
<Divider className="my-1" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Button from '@/app/components/base/button'
|
|||||||
import Confirm from '@/app/components/base/confirm'
|
import Confirm from '@/app/components/base/confirm'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||||
|
import { IS_CE_EDITION } from '@/config'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
|
|
||||||
const i18nPrefix = 'batchAction'
|
const i18nPrefix = 'batchAction'
|
||||||
@@ -87,7 +88,7 @@ const BatchAction: FC<IBatchActionProps> = ({
|
|||||||
<span className="px-0.5">{t('metadata.metadata', { ns: 'dataset' })}</span>
|
<span className="px-0.5">{t('metadata.metadata', { ns: 'dataset' })}</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{onBatchSummary && (
|
{onBatchSummary && IS_CE_EDITION && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="gap-x-0.5 px-3"
|
className="gap-x-0.5 px-3"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-me
|
|||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||||
|
import { IS_CE_EDITION } from '@/config'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import { useDocLink } from '@/context/i18n'
|
import { useDocLink } from '@/context/i18n'
|
||||||
@@ -359,7 +360,7 @@ const Form = () => {
|
|||||||
{
|
{
|
||||||
indexMethod === IndexingType.QUALIFIED
|
indexMethod === IndexingType.QUALIFIED
|
||||||
&& [ChunkingMode.text, ChunkingMode.parentChild].includes(currentDataset?.doc_form as ChunkingMode)
|
&& [ChunkingMode.text, ChunkingMode.parentChild].includes(currentDataset?.doc_form as ChunkingMode)
|
||||||
&& (
|
&& IS_CE_EDITION && (
|
||||||
<>
|
<>
|
||||||
<Divider
|
<Divider
|
||||||
type="horizontal"
|
type="horizontal"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
} from '@/app/components/workflow/nodes/_base/components/layout'
|
} from '@/app/components/workflow/nodes/_base/components/layout'
|
||||||
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
|
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
|
||||||
|
import { IS_CE_EDITION } from '@/config'
|
||||||
import Split from '../_base/components/split'
|
import Split from '../_base/components/split'
|
||||||
import ChunkStructure from './components/chunk-structure'
|
import ChunkStructure from './components/chunk-structure'
|
||||||
import EmbeddingModel from './components/embedding-model'
|
import EmbeddingModel from './components/embedding-model'
|
||||||
@@ -172,7 +173,7 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
|||||||
{
|
{
|
||||||
data.indexing_technique === IndexMethodEnum.QUALIFIED
|
data.indexing_technique === IndexMethodEnum.QUALIFIED
|
||||||
&& [ChunkStructureEnum.general, ChunkStructureEnum.parent_child].includes(data.chunk_structure)
|
&& [ChunkStructureEnum.general, ChunkStructureEnum.parent_child].includes(data.chunk_structure)
|
||||||
&& (
|
&& IS_CE_EDITION && (
|
||||||
<>
|
<>
|
||||||
<SummaryIndexSetting
|
<SummaryIndexSetting
|
||||||
summaryIndexSetting={data.summary_index_setting}
|
summaryIndexSetting={data.summary_index_setting}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { App, AppCategory } from '@/models/explore'
|
import type { App, AppCategory } from '@/models/explore'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
import { useLocale } from '@/context/i18n'
|
||||||
import { AccessMode } from '@/models/access-control'
|
import { AccessMode } from '@/models/access-control'
|
||||||
import { fetchAppList, fetchBanners, fetchInstalledAppList, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore'
|
import { fetchAppList, fetchBanners, fetchInstalledAppList, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore'
|
||||||
import { AppSourceType, fetchAppMeta, fetchAppParams } from './share'
|
import { AppSourceType, fetchAppMeta, fetchAppParams } from './share'
|
||||||
@@ -13,8 +14,9 @@ type ExploreAppListData = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useExploreAppList = () => {
|
export const useExploreAppList = () => {
|
||||||
|
const locale = useLocale()
|
||||||
return useQuery<ExploreAppListData>({
|
return useQuery<ExploreAppListData>({
|
||||||
queryKey: [NAME_SPACE, 'appList'],
|
queryKey: [NAME_SPACE, 'appList', locale],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { categories, recommended_apps } = await fetchAppList()
|
const { categories, recommended_apps } = await fetchAppList()
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user