feat(web): refine onboarding UI (#37433)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: fatelei <fatelei@gmail.com> Co-authored-by: Asuka Minato <i@asukaminato.eu.org> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com> Co-authored-by: gigglewang <gigglewang@dify.ai> Co-authored-by: Yunlu Wen <yunlu.wen@dify.ai> Co-authored-by: chariri <w@chariri.moe> Co-authored-by: Evan <2869018789@qq.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
@ -11,6 +11,7 @@ from .data_migration import (
|
||||
migration_data_wizard,
|
||||
)
|
||||
from .plugin import (
|
||||
backfill_plugin_auto_upgrade,
|
||||
extract_plugins,
|
||||
extract_unique_plugins,
|
||||
install_plugins,
|
||||
@ -49,6 +50,7 @@ from .vector import (
|
||||
__all__ = [
|
||||
"add_qdrant_index",
|
||||
"archive_workflow_runs",
|
||||
"backfill_plugin_auto_upgrade",
|
||||
"clean_expired_messages",
|
||||
"clean_workflow_runs",
|
||||
"cleanup_orphaned_draft_variables",
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
import click
|
||||
from pydantic import TypeAdapter
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy import delete, func, select
|
||||
from sqlalchemy.engine import CursorResult
|
||||
|
||||
from configs import dify_config
|
||||
@ -15,11 +16,13 @@ from core.plugin.plugin_service import PluginService
|
||||
from core.tools.utils.system_encryption import encrypt_system_params
|
||||
from extensions.ext_database import db
|
||||
from models import Tenant
|
||||
from models.account import TenantPluginAutoUpgradeStrategy
|
||||
from models.oauth import DatasourceOauthParamConfig, DatasourceProvider
|
||||
from models.provider_ids import DatasourceProviderID, ToolProviderID
|
||||
from models.source import DataSourceApiKeyAuthBinding, DataSourceOauthBinding
|
||||
from models.tools import ToolOAuthSystemClient
|
||||
from services.plugin.data_migration import PluginDataMigration
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
from services.plugin.plugin_migration import PluginMigration
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -402,6 +405,110 @@ def migrate_data_for_plugin():
|
||||
click.echo(click.style("Migrate data for plugin completed.", fg="green"))
|
||||
|
||||
|
||||
def _candidate_auto_upgrade_strategy_tenant_ids_stmt(limit: int | None = None):
|
||||
category_count = len(TenantPluginAutoUpgradeStrategy.PluginCategory)
|
||||
stmt = (
|
||||
select(TenantPluginAutoUpgradeStrategy.tenant_id)
|
||||
.group_by(TenantPluginAutoUpgradeStrategy.tenant_id)
|
||||
.having(func.count(func.distinct(TenantPluginAutoUpgradeStrategy.category)) < category_count)
|
||||
.order_by(TenantPluginAutoUpgradeStrategy.tenant_id)
|
||||
)
|
||||
|
||||
if limit is not None:
|
||||
stmt = stmt.limit(limit)
|
||||
|
||||
return stmt
|
||||
|
||||
|
||||
def _count_auto_upgrade_strategy_tenant_ids(limit: int | None) -> int:
|
||||
candidate_stmt = _candidate_auto_upgrade_strategy_tenant_ids_stmt(limit).subquery()
|
||||
return db.session.scalar(select(func.count()).select_from(candidate_stmt)) or 0
|
||||
|
||||
|
||||
def _iter_auto_upgrade_strategy_tenant_ids(limit: int | None):
|
||||
stmt = _candidate_auto_upgrade_strategy_tenant_ids_stmt(limit).execution_options(yield_per=1000)
|
||||
yield from db.session.scalars(stmt)
|
||||
|
||||
|
||||
@click.command(
|
||||
"backfill-plugin-auto-upgrade",
|
||||
help="Backfill category-scoped plugin auto-upgrade strategies and normalize plugin lists.",
|
||||
)
|
||||
@click.option("--tenant-id", multiple=True, help="Tenant ID to backfill. Can be passed multiple times.")
|
||||
@click.option("--limit", type=int, default=None, help="Maximum number of candidate tenants to process.")
|
||||
@click.option("--batch-size", type=int, default=500, show_default=True, help="Progress reporting batch size.")
|
||||
@click.option("--dry-run", is_flag=True, help="Only print candidate tenant count.")
|
||||
def backfill_plugin_auto_upgrade(
|
||||
tenant_id: tuple[str, ...],
|
||||
limit: int | None,
|
||||
batch_size: int,
|
||||
dry_run: bool,
|
||||
):
|
||||
"""
|
||||
Backfill historical auto-upgrade strategies after the category column exists.
|
||||
|
||||
Missing category rows are created from the tenant's tool/default row. Pure default
|
||||
strategies become latest for model plugins and fix-only for all other categories.
|
||||
Tenants with include/exclude plugin IDs are split
|
||||
by installed plugin category using plugin daemon metadata.
|
||||
"""
|
||||
start_at = time.perf_counter()
|
||||
candidate_count = len(tenant_id) if tenant_id else _count_auto_upgrade_strategy_tenant_ids(limit)
|
||||
click.echo(click.style(f"Found {candidate_count} candidate tenants.", fg="yellow"))
|
||||
|
||||
if dry_run:
|
||||
elapsed = time.perf_counter() - start_at
|
||||
click.echo(click.style(f"Dry run completed. elapsed={elapsed:.2f}s", fg="green"))
|
||||
return
|
||||
|
||||
tenant_ids = list(tenant_id) if tenant_id else _iter_auto_upgrade_strategy_tenant_ids(limit)
|
||||
|
||||
backfilled_count = 0
|
||||
created_count = 0
|
||||
normalized_count = 0
|
||||
skipped_count = 0
|
||||
failed_count = 0
|
||||
for index, current_tenant_id in enumerate(tenant_ids, start=1):
|
||||
try:
|
||||
result = PluginAutoUpgradeService.backfill_strategy_categories(
|
||||
current_tenant_id,
|
||||
)
|
||||
except Exception as e:
|
||||
failed_count += 1
|
||||
click.echo(click.style(f"Failed tenant {current_tenant_id}: {str(e)}", fg="red"))
|
||||
continue
|
||||
|
||||
if result.created_count > 0:
|
||||
backfilled_count += 1
|
||||
created_count += result.created_count
|
||||
elif not result.normalized:
|
||||
skipped_count += 1
|
||||
if result.normalized:
|
||||
normalized_count += 1
|
||||
|
||||
if batch_size > 0 and index % batch_size == 0:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Processed {index}/{candidate_count} tenants. "
|
||||
f"backfilled={backfilled_count}, created_rows={created_count}, "
|
||||
f"normalized={normalized_count}, skipped={skipped_count}, failed={failed_count}, "
|
||||
f"elapsed={time.perf_counter() - start_at:.2f}s",
|
||||
fg="yellow",
|
||||
)
|
||||
)
|
||||
|
||||
elapsed = time.perf_counter() - start_at
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Backfill plugin auto-upgrade strategy categories completed. "
|
||||
f"backfilled={backfilled_count}, created_rows={created_count}, "
|
||||
f"normalized={normalized_count}, skipped={skipped_count}, failed={failed_count}, "
|
||||
f"elapsed={elapsed:.2f}s",
|
||||
fg="green",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@click.command("extract-plugins", help="Extract plugins.")
|
||||
@click.option("--output_file", prompt=True, help="The file to store the extracted plugins.", default="plugins.jsonl")
|
||||
@click.option("--workers", prompt=True, help="The number of workers to extract plugins.", default=10)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import re
|
||||
import uuid
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
@ -41,12 +42,12 @@ from core.trigger.constants import TRIGGER_NODE_TYPES
|
||||
from extensions.ext_database import db
|
||||
from fields.base import ResponseModel
|
||||
from graphon.enums import WorkflowExecutionStatus
|
||||
from libs.helper import build_icon_url, to_timestamp
|
||||
from libs.helper import build_icon_url, dump_response, to_timestamp
|
||||
from libs.login import login_required
|
||||
from models import Account, App, DatasetPermissionEnum, Workflow
|
||||
from models.model import IconType
|
||||
from services.app_dsl_service import AppDslService
|
||||
from services.app_service import AppListParams, AppService, CreateAppParams
|
||||
from services.app_service import AppListParams, AppListSortBy, AppService, CreateAppParams, StarredAppListParams
|
||||
from services.enterprise.enterprise_service import EnterpriseService
|
||||
from services.entities.dsl_entities import ImportMode, ImportStatus
|
||||
from services.entities.knowledge_entities.knowledge_entities import (
|
||||
@ -73,10 +74,14 @@ _CREATOR_IDS_BRACKET_PATTERN = re.compile(r"^creator_ids\[(\d+)\]$")
|
||||
AppListMode = Literal["completion", "chat", "advanced-chat", "workflow", "agent-chat", "agent", "channel", "all"]
|
||||
|
||||
|
||||
class AppListQuery(BaseModel):
|
||||
class AppListBaseQuery(BaseModel):
|
||||
page: int = Field(default=1, ge=1, le=99999, description="Page number (1-99999)")
|
||||
limit: int = Field(default=20, ge=1, le=100, description="Page size (1-100)")
|
||||
mode: AppListMode = Field(default=cast(AppListMode, "all"), description="App mode filter")
|
||||
sort_by: AppListSortBy = Field(
|
||||
default="last_modified",
|
||||
description="Sort apps by last modified, recently created, or earliest created",
|
||||
)
|
||||
name: str | None = Field(default=None, description="Filter by app name")
|
||||
tag_ids: list[str] | None = Field(default=None, description="Filter by tag IDs")
|
||||
creator_ids: list[str] | None = Field(default=None, description="Filter by creator account IDs")
|
||||
@ -119,6 +124,14 @@ class AppListQuery(BaseModel):
|
||||
raise ValueError("Invalid UUID format in creator_ids.") from exc
|
||||
|
||||
|
||||
class AppListQuery(AppListBaseQuery):
|
||||
pass
|
||||
|
||||
|
||||
class StarredAppListQuery(AppListBaseQuery):
|
||||
pass
|
||||
|
||||
|
||||
def _normalize_app_list_query_args(query_args: MultiDict[str, str]) -> dict[str, str | list[str]]:
|
||||
normalized: dict[str, str | list[str]] = {}
|
||||
indexed_tag_ids: list[tuple[int, str]] = []
|
||||
@ -387,6 +400,7 @@ class AppPartial(ResponseModel):
|
||||
create_user_name: str | None = None
|
||||
author_name: str | None = None
|
||||
has_draft_trigger: bool | None = None
|
||||
is_starred: bool = False
|
||||
|
||||
@computed_field(return_type=str | None) # type: ignore
|
||||
@property
|
||||
@ -456,12 +470,54 @@ class AppExportResponse(ResponseModel):
|
||||
data: str
|
||||
|
||||
|
||||
def _enrich_app_list_items(session: Session, *, apps: Sequence[App], tenant_id: str) -> None:
|
||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
||||
app_ids = [str(app.id) for app in apps]
|
||||
res = EnterpriseService.WebAppAuth.batch_get_app_access_mode_by_id(app_ids=app_ids)
|
||||
if len(res) != len(app_ids):
|
||||
raise BadRequest("Invalid app id in webapp auth")
|
||||
|
||||
for app in apps:
|
||||
if str(app.id) in res:
|
||||
app.access_mode = res[str(app.id)].access_mode
|
||||
|
||||
workflow_capable_app_ids = [str(app.id) for app in apps if app.mode in {"workflow", "advanced-chat"}]
|
||||
draft_trigger_app_ids: set[str] = set()
|
||||
if workflow_capable_app_ids:
|
||||
draft_workflows = (
|
||||
session.execute(
|
||||
select(Workflow).where(
|
||||
Workflow.version == Workflow.VERSION_DRAFT,
|
||||
Workflow.app_id.in_(workflow_capable_app_ids),
|
||||
Workflow.tenant_id == tenant_id,
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
trigger_node_types = TRIGGER_NODE_TYPES
|
||||
for workflow in draft_workflows:
|
||||
node_id = None
|
||||
try:
|
||||
for node_id, node_data in workflow.walk_nodes():
|
||||
if node_data.get("type") in trigger_node_types:
|
||||
draft_trigger_app_ids.add(str(workflow.app_id))
|
||||
break
|
||||
except Exception:
|
||||
_logger.exception("error while walking nodes, workflow_id=%s, node_id=%s", workflow.id, node_id)
|
||||
continue
|
||||
|
||||
for app in apps:
|
||||
app.has_draft_trigger = str(app.id) in draft_trigger_app_ids
|
||||
|
||||
|
||||
register_enum_models(console_ns, RetrievalMethod, WorkflowExecutionStatus, DatasetPermissionEnum)
|
||||
register_response_schema_models(console_ns, AppTraceResponse, RedirectUrlResponse, SimpleResultResponse)
|
||||
|
||||
register_schema_models(
|
||||
console_ns,
|
||||
AppListQuery,
|
||||
StarredAppListQuery,
|
||||
CreateAppPayload,
|
||||
UpdateAppPayload,
|
||||
CopyAppPayload,
|
||||
@ -521,6 +577,7 @@ class AppListApi(Resource):
|
||||
page=args.page,
|
||||
limit=args.limit,
|
||||
mode=args.mode,
|
||||
sort_by=args.sort_by,
|
||||
name=args.name,
|
||||
tag_ids=args.tag_ids,
|
||||
creator_ids=args.creator_ids,
|
||||
@ -534,46 +591,7 @@ class AppListApi(Resource):
|
||||
empty = AppPagination(page=args.page, limit=args.limit, total=0, has_more=False, data=[])
|
||||
return empty.model_dump(mode="json"), 200
|
||||
|
||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
||||
app_ids = [str(app.id) for app in app_pagination.items]
|
||||
res = EnterpriseService.WebAppAuth.batch_get_app_access_mode_by_id(app_ids=app_ids)
|
||||
if len(res) != len(app_ids):
|
||||
raise BadRequest("Invalid app id in webapp auth")
|
||||
|
||||
for app in app_pagination.items:
|
||||
if str(app.id) in res:
|
||||
app.access_mode = res[str(app.id)].access_mode
|
||||
|
||||
workflow_capable_app_ids = [
|
||||
str(app.id) for app in app_pagination.items if app.mode in {"workflow", "advanced-chat"}
|
||||
]
|
||||
draft_trigger_app_ids: set[str] = set()
|
||||
if workflow_capable_app_ids:
|
||||
draft_workflows = (
|
||||
session.execute(
|
||||
select(Workflow).where(
|
||||
Workflow.version == Workflow.VERSION_DRAFT,
|
||||
Workflow.app_id.in_(workflow_capable_app_ids),
|
||||
Workflow.tenant_id == current_tenant_id,
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
trigger_node_types = TRIGGER_NODE_TYPES
|
||||
for workflow in draft_workflows:
|
||||
node_id = None
|
||||
try:
|
||||
for node_id, node_data in workflow.walk_nodes():
|
||||
if node_data.get("type") in trigger_node_types:
|
||||
draft_trigger_app_ids.add(str(workflow.app_id))
|
||||
break
|
||||
except Exception:
|
||||
_logger.exception("error while walking nodes, workflow_id=%s, node_id=%s", workflow.id, node_id)
|
||||
continue
|
||||
|
||||
for app in app_pagination.items:
|
||||
app.has_draft_trigger = str(app.id) in draft_trigger_app_ids
|
||||
_enrich_app_list_items(session, apps=app_pagination.items, tenant_id=current_tenant_id)
|
||||
|
||||
pagination_model = AppPagination.model_validate(app_pagination, from_attributes=True)
|
||||
return pagination_model.model_dump(mode="json"), 200
|
||||
@ -609,6 +627,78 @@ class AppListApi(Resource):
|
||||
return app_detail.model_dump(mode="json"), 201
|
||||
|
||||
|
||||
@console_ns.route("/apps/starred")
|
||||
class StarredAppListApi(Resource):
|
||||
@console_ns.doc("list_starred_apps")
|
||||
@console_ns.doc(description="Get applications starred by the current account")
|
||||
@console_ns.doc(params=query_params_from_model(StarredAppListQuery))
|
||||
@console_ns.response(200, "Success", console_ns.models[AppPagination.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@enterprise_license_required
|
||||
@with_session(write=False)
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
def get(self, current_tenant_id: str, current_user_id: str, session: Session):
|
||||
args = StarredAppListQuery.model_validate(_normalize_app_list_query_args(request.args))
|
||||
params = StarredAppListParams(
|
||||
page=args.page,
|
||||
limit=args.limit,
|
||||
mode=args.mode,
|
||||
sort_by=args.sort_by,
|
||||
name=args.name,
|
||||
tag_ids=args.tag_ids,
|
||||
creator_ids=args.creator_ids,
|
||||
is_created_by_me=args.is_created_by_me,
|
||||
)
|
||||
|
||||
app_pagination = AppService().get_paginate_starred_apps(current_user_id, current_tenant_id, params)
|
||||
if not app_pagination:
|
||||
empty = AppPagination(page=args.page, limit=args.limit, total=0, has_more=False, data=[])
|
||||
return empty.model_dump(mode="json"), 200
|
||||
|
||||
_enrich_app_list_items(session, apps=app_pagination.items, tenant_id=current_tenant_id)
|
||||
|
||||
pagination_model = AppPagination.model_validate(app_pagination, from_attributes=True)
|
||||
return pagination_model.model_dump(mode="json"), 200
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/star")
|
||||
class AppStarApi(Resource):
|
||||
@console_ns.doc("star_app")
|
||||
@console_ns.doc(description="Star an application for the current account")
|
||||
@console_ns.doc(params={"app_id": "Application ID"})
|
||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||
@console_ns.response(404, "App not found")
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@enterprise_license_required
|
||||
@with_current_user_id
|
||||
@with_session
|
||||
@get_app_model(mode=None)
|
||||
def post(self, session: Session, current_user_id: str, app_model: App):
|
||||
AppService.star_app(session, app=app_model, account_id=current_user_id)
|
||||
return dump_response(SimpleResultResponse, {"result": "success"})
|
||||
|
||||
@console_ns.doc("unstar_app")
|
||||
@console_ns.doc(description="Remove the current account's star from an application")
|
||||
@console_ns.doc(params={"app_id": "Application ID"})
|
||||
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
||||
@console_ns.response(404, "App not found")
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@enterprise_license_required
|
||||
@with_current_user_id
|
||||
@with_session
|
||||
@get_app_model(mode=None)
|
||||
def delete(self, session: Session, current_user_id: str, app_model: App):
|
||||
AppService.unstar_app(session, app=app_model, account_id=current_user_id)
|
||||
return dump_response(SimpleResultResponse, {"result": "success"})
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>")
|
||||
class AppApi(Resource):
|
||||
@console_ns.doc("get_app_detail")
|
||||
@ -628,7 +718,7 @@ class AppApi(Resource):
|
||||
|
||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
||||
app_setting = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=str(app_model.id))
|
||||
app_model.access_mode = app_setting.access_mode # type: ignore[attr-defined]
|
||||
app_model.access_mode = app_setting.access_mode
|
||||
|
||||
response_model = AppDetailWithSite.model_validate(app_model, from_attributes=True)
|
||||
return response_model.model_dump(mode="json")
|
||||
|
||||
@ -66,6 +66,10 @@ class RecommendedAppListResponse(ResponseModel):
|
||||
categories: list[str]
|
||||
|
||||
|
||||
class LearnDifyAppListResponse(ResponseModel):
|
||||
recommended_apps: list[RecommendedAppResponse]
|
||||
|
||||
|
||||
class RecommendedAppDetailResponse(RootModel[dict[str, Any]]):
|
||||
root: dict[str, Any]
|
||||
|
||||
@ -76,10 +80,19 @@ register_schema_models(
|
||||
RecommendedAppInfoResponse,
|
||||
RecommendedAppResponse,
|
||||
RecommendedAppListResponse,
|
||||
LearnDifyAppListResponse,
|
||||
)
|
||||
register_response_schema_models(console_ns, RecommendedAppDetailResponse)
|
||||
|
||||
|
||||
def _resolve_language(language: str | None, user: Account) -> str:
|
||||
if language and language in languages:
|
||||
return language
|
||||
if user.interface_language:
|
||||
return user.interface_language
|
||||
return languages[0]
|
||||
|
||||
|
||||
@console_ns.route("/explore/apps")
|
||||
class RecommendedAppListApi(Resource):
|
||||
@console_ns.doc(params=query_params_from_model(RecommendedAppsQuery))
|
||||
@ -90,13 +103,7 @@ class RecommendedAppListApi(Resource):
|
||||
def get(self, current_user: Account):
|
||||
# language args
|
||||
args = RecommendedAppsQuery.model_validate(request.args.to_dict(flat=True))
|
||||
language = args.language
|
||||
if language and language in languages:
|
||||
language_prefix = language
|
||||
elif current_user.interface_language:
|
||||
language_prefix = current_user.interface_language
|
||||
else:
|
||||
language_prefix = languages[0]
|
||||
language_prefix = _resolve_language(args.language, current_user)
|
||||
|
||||
return RecommendedAppListResponse.model_validate(
|
||||
RecommendedAppService.get_recommended_apps_and_categories(db.session, language_prefix),
|
||||
@ -104,6 +111,23 @@ class RecommendedAppListApi(Resource):
|
||||
).model_dump(mode="json")
|
||||
|
||||
|
||||
@console_ns.route("/explore/apps/learn-dify")
|
||||
class LearnDifyAppListApi(Resource):
|
||||
@console_ns.doc(params=query_params_from_model(RecommendedAppsQuery))
|
||||
@console_ns.response(200, "Success", console_ns.models[LearnDifyAppListResponse.__name__])
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
def get(self, current_user: Account):
|
||||
args = RecommendedAppsQuery.model_validate(request.args.to_dict(flat=True))
|
||||
language_prefix = _resolve_language(args.language, current_user)
|
||||
|
||||
return LearnDifyAppListResponse.model_validate(
|
||||
RecommendedAppService.get_learn_dify_apps(db.session, language_prefix),
|
||||
from_attributes=True,
|
||||
).model_dump(mode="json")
|
||||
|
||||
|
||||
@console_ns.route("/explore/apps/<uuid:app_id>")
|
||||
class RecommendedAppApi(Resource):
|
||||
@console_ns.response(200, "Success", console_ns.models[RecommendedAppDetailResponse.__name__])
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import io
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, Literal
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal, TypedDict
|
||||
|
||||
from flask import request, send_file
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, RootModel
|
||||
from pydantic import BaseModel, ConfigDict, Field, RootModel
|
||||
from werkzeug.datastructures import FileStorage
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
@ -26,15 +27,33 @@ from controllers.console.wraps import (
|
||||
with_current_user,
|
||||
with_current_user_id,
|
||||
)
|
||||
from core.helper.position_helper import is_filtered
|
||||
from core.plugin.entities.plugin import PluginCategory, PluginInstallationSource
|
||||
from core.plugin.impl.exc import PluginDaemonClientSideError
|
||||
from core.plugin.plugin_service import PluginService
|
||||
from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
|
||||
from core.tools.entities.common_entities import I18nObject
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
from core.tools.tool_manager import ToolManager
|
||||
from fields.base import ResponseModel
|
||||
from graphon.model_runtime.utils.encoders import jsonable_encoder
|
||||
from libs.helper import dump_response
|
||||
from libs.login import login_required
|
||||
from models.account import Account, TenantPluginAutoUpgradeStrategy, TenantPluginPermission
|
||||
from models.provider_ids import ToolProviderID
|
||||
from services.entities.model_provider_entities import ProviderEntityResponse
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
from services.plugin.plugin_parameter_service import PluginParameterService
|
||||
from services.plugin.plugin_permission_service import PluginPermissionService
|
||||
from services.tools.tools_transform_service import ToolTransformService
|
||||
|
||||
|
||||
class AutoUpgradeSettingsResponse(TypedDict):
|
||||
strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting
|
||||
upgrade_time_of_day: int
|
||||
upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode
|
||||
exclude_plugins: list[str]
|
||||
include_plugins: list[str]
|
||||
|
||||
|
||||
class ParserList(BaseModel):
|
||||
@ -42,6 +61,11 @@ class ParserList(BaseModel):
|
||||
page_size: int = Field(default=256, ge=1, le=256, description="Page size (1-256)")
|
||||
|
||||
|
||||
class PluginCategoryListQuery(BaseModel):
|
||||
page: int = Field(default=1, ge=1, description="Page number")
|
||||
page_size: int = Field(default=256, ge=1, le=256, description="Page size (1-256)")
|
||||
|
||||
|
||||
class ParserLatest(BaseModel):
|
||||
plugin_ids: list[str]
|
||||
|
||||
@ -100,8 +124,8 @@ class ParserUninstall(BaseModel):
|
||||
|
||||
|
||||
class ParserPermissionChange(BaseModel):
|
||||
install_permission: TenantPluginPermission.InstallPermission
|
||||
debug_permission: TenantPluginPermission.DebugPermission
|
||||
install_permission: TenantPluginPermission.InstallPermission = TenantPluginPermission.InstallPermission.EVERYONE
|
||||
debug_permission: TenantPluginPermission.DebugPermission = TenantPluginPermission.DebugPermission.EVERYONE
|
||||
|
||||
|
||||
class ParserDynamicOptions(BaseModel):
|
||||
@ -137,13 +161,64 @@ class PluginAutoUpgradeSettingsPayload(BaseModel):
|
||||
include_plugins: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ParserPreferencesChange(BaseModel):
|
||||
permission: PluginPermissionSettingsPayload
|
||||
class PluginAutoUpgradeChangeResponse(ResponseModel):
|
||||
success: bool
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class PluginAutoUpgradeSettingsResponseModel(ResponseModel):
|
||||
strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting
|
||||
upgrade_time_of_day: int
|
||||
upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode
|
||||
exclude_plugins: list[str]
|
||||
include_plugins: list[str]
|
||||
|
||||
|
||||
class PluginAutoUpgradeFetchResponse(ResponseModel):
|
||||
category: TenantPluginAutoUpgradeStrategy.PluginCategory
|
||||
auto_upgrade: PluginAutoUpgradeSettingsResponseModel
|
||||
|
||||
|
||||
class PluginDeclarationResponse(ResponseModel):
|
||||
version: str
|
||||
author: str | None
|
||||
name: str
|
||||
description: I18nObject
|
||||
icon: str
|
||||
icon_dark: str | None = None
|
||||
label: I18nObject
|
||||
category: PluginCategory
|
||||
created_at: datetime
|
||||
resource: Mapping[str, Any]
|
||||
plugins: Mapping[str, list[str] | None]
|
||||
tags: list[str] = Field(default_factory=list)
|
||||
repo: str | None = None
|
||||
verified: bool = False
|
||||
tool: Mapping[str, Any] | None = None
|
||||
model: ProviderEntityResponse | None = None
|
||||
endpoint: Mapping[str, Any] | None = None
|
||||
agent_strategy: Mapping[str, Any] | None = None
|
||||
datasource: Mapping[str, Any] | None = None
|
||||
trigger: Mapping[str, Any] | None = None
|
||||
meta: Mapping[str, Any]
|
||||
|
||||
|
||||
class ParserAutoUpgradeChange(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
category: TenantPluginAutoUpgradeStrategy.PluginCategory
|
||||
auto_upgrade: PluginAutoUpgradeSettingsPayload
|
||||
|
||||
|
||||
class ParserAutoUpgradeFetch(BaseModel):
|
||||
category: TenantPluginAutoUpgradeStrategy.PluginCategory
|
||||
|
||||
|
||||
class ParserExcludePlugin(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
plugin_id: str
|
||||
category: TenantPluginAutoUpgradeStrategy.PluginCategory
|
||||
|
||||
|
||||
class ParserReadme(BaseModel):
|
||||
@ -157,6 +232,63 @@ class PluginDebuggingKeyResponse(ResponseModel):
|
||||
port: int
|
||||
|
||||
|
||||
class PluginCategoryInstalledPluginResponse(ResponseModel):
|
||||
id: str
|
||||
name: str
|
||||
tenant_id: str
|
||||
plugin_id: str
|
||||
plugin_unique_identifier: str
|
||||
endpoints_active: int
|
||||
endpoints_setups: int
|
||||
installation_id: str
|
||||
declaration: PluginDeclarationResponse
|
||||
runtime_type: str
|
||||
version: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
source: PluginInstallationSource
|
||||
checksum: str
|
||||
meta: Mapping[str, Any]
|
||||
|
||||
|
||||
class PluginCategoryBuiltinToolResponse(ResponseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
author: str
|
||||
name: str
|
||||
label: I18nObject
|
||||
description: I18nObject
|
||||
parameters: list[Mapping[str, Any]] | None = None
|
||||
labels: list[str]
|
||||
output_schema: Mapping[str, object]
|
||||
|
||||
|
||||
class PluginCategoryBuiltinToolProviderResponse(ResponseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: str
|
||||
author: str
|
||||
name: str
|
||||
plugin_id: str | None
|
||||
plugin_unique_identifier: str | None
|
||||
description: I18nObject
|
||||
icon: str | Mapping[str, str]
|
||||
icon_dark: str | Mapping[str, str] | None
|
||||
label: I18nObject
|
||||
type: ToolProviderType
|
||||
team_credentials: Mapping[str, object]
|
||||
is_team_authorization: bool
|
||||
allow_delete: bool
|
||||
tools: list[PluginCategoryBuiltinToolResponse]
|
||||
labels: list[str]
|
||||
|
||||
|
||||
class PluginCategoryListResponse(ResponseModel):
|
||||
plugins: list[PluginCategoryInstalledPluginResponse]
|
||||
builtin_tools: list[PluginCategoryBuiltinToolProviderResponse]
|
||||
has_more: bool
|
||||
|
||||
|
||||
class PluginDaemonOperationResponse(RootModel[Any]):
|
||||
root: Any
|
||||
|
||||
@ -200,11 +332,6 @@ class PluginOperationSuccessResponse(ResponseModel):
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class PluginPreferencesResponse(ResponseModel):
|
||||
permission: PluginPermissionSettingsPayload
|
||||
auto_upgrade: PluginAutoUpgradeSettingsPayload
|
||||
|
||||
|
||||
class PluginReadmeResponse(ResponseModel):
|
||||
readme: str
|
||||
|
||||
@ -212,6 +339,7 @@ class PluginReadmeResponse(ResponseModel):
|
||||
register_schema_models(
|
||||
console_ns,
|
||||
ParserList,
|
||||
PluginCategoryListQuery,
|
||||
PluginAutoUpgradeSettingsPayload,
|
||||
PluginPermissionSettingsPayload,
|
||||
ParserLatest,
|
||||
@ -228,13 +356,21 @@ register_schema_models(
|
||||
ParserPermissionChange,
|
||||
ParserDynamicOptions,
|
||||
ParserDynamicOptionsWithCredentials,
|
||||
ParserPreferencesChange,
|
||||
ParserAutoUpgradeChange,
|
||||
ParserAutoUpgradeFetch,
|
||||
ParserExcludePlugin,
|
||||
ParserReadme,
|
||||
)
|
||||
register_response_schema_models(
|
||||
console_ns,
|
||||
PluginAutoUpgradeChangeResponse,
|
||||
PluginAutoUpgradeFetchResponse,
|
||||
PluginAutoUpgradeSettingsResponseModel,
|
||||
BinaryFileResponse,
|
||||
PluginCategoryBuiltinToolProviderResponse,
|
||||
PluginCategoryBuiltinToolResponse,
|
||||
PluginCategoryInstalledPluginResponse,
|
||||
PluginCategoryListResponse,
|
||||
PluginDaemonOperationResponse,
|
||||
PluginDebuggingKeyResponse,
|
||||
PluginDynamicOptionsResponse,
|
||||
@ -243,7 +379,6 @@ register_response_schema_models(
|
||||
PluginManifestResponse,
|
||||
PluginOperationSuccessResponse,
|
||||
PluginPermissionResponse,
|
||||
PluginPreferencesResponse,
|
||||
PluginReadmeResponse,
|
||||
PluginTaskResponse,
|
||||
PluginTasksResponse,
|
||||
@ -254,12 +389,36 @@ register_response_schema_models(
|
||||
register_enum_models(
|
||||
console_ns,
|
||||
TenantPluginPermission.DebugPermission,
|
||||
TenantPluginAutoUpgradeStrategy.PluginCategory,
|
||||
TenantPluginAutoUpgradeStrategy.UpgradeMode,
|
||||
TenantPluginAutoUpgradeStrategy.StrategySetting,
|
||||
TenantPluginPermission.InstallPermission,
|
||||
)
|
||||
|
||||
|
||||
def _default_auto_upgrade_settings(
|
||||
tenant_id: str,
|
||||
category: TenantPluginAutoUpgradeStrategy.PluginCategory,
|
||||
) -> AutoUpgradeSettingsResponse:
|
||||
return {
|
||||
"strategy_setting": PluginAutoUpgradeService.default_strategy_setting_for_category(category),
|
||||
"upgrade_time_of_day": PluginAutoUpgradeService.default_upgrade_time_of_day(tenant_id),
|
||||
"upgrade_mode": TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
"exclude_plugins": [],
|
||||
"include_plugins": [],
|
||||
}
|
||||
|
||||
|
||||
def _auto_upgrade_settings_to_dict(strategy: TenantPluginAutoUpgradeStrategy) -> AutoUpgradeSettingsResponse:
|
||||
return {
|
||||
"strategy_setting": strategy.strategy_setting,
|
||||
"upgrade_time_of_day": strategy.upgrade_time_of_day,
|
||||
"upgrade_mode": strategy.upgrade_mode,
|
||||
"exclude_plugins": strategy.exclude_plugins,
|
||||
"include_plugins": strategy.include_plugins,
|
||||
}
|
||||
|
||||
|
||||
def _read_upload_content(file: FileStorage, max_size: int) -> bytes:
|
||||
"""
|
||||
Read the uploaded file and validate its actual size before delegating to the plugin service.
|
||||
@ -274,6 +433,33 @@ def _read_upload_content(file: FileStorage, max_size: int) -> bytes:
|
||||
return content
|
||||
|
||||
|
||||
def _list_hardcoded_builtin_tool_providers(tenant_id: str) -> list[dict[str, Any]]:
|
||||
db_builtin_providers = {
|
||||
str(ToolProviderID(provider.provider)): provider
|
||||
for provider in ToolManager.list_default_builtin_providers(tenant_id)
|
||||
}
|
||||
builtin_providers = []
|
||||
|
||||
for provider in ToolManager.list_hardcoded_providers():
|
||||
if is_filtered(
|
||||
include_set=dify_config.POSITION_TOOL_INCLUDES_SET,
|
||||
exclude_set=dify_config.POSITION_TOOL_EXCLUDES_SET,
|
||||
data=provider,
|
||||
name_func=lambda provider_controller: provider_controller.entity.identity.name,
|
||||
):
|
||||
continue
|
||||
|
||||
user_provider = ToolTransformService.builtin_provider_to_user_provider(
|
||||
provider_controller=provider,
|
||||
db_provider=db_builtin_providers.get(provider.entity.identity.name),
|
||||
decrypt_credentials=False,
|
||||
)
|
||||
ToolTransformService.repack_provider(tenant_id=tenant_id, provider=user_provider)
|
||||
builtin_providers.append(user_provider)
|
||||
|
||||
return [provider.to_dict() for provider in BuiltinToolProviderSort.sort(builtin_providers)]
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/debugging-key")
|
||||
class PluginDebuggingKeyApi(Resource):
|
||||
@console_ns.response(200, "Success", console_ns.models[PluginDebuggingKeyResponse.__name__])
|
||||
@ -312,6 +498,41 @@ class PluginListApi(Resource):
|
||||
return jsonable_encoder({"plugins": plugins_with_total.list, "total": plugins_with_total.total})
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/<string:category>/list")
|
||||
class PluginCategoryListApi(Resource):
|
||||
@console_ns.doc(params=query_params_from_model(PluginCategoryListQuery))
|
||||
@console_ns.response(200, "Success", console_ns.models[PluginCategoryListResponse.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, category: str):
|
||||
args = PluginCategoryListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
try:
|
||||
plugin_category = PluginCategory(category)
|
||||
except ValueError:
|
||||
return {"code": "invalid_param", "message": "invalid plugin category"}, 400
|
||||
|
||||
try:
|
||||
plugins = PluginService.list_by_category(tenant_id, plugin_category, args.page, args.page_size)
|
||||
except PluginDaemonClientSideError as e:
|
||||
return {"code": "plugin_error", "message": e.description}, 400
|
||||
|
||||
builtin_tools = []
|
||||
if plugin_category == PluginCategory.Tool:
|
||||
builtin_tools = _list_hardcoded_builtin_tool_providers(tenant_id)
|
||||
|
||||
return dump_response(
|
||||
PluginCategoryListResponse,
|
||||
{
|
||||
"plugins": jsonable_encoder(plugins.list),
|
||||
"builtin_tools": builtin_tools,
|
||||
"has_more": plugins.has_more,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/list/latest-versions")
|
||||
class PluginListLatestVersionsApi(Resource):
|
||||
@console_ns.expect(console_ns.models[ParserLatest.__name__])
|
||||
@ -713,11 +934,13 @@ class PluginChangePermissionApi(Resource):
|
||||
|
||||
args = ParserPermissionChange.model_validate(console_ns.payload)
|
||||
|
||||
return {
|
||||
"success": PluginPermissionService.change_permission(
|
||||
tenant_id, args.install_permission, args.debug_permission
|
||||
)
|
||||
}
|
||||
set_permission_result = PluginPermissionService.change_permission(
|
||||
tenant_id, args.install_permission, args.debug_permission
|
||||
)
|
||||
if not set_permission_result:
|
||||
return jsonable_encoder({"success": False, "message": "Failed to set permission"})
|
||||
|
||||
return jsonable_encoder({"success": True})
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/permission/fetch")
|
||||
@ -806,10 +1029,10 @@ class PluginFetchDynamicSelectOptionsWithCredentialsApi(Resource):
|
||||
return jsonable_encoder({"options": options})
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/preferences/change")
|
||||
class PluginChangePreferencesApi(Resource):
|
||||
@console_ns.expect(console_ns.models[ParserPreferencesChange.__name__])
|
||||
@console_ns.response(200, "Success", console_ns.models[PluginOperationSuccessResponse.__name__])
|
||||
@console_ns.route("/workspaces/current/plugin/auto-upgrade/change")
|
||||
class PluginChangeAutoUpgradeApi(Resource):
|
||||
@console_ns.expect(console_ns.models[ParserAutoUpgradeChange.__name__])
|
||||
@console_ns.response(200, "Success", console_ns.models[PluginAutoUpgradeChangeResponse.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@ -819,38 +1042,17 @@ class PluginChangePreferencesApi(Resource):
|
||||
if not user.is_admin_or_owner:
|
||||
raise Forbidden()
|
||||
|
||||
args = ParserPreferencesChange.model_validate(console_ns.payload)
|
||||
|
||||
permission = args.permission
|
||||
|
||||
install_permission = permission.install_permission
|
||||
debug_permission = permission.debug_permission
|
||||
args = ParserAutoUpgradeChange.model_validate(console_ns.payload)
|
||||
|
||||
auto_upgrade = args.auto_upgrade
|
||||
|
||||
strategy_setting = auto_upgrade.strategy_setting
|
||||
upgrade_time_of_day = auto_upgrade.upgrade_time_of_day
|
||||
upgrade_mode = auto_upgrade.upgrade_mode
|
||||
exclude_plugins = auto_upgrade.exclude_plugins
|
||||
include_plugins = auto_upgrade.include_plugins
|
||||
|
||||
# set permission
|
||||
set_permission_result = PluginPermissionService.change_permission(
|
||||
tenant_id,
|
||||
install_permission,
|
||||
debug_permission,
|
||||
)
|
||||
if not set_permission_result:
|
||||
return jsonable_encoder({"success": False, "message": "Failed to set permission"})
|
||||
|
||||
# set auto upgrade strategy
|
||||
set_auto_upgrade_strategy_result = PluginAutoUpgradeService.change_strategy(
|
||||
tenant_id,
|
||||
strategy_setting,
|
||||
upgrade_time_of_day,
|
||||
upgrade_mode,
|
||||
exclude_plugins,
|
||||
include_plugins,
|
||||
auto_upgrade.strategy_setting,
|
||||
auto_upgrade.upgrade_time_of_day,
|
||||
auto_upgrade.upgrade_mode,
|
||||
auto_upgrade.exclude_plugins,
|
||||
auto_upgrade.include_plugins,
|
||||
category=args.category,
|
||||
)
|
||||
if not set_auto_upgrade_strategy_result:
|
||||
return jsonable_encoder({"success": False, "message": "Failed to set auto upgrade strategy"})
|
||||
@ -858,49 +1060,35 @@ class PluginChangePreferencesApi(Resource):
|
||||
return jsonable_encoder({"success": True})
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/preferences/fetch")
|
||||
class PluginFetchPreferencesApi(Resource):
|
||||
@console_ns.response(200, "Success", console_ns.models[PluginPreferencesResponse.__name__])
|
||||
@console_ns.route("/workspaces/current/plugin/auto-upgrade/fetch")
|
||||
class PluginFetchAutoUpgradeApi(Resource):
|
||||
@console_ns.doc(params=query_params_from_model(ParserAutoUpgradeFetch))
|
||||
@console_ns.response(200, "Success", console_ns.models[PluginAutoUpgradeFetchResponse.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str):
|
||||
permission = PluginPermissionService.get_permission(tenant_id)
|
||||
permission_dict = {
|
||||
"install_permission": TenantPluginPermission.InstallPermission.EVERYONE,
|
||||
"debug_permission": TenantPluginPermission.DebugPermission.EVERYONE,
|
||||
}
|
||||
args = ParserAutoUpgradeFetch.model_validate(request.args.to_dict(flat=True))
|
||||
auto_upgrade = PluginAutoUpgradeService.get_strategy(tenant_id, args.category)
|
||||
auto_upgrade_dict = (
|
||||
_auto_upgrade_settings_to_dict(auto_upgrade)
|
||||
if auto_upgrade
|
||||
else _default_auto_upgrade_settings(tenant_id, args.category)
|
||||
)
|
||||
|
||||
if permission:
|
||||
permission_dict["install_permission"] = permission.install_permission
|
||||
permission_dict["debug_permission"] = permission.debug_permission
|
||||
|
||||
auto_upgrade = PluginAutoUpgradeService.get_strategy(tenant_id)
|
||||
auto_upgrade_dict = {
|
||||
"strategy_setting": TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED,
|
||||
"upgrade_time_of_day": 0,
|
||||
"upgrade_mode": TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
"exclude_plugins": [],
|
||||
"include_plugins": [],
|
||||
}
|
||||
|
||||
if auto_upgrade:
|
||||
auto_upgrade_dict = {
|
||||
"strategy_setting": auto_upgrade.strategy_setting,
|
||||
"upgrade_time_of_day": auto_upgrade.upgrade_time_of_day,
|
||||
"upgrade_mode": auto_upgrade.upgrade_mode,
|
||||
"exclude_plugins": auto_upgrade.exclude_plugins,
|
||||
"include_plugins": auto_upgrade.include_plugins,
|
||||
return jsonable_encoder(
|
||||
{
|
||||
"category": args.category,
|
||||
"auto_upgrade": auto_upgrade_dict,
|
||||
}
|
||||
|
||||
return jsonable_encoder({"permission": permission_dict, "auto_upgrade": auto_upgrade_dict})
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/preferences/autoupgrade/exclude")
|
||||
@console_ns.route("/workspaces/current/plugin/auto-upgrade/exclude")
|
||||
class PluginAutoUpgradeExcludePluginApi(Resource):
|
||||
@console_ns.expect(console_ns.models[ParserExcludePlugin.__name__])
|
||||
@console_ns.response(200, "Success", console_ns.models[PluginOperationSuccessResponse.__name__])
|
||||
@console_ns.response(200, "Success", console_ns.models[SuccessResponse.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@ -909,7 +1097,9 @@ class PluginAutoUpgradeExcludePluginApi(Resource):
|
||||
# exclude one single plugin
|
||||
args = ParserExcludePlugin.model_validate(console_ns.payload)
|
||||
|
||||
return jsonable_encoder({"success": PluginAutoUpgradeService.exclude_plugin(tenant_id, args.plugin_id)})
|
||||
return jsonable_encoder(
|
||||
{"success": PluginAutoUpgradeService.exclude_plugin(tenant_id, args.plugin_id, args.category)}
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/readme")
|
||||
|
||||
@ -31,9 +31,9 @@ from controllers.console.wraps import (
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from extensions.ext_database import db
|
||||
from fields.base import ResponseModel
|
||||
from libs.helper import TimestampField, dump_response, to_timestamp
|
||||
from libs.helper import OptionalTimestampField, TimestampField, dump_response, to_timestamp
|
||||
from libs.login import login_required
|
||||
from models.account import Account, Tenant, TenantCustomConfigDict, TenantStatus
|
||||
from models.account import Account, Tenant, TenantAccountJoin, TenantCustomConfigDict, TenantStatus
|
||||
from services.account_service import TenantService
|
||||
from services.billing_service import BillingService, SubscriptionPlan
|
||||
from services.enterprise.enterprise_service import EnterpriseService
|
||||
@ -219,6 +219,7 @@ tenants_fields = {
|
||||
"plan": fields.String,
|
||||
"status": fields.String,
|
||||
"created_at": TimestampField,
|
||||
"last_opened_at": OptionalTimestampField,
|
||||
"current": fields.Boolean,
|
||||
}
|
||||
|
||||
@ -234,7 +235,12 @@ class TenantListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def get(self, current_tenant_id: str, current_user: Account):
|
||||
tenants = TenantService.get_join_tenants(current_user)
|
||||
tenant_rows: list[tuple[Tenant, TenantAccountJoin]] = [
|
||||
(tenant, membership)
|
||||
for tenant, membership in TenantService.get_workspaces_for_account(db.session, current_user.id)
|
||||
if tenant.status == TenantStatus.NORMAL
|
||||
]
|
||||
tenants = [tenant for tenant, _ in tenant_rows]
|
||||
tenant_dicts = []
|
||||
is_enterprise_only = dify_config.ENTERPRISE_ENABLED and not dify_config.BILLING_ENABLED
|
||||
is_saas = dify_config.EDITION == "CLOUD" and dify_config.BILLING_ENABLED
|
||||
@ -247,7 +253,7 @@ class TenantListApi(Resource):
|
||||
if not tenant_plans:
|
||||
logger.warning("get_plan_bulk returned empty result, falling back to legacy feature path")
|
||||
|
||||
for tenant in tenants:
|
||||
for tenant, membership in tenant_rows:
|
||||
plan: str = CloudPlan.SANDBOX
|
||||
if is_saas:
|
||||
tenant_plan = tenant_plans.get(tenant.id)
|
||||
@ -266,6 +272,7 @@ class TenantListApi(Resource):
|
||||
"name": tenant.name,
|
||||
"status": tenant.status,
|
||||
"created_at": tenant.created_at,
|
||||
"last_opened_at": membership.last_opened_at,
|
||||
"plan": plan,
|
||||
"current": tenant.id == current_tenant_id if current_tenant_id else False,
|
||||
}
|
||||
|
||||
@ -168,6 +168,7 @@ class PluginInstallTask(BasePluginEntity):
|
||||
class PluginInstallTaskStartResponse(BaseModel):
|
||||
all_installed: bool = Field(description="Whether all plugins are installed.")
|
||||
task_id: str = Field(description="The ID of the install task.")
|
||||
task: PluginInstallTask | None = Field(default=None, description="The install task.")
|
||||
|
||||
|
||||
class PluginVerification(BaseModel):
|
||||
@ -206,6 +207,11 @@ class PluginListResponse(BaseModel):
|
||||
total: int
|
||||
|
||||
|
||||
class PluginListWithoutTotalResponse(BaseModel):
|
||||
list: list[PluginEntity]
|
||||
has_more: bool
|
||||
|
||||
|
||||
class PluginDynamicSelectOptionsResponse(BaseModel):
|
||||
options: Sequence[PluginParameterOption] = Field(description="The options of the dynamic select.")
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ from requests import HTTPError
|
||||
from core.plugin.entities.bundle import PluginBundleDependency
|
||||
from core.plugin.entities.plugin import (
|
||||
MissingPluginDependency,
|
||||
PluginCategory,
|
||||
PluginDeclaration,
|
||||
PluginEntity,
|
||||
PluginInstallation,
|
||||
@ -16,6 +17,7 @@ from core.plugin.entities.plugin_daemon import (
|
||||
PluginInstallTask,
|
||||
PluginInstallTaskStartResponse,
|
||||
PluginListResponse,
|
||||
PluginListWithoutTotalResponse,
|
||||
PluginReadmeResponse,
|
||||
)
|
||||
from core.plugin.impl.base import BasePluginClient
|
||||
@ -74,6 +76,16 @@ class PluginInstaller(BasePluginClient):
|
||||
params={"page": page, "page_size": page_size, "response_type": "paged"},
|
||||
)
|
||||
|
||||
def list_plugins_by_category(
|
||||
self, tenant_id: str, category: PluginCategory, page: int, page_size: int
|
||||
) -> PluginListWithoutTotalResponse:
|
||||
return self._request_with_plugin_daemon_response(
|
||||
"GET",
|
||||
f"plugin/{tenant_id}/management/{category.value}/list",
|
||||
PluginListWithoutTotalResponse,
|
||||
params={"page": page, "page_size": page_size, "response_type": "paged"},
|
||||
)
|
||||
|
||||
def upload_pkg(
|
||||
self,
|
||||
tenant_id: str,
|
||||
|
||||
@ -31,6 +31,7 @@ from core.helper.marketplace import download_plugin_pkg
|
||||
from core.helper.model_provider_cache import ProviderCredentialsCache, ProviderCredentialsCacheType
|
||||
from core.plugin.entities.bundle import PluginBundleDependency
|
||||
from core.plugin.entities.plugin import (
|
||||
PluginCategory,
|
||||
PluginDeclaration,
|
||||
PluginEntity,
|
||||
PluginInstallation,
|
||||
@ -41,6 +42,7 @@ from core.plugin.entities.plugin_daemon import (
|
||||
PluginInstallTask,
|
||||
PluginInstallTaskStatus,
|
||||
PluginListResponse,
|
||||
PluginListWithoutTotalResponse,
|
||||
PluginModelProviderEntity,
|
||||
PluginVerification,
|
||||
)
|
||||
@ -437,6 +439,19 @@ class PluginService:
|
||||
PluginService._reconcile_endpoint_counts(tenant_id, user_id, plugins.list)
|
||||
return plugins
|
||||
|
||||
@staticmethod
|
||||
def list_by_category(
|
||||
tenant_id: str, category: PluginCategory, page: int, page_size: int
|
||||
) -> PluginListWithoutTotalResponse:
|
||||
"""
|
||||
List plugins in one category with a has-more cursor signal and without calculating total.
|
||||
|
||||
The daemon scans tenant installations in the existing list order and stops once it finds one extra match.
|
||||
This keeps pagination usable before category is persisted on installation rows.
|
||||
"""
|
||||
manager = PluginInstaller()
|
||||
return manager.list_plugins_by_category(tenant_id, category, page, page_size)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_endpoint_count(value: object) -> int:
|
||||
"""Convert daemon endpoint counters to safe non-negative integers.
|
||||
|
||||
@ -5,6 +5,7 @@ def init_app(app: DifyApp):
|
||||
from commands import (
|
||||
add_qdrant_index,
|
||||
archive_workflow_runs,
|
||||
backfill_plugin_auto_upgrade,
|
||||
clean_expired_messages,
|
||||
clean_workflow_runs,
|
||||
cleanup_orphaned_draft_variables,
|
||||
@ -53,6 +54,7 @@ def init_app(app: DifyApp):
|
||||
upgrade_db,
|
||||
fix_app_site_missing,
|
||||
migrate_data_for_plugin,
|
||||
backfill_plugin_auto_upgrade,
|
||||
extract_plugins,
|
||||
extract_unique_plugins,
|
||||
install_plugins,
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
"""add tenant account join last opened at
|
||||
|
||||
Revision ID: b7c2d9e8a1f4
|
||||
Revises: 9f4b7c2d1a80
|
||||
Create Date: 2026-06-05 11:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "b7c2d9e8a1f4"
|
||||
down_revision = "9f4b7c2d1a80"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table("tenant_account_joins", schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column("last_opened_at", sa.DateTime(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table("tenant_account_joins", schema=None) as batch_op:
|
||||
batch_op.drop_column("last_opened_at")
|
||||
@ -0,0 +1,42 @@
|
||||
"""add plugin auto upgrade category
|
||||
|
||||
Revision ID: f6a7b8c9d012
|
||||
Revises: b7c2d9e8a1f4
|
||||
Create Date: 2026-05-15 12:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "f6a7b8c9d012"
|
||||
down_revision = "b7c2d9e8a1f4"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
LEGACY_CATEGORY = "tool"
|
||||
UNIQUE_CONSTRAINT_NAME = "unique_tenant_plugin_auto_upgrade_strategy"
|
||||
UPGRADE_TIME_INDEX_NAME = "idx_tenant_plugin_auto_upgrade_strategy_time"
|
||||
STRATEGY_TABLE_NAME = "tenant_plugin_auto_upgrade_strategies"
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table(STRATEGY_TABLE_NAME, schema=None) as batch_op:
|
||||
batch_op.add_column(
|
||||
sa.Column("category", sa.String(length=32), server_default=LEGACY_CATEGORY, nullable=False)
|
||||
)
|
||||
batch_op.drop_constraint(UNIQUE_CONSTRAINT_NAME, type_="unique")
|
||||
batch_op.create_unique_constraint(UNIQUE_CONSTRAINT_NAME, ["tenant_id", "category"])
|
||||
batch_op.create_index(UPGRADE_TIME_INDEX_NAME, ["upgrade_time_of_day"])
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute(sa.text(f"DELETE FROM {STRATEGY_TABLE_NAME} WHERE category != '{LEGACY_CATEGORY}'"))
|
||||
|
||||
with op.batch_alter_table(STRATEGY_TABLE_NAME, schema=None) as batch_op:
|
||||
batch_op.drop_index(UPGRADE_TIME_INDEX_NAME)
|
||||
batch_op.drop_constraint(UNIQUE_CONSTRAINT_NAME, type_="unique")
|
||||
batch_op.drop_column("category")
|
||||
batch_op.create_unique_constraint(UNIQUE_CONSTRAINT_NAME, ["tenant_id"])
|
||||
@ -0,0 +1,26 @@
|
||||
"""add learn dify flag to recommended apps
|
||||
|
||||
Revision ID: f5e8a9c0d2b3
|
||||
Revises: f6a7b8c9d012
|
||||
Create Date: 2026-05-18 15:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "f5e8a9c0d2b3"
|
||||
down_revision = "f6a7b8c9d012"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table("recommended_apps", schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column("is_learn_dify", sa.Boolean(), server_default=sa.text("false"), nullable=False))
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table("recommended_apps", schema=None) as batch_op:
|
||||
batch_op.drop_column("is_learn_dify")
|
||||
@ -0,0 +1,38 @@
|
||||
"""add app stars
|
||||
|
||||
Revision ID: c4d5e6f7a8b9
|
||||
Revises: f5e8a9c0d2b3
|
||||
Create Date: 2026-06-08 12:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
import models.types
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "c4d5e6f7a8b9"
|
||||
down_revision = "f5e8a9c0d2b3"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"app_stars",
|
||||
sa.Column("id", models.types.StringUUID(), nullable=False),
|
||||
sa.Column("tenant_id", models.types.StringUUID(), nullable=False),
|
||||
sa.Column("app_id", models.types.StringUUID(), nullable=False),
|
||||
sa.Column("account_id", models.types.StringUUID(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.current_timestamp(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id", name="app_star_pkey"),
|
||||
sa.UniqueConstraint("tenant_id", "account_id", "app_id", name="app_star_tenant_account_app_unique"),
|
||||
)
|
||||
with op.batch_alter_table("app_stars", schema=None) as batch_op:
|
||||
batch_op.create_index("app_star_tenant_account_idx", ["tenant_id", "account_id"], unique=False)
|
||||
batch_op.create_index("app_star_app_idx", ["app_id"], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("app_stars")
|
||||
@ -73,6 +73,7 @@ from .model import (
|
||||
AppMCPServer,
|
||||
AppMode,
|
||||
AppModelConfig,
|
||||
AppStar,
|
||||
Conversation,
|
||||
DatasetRetrieverResource,
|
||||
DifySetup,
|
||||
@ -175,6 +176,7 @@ __all__ = [
|
||||
"AppMCPServer",
|
||||
"AppMode",
|
||||
"AppModelConfig",
|
||||
"AppStar",
|
||||
"AppTrigger",
|
||||
"AppTriggerStatus",
|
||||
"AppTriggerType",
|
||||
|
||||
@ -301,6 +301,7 @@ class TenantAccountJoin(TypeBase):
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, server_default=func.current_timestamp(), nullable=False, init=False, onupdate=func.current_timestamp()
|
||||
)
|
||||
last_opened_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None)
|
||||
|
||||
|
||||
class AccountIntegrate(TypeBase):
|
||||
@ -389,6 +390,14 @@ class TenantPluginPermission(TypeBase):
|
||||
|
||||
|
||||
class TenantPluginAutoUpgradeStrategy(TypeBase):
|
||||
class PluginCategory(enum.StrEnum):
|
||||
TOOL = "tool"
|
||||
MODEL = "model"
|
||||
EXTENSION = "extension"
|
||||
AGENT_STRATEGY = "agent-strategy"
|
||||
DATASOURCE = "datasource"
|
||||
TRIGGER = "trigger"
|
||||
|
||||
class StrategySetting(enum.StrEnum):
|
||||
DISABLED = "disabled"
|
||||
FIX_ONLY = "fix_only"
|
||||
@ -402,13 +411,20 @@ class TenantPluginAutoUpgradeStrategy(TypeBase):
|
||||
__tablename__ = "tenant_plugin_auto_upgrade_strategies"
|
||||
__table_args__ = (
|
||||
sa.PrimaryKeyConstraint("id", name="tenant_plugin_auto_upgrade_strategy_pkey"),
|
||||
sa.UniqueConstraint("tenant_id", name="unique_tenant_plugin_auto_upgrade_strategy"),
|
||||
sa.UniqueConstraint("tenant_id", "category", name="unique_tenant_plugin_auto_upgrade_strategy"),
|
||||
sa.Index("idx_tenant_plugin_auto_upgrade_strategy_time", "upgrade_time_of_day"),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(
|
||||
StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False
|
||||
)
|
||||
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
category: Mapped[PluginCategory] = mapped_column(
|
||||
EnumText(PluginCategory, length=32),
|
||||
nullable=False,
|
||||
server_default="tool",
|
||||
default=PluginCategory.TOOL,
|
||||
)
|
||||
strategy_setting: Mapped[StrategySetting] = mapped_column(
|
||||
EnumText(StrategySetting, length=16),
|
||||
nullable=False,
|
||||
|
||||
@ -397,6 +397,12 @@ class App(Base):
|
||||
__tablename__ = "apps"
|
||||
__table_args__ = (sa.PrimaryKeyConstraint("id", name="app_pkey"), sa.Index("app_tenant_id_idx", "tenant_id"))
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Response-only attributes attached by app list/detail enrichers.
|
||||
access_mode: str | None
|
||||
has_draft_trigger: bool
|
||||
is_starred: bool
|
||||
|
||||
id: Mapped[str] = mapped_column(StringUUID, default=lambda: str(uuid4()))
|
||||
tenant_id: Mapped[str] = mapped_column(StringUUID)
|
||||
name: Mapped[str] = mapped_column(String(255))
|
||||
@ -654,6 +660,28 @@ class App(Base):
|
||||
return None
|
||||
|
||||
|
||||
class AppStar(Base):
|
||||
"""Account-scoped star marker for apps in a workspace."""
|
||||
|
||||
__tablename__ = "app_stars"
|
||||
__table_args__ = (
|
||||
sa.PrimaryKeyConstraint("id", name="app_star_pkey"),
|
||||
sa.UniqueConstraint("tenant_id", "account_id", "app_id", name="app_star_tenant_account_app_unique"),
|
||||
sa.Index("app_star_tenant_account_idx", "tenant_id", "account_id"),
|
||||
sa.Index("app_star_app_idx", "app_id"),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(StringUUID, default=lambda: str(uuidv7()))
|
||||
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
account_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return f"<AppStar app_id={self.app_id} account_id={self.account_id}>"
|
||||
|
||||
|
||||
class AppModelConfig(TypeBase):
|
||||
__tablename__ = "app_model_configs"
|
||||
__table_args__ = (sa.PrimaryKeyConstraint("id", name="app_model_config_pkey"), sa.Index("app_app_id_idx", "app_id"))
|
||||
@ -907,6 +935,9 @@ class RecommendedApp(TypeBase):
|
||||
custom_disclaimer: Mapped[str] = mapped_column(LongText, default="")
|
||||
position: Mapped[int] = mapped_column(sa.Integer, nullable=False, default=0)
|
||||
is_listed: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=True)
|
||||
is_learn_dify: Mapped[bool] = mapped_column(
|
||||
sa.Boolean, nullable=False, server_default=sa.text("false"), default=False
|
||||
)
|
||||
install_count: Mapped[int] = mapped_column(sa.Integer, nullable=False, default=0)
|
||||
language: Mapped[str] = mapped_column(
|
||||
String(255),
|
||||
|
||||
@ -531,6 +531,7 @@ Get list of applications with pagination and filtering
|
||||
| mode | query | App mode filter | No | string, <br>**Available values:** "advanced-chat", "agent", "agent-chat", "all", "channel", "chat", "completion", "workflow", <br>**Default:** all |
|
||||
| name | query | Filter by app name | No | string |
|
||||
| page | query | Page number (1-99999) | No | integer, <br>**Default:** 1 |
|
||||
| sort_by | query | Sort apps by last modified, recently created, or earliest created | No | string, <br>**Available values:** "earliest_created", "last_modified", "recently_created", <br>**Default:** last_modified |
|
||||
| tag_ids | query | Filter by tag IDs | No | [ string ] |
|
||||
|
||||
#### Responses
|
||||
@ -600,6 +601,28 @@ Create a new application
|
||||
| 200 | Import confirmed | **application/json**: [Import](#import)<br> |
|
||||
| 400 | Import failed | **application/json**: [Import](#import)<br> |
|
||||
|
||||
### [GET] /apps/starred
|
||||
Get applications starred by the current account
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| creator_ids | query | Filter by creator account IDs | No | [ string ] |
|
||||
| is_created_by_me | query | Filter by creator | No | boolean |
|
||||
| limit | query | Page size (1-100) | No | integer, <br>**Default:** 20 |
|
||||
| mode | query | App mode filter | No | string, <br>**Available values:** "advanced-chat", "agent", "agent-chat", "all", "channel", "chat", "completion", "workflow", <br>**Default:** all |
|
||||
| name | query | Filter by app name | No | string |
|
||||
| page | query | Page number (1-99999) | No | integer, <br>**Default:** 1 |
|
||||
| sort_by | query | Sort apps by last modified, recently created, or earliest created | No | string, <br>**Available values:** "earliest_created", "last_modified", "recently_created", <br>**Default:** last_modified |
|
||||
| tag_ids | query | Filter by tag IDs | No | [ string ] |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [AppPagination](#apppagination)<br> |
|
||||
|
||||
### [POST] /apps/workflows/online-users
|
||||
Get workflow online users
|
||||
|
||||
@ -2045,6 +2068,38 @@ Reset access token for application site
|
||||
| 403 | Insufficient permissions (admin/owner required) | |
|
||||
| 404 | App or site not found | |
|
||||
|
||||
### [DELETE] /apps/{app_id}/star
|
||||
Remove the current account's star from an application
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| app_id | path | Application ID | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
||||
| 404 | App not found | |
|
||||
|
||||
### [POST] /apps/{app_id}/star
|
||||
Star an application for the current account
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| app_id | path | Application ID | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
||||
| 404 | App not found | |
|
||||
|
||||
### [GET] /apps/{app_id}/statistics/average-response-time
|
||||
Get average response time statistics for an application
|
||||
|
||||
@ -5573,6 +5628,19 @@ Check if dataset is in use
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [RecommendedAppListResponse](#recommendedapplistresponse)<br> |
|
||||
|
||||
### [GET] /explore/apps/learn-dify
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| language | query | Language code for recommended app localization | No | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [LearnDifyAppListResponse](#learndifyapplistresponse)<br> |
|
||||
|
||||
### [GET] /explore/apps/{app_id}
|
||||
#### Parameters
|
||||
|
||||
@ -9391,6 +9459,45 @@ Returns permission flags that control workspace features like member invitations
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [BinaryFileResponse](#binaryfileresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/plugin/auto-upgrade/change
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [ParserAutoUpgradeChange](#parserautoupgradechange)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [PluginAutoUpgradeChangeResponse](#pluginautoupgradechangeresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/plugin/auto-upgrade/exclude
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [ParserExcludePlugin](#parserexcludeplugin)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [SuccessResponse](#successresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/plugin/auto-upgrade/fetch
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| category | query | | Yes | string, <br>**Available values:** "agent-strategy", "datasource", "extension", "model", "tool", "trigger" |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [PluginAutoUpgradeFetchResponse](#pluginautoupgradefetchresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/plugin/debugging-key
|
||||
#### Responses
|
||||
|
||||
@ -9570,39 +9677,6 @@ Returns permission flags that control workspace features like member invitations
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [PluginPermissionResponse](#pluginpermissionresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/plugin/preferences/autoupgrade/exclude
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [ParserExcludePlugin](#parserexcludeplugin)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [PluginOperationSuccessResponse](#pluginoperationsuccessresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/plugin/preferences/change
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [ParserPreferencesChange](#parserpreferenceschange)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [PluginOperationSuccessResponse](#pluginoperationsuccessresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/plugin/preferences/fetch
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [PluginPreferencesResponse](#pluginpreferencesresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/plugin/readme
|
||||
#### Parameters
|
||||
|
||||
@ -9744,6 +9818,21 @@ Returns permission flags that control workspace features like member invitations
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [PluginDaemonOperationResponse](#plugindaemonoperationresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/plugin/{category}/list
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| page | query | Page number | No | integer, <br>**Default:** 1 |
|
||||
| page_size | query | Page size (1-256) | No | integer, <br>**Default:** 256 |
|
||||
| category | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [PluginCategoryListResponse](#plugincategorylistresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/tool-labels
|
||||
#### Responses
|
||||
|
||||
@ -10641,7 +10730,7 @@ Default namespace
|
||||
| deprecated | boolean | | No |
|
||||
| features | [ [ModelFeature](#modelfeature) ] | | No |
|
||||
| fetch_from | [FetchFrom](#fetchfrom) | | Yes |
|
||||
| label | [I18nObject](#i18nobject) | | Yes |
|
||||
| label | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | Yes |
|
||||
| model | string | | Yes |
|
||||
| model_properties | object | | Yes |
|
||||
| model_type | [ModelType](#modeltype) | | Yes |
|
||||
@ -12167,6 +12256,7 @@ Enum class for api provider schema type.
|
||||
| mode | string, <br>**Available values:** "advanced-chat", "agent", "agent-chat", "all", "channel", "chat", "completion", "workflow", <br>**Default:** all | App mode filter<br>*Enum:* `"advanced-chat"`, `"agent"`, `"agent-chat"`, `"all"`, `"channel"`, `"chat"`, `"completion"`, `"workflow"` | No |
|
||||
| name | string | Filter by app name | No |
|
||||
| page | integer, <br>**Default:** 1 | Page number (1-99999) | No |
|
||||
| sort_by | string, <br>**Available values:** "earliest_created", "last_modified", "recently_created", <br>**Default:** last_modified | Sort apps by last modified, recently created, or earliest created<br>*Enum:* `"earliest_created"`, `"last_modified"`, `"recently_created"` | No |
|
||||
| tag_ids | [ string ] | Filter by tag IDs | No |
|
||||
|
||||
#### AppMCPServerResponse
|
||||
@ -12222,6 +12312,7 @@ AppMCPServer Status Enum
|
||||
| icon_background | string | | No |
|
||||
| icon_type | string | | No |
|
||||
| id | string | | Yes |
|
||||
| is_starred | boolean | | No |
|
||||
| max_active_requests | integer | | No |
|
||||
| mode_compatible_with_agent | string | | Yes |
|
||||
| name | string | | Yes |
|
||||
@ -13050,10 +13141,10 @@ Model class for credential form schema.
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| default | string | | No |
|
||||
| label | [I18nObject](#i18nobject) | | Yes |
|
||||
| label | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | Yes |
|
||||
| max_length | integer | | No |
|
||||
| options | [ [FormOption](#formoption) ] | | No |
|
||||
| placeholder | [I18nObject](#i18nobject) | | No |
|
||||
| placeholder | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | No |
|
||||
| required | boolean, <br>**Default:** true | | No |
|
||||
| show_on | [ [FormShowOnObject](#formshowonobject) ], <br>**Default:** | | No |
|
||||
| type | [FormType](#formtype) | | Yes |
|
||||
@ -14473,8 +14564,8 @@ Enum class for fetch from.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| label | [I18nObject](#i18nobject) | | Yes |
|
||||
| placeholder | [I18nObject](#i18nobject) | | No |
|
||||
| label | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | Yes |
|
||||
| placeholder | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | No |
|
||||
|
||||
#### FileInfo
|
||||
|
||||
@ -14605,7 +14696,7 @@ Model class for form option.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| label | [I18nObject](#i18nobject) | | Yes |
|
||||
| label | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | Yes |
|
||||
| show_on | [ [FormShowOnObject](#formshowonobject) ], <br>**Default:** | | No |
|
||||
| value | string | | Yes |
|
||||
|
||||
@ -14837,6 +14928,8 @@ Model class for i18n object.
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| en_US | string | | Yes |
|
||||
| ja_JP | string | | No |
|
||||
| pt_BR | string | | No |
|
||||
| zh_Hans | string | | No |
|
||||
|
||||
#### IconInfo
|
||||
@ -15098,6 +15191,12 @@ Enum class for large language model mode.
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| LLMMode | string | Enum class for large language model mode. | |
|
||||
|
||||
#### LearnDifyAppListResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| recommended_apps | [ [RecommendedAppResponse](#recommendedappresponse) ] | | Yes |
|
||||
|
||||
#### LegacyEndpointUpdatePayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -15932,8 +16031,8 @@ Model class for parameter rule.
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| default | | | No |
|
||||
| help | [I18nObject](#i18nobject) | | No |
|
||||
| label | [I18nObject](#i18nobject) | | Yes |
|
||||
| help | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | No |
|
||||
| label | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | Yes |
|
||||
| max | number | | No |
|
||||
| min | number | | No |
|
||||
| name | string | | Yes |
|
||||
@ -15983,6 +16082,19 @@ Enum class for parameter type.
|
||||
| file_name | string | | Yes |
|
||||
| plugin_unique_identifier | string | | Yes |
|
||||
|
||||
#### ParserAutoUpgradeChange
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| auto_upgrade | [PluginAutoUpgradeSettingsPayload](#pluginautoupgradesettingspayload) | | Yes |
|
||||
| category | [PluginCategory](#plugincategory) | | Yes |
|
||||
|
||||
#### ParserAutoUpgradeFetch
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| category | [PluginCategory](#plugincategory) | | Yes |
|
||||
|
||||
#### ParserCreateCredential
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -16079,6 +16191,7 @@ Enum class for parameter type.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| category | [PluginCategory](#plugincategory) | | Yes |
|
||||
| plugin_id | string | | Yes |
|
||||
|
||||
#### ParserGetCredentials
|
||||
@ -16166,8 +16279,8 @@ Enum class for parameter type.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| debug_permission | [DebugPermission](#debugpermission) | | Yes |
|
||||
| install_permission | [InstallPermission](#installpermission) | | Yes |
|
||||
| debug_permission | [DebugPermission](#debugpermission) | | No |
|
||||
| install_permission | [InstallPermission](#installpermission) | | No |
|
||||
|
||||
#### ParserPluginIdentifierQuery
|
||||
|
||||
@ -16197,13 +16310,6 @@ Enum class for parameter type.
|
||||
| model | string | | Yes |
|
||||
| model_type | [ModelType](#modeltype) | | Yes |
|
||||
|
||||
#### ParserPreferencesChange
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| auto_upgrade | [PluginAutoUpgradeSettingsPayload](#pluginautoupgradesettingspayload) | | Yes |
|
||||
| permission | [PluginPermissionSettingsPayload](#pluginpermissionsettingspayload) | | Yes |
|
||||
|
||||
#### ParserPreferredProviderType
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -16348,6 +16454,20 @@ Shared permission levels for resources (datasets, credentials, etc.)
|
||||
| unit | string | | No |
|
||||
| variable | string | | Yes |
|
||||
|
||||
#### PluginAutoUpgradeChangeResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| message | string | | No |
|
||||
| success | boolean | | Yes |
|
||||
|
||||
#### PluginAutoUpgradeFetchResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| auto_upgrade | [PluginAutoUpgradeSettingsResponseModel](#pluginautoupgradesettingsresponsemodel) | | Yes |
|
||||
| category | [PluginCategory](#plugincategory) | | Yes |
|
||||
|
||||
#### PluginAutoUpgradeSettingsPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -16358,6 +16478,90 @@ Shared permission levels for resources (datasets, credentials, etc.)
|
||||
| upgrade_mode | [UpgradeMode](#upgrademode) | | No |
|
||||
| upgrade_time_of_day | integer | | No |
|
||||
|
||||
#### PluginAutoUpgradeSettingsResponseModel
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| exclude_plugins | [ string ] | | Yes |
|
||||
| include_plugins | [ string ] | | Yes |
|
||||
| strategy_setting | [StrategySetting](#strategysetting) | | Yes |
|
||||
| upgrade_mode | [UpgradeMode](#upgrademode) | | Yes |
|
||||
| upgrade_time_of_day | integer | | Yes |
|
||||
|
||||
#### PluginCategory
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| PluginCategory | string | | |
|
||||
|
||||
#### PluginCategoryBuiltinToolProviderResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| allow_delete | boolean | | Yes |
|
||||
| author | string | | Yes |
|
||||
| description | [core__tools__entities__common_entities__I18nObject](#core__tools__entities__common_entities__i18nobject) | | Yes |
|
||||
| icon | string<br>object | | Yes |
|
||||
| icon_dark | string<br>object | | Yes |
|
||||
| id | string | | Yes |
|
||||
| is_team_authorization | boolean | | Yes |
|
||||
| label | [core__tools__entities__common_entities__I18nObject](#core__tools__entities__common_entities__i18nobject) | | Yes |
|
||||
| labels | [ string ] | | Yes |
|
||||
| name | string | | Yes |
|
||||
| plugin_id | string | | Yes |
|
||||
| plugin_unique_identifier | string | | Yes |
|
||||
| team_credentials | object | | Yes |
|
||||
| tools | [ [PluginCategoryBuiltinToolResponse](#plugincategorybuiltintoolresponse) ] | | Yes |
|
||||
| type | [ToolProviderType](#toolprovidertype) | | Yes |
|
||||
|
||||
#### PluginCategoryBuiltinToolResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| author | string | | Yes |
|
||||
| description | [core__tools__entities__common_entities__I18nObject](#core__tools__entities__common_entities__i18nobject) | | Yes |
|
||||
| label | [core__tools__entities__common_entities__I18nObject](#core__tools__entities__common_entities__i18nobject) | | Yes |
|
||||
| labels | [ string ] | | Yes |
|
||||
| name | string | | Yes |
|
||||
| output_schema | object | | Yes |
|
||||
| parameters | [ object ] | | No |
|
||||
|
||||
#### PluginCategoryInstalledPluginResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| checksum | string | | Yes |
|
||||
| created_at | dateTime | | Yes |
|
||||
| declaration | [PluginDeclarationResponse](#plugindeclarationresponse) | | Yes |
|
||||
| endpoints_active | integer | | Yes |
|
||||
| endpoints_setups | integer | | Yes |
|
||||
| id | string | | Yes |
|
||||
| installation_id | string | | Yes |
|
||||
| meta | object | | Yes |
|
||||
| name | string | | Yes |
|
||||
| plugin_id | string | | Yes |
|
||||
| plugin_unique_identifier | string | | Yes |
|
||||
| runtime_type | string | | Yes |
|
||||
| source | [PluginInstallationSource](#plugininstallationsource) | | Yes |
|
||||
| tenant_id | string | | Yes |
|
||||
| updated_at | dateTime | | Yes |
|
||||
| version | string | | Yes |
|
||||
|
||||
#### PluginCategoryListQuery
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| page | integer, <br>**Default:** 1 | Page number | No |
|
||||
| page_size | integer, <br>**Default:** 256 | Page size (1-256) | No |
|
||||
|
||||
#### PluginCategoryListResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| builtin_tools | [ [PluginCategoryBuiltinToolProviderResponse](#plugincategorybuiltintoolproviderresponse) ] | | Yes |
|
||||
| has_more | boolean | | Yes |
|
||||
| plugins | [ [PluginCategoryInstalledPluginResponse](#plugincategoryinstalledpluginresponse) ] | | Yes |
|
||||
|
||||
#### PluginDaemonOperationResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -16372,6 +16576,32 @@ Shared permission levels for resources (datasets, credentials, etc.)
|
||||
| key | string | | Yes |
|
||||
| port | integer | | Yes |
|
||||
|
||||
#### PluginDeclarationResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| agent_strategy | object | | No |
|
||||
| author | string | | Yes |
|
||||
| category | [PluginCategory](#plugincategory) | | Yes |
|
||||
| created_at | dateTime | | Yes |
|
||||
| datasource | object | | No |
|
||||
| description | [core__tools__entities__common_entities__I18nObject](#core__tools__entities__common_entities__i18nobject) | | Yes |
|
||||
| endpoint | object | | No |
|
||||
| icon | string | | Yes |
|
||||
| icon_dark | string | | No |
|
||||
| label | [core__tools__entities__common_entities__I18nObject](#core__tools__entities__common_entities__i18nobject) | | Yes |
|
||||
| meta | object | | Yes |
|
||||
| model | [ProviderEntityResponse](#providerentityresponse) | | No |
|
||||
| name | string | | Yes |
|
||||
| plugins | object | | Yes |
|
||||
| repo | string | | No |
|
||||
| resource | object | | Yes |
|
||||
| tags | [ string ] | | No |
|
||||
| tool | object | | No |
|
||||
| trigger | object | | No |
|
||||
| verified | boolean | | No |
|
||||
| version | string | | Yes |
|
||||
|
||||
#### PluginDependency
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -16405,6 +16635,12 @@ Shared permission levels for resources (datasets, credentials, etc.)
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| PluginInstallationScope | string | | |
|
||||
|
||||
#### PluginInstallationSource
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| PluginInstallationSource | string | | |
|
||||
|
||||
#### PluginInstallationsResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -16457,13 +16693,6 @@ Shared permission levels for resources (datasets, credentials, etc.)
|
||||
| debug_permission | [DebugPermission](#debugpermission) | | No |
|
||||
| install_permission | [InstallPermission](#installpermission) | | No |
|
||||
|
||||
#### PluginPreferencesResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| auto_upgrade | [PluginAutoUpgradeSettingsPayload](#pluginautoupgradesettingspayload) | | Yes |
|
||||
| permission | [PluginPermissionSettingsPayload](#pluginpermissionsettingspayload) | | Yes |
|
||||
|
||||
#### PluginReadmeResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -16550,14 +16779,35 @@ Model class for provider credential schema.
|
||||
| error | string | | No |
|
||||
| result | string, <br>**Available values:** "error", "success" | *Enum:* `"error"`, `"success"` | Yes |
|
||||
|
||||
#### ProviderEntityResponse
|
||||
|
||||
Runtime provider response with codegen-safe model pricing schemas.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| background | string | | No |
|
||||
| configurate_methods | [ [ConfigurateMethod](#configuratemethod) ] | | Yes |
|
||||
| description | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | No |
|
||||
| help | [ProviderHelpEntity](#providerhelpentity) | | No |
|
||||
| icon_small | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | No |
|
||||
| icon_small_dark | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | No |
|
||||
| label | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | Yes |
|
||||
| model_credential_schema | [ModelCredentialSchema](#modelcredentialschema) | | No |
|
||||
| models | [ [AIModelEntityResponse](#aimodelentityresponse) ], <br>**Default:** | | No |
|
||||
| position | object | | No |
|
||||
| provider | string | | Yes |
|
||||
| provider_credential_schema | [ProviderCredentialSchema](#providercredentialschema) | | No |
|
||||
| provider_name | string | | No |
|
||||
| supported_model_types | [ [ModelType](#modeltype) ] | | Yes |
|
||||
|
||||
#### ProviderHelpEntity
|
||||
|
||||
Model class for provider help.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| title | [I18nObject](#i18nobject) | | Yes |
|
||||
| url | [I18nObject](#i18nobject) | | Yes |
|
||||
| title | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | Yes |
|
||||
| url | [graphon__model_runtime__entities__common_entities__I18nObject](#graphon__model_runtime__entities__common_entities__i18nobject) | | Yes |
|
||||
|
||||
#### ProviderModelWithStatusEntity
|
||||
|
||||
@ -17473,6 +17723,19 @@ Query parameters for listing snippet published workflows.
|
||||
| updated_by | [SimpleAccount](#simpleaccount) | | No |
|
||||
| version | string | | Yes |
|
||||
|
||||
#### StarredAppListQuery
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| creator_ids | [ string ] | Filter by creator account IDs | No |
|
||||
| is_created_by_me | boolean | Filter by creator | No |
|
||||
| limit | integer, <br>**Default:** 20 | Page size (1-100) | No |
|
||||
| mode | string, <br>**Available values:** "advanced-chat", "agent", "agent-chat", "all", "channel", "chat", "completion", "workflow", <br>**Default:** all | App mode filter<br>*Enum:* `"advanced-chat"`, `"agent"`, `"agent-chat"`, `"all"`, `"channel"`, `"chat"`, `"completion"`, `"workflow"` | No |
|
||||
| name | string | Filter by app name | No |
|
||||
| page | integer, <br>**Default:** 1 | Page number (1-99999) | No |
|
||||
| sort_by | string, <br>**Available values:** "earliest_created", "last_modified", "recently_created", <br>**Default:** last_modified | Sort apps by last modified, recently created, or earliest created<br>*Enum:* `"earliest_created"`, `"last_modified"`, `"recently_created"` | No |
|
||||
| tag_ids | [ string ] | Filter by tag IDs | No |
|
||||
|
||||
#### StatisticTimeRangeQuery
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -17817,6 +18080,14 @@ Tag type
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| ToolProviderOpaqueResponse | | | |
|
||||
|
||||
#### ToolProviderType
|
||||
|
||||
Enum class for tool provider
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| ToolProviderType | string | Enum class for tool provider | |
|
||||
|
||||
#### TraceAppConfigResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@ -19236,6 +19507,26 @@ Workflow tool configuration
|
||||
| use_count | integer | | No |
|
||||
| version | integer | | No |
|
||||
|
||||
#### core__tools__entities__common_entities__I18nObject
|
||||
|
||||
Model class for i18n object.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| en_US | string | | Yes |
|
||||
| ja_JP | string | | No |
|
||||
| pt_BR | string | | No |
|
||||
| zh_Hans | string | | No |
|
||||
|
||||
#### graphon__model_runtime__entities__common_entities__I18nObject
|
||||
|
||||
Model class for i18n object.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| en_US | string | | Yes |
|
||||
| zh_Hans | string | | No |
|
||||
|
||||
## FastOpenAPI Preview (OpenAPI 3.1)
|
||||
|
||||
### Dify API (FastOpenAPI PoC)
|
||||
|
||||
@ -73,6 +73,7 @@ def check_upgradable_plugin_task():
|
||||
strategy.upgrade_mode,
|
||||
strategy.exclude_plugins,
|
||||
strategy.include_plugins,
|
||||
strategy.category,
|
||||
)
|
||||
|
||||
# Only sleep if batch_interval_time > 0.0001 AND current batch is not the last one
|
||||
|
||||
@ -70,6 +70,7 @@ from services.errors.account import (
|
||||
)
|
||||
from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError
|
||||
from services.feature_service import FeatureService
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
from tasks.delete_account_task import delete_account_task
|
||||
from tasks.mail_account_deletion_task import send_account_deletion_verification_code
|
||||
from tasks.mail_change_mail_task import (
|
||||
@ -263,6 +264,7 @@ class AccountService:
|
||||
|
||||
account.set_tenant_id(available_ta.tenant_id)
|
||||
available_ta.current = True
|
||||
available_ta.last_opened_at = naive_utc_now()
|
||||
db.session.commit()
|
||||
|
||||
AccountService._refresh_account_last_active(account)
|
||||
@ -1167,15 +1169,17 @@ class TenantService:
|
||||
db.session.add(tenant)
|
||||
db.session.commit()
|
||||
|
||||
plugin_upgrade_strategy = TenantPluginAutoUpgradeStrategy(
|
||||
tenant_id=tenant.id,
|
||||
strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
upgrade_time_of_day=0,
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
exclude_plugins=[],
|
||||
include_plugins=[],
|
||||
)
|
||||
db.session.add(plugin_upgrade_strategy)
|
||||
for category in TenantPluginAutoUpgradeStrategy.PluginCategory:
|
||||
plugin_upgrade_strategy = TenantPluginAutoUpgradeStrategy(
|
||||
tenant_id=tenant.id,
|
||||
category=category,
|
||||
strategy_setting=PluginAutoUpgradeService.default_strategy_setting_for_category(category),
|
||||
upgrade_time_of_day=PluginAutoUpgradeService.default_upgrade_time_of_day(tenant.id),
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
exclude_plugins=[],
|
||||
include_plugins=[],
|
||||
)
|
||||
db.session.add(plugin_upgrade_strategy)
|
||||
db.session.commit()
|
||||
|
||||
tenant.encrypt_public_key = generate_key_pair(tenant.id)
|
||||
@ -1447,6 +1451,7 @@ class TenantService:
|
||||
.values(current=False)
|
||||
)
|
||||
tenant_account_join.current = True
|
||||
tenant_account_join.last_opened_at = naive_utc_now()
|
||||
# Set the current tenant for the account
|
||||
account.set_tenant_id(tenant_account_join.tenant_id)
|
||||
db.session.commit()
|
||||
|
||||
@ -23,7 +23,7 @@ from graphon.model_runtime.entities.model_entities import ModelPropertyKey, Mode
|
||||
from graphon.model_runtime.model_providers.base.large_language_model import LargeLanguageModel
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.login import current_user
|
||||
from models import Account
|
||||
from models import Account, AppStar
|
||||
from models.agent import Agent, AgentIconType, AgentScope, AgentSource, AgentStatus
|
||||
from models.model import App, AppMode, AppModelConfig, IconType, Site
|
||||
from models.tools import ApiToolProvider
|
||||
@ -36,19 +36,29 @@ from tasks.remove_app_and_related_data_task import remove_app_and_related_data_t
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AppListSortBy = Literal["last_modified", "recently_created", "earliest_created"]
|
||||
|
||||
class AppListParams(BaseModel):
|
||||
|
||||
class AppListBaseParams(BaseModel):
|
||||
page: int = Field(default=1, ge=1)
|
||||
limit: int = Field(default=20, ge=1, le=100)
|
||||
mode: Literal["completion", "chat", "advanced-chat", "workflow", "agent-chat", "agent", "channel", "all"] = "all"
|
||||
sort_by: AppListSortBy = "last_modified"
|
||||
name: str | None = None
|
||||
tag_ids: list[str] | None = None
|
||||
creator_ids: list[str] | None = None
|
||||
is_created_by_me: bool | None = None
|
||||
|
||||
|
||||
class AppListParams(AppListBaseParams):
|
||||
status: str | None = None
|
||||
openapi_visible: bool = False
|
||||
|
||||
|
||||
class StarredAppListParams(AppListBaseParams):
|
||||
pass
|
||||
|
||||
|
||||
class CreateAppParams(BaseModel):
|
||||
name: str = Field(min_length=1)
|
||||
description: str | None = None
|
||||
@ -62,6 +72,83 @@ class CreateAppParams(BaseModel):
|
||||
|
||||
|
||||
class AppService:
|
||||
@staticmethod
|
||||
def _build_app_list_filters(
|
||||
user_id: str, tenant_id: str, params: AppListBaseParams
|
||||
) -> list[sa.ColumnElement[bool]]:
|
||||
filters = [App.tenant_id == tenant_id, App.is_universal == False]
|
||||
|
||||
if params.mode == "workflow":
|
||||
filters.append(App.mode == AppMode.WORKFLOW)
|
||||
elif params.mode == "completion":
|
||||
filters.append(App.mode == AppMode.COMPLETION)
|
||||
elif params.mode == "chat":
|
||||
filters.append(App.mode == AppMode.CHAT)
|
||||
elif params.mode == "advanced-chat":
|
||||
filters.append(App.mode == AppMode.ADVANCED_CHAT)
|
||||
elif params.mode == "agent-chat":
|
||||
filters.append(App.mode == AppMode.AGENT_CHAT)
|
||||
elif params.mode == "agent":
|
||||
filters.append(App.mode == AppMode.AGENT)
|
||||
|
||||
if isinstance(params, AppListParams):
|
||||
if params.status:
|
||||
filters.append(App.status == params.status)
|
||||
# OpenAPI surface visibility gate. Pushed into the query so
|
||||
# `pagination.total` reflects only apps the openapi caller can
|
||||
# actually reach; post-filtering by enable_api after the page
|
||||
# arrives would make `total` page-dependent.
|
||||
if params.openapi_visible:
|
||||
filters.append(App.enable_api.is_(True))
|
||||
|
||||
if params.is_created_by_me:
|
||||
filters.append(App.created_by == user_id)
|
||||
if params.creator_ids:
|
||||
filters.append(App.created_by.in_(params.creator_ids))
|
||||
if params.name:
|
||||
from libs.helper import escape_like_pattern
|
||||
|
||||
name = params.name[:30]
|
||||
escaped_name = escape_like_pattern(name)
|
||||
filters.append(App.name.ilike(f"%{escaped_name}%", escape="\\"))
|
||||
if params.tag_ids and len(params.tag_ids) > 0:
|
||||
target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, params.tag_ids, match_all=True)
|
||||
if target_ids and len(target_ids) > 0:
|
||||
filters.append(App.id.in_(target_ids))
|
||||
else:
|
||||
return []
|
||||
|
||||
return filters
|
||||
|
||||
@staticmethod
|
||||
def _build_app_list_order_by(sort_by: AppListSortBy) -> sa.ColumnElement[Any]:
|
||||
return {
|
||||
"last_modified": App.updated_at.desc(),
|
||||
"recently_created": App.created_at.desc(),
|
||||
"earliest_created": App.created_at.asc(),
|
||||
}[sort_by]
|
||||
|
||||
@staticmethod
|
||||
def get_starred_app_ids(
|
||||
session: Session | scoped_session,
|
||||
*,
|
||||
tenant_id: str,
|
||||
account_id: str,
|
||||
app_ids: Sequence[str],
|
||||
) -> set[str]:
|
||||
"""Return app IDs starred by this account within the tenant."""
|
||||
if not app_ids:
|
||||
return set()
|
||||
|
||||
starred_app_ids = session.scalars(
|
||||
select(AppStar.app_id).where(
|
||||
AppStar.tenant_id == tenant_id,
|
||||
AppStar.account_id == account_id,
|
||||
AppStar.app_id.in_(list(app_ids)),
|
||||
)
|
||||
).all()
|
||||
return set(starred_app_ids)
|
||||
|
||||
@staticmethod
|
||||
def get_app_by_id(
|
||||
session: Session | scoped_session,
|
||||
@ -109,61 +196,104 @@ class AppService:
|
||||
|
||||
def get_paginate_apps(self, user_id: str, tenant_id: str, params: AppListParams) -> Pagination | None:
|
||||
"""
|
||||
Get app list with pagination
|
||||
Get app list with pagination, filters, and explicit sort order.
|
||||
:param user_id: user id
|
||||
:param tenant_id: tenant id
|
||||
:param params: query parameters
|
||||
:return:
|
||||
"""
|
||||
filters = [App.tenant_id == tenant_id, App.is_universal == False]
|
||||
filters = self._build_app_list_filters(user_id, tenant_id, params)
|
||||
if not filters:
|
||||
return None
|
||||
|
||||
if params.mode == "workflow":
|
||||
filters.append(App.mode == AppMode.WORKFLOW)
|
||||
elif params.mode == "completion":
|
||||
filters.append(App.mode == AppMode.COMPLETION)
|
||||
elif params.mode == "chat":
|
||||
filters.append(App.mode == AppMode.CHAT)
|
||||
elif params.mode == "advanced-chat":
|
||||
filters.append(App.mode == AppMode.ADVANCED_CHAT)
|
||||
elif params.mode == "agent-chat":
|
||||
filters.append(App.mode == AppMode.AGENT_CHAT)
|
||||
elif params.mode == "agent":
|
||||
filters.append(App.mode == AppMode.AGENT)
|
||||
|
||||
if params.status:
|
||||
filters.append(App.status == params.status)
|
||||
# OpenAPI surface visibility gate. Pushed into the query so
|
||||
# `pagination.total` reflects only apps the openapi caller can
|
||||
# actually reach — post-filtering by enable_api after the page
|
||||
# arrives would make `total` page-dependent.
|
||||
if params.openapi_visible:
|
||||
filters.append(App.enable_api.is_(True))
|
||||
if params.is_created_by_me:
|
||||
filters.append(App.created_by == user_id)
|
||||
if params.creator_ids:
|
||||
filters.append(App.created_by.in_(params.creator_ids))
|
||||
if params.name:
|
||||
from libs.helper import escape_like_pattern
|
||||
|
||||
name = params.name[:30]
|
||||
escaped_name = escape_like_pattern(name)
|
||||
filters.append(App.name.ilike(f"%{escaped_name}%", escape="\\"))
|
||||
if params.tag_ids and len(params.tag_ids) > 0:
|
||||
target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, params.tag_ids, match_all=True)
|
||||
if target_ids and len(target_ids) > 0:
|
||||
filters.append(App.id.in_(target_ids))
|
||||
else:
|
||||
return None
|
||||
order_by = self._build_app_list_order_by(params.sort_by)
|
||||
|
||||
app_models = db.paginate(
|
||||
sa.select(App).where(*filters).order_by(App.created_at.desc()),
|
||||
sa.select(App).where(*filters).order_by(order_by),
|
||||
page=params.page,
|
||||
per_page=params.limit,
|
||||
error_out=False,
|
||||
)
|
||||
|
||||
app_ids = [str(app.id) for app in app_models.items]
|
||||
starred_app_ids = self.get_starred_app_ids(
|
||||
db.session,
|
||||
tenant_id=tenant_id,
|
||||
account_id=user_id,
|
||||
app_ids=app_ids,
|
||||
)
|
||||
for app in app_models.items:
|
||||
app.is_starred = str(app.id) in starred_app_ids
|
||||
|
||||
return app_models
|
||||
|
||||
def get_paginate_starred_apps(
|
||||
self, user_id: str, tenant_id: str, params: StarredAppListParams
|
||||
) -> Pagination | None:
|
||||
"""
|
||||
Get apps starred by the current account with pagination, filters, and explicit sort order.
|
||||
"""
|
||||
filters = self._build_app_list_filters(user_id, tenant_id, params)
|
||||
if not filters:
|
||||
return None
|
||||
|
||||
order_by = self._build_app_list_order_by(params.sort_by)
|
||||
app_models = db.paginate(
|
||||
sa.select(App)
|
||||
.join(
|
||||
AppStar,
|
||||
sa.and_(
|
||||
AppStar.tenant_id == App.tenant_id,
|
||||
AppStar.app_id == App.id,
|
||||
AppStar.account_id == user_id,
|
||||
),
|
||||
)
|
||||
.where(AppStar.tenant_id == tenant_id, *filters)
|
||||
.order_by(order_by),
|
||||
page=params.page,
|
||||
per_page=params.limit,
|
||||
error_out=False,
|
||||
)
|
||||
|
||||
for app in app_models.items:
|
||||
app.is_starred = True
|
||||
|
||||
return app_models
|
||||
|
||||
@staticmethod
|
||||
def star_app(session: Session, *, app: App, account_id: str) -> None:
|
||||
"""Create the account's app star if it does not already exist."""
|
||||
existing_star = session.scalar(
|
||||
select(AppStar)
|
||||
.where(
|
||||
AppStar.tenant_id == app.tenant_id,
|
||||
AppStar.app_id == app.id,
|
||||
AppStar.account_id == account_id,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
if existing_star:
|
||||
return
|
||||
|
||||
session.add(AppStar(tenant_id=app.tenant_id, app_id=app.id, account_id=account_id))
|
||||
|
||||
@staticmethod
|
||||
def unstar_app(session: Session, *, app: App, account_id: str) -> None:
|
||||
"""Remove the account's app star if present."""
|
||||
existing_star = session.scalar(
|
||||
select(AppStar)
|
||||
.where(
|
||||
AppStar.tenant_id == app.tenant_id,
|
||||
AppStar.app_id == app.id,
|
||||
AppStar.account_id == account_id,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
if not existing_star:
|
||||
return
|
||||
|
||||
session.delete(existing_star)
|
||||
|
||||
def create_app(self, tenant_id: str, params: CreateAppParams, account: Account) -> App:
|
||||
"""
|
||||
Create app
|
||||
|
||||
@ -192,6 +192,27 @@ class SimpleProviderEntityResponse(BaseModel):
|
||||
return self
|
||||
|
||||
|
||||
class ProviderEntityResponse(BaseModel):
|
||||
"""Runtime provider response with codegen-safe model pricing schemas."""
|
||||
|
||||
provider: str
|
||||
provider_name: str = ""
|
||||
label: I18nObject
|
||||
description: I18nObject | None = None
|
||||
icon_small: I18nObject | None = None
|
||||
icon_small_dark: I18nObject | None = None
|
||||
background: str | None = None
|
||||
help: ProviderHelpEntity | None = None
|
||||
supported_model_types: Sequence[ModelType]
|
||||
configurate_methods: list[ConfigurateMethod]
|
||||
models: list[AIModelEntityResponse] = []
|
||||
provider_credential_schema: ProviderCredentialSchema | None = None
|
||||
model_credential_schema: ModelCredentialSchema | None = None
|
||||
position: dict[str, list[str]] | None = {}
|
||||
|
||||
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
|
||||
|
||||
|
||||
class DefaultModelResponse(BaseModel):
|
||||
"""
|
||||
Default model entity.
|
||||
|
||||
@ -1,18 +1,295 @@
|
||||
"""Manage tenant plugin auto-upgrade strategies.
|
||||
|
||||
The storage is category-scoped: each tenant can have one strategy per plugin
|
||||
category. Public mutation helpers require an explicit category so callers do
|
||||
not accidentally overwrite every plugin type with one workspace-level policy.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from hashlib import sha256
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.db.session_factory import session_factory
|
||||
from core.plugin.impl.plugin import PluginInstaller
|
||||
from models.account import TenantPluginAutoUpgradeStrategy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PluginCategory = TenantPluginAutoUpgradeStrategy.PluginCategory
|
||||
PLUGIN_CATEGORIES = tuple(PluginCategory)
|
||||
SECONDS_PER_DAY = 24 * 60 * 60
|
||||
AUTO_UPGRADE_CHECK_SLOT_SECONDS = 15 * 60
|
||||
AUTO_UPGRADE_CHECK_SLOT_COUNT = SECONDS_PER_DAY // AUTO_UPGRADE_CHECK_SLOT_SECONDS
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PluginAutoUpgradeBackfillResult:
|
||||
created_count: int
|
||||
normalized: bool
|
||||
|
||||
|
||||
class PluginAutoUpgradeService:
|
||||
@staticmethod
|
||||
def get_strategy(tenant_id: str) -> TenantPluginAutoUpgradeStrategy | None:
|
||||
with session_factory.create_session() as session:
|
||||
return session.scalar(
|
||||
select(TenantPluginAutoUpgradeStrategy)
|
||||
.where(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
def default_strategy_setting_for_category(
|
||||
category: PluginCategory,
|
||||
) -> TenantPluginAutoUpgradeStrategy.StrategySetting:
|
||||
if category == PluginCategory.MODEL:
|
||||
return TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST
|
||||
return TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY
|
||||
|
||||
@staticmethod
|
||||
def default_upgrade_time_of_day(tenant_id: str) -> int:
|
||||
"""Spread default checks across 15-minute aligned slots by tenant."""
|
||||
hash_input = tenant_id.encode()
|
||||
slot = int.from_bytes(sha256(hash_input).digest()[:8], "big") % AUTO_UPGRADE_CHECK_SLOT_COUNT
|
||||
return slot * AUTO_UPGRADE_CHECK_SLOT_SECONDS
|
||||
|
||||
@staticmethod
|
||||
def _coerce_category(category: object) -> PluginCategory | None:
|
||||
"""Accept daemon enum/string categories and ignore unknown values."""
|
||||
category_value = getattr(category, "value", category)
|
||||
if category_value is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return PluginCategory(str(category_value))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_installed_plugin_categories(tenant_id: str) -> dict[str, PluginCategory]:
|
||||
"""Build a plugin_id -> category map for splitting legacy include/exclude lists."""
|
||||
installed_plugins = PluginInstaller().list_plugins(tenant_id)
|
||||
plugin_categories: dict[str, PluginCategory] = {}
|
||||
|
||||
for plugin in installed_plugins:
|
||||
plugin_category = PluginAutoUpgradeService._coerce_category(plugin.declaration.category)
|
||||
if plugin_category is not None:
|
||||
plugin_categories[plugin.plugin_id] = plugin_category
|
||||
|
||||
return plugin_categories
|
||||
|
||||
@staticmethod
|
||||
def _filter_plugin_ids_for_category(
|
||||
plugin_ids: list[str],
|
||||
category: PluginCategory,
|
||||
plugin_categories: dict[str, PluginCategory],
|
||||
) -> list[str]:
|
||||
return [plugin_id for plugin_id in plugin_ids if plugin_categories.get(plugin_id) == category]
|
||||
|
||||
@staticmethod
|
||||
def _log_unknown_plugin_ids(
|
||||
tenant_id: str,
|
||||
field_name: str,
|
||||
plugin_ids: list[str],
|
||||
plugin_categories: dict[str, PluginCategory],
|
||||
) -> None:
|
||||
unknown_plugin_ids = [plugin_id for plugin_id in plugin_ids if plugin_id not in plugin_categories]
|
||||
if not unknown_plugin_ids:
|
||||
return
|
||||
|
||||
logger.warning(
|
||||
"Skipped unknown plugin IDs while backfilling plugin auto-upgrade strategies: "
|
||||
"tenant_id=%s, field=%s, plugin_ids=%s",
|
||||
tenant_id,
|
||||
field_name,
|
||||
unknown_plugin_ids,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _has_default_strategy(strategy: TenantPluginAutoUpgradeStrategy) -> bool:
|
||||
return (
|
||||
strategy.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY
|
||||
and strategy.upgrade_time_of_day == 0
|
||||
and strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE
|
||||
and not strategy.exclude_plugins
|
||||
and not strategy.include_plugins
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _strategy_setting_for_category(
|
||||
source_strategy: TenantPluginAutoUpgradeStrategy,
|
||||
category: PluginCategory,
|
||||
source_has_default_strategy: bool,
|
||||
) -> TenantPluginAutoUpgradeStrategy.StrategySetting:
|
||||
# Only pure legacy defaults adopt the new model=latest default. User-edited
|
||||
# strategies keep their original setting across all categories.
|
||||
if source_has_default_strategy:
|
||||
return PluginAutoUpgradeService.default_strategy_setting_for_category(category)
|
||||
return source_strategy.strategy_setting
|
||||
|
||||
@staticmethod
|
||||
def _upgrade_time_of_day_for_category(
|
||||
tenant_id: str,
|
||||
source_strategy: TenantPluginAutoUpgradeStrategy,
|
||||
source_has_default_strategy: bool,
|
||||
) -> int:
|
||||
# Pure legacy defaults are spread by tenant so all default rows do not
|
||||
# concentrate in the same scheduler window. User-edited schedules keep their time.
|
||||
if source_has_default_strategy:
|
||||
return PluginAutoUpgradeService.default_upgrade_time_of_day(tenant_id)
|
||||
return source_strategy.upgrade_time_of_day
|
||||
|
||||
@staticmethod
|
||||
def backfill_strategy_categories(
|
||||
tenant_id: str,
|
||||
) -> PluginAutoUpgradeBackfillResult:
|
||||
"""Create missing category strategies and split include/exclude lists when needed.
|
||||
|
||||
The historical row is treated as the workspace-level source strategy.
|
||||
New category rows copy it first, then plugin lists are narrowed by real
|
||||
plugin category when the source strategy contains include/exclude IDs.
|
||||
"""
|
||||
with session_factory.create_session() as session, session.begin():
|
||||
strategies = list(
|
||||
session.scalars(
|
||||
select(TenantPluginAutoUpgradeStrategy).where(
|
||||
TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id
|
||||
)
|
||||
).all()
|
||||
)
|
||||
if not strategies:
|
||||
return PluginAutoUpgradeBackfillResult(created_count=0, normalized=False)
|
||||
|
||||
# Schema migration marks the historical workspace-level row as tool.
|
||||
source_strategy = next(
|
||||
(strategy for strategy in strategies if strategy.category == PluginCategory.TOOL),
|
||||
strategies[0],
|
||||
)
|
||||
source_has_default_strategy = PluginAutoUpgradeService._has_default_strategy(source_strategy)
|
||||
strategies_by_category = {strategy.category: strategy for strategy in strategies}
|
||||
exclude_plugins = source_strategy.exclude_plugins
|
||||
include_plugins = source_strategy.include_plugins
|
||||
should_split_plugin_lists = bool(exclude_plugins or include_plugins)
|
||||
# Query daemon only for tenants that actually customized plugin lists.
|
||||
plugin_categories = (
|
||||
PluginAutoUpgradeService._get_installed_plugin_categories(tenant_id)
|
||||
if should_split_plugin_lists
|
||||
else {}
|
||||
)
|
||||
if should_split_plugin_lists:
|
||||
PluginAutoUpgradeService._log_unknown_plugin_ids(
|
||||
tenant_id,
|
||||
"exclude_plugins",
|
||||
exclude_plugins,
|
||||
plugin_categories,
|
||||
)
|
||||
PluginAutoUpgradeService._log_unknown_plugin_ids(
|
||||
tenant_id,
|
||||
"include_plugins",
|
||||
include_plugins,
|
||||
plugin_categories,
|
||||
)
|
||||
|
||||
created_count = 0
|
||||
for category in PLUGIN_CATEGORIES:
|
||||
strategy = strategies_by_category.get(category)
|
||||
if strategy is None:
|
||||
# Start from the legacy workspace-level behavior before narrowing lists.
|
||||
strategy = TenantPluginAutoUpgradeStrategy(
|
||||
tenant_id=tenant_id,
|
||||
category=category,
|
||||
strategy_setting=PluginAutoUpgradeService._strategy_setting_for_category(
|
||||
source_strategy, category, source_has_default_strategy
|
||||
),
|
||||
upgrade_time_of_day=PluginAutoUpgradeService._upgrade_time_of_day_for_category(
|
||||
tenant_id, source_strategy, source_has_default_strategy
|
||||
),
|
||||
upgrade_mode=source_strategy.upgrade_mode,
|
||||
exclude_plugins=source_strategy.exclude_plugins.copy(),
|
||||
include_plugins=source_strategy.include_plugins.copy(),
|
||||
)
|
||||
session.add(strategy)
|
||||
created_count += 1
|
||||
elif source_has_default_strategy:
|
||||
strategy.strategy_setting = PluginAutoUpgradeService.default_strategy_setting_for_category(
|
||||
strategy.category
|
||||
)
|
||||
strategy.upgrade_time_of_day = PluginAutoUpgradeService.default_upgrade_time_of_day(tenant_id)
|
||||
|
||||
if not should_split_plugin_lists:
|
||||
continue
|
||||
|
||||
# Narrow include/exclude lists to the current category after all rows exist.
|
||||
strategy.exclude_plugins = PluginAutoUpgradeService._filter_plugin_ids_for_category(
|
||||
exclude_plugins,
|
||||
strategy.category,
|
||||
plugin_categories,
|
||||
)
|
||||
strategy.include_plugins = PluginAutoUpgradeService._filter_plugin_ids_for_category(
|
||||
include_plugins,
|
||||
strategy.category,
|
||||
plugin_categories,
|
||||
)
|
||||
|
||||
return PluginAutoUpgradeBackfillResult(created_count=created_count, normalized=should_split_plugin_lists)
|
||||
|
||||
@staticmethod
|
||||
def _get_strategy(
|
||||
session: Session,
|
||||
tenant_id: str,
|
||||
category: PluginCategory,
|
||||
) -> TenantPluginAutoUpgradeStrategy | None:
|
||||
return session.scalar(
|
||||
select(TenantPluginAutoUpgradeStrategy)
|
||||
.where(
|
||||
TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id,
|
||||
TenantPluginAutoUpgradeStrategy.category == category,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_strategy(
|
||||
tenant_id: str,
|
||||
category: PluginCategory,
|
||||
) -> TenantPluginAutoUpgradeStrategy | None:
|
||||
with session_factory.create_session() as session:
|
||||
return PluginAutoUpgradeService._get_strategy(session, tenant_id, category)
|
||||
|
||||
@staticmethod
|
||||
def get_strategies(tenant_id: str) -> list[TenantPluginAutoUpgradeStrategy]:
|
||||
with session_factory.create_session() as session:
|
||||
return list(
|
||||
session.scalars(
|
||||
select(TenantPluginAutoUpgradeStrategy).where(
|
||||
TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id
|
||||
)
|
||||
).all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _change_strategy(
|
||||
session: Session,
|
||||
tenant_id: str,
|
||||
category: PluginCategory,
|
||||
strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting,
|
||||
upgrade_time_of_day: int,
|
||||
upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode,
|
||||
exclude_plugins: list[str],
|
||||
include_plugins: list[str],
|
||||
) -> None:
|
||||
exist_strategy = PluginAutoUpgradeService._get_strategy(session, tenant_id, category)
|
||||
if not exist_strategy:
|
||||
strategy = TenantPluginAutoUpgradeStrategy(
|
||||
tenant_id=tenant_id,
|
||||
category=category,
|
||||
strategy_setting=strategy_setting,
|
||||
upgrade_time_of_day=upgrade_time_of_day,
|
||||
upgrade_mode=upgrade_mode,
|
||||
exclude_plugins=exclude_plugins,
|
||||
include_plugins=include_plugins,
|
||||
)
|
||||
session.add(strategy)
|
||||
else:
|
||||
exist_strategy.strategy_setting = strategy_setting
|
||||
exist_strategy.upgrade_time_of_day = upgrade_time_of_day
|
||||
exist_strategy.upgrade_mode = upgrade_mode
|
||||
exist_strategy.exclude_plugins = exclude_plugins
|
||||
exist_strategy.include_plugins = include_plugins
|
||||
|
||||
@staticmethod
|
||||
def change_strategy(
|
||||
@ -22,64 +299,72 @@ class PluginAutoUpgradeService:
|
||||
upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode,
|
||||
exclude_plugins: list[str],
|
||||
include_plugins: list[str],
|
||||
category: PluginCategory,
|
||||
) -> bool:
|
||||
with session_factory.create_session() as session, session.begin():
|
||||
exist_strategy = session.scalar(
|
||||
select(TenantPluginAutoUpgradeStrategy)
|
||||
.where(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
PluginAutoUpgradeService._change_strategy(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
category=category,
|
||||
strategy_setting=strategy_setting,
|
||||
upgrade_time_of_day=upgrade_time_of_day,
|
||||
upgrade_mode=upgrade_mode,
|
||||
exclude_plugins=exclude_plugins,
|
||||
include_plugins=include_plugins,
|
||||
)
|
||||
if not exist_strategy:
|
||||
strategy = TenantPluginAutoUpgradeStrategy(
|
||||
tenant_id=tenant_id,
|
||||
strategy_setting=strategy_setting,
|
||||
upgrade_time_of_day=upgrade_time_of_day,
|
||||
upgrade_mode=upgrade_mode,
|
||||
exclude_plugins=exclude_plugins,
|
||||
include_plugins=include_plugins,
|
||||
)
|
||||
session.add(strategy)
|
||||
else:
|
||||
exist_strategy.strategy_setting = strategy_setting
|
||||
exist_strategy.upgrade_time_of_day = upgrade_time_of_day
|
||||
exist_strategy.upgrade_mode = upgrade_mode
|
||||
exist_strategy.exclude_plugins = exclude_plugins
|
||||
exist_strategy.include_plugins = include_plugins
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def exclude_plugin(tenant_id: str, plugin_id: str) -> bool:
|
||||
with session_factory.create_session() as session, session.begin():
|
||||
exist_strategy = session.scalar(
|
||||
select(TenantPluginAutoUpgradeStrategy)
|
||||
.where(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
def _exclude_plugin(
|
||||
session: Session,
|
||||
tenant_id: str,
|
||||
category: PluginCategory,
|
||||
plugin_id: str,
|
||||
) -> None:
|
||||
"""Remove one plugin from automatic updates for a single category strategy."""
|
||||
exist_strategy = PluginAutoUpgradeService._get_strategy(session, tenant_id, category)
|
||||
if not exist_strategy:
|
||||
PluginAutoUpgradeService._change_strategy(
|
||||
session,
|
||||
tenant_id,
|
||||
category,
|
||||
TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
0,
|
||||
TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
[plugin_id],
|
||||
[],
|
||||
)
|
||||
if not exist_strategy:
|
||||
# create for this tenant
|
||||
PluginAutoUpgradeService.change_strategy(
|
||||
tenant_id,
|
||||
TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
0,
|
||||
TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
[plugin_id],
|
||||
[],
|
||||
)
|
||||
return True
|
||||
else:
|
||||
if exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE:
|
||||
if plugin_id not in exist_strategy.exclude_plugins:
|
||||
new_exclude_plugins = exist_strategy.exclude_plugins.copy()
|
||||
new_exclude_plugins.append(plugin_id)
|
||||
exist_strategy.exclude_plugins = new_exclude_plugins
|
||||
elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL:
|
||||
if plugin_id in exist_strategy.include_plugins:
|
||||
new_include_plugins = exist_strategy.include_plugins.copy()
|
||||
new_include_plugins.remove(plugin_id)
|
||||
exist_strategy.include_plugins = new_include_plugins
|
||||
elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL:
|
||||
exist_strategy.upgrade_mode = TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE
|
||||
exist_strategy.exclude_plugins = [plugin_id]
|
||||
else:
|
||||
if exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE:
|
||||
# In exclude mode, disabling one plugin means adding it to exclude_plugins.
|
||||
if plugin_id not in exist_strategy.exclude_plugins:
|
||||
new_exclude_plugins = exist_strategy.exclude_plugins.copy()
|
||||
new_exclude_plugins.append(plugin_id)
|
||||
exist_strategy.exclude_plugins = new_exclude_plugins
|
||||
elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL:
|
||||
# In partial mode, disabling one plugin means removing it from include_plugins.
|
||||
if plugin_id in exist_strategy.include_plugins:
|
||||
new_include_plugins = exist_strategy.include_plugins.copy()
|
||||
new_include_plugins.remove(plugin_id)
|
||||
exist_strategy.include_plugins = new_include_plugins
|
||||
elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL:
|
||||
# In all mode, switch to exclude mode so only this plugin is skipped.
|
||||
exist_strategy.upgrade_mode = TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE
|
||||
exist_strategy.exclude_plugins = [plugin_id]
|
||||
|
||||
return True
|
||||
@staticmethod
|
||||
def exclude_plugin(
|
||||
tenant_id: str,
|
||||
plugin_id: str,
|
||||
category: PluginCategory,
|
||||
) -> bool:
|
||||
with session_factory.create_session() as session, session.begin():
|
||||
PluginAutoUpgradeService._exclude_plugin(
|
||||
session,
|
||||
tenant_id,
|
||||
category,
|
||||
plugin_id,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Any, TypedDict, override
|
||||
from typing import Any, NotRequired, TypedDict, override
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
@ -22,6 +22,7 @@ class RecommendedAppItemDict(TypedDict):
|
||||
categories: list[str]
|
||||
position: int
|
||||
is_listed: bool
|
||||
can_trial: NotRequired[bool]
|
||||
|
||||
|
||||
class RecommendedAppsResultDict(TypedDict):
|
||||
@ -64,14 +65,47 @@ class DatabaseRecommendAppRetrieval(RecommendAppRetrievalBase):
|
||||
:param language: language
|
||||
:return:
|
||||
"""
|
||||
recommended_apps = db.session.scalars(
|
||||
select(RecommendedApp).where(RecommendedApp.is_listed == True, RecommendedApp.language == language)
|
||||
).all()
|
||||
recommended_apps = cls._fetch_listed_recommended_apps(language)
|
||||
|
||||
if len(recommended_apps) == 0:
|
||||
recommended_apps = db.session.scalars(
|
||||
select(RecommendedApp).where(RecommendedApp.is_listed == True, RecommendedApp.language == languages[0])
|
||||
).all()
|
||||
recommended_apps = cls._fetch_listed_recommended_apps(languages[0])
|
||||
|
||||
return cls._format_recommended_apps(recommended_apps, language)
|
||||
|
||||
@classmethod
|
||||
def fetch_learn_dify_apps_from_db(cls, language: str) -> RecommendedAppsResultDict:
|
||||
"""
|
||||
Fetch listed recommended apps explicitly marked for the Learn Dify section.
|
||||
:param language: language
|
||||
:return:
|
||||
"""
|
||||
recommended_apps = cls._fetch_listed_recommended_apps(language, is_learn_dify=True)
|
||||
|
||||
if len(recommended_apps) == 0 and language != languages[0]:
|
||||
recommended_apps = cls._fetch_listed_recommended_apps(languages[0], is_learn_dify=True)
|
||||
|
||||
return cls._format_recommended_apps(recommended_apps, language)
|
||||
|
||||
@classmethod
|
||||
def _fetch_listed_recommended_apps(
|
||||
cls, language: str, *, is_learn_dify: bool | None = None
|
||||
) -> list[RecommendedApp]:
|
||||
filters = [RecommendedApp.is_listed.is_(True), RecommendedApp.language == language]
|
||||
if is_learn_dify is not None:
|
||||
filters.append(RecommendedApp.is_learn_dify.is_(is_learn_dify))
|
||||
|
||||
return list(db.session.scalars(select(RecommendedApp).where(*filters)).all())
|
||||
|
||||
@classmethod
|
||||
def _format_recommended_apps(
|
||||
cls, recommended_apps: list[RecommendedApp], language: str
|
||||
) -> RecommendedAppsResultDict:
|
||||
"""
|
||||
Serialize DB recommended app rows into the Explore list response shape.
|
||||
:param recommended_apps: recommended app rows
|
||||
:param language: language used for category ordering
|
||||
:return:
|
||||
"""
|
||||
|
||||
categories = set()
|
||||
recommended_apps_result: list[RecommendedAppItemDict] = []
|
||||
|
||||
@ -6,6 +6,7 @@ from sqlalchemy.orm import scoped_session
|
||||
from configs import dify_config
|
||||
from models.model import AccountTrialAppRecord, TrialApp
|
||||
from services.feature_service import FeatureService
|
||||
from services.recommend_app.database.database_retrieval import DatabaseRecommendAppRetrieval
|
||||
from services.recommend_app.recommend_app_factory import RecommendAppRetrievalFactory
|
||||
|
||||
|
||||
@ -31,13 +32,24 @@ class RecommendedAppService:
|
||||
apps = result["recommended_apps"]
|
||||
for app in apps:
|
||||
app_id = app["app_id"]
|
||||
trial_app_model = session.scalar(select(TrialApp).where(TrialApp.app_id == app_id).limit(1))
|
||||
if trial_app_model:
|
||||
app["can_trial"] = True
|
||||
else:
|
||||
app["can_trial"] = False
|
||||
app["can_trial"] = cls._can_trial_app(session, app_id)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_learn_dify_apps(cls, session: scoped_session, language: str) -> dict[str, Any]:
|
||||
"""
|
||||
Get database-backed recommended apps marked as Learn Dify.
|
||||
:param language: language
|
||||
:return:
|
||||
"""
|
||||
result = DatabaseRecommendAppRetrieval.fetch_learn_dify_apps_from_db(language)
|
||||
|
||||
if FeatureService.get_system_features().enable_trial_app:
|
||||
for app in result["recommended_apps"]:
|
||||
app["can_trial"] = cls._can_trial_app(session, app["app_id"])
|
||||
|
||||
return {"recommended_apps": result["recommended_apps"]}
|
||||
|
||||
@classmethod
|
||||
def get_recommend_app_detail(cls, session: scoped_session, app_id: str) -> dict[str, Any] | None:
|
||||
"""
|
||||
@ -52,11 +64,7 @@ class RecommendedAppService:
|
||||
return None
|
||||
if FeatureService.get_system_features().enable_trial_app:
|
||||
app_id = result["id"]
|
||||
trial_app_model = session.scalar(select(TrialApp).where(TrialApp.app_id == app_id).limit(1))
|
||||
if trial_app_model:
|
||||
result["can_trial"] = True
|
||||
else:
|
||||
result["can_trial"] = False
|
||||
result["can_trial"] = cls._can_trial_app(session, app_id)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
@ -77,3 +85,8 @@ class RecommendedAppService:
|
||||
else:
|
||||
session.add(AccountTrialAppRecord(app_id=app_id, count=1, account_id=account_id))
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def _can_trial_app(session: scoped_session, app_id: str) -> bool:
|
||||
trial_app_model = session.scalar(select(TrialApp).where(TrialApp.app_id == app_id).limit(1))
|
||||
return trial_app_model is not None
|
||||
|
||||
@ -7,7 +7,7 @@ import click
|
||||
from celery import shared_task
|
||||
|
||||
from core.plugin.entities.marketplace import MarketplacePluginSnapshot
|
||||
from core.plugin.entities.plugin import PluginInstallationSource
|
||||
from core.plugin.entities.plugin import PluginInstallation, PluginInstallationSource
|
||||
from core.plugin.impl.plugin import PluginInstaller
|
||||
from core.plugin.plugin_service import PluginService
|
||||
from extensions.ext_redis import redis_client
|
||||
@ -15,6 +15,7 @@ from models.account import TenantPluginAutoUpgradeStrategy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PluginCategory = TenantPluginAutoUpgradeStrategy.PluginCategory
|
||||
RETRY_TIMES_OF_ONE_PLUGIN_IN_ONE_TENANT = 3
|
||||
CACHE_REDIS_KEY_PREFIX = "plugin_autoupgrade_check_task:cached_plugin_snapshot:"
|
||||
CACHE_REDIS_TTL = 60 * 60 # 1 hour
|
||||
@ -72,6 +73,25 @@ def marketplace_batch_fetch_plugin_manifests(
|
||||
return result
|
||||
|
||||
|
||||
def _normalize_category(category: PluginCategory | str | None) -> str | None:
|
||||
if category is None:
|
||||
return None
|
||||
if isinstance(category, PluginCategory):
|
||||
return category.value
|
||||
return str(category)
|
||||
|
||||
|
||||
def _plugin_matches_category(plugin: PluginInstallation, category: str | None) -> bool:
|
||||
"""Return whether an installed plugin should be checked by a category strategy."""
|
||||
if category is None:
|
||||
return True
|
||||
|
||||
declaration = getattr(plugin, "declaration", None)
|
||||
plugin_category = getattr(declaration, "category", None)
|
||||
plugin_category_value = getattr(plugin_category, "value", plugin_category)
|
||||
return plugin_category_value == category
|
||||
|
||||
|
||||
@shared_task(queue="plugin")
|
||||
def process_tenant_plugin_autoupgrade_check_task(
|
||||
tenant_id: str,
|
||||
@ -80,13 +100,15 @@ def process_tenant_plugin_autoupgrade_check_task(
|
||||
upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode,
|
||||
exclude_plugins: list[str],
|
||||
include_plugins: list[str],
|
||||
category: PluginCategory | str | None = None,
|
||||
):
|
||||
try:
|
||||
manager = PluginInstaller()
|
||||
category_value = _normalize_category(category)
|
||||
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Checking upgradable plugin for tenant: {tenant_id}",
|
||||
f"Checking upgradable plugin for tenant: {tenant_id}, category: {category_value or 'all'}",
|
||||
fg="green",
|
||||
)
|
||||
)
|
||||
@ -102,7 +124,11 @@ def process_tenant_plugin_autoupgrade_check_task(
|
||||
all_plugins = manager.list_plugins(tenant_id)
|
||||
|
||||
for plugin in all_plugins:
|
||||
if plugin.source == PluginInstallationSource.Marketplace and plugin.plugin_id in include_plugins:
|
||||
if (
|
||||
plugin.source == PluginInstallationSource.Marketplace
|
||||
and plugin.plugin_id in include_plugins
|
||||
and _plugin_matches_category(plugin, category_value)
|
||||
):
|
||||
plugin_ids.append(
|
||||
(
|
||||
plugin.plugin_id,
|
||||
@ -117,7 +143,9 @@ def process_tenant_plugin_autoupgrade_check_task(
|
||||
plugin_ids = [
|
||||
(plugin.plugin_id, plugin.version, plugin.plugin_unique_identifier)
|
||||
for plugin in all_plugins
|
||||
if plugin.source == PluginInstallationSource.Marketplace and plugin.plugin_id not in exclude_plugins
|
||||
if plugin.source == PluginInstallationSource.Marketplace
|
||||
and plugin.plugin_id not in exclude_plugins
|
||||
and _plugin_matches_category(plugin, category_value)
|
||||
]
|
||||
elif upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL:
|
||||
all_plugins = manager.list_plugins(tenant_id)
|
||||
@ -125,6 +153,7 @@ def process_tenant_plugin_autoupgrade_check_task(
|
||||
(plugin.plugin_id, plugin.version, plugin.plugin_unique_identifier)
|
||||
for plugin in all_plugins
|
||||
if plugin.source == PluginInstallationSource.Marketplace
|
||||
and _plugin_matches_category(plugin, category_value)
|
||||
]
|
||||
|
||||
if not plugin_ids:
|
||||
|
||||
@ -22,6 +22,7 @@ from models import (
|
||||
AppDatasetJoin,
|
||||
AppMCPServer,
|
||||
AppModelConfig,
|
||||
AppStar,
|
||||
AppTrigger,
|
||||
Conversation,
|
||||
EndUser,
|
||||
@ -64,6 +65,7 @@ def remove_app_and_related_data_task(self, tenant_id: str, app_id: str):
|
||||
_delete_app_mcp_servers(tenant_id, app_id)
|
||||
_delete_app_api_tokens(tenant_id, app_id)
|
||||
_delete_installed_apps(tenant_id, app_id)
|
||||
_delete_app_stars(tenant_id, app_id)
|
||||
_delete_recommended_apps(tenant_id, app_id)
|
||||
_delete_app_annotation_data(tenant_id, app_id)
|
||||
_delete_app_dataset_joins(tenant_id, app_id)
|
||||
@ -173,6 +175,18 @@ def _delete_installed_apps(tenant_id: str, app_id: str):
|
||||
)
|
||||
|
||||
|
||||
def _delete_app_stars(tenant_id: str, app_id: str):
|
||||
def del_app_star(session, app_star_id: str):
|
||||
session.execute(delete(AppStar).where(AppStar.id == app_star_id).execution_options(synchronize_session=False))
|
||||
|
||||
_delete_records(
|
||||
"""select id from app_stars where tenant_id=:tenant_id and app_id=:app_id limit 1000""",
|
||||
{"tenant_id": tenant_id, "app_id": app_id},
|
||||
del_app_star,
|
||||
"app star",
|
||||
)
|
||||
|
||||
|
||||
def _delete_recommended_apps(tenant_id: str, app_id: str):
|
||||
def del_recommended_app(session, recommended_app_id: str):
|
||||
session.execute(
|
||||
|
||||
@ -7,6 +7,8 @@ from models.account import TenantPluginAutoUpgradeStrategy, TenantPluginPermissi
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
from services.plugin.plugin_permission_service import PluginPermissionService
|
||||
|
||||
PLUGIN_CATEGORY = TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tenant(flask_req_ctx):
|
||||
@ -71,7 +73,7 @@ class TestPluginPermissionLifecycle:
|
||||
|
||||
class TestPluginAutoUpgradeLifecycle:
|
||||
def test_get_returns_none_for_new_tenant(self, tenant):
|
||||
assert PluginAutoUpgradeService.get_strategy(tenant) is None
|
||||
assert PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY) is None
|
||||
|
||||
def test_change_creates_row(self, tenant):
|
||||
result = PluginAutoUpgradeService.change_strategy(
|
||||
@ -81,10 +83,11 @@ class TestPluginAutoUpgradeLifecycle:
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL,
|
||||
exclude_plugins=[],
|
||||
include_plugins=[],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
assert result is True
|
||||
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant)
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY)
|
||||
assert strategy is not None
|
||||
assert strategy.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST
|
||||
assert strategy.upgrade_time_of_day == 3
|
||||
@ -97,6 +100,7 @@ class TestPluginAutoUpgradeLifecycle:
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL,
|
||||
exclude_plugins=[],
|
||||
include_plugins=[],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
PluginAutoUpgradeService.change_strategy(
|
||||
tenant,
|
||||
@ -105,9 +109,10 @@ class TestPluginAutoUpgradeLifecycle:
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL,
|
||||
exclude_plugins=[],
|
||||
include_plugins=["plugin-a"],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant)
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY)
|
||||
assert strategy is not None
|
||||
assert strategy.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST
|
||||
assert strategy.upgrade_time_of_day == 12
|
||||
@ -115,9 +120,9 @@ class TestPluginAutoUpgradeLifecycle:
|
||||
assert strategy.include_plugins == ["plugin-a"]
|
||||
|
||||
def test_exclude_plugin_creates_strategy_when_none_exists(self, tenant):
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "my-plugin")
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "my-plugin", PLUGIN_CATEGORY)
|
||||
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant)
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY)
|
||||
assert strategy is not None
|
||||
assert strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE
|
||||
assert "my-plugin" in strategy.exclude_plugins
|
||||
@ -130,10 +135,11 @@ class TestPluginAutoUpgradeLifecycle:
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
exclude_plugins=["existing"],
|
||||
include_plugins=[],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "new-plugin")
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "new-plugin", PLUGIN_CATEGORY)
|
||||
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant)
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY)
|
||||
assert strategy is not None
|
||||
assert "existing" in strategy.exclude_plugins
|
||||
assert "new-plugin" in strategy.exclude_plugins
|
||||
@ -146,10 +152,11 @@ class TestPluginAutoUpgradeLifecycle:
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
exclude_plugins=["same-plugin"],
|
||||
include_plugins=[],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "same-plugin")
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "same-plugin", PLUGIN_CATEGORY)
|
||||
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant)
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY)
|
||||
assert strategy is not None
|
||||
assert strategy.exclude_plugins.count("same-plugin") == 1
|
||||
|
||||
@ -161,10 +168,11 @@ class TestPluginAutoUpgradeLifecycle:
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL,
|
||||
exclude_plugins=[],
|
||||
include_plugins=["p1", "p2"],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "p1")
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "p1", PLUGIN_CATEGORY)
|
||||
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant)
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY)
|
||||
assert strategy is not None
|
||||
assert "p1" not in strategy.include_plugins
|
||||
assert "p2" in strategy.include_plugins
|
||||
@ -177,10 +185,11 @@ class TestPluginAutoUpgradeLifecycle:
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL,
|
||||
exclude_plugins=[],
|
||||
include_plugins=[],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "excluded-plugin")
|
||||
PluginAutoUpgradeService.exclude_plugin(tenant, "excluded-plugin", PLUGIN_CATEGORY)
|
||||
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant)
|
||||
strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY)
|
||||
assert strategy is not None
|
||||
assert strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE
|
||||
assert "excluded-plugin" in strategy.exclude_plugins
|
||||
|
||||
@ -51,6 +51,7 @@ def _create_recommended_app(
|
||||
categories: list[str] | None = None,
|
||||
language: str = "en-US",
|
||||
is_listed: bool = True,
|
||||
is_learn_dify: bool = False,
|
||||
position: int = 1,
|
||||
) -> RecommendedApp:
|
||||
rec = RecommendedApp(
|
||||
@ -62,6 +63,7 @@ def _create_recommended_app(
|
||||
categories=[category] if categories is None else categories,
|
||||
language=language,
|
||||
is_listed=is_listed,
|
||||
is_learn_dify=is_learn_dify,
|
||||
position=position,
|
||||
)
|
||||
rec.id = str(uuid4())
|
||||
@ -205,6 +207,65 @@ class TestFetchRecommendedAppsFromDb:
|
||||
app_ids = {r["app_id"] for r in result["recommended_apps"]}
|
||||
assert app1.id not in app_ids
|
||||
|
||||
def test_fetch_learn_dify_apps_uses_flag_not_categories(
|
||||
self,
|
||||
flask_app_with_containers,
|
||||
db_session_with_containers: Session,
|
||||
):
|
||||
tenant_id = str(uuid4())
|
||||
learn_dify_app = _create_app(db_session_with_containers, tenant_id=tenant_id)
|
||||
_create_site(db_session_with_containers, app_id=learn_dify_app.id)
|
||||
_create_recommended_app(
|
||||
db_session_with_containers,
|
||||
app_id=learn_dify_app.id,
|
||||
category="workflow",
|
||||
categories=["Workflow"],
|
||||
is_learn_dify=True,
|
||||
)
|
||||
|
||||
category_only_app = _create_app(db_session_with_containers, tenant_id=tenant_id)
|
||||
_create_site(db_session_with_containers, app_id=category_only_app.id)
|
||||
_create_recommended_app(
|
||||
db_session_with_containers,
|
||||
app_id=category_only_app.id,
|
||||
category="Learn Dify",
|
||||
categories=["Learn Dify"],
|
||||
is_learn_dify=False,
|
||||
)
|
||||
|
||||
db_session_with_containers.expire_all()
|
||||
|
||||
result = DatabaseRecommendAppRetrieval.fetch_learn_dify_apps_from_db("en-US")
|
||||
|
||||
app_ids = {r["app_id"] for r in result["recommended_apps"]}
|
||||
assert learn_dify_app.id in app_ids
|
||||
assert category_only_app.id not in app_ids
|
||||
recommended_app = next(item for item in result["recommended_apps"] if item["app_id"] == learn_dify_app.id)
|
||||
assert recommended_app["categories"] == ["Workflow"]
|
||||
|
||||
def test_fetch_learn_dify_apps_falls_back_to_default_language(
|
||||
self,
|
||||
flask_app_with_containers,
|
||||
db_session_with_containers: Session,
|
||||
):
|
||||
tenant_id = str(uuid4())
|
||||
learn_dify_app = _create_app(db_session_with_containers, tenant_id=tenant_id)
|
||||
_create_site(db_session_with_containers, app_id=learn_dify_app.id)
|
||||
_create_recommended_app(
|
||||
db_session_with_containers,
|
||||
app_id=learn_dify_app.id,
|
||||
categories=["Workflow"],
|
||||
is_learn_dify=True,
|
||||
language="en-US",
|
||||
)
|
||||
|
||||
db_session_with_containers.expire_all()
|
||||
|
||||
result = DatabaseRecommendAppRetrieval.fetch_learn_dify_apps_from_db("fr-FR")
|
||||
|
||||
app_ids = {r["app_id"] for r in result["recommended_apps"]}
|
||||
assert learn_dify_app.id in app_ids
|
||||
|
||||
|
||||
class TestFetchRecommendedAppDetailFromDb:
|
||||
def test_returns_none_when_not_listed(self, flask_app_with_containers: Flask, db_session_with_containers: Session):
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from unittest.mock import create_autospec, patch
|
||||
|
||||
import pytest
|
||||
import sqlalchemy as sa
|
||||
from faker import Faker
|
||||
from pydantic import ValidationError
|
||||
from sqlalchemy.orm import Session
|
||||
@ -245,6 +247,236 @@ class TestAppService:
|
||||
assert app.tenant_id == tenant.id
|
||||
assert app.mode == "chat"
|
||||
|
||||
def test_get_paginate_apps_sorts_by_modified_and_created_times(
|
||||
self, db_session_with_containers: Session, mock_external_service_dependencies
|
||||
):
|
||||
"""
|
||||
Test app list sort options for modified time and creation time.
|
||||
"""
|
||||
fake = Faker()
|
||||
|
||||
account = AccountService.create_account(
|
||||
email=fake.email(),
|
||||
name=fake.name(),
|
||||
interface_language="en-US",
|
||||
password=generate_valid_password(fake),
|
||||
)
|
||||
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
|
||||
tenant = account.current_tenant
|
||||
|
||||
from services.app_service import AppListParams, AppService, CreateAppParams
|
||||
|
||||
app_service = AppService()
|
||||
oldest_created = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Oldest Created", mode="chat", icon_type="emoji", icon="1"),
|
||||
account,
|
||||
)
|
||||
newest_modified = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Newest Modified", mode="chat", icon_type="emoji", icon="2"),
|
||||
account,
|
||||
)
|
||||
newest_created = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Newest Created", mode="chat", icon_type="emoji", icon="3"),
|
||||
account,
|
||||
)
|
||||
|
||||
timestamp_by_app_id = {
|
||||
oldest_created.id: (datetime(2026, 1, 1, 10, 0, 0), datetime(2026, 1, 1, 10, 0, 0)),
|
||||
newest_modified.id: (datetime(2026, 1, 2, 10, 0, 0), datetime(2026, 1, 4, 10, 0, 0)),
|
||||
newest_created.id: (datetime(2026, 1, 3, 10, 0, 0), datetime(2026, 1, 3, 10, 0, 0)),
|
||||
}
|
||||
for app_id, (created_at, updated_at) in timestamp_by_app_id.items():
|
||||
db_session_with_containers.execute(
|
||||
sa.update(App).where(App.id == app_id).values(created_at=created_at, updated_at=updated_at)
|
||||
)
|
||||
db_session_with_containers.commit()
|
||||
|
||||
last_modified_apps = app_service.get_paginate_apps(
|
||||
account.id, tenant.id, AppListParams(page=1, limit=10, mode="chat")
|
||||
)
|
||||
assert last_modified_apps is not None
|
||||
assert [app.name for app in last_modified_apps.items] == [
|
||||
"Newest Modified",
|
||||
"Newest Created",
|
||||
"Oldest Created",
|
||||
]
|
||||
|
||||
recently_created_apps = app_service.get_paginate_apps(
|
||||
account.id, tenant.id, AppListParams(page=1, limit=10, mode="chat", sort_by="recently_created")
|
||||
)
|
||||
assert recently_created_apps is not None
|
||||
assert [app.name for app in recently_created_apps.items] == [
|
||||
"Newest Created",
|
||||
"Newest Modified",
|
||||
"Oldest Created",
|
||||
]
|
||||
|
||||
earliest_created_apps = app_service.get_paginate_apps(
|
||||
account.id, tenant.id, AppListParams(page=1, limit=10, mode="chat", sort_by="earliest_created")
|
||||
)
|
||||
assert earliest_created_apps is not None
|
||||
assert [app.name for app in earliest_created_apps.items] == [
|
||||
"Oldest Created",
|
||||
"Newest Modified",
|
||||
"Newest Created",
|
||||
]
|
||||
|
||||
def test_get_paginate_apps_marks_starred_apps(
|
||||
self, db_session_with_containers: Session, mock_external_service_dependencies
|
||||
):
|
||||
"""
|
||||
Test app list marks apps starred by the current account.
|
||||
"""
|
||||
fake = Faker()
|
||||
|
||||
account = AccountService.create_account(
|
||||
email=fake.email(),
|
||||
name=fake.name(),
|
||||
interface_language="en-US",
|
||||
password=generate_valid_password(fake),
|
||||
)
|
||||
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
|
||||
tenant = account.current_tenant
|
||||
|
||||
from models import AppStar
|
||||
from services.app_service import AppListParams, AppService, CreateAppParams
|
||||
|
||||
app_service = AppService()
|
||||
starred_app = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Starred App", mode="chat", icon_type="emoji", icon="1"),
|
||||
account,
|
||||
)
|
||||
unstarred_app = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Unstarred App", mode="chat", icon_type="emoji", icon="2"),
|
||||
account,
|
||||
)
|
||||
|
||||
app_service.star_app(db_session_with_containers, app=starred_app, account_id=account.id)
|
||||
app_service.star_app(db_session_with_containers, app=starred_app, account_id=account.id)
|
||||
db_session_with_containers.commit()
|
||||
|
||||
star_count = db_session_with_containers.scalar(
|
||||
sa.select(sa.func.count()).select_from(AppStar).where(AppStar.app_id == starred_app.id)
|
||||
)
|
||||
assert star_count == 1
|
||||
|
||||
paginated_apps = app_service.get_paginate_apps(
|
||||
account.id, tenant.id, AppListParams(page=1, limit=10, mode="chat")
|
||||
)
|
||||
assert paginated_apps is not None
|
||||
starred_by_app_id = {app.id: app.is_starred for app in paginated_apps.items}
|
||||
assert starred_by_app_id[starred_app.id] is True
|
||||
assert starred_by_app_id[unstarred_app.id] is False
|
||||
|
||||
app_service.unstar_app(db_session_with_containers, app=starred_app, account_id=account.id)
|
||||
db_session_with_containers.commit()
|
||||
|
||||
paginated_apps = app_service.get_paginate_apps(
|
||||
account.id, tenant.id, AppListParams(page=1, limit=10, mode="chat")
|
||||
)
|
||||
assert paginated_apps is not None
|
||||
starred_by_app_id = {app.id: app.is_starred for app in paginated_apps.items}
|
||||
assert starred_by_app_id[starred_app.id] is False
|
||||
assert starred_by_app_id[unstarred_app.id] is False
|
||||
|
||||
def test_get_paginate_starred_apps_returns_only_starred_apps_with_requested_sort(
|
||||
self, db_session_with_containers: Session, mock_external_service_dependencies
|
||||
):
|
||||
"""
|
||||
Test starred app list returns only starred apps ordered by requested app sort.
|
||||
"""
|
||||
fake = Faker()
|
||||
|
||||
account = AccountService.create_account(
|
||||
email=fake.email(),
|
||||
name=fake.name(),
|
||||
interface_language="en-US",
|
||||
password=generate_valid_password(fake),
|
||||
)
|
||||
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
|
||||
tenant = account.current_tenant
|
||||
|
||||
from services.app_service import AppService, CreateAppParams, StarredAppListParams
|
||||
|
||||
app_service = AppService()
|
||||
oldest_created_app = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Oldest Created Starred App", mode="chat", icon_type="emoji", icon="1"),
|
||||
account,
|
||||
)
|
||||
newest_modified_app = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Newest Modified Starred App", mode="chat", icon_type="emoji", icon="2"),
|
||||
account,
|
||||
)
|
||||
newest_created_app = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Newest Created Starred App", mode="chat", icon_type="emoji", icon="3"),
|
||||
account,
|
||||
)
|
||||
unstarred_app = app_service.create_app(
|
||||
tenant.id,
|
||||
CreateAppParams(name="Unstarred App", mode="chat", icon_type="emoji", icon="4"),
|
||||
account,
|
||||
)
|
||||
|
||||
app_service.star_app(db_session_with_containers, app=oldest_created_app, account_id=account.id)
|
||||
app_service.star_app(db_session_with_containers, app=newest_modified_app, account_id=account.id)
|
||||
app_service.star_app(db_session_with_containers, app=newest_created_app, account_id=account.id)
|
||||
|
||||
timestamp_by_app_id = {
|
||||
oldest_created_app.id: (datetime(2026, 1, 1, 10, 0, 0), datetime(2026, 1, 1, 10, 0, 0)),
|
||||
newest_modified_app.id: (datetime(2026, 1, 2, 10, 0, 0), datetime(2026, 1, 4, 10, 0, 0)),
|
||||
newest_created_app.id: (datetime(2026, 1, 3, 10, 0, 0), datetime(2026, 1, 3, 10, 0, 0)),
|
||||
unstarred_app.id: (datetime(2026, 1, 5, 10, 0, 0), datetime(2026, 1, 5, 10, 0, 0)),
|
||||
}
|
||||
for app_id, (created_at, updated_at) in timestamp_by_app_id.items():
|
||||
db_session_with_containers.execute(
|
||||
sa.update(App).where(App.id == app_id).values(created_at=created_at, updated_at=updated_at)
|
||||
)
|
||||
db_session_with_containers.commit()
|
||||
|
||||
last_modified_apps = app_service.get_paginate_starred_apps(
|
||||
account.id, tenant.id, StarredAppListParams(page=1, limit=10, mode="chat")
|
||||
)
|
||||
assert last_modified_apps is not None
|
||||
assert [app.name for app in last_modified_apps.items] == [
|
||||
"Newest Modified Starred App",
|
||||
"Newest Created Starred App",
|
||||
"Oldest Created Starred App",
|
||||
]
|
||||
assert all(app.is_starred for app in last_modified_apps.items)
|
||||
assert unstarred_app.id not in {app.id for app in last_modified_apps.items}
|
||||
|
||||
recently_created_apps = app_service.get_paginate_starred_apps(
|
||||
account.id,
|
||||
tenant.id,
|
||||
StarredAppListParams(page=1, limit=10, mode="chat", sort_by="recently_created"),
|
||||
)
|
||||
assert recently_created_apps is not None
|
||||
assert [app.name for app in recently_created_apps.items] == [
|
||||
"Newest Created Starred App",
|
||||
"Newest Modified Starred App",
|
||||
"Oldest Created Starred App",
|
||||
]
|
||||
|
||||
earliest_created_apps = app_service.get_paginate_starred_apps(
|
||||
account.id,
|
||||
tenant.id,
|
||||
StarredAppListParams(page=1, limit=10, mode="chat", sort_by="earliest_created"),
|
||||
)
|
||||
assert earliest_created_apps is not None
|
||||
assert [app.name for app in earliest_created_apps.items] == [
|
||||
"Oldest Created Starred App",
|
||||
"Newest Modified Starred App",
|
||||
"Newest Created Starred App",
|
||||
]
|
||||
|
||||
def test_get_paginate_apps_with_filters(
|
||||
self, db_session_with_containers: Session, mock_external_service_dependencies
|
||||
):
|
||||
|
||||
@ -263,6 +263,54 @@ class TestRecommendedAppServiceGetDetail:
|
||||
mock_factory_class.get_recommend_app_factory.assert_called_with(mode)
|
||||
|
||||
|
||||
# ── Pure logic tests: get_learn_dify_apps ──────────────────────────────
|
||||
|
||||
|
||||
class TestRecommendedAppServiceGetLearnDifyApps:
|
||||
def test_returns_database_learn_dify_apps_without_remote_factory(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
expected_app = RecommendedAppPayload(app_id="app-1", category="Workflow")
|
||||
mock_database_retrieval = MagicMock()
|
||||
mock_database_retrieval.fetch_learn_dify_apps_from_db.return_value = {
|
||||
"recommended_apps": [expected_app],
|
||||
"categories": ["Workflow"],
|
||||
}
|
||||
monkeypatch.setattr(service_module, "DatabaseRecommendAppRetrieval", mock_database_retrieval)
|
||||
monkeypatch.setattr(
|
||||
service_module.FeatureService,
|
||||
"get_system_features",
|
||||
MagicMock(return_value=SimpleNamespace(enable_trial_app=False)),
|
||||
)
|
||||
factory_mock = MagicMock()
|
||||
monkeypatch.setattr(service_module.RecommendAppRetrievalFactory, "get_recommend_app_factory", factory_mock)
|
||||
|
||||
result = RecommendedAppService.get_learn_dify_apps(db.session, "en-US")
|
||||
|
||||
assert result == {"recommended_apps": [expected_app]}
|
||||
mock_database_retrieval.fetch_learn_dify_apps_from_db.assert_called_once_with("en-US")
|
||||
factory_mock.assert_not_called()
|
||||
|
||||
def test_sets_can_trial_when_trial_feature_enabled(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
app = RecommendedAppPayload(app_id="app-1", category="Workflow")
|
||||
mock_database_retrieval = MagicMock()
|
||||
mock_database_retrieval.fetch_learn_dify_apps_from_db.return_value = {
|
||||
"recommended_apps": [app],
|
||||
"categories": ["Workflow"],
|
||||
}
|
||||
monkeypatch.setattr(service_module, "DatabaseRecommendAppRetrieval", mock_database_retrieval)
|
||||
monkeypatch.setattr(
|
||||
service_module.FeatureService,
|
||||
"get_system_features",
|
||||
MagicMock(return_value=SimpleNamespace(enable_trial_app=True)),
|
||||
)
|
||||
can_trial_mock = MagicMock(return_value=True)
|
||||
monkeypatch.setattr(RecommendedAppService, "_can_trial_app", can_trial_mock)
|
||||
|
||||
result = RecommendedAppService.get_learn_dify_apps(db.session, "en-US")
|
||||
|
||||
assert result["recommended_apps"][0]["can_trial"] is True
|
||||
can_trial_mock.assert_called_once_with(db.session, "app-1")
|
||||
|
||||
|
||||
# ── Integration tests: trial app features (real DB) ────────────────────
|
||||
|
||||
|
||||
|
||||
@ -79,6 +79,46 @@ class TestRecommendedAppListApi:
|
||||
assert result == result_data
|
||||
|
||||
|
||||
class TestLearnDifyAppListApi:
|
||||
def test_get_with_language_param(self, app: Flask):
|
||||
api = module.LearnDifyAppListApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
result_data = {"recommended_apps": []}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", query_string={"language": "en-US"}),
|
||||
patch.object(
|
||||
module.RecommendedAppService,
|
||||
"get_learn_dify_apps",
|
||||
return_value=result_data,
|
||||
) as service_mock,
|
||||
):
|
||||
result = method(api, make_account("fr-FR"))
|
||||
|
||||
service_mock.assert_called_once_with(ANY, "en-US")
|
||||
assert result == result_data
|
||||
|
||||
def test_get_fallback_to_user_language(self, app: Flask):
|
||||
api = module.LearnDifyAppListApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
result_data = {"recommended_apps": []}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", query_string={"language": "invalid"}),
|
||||
patch.object(
|
||||
module.RecommendedAppService,
|
||||
"get_learn_dify_apps",
|
||||
return_value=result_data,
|
||||
) as service_mock,
|
||||
):
|
||||
result = method(api, make_account("fr-FR"))
|
||||
|
||||
service_mock.assert_called_once_with(ANY, "fr-FR")
|
||||
assert result == result_data
|
||||
|
||||
|
||||
class TestRecommendedAppApi:
|
||||
def test_get_success(self, app: Flask):
|
||||
api = module.RecommendedAppApi()
|
||||
@ -144,3 +184,29 @@ class TestRecommendedAppResponseModels:
|
||||
assert response["recommended_apps"][0]["app_id"] == "app-1"
|
||||
assert response["recommended_apps"][0]["categories"] == ["cat", "other"]
|
||||
assert response["categories"] == ["cat"]
|
||||
|
||||
def test_learn_dify_app_list_response_serialization(self):
|
||||
response = module.LearnDifyAppListResponse.model_validate(
|
||||
{
|
||||
"recommended_apps": [
|
||||
{
|
||||
"app": {
|
||||
"id": "app-1",
|
||||
"name": "App",
|
||||
"mode": "chat",
|
||||
"icon": "icon.png",
|
||||
"icon_type": "emoji",
|
||||
"icon_background": "#fff",
|
||||
},
|
||||
"app_id": "app-1",
|
||||
"description": "desc",
|
||||
"categories": ["Workflow"],
|
||||
"position": 1,
|
||||
"is_listed": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
).model_dump(mode="json")
|
||||
|
||||
assert response["recommended_apps"][0]["app_id"] == "app-1"
|
||||
assert response["recommended_apps"][0]["categories"] == ["Workflow"]
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import io
|
||||
from datetime import datetime
|
||||
from inspect import unwrap
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@ -10,12 +12,14 @@ from werkzeug.exceptions import Forbidden
|
||||
from controllers.console.workspace.plugin import (
|
||||
PluginAssetApi,
|
||||
PluginAutoUpgradeExcludePluginApi,
|
||||
PluginCategoryListApi,
|
||||
PluginChangeAutoUpgradeApi,
|
||||
PluginChangePermissionApi,
|
||||
PluginChangePreferencesApi,
|
||||
PluginDebuggingKeyApi,
|
||||
PluginDeleteAllInstallTaskItemsApi,
|
||||
PluginDeleteInstallTaskApi,
|
||||
PluginDeleteInstallTaskItemApi,
|
||||
PluginFetchAutoUpgradeApi,
|
||||
PluginFetchDynamicSelectOptionsApi,
|
||||
PluginFetchDynamicSelectOptionsWithCredentialsApi,
|
||||
PluginFetchInstallTaskApi,
|
||||
@ -23,7 +27,6 @@ from controllers.console.workspace.plugin import (
|
||||
PluginFetchManifestApi,
|
||||
PluginFetchMarketplacePkgApi,
|
||||
PluginFetchPermissionApi,
|
||||
PluginFetchPreferencesApi,
|
||||
PluginIconApi,
|
||||
PluginInstallFromGithubApi,
|
||||
PluginInstallFromMarketplaceApi,
|
||||
@ -43,6 +46,69 @@ from core.plugin.impl.exc import PluginDaemonClientSideError
|
||||
from models.account import Account, TenantAccountRole, TenantPluginAutoUpgradeStrategy, TenantPluginPermission
|
||||
|
||||
|
||||
def _plugin_category_list_item(category: str = "tool") -> dict[str, Any]:
|
||||
now = datetime(2023, 1, 1, 0, 0, 0)
|
||||
return {
|
||||
"id": "entity-1",
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
"tenant_id": "t1",
|
||||
"endpoints_setups": 0,
|
||||
"endpoints_active": 0,
|
||||
"runtime_type": "remote",
|
||||
"source": "marketplace",
|
||||
"meta": {},
|
||||
"plugin_id": "test-author/test-plugin",
|
||||
"plugin_unique_identifier": "test-author/test-plugin:1.0.0@checksum",
|
||||
"version": "1.0.0",
|
||||
"checksum": "checksum",
|
||||
"name": "test-plugin",
|
||||
"installation_id": "entity-1",
|
||||
"declaration": {
|
||||
"version": "1.0.0",
|
||||
"author": "test-author",
|
||||
"name": "test-plugin",
|
||||
"description": {"en_US": "Test plugin"},
|
||||
"icon": "icon.svg",
|
||||
"label": {"en_US": "Test Plugin"},
|
||||
"category": category,
|
||||
"created_at": now,
|
||||
"resource": {"memory": 268435456, "permission": None},
|
||||
"plugins": {"tools": ["provider/test.yaml"]},
|
||||
"meta": {"version": "1.0.0"},
|
||||
"tool": {
|
||||
"identity": {
|
||||
"author": "test-author",
|
||||
"name": "test-plugin",
|
||||
"description": {"en_US": "Test plugin"},
|
||||
"icon": "icon.svg",
|
||||
"label": {"en_US": "Test Plugin"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _builtin_tool_provider_item() -> dict[str, Any]:
|
||||
return {
|
||||
"id": "builtin",
|
||||
"author": "dify",
|
||||
"name": "builtin",
|
||||
"plugin_id": "",
|
||||
"plugin_unique_identifier": "",
|
||||
"description": {"en_US": "Builtin tool provider"},
|
||||
"icon": "icon.svg",
|
||||
"icon_dark": "",
|
||||
"label": {"en_US": "Builtin"},
|
||||
"type": "builtin",
|
||||
"team_credentials": {},
|
||||
"is_team_authorization": False,
|
||||
"allow_delete": True,
|
||||
"tools": [],
|
||||
"labels": [],
|
||||
}
|
||||
|
||||
|
||||
def _account(role: TenantAccountRole = TenantAccountRole.OWNER) -> Account:
|
||||
account = Account(name="Test User", email="u1@example.com")
|
||||
account.id = "u1"
|
||||
@ -142,6 +208,83 @@ class TestPluginListApi:
|
||||
mock_list_with_total.assert_called_once_with("t1", "u1", 1, 10)
|
||||
|
||||
|
||||
class TestPluginCategoryListApi:
|
||||
def test_plugin_category_list(self, app: Flask):
|
||||
api = PluginCategoryListApi()
|
||||
method = unwrap(api.get)
|
||||
plugin_item = _plugin_category_list_item()
|
||||
builtin_item = _builtin_tool_provider_item()
|
||||
mock_list = MagicMock(list=[plugin_item], has_more=True)
|
||||
|
||||
with (
|
||||
app.test_request_context("/?page=2&page_size=10"),
|
||||
patch(
|
||||
"controllers.console.workspace.plugin.PluginService.list_by_category", return_value=mock_list
|
||||
) as list_mock,
|
||||
patch(
|
||||
"controllers.console.workspace.plugin._list_hardcoded_builtin_tool_providers",
|
||||
return_value=[builtin_item],
|
||||
) as builtin_mock,
|
||||
):
|
||||
result = method(api, "t1", "tool")
|
||||
|
||||
list_mock.assert_called_once()
|
||||
assert list_mock.call_args.args[0] == "t1"
|
||||
assert list_mock.call_args.args[1] == "tool"
|
||||
assert list_mock.call_args.args[2] == 2
|
||||
assert list_mock.call_args.args[3] == 10
|
||||
assert result["plugins"][0]["id"] == "entity-1"
|
||||
assert result["plugins"][0]["plugin_unique_identifier"] == "test-author/test-plugin:1.0.0@checksum"
|
||||
assert result["builtin_tools"][0]["id"] == "builtin"
|
||||
assert result["builtin_tools"][0]["type"] == "builtin"
|
||||
assert result["has_more"] is True
|
||||
assert "total" not in result
|
||||
builtin_mock.assert_called_once_with("t1")
|
||||
|
||||
def test_non_tool_category_does_not_include_builtin_tools(self, app: Flask):
|
||||
api = PluginCategoryListApi()
|
||||
method = unwrap(api.get)
|
||||
mock_list = MagicMock(list=[_plugin_category_list_item(category="datasource")], has_more=False)
|
||||
|
||||
with (
|
||||
app.test_request_context("/?page=1&page_size=10"),
|
||||
patch("controllers.console.workspace.plugin.PluginService.list_by_category", return_value=mock_list),
|
||||
patch("controllers.console.workspace.plugin._list_hardcoded_builtin_tool_providers") as builtin_mock,
|
||||
):
|
||||
result = method(api, "t1", "datasource")
|
||||
|
||||
assert result["plugins"][0]["id"] == "entity-1"
|
||||
assert result["builtin_tools"] == []
|
||||
assert result["has_more"] is False
|
||||
builtin_mock.assert_not_called()
|
||||
|
||||
def test_invalid_category(self, app: Flask):
|
||||
api = PluginCategoryListApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
with (
|
||||
app.test_request_context("/?page=1&page_size=10"),
|
||||
):
|
||||
result = method(api, "t1", "unknown")
|
||||
|
||||
assert result == ({"code": "invalid_param", "message": "invalid plugin category"}, 400)
|
||||
|
||||
def test_daemon_error(self, app: Flask):
|
||||
api = PluginCategoryListApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
with (
|
||||
app.test_request_context("/?page=1&page_size=10"),
|
||||
patch(
|
||||
"controllers.console.workspace.plugin.PluginService.list_by_category",
|
||||
side_effect=PluginDaemonClientSideError("error"),
|
||||
),
|
||||
):
|
||||
result = method(api, "t1", "tool")
|
||||
|
||||
assert result == ({"code": "plugin_error", "message": "error"}, 400)
|
||||
|
||||
|
||||
class TestPluginIconApi:
|
||||
def test_plugin_icon(self, app: Flask):
|
||||
api = PluginIconApi()
|
||||
@ -857,18 +1000,15 @@ class TestPluginFetchDynamicSelectOptionsWithCredentialsApi:
|
||||
assert result == ({"code": "plugin_error", "message": "error"}, 400)
|
||||
|
||||
|
||||
class TestPluginChangePreferencesApi:
|
||||
class TestPluginChangeAutoUpgradeApi:
|
||||
def test_success(self, app: Flask):
|
||||
api = PluginChangePreferencesApi()
|
||||
api = PluginChangeAutoUpgradeApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
user = _account()
|
||||
|
||||
payload = {
|
||||
"permission": {
|
||||
"install_permission": TenantPluginPermission.InstallPermission.EVERYONE,
|
||||
"debug_permission": TenantPluginPermission.DebugPermission.EVERYONE,
|
||||
},
|
||||
"category": TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value,
|
||||
"auto_upgrade": {
|
||||
"strategy_setting": TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
"upgrade_time_of_day": 0,
|
||||
@ -880,24 +1020,52 @@ class TestPluginChangePreferencesApi:
|
||||
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.plugin.PluginPermissionService.change_permission", return_value=True),
|
||||
patch("controllers.console.workspace.plugin.PluginAutoUpgradeService.change_strategy", return_value=True),
|
||||
patch(
|
||||
"controllers.console.workspace.plugin.PluginAutoUpgradeService.change_strategy", return_value=True
|
||||
) as change,
|
||||
):
|
||||
result = method(api, "t1", user)
|
||||
|
||||
assert result["success"] is True
|
||||
change.assert_called_once()
|
||||
|
||||
def test_permission_fail(self, app: Flask):
|
||||
api = PluginChangePreferencesApi()
|
||||
def test_success_with_model_category_auto_upgrade(self, app: Flask):
|
||||
api = PluginChangeAutoUpgradeApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
user = _account()
|
||||
|
||||
payload = {
|
||||
"permission": {
|
||||
"install_permission": TenantPluginPermission.InstallPermission.EVERYONE,
|
||||
"debug_permission": TenantPluginPermission.DebugPermission.EVERYONE,
|
||||
"category": TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL.value,
|
||||
"auto_upgrade": {
|
||||
"strategy_setting": TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST,
|
||||
"upgrade_time_of_day": 3600,
|
||||
"upgrade_mode": TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL,
|
||||
"exclude_plugins": [],
|
||||
"include_plugins": [],
|
||||
},
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch(
|
||||
"controllers.console.workspace.plugin.PluginAutoUpgradeService.change_strategy", return_value=True
|
||||
) as change,
|
||||
):
|
||||
result = method(api, "t1", user)
|
||||
|
||||
assert result["success"] is True
|
||||
change.assert_called_once()
|
||||
assert change.call_args.kwargs["category"] == TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL
|
||||
|
||||
def test_auto_upgrade_fail(self, app: Flask):
|
||||
api = PluginChangeAutoUpgradeApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
user = MagicMock(is_admin_or_owner=True)
|
||||
|
||||
payload = {
|
||||
"category": TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value,
|
||||
"auto_upgrade": {
|
||||
"strategy_setting": TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
"upgrade_time_of_day": 0,
|
||||
@ -909,24 +1077,20 @@ class TestPluginChangePreferencesApi:
|
||||
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.plugin.PluginPermissionService.change_permission", return_value=False),
|
||||
patch("controllers.console.workspace.plugin.PluginAutoUpgradeService.change_strategy", return_value=False),
|
||||
):
|
||||
result = method(api, "t1", user)
|
||||
|
||||
assert result["success"] is False
|
||||
|
||||
|
||||
class TestPluginFetchPreferencesApi:
|
||||
class TestPluginFetchAutoUpgradeApi:
|
||||
def test_success(self, app: Flask):
|
||||
api = PluginFetchPreferencesApi()
|
||||
api = PluginFetchAutoUpgradeApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
permission = MagicMock(
|
||||
install_permission=TenantPluginPermission.InstallPermission.EVERYONE,
|
||||
debug_permission=TenantPluginPermission.DebugPermission.EVERYONE,
|
||||
)
|
||||
|
||||
auto_upgrade = MagicMock(
|
||||
category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL,
|
||||
strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
upgrade_time_of_day=1,
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
@ -935,18 +1099,16 @@ class TestPluginFetchPreferencesApi:
|
||||
)
|
||||
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
app.test_request_context(f"/?category={TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value}"),
|
||||
patch(
|
||||
"controllers.console.workspace.plugin.PluginPermissionService.get_permission", return_value=permission
|
||||
),
|
||||
patch(
|
||||
"controllers.console.workspace.plugin.PluginAutoUpgradeService.get_strategy", return_value=auto_upgrade
|
||||
"controllers.console.workspace.plugin.PluginAutoUpgradeService.get_strategy",
|
||||
return_value=auto_upgrade,
|
||||
),
|
||||
):
|
||||
result = method(api, "t1")
|
||||
|
||||
assert "permission" in result
|
||||
assert "auto_upgrade" in result
|
||||
assert result["category"] == TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL
|
||||
assert result["auto_upgrade"]["upgrade_time_of_day"] == 1
|
||||
|
||||
|
||||
class TestPluginAutoUpgradeExcludePluginApi:
|
||||
@ -954,7 +1116,7 @@ class TestPluginAutoUpgradeExcludePluginApi:
|
||||
api = PluginAutoUpgradeExcludePluginApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
payload = {"plugin_id": "p"}
|
||||
payload = {"plugin_id": "p", "category": TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
@ -968,7 +1130,7 @@ class TestPluginAutoUpgradeExcludePluginApi:
|
||||
api = PluginAutoUpgradeExcludePluginApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
payload = {"plugin_id": "p"}
|
||||
payload = {"plugin_id": "p", "category": TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
|
||||
@ -53,6 +53,12 @@ def make_tenant(
|
||||
return tenant
|
||||
|
||||
|
||||
def make_membership(*, last_opened_at=None) -> MagicMock:
|
||||
membership = MagicMock()
|
||||
membership.last_opened_at = last_opened_at
|
||||
return membership
|
||||
|
||||
|
||||
def make_account_with_tenant(tenant: Tenant) -> Account:
|
||||
account = make_account()
|
||||
account._current_tenant = tenant
|
||||
@ -66,13 +72,17 @@ class TestTenantListApi:
|
||||
|
||||
tenant1 = make_tenant("t1", name="Tenant 1")
|
||||
tenant2 = make_tenant("t2", name="Tenant 2")
|
||||
last_opened_at = naive_utc_now()
|
||||
user = make_account()
|
||||
|
||||
with (
|
||||
app.test_request_context("/workspaces"),
|
||||
patch(
|
||||
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
|
||||
return_value=[tenant1, tenant2],
|
||||
"controllers.console.workspace.workspace.TenantService.get_workspaces_for_account",
|
||||
return_value=[
|
||||
(tenant1, make_membership(last_opened_at=last_opened_at)),
|
||||
(tenant2, make_membership()),
|
||||
],
|
||||
),
|
||||
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", False),
|
||||
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", True),
|
||||
@ -92,7 +102,9 @@ class TestTenantListApi:
|
||||
assert len(result["workspaces"]) == 2
|
||||
assert result["workspaces"][0]["current"] is True
|
||||
assert result["workspaces"][0]["plan"] == CloudPlan.TEAM
|
||||
assert result["workspaces"][0]["last_opened_at"] == int(last_opened_at.timestamp())
|
||||
assert result["workspaces"][1]["plan"] == CloudPlan.PROFESSIONAL
|
||||
assert result["workspaces"][1]["last_opened_at"] is None
|
||||
get_plan_bulk_mock.assert_called_once_with(["t1", "t2"])
|
||||
get_features_mock.assert_not_called()
|
||||
|
||||
@ -116,8 +128,8 @@ class TestTenantListApi:
|
||||
with (
|
||||
app.test_request_context("/workspaces"),
|
||||
patch(
|
||||
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
|
||||
return_value=[tenant1, tenant2],
|
||||
"controllers.console.workspace.workspace.TenantService.get_workspaces_for_account",
|
||||
return_value=[(tenant1, make_membership()), (tenant2, make_membership())],
|
||||
),
|
||||
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", False),
|
||||
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", True),
|
||||
@ -159,8 +171,8 @@ class TestTenantListApi:
|
||||
with (
|
||||
app.test_request_context("/workspaces"),
|
||||
patch(
|
||||
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
|
||||
return_value=[tenant1, tenant2],
|
||||
"controllers.console.workspace.workspace.TenantService.get_workspaces_for_account",
|
||||
return_value=[(tenant1, make_membership()), (tenant2, make_membership())],
|
||||
),
|
||||
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", False),
|
||||
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", True),
|
||||
@ -198,8 +210,8 @@ class TestTenantListApi:
|
||||
with (
|
||||
app.test_request_context("/workspaces"),
|
||||
patch(
|
||||
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
|
||||
return_value=[tenant],
|
||||
"controllers.console.workspace.workspace.TenantService.get_workspaces_for_account",
|
||||
return_value=[(tenant, make_membership())],
|
||||
),
|
||||
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", False),
|
||||
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", False),
|
||||
@ -226,8 +238,8 @@ class TestTenantListApi:
|
||||
with (
|
||||
app.test_request_context("/workspaces"),
|
||||
patch(
|
||||
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
|
||||
return_value=[tenant1, tenant2],
|
||||
"controllers.console.workspace.workspace.TenantService.get_workspaces_for_account",
|
||||
return_value=[(tenant1, make_membership()), (tenant2, make_membership())],
|
||||
),
|
||||
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", True),
|
||||
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", False),
|
||||
@ -251,7 +263,7 @@ class TestTenantListApi:
|
||||
with (
|
||||
app.test_request_context("/workspaces"),
|
||||
patch(
|
||||
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
|
||||
"controllers.console.workspace.workspace.TenantService.get_workspaces_for_account",
|
||||
return_value=[],
|
||||
),
|
||||
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", True),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""OpenAPI JSON rendering tests for Flask-RESTX API blueprints."""
|
||||
|
||||
import json
|
||||
from collections.abc import Iterator
|
||||
|
||||
import pytest
|
||||
@ -187,3 +188,39 @@ def test_console_account_avatar_query_param_renders_as_query(monkeypatch: pytest
|
||||
assert "payload" not in params
|
||||
assert params["avatar"]["in"] == "query"
|
||||
assert params["avatar"]["required"] is True
|
||||
|
||||
|
||||
def test_console_plugin_category_list_exported_schema_uses_typed_items(tmp_path):
|
||||
from dev.generate_swagger_specs import generate_specs
|
||||
|
||||
written_paths = generate_specs(tmp_path)
|
||||
console_openapi_path = next(path for path in written_paths if path.name == "console-openapi.json")
|
||||
payload = json.loads(console_openapi_path.read_text(encoding="utf-8"))
|
||||
operation = payload["paths"]["/workspaces/current/plugin/{category}/list"]["get"]
|
||||
response_ref = operation["responses"]["200"]["content"]["application/json"]["schema"]["$ref"].removeprefix(
|
||||
"#/components/schemas/"
|
||||
)
|
||||
schemas = payload["components"]["schemas"]
|
||||
response_schema = schemas[response_ref]
|
||||
|
||||
assert response_schema["properties"]["plugins"]["items"]["$ref"] == (
|
||||
"#/components/schemas/PluginCategoryInstalledPluginResponse"
|
||||
)
|
||||
assert response_schema["properties"]["builtin_tools"]["items"]["$ref"] == (
|
||||
"#/components/schemas/PluginCategoryBuiltinToolProviderResponse"
|
||||
)
|
||||
|
||||
installed_plugin_schema = schemas["PluginCategoryInstalledPluginResponse"]
|
||||
for field in (
|
||||
"plugin_unique_identifier",
|
||||
"source",
|
||||
"version",
|
||||
"declaration",
|
||||
"endpoints_active",
|
||||
"endpoints_setups",
|
||||
):
|
||||
assert field in installed_plugin_schema["properties"]
|
||||
|
||||
builtin_tool_schema = schemas["PluginCategoryBuiltinToolProviderResponse"]
|
||||
for field in ("plugin_unique_identifier", "team_credentials", "type", "tools"):
|
||||
assert field in builtin_tool_schema["properties"]
|
||||
|
||||
@ -33,6 +33,7 @@ from core.plugin.entities.plugin_daemon import (
|
||||
PluginInstallTaskStartResponse,
|
||||
PluginInstallTaskStatus,
|
||||
PluginListResponse,
|
||||
PluginListWithoutTotalResponse,
|
||||
PluginReadmeResponse,
|
||||
PluginVerification,
|
||||
)
|
||||
@ -123,6 +124,26 @@ class TestPluginDiscovery:
|
||||
assert call_args[1]["params"]["page_size"] == 5
|
||||
assert result.total == 10
|
||||
|
||||
def test_list_plugins_by_category(self, plugin_installer, mock_plugin_entity):
|
||||
"""Test category plugin listing without total."""
|
||||
mock_response = PluginListWithoutTotalResponse(list=[mock_plugin_entity], has_more=True)
|
||||
|
||||
with patch.object(
|
||||
plugin_installer, "_request_with_plugin_daemon_response", return_value=mock_response
|
||||
) as mock_request:
|
||||
result = plugin_installer.list_plugins_by_category(
|
||||
"test-tenant", category=PluginCategory.Tool, page=2, page_size=10
|
||||
)
|
||||
|
||||
mock_request.assert_called_once()
|
||||
call_args = mock_request.call_args
|
||||
assert call_args.args[1] == "plugin/test-tenant/management/tool/list"
|
||||
assert call_args.args[2] is PluginListWithoutTotalResponse
|
||||
assert call_args.kwargs["params"]["page"] == 2
|
||||
assert call_args.kwargs["params"]["page_size"] == 10
|
||||
assert result.list == [mock_plugin_entity]
|
||||
assert result.has_more is True
|
||||
|
||||
def test_list_plugins_empty_result(self, plugin_installer):
|
||||
"""Test plugin listing when no plugins are installed."""
|
||||
# Arrange: Mock empty response
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from models.account import TenantPluginAutoUpgradeStrategy
|
||||
|
||||
MODULE = "services.plugin.plugin_auto_upgrade_service"
|
||||
PLUGIN_CATEGORY = TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL
|
||||
|
||||
|
||||
def _patched_session():
|
||||
@ -25,7 +27,7 @@ class TestGetStrategy:
|
||||
with p1:
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.get_strategy("t1")
|
||||
result = PluginAutoUpgradeService.get_strategy("t1", PLUGIN_CATEGORY)
|
||||
|
||||
assert result is strategy
|
||||
|
||||
@ -36,7 +38,7 @@ class TestGetStrategy:
|
||||
with p1:
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.get_strategy("t1")
|
||||
result = PluginAutoUpgradeService.get_strategy("t1", PLUGIN_CATEGORY)
|
||||
|
||||
assert result is None
|
||||
|
||||
@ -57,6 +59,7 @@ class TestChangeStrategy:
|
||||
TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL,
|
||||
[],
|
||||
[],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
|
||||
assert result is True
|
||||
@ -77,6 +80,7 @@ class TestChangeStrategy:
|
||||
TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL,
|
||||
["p1"],
|
||||
["p2"],
|
||||
category=PLUGIN_CATEGORY,
|
||||
)
|
||||
|
||||
assert result is True
|
||||
@ -96,17 +100,19 @@ class TestExcludePlugin:
|
||||
p1,
|
||||
patch(f"{MODULE}.select"),
|
||||
patch(f"{MODULE}.TenantPluginAutoUpgradeStrategy") as strat_cls,
|
||||
patch(f"{MODULE}.PluginAutoUpgradeService.change_strategy") as cs,
|
||||
):
|
||||
strat_cls.StrategySetting.FIX_ONLY = "fix_only"
|
||||
strat_cls.UpgradeMode.EXCLUDE = "exclude"
|
||||
cs.return_value = True
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.exclude_plugin("t1", "plugin-1")
|
||||
result = PluginAutoUpgradeService.exclude_plugin(
|
||||
"t1",
|
||||
"plugin-1",
|
||||
PLUGIN_CATEGORY,
|
||||
)
|
||||
|
||||
assert result is True
|
||||
cs.assert_called_once()
|
||||
session.add.assert_called_once()
|
||||
|
||||
def test_appends_to_exclude_list_in_exclude_mode(self):
|
||||
p1, session = _patched_session()
|
||||
@ -121,7 +127,7 @@ class TestExcludePlugin:
|
||||
strat_cls.UpgradeMode.ALL = "all"
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.exclude_plugin("t1", "p-new")
|
||||
result = PluginAutoUpgradeService.exclude_plugin("t1", "p-new", PLUGIN_CATEGORY)
|
||||
|
||||
assert result is True
|
||||
assert existing.exclude_plugins == ["p-existing", "p-new"]
|
||||
@ -139,7 +145,7 @@ class TestExcludePlugin:
|
||||
strat_cls.UpgradeMode.ALL = "all"
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.exclude_plugin("t1", "p1")
|
||||
result = PluginAutoUpgradeService.exclude_plugin("t1", "p1", PLUGIN_CATEGORY)
|
||||
|
||||
assert result is True
|
||||
assert existing.include_plugins == ["p2"]
|
||||
@ -156,7 +162,7 @@ class TestExcludePlugin:
|
||||
strat_cls.UpgradeMode.ALL = "all"
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.exclude_plugin("t1", "p1")
|
||||
result = PluginAutoUpgradeService.exclude_plugin("t1", "p1", PLUGIN_CATEGORY)
|
||||
|
||||
assert result is True
|
||||
assert existing.upgrade_mode == "exclude"
|
||||
@ -175,6 +181,101 @@ class TestExcludePlugin:
|
||||
strat_cls.UpgradeMode.ALL = "all"
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
PluginAutoUpgradeService.exclude_plugin("t1", "p1")
|
||||
PluginAutoUpgradeService.exclude_plugin("t1", "p1", PLUGIN_CATEGORY)
|
||||
|
||||
assert existing.exclude_plugins == ["p1"]
|
||||
|
||||
|
||||
class TestBackfillStrategyCategories:
|
||||
def test_creates_default_missing_categories_without_fetching_daemon(self):
|
||||
p1, session = _patched_session()
|
||||
tool_strategy = SimpleNamespace(
|
||||
category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL,
|
||||
strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
upgrade_time_of_day=0,
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
exclude_plugins=[],
|
||||
include_plugins=[],
|
||||
)
|
||||
session.scalars.return_value.all.return_value = [tool_strategy]
|
||||
installer = MagicMock()
|
||||
|
||||
with p1, patch(f"{MODULE}.PluginInstaller", return_value=installer):
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.backfill_strategy_categories("t1")
|
||||
expected_time = PluginAutoUpgradeService.default_upgrade_time_of_day("t1")
|
||||
|
||||
assert result.created_count == len(TenantPluginAutoUpgradeStrategy.PluginCategory) - 1
|
||||
assert result.normalized is False
|
||||
installer.list_plugins.assert_not_called()
|
||||
assert tool_strategy.upgrade_time_of_day == expected_time
|
||||
created_strategies = [call.args[0] for call in session.add.call_args_list]
|
||||
model_strategy = next(
|
||||
strategy
|
||||
for strategy in created_strategies
|
||||
if strategy.category == TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL
|
||||
)
|
||||
assert model_strategy.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST
|
||||
assert model_strategy.upgrade_time_of_day == expected_time
|
||||
|
||||
def test_default_upgrade_time_is_aligned_to_fifteen_minutes(self):
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
default_time = PluginAutoUpgradeService.default_upgrade_time_of_day("t1")
|
||||
|
||||
assert default_time % (15 * 60) == 0
|
||||
assert 0 <= default_time < 24 * 60 * 60
|
||||
|
||||
def test_creates_missing_categories_and_splits_known_plugins(self):
|
||||
p1, session = _patched_session()
|
||||
tool_strategy = SimpleNamespace(
|
||||
category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL,
|
||||
strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
upgrade_time_of_day=0,
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
exclude_plugins=["tool-plugin", "model-plugin", "unknown-plugin"],
|
||||
include_plugins=["model-plugin", "tool-plugin"],
|
||||
)
|
||||
model_strategy = SimpleNamespace(
|
||||
category=TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL,
|
||||
strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY,
|
||||
upgrade_time_of_day=0,
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE,
|
||||
exclude_plugins=["tool-plugin", "model-plugin", "unknown-plugin"],
|
||||
include_plugins=["model-plugin", "tool-plugin"],
|
||||
)
|
||||
session.scalars.return_value.all.return_value = [tool_strategy, model_strategy]
|
||||
|
||||
installed_plugins = [
|
||||
SimpleNamespace(
|
||||
plugin_id="tool-plugin",
|
||||
declaration=SimpleNamespace(category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL),
|
||||
),
|
||||
SimpleNamespace(
|
||||
plugin_id="model-plugin",
|
||||
declaration=SimpleNamespace(category=TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL),
|
||||
),
|
||||
]
|
||||
installer = MagicMock()
|
||||
installer.list_plugins.return_value = installed_plugins
|
||||
|
||||
with p1, patch(f"{MODULE}.PluginInstaller", return_value=installer), patch(f"{MODULE}.logger") as logger:
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.backfill_strategy_categories("t1")
|
||||
|
||||
assert result.created_count == len(TenantPluginAutoUpgradeStrategy.PluginCategory) - 2
|
||||
assert result.normalized is True
|
||||
assert session.add.call_count == len(TenantPluginAutoUpgradeStrategy.PluginCategory) - 2
|
||||
assert tool_strategy.exclude_plugins == ["tool-plugin"]
|
||||
assert tool_strategy.include_plugins == ["tool-plugin"]
|
||||
assert model_strategy.exclude_plugins == ["model-plugin"]
|
||||
assert model_strategy.include_plugins == ["model-plugin"]
|
||||
logger.warning.assert_called_once_with(
|
||||
"Skipped unknown plugin IDs while backfilling plugin auto-upgrade strategies: "
|
||||
"tenant_id=%s, field=%s, plugin_ids=%s",
|
||||
"t1",
|
||||
"exclude_plugins",
|
||||
["unknown-plugin"],
|
||||
)
|
||||
|
||||
@ -65,6 +65,7 @@ class TestAccountAssociatedDataFactory:
|
||||
tenant_join.account_id = account_id
|
||||
tenant_join.current = current
|
||||
tenant_join.role = role
|
||||
tenant_join.last_opened_at = kwargs.pop("last_opened_at", None)
|
||||
for key, value in kwargs.items():
|
||||
setattr(tenant_join, key, value)
|
||||
return tenant_join
|
||||
@ -489,11 +490,13 @@ class TestAccountService:
|
||||
# Mock datetime
|
||||
with (
|
||||
patch("services.account_service.datetime") as mock_datetime,
|
||||
patch("services.account_service.naive_utc_now") as mock_naive_utc_now,
|
||||
patch.object(AccountService, "_refresh_account_last_active") as mock_refresh_last_active,
|
||||
):
|
||||
mock_now = datetime.now()
|
||||
mock_datetime.now.return_value = mock_now
|
||||
mock_datetime.UTC = "UTC"
|
||||
mock_naive_utc_now.return_value = mock_now
|
||||
|
||||
# Execute test
|
||||
result = AccountService.load_user("user-123")
|
||||
@ -501,6 +504,7 @@ class TestAccountService:
|
||||
# Verify results
|
||||
assert result == mock_account
|
||||
assert mock_available_tenant.current is True
|
||||
assert mock_available_tenant.last_opened_at == mock_now
|
||||
self._assert_database_operations_called(mock_db_dependencies["db"])
|
||||
mock_refresh_last_active.assert_called_once_with(mock_account)
|
||||
|
||||
@ -922,11 +926,16 @@ class TestTenantService:
|
||||
# Mock scalar for the join query
|
||||
mock_db.session.scalar.return_value = mock_tenant_join
|
||||
|
||||
# Execute test
|
||||
TenantService.switch_tenant(mock_account, "tenant-456")
|
||||
with patch("services.account_service.naive_utc_now") as mock_naive_utc_now:
|
||||
mock_now = datetime(2026, 6, 5, 11, 0, 0)
|
||||
mock_naive_utc_now.return_value = mock_now
|
||||
|
||||
# Execute test
|
||||
TenantService.switch_tenant(mock_account, "tenant-456")
|
||||
|
||||
# Verify tenant was switched
|
||||
assert mock_tenant_join.current is True
|
||||
assert mock_tenant_join.last_opened_at == mock_now
|
||||
self._assert_database_operations_called(mock_db)
|
||||
|
||||
def test_switch_tenant_no_tenant_id(self):
|
||||
|
||||
@ -4,19 +4,25 @@ from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from core.plugin.entities.marketplace import MarketplacePluginSnapshot
|
||||
from core.plugin.entities.plugin import PluginInstallationSource
|
||||
from core.plugin.entities.plugin import PluginCategory, PluginInstallationSource
|
||||
from models.account import TenantPluginAutoUpgradeStrategy
|
||||
|
||||
MODULE = "tasks.process_tenant_plugin_autoupgrade_check_task"
|
||||
|
||||
|
||||
def _make_plugin(plugin_id: str, version: str, source=PluginInstallationSource.Marketplace):
|
||||
def _make_plugin(
|
||||
plugin_id: str,
|
||||
version: str,
|
||||
source=PluginInstallationSource.Marketplace,
|
||||
category: PluginCategory = PluginCategory.Tool,
|
||||
):
|
||||
"""Build a minimal stand-in for a PluginInstallation entry returned by manager.list_plugins."""
|
||||
return SimpleNamespace(
|
||||
plugin_id=plugin_id,
|
||||
version=version,
|
||||
plugin_unique_identifier=f"{plugin_id}:{version}@deadbeef",
|
||||
source=source,
|
||||
declaration=SimpleNamespace(category=category),
|
||||
)
|
||||
|
||||
|
||||
@ -39,6 +45,7 @@ def _run_task(
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL,
|
||||
exclude_plugins=None,
|
||||
include_plugins=None,
|
||||
category=None,
|
||||
):
|
||||
"""
|
||||
Execute the celery task synchronously with mocks for the plugin manager,
|
||||
@ -72,6 +79,7 @@ def _run_task(
|
||||
upgrade_mode,
|
||||
exclude_plugins or [],
|
||||
include_plugins or [],
|
||||
category,
|
||||
)
|
||||
|
||||
return upgrade_mock, upgrade_calls
|
||||
@ -246,6 +254,26 @@ class TestUpgradeMode:
|
||||
assert upgrade_mock.call_count == 1
|
||||
assert calls[0][1] == plugins[0].plugin_unique_identifier
|
||||
|
||||
def test_category_strategy_only_upgrades_matching_category(self):
|
||||
plugins = [
|
||||
_make_plugin("acme/model-provider", "1.0.0", category=PluginCategory.Model),
|
||||
_make_plugin("acme/tool-provider", "1.0.0", category=PluginCategory.Tool),
|
||||
]
|
||||
manifests = [
|
||||
_make_manifest("acme/model-provider", "1.0.1"),
|
||||
_make_manifest("acme/tool-provider", "1.0.1"),
|
||||
]
|
||||
|
||||
upgrade_mock, calls = _run_task(
|
||||
plugins=plugins,
|
||||
manifests=manifests,
|
||||
upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL,
|
||||
category=TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL,
|
||||
)
|
||||
|
||||
upgrade_mock.assert_called_once()
|
||||
assert calls[0][1] == plugins[0].plugin_unique_identifier
|
||||
|
||||
|
||||
class TestErrorIsolation:
|
||||
def test_one_plugin_failure_does_not_block_others(self):
|
||||
|
||||
@ -4,6 +4,7 @@ import pytest
|
||||
|
||||
from libs.archive_storage import ArchiveStorageNotConfiguredError
|
||||
from tasks.remove_app_and_related_data_task import (
|
||||
_delete_app_stars,
|
||||
_delete_app_workflow_archive_logs,
|
||||
_delete_archived_workflow_run_files,
|
||||
_delete_draft_variable_offload_data,
|
||||
@ -89,6 +90,27 @@ class TestDeleteWorkflowArchiveLogs:
|
||||
mock_session.execute.assert_called_once()
|
||||
|
||||
|
||||
class TestDeleteAppStars:
|
||||
@patch("tasks.remove_app_and_related_data_task._delete_records")
|
||||
def test_delete_app_stars_calls_delete_records(self, mock_delete_records):
|
||||
tenant_id = "tenant-1"
|
||||
app_id = "app-1"
|
||||
|
||||
_delete_app_stars(tenant_id, app_id)
|
||||
|
||||
mock_delete_records.assert_called_once()
|
||||
query_sql, params, delete_func, name = mock_delete_records.call_args[0]
|
||||
assert "app_stars" in query_sql
|
||||
assert params == {"tenant_id": tenant_id, "app_id": app_id}
|
||||
assert name == "app star"
|
||||
|
||||
mock_session = MagicMock()
|
||||
|
||||
delete_func(mock_session, "star-1")
|
||||
|
||||
mock_session.execute.assert_called_once()
|
||||
|
||||
|
||||
class TestDeleteArchivedWorkflowRunFiles:
|
||||
@patch("tasks.remove_app_and_related_data_task.get_archive_storage")
|
||||
@patch("tasks.remove_app_and_related_data_task.logger")
|
||||
|
||||
@ -4,5 +4,4 @@ Feature: Authenticated app console
|
||||
Given I am signed in as the default E2E admin
|
||||
When I open the apps console
|
||||
Then I should stay on the apps console
|
||||
And I should see the "Create from Blank" button
|
||||
And I should not see the "Sign in" button
|
||||
|
||||
@ -4,4 +4,3 @@ Feature: Fresh installation bootstrap
|
||||
Given the last authentication bootstrap came from a fresh install
|
||||
When I open the apps console
|
||||
Then I should stay on the apps console
|
||||
And I should see the "Create from Blank" button
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import type { DifyWorld } from '../../support/world'
|
||||
import { Then, When } from '@cucumber/cucumber'
|
||||
import { expect } from '@playwright/test'
|
||||
import { openBlankAppCreation } from '../../../support/apps'
|
||||
|
||||
When('I start creating a blank app', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Create from Blank' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Create from Blank' }).click()
|
||||
await openBlankAppCreation(this.getPage())
|
||||
})
|
||||
|
||||
When('I enter a unique E2E app name', async function (this: DifyWorld) {
|
||||
|
||||
@ -29,7 +29,7 @@ Then('the app should no longer appear in the apps console', async function (this
|
||||
)
|
||||
}
|
||||
|
||||
await expect(this.getPage().getByTitle(appName)).not.toBeVisible({
|
||||
await expect(this.getPage().getByRole('link', { name: appName, exact: true })).not.toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { DifyWorld } from '../../support/world'
|
||||
import { Given, When } from '@cucumber/cucumber'
|
||||
import { expect } from '@playwright/test'
|
||||
import { createTestApp } from '../../../support/api'
|
||||
|
||||
Given('there is an existing E2E app available for testing', async function (this: DifyWorld) {
|
||||
@ -15,14 +16,13 @@ When('I open the options menu for the last created E2E app', async function (thi
|
||||
throw new Error('No app name stored. Run "I enter a unique E2E app name" first.')
|
||||
|
||||
const page = this.getPage()
|
||||
// Scope to the specific card: the card root is the innermost div that contains
|
||||
// both the unique app name text and a More button (they are in separate branches,
|
||||
// so no child div satisfies both). .last() picks the deepest match in DOM order.
|
||||
const appLink = page.getByRole('link', { name: appName, exact: true })
|
||||
const appCard = page
|
||||
.locator('div')
|
||||
.filter({ has: page.getByText(appName, { exact: true }) })
|
||||
.filter({ has: appLink })
|
||||
.filter({ has: page.getByRole('button', { name: 'More' }) })
|
||||
.last()
|
||||
await expect(appLink).toBeVisible()
|
||||
await appCard.hover()
|
||||
await appCard.getByRole('button', { name: 'More' }).click()
|
||||
})
|
||||
|
||||
@ -4,7 +4,7 @@ import { expect } from '@playwright/test'
|
||||
import { adminCredentials } from '../../../fixtures/auth'
|
||||
|
||||
When('I open the sign-in page', async function (this: DifyWorld) {
|
||||
await this.getPage().goto('/signin')
|
||||
await this.getPage().goto('/signin?redirect_url=%2Fapps')
|
||||
})
|
||||
|
||||
When('I sign in as the default E2E admin', async function (this: DifyWorld) {
|
||||
|
||||
@ -2,6 +2,7 @@ import type { DifyWorld } from '../../support/world'
|
||||
import { Given, When } from '@cucumber/cucumber'
|
||||
import { expect } from '@playwright/test'
|
||||
import { createTestApp, syncMinimalWorkflowDraft } from '../../../support/api'
|
||||
import { waitForAppsConsole } from '../../../support/apps'
|
||||
|
||||
Given('a {string} app has been created via API', async function (this: DifyWorld, mode: string) {
|
||||
const app = await createTestApp(`E2E ${Date.now()}`, mode)
|
||||
@ -17,6 +18,8 @@ Given('a minimal workflow draft has been synced', async function (this: DifyWorl
|
||||
When('I open the app from the app list', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
await page.goto('/apps')
|
||||
await expect(page.getByRole('button', { name: 'Create from Blank' })).toBeVisible()
|
||||
await page.getByText(this.lastCreatedAppName!).click()
|
||||
await waitForAppsConsole(page)
|
||||
const appLink = page.getByRole('link', { name: this.lastCreatedAppName!, exact: true })
|
||||
await expect(appLink).toBeVisible()
|
||||
await appLink.click()
|
||||
})
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import type { DifyWorld } from '../../support/world'
|
||||
import { Then, When } from '@cucumber/cucumber'
|
||||
import { expect } from '@playwright/test'
|
||||
import { waitForAppsConsole } from '../../../support/apps'
|
||||
|
||||
When('I open the apps console', async function (this: DifyWorld) {
|
||||
await this.getPage().goto('/apps')
|
||||
})
|
||||
|
||||
Then('I should stay on the apps console', async function (this: DifyWorld) {
|
||||
await expect(this.getPage()).toHaveURL(/\/apps(?:\?.*)?$/)
|
||||
await waitForAppsConsole(this.getPage())
|
||||
})
|
||||
|
||||
Then('I should be redirected to the signin page', async function (this: DifyWorld) {
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import type { Browser, Page } from '@playwright/test'
|
||||
import type { APIResponse, Browser, BrowserContext } from '@playwright/test'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { expect } from '@playwright/test'
|
||||
import { defaultBaseURL, defaultLocale } from '../test-env'
|
||||
import { waitForAppsConsole } from '../support/apps'
|
||||
import { apiURL, defaultBaseURL, defaultLocale } from '../test-env'
|
||||
|
||||
export type AuthSessionMetadata = {
|
||||
adminEmail: string
|
||||
@ -12,7 +13,8 @@ export type AuthSessionMetadata = {
|
||||
usedInitPassword: boolean
|
||||
}
|
||||
|
||||
export const AUTH_BOOTSTRAP_TIMEOUT_MS = 120_000
|
||||
export const AUTH_BOOTSTRAP_TIMEOUT_MS = 180_000
|
||||
const AUTH_FLOW_TIMEOUT_MS = AUTH_BOOTSTRAP_TIMEOUT_MS - 30_000
|
||||
const e2eRoot = fileURLToPath(new URL('..', import.meta.url))
|
||||
|
||||
export const authDir = path.join(e2eRoot, '.auth')
|
||||
@ -35,89 +37,106 @@ export const readAuthSessionMetadata = async () => {
|
||||
return JSON.parse(content) as AuthSessionMetadata
|
||||
}
|
||||
|
||||
const escapeRegex = (value: string) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
const appURL = (baseURL: string, pathname: string) => new URL(pathname, baseURL).toString()
|
||||
const apiEndpoint = (pathname: string) => new URL(pathname, apiURL).toString()
|
||||
|
||||
type AuthPageState = 'install' | 'login' | 'init'
|
||||
type SetupStatusResponse = {
|
||||
step: 'not_started' | 'finished'
|
||||
}
|
||||
|
||||
type InitStatusResponse = {
|
||||
status: 'not_started' | 'finished'
|
||||
}
|
||||
|
||||
type AuthBootstrapResult = {
|
||||
mode: AuthSessionMetadata['mode']
|
||||
usedInitPassword: boolean
|
||||
}
|
||||
|
||||
const getRemainingTimeout = (deadline: number) => Math.max(deadline - Date.now(), 1)
|
||||
|
||||
const waitForPageState = async (page: Page, deadline: number): Promise<AuthPageState> => {
|
||||
const installHeading = page.getByRole('heading', { name: 'Setting up an admin account' })
|
||||
const signInButton = page.getByRole('button', { name: 'Sign in' })
|
||||
const initPasswordField = page.getByLabel('Admin initialization password')
|
||||
const encodeField = (value: string) => Buffer.from(value, 'utf8').toString('base64')
|
||||
|
||||
try {
|
||||
return await Promise.any<AuthPageState>([
|
||||
installHeading
|
||||
.waitFor({ state: 'visible', timeout: getRemainingTimeout(deadline) })
|
||||
.then(() => 'install'),
|
||||
signInButton
|
||||
.waitFor({ state: 'visible', timeout: getRemainingTimeout(deadline) })
|
||||
.then(() => 'login'),
|
||||
initPasswordField
|
||||
.waitFor({ state: 'visible', timeout: getRemainingTimeout(deadline) })
|
||||
.then(() => 'init'),
|
||||
])
|
||||
}
|
||||
catch {
|
||||
throw new Error(`Unable to determine auth page state for ${page.url()}`)
|
||||
}
|
||||
const assertAPIResponse = async (response: APIResponse, action: string) => {
|
||||
if (response.ok())
|
||||
return
|
||||
|
||||
const body = await response.text().catch(() => '')
|
||||
throw new Error(
|
||||
`${action} failed with ${response.status()} ${response.statusText()}${body ? `: ${body}` : ''}`,
|
||||
)
|
||||
}
|
||||
|
||||
const completeInitPasswordIfNeeded = async (page: Page, deadline: number) => {
|
||||
const initPasswordField = page.getByLabel('Admin initialization password')
|
||||
|
||||
const needsInitPassword = await initPasswordField
|
||||
.waitFor({ state: 'visible', timeout: Math.min(getRemainingTimeout(deadline), 3_000) })
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
|
||||
if (!needsInitPassword)
|
||||
return false
|
||||
|
||||
await initPasswordField.fill(initPassword)
|
||||
await page.getByRole('button', { name: 'Validate' }).click()
|
||||
await expect(page.getByRole('heading', { name: 'Setting up an admin account' })).toBeVisible({
|
||||
const getConsoleAPI = async <T>(context: BrowserContext, pathname: string, deadline: number) => {
|
||||
const response = await context.request.get(apiEndpoint(pathname), {
|
||||
timeout: getRemainingTimeout(deadline),
|
||||
})
|
||||
await assertAPIResponse(response, `GET ${pathname}`)
|
||||
return response.json() as Promise<T>
|
||||
}
|
||||
|
||||
const postConsoleAPI = async (
|
||||
context: BrowserContext,
|
||||
pathname: string,
|
||||
deadline: number,
|
||||
data: Record<string, unknown>,
|
||||
) => {
|
||||
const response = await context.request.post(apiEndpoint(pathname), {
|
||||
data,
|
||||
timeout: getRemainingTimeout(deadline),
|
||||
})
|
||||
await assertAPIResponse(response, `POST ${pathname}`)
|
||||
}
|
||||
|
||||
const validateInitPasswordIfNeeded = async (context: BrowserContext, deadline: number) => {
|
||||
const initStatus = await getConsoleAPI<InitStatusResponse>(context, '/console/api/init', deadline)
|
||||
if (initStatus.status === 'finished')
|
||||
return false
|
||||
|
||||
console.warn('[e2e] auth bootstrap: validating init password')
|
||||
await postConsoleAPI(context, '/console/api/init', deadline, { password: initPassword })
|
||||
return true
|
||||
}
|
||||
|
||||
const completeInstall = async (page: Page, baseURL: string, deadline: number) => {
|
||||
await expect(page.getByRole('heading', { name: 'Setting up an admin account' })).toBeVisible({
|
||||
timeout: getRemainingTimeout(deadline),
|
||||
})
|
||||
const ensureAdminAccount = async (
|
||||
context: BrowserContext,
|
||||
deadline: number,
|
||||
): Promise<AuthBootstrapResult> => {
|
||||
const setupStatus = await getConsoleAPI<SetupStatusResponse>(
|
||||
context,
|
||||
'/console/api/setup',
|
||||
deadline,
|
||||
)
|
||||
let usedInitPassword = false
|
||||
|
||||
await page.getByLabel('Email address').fill(adminCredentials.email)
|
||||
await page.getByLabel('Username').fill(adminCredentials.name)
|
||||
await page.getByLabel('Password').fill(adminCredentials.password)
|
||||
await page.getByRole('button', { name: 'Set up' }).click()
|
||||
if (setupStatus.step === 'not_started') {
|
||||
usedInitPassword = await validateInitPasswordIfNeeded(context, deadline)
|
||||
console.warn('[e2e] auth bootstrap: creating admin account')
|
||||
await postConsoleAPI(context, '/console/api/setup', deadline, {
|
||||
email: adminCredentials.email,
|
||||
name: adminCredentials.name,
|
||||
password: adminCredentials.password,
|
||||
language: defaultLocale,
|
||||
})
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`^${escapeRegex(baseURL)}/apps(?:\\?.*)?$`), {
|
||||
timeout: getRemainingTimeout(deadline),
|
||||
})
|
||||
return { mode: 'install', usedInitPassword }
|
||||
}
|
||||
|
||||
return { mode: 'login', usedInitPassword }
|
||||
}
|
||||
|
||||
const completeLogin = async (page: Page, baseURL: string, deadline: number) => {
|
||||
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible({
|
||||
timeout: getRemainingTimeout(deadline),
|
||||
})
|
||||
|
||||
await page.getByLabel('Email address').fill(adminCredentials.email)
|
||||
await page.getByLabel('Password').fill(adminCredentials.password)
|
||||
await page.getByRole('button', { name: 'Sign in' }).click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`^${escapeRegex(baseURL)}/apps(?:\\?.*)?$`), {
|
||||
timeout: getRemainingTimeout(deadline),
|
||||
const loginAdmin = async (context: BrowserContext, deadline: number) => {
|
||||
console.warn('[e2e] auth bootstrap: logging in admin')
|
||||
await postConsoleAPI(context, '/console/api/login', deadline, {
|
||||
email: adminCredentials.email,
|
||||
password: encodeField(adminCredentials.password),
|
||||
remember_me: true,
|
||||
})
|
||||
}
|
||||
|
||||
export const ensureAuthenticatedState = async (browser: Browser, configuredBaseURL?: string) => {
|
||||
const baseURL = resolveBaseURL(configuredBaseURL)
|
||||
const deadline = Date.now() + AUTH_BOOTSTRAP_TIMEOUT_MS
|
||||
const deadline = Date.now() + AUTH_FLOW_TIMEOUT_MS
|
||||
|
||||
await mkdir(authDir, { recursive: true })
|
||||
|
||||
@ -128,37 +147,22 @@ export const ensureAuthenticatedState = async (browser: Browser, configuredBaseU
|
||||
const page = await context.newPage()
|
||||
|
||||
try {
|
||||
await page.goto(appURL(baseURL, '/install'), {
|
||||
const { mode, usedInitPassword } = await ensureAdminAccount(context, deadline)
|
||||
await loginAdmin(context, deadline)
|
||||
|
||||
console.warn('[e2e] auth bootstrap: verifying apps console')
|
||||
await page.goto(appURL(baseURL, '/apps'), {
|
||||
timeout: getRemainingTimeout(deadline),
|
||||
waitUntil: 'domcontentloaded',
|
||||
})
|
||||
|
||||
let usedInitPassword = await completeInitPasswordIfNeeded(page, deadline)
|
||||
let pageState = await waitForPageState(page, deadline)
|
||||
|
||||
while (pageState === 'init') {
|
||||
const completedInitPassword = await completeInitPasswordIfNeeded(page, deadline)
|
||||
if (!completedInitPassword)
|
||||
throw new Error(`Unable to validate initialization password for ${page.url()}`)
|
||||
|
||||
usedInitPassword = true
|
||||
pageState = await waitForPageState(page, deadline)
|
||||
}
|
||||
|
||||
if (pageState === 'install')
|
||||
await completeInstall(page, baseURL, deadline)
|
||||
else await completeLogin(page, baseURL, deadline)
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Create from Blank' })).toBeVisible({
|
||||
timeout: getRemainingTimeout(deadline),
|
||||
})
|
||||
await waitForAppsConsole(page, getRemainingTimeout(deadline))
|
||||
|
||||
await context.storageState({ path: authStatePath })
|
||||
|
||||
const metadata: AuthSessionMetadata = {
|
||||
adminEmail: adminCredentials.email,
|
||||
baseURL,
|
||||
mode: pageState,
|
||||
mode,
|
||||
usedInitPassword,
|
||||
}
|
||||
|
||||
|
||||
24
e2e/support/apps.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
export const waitForAppsConsole = async (page: Page, timeout?: number) => {
|
||||
await expect(page).toHaveURL(/\/apps(?:\?.*)?$/, timeout === undefined ? undefined : { timeout })
|
||||
await expect(page.getByRole('heading', { name: 'Studio' })).toBeVisible(
|
||||
timeout === undefined ? undefined : { timeout },
|
||||
)
|
||||
}
|
||||
|
||||
export const openBlankAppCreation = async (page: Page) => {
|
||||
const createFromBlankButton = page.getByRole('button', { name: 'Create from Blank' }).first()
|
||||
const isDirectCreateVisible = await createFromBlankButton
|
||||
.isVisible({ timeout: 3_000 })
|
||||
.catch(() => false)
|
||||
|
||||
if (isDirectCreateVisible) {
|
||||
await createFromBlankButton.click()
|
||||
return
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Create' }).click()
|
||||
await page.getByRole('menuitem', { name: 'Create from Blank' }).click()
|
||||
}
|
||||
@ -152,17 +152,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx": {
|
||||
"no-restricted-globals": {
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -396,9 +385,6 @@
|
||||
},
|
||||
"web/app/components/app-sidebar/index.tsx": {
|
||||
"no-restricted-globals": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
@ -990,9 +976,6 @@
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/overview/trigger-card.tsx": {
|
||||
@ -1051,10 +1034,10 @@
|
||||
},
|
||||
"web/app/components/apps/app-card.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/apps/import-from-marketplace-template-modal.tsx": {
|
||||
@ -1065,17 +1048,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/apps/new-app-card.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/action-button/index.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@ -1886,7 +1858,7 @@
|
||||
},
|
||||
"web/app/components/base/icons/src/vender/line/alertsAndFeedback/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/icons/src/vender/line/arrows/index.ts": {
|
||||
@ -1921,7 +1893,7 @@
|
||||
},
|
||||
"web/app/components/base/icons/src/vender/line/financeAndECommerce/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 4
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/base/icons/src/vender/line/general/index.ts": {
|
||||
@ -3474,6 +3446,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/list/__tests__/header.spec.tsx": {
|
||||
"jsx-a11y/label-has-associated-control": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/list/dataset-card/__tests__/index.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -3628,11 +3605,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/app-list/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/banner/__tests__/indicator-button.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -3647,12 +3619,6 @@
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/banner/indicator-button.tsx": {
|
||||
@ -3663,14 +3629,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/category.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/item-operation/__tests__/index.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -3679,12 +3637,17 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/learn-dify/item.tsx": {
|
||||
"jsx-a11y/no-noninteractive-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/sidebar/app-nav-item/index.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/try-app/app/text-generation.tsx": {
|
||||
@ -3700,34 +3663,16 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/actions/commands/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/actions/commands/registry.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/actions/commands/slash.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/actions/commands/types.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/actions/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/actions/plugin.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -3743,11 +3688,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/command-selector.tsx": {
|
||||
"react/unsupported-syntax": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/components/__tests__/search-input.spec.tsx": {
|
||||
"jsx-a11y/no-autofocus": {
|
||||
"count": 1
|
||||
@ -3779,11 +3719,6 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/hooks/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/goto-anything/hooks/use-goto-anything-results.ts": {
|
||||
"@tanstack/query/exhaustive-deps": {
|
||||
"count": 1
|
||||
@ -3798,10 +3733,10 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/data-source-page-new/card.tsx": {
|
||||
"jsx-a11y/alt-text": {
|
||||
"count": 1
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
@ -3815,16 +3750,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/data-source-page-new/install-from-marketplace.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/data-source-page-new/item.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"web/app/components/header/account-setting/data-source-page-new/plugin-actions.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
@ -4027,6 +3957,11 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/model-provider-page/model-provider-page-body.tsx": {
|
||||
"jsx-a11y/anchor-has-content": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/model-provider-page/provider-added-card/cooldown-timer.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -4096,8 +4031,13 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/card/index.tsx": {
|
||||
"ts/no-non-null-asserted-optional-chain": {
|
||||
"web/app/components/main-nav/components/web-apps-section.tsx": {
|
||||
"jsx-a11y/no-autofocus": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/main-nav/components/workspace-switcher.tsx": {
|
||||
"jsx-a11y/no-autofocus": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
@ -4490,11 +4430,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-page/empty/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-page/filter-management/__tests__/index.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -4513,11 +4448,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -4555,11 +4485,6 @@
|
||||
"count": 25
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/update-plugin/from-market-place.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/update-plugin/plugin-version-picker.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -4940,32 +4865,11 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/create-card.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/headers-input.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/mcp-server-param-item.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/provider-card.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
@ -4977,40 +4881,8 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/sections/authentication-section.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/sections/configurations-section.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/provider-list.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/provider/custom-create-card.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/provider/empty.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"web/app/components/tools/provider/detail.tsx": {
|
||||
"jsx-a11y/anchor-has-content": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
@ -5027,6 +4899,14 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/tool-provider-grid.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/types.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 4
|
||||
@ -5334,11 +5214,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/header/__tests__/index.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/header/online-users.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
@ -5448,16 +5323,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/hooks/use-workflow-canvas-maximize.ts": {
|
||||
"no-restricted-globals": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/hooks/use-workflow-interactions.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/hooks/use-workflow-run-event/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 19
|
||||
@ -7406,11 +7271,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/store/workflow/layout-slice.ts": {
|
||||
"no-restricted-properties": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/store/workflow/workflow-draft-slice.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -7728,11 +7588,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/context/provider-context-provider.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/context/web-app-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
@ -7799,46 +7654,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/i18n/de-DE/billing.json": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/i18n/en-US/app-debug.json": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/i18n/fr-FR/app-debug.json": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/i18n/fr-FR/plugin-trigger.json": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/i18n/fr-FR/tools.json": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/i18n/pt-BR/common.json": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/i18n/ru-RU/common.json": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/i18n/uk-UA/app-debug.json": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/models/access-control.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 2
|
||||
@ -7849,14 +7664,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/models/common.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/models/datasets.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 7
|
||||
@ -7971,7 +7778,7 @@
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 29
|
||||
"count": 27
|
||||
}
|
||||
},
|
||||
"web/service/datasets.ts": {
|
||||
@ -8167,20 +7974,6 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/service/use-plugins.ts": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"regexp/no-unused-capturing-group": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/service/use-snippet-workflows.ts": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
|
||||
@ -1191,6 +1191,7 @@ export type AppPartial = {
|
||||
icon_background?: string | null
|
||||
icon_type?: string | null
|
||||
id: string
|
||||
is_starred?: boolean
|
||||
max_active_requests?: number | null
|
||||
mode_compatible_with_agent: string
|
||||
name: string
|
||||
@ -2683,6 +2684,7 @@ export type GetAppsData = {
|
||||
| 'workflow'
|
||||
name?: string
|
||||
page?: number
|
||||
sort_by?: 'earliest_created' | 'last_modified' | 'recently_created'
|
||||
tag_ids?: Array<string>
|
||||
}
|
||||
url: '/apps'
|
||||
@ -2771,6 +2773,36 @@ export type PostAppsImportsByImportIdConfirmResponses = {
|
||||
export type PostAppsImportsByImportIdConfirmResponse
|
||||
= PostAppsImportsByImportIdConfirmResponses[keyof PostAppsImportsByImportIdConfirmResponses]
|
||||
|
||||
export type GetAppsStarredData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
creator_ids?: Array<string>
|
||||
is_created_by_me?: boolean
|
||||
limit?: number
|
||||
mode?:
|
||||
| 'advanced-chat'
|
||||
| 'agent'
|
||||
| 'agent-chat'
|
||||
| 'all'
|
||||
| 'channel'
|
||||
| 'chat'
|
||||
| 'completion'
|
||||
| 'workflow'
|
||||
name?: string
|
||||
page?: number
|
||||
sort_by?: 'earliest_created' | 'last_modified' | 'recently_created'
|
||||
tag_ids?: Array<string>
|
||||
}
|
||||
url: '/apps/starred'
|
||||
}
|
||||
|
||||
export type GetAppsStarredResponses = {
|
||||
200: AppPagination
|
||||
}
|
||||
|
||||
export type GetAppsStarredResponse = GetAppsStarredResponses[keyof GetAppsStarredResponses]
|
||||
|
||||
export type PostAppsWorkflowsOnlineUsersData = {
|
||||
body: WorkflowOnlineUsersPayload
|
||||
path?: never
|
||||
@ -4250,6 +4282,46 @@ export type PostAppsByAppIdSiteAccessTokenResetResponses = {
|
||||
export type PostAppsByAppIdSiteAccessTokenResetResponse
|
||||
= PostAppsByAppIdSiteAccessTokenResetResponses[keyof PostAppsByAppIdSiteAccessTokenResetResponses]
|
||||
|
||||
export type DeleteAppsByAppIdStarData = {
|
||||
body?: never
|
||||
path: {
|
||||
app_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/apps/{app_id}/star'
|
||||
}
|
||||
|
||||
export type DeleteAppsByAppIdStarErrors = {
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type DeleteAppsByAppIdStarResponses = {
|
||||
200: SimpleResultResponse
|
||||
}
|
||||
|
||||
export type DeleteAppsByAppIdStarResponse
|
||||
= DeleteAppsByAppIdStarResponses[keyof DeleteAppsByAppIdStarResponses]
|
||||
|
||||
export type PostAppsByAppIdStarData = {
|
||||
body?: never
|
||||
path: {
|
||||
app_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/apps/{app_id}/star'
|
||||
}
|
||||
|
||||
export type PostAppsByAppIdStarErrors = {
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type PostAppsByAppIdStarResponses = {
|
||||
200: SimpleResultResponse
|
||||
}
|
||||
|
||||
export type PostAppsByAppIdStarResponse
|
||||
= PostAppsByAppIdStarResponses[keyof PostAppsByAppIdStarResponses]
|
||||
|
||||
export type GetAppsByAppIdStatisticsAverageResponseTimeData = {
|
||||
body?: never
|
||||
path: {
|
||||
|
||||
@ -2000,6 +2000,7 @@ export const zAppPartial = z.object({
|
||||
icon_background: z.string().nullish(),
|
||||
icon_type: z.string().nullish(),
|
||||
id: z.string(),
|
||||
is_starred: z.boolean().optional().default(false),
|
||||
max_active_requests: z.int().nullish(),
|
||||
mode_compatible_with_agent: z.string(),
|
||||
name: z.string(),
|
||||
@ -3625,6 +3626,10 @@ export const zGetAppsQuery = z.object({
|
||||
.default('all'),
|
||||
name: z.string().optional(),
|
||||
page: z.int().gte(1).lte(99999).optional().default(1),
|
||||
sort_by: z
|
||||
.enum(['earliest_created', 'last_modified', 'recently_created'])
|
||||
.optional()
|
||||
.default('last_modified'),
|
||||
tag_ids: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
@ -3665,6 +3670,37 @@ export const zPostAppsImportsByImportIdConfirmPath = z.object({
|
||||
*/
|
||||
export const zPostAppsImportsByImportIdConfirmResponse = zImport
|
||||
|
||||
export const zGetAppsStarredQuery = z.object({
|
||||
creator_ids: z.array(z.string()).optional(),
|
||||
is_created_by_me: z.boolean().optional(),
|
||||
limit: z.int().gte(1).lte(100).optional().default(20),
|
||||
mode: z
|
||||
.enum([
|
||||
'advanced-chat',
|
||||
'agent',
|
||||
'agent-chat',
|
||||
'all',
|
||||
'channel',
|
||||
'chat',
|
||||
'completion',
|
||||
'workflow',
|
||||
])
|
||||
.optional()
|
||||
.default('all'),
|
||||
name: z.string().optional(),
|
||||
page: z.int().gte(1).lte(99999).optional().default(1),
|
||||
sort_by: z
|
||||
.enum(['earliest_created', 'last_modified', 'recently_created'])
|
||||
.optional()
|
||||
.default('last_modified'),
|
||||
tag_ids: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zGetAppsStarredResponse = zAppPagination
|
||||
|
||||
export const zPostAppsWorkflowsOnlineUsersBody = zWorkflowOnlineUsersPayload
|
||||
|
||||
/**
|
||||
@ -4541,6 +4577,24 @@ export const zPostAppsByAppIdSiteAccessTokenResetPath = z.object({
|
||||
*/
|
||||
export const zPostAppsByAppIdSiteAccessTokenResetResponse = zAppSiteResponse
|
||||
|
||||
export const zDeleteAppsByAppIdStarPath = z.object({
|
||||
app_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zDeleteAppsByAppIdStarResponse = zSimpleResultResponse
|
||||
|
||||
export const zPostAppsByAppIdStarPath = z.object({
|
||||
app_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zPostAppsByAppIdStarResponse = zSimpleResultResponse
|
||||
|
||||
export const zGetAppsByAppIdStatisticsAverageResponseTimePath = z.object({
|
||||
app_id: z.string(),
|
||||
})
|
||||
|
||||
@ -6,6 +6,8 @@ import * as z from 'zod'
|
||||
import {
|
||||
zGetExploreAppsByAppIdPath,
|
||||
zGetExploreAppsByAppIdResponse,
|
||||
zGetExploreAppsLearnDifyQuery,
|
||||
zGetExploreAppsLearnDifyResponse,
|
||||
zGetExploreAppsQuery,
|
||||
zGetExploreAppsResponse,
|
||||
zGetExploreBannersQuery,
|
||||
@ -13,6 +15,21 @@ import {
|
||||
} from './zod.gen'
|
||||
|
||||
export const get = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
operationId: 'getExploreAppsLearnDify',
|
||||
path: '/explore/apps/learn-dify',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ query: zGetExploreAppsLearnDifyQuery.optional() }))
|
||||
.output(zGetExploreAppsLearnDifyResponse)
|
||||
|
||||
export const learnDify = {
|
||||
get,
|
||||
}
|
||||
|
||||
export const get2 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -24,10 +41,10 @@ export const get = oc
|
||||
.output(zGetExploreAppsByAppIdResponse)
|
||||
|
||||
export const byAppId = {
|
||||
get,
|
||||
get: get2,
|
||||
}
|
||||
|
||||
export const get2 = oc
|
||||
export const get3 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -39,14 +56,15 @@ export const get2 = oc
|
||||
.output(zGetExploreAppsResponse)
|
||||
|
||||
export const apps = {
|
||||
get: get2,
|
||||
get: get3,
|
||||
learnDify,
|
||||
byAppId,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get banner list
|
||||
*/
|
||||
export const get3 = oc
|
||||
export const get4 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -59,7 +77,7 @@ export const get3 = oc
|
||||
.output(zGetExploreBannersResponse)
|
||||
|
||||
export const banners = {
|
||||
get: get3,
|
||||
get: get4,
|
||||
}
|
||||
|
||||
export const explore = {
|
||||
|
||||
@ -9,6 +9,10 @@ export type RecommendedAppListResponse = {
|
||||
recommended_apps: Array<RecommendedAppResponse>
|
||||
}
|
||||
|
||||
export type LearnDifyAppListResponse = {
|
||||
recommended_apps: Array<RecommendedAppResponse>
|
||||
}
|
||||
|
||||
export type RecommendedAppDetailResponse = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
@ -61,6 +65,22 @@ export type GetExploreAppsResponses = {
|
||||
|
||||
export type GetExploreAppsResponse = GetExploreAppsResponses[keyof GetExploreAppsResponses]
|
||||
|
||||
export type GetExploreAppsLearnDifyData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
language?: string
|
||||
}
|
||||
url: '/explore/apps/learn-dify'
|
||||
}
|
||||
|
||||
export type GetExploreAppsLearnDifyResponses = {
|
||||
200: LearnDifyAppListResponse
|
||||
}
|
||||
|
||||
export type GetExploreAppsLearnDifyResponse
|
||||
= GetExploreAppsLearnDifyResponses[keyof GetExploreAppsLearnDifyResponses]
|
||||
|
||||
export type GetExploreAppsByAppIdData = {
|
||||
body?: never
|
||||
path: {
|
||||
|
||||
@ -60,6 +60,13 @@ export const zRecommendedAppListResponse = z.object({
|
||||
recommended_apps: z.array(zRecommendedAppResponse),
|
||||
})
|
||||
|
||||
/**
|
||||
* LearnDifyAppListResponse
|
||||
*/
|
||||
export const zLearnDifyAppListResponse = z.object({
|
||||
recommended_apps: z.array(zRecommendedAppResponse),
|
||||
})
|
||||
|
||||
export const zGetExploreAppsQuery = z.object({
|
||||
language: z.string().optional(),
|
||||
})
|
||||
@ -69,6 +76,15 @@ export const zGetExploreAppsQuery = z.object({
|
||||
*/
|
||||
export const zGetExploreAppsResponse = zRecommendedAppListResponse
|
||||
|
||||
export const zGetExploreAppsLearnDifyQuery = z.object({
|
||||
language: z.string().optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zGetExploreAppsLearnDifyResponse = zLearnDifyAppListResponse
|
||||
|
||||
export const zGetExploreAppsByAppIdPath = z.object({
|
||||
app_id: z.string(),
|
||||
})
|
||||
|
||||
@ -67,6 +67,11 @@ import {
|
||||
zGetWorkspacesCurrentPermissionResponse,
|
||||
zGetWorkspacesCurrentPluginAssetQuery,
|
||||
zGetWorkspacesCurrentPluginAssetResponse,
|
||||
zGetWorkspacesCurrentPluginAutoUpgradeFetchQuery,
|
||||
zGetWorkspacesCurrentPluginAutoUpgradeFetchResponse,
|
||||
zGetWorkspacesCurrentPluginByCategoryListPath,
|
||||
zGetWorkspacesCurrentPluginByCategoryListQuery,
|
||||
zGetWorkspacesCurrentPluginByCategoryListResponse,
|
||||
zGetWorkspacesCurrentPluginDebuggingKeyResponse,
|
||||
zGetWorkspacesCurrentPluginFetchManifestQuery,
|
||||
zGetWorkspacesCurrentPluginFetchManifestResponse,
|
||||
@ -79,7 +84,6 @@ import {
|
||||
zGetWorkspacesCurrentPluginParametersDynamicOptionsQuery,
|
||||
zGetWorkspacesCurrentPluginParametersDynamicOptionsResponse,
|
||||
zGetWorkspacesCurrentPluginPermissionFetchResponse,
|
||||
zGetWorkspacesCurrentPluginPreferencesFetchResponse,
|
||||
zGetWorkspacesCurrentPluginReadmeQuery,
|
||||
zGetWorkspacesCurrentPluginReadmeResponse,
|
||||
zGetWorkspacesCurrentPluginTasksByTaskIdPath,
|
||||
@ -214,6 +218,10 @@ import {
|
||||
zPostWorkspacesCurrentModelProvidersByProviderPreferredProviderTypeBody,
|
||||
zPostWorkspacesCurrentModelProvidersByProviderPreferredProviderTypePath,
|
||||
zPostWorkspacesCurrentModelProvidersByProviderPreferredProviderTypeResponse,
|
||||
zPostWorkspacesCurrentPluginAutoUpgradeChangeBody,
|
||||
zPostWorkspacesCurrentPluginAutoUpgradeChangeResponse,
|
||||
zPostWorkspacesCurrentPluginAutoUpgradeExcludeBody,
|
||||
zPostWorkspacesCurrentPluginAutoUpgradeExcludeResponse,
|
||||
zPostWorkspacesCurrentPluginInstallGithubBody,
|
||||
zPostWorkspacesCurrentPluginInstallGithubResponse,
|
||||
zPostWorkspacesCurrentPluginInstallMarketplaceBody,
|
||||
@ -228,10 +236,6 @@ import {
|
||||
zPostWorkspacesCurrentPluginParametersDynamicOptionsWithCredentialsResponse,
|
||||
zPostWorkspacesCurrentPluginPermissionChangeBody,
|
||||
zPostWorkspacesCurrentPluginPermissionChangeResponse,
|
||||
zPostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeBody,
|
||||
zPostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeResponse,
|
||||
zPostWorkspacesCurrentPluginPreferencesChangeBody,
|
||||
zPostWorkspacesCurrentPluginPreferencesChangeResponse,
|
||||
zPostWorkspacesCurrentPluginTasksByTaskIdDeleteByIdentifierPath,
|
||||
zPostWorkspacesCurrentPluginTasksByTaskIdDeleteByIdentifierResponse,
|
||||
zPostWorkspacesCurrentPluginTasksByTaskIdDeletePath,
|
||||
@ -1487,7 +1491,58 @@ export const asset = {
|
||||
get: get20,
|
||||
}
|
||||
|
||||
export const post26 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
operationId: 'postWorkspacesCurrentPluginAutoUpgradeChange',
|
||||
path: '/workspaces/current/plugin/auto-upgrade/change',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ body: zPostWorkspacesCurrentPluginAutoUpgradeChangeBody }))
|
||||
.output(zPostWorkspacesCurrentPluginAutoUpgradeChangeResponse)
|
||||
|
||||
export const change = {
|
||||
post: post26,
|
||||
}
|
||||
|
||||
export const post27 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
operationId: 'postWorkspacesCurrentPluginAutoUpgradeExclude',
|
||||
path: '/workspaces/current/plugin/auto-upgrade/exclude',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ body: zPostWorkspacesCurrentPluginAutoUpgradeExcludeBody }))
|
||||
.output(zPostWorkspacesCurrentPluginAutoUpgradeExcludeResponse)
|
||||
|
||||
export const exclude = {
|
||||
post: post27,
|
||||
}
|
||||
|
||||
export const get21 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
operationId: 'getWorkspacesCurrentPluginAutoUpgradeFetch',
|
||||
path: '/workspaces/current/plugin/auto-upgrade/fetch',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ query: zGetWorkspacesCurrentPluginAutoUpgradeFetchQuery }))
|
||||
.output(zGetWorkspacesCurrentPluginAutoUpgradeFetchResponse)
|
||||
|
||||
export const fetch_ = {
|
||||
get: get21,
|
||||
}
|
||||
|
||||
export const autoUpgrade = {
|
||||
change,
|
||||
exclude,
|
||||
fetch: fetch_,
|
||||
}
|
||||
|
||||
export const get22 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -1498,10 +1553,10 @@ export const get21 = oc
|
||||
.output(zGetWorkspacesCurrentPluginDebuggingKeyResponse)
|
||||
|
||||
export const debuggingKey = {
|
||||
get: get21,
|
||||
get: get22,
|
||||
}
|
||||
|
||||
export const get22 = oc
|
||||
export const get23 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -1513,10 +1568,10 @@ export const get22 = oc
|
||||
.output(zGetWorkspacesCurrentPluginFetchManifestResponse)
|
||||
|
||||
export const fetchManifest = {
|
||||
get: get22,
|
||||
get: get23,
|
||||
}
|
||||
|
||||
export const get23 = oc
|
||||
export const get24 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -1528,10 +1583,10 @@ export const get23 = oc
|
||||
.output(zGetWorkspacesCurrentPluginIconResponse)
|
||||
|
||||
export const icon = {
|
||||
get: get23,
|
||||
get: get24,
|
||||
}
|
||||
|
||||
export const post26 = oc
|
||||
export const post28 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -1543,10 +1598,10 @@ export const post26 = oc
|
||||
.output(zPostWorkspacesCurrentPluginInstallGithubResponse)
|
||||
|
||||
export const github = {
|
||||
post: post26,
|
||||
post: post28,
|
||||
}
|
||||
|
||||
export const post27 = oc
|
||||
export const post29 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -1558,10 +1613,10 @@ export const post27 = oc
|
||||
.output(zPostWorkspacesCurrentPluginInstallMarketplaceResponse)
|
||||
|
||||
export const marketplace = {
|
||||
post: post27,
|
||||
post: post29,
|
||||
}
|
||||
|
||||
export const post28 = oc
|
||||
export const post30 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -1573,7 +1628,7 @@ export const post28 = oc
|
||||
.output(zPostWorkspacesCurrentPluginInstallPkgResponse)
|
||||
|
||||
export const pkg = {
|
||||
post: post28,
|
||||
post: post30,
|
||||
}
|
||||
|
||||
export const install = {
|
||||
@ -1582,7 +1637,7 @@ export const install = {
|
||||
pkg,
|
||||
}
|
||||
|
||||
export const post29 = oc
|
||||
export const post31 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -1594,14 +1649,14 @@ export const post29 = oc
|
||||
.output(zPostWorkspacesCurrentPluginListInstallationsIdsResponse)
|
||||
|
||||
export const ids = {
|
||||
post: post29,
|
||||
post: post31,
|
||||
}
|
||||
|
||||
export const installations = {
|
||||
ids,
|
||||
}
|
||||
|
||||
export const post30 = oc
|
||||
export const post32 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -1613,10 +1668,10 @@ export const post30 = oc
|
||||
.output(zPostWorkspacesCurrentPluginListLatestVersionsResponse)
|
||||
|
||||
export const latestVersions = {
|
||||
post: post30,
|
||||
post: post32,
|
||||
}
|
||||
|
||||
export const get24 = oc
|
||||
export const get25 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -1628,12 +1683,12 @@ export const get24 = oc
|
||||
.output(zGetWorkspacesCurrentPluginListResponse)
|
||||
|
||||
export const list2 = {
|
||||
get: get24,
|
||||
get: get25,
|
||||
installations,
|
||||
latestVersions,
|
||||
}
|
||||
|
||||
export const get25 = oc
|
||||
export const get26 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -1645,14 +1700,14 @@ export const get25 = oc
|
||||
.output(zGetWorkspacesCurrentPluginMarketplacePkgResponse)
|
||||
|
||||
export const pkg2 = {
|
||||
get: get25,
|
||||
get: get26,
|
||||
}
|
||||
|
||||
export const marketplace2 = {
|
||||
pkg: pkg2,
|
||||
}
|
||||
|
||||
export const get26 = oc
|
||||
export const get27 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -1664,13 +1719,13 @@ export const get26 = oc
|
||||
.output(zGetWorkspacesCurrentPluginParametersDynamicOptionsResponse)
|
||||
|
||||
export const dynamicOptions = {
|
||||
get: get26,
|
||||
get: get27,
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch dynamic options using credentials directly (for edit mode)
|
||||
*/
|
||||
export const post31 = oc
|
||||
export const post33 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -1685,7 +1740,7 @@ export const post31 = oc
|
||||
.output(zPostWorkspacesCurrentPluginParametersDynamicOptionsWithCredentialsResponse)
|
||||
|
||||
export const dynamicOptionsWithCredentials = {
|
||||
post: post31,
|
||||
post: post33,
|
||||
}
|
||||
|
||||
export const parameters = {
|
||||
@ -1693,7 +1748,7 @@ export const parameters = {
|
||||
dynamicOptionsWithCredentials,
|
||||
}
|
||||
|
||||
export const post32 = oc
|
||||
export const post34 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -1704,11 +1759,11 @@ export const post32 = oc
|
||||
.input(z.object({ body: zPostWorkspacesCurrentPluginPermissionChangeBody }))
|
||||
.output(zPostWorkspacesCurrentPluginPermissionChangeResponse)
|
||||
|
||||
export const change = {
|
||||
post: post32,
|
||||
export const change2 = {
|
||||
post: post34,
|
||||
}
|
||||
|
||||
export const get27 = oc
|
||||
export const get28 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -1718,65 +1773,11 @@ export const get27 = oc
|
||||
})
|
||||
.output(zGetWorkspacesCurrentPluginPermissionFetchResponse)
|
||||
|
||||
export const fetch_ = {
|
||||
get: get27,
|
||||
}
|
||||
|
||||
export const permission2 = {
|
||||
change,
|
||||
fetch: fetch_,
|
||||
}
|
||||
|
||||
export const post33 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
operationId: 'postWorkspacesCurrentPluginPreferencesAutoupgradeExclude',
|
||||
path: '/workspaces/current/plugin/preferences/autoupgrade/exclude',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ body: zPostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeBody }))
|
||||
.output(zPostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeResponse)
|
||||
|
||||
export const exclude = {
|
||||
post: post33,
|
||||
}
|
||||
|
||||
export const autoupgrade = {
|
||||
exclude,
|
||||
}
|
||||
|
||||
export const post34 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
operationId: 'postWorkspacesCurrentPluginPreferencesChange',
|
||||
path: '/workspaces/current/plugin/preferences/change',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ body: zPostWorkspacesCurrentPluginPreferencesChangeBody }))
|
||||
.output(zPostWorkspacesCurrentPluginPreferencesChangeResponse)
|
||||
|
||||
export const change2 = {
|
||||
post: post34,
|
||||
}
|
||||
|
||||
export const get28 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
operationId: 'getWorkspacesCurrentPluginPreferencesFetch',
|
||||
path: '/workspaces/current/plugin/preferences/fetch',
|
||||
tags: ['console'],
|
||||
})
|
||||
.output(zGetWorkspacesCurrentPluginPreferencesFetchResponse)
|
||||
|
||||
export const fetch2 = {
|
||||
get: get28,
|
||||
}
|
||||
|
||||
export const preferences = {
|
||||
autoupgrade,
|
||||
export const permission2 = {
|
||||
change: change2,
|
||||
fetch: fetch2,
|
||||
}
|
||||
@ -1973,8 +1974,33 @@ export const upload = {
|
||||
pkg: pkg3,
|
||||
}
|
||||
|
||||
export const get32 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
operationId: 'getWorkspacesCurrentPluginByCategoryList',
|
||||
path: '/workspaces/current/plugin/{category}/list',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
params: zGetWorkspacesCurrentPluginByCategoryListPath,
|
||||
query: zGetWorkspacesCurrentPluginByCategoryListQuery.optional(),
|
||||
}),
|
||||
)
|
||||
.output(zGetWorkspacesCurrentPluginByCategoryListResponse)
|
||||
|
||||
export const list3 = {
|
||||
get: get32,
|
||||
}
|
||||
|
||||
export const byCategory = {
|
||||
list: list3,
|
||||
}
|
||||
|
||||
export const plugin2 = {
|
||||
asset,
|
||||
autoUpgrade,
|
||||
debuggingKey,
|
||||
fetchManifest,
|
||||
icon,
|
||||
@ -1983,15 +2009,15 @@ export const plugin2 = {
|
||||
marketplace: marketplace2,
|
||||
parameters,
|
||||
permission: permission2,
|
||||
preferences,
|
||||
readme,
|
||||
tasks,
|
||||
uninstall,
|
||||
upgrade,
|
||||
upload,
|
||||
byCategory,
|
||||
}
|
||||
|
||||
export const get32 = oc
|
||||
export const get33 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2002,7 +2028,7 @@ export const get32 = oc
|
||||
.output(zGetWorkspacesCurrentToolLabelsResponse)
|
||||
|
||||
export const toolLabels = {
|
||||
get: get32,
|
||||
get: get33,
|
||||
}
|
||||
|
||||
export const post44 = oc
|
||||
@ -2035,7 +2061,7 @@ export const delete9 = {
|
||||
post: post45,
|
||||
}
|
||||
|
||||
export const get33 = oc
|
||||
export const get34 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2046,11 +2072,11 @@ export const get33 = oc
|
||||
.input(z.object({ query: zGetWorkspacesCurrentToolProviderApiGetQuery }))
|
||||
.output(zGetWorkspacesCurrentToolProviderApiGetResponse)
|
||||
|
||||
export const get34 = {
|
||||
get: get33,
|
||||
export const get35 = {
|
||||
get: get34,
|
||||
}
|
||||
|
||||
export const get35 = oc
|
||||
export const get36 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2062,7 +2088,7 @@ export const get35 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderApiRemoteResponse)
|
||||
|
||||
export const remote = {
|
||||
get: get35,
|
||||
get: get36,
|
||||
}
|
||||
|
||||
export const post46 = oc
|
||||
@ -2099,7 +2125,7 @@ export const test = {
|
||||
pre,
|
||||
}
|
||||
|
||||
export const get36 = oc
|
||||
export const get37 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2111,7 +2137,7 @@ export const get36 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderApiToolsResponse)
|
||||
|
||||
export const tools = {
|
||||
get: get36,
|
||||
get: get37,
|
||||
}
|
||||
|
||||
export const post48 = oc
|
||||
@ -2132,7 +2158,7 @@ export const update2 = {
|
||||
export const api = {
|
||||
add,
|
||||
delete: delete9,
|
||||
get: get34,
|
||||
get: get35,
|
||||
remote,
|
||||
schema,
|
||||
test,
|
||||
@ -2160,7 +2186,7 @@ export const add2 = {
|
||||
post: post49,
|
||||
}
|
||||
|
||||
export const get37 = oc
|
||||
export const get38 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2177,10 +2203,10 @@ export const get37 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderBuiltinByProviderCredentialInfoResponse)
|
||||
|
||||
export const info = {
|
||||
get: get37,
|
||||
get: get38,
|
||||
}
|
||||
|
||||
export const get38 = oc
|
||||
export const get39 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2200,7 +2226,7 @@ export const get38 = oc
|
||||
)
|
||||
|
||||
export const byCredentialType = {
|
||||
get: get38,
|
||||
get: get39,
|
||||
}
|
||||
|
||||
export const schema2 = {
|
||||
@ -2212,7 +2238,7 @@ export const credential = {
|
||||
schema: schema2,
|
||||
}
|
||||
|
||||
export const get39 = oc
|
||||
export const get40 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2229,7 +2255,7 @@ export const get39 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderBuiltinByProviderCredentialsResponse)
|
||||
|
||||
export const credentials3 = {
|
||||
get: get39,
|
||||
get: get40,
|
||||
}
|
||||
|
||||
export const post50 = oc
|
||||
@ -2272,7 +2298,7 @@ export const delete10 = {
|
||||
post: post51,
|
||||
}
|
||||
|
||||
export const get40 = oc
|
||||
export const get41 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2284,10 +2310,10 @@ export const get40 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderBuiltinByProviderIconResponse)
|
||||
|
||||
export const icon2 = {
|
||||
get: get40,
|
||||
get: get41,
|
||||
}
|
||||
|
||||
export const get41 = oc
|
||||
export const get42 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2299,10 +2325,10 @@ export const get41 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderBuiltinByProviderInfoResponse)
|
||||
|
||||
export const info2 = {
|
||||
get: get41,
|
||||
get: get42,
|
||||
}
|
||||
|
||||
export const get42 = oc
|
||||
export const get43 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2316,7 +2342,7 @@ export const get42 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderBuiltinByProviderOauthClientSchemaResponse)
|
||||
|
||||
export const clientSchema = {
|
||||
get: get42,
|
||||
get: get43,
|
||||
}
|
||||
|
||||
export const delete11 = oc
|
||||
@ -2334,7 +2360,7 @@ export const delete11 = oc
|
||||
)
|
||||
.output(zDeleteWorkspacesCurrentToolProviderBuiltinByProviderOauthCustomClientResponse)
|
||||
|
||||
export const get43 = oc
|
||||
export const get44 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2365,7 +2391,7 @@ export const post52 = oc
|
||||
|
||||
export const customClient = {
|
||||
delete: delete11,
|
||||
get: get43,
|
||||
get: get44,
|
||||
post: post52,
|
||||
}
|
||||
|
||||
@ -2374,7 +2400,7 @@ export const oauth = {
|
||||
customClient,
|
||||
}
|
||||
|
||||
export const get44 = oc
|
||||
export const get45 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2386,7 +2412,7 @@ export const get44 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderBuiltinByProviderToolsResponse)
|
||||
|
||||
export const tools2 = {
|
||||
get: get44,
|
||||
get: get45,
|
||||
}
|
||||
|
||||
export const post53 = oc
|
||||
@ -2441,7 +2467,7 @@ export const auth = {
|
||||
post: post54,
|
||||
}
|
||||
|
||||
export const get45 = oc
|
||||
export const get46 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2453,14 +2479,14 @@ export const get45 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderMcpToolsByProviderIdResponse)
|
||||
|
||||
export const byProviderId = {
|
||||
get: get45,
|
||||
get: get46,
|
||||
}
|
||||
|
||||
export const tools3 = {
|
||||
byProviderId,
|
||||
}
|
||||
|
||||
export const get46 = oc
|
||||
export const get47 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2472,7 +2498,7 @@ export const get46 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderMcpUpdateByProviderIdResponse)
|
||||
|
||||
export const byProviderId2 = {
|
||||
get: get46,
|
||||
get: get47,
|
||||
}
|
||||
|
||||
export const update4 = {
|
||||
@ -2551,7 +2577,7 @@ export const delete13 = {
|
||||
post: post57,
|
||||
}
|
||||
|
||||
export const get47 = oc
|
||||
export const get48 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2562,11 +2588,11 @@ export const get47 = oc
|
||||
.input(z.object({ query: zGetWorkspacesCurrentToolProviderWorkflowGetQuery.optional() }))
|
||||
.output(zGetWorkspacesCurrentToolProviderWorkflowGetResponse)
|
||||
|
||||
export const get48 = {
|
||||
get: get47,
|
||||
export const get49 = {
|
||||
get: get48,
|
||||
}
|
||||
|
||||
export const get49 = oc
|
||||
export const get50 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2578,7 +2604,7 @@ export const get49 = oc
|
||||
.output(zGetWorkspacesCurrentToolProviderWorkflowToolsResponse)
|
||||
|
||||
export const tools4 = {
|
||||
get: get49,
|
||||
get: get50,
|
||||
}
|
||||
|
||||
export const post58 = oc
|
||||
@ -2599,7 +2625,7 @@ export const update5 = {
|
||||
export const workflow = {
|
||||
create: create2,
|
||||
delete: delete13,
|
||||
get: get48,
|
||||
get: get49,
|
||||
tools: tools4,
|
||||
update: update5,
|
||||
}
|
||||
@ -2611,7 +2637,7 @@ export const toolProvider = {
|
||||
workflow,
|
||||
}
|
||||
|
||||
export const get50 = oc
|
||||
export const get51 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2623,10 +2649,10 @@ export const get50 = oc
|
||||
.output(zGetWorkspacesCurrentToolProvidersResponse)
|
||||
|
||||
export const toolProviders = {
|
||||
get: get50,
|
||||
get: get51,
|
||||
}
|
||||
|
||||
export const get51 = oc
|
||||
export const get52 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2637,10 +2663,10 @@ export const get51 = oc
|
||||
.output(zGetWorkspacesCurrentToolsApiResponse)
|
||||
|
||||
export const api2 = {
|
||||
get: get51,
|
||||
get: get52,
|
||||
}
|
||||
|
||||
export const get52 = oc
|
||||
export const get53 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2651,10 +2677,10 @@ export const get52 = oc
|
||||
.output(zGetWorkspacesCurrentToolsBuiltinResponse)
|
||||
|
||||
export const builtin2 = {
|
||||
get: get52,
|
||||
get: get53,
|
||||
}
|
||||
|
||||
export const get53 = oc
|
||||
export const get54 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2665,10 +2691,10 @@ export const get53 = oc
|
||||
.output(zGetWorkspacesCurrentToolsMcpResponse)
|
||||
|
||||
export const mcp2 = {
|
||||
get: get53,
|
||||
get: get54,
|
||||
}
|
||||
|
||||
export const get54 = oc
|
||||
export const get55 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2679,7 +2705,7 @@ export const get54 = oc
|
||||
.output(zGetWorkspacesCurrentToolsWorkflowResponse)
|
||||
|
||||
export const workflow2 = {
|
||||
get: get54,
|
||||
get: get55,
|
||||
}
|
||||
|
||||
export const tools5 = {
|
||||
@ -2689,7 +2715,7 @@ export const tools5 = {
|
||||
workflow: workflow2,
|
||||
}
|
||||
|
||||
export const get55 = oc
|
||||
export const get56 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2701,13 +2727,13 @@ export const get55 = oc
|
||||
.output(zGetWorkspacesCurrentTriggerProviderByProviderIconResponse)
|
||||
|
||||
export const icon3 = {
|
||||
get: get55,
|
||||
get: get56,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info for a trigger provider
|
||||
*/
|
||||
export const get56 = oc
|
||||
export const get57 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2720,7 +2746,7 @@ export const get56 = oc
|
||||
.output(zGetWorkspacesCurrentTriggerProviderByProviderInfoResponse)
|
||||
|
||||
export const info3 = {
|
||||
get: get56,
|
||||
get: get57,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2741,7 +2767,7 @@ export const delete14 = oc
|
||||
/**
|
||||
* Get OAuth client configuration for a provider
|
||||
*/
|
||||
export const get57 = oc
|
||||
export const get58 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2775,7 +2801,7 @@ export const post59 = oc
|
||||
|
||||
export const client = {
|
||||
delete: delete14,
|
||||
get: get57,
|
||||
get: get58,
|
||||
post: post59,
|
||||
}
|
||||
|
||||
@ -2842,7 +2868,7 @@ export const create3 = {
|
||||
/**
|
||||
* Get the request logs for a subscription instance for a trigger provider
|
||||
*/
|
||||
export const get58 = oc
|
||||
export const get59 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2863,7 +2889,7 @@ export const get58 = oc
|
||||
)
|
||||
|
||||
export const bySubscriptionBuilderId2 = {
|
||||
get: get58,
|
||||
get: get59,
|
||||
}
|
||||
|
||||
export const logs = {
|
||||
@ -2937,7 +2963,7 @@ export const verifyAndUpdate = {
|
||||
/**
|
||||
* Get a subscription instance for a trigger provider
|
||||
*/
|
||||
export const get59 = oc
|
||||
export const get60 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2958,7 +2984,7 @@ export const get59 = oc
|
||||
)
|
||||
|
||||
export const bySubscriptionBuilderId5 = {
|
||||
get: get59,
|
||||
get: get60,
|
||||
}
|
||||
|
||||
export const builder = {
|
||||
@ -2973,7 +2999,7 @@ export const builder = {
|
||||
/**
|
||||
* List all trigger subscriptions for the current tenant's provider
|
||||
*/
|
||||
export const get60 = oc
|
||||
export const get61 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -2985,14 +3011,14 @@ export const get60 = oc
|
||||
.input(z.object({ params: zGetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListPath }))
|
||||
.output(zGetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListResponse)
|
||||
|
||||
export const list3 = {
|
||||
get: get60,
|
||||
export const list4 = {
|
||||
get: get61,
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate OAuth authorization flow for a trigger provider
|
||||
*/
|
||||
export const get61 = oc
|
||||
export const get62 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -3009,7 +3035,7 @@ export const get61 = oc
|
||||
.output(zGetWorkspacesCurrentTriggerProviderByProviderSubscriptionsOauthAuthorizeResponse)
|
||||
|
||||
export const authorize = {
|
||||
get: get61,
|
||||
get: get62,
|
||||
}
|
||||
|
||||
export const oauth3 = {
|
||||
@ -3050,7 +3076,7 @@ export const verify = {
|
||||
|
||||
export const subscriptions = {
|
||||
builder,
|
||||
list: list3,
|
||||
list: list4,
|
||||
oauth: oauth3,
|
||||
verify,
|
||||
}
|
||||
@ -3126,7 +3152,7 @@ export const triggerProvider = {
|
||||
/**
|
||||
* List all trigger providers for the current tenant
|
||||
*/
|
||||
export const get62 = oc
|
||||
export const get63 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -3138,7 +3164,7 @@ export const get62 = oc
|
||||
.output(zGetWorkspacesCurrentTriggersResponse)
|
||||
|
||||
export const triggers = {
|
||||
get: get62,
|
||||
get: get63,
|
||||
}
|
||||
|
||||
export const post67 = oc
|
||||
@ -3237,7 +3263,7 @@ export const switch3 = {
|
||||
post: post71,
|
||||
}
|
||||
|
||||
export const get63 = oc
|
||||
export const get64 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -3249,7 +3275,7 @@ export const get63 = oc
|
||||
.output(zGetWorkspacesByTenantIdModelProvidersByProviderByIconTypeByLangResponse)
|
||||
|
||||
export const byLang = {
|
||||
get: get63,
|
||||
get: get64,
|
||||
}
|
||||
|
||||
export const byIconType = {
|
||||
@ -3268,7 +3294,7 @@ export const byTenantId = {
|
||||
modelProviders: modelProviders2,
|
||||
}
|
||||
|
||||
export const get64 = oc
|
||||
export const get65 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -3279,7 +3305,7 @@ export const get64 = oc
|
||||
.output(zGetWorkspacesResponse)
|
||||
|
||||
export const workspaces = {
|
||||
get: get64,
|
||||
get: get65,
|
||||
current,
|
||||
customConfig,
|
||||
info: info4,
|
||||
|
||||
@ -376,6 +376,30 @@ export type WorkspacePermissionResponse = {
|
||||
|
||||
export type BinaryFileResponse = Blob | File
|
||||
|
||||
export type ParserAutoUpgradeChange = {
|
||||
auto_upgrade: PluginAutoUpgradeSettingsPayload
|
||||
category: PluginCategory
|
||||
}
|
||||
|
||||
export type PluginAutoUpgradeChangeResponse = {
|
||||
message?: string | null
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export type ParserExcludePlugin = {
|
||||
category: PluginCategory
|
||||
plugin_id: string
|
||||
}
|
||||
|
||||
export type SuccessResponse = {
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export type PluginAutoUpgradeFetchResponse = {
|
||||
auto_upgrade: PluginAutoUpgradeSettingsResponseModel
|
||||
category: PluginCategory
|
||||
}
|
||||
|
||||
export type PluginDebuggingKeyResponse = {
|
||||
host: string
|
||||
key: string
|
||||
@ -432,12 +456,8 @@ export type ParserDynamicOptionsWithCredentials = {
|
||||
}
|
||||
|
||||
export type ParserPermissionChange = {
|
||||
debug_permission: DebugPermission
|
||||
install_permission: InstallPermission
|
||||
}
|
||||
|
||||
export type SuccessResponse = {
|
||||
success: boolean
|
||||
debug_permission?: DebugPermission
|
||||
install_permission?: InstallPermission
|
||||
}
|
||||
|
||||
export type PluginPermissionResponse = {
|
||||
@ -445,25 +465,6 @@ export type PluginPermissionResponse = {
|
||||
install_permission: InstallPermission
|
||||
}
|
||||
|
||||
export type ParserExcludePlugin = {
|
||||
plugin_id: string
|
||||
}
|
||||
|
||||
export type PluginOperationSuccessResponse = {
|
||||
message?: string | null
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export type ParserPreferencesChange = {
|
||||
auto_upgrade: PluginAutoUpgradeSettingsPayload
|
||||
permission: PluginPermissionSettingsPayload
|
||||
}
|
||||
|
||||
export type PluginPreferencesResponse = {
|
||||
auto_upgrade: PluginAutoUpgradeSettingsPayload
|
||||
permission: PluginPermissionSettingsPayload
|
||||
}
|
||||
|
||||
export type PluginReadmeResponse = {
|
||||
readme: string
|
||||
}
|
||||
@ -499,6 +500,12 @@ export type ParserGithubUpload = {
|
||||
version: string
|
||||
}
|
||||
|
||||
export type PluginCategoryListResponse = {
|
||||
builtin_tools: Array<PluginCategoryBuiltinToolProviderResponse>
|
||||
has_more: boolean
|
||||
plugins: Array<PluginCategoryInstalledPluginResponse>
|
||||
}
|
||||
|
||||
export type ToolProviderOpaqueResponse = unknown
|
||||
|
||||
export type ApiToolProviderAddPayload = {
|
||||
@ -901,8 +908,8 @@ export type ModelCredentialLoadBalancingResponse = {
|
||||
|
||||
export type ParameterRule = {
|
||||
default?: unknown | null
|
||||
help?: I18nObject | null
|
||||
label: I18nObject
|
||||
help?: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject | null
|
||||
label: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject
|
||||
max?: number | null
|
||||
min?: number | null
|
||||
name: string
|
||||
@ -923,10 +930,6 @@ export type ProviderWithModelsResponse = {
|
||||
tenant_id: string
|
||||
}
|
||||
|
||||
export type DebugPermission = 'admins' | 'everyone' | 'noone'
|
||||
|
||||
export type InstallPermission = 'admins' | 'everyone' | 'noone'
|
||||
|
||||
export type PluginAutoUpgradeSettingsPayload = {
|
||||
exclude_plugins?: Array<string>
|
||||
include_plugins?: Array<string>
|
||||
@ -935,9 +938,75 @@ export type PluginAutoUpgradeSettingsPayload = {
|
||||
upgrade_time_of_day?: number
|
||||
}
|
||||
|
||||
export type PluginPermissionSettingsPayload = {
|
||||
debug_permission?: DebugPermission
|
||||
install_permission?: InstallPermission
|
||||
export type PluginCategory
|
||||
= | 'agent-strategy'
|
||||
| 'datasource'
|
||||
| 'extension'
|
||||
| 'model'
|
||||
| 'tool'
|
||||
| 'trigger'
|
||||
|
||||
export type PluginAutoUpgradeSettingsResponseModel = {
|
||||
exclude_plugins: Array<string>
|
||||
include_plugins: Array<string>
|
||||
strategy_setting: StrategySetting
|
||||
upgrade_mode: UpgradeMode
|
||||
upgrade_time_of_day: number
|
||||
}
|
||||
|
||||
export type DebugPermission = 'admins' | 'everyone' | 'noone'
|
||||
|
||||
export type InstallPermission = 'admins' | 'everyone' | 'noone'
|
||||
|
||||
export type PluginCategoryBuiltinToolProviderResponse = {
|
||||
allow_delete: boolean
|
||||
author: string
|
||||
description: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
icon:
|
||||
| string
|
||||
| {
|
||||
[key: string]: string
|
||||
}
|
||||
icon_dark:
|
||||
| string
|
||||
| {
|
||||
[key: string]: string
|
||||
}
|
||||
| null
|
||||
id: string
|
||||
is_team_authorization: boolean
|
||||
label: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
labels: Array<string>
|
||||
name: string
|
||||
plugin_id: string | null
|
||||
plugin_unique_identifier: string | null
|
||||
team_credentials: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
tools: Array<PluginCategoryBuiltinToolResponse>
|
||||
type: ToolProviderType
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type PluginCategoryInstalledPluginResponse = {
|
||||
checksum: string
|
||||
created_at: string
|
||||
declaration: PluginDeclarationResponse
|
||||
endpoints_active: number
|
||||
endpoints_setups: number
|
||||
id: string
|
||||
installation_id: string
|
||||
meta: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
name: string
|
||||
plugin_id: string
|
||||
plugin_unique_identifier: string
|
||||
runtime_type: string
|
||||
source: PluginInstallationSource
|
||||
tenant_id: string
|
||||
updated_at: string
|
||||
version: string
|
||||
}
|
||||
|
||||
export type ApiProviderSchemaType = 'openai_actions' | 'openai_plugin' | 'openapi' | 'swagger'
|
||||
@ -976,12 +1045,14 @@ export type CustomConfigurationResponse = {
|
||||
|
||||
export type I18nObject = {
|
||||
en_US: string
|
||||
ja_JP?: string | null
|
||||
pt_BR?: string | null
|
||||
zh_Hans?: string | null
|
||||
}
|
||||
|
||||
export type ProviderHelpEntity = {
|
||||
title: I18nObject
|
||||
url: I18nObject
|
||||
title: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject
|
||||
url: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject
|
||||
}
|
||||
|
||||
export type ModelCredentialSchema = {
|
||||
@ -1036,6 +1107,11 @@ export type ModelStatus
|
||||
| 'no-permission'
|
||||
| 'quota-exceeded'
|
||||
|
||||
export type GraphonModelRuntimeEntitiesCommonEntitiesI18nObject = {
|
||||
en_US: string
|
||||
zh_Hans?: string | null
|
||||
}
|
||||
|
||||
export type ParameterType = 'boolean' | 'float' | 'int' | 'string' | 'text'
|
||||
|
||||
export type ProviderModelWithStatusEntity = {
|
||||
@ -1059,13 +1135,86 @@ export type StrategySetting = 'disabled' | 'fix_only' | 'latest'
|
||||
|
||||
export type UpgradeMode = 'all' | 'exclude' | 'partial'
|
||||
|
||||
export type CoreToolsEntitiesCommonEntitiesI18nObject = {
|
||||
en_US: string
|
||||
ja_JP?: string | null
|
||||
pt_BR?: string | null
|
||||
zh_Hans?: string | null
|
||||
}
|
||||
|
||||
export type PluginCategoryBuiltinToolResponse = {
|
||||
author: string
|
||||
description: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
label: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
labels: Array<string>
|
||||
name: string
|
||||
output_schema: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
parameters?: Array<{
|
||||
[key: string]: unknown
|
||||
}> | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ToolProviderType
|
||||
= | 'api'
|
||||
| 'app'
|
||||
| 'builtin'
|
||||
| 'dataset-retrieval'
|
||||
| 'mcp'
|
||||
| 'plugin'
|
||||
| 'workflow'
|
||||
|
||||
export type PluginDeclarationResponse = {
|
||||
agent_strategy?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
author: string | null
|
||||
category: PluginCategory
|
||||
created_at: string
|
||||
datasource?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
description: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
endpoint?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
icon: string
|
||||
icon_dark?: string | null
|
||||
label: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
meta: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
model?: ProviderEntityResponse | null
|
||||
name: string
|
||||
plugins: {
|
||||
[key: string]: Array<string> | null
|
||||
}
|
||||
repo?: string | null
|
||||
resource: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
tags?: Array<string>
|
||||
tool?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
trigger?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
verified?: boolean
|
||||
version: string
|
||||
}
|
||||
|
||||
export type PluginInstallationSource = 'github' | 'marketplace' | 'package' | 'remote'
|
||||
|
||||
export type ToolParameterForm = 'form' | 'llm' | 'schema'
|
||||
|
||||
export type AiModelEntityResponse = {
|
||||
deprecated?: boolean
|
||||
features?: Array<ModelFeature> | null
|
||||
fetch_from: FetchFrom
|
||||
label: I18nObject
|
||||
label: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject
|
||||
model: string
|
||||
model_properties: {
|
||||
[key in ModelPropertyKey]?: unknown
|
||||
@ -1094,10 +1243,10 @@ export type CustomModelConfiguration = {
|
||||
|
||||
export type CredentialFormSchema = {
|
||||
default?: string | null
|
||||
label: I18nObject
|
||||
label: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject
|
||||
max_length?: number
|
||||
options?: Array<FormOption> | null
|
||||
placeholder?: I18nObject | null
|
||||
placeholder?: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject | null
|
||||
required?: boolean
|
||||
show_on?: Array<FormShowOnObject>
|
||||
type: FormType
|
||||
@ -1105,8 +1254,8 @@ export type CredentialFormSchema = {
|
||||
}
|
||||
|
||||
export type FieldModelSchema = {
|
||||
label: I18nObject
|
||||
placeholder?: I18nObject | null
|
||||
label: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject
|
||||
placeholder?: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject | null
|
||||
}
|
||||
|
||||
export type ProviderQuotaType = 'free' | 'paid' | 'trial'
|
||||
@ -1120,6 +1269,25 @@ export type QuotaConfiguration = {
|
||||
restrict_models?: Array<RestrictModel>
|
||||
}
|
||||
|
||||
export type ProviderEntityResponse = {
|
||||
background?: string | null
|
||||
configurate_methods: Array<ConfigurateMethod>
|
||||
description?: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject | null
|
||||
help?: ProviderHelpEntity | null
|
||||
icon_small?: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject | null
|
||||
icon_small_dark?: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject | null
|
||||
label: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject
|
||||
model_credential_schema?: ModelCredentialSchema | null
|
||||
models?: Array<AiModelEntityResponse>
|
||||
position?: {
|
||||
[key: string]: Array<string>
|
||||
} | null
|
||||
provider: string
|
||||
provider_credential_schema?: ProviderCredentialSchema | null
|
||||
provider_name?: string
|
||||
supported_model_types: Array<ModelType>
|
||||
}
|
||||
|
||||
export type PriceConfigResponse = {
|
||||
currency: string
|
||||
input: string
|
||||
@ -1128,7 +1296,7 @@ export type PriceConfigResponse = {
|
||||
}
|
||||
|
||||
export type FormOption = {
|
||||
label: I18nObject
|
||||
label: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject
|
||||
show_on?: Array<FormShowOnObject>
|
||||
value: string
|
||||
}
|
||||
@ -2166,6 +2334,50 @@ export type GetWorkspacesCurrentPluginAssetResponses = {
|
||||
export type GetWorkspacesCurrentPluginAssetResponse
|
||||
= GetWorkspacesCurrentPluginAssetResponses[keyof GetWorkspacesCurrentPluginAssetResponses]
|
||||
|
||||
export type PostWorkspacesCurrentPluginAutoUpgradeChangeData = {
|
||||
body: ParserAutoUpgradeChange
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/plugin/auto-upgrade/change'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentPluginAutoUpgradeChangeResponses = {
|
||||
200: PluginAutoUpgradeChangeResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentPluginAutoUpgradeChangeResponse
|
||||
= PostWorkspacesCurrentPluginAutoUpgradeChangeResponses[keyof PostWorkspacesCurrentPluginAutoUpgradeChangeResponses]
|
||||
|
||||
export type PostWorkspacesCurrentPluginAutoUpgradeExcludeData = {
|
||||
body: ParserExcludePlugin
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/plugin/auto-upgrade/exclude'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentPluginAutoUpgradeExcludeResponses = {
|
||||
200: SuccessResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentPluginAutoUpgradeExcludeResponse
|
||||
= PostWorkspacesCurrentPluginAutoUpgradeExcludeResponses[keyof PostWorkspacesCurrentPluginAutoUpgradeExcludeResponses]
|
||||
|
||||
export type GetWorkspacesCurrentPluginAutoUpgradeFetchData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query: {
|
||||
category: 'agent-strategy' | 'datasource' | 'extension' | 'model' | 'tool' | 'trigger'
|
||||
}
|
||||
url: '/workspaces/current/plugin/auto-upgrade/fetch'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentPluginAutoUpgradeFetchResponses = {
|
||||
200: PluginAutoUpgradeFetchResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentPluginAutoUpgradeFetchResponse
|
||||
= GetWorkspacesCurrentPluginAutoUpgradeFetchResponses[keyof GetWorkspacesCurrentPluginAutoUpgradeFetchResponses]
|
||||
|
||||
export type GetWorkspacesCurrentPluginDebuggingKeyData = {
|
||||
body?: never
|
||||
path?: never
|
||||
@ -2379,48 +2591,6 @@ export type GetWorkspacesCurrentPluginPermissionFetchResponses = {
|
||||
export type GetWorkspacesCurrentPluginPermissionFetchResponse
|
||||
= GetWorkspacesCurrentPluginPermissionFetchResponses[keyof GetWorkspacesCurrentPluginPermissionFetchResponses]
|
||||
|
||||
export type PostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeData = {
|
||||
body: ParserExcludePlugin
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/plugin/preferences/autoupgrade/exclude'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeResponses = {
|
||||
200: PluginOperationSuccessResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeResponse
|
||||
= PostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeResponses[keyof PostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeResponses]
|
||||
|
||||
export type PostWorkspacesCurrentPluginPreferencesChangeData = {
|
||||
body: ParserPreferencesChange
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/plugin/preferences/change'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentPluginPreferencesChangeResponses = {
|
||||
200: PluginOperationSuccessResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentPluginPreferencesChangeResponse
|
||||
= PostWorkspacesCurrentPluginPreferencesChangeResponses[keyof PostWorkspacesCurrentPluginPreferencesChangeResponses]
|
||||
|
||||
export type GetWorkspacesCurrentPluginPreferencesFetchData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/plugin/preferences/fetch'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentPluginPreferencesFetchResponses = {
|
||||
200: PluginPreferencesResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentPluginPreferencesFetchResponse
|
||||
= GetWorkspacesCurrentPluginPreferencesFetchResponses[keyof GetWorkspacesCurrentPluginPreferencesFetchResponses]
|
||||
|
||||
export type GetWorkspacesCurrentPluginReadmeData = {
|
||||
body?: never
|
||||
path?: never
|
||||
@ -2602,6 +2772,25 @@ export type PostWorkspacesCurrentPluginUploadPkgResponses = {
|
||||
export type PostWorkspacesCurrentPluginUploadPkgResponse
|
||||
= PostWorkspacesCurrentPluginUploadPkgResponses[keyof PostWorkspacesCurrentPluginUploadPkgResponses]
|
||||
|
||||
export type GetWorkspacesCurrentPluginByCategoryListData = {
|
||||
body?: never
|
||||
path: {
|
||||
category: string
|
||||
}
|
||||
query?: {
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
url: '/workspaces/current/plugin/{category}/list'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentPluginByCategoryListResponses = {
|
||||
200: PluginCategoryListResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentPluginByCategoryListResponse
|
||||
= GetWorkspacesCurrentPluginByCategoryListResponses[keyof GetWorkspacesCurrentPluginByCategoryListResponses]
|
||||
|
||||
export type GetWorkspacesCurrentToolLabelsData = {
|
||||
body?: never
|
||||
path?: never
|
||||
|
||||
@ -289,6 +289,21 @@ export const zWorkspacePermissionResponse = z.object({
|
||||
*/
|
||||
export const zBinaryFileResponse = z.custom<Blob | File>()
|
||||
|
||||
/**
|
||||
* PluginAutoUpgradeChangeResponse
|
||||
*/
|
||||
export const zPluginAutoUpgradeChangeResponse = z.object({
|
||||
message: z.string().nullish(),
|
||||
success: z.boolean(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SuccessResponse
|
||||
*/
|
||||
export const zSuccessResponse = z.object({
|
||||
success: z.boolean(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginDebuggingKeyResponse
|
||||
*/
|
||||
@ -375,28 +390,6 @@ export const zParserDynamicOptionsWithCredentials = z.object({
|
||||
provider: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SuccessResponse
|
||||
*/
|
||||
export const zSuccessResponse = z.object({
|
||||
success: z.boolean(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ParserExcludePlugin
|
||||
*/
|
||||
export const zParserExcludePlugin = z.object({
|
||||
plugin_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginOperationSuccessResponse
|
||||
*/
|
||||
export const zPluginOperationSuccessResponse = z.object({
|
||||
message: z.string().nullish(),
|
||||
success: z.boolean(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginReadmeResponse
|
||||
*/
|
||||
@ -1022,6 +1015,26 @@ export const zModelCredentialResponse = z.object({
|
||||
load_balancing: zModelCredentialLoadBalancingResponse,
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginCategory
|
||||
*/
|
||||
export const zPluginCategory = z.enum([
|
||||
'agent-strategy',
|
||||
'datasource',
|
||||
'extension',
|
||||
'model',
|
||||
'tool',
|
||||
'trigger',
|
||||
])
|
||||
|
||||
/**
|
||||
* ParserExcludePlugin
|
||||
*/
|
||||
export const zParserExcludePlugin = z.object({
|
||||
category: zPluginCategory,
|
||||
plugin_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* DebugPermission
|
||||
*/
|
||||
@ -1036,8 +1049,8 @@ export const zInstallPermission = z.enum(['admins', 'everyone', 'noone'])
|
||||
* ParserPermissionChange
|
||||
*/
|
||||
export const zParserPermissionChange = z.object({
|
||||
debug_permission: zDebugPermission,
|
||||
install_permission: zInstallPermission,
|
||||
debug_permission: zDebugPermission.optional().default('everyone'),
|
||||
install_permission: zInstallPermission.optional().default('everyone'),
|
||||
})
|
||||
|
||||
/**
|
||||
@ -1048,14 +1061,6 @@ export const zPluginPermissionResponse = z.object({
|
||||
install_permission: zInstallPermission,
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginPermissionSettingsPayload
|
||||
*/
|
||||
export const zPluginPermissionSettingsPayload = z.object({
|
||||
debug_permission: zDebugPermission.optional().default('everyone'),
|
||||
install_permission: zInstallPermission.optional().default('everyone'),
|
||||
})
|
||||
|
||||
/**
|
||||
* ApiProviderSchemaType
|
||||
*
|
||||
@ -1178,19 +1183,11 @@ export const zConfigurateMethod = z.enum(['customizable-model', 'predefined-mode
|
||||
*/
|
||||
export const zI18nObject = z.object({
|
||||
en_US: z.string(),
|
||||
ja_JP: z.string().nullish(),
|
||||
pt_BR: z.string().nullish(),
|
||||
zh_Hans: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ProviderHelpEntity
|
||||
*
|
||||
* Model class for provider help.
|
||||
*/
|
||||
export const zProviderHelpEntity = z.object({
|
||||
title: zI18nObject,
|
||||
url: zI18nObject,
|
||||
})
|
||||
|
||||
/**
|
||||
* ProviderType
|
||||
*/
|
||||
@ -1254,6 +1251,26 @@ export const zModelStatus = z.enum([
|
||||
'quota-exceeded',
|
||||
])
|
||||
|
||||
/**
|
||||
* I18nObject
|
||||
*
|
||||
* Model class for i18n object.
|
||||
*/
|
||||
export const zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject = z.object({
|
||||
en_US: z.string(),
|
||||
zh_Hans: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ProviderHelpEntity
|
||||
*
|
||||
* Model class for provider help.
|
||||
*/
|
||||
export const zProviderHelpEntity = z.object({
|
||||
title: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject,
|
||||
url: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject,
|
||||
})
|
||||
|
||||
/**
|
||||
* ParameterType
|
||||
*
|
||||
@ -1268,8 +1285,8 @@ export const zParameterType = z.enum(['boolean', 'float', 'int', 'string', 'text
|
||||
*/
|
||||
export const zParameterRule = z.object({
|
||||
default: z.unknown().nullish(),
|
||||
help: zI18nObject.nullish(),
|
||||
label: zI18nObject,
|
||||
help: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject.nullish(),
|
||||
label: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject,
|
||||
max: z.number().nullish(),
|
||||
min: z.number().nullish(),
|
||||
name: z.string(),
|
||||
@ -1356,21 +1373,98 @@ export const zPluginAutoUpgradeSettingsPayload = z.object({
|
||||
})
|
||||
|
||||
/**
|
||||
* ParserPreferencesChange
|
||||
* ParserAutoUpgradeChange
|
||||
*/
|
||||
export const zParserPreferencesChange = z.object({
|
||||
export const zParserAutoUpgradeChange = z.object({
|
||||
auto_upgrade: zPluginAutoUpgradeSettingsPayload,
|
||||
permission: zPluginPermissionSettingsPayload,
|
||||
category: zPluginCategory,
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginPreferencesResponse
|
||||
* PluginAutoUpgradeSettingsResponseModel
|
||||
*/
|
||||
export const zPluginPreferencesResponse = z.object({
|
||||
auto_upgrade: zPluginAutoUpgradeSettingsPayload,
|
||||
permission: zPluginPermissionSettingsPayload,
|
||||
export const zPluginAutoUpgradeSettingsResponseModel = z.object({
|
||||
exclude_plugins: z.array(z.string()),
|
||||
include_plugins: z.array(z.string()),
|
||||
strategy_setting: zStrategySetting,
|
||||
upgrade_mode: zUpgradeMode,
|
||||
upgrade_time_of_day: z.int(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginAutoUpgradeFetchResponse
|
||||
*/
|
||||
export const zPluginAutoUpgradeFetchResponse = z.object({
|
||||
auto_upgrade: zPluginAutoUpgradeSettingsResponseModel,
|
||||
category: zPluginCategory,
|
||||
})
|
||||
|
||||
/**
|
||||
* I18nObject
|
||||
*
|
||||
* Model class for i18n object.
|
||||
*/
|
||||
export const zCoreToolsEntitiesCommonEntitiesI18nObject = z.object({
|
||||
en_US: z.string(),
|
||||
ja_JP: z.string().nullish(),
|
||||
pt_BR: z.string().nullish(),
|
||||
zh_Hans: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginCategoryBuiltinToolResponse
|
||||
*/
|
||||
export const zPluginCategoryBuiltinToolResponse = z.object({
|
||||
author: z.string(),
|
||||
description: zCoreToolsEntitiesCommonEntitiesI18nObject,
|
||||
label: zCoreToolsEntitiesCommonEntitiesI18nObject,
|
||||
labels: z.array(z.string()),
|
||||
name: z.string(),
|
||||
output_schema: z.record(z.string(), z.unknown()),
|
||||
parameters: z.array(z.record(z.string(), z.unknown())).nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ToolProviderType
|
||||
*
|
||||
* Enum class for tool provider
|
||||
*/
|
||||
export const zToolProviderType = z.enum([
|
||||
'api',
|
||||
'app',
|
||||
'builtin',
|
||||
'dataset-retrieval',
|
||||
'mcp',
|
||||
'plugin',
|
||||
'workflow',
|
||||
])
|
||||
|
||||
/**
|
||||
* PluginCategoryBuiltinToolProviderResponse
|
||||
*/
|
||||
export const zPluginCategoryBuiltinToolProviderResponse = z.object({
|
||||
allow_delete: z.boolean(),
|
||||
author: z.string(),
|
||||
description: zCoreToolsEntitiesCommonEntitiesI18nObject,
|
||||
icon: z.union([z.string(), z.record(z.string(), z.string())]),
|
||||
icon_dark: z.union([z.string(), z.record(z.string(), z.string())]).nullable(),
|
||||
id: z.string(),
|
||||
is_team_authorization: z.boolean(),
|
||||
label: zCoreToolsEntitiesCommonEntitiesI18nObject,
|
||||
labels: z.array(z.string()),
|
||||
name: z.string(),
|
||||
plugin_id: z.string().nullable(),
|
||||
plugin_unique_identifier: z.string().nullable(),
|
||||
team_credentials: z.record(z.string(), z.unknown()),
|
||||
tools: z.array(zPluginCategoryBuiltinToolResponse),
|
||||
type: zToolProviderType,
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginInstallationSource
|
||||
*/
|
||||
export const zPluginInstallationSource = z.enum(['github', 'marketplace', 'package', 'remote'])
|
||||
|
||||
/**
|
||||
* ToolParameterForm
|
||||
*/
|
||||
@ -1458,8 +1552,8 @@ export const zCustomConfigurationResponse = z.object({
|
||||
* FieldModelSchema
|
||||
*/
|
||||
export const zFieldModelSchema = z.object({
|
||||
label: zI18nObject,
|
||||
placeholder: zI18nObject.nullish(),
|
||||
label: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject,
|
||||
placeholder: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject.nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
@ -1489,7 +1583,7 @@ export const zAiModelEntityResponse = z.object({
|
||||
deprecated: z.boolean().optional().default(false),
|
||||
features: z.array(zModelFeature).nullish(),
|
||||
fetch_from: zFetchFrom,
|
||||
label: zI18nObject,
|
||||
label: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject,
|
||||
model: z.string(),
|
||||
model_properties: z.record(z.string(), z.unknown()),
|
||||
model_type: zModelType,
|
||||
@ -1573,7 +1667,7 @@ export const zFormShowOnObject = z.object({
|
||||
* Model class for form option.
|
||||
*/
|
||||
export const zFormOption = z.object({
|
||||
label: zI18nObject,
|
||||
label: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject,
|
||||
show_on: z.array(zFormShowOnObject).optional().default([]),
|
||||
value: z.string(),
|
||||
})
|
||||
@ -1592,10 +1686,10 @@ export const zFormType = z.enum(['radio', 'secret-input', 'select', 'switch', 't
|
||||
*/
|
||||
export const zCredentialFormSchema = z.object({
|
||||
default: z.string().nullish(),
|
||||
label: zI18nObject,
|
||||
label: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject,
|
||||
max_length: z.int().optional().default(0),
|
||||
options: z.array(zFormOption).nullish(),
|
||||
placeholder: zI18nObject.nullish(),
|
||||
placeholder: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject.nullish(),
|
||||
required: z.boolean().optional().default(true),
|
||||
show_on: z.array(zFormShowOnObject).optional().default([]),
|
||||
type: zFormType,
|
||||
@ -1621,6 +1715,86 @@ export const zProviderCredentialSchema = z.object({
|
||||
credential_form_schemas: z.array(zCredentialFormSchema),
|
||||
})
|
||||
|
||||
/**
|
||||
* ProviderEntityResponse
|
||||
*
|
||||
* Runtime provider response with codegen-safe model pricing schemas.
|
||||
*/
|
||||
export const zProviderEntityResponse = z.object({
|
||||
background: z.string().nullish(),
|
||||
configurate_methods: z.array(zConfigurateMethod),
|
||||
description: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject.nullish(),
|
||||
help: zProviderHelpEntity.nullish(),
|
||||
icon_small: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject.nullish(),
|
||||
icon_small_dark: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject.nullish(),
|
||||
label: zGraphonModelRuntimeEntitiesCommonEntitiesI18nObject,
|
||||
model_credential_schema: zModelCredentialSchema.nullish(),
|
||||
models: z.array(zAiModelEntityResponse).optional().default([]),
|
||||
position: z.record(z.string(), z.array(z.string())).nullish().default({}),
|
||||
provider: z.string(),
|
||||
provider_credential_schema: zProviderCredentialSchema.nullish(),
|
||||
provider_name: z.string().optional().default(''),
|
||||
supported_model_types: z.array(zModelType),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginDeclarationResponse
|
||||
*/
|
||||
export const zPluginDeclarationResponse = z.object({
|
||||
agent_strategy: z.record(z.string(), z.unknown()).nullish(),
|
||||
author: z.string().nullable(),
|
||||
category: zPluginCategory,
|
||||
created_at: z.iso.datetime(),
|
||||
datasource: z.record(z.string(), z.unknown()).nullish(),
|
||||
description: zCoreToolsEntitiesCommonEntitiesI18nObject,
|
||||
endpoint: z.record(z.string(), z.unknown()).nullish(),
|
||||
icon: z.string(),
|
||||
icon_dark: z.string().nullish(),
|
||||
label: zCoreToolsEntitiesCommonEntitiesI18nObject,
|
||||
meta: z.record(z.string(), z.unknown()),
|
||||
model: zProviderEntityResponse.nullish(),
|
||||
name: z.string(),
|
||||
plugins: z.record(z.string(), z.array(z.string()).nullable()),
|
||||
repo: z.string().nullish(),
|
||||
resource: z.record(z.string(), z.unknown()),
|
||||
tags: z.array(z.string()).optional(),
|
||||
tool: z.record(z.string(), z.unknown()).nullish(),
|
||||
trigger: z.record(z.string(), z.unknown()).nullish(),
|
||||
verified: z.boolean().optional().default(false),
|
||||
version: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginCategoryInstalledPluginResponse
|
||||
*/
|
||||
export const zPluginCategoryInstalledPluginResponse = z.object({
|
||||
checksum: z.string(),
|
||||
created_at: z.iso.datetime(),
|
||||
declaration: zPluginDeclarationResponse,
|
||||
endpoints_active: z.int(),
|
||||
endpoints_setups: z.int(),
|
||||
id: z.string(),
|
||||
installation_id: z.string(),
|
||||
meta: z.record(z.string(), z.unknown()),
|
||||
name: z.string(),
|
||||
plugin_id: z.string(),
|
||||
plugin_unique_identifier: z.string(),
|
||||
runtime_type: z.string(),
|
||||
source: zPluginInstallationSource,
|
||||
tenant_id: z.string(),
|
||||
updated_at: z.iso.datetime(),
|
||||
version: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginCategoryListResponse
|
||||
*/
|
||||
export const zPluginCategoryListResponse = z.object({
|
||||
builtin_tools: z.array(zPluginCategoryBuiltinToolProviderResponse),
|
||||
has_more: z.boolean(),
|
||||
plugins: z.array(zPluginCategoryInstalledPluginResponse),
|
||||
})
|
||||
|
||||
/**
|
||||
* QuotaUnit
|
||||
*/
|
||||
@ -2294,6 +2468,30 @@ export const zGetWorkspacesCurrentPluginAssetQuery = z.object({
|
||||
*/
|
||||
export const zGetWorkspacesCurrentPluginAssetResponse = zBinaryFileResponse
|
||||
|
||||
export const zPostWorkspacesCurrentPluginAutoUpgradeChangeBody = zParserAutoUpgradeChange
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zPostWorkspacesCurrentPluginAutoUpgradeChangeResponse
|
||||
= zPluginAutoUpgradeChangeResponse
|
||||
|
||||
export const zPostWorkspacesCurrentPluginAutoUpgradeExcludeBody = zParserExcludePlugin
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zPostWorkspacesCurrentPluginAutoUpgradeExcludeResponse = zSuccessResponse
|
||||
|
||||
export const zGetWorkspacesCurrentPluginAutoUpgradeFetchQuery = z.object({
|
||||
category: z.enum(['agent-strategy', 'datasource', 'extension', 'model', 'tool', 'trigger']),
|
||||
})
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zGetWorkspacesCurrentPluginAutoUpgradeFetchResponse = zPluginAutoUpgradeFetchResponse
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
@ -2408,26 +2606,6 @@ export const zPostWorkspacesCurrentPluginPermissionChangeResponse = zSuccessResp
|
||||
*/
|
||||
export const zGetWorkspacesCurrentPluginPermissionFetchResponse = zPluginPermissionResponse
|
||||
|
||||
export const zPostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeBody = zParserExcludePlugin
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zPostWorkspacesCurrentPluginPreferencesAutoupgradeExcludeResponse
|
||||
= zPluginOperationSuccessResponse
|
||||
|
||||
export const zPostWorkspacesCurrentPluginPreferencesChangeBody = zParserPreferencesChange
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zPostWorkspacesCurrentPluginPreferencesChangeResponse = zPluginOperationSuccessResponse
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zGetWorkspacesCurrentPluginPreferencesFetchResponse = zPluginPreferencesResponse
|
||||
|
||||
export const zGetWorkspacesCurrentPluginReadmeQuery = z.object({
|
||||
language: z.string().optional().default('en-US'),
|
||||
plugin_unique_identifier: z.string(),
|
||||
@ -2519,6 +2697,20 @@ export const zPostWorkspacesCurrentPluginUploadGithubResponse = zPluginDaemonOpe
|
||||
*/
|
||||
export const zPostWorkspacesCurrentPluginUploadPkgResponse = zPluginDaemonOperationResponse
|
||||
|
||||
export const zGetWorkspacesCurrentPluginByCategoryListPath = z.object({
|
||||
category: z.string(),
|
||||
})
|
||||
|
||||
export const zGetWorkspacesCurrentPluginByCategoryListQuery = z.object({
|
||||
page: z.int().gte(1).optional().default(1),
|
||||
page_size: z.int().gte(1).lte(256).optional().default(256),
|
||||
})
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zGetWorkspacesCurrentPluginByCategoryListResponse = zPluginCategoryListResponse
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
|
||||
@ -265,6 +265,12 @@
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
@utility title-5xl-semi-bold {
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
@utility title-5xl-bold {
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
|
||||
@ -156,6 +156,19 @@ html[data-theme="dark"] {
|
||||
--color-components-main-nav-nav-button-bg-active: rgb(200 206 218 / 0.14);
|
||||
--color-components-main-nav-nav-button-border: rgb(255 255 255 / 0.08);
|
||||
--color-components-main-nav-nav-button-bg-hover: rgb(200 206 218 / 0.04);
|
||||
--color-components-main-nav-glass-text-glow: #3146ff2e;
|
||||
--color-components-main-nav-glass-surface-first: #0033ff14;
|
||||
--color-components-main-nav-glass-surface-middle-1: #0033ff1f;
|
||||
--color-components-main-nav-glass-surface-middle-2: #0033ff1a;
|
||||
--color-components-main-nav-glass-surface-end: #0033ff14;
|
||||
--color-components-main-nav-glass-edge-highlight-first: #fffffffa;
|
||||
--color-components-main-nav-glass-edge-highlight-end: #ffffff6b;
|
||||
--color-components-main-nav-glass-edge-reflection-first: #0033ff00;
|
||||
--color-components-main-nav-glass-edge-reflection-middle: #0033ff99;
|
||||
--color-components-main-nav-glass-edge-reflection-end: #0033ff00;
|
||||
--color-components-main-nav-glass-inner-glow: #ffffff4d;
|
||||
--color-components-main-nav-glass-shadow-reflection: #0033ff0a;
|
||||
--color-components-main-nav-glass-shadow-reflection-glow: #ffffff00;
|
||||
|
||||
--color-components-main-nav-nav-user-border: rgb(255 255 255 / 0.05);
|
||||
|
||||
|
||||
@ -156,6 +156,19 @@ html[data-theme="light"] {
|
||||
--color-components-main-nav-nav-button-bg-active: #fcfcfd;
|
||||
--color-components-main-nav-nav-button-border: rgb(255 255 255 / 0.95);
|
||||
--color-components-main-nav-nav-button-bg-hover: rgb(16 24 40 / 0.04);
|
||||
--color-components-main-nav-glass-text-glow: #3146ff2e;
|
||||
--color-components-main-nav-glass-surface-first: #0033ff14;
|
||||
--color-components-main-nav-glass-surface-middle-1: #0033ff1f;
|
||||
--color-components-main-nav-glass-surface-middle-2: #0033ff1a;
|
||||
--color-components-main-nav-glass-surface-end: #0033ff14;
|
||||
--color-components-main-nav-glass-edge-highlight-first: #fffffffa;
|
||||
--color-components-main-nav-glass-edge-highlight-end: #ffffff6b;
|
||||
--color-components-main-nav-glass-edge-reflection-first: #0033ff00;
|
||||
--color-components-main-nav-glass-edge-reflection-middle: #0033ff99;
|
||||
--color-components-main-nav-glass-edge-reflection-end: #0033ff00;
|
||||
--color-components-main-nav-glass-inner-glow: #ffffff4d;
|
||||
--color-components-main-nav-glass-shadow-reflection: #0033ff0a;
|
||||
--color-components-main-nav-glass-shadow-reflection-glow: #ffffff00;
|
||||
|
||||
--color-components-main-nav-nav-user-border: #ffffff;
|
||||
|
||||
|
||||
@ -163,6 +163,19 @@
|
||||
--color-components-main-nav-nav-button-bg-active: var(--color-components-main-nav-nav-button-bg-active);
|
||||
--color-components-main-nav-nav-button-border: var(--color-components-main-nav-nav-button-border);
|
||||
--color-components-main-nav-nav-button-bg-hover: var(--color-components-main-nav-nav-button-bg-hover);
|
||||
--color-components-main-nav-glass-text-glow: var(--color-components-main-nav-glass-text-glow);
|
||||
--color-components-main-nav-glass-surface-first: var(--color-components-main-nav-glass-surface-first);
|
||||
--color-components-main-nav-glass-surface-middle-1: var(--color-components-main-nav-glass-surface-middle-1);
|
||||
--color-components-main-nav-glass-surface-middle-2: var(--color-components-main-nav-glass-surface-middle-2);
|
||||
--color-components-main-nav-glass-surface-end: var(--color-components-main-nav-glass-surface-end);
|
||||
--color-components-main-nav-glass-edge-highlight-first: var(--color-components-main-nav-glass-edge-highlight-first);
|
||||
--color-components-main-nav-glass-edge-highlight-end: var(--color-components-main-nav-glass-edge-highlight-end);
|
||||
--color-components-main-nav-glass-edge-reflection-first: var(--color-components-main-nav-glass-edge-reflection-first);
|
||||
--color-components-main-nav-glass-edge-reflection-middle: var(--color-components-main-nav-glass-edge-reflection-middle);
|
||||
--color-components-main-nav-glass-edge-reflection-end: var(--color-components-main-nav-glass-edge-reflection-end);
|
||||
--color-components-main-nav-glass-inner-glow: var(--color-components-main-nav-glass-inner-glow);
|
||||
--color-components-main-nav-glass-shadow-reflection: var(--color-components-main-nav-glass-shadow-reflection);
|
||||
--color-components-main-nav-glass-shadow-reflection-glow: var(--color-components-main-nav-glass-shadow-reflection-glow);
|
||||
|
||||
--color-components-main-nav-nav-user-border: var(--color-components-main-nav-nav-user-border);
|
||||
|
||||
|
||||
43
packages/iconify-collections/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
# @dify/iconify-collections
|
||||
|
||||
Pre-generated Iconify collections for Dify custom SVG icons. The web app imports these collections from this package so Tailwind does not need to scan and build custom SVG icon data from the old `web/app/components/base/icons/src` tree during dev startup.
|
||||
|
||||
## Adding Custom SVG Icons
|
||||
|
||||
Add new SVG source files under one of these directories:
|
||||
|
||||
- `assets/public/...` for multi-color or public brand-like icons.
|
||||
- `assets/vender/...` for UI vendor icons that should render with `currentColor`.
|
||||
|
||||
After adding or changing SVG files, regenerate the packaged collections:
|
||||
|
||||
```bash
|
||||
pnpm --filter @dify/iconify-collections generate
|
||||
```
|
||||
|
||||
Then run the dimension guard:
|
||||
|
||||
```bash
|
||||
pnpm --filter @dify/iconify-collections check:dimensions
|
||||
```
|
||||
|
||||
This protects existing icon groups with layout-sensitive intrinsic sizes, such as the `main-nav-*` icons that must remain `20x20` after collection flattening.
|
||||
|
||||
Commit both the SVG source files and the generated package files under `custom-public/` or `custom-vender/`.
|
||||
Restart the web dev server after regenerating icons. Tailwind loads this plugin collection at startup, so an already-running dev server may not render newly-added `i-custom-*` classes until it restarts.
|
||||
|
||||
Use the generated icons through Tailwind icon classes in frontend code. For example:
|
||||
|
||||
```text
|
||||
assets/vender/integrations/mcp.svg
|
||||
```
|
||||
|
||||
becomes:
|
||||
|
||||
```tsx
|
||||
<span aria-hidden className="i-custom-vender-integrations-mcp size-4" />
|
||||
```
|
||||
|
||||
Do not add new generated React icon components or JSON files under `web/app/components/base/icons/src/...` for new custom SVG icons. That path is legacy; new custom icons should flow through this package and be consumed as `i-custom-*` classes.
|
||||
|
||||
When reviewing generated `icons.json` diffs, check that unrelated existing icon groups did not lose or change their intrinsic `width` and `height`. If a group is layout-sensitive, add it to `scripts/check-icon-dimensions.ts`.
|
||||
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 15.3333 14.6667" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M13.1423 4.75207L12.9779 5.12919C12.8576 5.40528 12.4757 5.40528 12.3554 5.12919L12.1911 4.75207C11.8981 4.07965 11.3703 3.54427 10.7118 3.25139L10.2053 3.02615C9.93153 2.90435 9.93153 2.50587 10.2053 2.38408L10.6835 2.17143C11.3589 1.87101 11.8961 1.31582 12.1841 0.620552L12.3529 0.213023C12.4705 -0.0710075 12.8628 -0.0710075 12.9804 0.213023L13.1492 0.620552C13.4372 1.31582 13.9744 1.87101 14.6499 2.17143L15.1279 2.38408C15.4018 2.50587 15.4018 2.90435 15.1279 3.02615L14.6215 3.25139C13.963 3.54427 13.4353 4.07965 13.1423 4.75207ZM5.33333 1.33333C8.045 1.33333 10.284 3.35708 10.6225 5.97663L12.1228 8.3358C12.2216 8.49113 12.2017 8.72313 11.9729 8.82113L10.6667 9.38067V11.3333C10.6667 12.0697 10.0697 12.6667 9.33333 12.6667H8.00067L8 14.6667H2L2.00017 12.2041C2.00022 11.4168 1.70901 10.6725 1.17033 10.0007C0.438047 9.08753 0 7.92827 0 6.66667C0 3.72115 2.38781 1.33333 5.33333 1.33333ZM13.4357 12.0683L12.3262 11.3286C12.9624 10.3761 13.3333 9.2314 13.3333 8.00007C13.3333 7.65933 13.3049 7.3252 13.2504 7L14.5457 6.66667C14.6252 7.09907 14.6667 7.54467 14.6667 8.00007C14.6667 9.50507 14.2133 10.9041 13.4357 12.0683Z" fill="var(--fill-0, #18222F)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 15.3333 14.6667" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M13.1423 4.75207L12.9779 5.12919C12.8576 5.40528 12.4757 5.40528 12.3554 5.12919L12.1911 4.75207C11.8981 4.07965 11.3703 3.54427 10.7118 3.25139L10.2053 3.02615C9.93153 2.90435 9.93153 2.50587 10.2053 2.38408L10.6835 2.17143C11.3589 1.87101 11.8961 1.31582 12.1841 0.620552L12.3529 0.213023C12.4705 -0.0710075 12.8628 -0.0710075 12.9804 0.213023L13.1492 0.620552C13.4372 1.31582 13.9744 1.87101 14.6499 2.17143L15.1279 2.38408C15.4018 2.50587 15.4018 2.90435 15.1279 3.02615L14.6215 3.25139C13.963 3.54427 13.4353 4.07965 13.1423 4.75207ZM5.33333 1.33333C8.045 1.33333 10.284 3.35708 10.6225 5.97663L12.1228 8.3358C12.2216 8.49113 12.2017 8.72313 11.9729 8.82113L10.6667 9.38067V11.3333C10.6667 12.0697 10.0697 12.6667 9.33333 12.6667H8.00067L8 14.6667H2L2.00017 12.2041C2.00022 11.4168 1.70901 10.6725 1.17033 10.0007C0.438047 9.08753 0 7.92827 0 6.66667C0 3.72115 2.38781 1.33333 5.33333 1.33333ZM5.33333 2.66667C3.12419 2.66667 1.33333 4.45753 1.33333 6.66667C1.33333 7.58993 1.64545 8.46193 2.21052 9.1666C2.93977 10.076 3.33357 11.1115 3.3335 12.2042L3.33342 13.3333H6.66713L6.6678 11.3333H9.33333V8.50127L10.3665 8.05873L9.33813 6.44175L9.30007 6.14745C9.04433 4.16761 7.34953 2.66667 5.33333 2.66667ZM12.3262 11.3286L13.4357 12.0683C14.2133 10.9041 14.6667 9.50507 14.6667 8.00007C14.6667 7.54467 14.6252 7.09907 14.5457 6.66667L13.2504 7C13.3049 7.3252 13.3333 7.65933 13.3333 8.00007C13.3333 9.2314 12.9624 10.3761 12.3262 11.3286Z" fill="var(--fill-0, #495464)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,10 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon">
|
||||
<g id="Vector">
|
||||
<path d="M5.92578 11.0095C5.92578 10.0175 5.12163 9.21267 4.12956 9.21267C3.13752 9.21271 2.33333 10.0175 2.33333 11.0095C2.33349 12.0015 3.13762 12.8057 4.12956 12.8058C5.12153 12.8058 5.92562 12.0015 5.92578 11.0095ZM13.6667 11.0095C13.6667 10.0175 12.8625 9.21271 11.8704 9.21267C10.8784 9.21267 10.0742 10.0175 10.0742 11.0095C10.0744 12.0015 10.8785 12.8058 11.8704 12.8058C12.8624 12.8057 13.6665 12.0015 13.6667 11.0095ZM9.79622 4.324C9.79619 3.33197 8.99205 2.52778 8 2.52778C7.00795 2.52778 6.20382 3.33197 6.20378 4.324C6.20378 5.31607 7.00793 6.12023 8 6.12023C8.99207 6.12023 9.79622 5.31607 9.79622 4.324ZM11.1296 4.324C11.1296 5.82362 10.0748 7.07639 8.66667 7.38194V7.9197L9.74284 8.71398C10.3012 8.19618 11.0489 7.87934 11.8704 7.87934C13.5989 7.87938 15 9.28112 15 11.0095C14.9998 12.7378 13.5988 14.1391 11.8704 14.1391C10.1421 14.1391 8.74104 12.7379 8.74089 11.0095C8.74089 10.5838 8.82585 10.1777 8.97982 9.80772L8 9.08377L7.01953 9.80772C7.17356 10.1778 7.25911 10.5837 7.25911 11.0095C7.25896 12.7379 5.85791 14.1391 4.12956 14.1391C2.40124 14.1391 1.00016 12.7378 1 11.0095C1 9.28112 2.40114 7.87938 4.12956 7.87934C4.95094 7.87934 5.69819 8.19637 6.25651 8.71398L7.33333 7.9197V7.38194C5.92523 7.07639 4.87044 5.82362 4.87044 4.324C4.87048 2.59559 6.27158 1.19444 8 1.19444C9.72842 1.19444 11.1295 2.59559 11.1296 4.324Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M9.79622 4.324C9.79619 3.33197 8.99205 2.52778 8 2.52778C7.00795 2.52778 6.20382 3.33197 6.20378 4.324C6.20378 5.31607 7.00793 6.12023 8 6.12023C8.99207 6.12023 9.79622 5.31607 9.79622 4.324Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M5.92578 11.0095C5.92578 10.0175 5.12163 9.21267 4.12956 9.21267C3.13752 9.21271 2.33333 10.0175 2.33333 11.0095C2.33349 12.0015 3.13762 12.8057 4.12956 12.8058C5.12153 12.8058 5.92562 12.0015 5.92578 11.0095Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M13.6667 11.0095C13.6667 10.0175 12.8625 9.21271 11.8704 9.21267C10.8784 9.21267 10.0742 10.0175 10.0742 11.0095C10.0744 12.0015 10.8785 12.8058 11.8704 12.8058C12.8624 12.8057 13.6665 12.0015 13.6667 11.0095Z" fill="var(--fill-0, #18222F)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 14 12.9447" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M4.92578 9.8151C4.92578 8.82303 4.12163 8.01823 3.12956 8.01823C2.13752 8.01827 1.33333 8.82306 1.33333 9.8151C1.33349 10.807 2.13762 11.6113 3.12956 11.6113C4.12153 11.6113 4.92563 10.807 4.92578 9.8151ZM12.6667 9.8151C12.6667 8.82306 11.8625 8.01827 10.8704 8.01823C9.87837 8.01823 9.07422 8.82303 9.07422 9.8151C9.07438 10.807 9.87847 11.6113 10.8704 11.6113C11.8624 11.6113 12.6665 10.807 12.6667 9.8151ZM8.79622 3.12956C8.79619 2.13752 7.99205 1.33333 7 1.33333C6.00795 1.33333 5.20382 2.13752 5.20378 3.12956C5.20378 4.12163 6.00793 4.92578 7 4.92578C7.99207 4.92578 8.79622 4.12163 8.79622 3.12956ZM10.1296 3.12956C10.1296 4.62918 9.07477 5.88194 7.66667 6.1875V6.72526L8.74284 7.51953C9.3012 7.00174 10.0489 6.6849 10.8704 6.6849C12.5989 6.68493 14 8.08668 14 9.8151C13.9998 11.5434 12.5988 12.9446 10.8704 12.9447C9.14209 12.9447 7.74104 11.5434 7.74089 9.8151C7.74089 9.38937 7.82585 8.98325 7.97982 8.61328L7 7.88932L6.01953 8.61328C6.17356 8.98332 6.25911 9.38929 6.25911 9.8151C6.25896 11.5434 4.85791 12.9447 3.12956 12.9447C1.40124 12.9446 0.000156326 11.5434 0 9.8151C0 8.08668 1.40114 6.68493 3.12956 6.6849C3.95094 6.6849 4.69819 7.00193 5.25651 7.51953L6.33333 6.72526V6.1875C4.92523 5.88194 3.87044 4.62918 3.87044 3.12956C3.87048 1.40114 5.27158 0 7 0C8.72843 0 10.1295 1.40114 10.1296 3.12956Z" fill="var(--fill-0, #495464)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 12.6667 14.2807" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M12.0014 3.2815L6.33333 0L0.665287 3.2815L6.33333 6.563L12.0014 3.2815ZM0 4.437V11L5.66667 14.2807V7.71767L0 4.437ZM7 14.2807L12.6667 11V4.437L7 7.71767V14.2807Z" fill="var(--fill-0, #18222F)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 403 B |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 12.6667 14.6667" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M6.33333 0L12.6667 3.66667V11L6.33333 14.6667L0 11V3.66667L6.33333 0ZM1.99592 4.0518L6.3334 6.56293L10.6708 4.05183L6.33333 1.54067L1.99592 4.0518ZM1.33333 5.20886V10.2313L5.66673 12.7401V7.71767L1.33333 5.20886ZM7.00007 12.74L11.3333 10.2313V5.20891L7.00007 7.71767V12.74Z" fill="var(--fill-0, #495464)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 515 B |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 12 13.3333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M3.33333 2C3.33333 0.895433 4.22877 0 5.33333 0C6.43793 0 7.33333 0.895433 7.33333 2C7.33333 2.23376 7.2932 2.45815 7.21953 2.66667H11.3333C11.7015 2.66667 12 2.96515 12 3.33333V5.41735C12 5.62345 11.9047 5.81796 11.7418 5.94423C11.5789 6.07053 11.3667 6.11433 11.1671 6.063C11.0079 6.022 10.8403 6 10.6667 6C9.56207 6 8.66667 6.8954 8.66667 8C8.66667 9.1046 9.56207 10 10.6667 10C10.8403 10 11.0079 9.978 11.1671 9.937C11.3667 9.88567 11.5789 9.92947 11.7418 10.0557C11.9047 10.1821 12 10.3765 12 10.5827V12.6667C12 13.0349 11.7015 13.3333 11.3333 13.3333H0.666667C0.29848 13.3333 0 13.0349 0 12.6667V3.33333C0 2.96515 0.29848 2.66667 0.666667 2.66667H3.44714C3.37343 2.45815 3.33333 2.23376 3.33333 2Z" fill="var(--fill-0, #18222F)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 940 B |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 12 13.3333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M2.66667 2.66667C2.66667 1.19391 3.86057 0 5.33333 0C6.80607 0 8 1.19391 8 2.66667H11.3333C11.7015 2.66667 12 2.96515 12 3.33333V6.1138C12 6.3302 11.8949 6.53313 11.7183 6.65813C11.5415 6.78307 11.3152 6.81447 11.1112 6.74233C10.973 6.69353 10.8237 6.66667 10.6667 6.66667C9.93027 6.66667 9.33333 7.2636 9.33333 8C9.33333 8.7364 9.93027 9.33333 10.6667 9.33333C10.8237 9.33333 10.973 9.30647 11.1112 9.25767C11.3152 9.18553 11.5415 9.21693 11.7183 9.34187C11.8949 9.46687 12 9.6698 12 9.8862V12.6667C12 13.0349 11.7015 13.3333 11.3333 13.3333H0.666667C0.29848 13.3333 0 13.0349 0 12.6667V3.33333C0 2.96515 0.29848 2.66667 0.666667 2.66667H2.66667ZM5.33333 1.33333C4.59695 1.33333 4 1.93029 4 2.66667C4 2.82369 4.02687 2.97301 4.0757 3.11117C4.14781 3.31521 4.11641 3.54157 3.99145 3.71826C3.86649 3.89495 3.66355 4 3.44714 4H1.33333V12H10.6667V10.6667C9.19393 10.6667 8 9.47273 8 8C8 6.52727 9.19393 5.33333 10.6667 5.33333V4H7.21953C7.00313 4 6.8002 3.89495 6.6752 3.71826C6.55027 3.54157 6.51887 3.31521 6.591 3.11117C6.6398 2.97301 6.66667 2.8237 6.66667 2.66667C6.66667 1.93029 6.06973 1.33333 5.33333 1.33333Z" fill="var(--fill-0, #495464)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="13.3333" height="13.3333" viewBox="0 0 13.3333 13.3333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6709 1.83301C12.754 1.83301 12.833 1.90708 12.833 2.00195V9.125L9.125 12.8311L2.00098 12.832C1.90424 12.8318 1.83301 12.7562 1.83301 12.6709V7.16699H2.16699V12.5H8.5V8.66699C8.5 8.57647 8.57647 8.5 8.66699 8.5L12 8.49902H12.5V2.16699H7.16699V1.83301H12.6709ZM11.4473 8.83301H8.83301V12.6533L9.68652 11.7998L11.8008 9.68652L12.6553 8.83203L11.4473 8.83301ZM2.83301 0.5V2.5H4.83301V2.83301H2.83301V4.83301H2.5V2.83301H0.5V2.5H2.5V0.5H2.83301Z" stroke="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 602 B |
@ -0,0 +1,3 @@
|
||||
<svg width="11.6416" height="13.086" viewBox="0 0 11.6416 13.086" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0996 0.512695C10.1337 0.518171 10.156 0.524407 10.168 0.52832C10.2148 0.543681 10.2522 0.579318 10.2705 0.625C10.5581 1.34398 10.5694 1.95842 10.4502 2.44824L10.3955 2.67578L10.5342 2.86328C10.9293 3.39873 11.1416 4.03345 11.1416 4.76172C11.1416 5.94177 10.8789 6.76774 10.4385 7.34668C9.99983 7.92308 9.33952 8.3137 8.42773 8.53711L7.91602 8.66309L8.06738 9.16699C8.1351 9.39201 8.17383 9.65236 8.17383 9.94336L8.16895 11.2803C8.16815 11.4068 8.16748 11.5418 8.16602 11.75L8.16309 12.1553L8.55762 12.2422C8.62114 12.2562 8.67222 12.3062 8.68555 12.3721C8.7036 12.4623 8.64494 12.5503 8.55469 12.5684C8.30873 12.6174 8.13226 12.5563 8.02148 12.4668C7.90812 12.3752 7.83118 12.2287 7.83105 12.043C7.83105 11.9847 7.83103 11.8999 7.83203 11.748C7.83356 11.5396 7.83507 11.4046 7.83594 11.2783C7.83901 10.8059 7.84082 10.3843 7.84082 9.94336C7.84082 9.41961 7.70678 8.93648 7.38281 8.65723C7.27271 8.56225 7.32913 8.38146 7.47363 8.36523C8.5007 8.24979 9.36433 7.98413 9.96094 7.37695C10.5669 6.76005 10.8086 5.884 10.8086 4.76172C10.8086 4.00303 10.5554 3.35601 10.0693 2.82227C10.0264 2.77514 10.0144 2.70773 10.0381 2.64844C10.1874 2.27509 10.24 1.81114 10.126 1.28125L10.0137 0.759766L9.50098 0.905273L9.49414 0.907227C9.09648 1.01979 8.63458 1.25105 8.11035 1.60742C8.06963 1.63503 8.01887 1.64314 7.97168 1.62988C7.37871 1.46312 6.74527 1.37892 6.1084 1.37891C5.4715 1.37891 4.83803 1.46315 4.24512 1.62988C4.19802 1.64313 4.14702 1.63474 4.10645 1.60742C3.57968 1.25283 3.11619 1.02297 2.71777 0.910156L2.20703 0.765625L2.09277 1.28418C1.97674 1.813 2.02979 2.27614 2.17871 2.64844C2.20225 2.70762 2.19033 2.77513 2.14746 2.82227C1.66395 3.35317 1.4082 4.00895 1.4082 4.76172C1.40822 5.88234 1.64983 6.75778 2.25391 7.375C2.84913 7.98307 3.71022 8.25023 4.7334 8.36523C4.87762 8.38143 4.93358 8.56104 4.82422 8.65625C4.66044 8.79875 4.55302 9.0215 4.48828 9.20898C4.41614 9.41793 4.36621 9.67286 4.36621 9.94336V12.043C4.36598 12.3722 4.10732 12.6499 3.64648 12.5693C3.55582 12.5535 3.49488 12.4666 3.51074 12.376C3.52276 12.3083 3.57458 12.2564 3.63965 12.2422L4.0332 12.1562V10.5586L3.49902 10.5947C2.9627 10.6308 2.58298 10.5389 2.30859 10.3555C2.16409 10.2588 2.0263 10.1279 1.83984 9.90527C1.80759 9.86673 1.71954 9.75814 1.64258 9.66211C1.60519 9.61545 1.57219 9.5735 1.55176 9.54785C1.54666 9.54146 1.54226 9.53528 1.53906 9.53125L1.53711 9.5293C1.53789 9.53032 1.54048 9.53427 1.54395 9.53906C1.54454 9.53989 1.55212 9.55023 1.56055 9.56348C1.56664 9.57332 1.58653 9.60864 1.59863 9.63477C1.62166 9.72473 1.52906 9.94237 1.35645 10.1016L1.14648 9.84082L1.53613 9.52734C1.53304 9.52351 1.52837 9.51827 1.52539 9.51465C1.52488 9.51403 1.52438 9.51285 1.52344 9.51172C1.52279 9.51094 1.51991 9.50772 1.5166 9.50391C1.51604 9.50326 1.51446 9.50199 1.5127 9.5C1.21539 9.13323 0.948869 8.85872 0.610352 8.7373C0.523946 8.70626 0.479023 8.61085 0.509766 8.52441C0.540776 8.43794 0.636154 8.39219 0.722656 8.42285C1.0883 8.55402 1.35448 8.77291 1.7793 9.29785C1.78078 9.29986 1.78248 9.30178 1.7832 9.30273C1.78551 9.30578 1.78769 9.30808 1.78809 9.30859C1.79204 9.31367 1.79973 9.32481 1.80859 9.33594C1.82821 9.36058 1.8612 9.401 1.89746 9.44629L2.0957 9.69141C2.2267 9.84782 2.35754 9.98732 2.49316 10.0781C2.78339 10.2725 3.19313 10.2925 3.58887 10.2529L4.01172 10.2109L4.03809 9.78711C4.05126 9.57333 4.09056 9.36582 4.15039 9.17578L4.31055 8.66699L3.79199 8.54004C2.88088 8.31734 2.21997 7.92594 1.78027 7.34863C1.33864 6.76857 1.07521 5.94109 1.0752 4.76172C1.0752 4.0396 1.28837 3.39917 1.68262 2.86328L1.82129 2.67578L1.76562 2.44824C1.64647 1.95845 1.65776 1.34391 1.94531 0.625C1.96354 0.57943 2.00137 0.544774 2.04785 0.529297C2.07893 0.520398 2.08893 0.517862 2.11621 0.513672C2.47591 0.45859 3.10496 0.581722 4.05176 1.1748L4.22852 1.28516L4.43164 1.2373C4.97053 1.11008 5.53743 1.04492 6.1084 1.04492C6.67867 1.04494 7.24479 1.11034 7.7832 1.2373L7.9873 1.28516L8.16504 1.17285C9.10977 0.576084 9.73863 0.454651 10.0996 0.512695ZM2.39551 9.2666C2.28256 9.36997 2.13716 9.45037 1.96484 9.45117C1.91043 9.42066 1.8462 9.37172 1.83301 9.35937C1.82701 9.35346 1.81779 9.34341 1.81445 9.33984C1.81233 9.33754 1.80911 9.3337 1.80762 9.33203C1.80466 9.3287 1.80177 9.32635 1.80078 9.3252L1.79687 9.32031L1.80078 9.32422L2.19043 9.01172C2.22028 9.0493 2.31937 9.17218 2.39551 9.2666Z" stroke="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="12" height="13.3333" viewBox="0 0 12 13.3333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.332 0.5C11.426 0.500094 11.5 0.580915 11.5 0.661133V12.6719C11.5 12.7601 11.4282 12.8329 11.3379 12.833H0.662109C0.578569 12.8329 0.5 12.7632 0.5 12.6621V4.20703L4.20898 0.5H11.332ZM4.83301 4.66699C4.83283 4.75878 4.75878 4.83283 4.66699 4.83301H0.833008V12.5H11.167V0.833008H4.83301V4.66699ZM3.64648 1.5332L1.53223 3.64648L0.678711 4.5H4.5V0.680664L3.64648 1.5332Z" stroke="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 517 B |
@ -0,0 +1,3 @@
|
||||
<svg width="14.6667" height="13.3333" viewBox="0 0 14.6667 13.3333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.13867 6.72949C8.67785 7.21017 8.03241 7.5 7.33301 7.5C6.63364 7.49991 5.9881 7.21021 5.52734 6.72949L5.16699 6.35254L4.80566 6.72949C4.34483 7.21027 3.69949 7.5 3 7.5C2.90569 7.5 2.81278 7.49447 2.72168 7.48438L2.16699 7.42285V12.5H12.5V7.42285L11.9453 7.48438C11.8543 7.49446 11.7612 7.49999 11.667 7.5C10.9676 7.5 10.3221 7.21017 9.86133 6.72949L9.5 6.35254L9.13867 6.72949ZM2.75977 1.08301L1.14258 3.88379C0.941179 4.21836 0.833008 4.60162 0.833008 5C0.833008 6.19661 1.80338 7.16699 3 7.16699C3.89354 7.16699 4.68499 6.62068 5.01172 5.80566C5.06765 5.66613 5.26532 5.66617 5.32129 5.80566C5.64794 6.62063 6.43956 7.16686 7.33301 7.16699C8.22659 7.16699 9.019 6.62072 9.3457 5.80566C9.4017 5.66633 9.5983 5.66633 9.6543 5.80566C9.981 6.62072 10.7734 7.16699 11.667 7.16699C12.8635 7.16682 13.833 6.1965 13.833 5C13.833 4.59984 13.725 4.21734 13.5186 3.87402H13.5176L11.9072 1.08301L11.7627 0.833008H2.90332L2.75977 1.08301ZM1.83301 7.22754L1.61133 7.0791C0.94019 6.62981 0.5 5.86624 0.5 5C0.5 4.53874 0.625339 4.09665 0.850586 3.72266L0.855469 3.71484L2.66309 0.583008C2.69288 0.531554 2.74815 0.5 2.80762 0.5H11.8594C11.9188 0.500086 11.9742 0.531575 12.0039 0.583008L13.8057 3.7041L13.8096 3.71191C14.0416 4.09734 14.167 4.53978 14.167 5C14.167 5.86614 13.7267 6.62979 13.0557 7.0791L12.833 7.22754V12.5H13.5V12.833H1.16699V12.5H1.83301V7.22754Z" stroke="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,6 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 13.4445 14.6667" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Vector">
|
||||
<path d="M7.873 1.01976C8.28503 1.01976 8.68078 1.18055 8.97605 1.46791C9.12039 1.60841 9.23516 1.77638 9.31357 1.96193C9.39199 2.14747 9.43248 2.34683 9.43265 2.54827C9.43282 2.7497 9.39267 2.94913 9.31457 3.13481C9.23647 3.32048 9.12199 3.48865 8.97788 3.62939L4.53331 7.98841C4.48512 8.03528 4.44681 8.09133 4.42065 8.15326C4.39449 8.21519 4.38102 8.28173 4.38102 8.34896C4.38102 8.41619 4.39449 8.48273 4.42065 8.54466C4.44681 8.60659 4.48512 8.66264 4.53331 8.70951C4.63178 8.80538 4.76378 8.85902 4.9012 8.85902C5.03862 8.85902 5.17062 8.80538 5.26908 8.70951L5.32897 8.65024L5.33019 8.64901L9.71304 4.3505C10.0085 4.06393 10.404 3.90381 10.8155 3.90415C11.2271 3.90449 11.6224 4.06527 11.9173 4.35233L11.9479 4.38228C12.0924 4.52293 12.2072 4.69111 12.2856 4.87689C12.3641 5.06267 12.4045 5.26228 12.4045 5.46393C12.4045 5.66559 12.3641 5.8652 12.2856 6.05098C12.2072 6.23676 12.0924 6.40494 11.9479 6.54559L6.62757 11.7632C6.51503 11.8726 6.42557 12.0034 6.36448 12.1479C6.30339 12.2925 6.27191 12.4478 6.27191 12.6047C6.27191 12.7616 6.30339 12.9169 6.36448 13.0615C6.42557 13.206 6.51503 13.3368 6.62757 13.4462L7.72023 14.5175C7.81867 14.6131 7.95053 14.6667 8.08781 14.6667C8.22508 14.6667 8.35695 14.6131 8.45539 14.5175C8.50358 14.4706 8.54189 14.4145 8.56805 14.3526C8.59421 14.2907 8.60769 14.2241 8.60769 14.1569C8.60769 14.0897 8.59421 14.0231 8.56805 13.9612C8.54189 13.8993 8.50358 13.8432 8.45539 13.7964L7.36273 12.7245C7.34667 12.7089 7.33391 12.6902 7.32519 12.6696C7.31647 12.649 7.31198 12.6268 7.31198 12.6044C7.31198 12.582 7.31647 12.5598 7.32519 12.5392C7.33391 12.5186 7.34667 12.4999 7.36273 12.4843L12.683 7.2673C12.924 7.03296 13.1155 6.75268 13.2463 6.44304C13.3771 6.1334 13.4445 5.80068 13.4445 5.46454C13.4445 5.12841 13.3771 4.79569 13.2463 4.48605C13.1155 4.17641 12.924 3.89613 12.683 3.66178L12.6525 3.63123C12.3646 3.35023 12.016 3.13908 11.6336 3.01405C11.2512 2.88903 10.8453 2.85347 10.447 2.91012C10.5038 2.51705 10.4668 2.1161 10.3389 1.74009C10.211 1.36408 9.99593 1.0237 9.71121 0.746808C9.21914 0.267942 8.55962 0 7.873 0C7.18639 0 6.52687 0.267942 6.0348 0.746808L0.152297 6.51564C0.104103 6.56251 0.0657945 6.61857 0.039636 6.6805C0.0134776 6.74243 0 6.80897 0 6.8762C0 6.94342 0.0134776 7.00997 0.039636 7.0719C0.0657945 7.13382 0.104103 7.18988 0.152297 7.23675C0.250735 7.33243 0.382601 7.38595 0.519877 7.38595C0.657153 7.38595 0.789019 7.33243 0.887457 7.23675L6.76996 1.46791C7.06523 1.18055 7.46098 1.01976 7.873 1.01976Z" fill="var(--fill-0, #495464)"/>
|
||||
<path d="M8.35362 2.74526C8.32747 2.80719 8.28916 2.86324 8.24096 2.91011L3.88989 7.17685C3.74539 7.3175 3.63053 7.48568 3.5521 7.67146C3.47367 7.85724 3.43327 8.05685 3.43327 8.25851C3.43327 8.46016 3.47367 8.65977 3.5521 8.84555C3.63053 9.03133 3.74539 9.19951 3.88989 9.34017C4.18516 9.62753 4.58092 9.78832 4.99294 9.78832C5.40496 9.78832 5.80072 9.62753 6.09598 9.34017L10.4464 5.07343C10.5449 4.97756 10.6769 4.92392 10.8143 4.92392C10.9518 4.92392 11.0837 4.97756 11.1822 5.07343C11.2304 5.1203 11.2687 5.17635 11.2949 5.23828C11.321 5.30021 11.3345 5.36675 11.3345 5.43398C11.3345 5.5012 11.321 5.56775 11.2949 5.62968C11.2687 5.69161 11.2304 5.74766 11.1822 5.79453L6.83114 10.0613C6.339 10.54 5.67951 10.8078 4.99294 10.8078C4.30636 10.8078 3.64688 10.54 3.15473 10.0613C2.91376 9.82692 2.72222 9.54664 2.59143 9.237C2.46064 8.92736 2.39325 8.59464 2.39325 8.25851C2.39325 7.92238 2.46064 7.58966 2.59143 7.28001C2.72222 6.97037 2.91376 6.69009 3.15473 6.45575L7.50519 2.18901C7.60366 2.09315 7.73566 2.03951 7.87308 2.03951C8.0105 2.03951 8.1425 2.09315 8.24096 2.18901C8.28916 2.23588 8.32747 2.29193 8.35362 2.35386C8.37978 2.41579 8.39326 2.48233 8.39326 2.54956C8.39326 2.61679 8.37978 2.68333 8.35362 2.74526Z" fill="var(--fill-0, #495464)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 14.5 14.5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M5.08333 0.75V13.75M2.19444 0.75H12.3056C13.1033 0.75 13.75 1.3967 13.75 2.19444V12.3056C13.75 13.1033 13.1033 13.75 12.3056 13.75H2.19444C1.3967 13.75 0.75 13.1033 0.75 12.3056V2.19444C0.75 1.3967 1.3967 0.75 2.19444 0.75Z" stroke="var(--stroke-0, black)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 527 B |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 12.3333 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M9.66667 4V0H11.6667C12.0349 0 12.3333 0.29848 12.3333 0.666667V3.33333C12.3333 3.70152 12.0349 4 11.6667 4H9.66667ZM8.33333 13.3333C8.33333 13.7015 8.03487 14 7.66667 14H5C4.63181 14 4.33333 13.7015 4.33333 13.3333V4H0V2.71625C0 2.47913 0.12594 2.25987 0.330753 2.14039L4 0H8.33333V13.3333Z" fill="var(--fill-0, #18222F)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 528 B |
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 12.3333 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M11.6667 0C12.0349 0 12.3333 0.29848 12.3333 0.666667V4C12.3333 4.36819 12.0349 4.66667 11.6667 4.66667H8.33333V13.3333C8.33333 13.7015 8.03487 14 7.66667 14H5C4.63181 14 4.33333 13.7015 4.33333 13.3333V4.66667H0.666667C0.29848 4.66667 0 4.36819 0 4V2.41202C0 2.15951 0.142667 1.92867 0.368527 1.81574L4 0H11.6667ZM8.33333 1.33333H4.31476L1.33333 2.82405V3.33333H5.66667V12.6667H7V3.33333H8.33333V1.33333ZM11 1.33333H9.66667V3.33333H11V1.33333Z" fill="var(--fill-0, #495464)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 681 B |
@ -0,0 +1,10 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 13.325 13.325" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Vector">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.81641 5.01888L5.91797 5.04883L12.8913 7.70573L12.9837 7.7487C13.3889 7.97463 13.4445 8.54581 13.0905 8.8457L13.0085 8.9056L10.4837 10.4837L8.9056 13.0085C8.62921 13.4507 7.99075 13.4178 7.7487 12.9837L7.70573 12.8913L5.04883 5.91797C4.85479 5.40863 5.31088 4.90871 5.81641 5.01888Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M3.87891 9.06445L2.22917 10.7142L1.28646 9.77148L2.9362 8.12175L3.87891 9.06445Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M2.33333 6.66667H0V5.33333H2.33333V6.66667Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M3.87891 2.93555L2.9362 3.87826L1.28646 2.22852L2.22917 1.28581L3.87891 2.93555Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M10.7142 2.22852L9.06445 3.87826L8.12175 2.93555L9.77148 1.28581L10.7142 2.22852Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M6.66667 2.33333H5.33333V0H6.66667V2.33333Z" fill="var(--fill-0, #18222F)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,10 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 13.325 13.325" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Vector">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.81641 5.01888L5.91797 5.04883L12.8913 7.70573L12.9837 7.7487C13.3889 7.97463 13.4445 8.54581 13.0905 8.8457L13.0085 8.9056L10.4837 10.4837L8.9056 13.0085C8.62921 13.4507 7.99075 13.4178 7.7487 12.9837L7.70573 12.8913L5.04883 5.91797C4.85479 5.40863 5.31088 4.90871 5.81641 5.01888ZM8.47852 11.1751L9.43359 9.64779L9.47786 9.58529C9.52536 9.52564 9.5828 9.47422 9.64779 9.43359L11.1751 8.47852L6.81901 6.81901L8.47852 11.1751Z" fill="var(--fill-0, #495464)"/>
|
||||
<path d="M3.87891 9.06445L2.22917 10.7142L1.28646 9.77148L2.9362 8.12175L3.87891 9.06445Z" fill="var(--fill-0, #495464)"/>
|
||||
<path d="M2.33333 6.66667H0V5.33333H2.33333V6.66667Z" fill="var(--fill-0, #495464)"/>
|
||||
<path d="M3.87891 2.93555L2.9362 3.87826L1.28646 2.22852L2.22917 1.28581L3.87891 2.93555Z" fill="var(--fill-0, #495464)"/>
|
||||
<path d="M10.7142 2.22852L9.06445 3.87826L8.12175 2.93555L9.77148 1.28581L10.7142 2.22852Z" fill="var(--fill-0, #495464)"/>
|
||||
<path d="M6.66667 2.33333H5.33333V0H6.66667V2.33333Z" fill="var(--fill-0, #495464)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,9 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 12.1 11.4333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Vector">
|
||||
<path d="M1.71667 0C0.768578 0 0 0.768578 0 1.71667C0 2.66476 0.768578 3.43333 1.71667 3.43333H3.71667C4.66475 3.43333 5.43333 2.66476 5.43333 1.71667C5.43333 0.768578 4.66476 0 3.71667 0H1.71667Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M8.38333 4C7.43524 4 6.66667 4.76858 6.66667 5.71667C6.66667 6.66476 7.43524 7.43333 8.38333 7.43333H10.3833C11.3314 7.43333 12.1 6.66476 12.1 5.71667C12.1 4.76858 11.3314 4 10.3833 4H8.38333Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M0 9.71667C0 8.76858 0.768578 8 1.71667 8H3.71667C4.66476 8 5.43333 8.76858 5.43333 9.71667C5.43333 10.6648 4.66475 11.4333 3.71667 11.4333H1.71667C0.768578 11.4333 0 10.6648 0 9.71667Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M7.05001 2.38334H6.38334V1.05001H7.05001C8.15458 1.05001 9.05001 1.94544 9.05001 3.05001H7.71667C7.71667 2.68182 7.4182 2.38334 7.05001 2.38334Z" fill="var(--fill-0, #18222F)"/>
|
||||
<path d="M9.05001 8.38334C9.05001 9.48791 8.15458 10.3833 7.05001 10.3833H6.38334V9.05001H7.05001C7.4182 9.05001 7.71667 8.75153 7.71667 8.38334H9.05001Z" fill="var(--fill-0, #18222F)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3333 2C13.7015 2 14 2.29848 14 2.66667V6C14 6.36819 13.7015 6.66667 13.3333 6.66667H10C9.63181 6.66667 9.33333 6.36819 9.33333 6V5H6.63086C5.94959 5.00026 5.70734 5.90184 6.29688 6.24349L10.3717 8.60221C12.1411 9.62681 11.4138 12.3331 9.36914 12.3333H6.66667V13.3333C6.66667 13.7015 6.36819 14 6 14H2.66667C2.29848 14 2 13.7015 2 13.3333V10C2 9.63181 2.29848 9.33333 2.66667 9.33333H6C6.36819 9.33333 6.66667 9.63181 6.66667 10V11H9.36914C10.0504 10.9997 10.2929 10.0981 9.70378 9.75651L5.62891 7.39779C3.85933 6.37322 4.58611 3.66693 6.63086 3.66667H9.33333V2.66667C9.33333 2.29848 9.63181 2 10 2H13.3333ZM3.33333 12.6667H5.33333V10.6667H3.33333V12.6667ZM10.6667 5.33333H12.6667V3.33333H10.6667V5.33333Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 861 B |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.66667 6.17435C2.66667 5.98785 2.66667 5.89459 2.69021 5.80837C2.71107 5.73198 2.74537 5.65992 2.7915 5.59556C2.84357 5.52292 2.91595 5.46411 3.0607 5.3465L7.3274 1.87983C7.56713 1.68502 7.687 1.58762 7.82027 1.55031C7.93787 1.51741 8.06213 1.51741 8.17973 1.55031C8.313 1.58762 8.43287 1.68502 8.6726 1.87983L12.9393 5.3465C13.0841 5.46411 13.1564 5.52292 13.2085 5.59556C13.2547 5.65992 13.2889 5.73198 13.3098 5.80837C13.3333 5.89459 13.3333 5.98785 13.3333 6.17435V12.2667C13.3333 12.64 13.3333 12.8267 13.2607 12.9693C13.1967 13.0947 13.0948 13.1967 12.9693 13.2607C12.8267 13.3333 12.6401 13.3333 12.2667 13.3333H3.73333C3.35997 13.3333 3.17328 13.3333 3.03067 13.2607C2.90523 13.1967 2.80325 13.0947 2.73933 12.9693C2.66667 12.8267 2.66667 12.64 2.66667 12.2667V6.17435Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M10 13.3333V9.66667C10 9.11438 9.55228 8.66667 9 8.66667H7C6.44772 8.66667 6 9.11438 6 9.66667V13.3333" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(3 3) scale(1.5)">
|
||||
<path d="M7 10.5C8.933 10.5 10.5 8.4853 10.5 6C10.5 3.51472 8.933 1.5 7 1.5M7 10.5C5.067 10.5 3.5 8.4853 3.5 6C3.5 3.51472 5.067 1.5 7 1.5M7 10.5H5C3.06701 10.5 1.5 8.4853 1.5 6C1.5 3.51472 3.06701 1.5 5 1.5H7" stroke="currentColor" stroke-width="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 410 B |
@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 15.25C12.4142 15.25 12.75 15.5858 12.75 16V16.0098C12.75 16.424 12.4142 16.7598 12 16.7598C11.5858 16.7598 11.25 16.424 11.25 16.0098V16C11.25 15.5858 11.5858 15.25 12 15.25Z" fill="currentColor"/>
|
||||
<path d="M14 7.25C14.4142 7.25 14.75 7.58579 14.75 8V10.5C14.75 10.7359 14.6389 10.958 14.4502 11.0996L12.75 12.374V13C12.75 13.4142 12.4142 13.75 12 13.75C11.5858 13.75 11.25 13.4142 11.25 13V12C11.25 11.7641 11.3611 11.542 11.5498 11.4004L13.25 10.125V8.75H10.75V9C10.75 9.41421 10.4142 9.75 10 9.75C9.58579 9.75 9.25 9.41421 9.25 9V8C9.25 7.58579 9.58579 7.25 10 7.25H14Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 716 B |
@ -0,0 +1,6 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-5.5 -6.95)">
|
||||
<path d="M14.449 8.37314C15.0613 7.87562 15.9387 7.87562 16.551 8.37314L22.3843 13.1127C22.7738 13.4292 23 13.9044 23 14.4063V22.7596C23 23.6801 22.2538 24.4263 21.3333 24.4263H18.8333V17.7599C18.8333 17.2997 18.4602 16.9266 18 16.9266H13C12.5398 16.9266 12.1667 17.2997 12.1667 17.7599V24.4263H9.66667C8.74619 24.4263 8 23.6801 8 22.7596V14.4063C8 13.9044 8.22616 13.4292 8.61568 13.1127L14.449 8.37314Z" fill="currentColor"/>
|
||||
<path d="M13.833 24.4263H17.1663V18.5933H13.833V24.4263Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 666 B |
@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.33301 7.71788C3.33301 7.48475 3.33301 7.36818 3.36243 7.2604C3.38851 7.16492 3.43138 7.07484 3.48905 6.99439C3.55414 6.90359 3.64461 6.83008 3.82555 6.68307L9.15892 2.34973C9.45859 2.10622 9.60842 1.98447 9.77501 1.93783C9.92201 1.8967 10.0773 1.8967 10.2243 1.93783C10.3909 1.98447 10.5408 2.10622 10.8404 2.34973L16.1738 6.68307C16.3548 6.83008 16.4452 6.90359 16.5103 6.99439C16.568 7.07484 16.6108 7.16492 16.6369 7.2604C16.6663 7.36818 16.6663 7.48475 16.6663 7.71788V15.3333C16.6663 15.7999 16.6663 16.0334 16.5755 16.2116C16.4956 16.3684 16.3682 16.4959 16.2113 16.5758C16.0331 16.6666 15.7998 16.6666 15.333 16.6666H4.66634C4.19963 16.6666 3.96627 16.6666 3.78802 16.5758C3.63122 16.4959 3.50373 16.3684 3.42383 16.2116C3.33301 16.0334 3.33301 15.7999 3.33301 15.3333V7.71788Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M12.5 16.6666V11.8333C12.5 11.281 12.0523 10.8333 11.5 10.8333H8.5C7.94772 10.8333 7.5 11.281 7.5 11.8333V16.6666" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,7 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-6.33 -5.55)">
|
||||
<path d="M9.66602 8.83333C9.66602 8.3731 10.0391 8 10.4993 8H14.666C15.1263 8 15.4993 8.3731 15.4993 8.83333V10.5H9.66602V8.83333Z" fill="currentColor"/>
|
||||
<path d="M17.166 8.83333C17.166 8.3731 17.5391 8 17.9993 8H22.166C22.6263 8 22.9993 8.3731 22.9993 8.83333V10.5H17.166V8.83333Z" fill="currentColor"/>
|
||||
<path d="M8 13.0001C8 12.5398 8.3731 12.1667 8.83333 12.1667H23.8333C24.2936 12.1667 24.6667 12.5398 24.6667 13.0001V21.3334C24.6667 21.7937 24.2936 22.1667 23.8333 22.1667H8.83333C8.3731 22.1667 8 21.7937 8 21.3334V13.0001Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 716 B |
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 7.50008C2.5 7.03984 2.8731 6.66675 3.33333 6.66675H16.6667C17.1269 6.66675 17.5 7.03985 17.5 7.50008V15.0001C17.5 15.4603 17.1269 15.8334 16.6667 15.8334H3.33333C2.8731 15.8334 2.5 15.4603 2.5 15.0001V7.50008Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.16699 6.66659V4.58325C4.16699 3.89289 4.72663 3.33325 5.41699 3.33325H7.08366C7.77402 3.33325 8.33366 3.89289 8.33366 4.58325V6.66659" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.667 6.66659V4.16659C11.667 3.70635 12.0401 3.33325 12.5003 3.33325H15.0003C15.4606 3.33325 15.8337 3.70635 15.8337 4.16659V6.66659" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 896 B |
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-4.667 -6.333)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5 8C9.11929 8 8 9.11929 8 10.5V22.1667C8 23.5474 9.11929 24.6667 10.5 24.6667H20.5C20.9602 24.6667 21.3333 24.2936 21.3333 23.8333V8.83333C21.3333 8.3731 20.9602 8 20.5 8H10.5ZM9.66667 22.1667C9.66667 22.6269 10.0398 23 10.5 23H19.6667V21.3333H10.5C10.0398 21.3333 9.66667 21.7064 9.66667 22.1667ZM12.1667 11.3333C11.7064 11.3333 11.3333 11.7064 11.3333 12.1667C11.3333 12.6269 11.7064 13 12.1667 13H17.1667C17.6269 13 18 12.6269 18 12.1667C18 11.7064 17.6269 11.3333 17.1667 11.3333H12.1667ZM11.3333 15.5C11.3333 15.0397 11.7064 14.6667 12.1667 14.6667H14.6667C15.1269 14.6667 15.5 15.0397 15.5 15.5C15.5 15.9602 15.1269 16.3333 14.6667 16.3333H12.1667C11.7064 16.3333 11.3333 15.9602 11.3333 15.5Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 933 B |
@ -0,0 +1,6 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 9.16675H10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.5 5.83325H12.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.16699 4.16667C4.16699 3.24619 4.91318 2.5 5.83366 2.5H15.417C15.6471 2.5 15.8337 2.68655 15.8337 2.91667V17.5H5.83366C4.91318 17.5 4.16699 16.7538 4.16699 15.8333V4.16667Z" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M4.16699 15.8334C4.16699 14.9129 4.91318 14.1667 5.83366 14.1667H15.8337V17.5001H5.83366C4.91318 17.5001 4.16699 16.7539 4.16699 15.8334Z" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 764 B |
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-6.3 -5.5)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.25369 8.58477C9.3624 8.23688 9.6846 8 10.0491 8H22.5491C22.9136 8 23.2357 8.23688 23.3445 8.58477L24.4481 12.1162C24.8042 13.256 24.5023 14.4059 23.7991 15.2164V22.1667C23.7991 22.6269 23.426 23 22.9657 23H9.63242C9.17219 23 8.79909 22.6269 8.79909 22.1667V15.2164C8.09588 14.4059 7.79393 13.256 8.15013 12.1162L9.25369 8.58477ZM18.0271 12.7092L17.6467 9.66667H14.9514L14.5711 12.7092C14.4412 13.7486 15.2516 14.6667 16.2991 14.6667C17.3465 14.6667 18.1568 13.7485 18.0271 12.7092ZM13.2718 9.66667H10.6617L9.74093 12.6133C9.42266 13.6317 10.1835 14.6667 11.2505 14.6667C12.0482 14.6667 12.721 14.0728 12.82 13.2812L13.2718 9.66667ZM19.3264 9.66667L19.6809 12.5025L19.7782 13.2812C19.8772 14.0728 20.55 14.6667 21.3477 14.6667C22.4147 14.6667 23.1755 13.6317 22.8572 12.6133L21.9364 9.66667H19.3264Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.667 9.99992V16.6666H3.33366V9.99992M7.91699 3.33325H12.0837M7.91699 3.33325L7.44543 7.10578C7.25334 8.6425 8.45158 9.99992 10.0003 9.99992C11.5491 9.99992 12.7473 8.6425 12.5552 7.10578L12.0837 3.33325M7.91699 3.33325H3.75033L2.64677 6.86465C2.16081 8.41975 3.32257 9.99992 4.95179 9.99992C6.1697 9.99992 7.19703 9.093 7.34809 7.88451L7.91699 3.33325ZM12.0837 3.33325H16.2503L17.3539 6.86465C17.8398 8.41975 16.6781 9.99992 15.0489 9.99992C13.831 9.99992 12.8037 9.093 12.6526 7.88451L12.0837 3.33325Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 712 B |
@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.0004 1.875C17.0398 1.87509 21.1246 5.9602 21.1249 10.9995C21.1249 13.1138 20.4037 15.0582 19.1972 16.6055L21.7958 19.2041C22.235 19.6433 22.2348 20.3556 21.7958 20.7949C21.3565 21.2343 20.6443 21.2343 20.205 20.7949L17.6064 18.1963C16.3417 19.1831 14.8123 19.8466 13.14 20.0552C12.5235 20.132 11.9616 19.6932 11.8847 19.0767C11.8081 18.4603 12.2454 17.8981 12.8617 17.8213C16.2516 17.3983 18.8749 14.5044 18.8749 10.9995C18.8746 7.20283 15.7971 4.12509 12.0004 4.125C8.4954 4.12505 5.60139 6.74948 5.17862 10.1396C5.10154 10.7559 4.53955 11.1934 3.92325 11.1167C3.30688 11.0398 2.86963 10.4777 2.9462 9.86133C3.50765 5.35896 7.34631 1.87505 12.0004 1.875Z" fill="currentColor"/>
|
||||
<path d="M3.70727 16.1747L7.91781 11.2624C8.24038 10.8861 8.85505 11.158 8.79357 11.6498L8.49979 14.0001H10.9127C11.3399 14.0001 11.5703 14.5012 11.2923 14.8255L7.08177 19.7378C6.7592 20.1141 6.14453 19.8422 6.20601 19.3504L6.49979 17.0001H4.0869C3.65972 17.0001 3.42927 16.499 3.70727 16.1747Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,8 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-7.14 -6.38)">
|
||||
<path d="M13.7969 17.1665C14.465 17.1665 15.1067 17.2803 15.7045 17.4872L14.7247 20.9165H12.9636C11.8131 20.9167 10.8803 21.8493 10.8803 22.9998C10.8803 23.2963 10.9436 23.5778 11.0552 23.8332H8.79695C8.33682 23.833 7.95953 23.4587 8.00349 23.0007C8.30296 19.8813 10.2937 17.1666 13.7969 17.1665Z" fill="currentColor"/>
|
||||
<path d="M25.4632 16.3333C25.7246 16.3333 25.9715 16.4558 26.1289 16.6645C26.2668 16.8473 26.3224 17.0777 26.286 17.3008L26.2648 17.3953L24.5981 23.2286C24.4959 23.5863 24.1686 23.8333 23.7965 23.8333H12.9632C12.5031 23.8331 12.1299 23.4601 12.1299 22.9999C12.1299 22.5398 12.5031 22.1668 12.9632 22.1666H15.6675L17.1616 16.9379L17.2105 16.8093C17.3465 16.5223 17.6376 16.3333 17.9632 16.3333H25.4632Z" fill="currentColor"/>
|
||||
<path d="M14.2132 9.25C16.0541 9.25 17.5465 10.7424 17.5465 12.5833C17.5465 14.4243 16.0541 15.9167 14.2132 15.9167C12.3724 15.9165 10.8799 14.4242 10.8799 12.5833C10.8799 10.7425 12.3724 9.25013 14.2132 9.25Z" fill="currentColor"/>
|
||||
<path d="M22.5474 8C22.7539 8.00029 22.9276 8.15533 22.951 8.36052C23.0941 9.62402 23.8103 10.3975 25.093 10.5114C25.3029 10.53 25.4635 10.7067 25.4632 10.9175C25.4628 11.128 25.3019 11.3037 25.0921 11.3219C23.8276 11.4314 23.0613 12.1977 22.9518 13.4622C22.9335 13.672 22.758 13.833 22.5474 13.8333C22.3367 13.8336 22.1609 13.6728 22.1421 13.4631C22.0281 12.1805 21.2546 11.4635 19.9912 11.3203C19.786 11.2971 19.6303 11.124 19.6299 10.9175C19.6297 10.7108 19.7851 10.5367 19.9904 10.513C21.2719 10.3651 21.9951 9.64128 22.1429 8.3597C22.1667 8.15453 22.3408 7.99979 22.5474 8Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,6 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.8206 2.0275C15.7973 1.82217 15.6238 1.66696 15.4171 1.66675C15.2104 1.66654 15.0365 1.82139 15.0128 2.02667C14.865 3.30836 14.1416 4.03176 12.8599 4.17959C12.6547 4.20326 12.4998 4.37719 12.5 4.58383C12.5003 4.79047 12.6554 4.96408 12.8608 4.98733C14.1243 5.13046 14.8978 5.84689 15.0117 7.12955C15.0304 7.33946 15.2064 7.50032 15.4171 7.50008C15.6278 7.49984 15.8035 7.33859 15.8217 7.12863C15.9311 5.86411 16.6973 5.09787 17.9619 4.98841C18.1718 4.97023 18.3331 4.79461 18.3333 4.58387C18.3336 4.37313 18.1728 4.19715 17.9628 4.17851C16.6802 4.06457 15.9637 3.29101 15.8206 2.0275Z" fill="currentColor"/>
|
||||
<path d="M7.29167 9.16659C8.9025 9.16659 10.2083 7.86075 10.2083 6.24992C10.2083 4.63909 8.9025 3.33325 7.29167 3.33325C5.68084 3.33325 4.375 4.63909 4.375 6.24992C4.375 7.86075 5.68084 9.16659 7.29167 9.16659Z" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M1.66699 16.6667C1.66699 13.9053 3.90557 11.6667 6.66699 11.6667H7.08366" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.16634 16.6666L10.833 10.8333H18.333L16.6663 16.6666H9.16634ZM9.16634 16.6666H5.83301" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(2 2.333)">
|
||||
<path d="M1.33333 2.33333C1.33333 1.78105 1.78105 1.33333 2.33333 1.33333C2.88562 1.33333 3.33333 1.78105 3.33333 2.33333C3.33333 2.88562 2.88562 3.33333 2.33333 3.33333C1.78105 3.33333 1.33333 2.88562 1.33333 2.33333ZM2.33333 0C1.04467 0 0 1.04467 0 2.33333C0 3.622 1.04467 4.66667 2.33333 4.66667C3.622 4.66667 4.66667 3.622 4.66667 2.33333C4.66667 1.04467 3.622 0 2.33333 0ZM6 3H11.3333V1.66667H6V3ZM8.66667 9C8.66667 8.44773 9.1144 8 9.66667 8C10.2189 8 10.6667 8.44773 10.6667 9C10.6667 9.55227 10.2189 10 9.66667 10C9.1144 10 8.66667 9.55227 8.66667 9ZM9.66667 6.66667C8.378 6.66667 7.33333 7.71133 7.33333 9C7.33333 10.2887 8.378 11.3333 9.66667 11.3333C10.9553 11.3333 12 10.2887 12 9C12 7.71133 10.9553 6.66667 9.66667 6.66667ZM0.666667 8.33333V9.66667H6V8.33333H0.666667Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 956 B |