Compare commits

...

70 Commits

Author SHA1 Message Date
Junyan Qin
f68a28bb6a doc: add debugging steps in api/README.md 2025-07-22 22:32:59 +08:00
Joel
69b8854c6c fix: not from markpalceplace show auto update 2025-07-22 22:32:58 +08:00
Junyan Qin
930b883e76 chore: remove volumns field in docker-compose-template.yaml for worker_beat service 2025-07-22 22:32:58 +08:00
Junyan Qin
b79b79dd19 chore: remove volumn field in worker_beat service 2025-07-22 22:32:58 +08:00
Junyan Qin
ca20e77007 chore: english comments 2025-07-22 22:32:58 +08:00
Joel
bffa130a33 Update web/app/components/plugins/plugin-detail-panel/detail-header.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 22:32:58 +08:00
Joel
4491565a28 Update web/app/components/base/date-and-time-picker/utils/dayjs.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 22:32:57 +08:00
Joel
d6570a7b75 Update web/app/components/plugins/reference-setting-modal/auto-update-setting/utils.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 22:32:57 +08:00
Joel
b659bbdd86 Update web/app/components/plugins/reference-setting-modal/auto-update-setting/utils.spec.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 22:32:57 +08:00
Joel
e2a7a00218 chore: lint error 2025-07-22 22:32:57 +08:00
Joel
e3e7cc140d chore: add icon json \n 2025-07-22 22:32:57 +08:00
crazywoola
cf39411c9e chore: add icons 2025-07-22 22:32:56 +08:00
Joel
f1fbbadb8b chore: fix: picker always show 2025-07-22 22:32:56 +08:00
Junyan Qin
16fff2b88a feat: resp DISABLED for exists users 2025-07-22 22:32:56 +08:00
Junyan Qin
15950f33c2 feat: create default autoupgrade strategy on tenant creating 2025-07-22 22:32:56 +08:00
Junyan Qin
9302e862db chore: add worker_beat to docker compose template 2025-07-22 22:32:56 +08:00
Junyan Qin
74a31ba6b5 chore: add scheduler tasks switch in docker .env 2025-07-22 22:32:56 +08:00
Junyan Qin
87814a2ef6 chore: add scheduler tasks switch in docker .env 2025-07-22 22:32:56 +08:00
Junyan Qin
b6249c8b31 feat: add switch to config celery schedule tasks 2025-07-22 22:32:55 +08:00
Joel
7ffb30d312 feat: handle downgrade install 2025-07-22 22:32:55 +08:00
Joel
869dccbb8e chore: add auto update show config 2025-07-22 22:32:55 +08:00
Joel
15b8efbd14 feat: select box setting 2025-07-22 22:32:55 +08:00
Joel
bac24e8db7 feat: downgrade modal 2025-07-22 22:32:55 +08:00
Joel
a55355120d feat: auto update button 2025-07-22 22:32:55 +08:00
Junyan Qin
8c5a9920bf fix: ruff format 2025-07-22 22:32:54 +08:00
Junyan Qin
c68824d7e7 fix: type static check errors 2025-07-22 22:32:54 +08:00
Junyan Qin
3d379e0b7c fix: ruff format 2025-07-22 22:32:54 +08:00
Junyan Qin
2d2ec8af9f feat: add plugin queue to celery cmd in entrypoint.sh 2025-07-22 22:32:54 +08:00
Junyan Qin
e6facf2370 perf: split tasks to multi worker 2025-07-22 22:32:54 +08:00
Junyan Qin
d28b7b40a1 feat: exclude one plugin 2025-07-22 22:32:54 +08:00
Junyan Qin
bfb98e9fa4 feat: add supports for update_all strategy 2025-07-22 22:32:53 +08:00
Junyan Qin
35aea020cb feat: combine plugin preferences apis 2025-07-22 22:32:53 +08:00
Junyan Qin
1b66570a0d fix: bugs 2025-07-22 22:32:53 +08:00
Junyan Qin
d70ae2f6e5 feat(auto-upgrade): celery scheduled task 2025-07-22 22:32:53 +08:00
Junyan Qin
cbde1d5bfd feat: crud for auto upgrade strategy 2025-07-22 22:32:53 +08:00
RockChinQ
cc5b0d05b4 feat(auto upgrade): add upgrade setting 2025-07-22 22:32:53 +08:00
Joel
3583d7e2c5 feat: add time zone 2025-07-22 22:32:52 +08:00
Joel
c47d58e5b7 chore: peroid not auto scroll 2025-07-22 22:32:52 +08:00
Joel
8f89b53d1b chore: icon fixed 2025-07-22 22:32:52 +08:00
Joel
36f51cbb6f fix: utc time show 2025-07-22 22:32:52 +08:00
Joel
1577ac361d fix: not use local time 2025-07-22 22:32:51 +08:00
Joel
2a9626601a fix: fetch installed plugin instead of all plugins 2025-07-22 22:32:51 +08:00
Joel
888a583b4d feat: handle downgrade install 2025-07-22 22:32:51 +08:00
Joel
32bdd1caf5 fix: not the same as 2025-07-22 22:32:51 +08:00
Joel
9a7975d4a3 feat: exculde call api 2025-07-22 22:32:51 +08:00
Joel
385eb6daad feat: downgrade detect 2025-07-22 22:32:50 +08:00
Joel
e06ba64eaa chore: add auto update show config 2025-07-22 22:32:50 +08:00
Joel
2b17c62fd1 feat: config can save 2025-07-22 22:32:50 +08:00
Joel
41bc5c4791 feat: no data placeholder 2025-07-22 22:32:50 +08:00
Joel
95dfa654c3 feat: pluging loading 2025-07-22 22:32:50 +08:00
Joel
8ac9e048aa feat: api to refernce settings 2025-07-22 22:32:50 +08:00
Joel
85565d737a feat: can select plugins 2025-07-22 22:32:49 +08:00
Joel
3cb9149f47 feat: select box setting 2025-07-22 22:32:49 +08:00
Joel
dc6238297f feat: fetch plugin list 2025-07-22 22:32:49 +08:00
Joel
b415dedf49 feat: select tool template 2025-07-22 22:32:49 +08:00
Joel
45d1488d2b chore: ui and clear 2025-07-22 22:32:49 +08:00
Joel
cd1ac31aea chore: temp i18n 2025-07-22 22:32:49 +08:00
Joel
ede64d067e feat: downgrade modal i18n 2025-07-22 22:32:48 +08:00
Joel
5c801c2276 feat: downgrade modal 2025-07-22 22:32:48 +08:00
Joel
1d5a573505 feat: show downgrade warning logic 2025-07-22 22:32:48 +08:00
Joel
897a2333de feat: auto update button 2025-07-22 22:32:48 +08:00
Joel
9f650290b1 feat: show list and select 2025-07-22 22:32:27 +08:00
Joel
306ebe4148 feat: plugin no data 2025-07-22 22:32:26 +08:00
Joel
494ef1ee3f feat: only choose 15 time 2025-07-22 22:32:26 +08:00
Joel
9e78346cff feat: plugins picker holder 2025-07-22 22:32:26 +08:00
Joel
3b1baf46bb fat: auto update mode 2025-07-22 22:32:26 +08:00
Joel
fe97086e53 feat: choose time 2025-07-22 22:32:26 +08:00
Joel
6fbc7d9a5a feat: choose auto update description and i18n 2025-07-22 22:32:26 +08:00
Joel
b3c4b52fdd feat: auto update strategy picker 2025-07-22 22:32:25 +08:00
Joel
4d61324631 feat: type config 2025-07-22 22:32:24 +08:00
304 changed files with 2475 additions and 578 deletions

View File

@@ -471,6 +471,16 @@ APP_MAX_ACTIVE_REQUESTS=0
# Celery beat configuration
CELERY_BEAT_SCHEDULER_TIME=1
# Celery schedule tasks configuration
ENABLE_CLEAN_EMBEDDING_CACHE_TASK=false
ENABLE_CLEAN_UNUSED_DATASETS_TASK=false
ENABLE_CREATE_TIDB_SERVERLESS_TASK=false
ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK=false
ENABLE_CLEAN_MESSAGES=false
ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK=false
ENABLE_DATASETS_QUEUE_MONITOR=false
ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK=true
# Position configuration
POSITION_TOOL_PINS=
POSITION_TOOL_INCLUDES=

View File

@@ -74,7 +74,12 @@
10. If you need to handle and debug the async tasks (e.g. dataset importing and documents indexing), please start the worker service.
```bash
uv run celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion
uv run celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion,plugin
```
Addition, if you want to debug the celery scheduled tasks, you can use the following command in another terminal:
```bash
uv run celery -A app.celery beat
```
## Testing

View File

@@ -832,6 +832,41 @@ class CeleryBeatConfig(BaseSettings):
)
class CeleryScheduleTasksConfig(BaseSettings):
ENABLE_CLEAN_EMBEDDING_CACHE_TASK: bool = Field(
description="Enable clean embedding cache task",
default=False,
)
ENABLE_CLEAN_UNUSED_DATASETS_TASK: bool = Field(
description="Enable clean unused datasets task",
default=False,
)
ENABLE_CREATE_TIDB_SERVERLESS_TASK: bool = Field(
description="Enable create tidb service job task",
default=False,
)
ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK: bool = Field(
description="Enable update tidb service job status task",
default=False,
)
ENABLE_CLEAN_MESSAGES: bool = Field(
description="Enable clean messages task",
default=False,
)
ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK: bool = Field(
description="Enable mail clean document notify task",
default=False,
)
ENABLE_DATASETS_QUEUE_MONITOR: bool = Field(
description="Enable queue monitor task",
default=False,
)
ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK: bool = Field(
description="Enable check upgradable plugin task",
default=True,
)
class PositionConfig(BaseSettings):
POSITION_PROVIDER_PINS: str = Field(
description="Comma-separated list of pinned model providers",
@@ -961,5 +996,6 @@ class FeatureConfig(
# hosted services config
HostedServiceConfig,
CeleryBeatConfig,
CeleryScheduleTasksConfig,
):
pass

View File

@@ -12,7 +12,8 @@ from controllers.console.wraps import account_initialization_required, setup_req
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.impl.exc import PluginDaemonClientSideError
from libs.login import login_required
from models.account import TenantPluginPermission
from models.account import TenantPluginAutoUpgradeStrategy, TenantPluginPermission
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.plugin.plugin_service import PluginService
@@ -534,6 +535,114 @@ class PluginFetchDynamicSelectOptionsApi(Resource):
return jsonable_encoder({"options": options})
class PluginChangePreferencesApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self):
user = current_user
if not user.is_admin_or_owner:
raise Forbidden()
req = reqparse.RequestParser()
req.add_argument("permission", type=dict, required=True, location="json")
req.add_argument("auto_upgrade", type=dict, required=True, location="json")
args = req.parse_args()
tenant_id = user.current_tenant_id
permission = args["permission"]
install_permission = TenantPluginPermission.InstallPermission(permission.get("install_permission", "everyone"))
debug_permission = TenantPluginPermission.DebugPermission(permission.get("debug_permission", "everyone"))
auto_upgrade = args["auto_upgrade"]
strategy_setting = TenantPluginAutoUpgradeStrategy.StrategySetting(
auto_upgrade.get("strategy_setting", "fix_only")
)
upgrade_time_of_day = auto_upgrade.get("upgrade_time_of_day", 0)
upgrade_mode = TenantPluginAutoUpgradeStrategy.UpgradeMode(auto_upgrade.get("upgrade_mode", "exclude"))
exclude_plugins = auto_upgrade.get("exclude_plugins", [])
include_plugins = auto_upgrade.get("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,
)
if not set_auto_upgrade_strategy_result:
return jsonable_encoder({"success": False, "message": "Failed to set auto upgrade strategy"})
return jsonable_encoder({"success": True})
class PluginFetchPreferencesApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self):
tenant_id = current_user.current_tenant_id
permission = PluginPermissionService.get_permission(tenant_id)
permission_dict = {
"install_permission": TenantPluginPermission.InstallPermission.EVERYONE,
"debug_permission": TenantPluginPermission.DebugPermission.EVERYONE,
}
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({"permission": permission_dict, "auto_upgrade": auto_upgrade_dict})
class PluginAutoUpgradeExcludePluginApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self):
# exclude one single plugin
tenant_id = current_user.current_tenant_id
req = reqparse.RequestParser()
req.add_argument("plugin_id", type=str, required=True, location="json")
args = req.parse_args()
return jsonable_encoder({"success": PluginAutoUpgradeService.exclude_plugin(tenant_id, args["plugin_id"])})
api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key")
api.add_resource(PluginListApi, "/workspaces/current/plugin/list")
api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions")
@@ -560,3 +669,7 @@ api.add_resource(PluginChangePermissionApi, "/workspaces/current/plugin/permissi
api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch")
api.add_resource(PluginFetchDynamicSelectOptionsApi, "/workspaces/current/plugin/parameters/dynamic-options")
api.add_resource(PluginFetchPreferencesApi, "/workspaces/current/plugin/preferences/fetch")
api.add_resource(PluginChangePreferencesApi, "/workspaces/current/plugin/preferences/change")
api.add_resource(PluginAutoUpgradeExcludePluginApi, "/workspaces/current/plugin/preferences/autoupgrade/exclude")

View File

@@ -25,9 +25,29 @@ def batch_fetch_plugin_manifests(plugin_ids: list[str]) -> Sequence[MarketplaceP
url = str(marketplace_api_url / "api/v1/plugins/batch")
response = requests.post(url, json={"plugin_ids": plugin_ids})
response.raise_for_status()
return [MarketplacePluginDeclaration(**plugin) for plugin in response.json()["data"]["plugins"]]
def batch_fetch_plugin_manifests_ignore_deserialization_error(
plugin_ids: list[str],
) -> Sequence[MarketplacePluginDeclaration]:
if len(plugin_ids) == 0:
return []
url = str(marketplace_api_url / "api/v1/plugins/batch")
response = requests.post(url, json={"plugin_ids": plugin_ids})
response.raise_for_status()
result: list[MarketplacePluginDeclaration] = []
for plugin in response.json()["data"]["plugins"]:
try:
result.append(MarketplacePluginDeclaration(**plugin))
except Exception as e:
pass
return result
def record_install_plugin_event(plugin_unique_identifier: str):
url = str(marketplace_api_url / "api/v1/stats/plugins/install_count")
response = requests.post(url, json={"unique_identifier": plugin_unique_identifier})

View File

@@ -22,7 +22,7 @@ if [[ "${MODE}" == "worker" ]]; then
exec celery -A app.celery worker -P ${CELERY_WORKER_CLASS:-gevent} $CONCURRENCY_OPTION \
--max-tasks-per-child ${MAX_TASK_PRE_CHILD:-50} --loglevel ${LOG_LEVEL:-INFO} \
-Q ${CELERY_QUEUES:-dataset,mail,ops_trace,app_deletion}
-Q ${CELERY_QUEUES:-dataset,mail,ops_trace,app_deletion,plugin}
elif [[ "${MODE}" == "beat" ]]; then
exec celery -A app.celery beat --loglevel ${LOG_LEVEL:-INFO}

View File

@@ -64,49 +64,62 @@ def init_app(app: DifyApp) -> Celery:
celery_app.set_default()
app.extensions["celery"] = celery_app
imports = [
"schedule.clean_embedding_cache_task",
"schedule.clean_unused_datasets_task",
"schedule.create_tidb_serverless_task",
"schedule.update_tidb_serverless_status_task",
"schedule.clean_messages",
"schedule.mail_clean_document_notify_task",
"schedule.queue_monitor_task",
]
imports = []
day = dify_config.CELERY_BEAT_SCHEDULER_TIME
beat_schedule = {
"clean_embedding_cache_task": {
# if you add a new task, please add the switch to CeleryScheduleTasksConfig
beat_schedule = {}
if dify_config.ENABLE_CLEAN_EMBEDDING_CACHE_TASK:
imports.append("schedule.clean_embedding_cache_task")
beat_schedule["clean_embedding_cache_task"] = {
"task": "schedule.clean_embedding_cache_task.clean_embedding_cache_task",
"schedule": timedelta(days=day),
},
"clean_unused_datasets_task": {
}
if dify_config.ENABLE_CLEAN_UNUSED_DATASETS_TASK:
imports.append("schedule.clean_unused_datasets_task")
beat_schedule["clean_unused_datasets_task"] = {
"task": "schedule.clean_unused_datasets_task.clean_unused_datasets_task",
"schedule": timedelta(days=day),
},
"create_tidb_serverless_task": {
}
if dify_config.ENABLE_CREATE_TIDB_SERVERLESS_TASK:
imports.append("schedule.create_tidb_serverless_task")
beat_schedule["create_tidb_serverless_task"] = {
"task": "schedule.create_tidb_serverless_task.create_tidb_serverless_task",
"schedule": crontab(minute="0", hour="*"),
},
"update_tidb_serverless_status_task": {
}
if dify_config.ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK:
imports.append("schedule.update_tidb_serverless_status_task")
beat_schedule["update_tidb_serverless_status_task"] = {
"task": "schedule.update_tidb_serverless_status_task.update_tidb_serverless_status_task",
"schedule": timedelta(minutes=10),
},
"clean_messages": {
}
if dify_config.ENABLE_CLEAN_MESSAGES:
imports.append("schedule.clean_messages")
beat_schedule["clean_messages"] = {
"task": "schedule.clean_messages.clean_messages",
"schedule": timedelta(days=day),
},
# every Monday
"mail_clean_document_notify_task": {
}
if dify_config.ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK:
imports.append("schedule.mail_clean_document_notify_task")
beat_schedule["mail_clean_document_notify_task"] = {
"task": "schedule.mail_clean_document_notify_task.mail_clean_document_notify_task",
"schedule": crontab(minute="0", hour="10", day_of_week="1"),
},
"datasets-queue-monitor": {
}
if dify_config.ENABLE_DATASETS_QUEUE_MONITOR:
imports.append("schedule.queue_monitor_task")
beat_schedule["datasets-queue-monitor"] = {
"task": "schedule.queue_monitor_task.queue_monitor_task",
"schedule": timedelta(
minutes=dify_config.QUEUE_MONITOR_INTERVAL if dify_config.QUEUE_MONITOR_INTERVAL else 30
),
},
}
}
if dify_config.ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK:
imports.append("schedule.check_upgradable_plugin_task")
beat_schedule["check_upgradable_plugin_task"] = {
"task": "schedule.check_upgradable_plugin_task.check_upgradable_plugin_task",
"schedule": crontab(minute="*/15"),
}
celery_app.conf.update(beat_schedule=beat_schedule, imports=imports)
return celery_app

View File

@@ -299,3 +299,35 @@ class TenantPluginPermission(Base):
db.String(16), nullable=False, server_default="everyone"
)
debug_permission: Mapped[DebugPermission] = mapped_column(db.String(16), nullable=False, server_default="noone")
class TenantPluginAutoUpgradeStrategy(Base):
class StrategySetting(enum.StrEnum):
DISABLED = "disabled"
FIX_ONLY = "fix_only"
LATEST = "latest"
class UpgradeMode(enum.StrEnum):
ALL = "all"
PARTIAL = "partial"
EXCLUDE = "exclude"
__tablename__ = "tenant_plugin_auto_upgrade_strategies"
__table_args__ = (
db.PrimaryKeyConstraint("id", name="tenant_plugin_auto_upgrade_strategy_pkey"),
db.UniqueConstraint("tenant_id", name="unique_tenant_plugin_auto_upgrade_strategy"),
)
id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()"))
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
strategy_setting: Mapped[StrategySetting] = mapped_column(db.String(16), nullable=False, server_default="fix_only")
upgrade_time_of_day: Mapped[int] = mapped_column(db.Integer, nullable=False, default=0) # seconds of the day
upgrade_mode: Mapped[UpgradeMode] = mapped_column(db.String(16), nullable=False, server_default="exclude")
exclude_plugins: Mapped[list[str]] = mapped_column(
db.ARRAY(db.String(255)), nullable=False
) # plugin_id (author/name)
include_plugins: Mapped[list[str]] = mapped_column(
db.ARRAY(db.String(255)), nullable=False
) # plugin_id (author/name)
created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp())
updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp())

View File

@@ -0,0 +1,49 @@
import time
import click
import app
from extensions.ext_database import db
from models.account import TenantPluginAutoUpgradeStrategy
from tasks.process_tenant_plugin_autoupgrade_check_task import process_tenant_plugin_autoupgrade_check_task
AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL = 15 * 60 # 15 minutes
@app.celery.task(queue="plugin")
def check_upgradable_plugin_task():
click.echo(click.style("Start check upgradable plugin.", fg="green"))
start_at = time.perf_counter()
now_seconds_of_day = time.time() % 86400 - 30 # we assume the tz is UTC
click.echo(click.style("Now seconds of day: {}".format(now_seconds_of_day), fg="green"))
strategies = (
db.session.query(TenantPluginAutoUpgradeStrategy)
.filter(
TenantPluginAutoUpgradeStrategy.upgrade_time_of_day >= now_seconds_of_day,
TenantPluginAutoUpgradeStrategy.upgrade_time_of_day
< now_seconds_of_day + AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL,
TenantPluginAutoUpgradeStrategy.strategy_setting
!= TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED,
)
.all()
)
for strategy in strategies:
process_tenant_plugin_autoupgrade_check_task.delay(
strategy.tenant_id,
strategy.strategy_setting,
strategy.upgrade_time_of_day,
strategy.upgrade_mode,
strategy.exclude_plugins,
strategy.include_plugins,
)
end_at = time.perf_counter()
click.echo(
click.style(
"Checked upgradable plugin success latency: {}".format(end_at - start_at),
fg="green",
)
)

View File

@@ -29,6 +29,7 @@ from models.account import (
Tenant,
TenantAccountJoin,
TenantAccountRole,
TenantPluginAutoUpgradeStrategy,
TenantStatus,
)
from models.model import DifySetup
@@ -828,6 +829,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)
db.session.commit()
tenant.encrypt_public_key = generate_key_pair(tenant.id)
db.session.commit()
return tenant

View File

@@ -0,0 +1,87 @@
from sqlalchemy.orm import Session
from extensions.ext_database import db
from models.account import TenantPluginAutoUpgradeStrategy
class PluginAutoUpgradeService:
@staticmethod
def get_strategy(tenant_id: str) -> TenantPluginAutoUpgradeStrategy | None:
with Session(db.engine) as session:
return (
session.query(TenantPluginAutoUpgradeStrategy)
.filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id)
.first()
)
@staticmethod
def change_strategy(
tenant_id: str,
strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting,
upgrade_time_of_day: int,
upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode,
exclude_plugins: list[str],
include_plugins: list[str],
) -> bool:
with Session(db.engine) as session:
exist_strategy = (
session.query(TenantPluginAutoUpgradeStrategy)
.filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id)
.first()
)
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
session.commit()
return True
@staticmethod
def exclude_plugin(tenant_id: str, plugin_id: str) -> bool:
with Session(db.engine) as session:
exist_strategy = (
session.query(TenantPluginAutoUpgradeStrategy)
.filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id)
.first()
)
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]
session.commit()
return True

View File

@@ -0,0 +1,166 @@
import traceback
import typing
import click
from celery import shared_task # type: ignore
from core.helper import marketplace
from core.helper.marketplace import MarketplacePluginDeclaration
from core.plugin.entities.plugin import PluginInstallationSource
from core.plugin.impl.plugin import PluginInstaller
from models.account import TenantPluginAutoUpgradeStrategy
RETRY_TIMES_OF_ONE_PLUGIN_IN_ONE_TENANT = 3
cached_plugin_manifests: dict[str, typing.Union[MarketplacePluginDeclaration, None]] = {}
def marketplace_batch_fetch_plugin_manifests(
plugin_ids_plain_list: list[str],
) -> list[MarketplacePluginDeclaration]:
global cached_plugin_manifests
# return marketplace.batch_fetch_plugin_manifests(plugin_ids_plain_list)
not_included_plugin_ids = [
plugin_id for plugin_id in plugin_ids_plain_list if plugin_id not in cached_plugin_manifests
]
if not_included_plugin_ids:
manifests = marketplace.batch_fetch_plugin_manifests_ignore_deserialization_error(not_included_plugin_ids)
for manifest in manifests:
cached_plugin_manifests[manifest.plugin_id] = manifest
if (
len(manifests) == 0
): # this indicates that the plugin not found in marketplace, should set None in cache to prevent future check
for plugin_id in not_included_plugin_ids:
cached_plugin_manifests[plugin_id] = None
result: list[MarketplacePluginDeclaration] = []
for plugin_id in plugin_ids_plain_list:
final_manifest = cached_plugin_manifests.get(plugin_id)
if final_manifest is not None:
result.append(final_manifest)
return result
@shared_task(queue="plugin")
def process_tenant_plugin_autoupgrade_check_task(
tenant_id: str,
strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting,
upgrade_time_of_day: int,
upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode,
exclude_plugins: list[str],
include_plugins: list[str],
):
try:
manager = PluginInstaller()
click.echo(
click.style(
"Checking upgradable plugin for tenant: {}".format(tenant_id),
fg="green",
)
)
if strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED:
return
# get plugin_ids to check
plugin_ids: list[tuple[str, str, str]] = [] # plugin_id, version, unique_identifier
click.echo(click.style("Upgrade mode: {}".format(upgrade_mode), fg="green"))
if upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL and include_plugins:
all_plugins = manager.list_plugins(tenant_id)
for plugin in all_plugins:
if plugin.source == PluginInstallationSource.Marketplace and plugin.plugin_id in include_plugins:
plugin_ids.append(
(
plugin.plugin_id,
plugin.version,
plugin.plugin_unique_identifier,
)
)
elif upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE:
# get all plugins and remove excluded plugins
all_plugins = manager.list_plugins(tenant_id)
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
]
elif upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL:
all_plugins = manager.list_plugins(tenant_id)
plugin_ids = [
(plugin.plugin_id, plugin.version, plugin.plugin_unique_identifier)
for plugin in all_plugins
if plugin.source == PluginInstallationSource.Marketplace
]
if not plugin_ids:
return
plugin_ids_plain_list = [plugin_id for plugin_id, _, _ in plugin_ids]
manifests = marketplace_batch_fetch_plugin_manifests(plugin_ids_plain_list)
if not manifests:
return
for manifest in manifests:
for plugin_id, version, original_unique_identifier in plugin_ids:
if manifest.plugin_id != plugin_id:
continue
try:
current_version = version
latest_version = manifest.latest_version
def fix_only_checker(latest_version, current_version):
latest_version_tuple = tuple(int(val) for val in latest_version.split("."))
current_version_tuple = tuple(int(val) for val in current_version.split("."))
if (
latest_version_tuple[0] == current_version_tuple[0]
and latest_version_tuple[1] == current_version_tuple[1]
):
return latest_version_tuple[2] != current_version_tuple[2]
return False
version_checker = {
TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST: lambda latest_version,
current_version: latest_version != current_version,
TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY: fix_only_checker,
}
if version_checker[strategy_setting](latest_version, current_version):
# execute upgrade
new_unique_identifier = manifest.latest_package_identifier
marketplace.record_install_plugin_event(new_unique_identifier)
click.echo(
click.style(
"Upgrade plugin: {} -> {}".format(original_unique_identifier, new_unique_identifier),
fg="green",
)
)
task_start_resp = manager.upgrade_plugin(
tenant_id,
original_unique_identifier,
new_unique_identifier,
PluginInstallationSource.Marketplace,
{
"plugin_unique_identifier": new_unique_identifier,
},
)
except Exception as e:
click.echo(click.style("Error when upgrading plugin: {}".format(e), fg="red"))
traceback.print_exc()
break
except Exception as e:
click.echo(click.style("Error when checking upgradable plugin: {}".format(e), fg="red"))
traceback.print_exc()
return

View File

@@ -1168,3 +1168,13 @@ QUEUE_MONITOR_THRESHOLD=200
QUEUE_MONITOR_ALERT_EMAILS=
# Monitor interval in minutes, default is 30 minutes
QUEUE_MONITOR_INTERVAL=30
# Celery schedule tasks configuration
ENABLE_CLEAN_EMBEDDING_CACHE_TASK=false
ENABLE_CLEAN_UNUSED_DATASETS_TASK=false
ENABLE_CREATE_TIDB_SERVERLESS_TASK=false
ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK=false
ENABLE_CLEAN_MESSAGES=false
ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK=false
ENABLE_DATASETS_QUEUE_MONITOR=false
ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK=true

View File

@@ -55,6 +55,25 @@ services:
- ssrf_proxy_network
- default
# worker_beat service
# Celery beat for scheduling periodic tasks.
worker_beat:
image: langgenius/dify-api:1.5.0
restart: always
environment:
# Use the shared environment variables.
<<: *shared-api-worker-env
# Startup mode, 'worker_beat' starts the Celery beat for scheduling periodic tasks.
MODE: beat
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- ssrf_proxy_network
- default
# Frontend web application.
web:
image: langgenius/dify-web:1.6.0

View File

@@ -527,6 +527,14 @@ x-shared-env: &shared-api-worker-env
QUEUE_MONITOR_THRESHOLD: ${QUEUE_MONITOR_THRESHOLD:-200}
QUEUE_MONITOR_ALERT_EMAILS: ${QUEUE_MONITOR_ALERT_EMAILS:-}
QUEUE_MONITOR_INTERVAL: ${QUEUE_MONITOR_INTERVAL:-30}
ENABLE_CLEAN_EMBEDDING_CACHE_TASK: ${ENABLE_CLEAN_EMBEDDING_CACHE_TASK:-false}
ENABLE_CLEAN_UNUSED_DATASETS_TASK: ${ENABLE_CLEAN_UNUSED_DATASETS_TASK:-false}
ENABLE_CREATE_TIDB_SERVERLESS_TASK: ${ENABLE_CREATE_TIDB_SERVERLESS_TASK:-false}
ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK: ${ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK:-false}
ENABLE_CLEAN_MESSAGES: ${ENABLE_CLEAN_MESSAGES:-false}
ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK: ${ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK:-false}
ENABLE_DATASETS_QUEUE_MONITOR: ${ENABLE_DATASETS_QUEUE_MONITOR:-false}
ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK: ${ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK:-true}
services:
# API service
@@ -584,6 +592,25 @@ services:
- ssrf_proxy_network
- default
# worker_beat service
# Celery beat for scheduling periodic tasks.
worker_beat:
image: langgenius/dify-api:1.5.0
restart: always
environment:
# Use the shared environment variables.
<<: *shared-api-worker-env
# Startup mode, 'worker_beat' starts the Celery beat for scheduling periodic tasks.
MODE: beat
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- ssrf_proxy_network
- default
# Frontend web application.
web:
image: langgenius/dify-web:1.6.0

View File

@@ -4,18 +4,21 @@ import cn from '@/utils/classnames'
type OptionListItemProps = {
isSelected: boolean
onClick: () => void
noAutoScroll?: boolean
} & React.LiHTMLAttributes<HTMLLIElement>
const OptionListItem: FC<OptionListItemProps> = ({
isSelected,
onClick,
noAutoScroll,
children,
}) => {
const listItemRef = useRef<HTMLLIElement>(null)
useEffect(() => {
if (isSelected)
if (isSelected && !noAutoScroll)
listItemRef.current?.scrollIntoView({ behavior: 'instant' })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (

View File

@@ -1,13 +1,18 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
const Header = () => {
type Props = {
title?: string
}
const Header = ({
title,
}: Props) => {
const { t } = useTranslation()
return (
<div className='flex flex-col border-b-[0.5px] border-divider-regular'>
<div className='system-md-semibold flex items-center px-2 py-1.5 text-text-primary'>
{t('time.title.pickTime')}
{title || t('time.title.pickTime')}
</div>
</div>
)

View File

@@ -20,6 +20,9 @@ const TimePicker = ({
onChange,
onClear,
renderTrigger,
title,
minuteFilter,
popupClassName,
}: TimePickerProps) => {
const { t } = useTranslation()
const [isOpen, setIsOpen] = useState(false)
@@ -108,6 +111,15 @@ const TimePicker = ({
const displayValue = value?.format(timeFormat) || ''
const placeholderDate = isOpen && selectedTime ? selectedTime.format(timeFormat) : (placeholder || t('time.defaultPlaceholder'))
const inputElem = (
<input
className='system-xs-regular flex-1 cursor-pointer appearance-none truncate bg-transparent p-1
text-components-input-text-filled outline-none placeholder:text-components-input-text-placeholder'
readOnly
value={isOpen ? '' : displayValue}
placeholder={placeholderDate}
/>
)
return (
<PortalToFollowElem
open={isOpen}
@@ -115,18 +127,16 @@ const TimePicker = ({
placement='bottom-end'
>
<PortalToFollowElemTrigger>
{renderTrigger ? (renderTrigger()) : (
{renderTrigger ? (renderTrigger({
inputElem,
onClick: handleClickTrigger,
isOpen,
})) : (
<div
className='group flex w-[252px] cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1 hover:bg-state-base-hover-alt'
onClick={handleClickTrigger}
>
<input
className='system-xs-regular flex-1 cursor-pointer appearance-none truncate bg-transparent p-1
text-components-input-text-filled outline-none placeholder:text-components-input-text-placeholder'
readOnly
value={isOpen ? '' : displayValue}
placeholder={placeholderDate}
/>
{inputElem}
<RiTimeLine className={cn(
'h-4 w-4 shrink-0 text-text-quaternary',
isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary',
@@ -142,14 +152,15 @@ const TimePicker = ({
</div>
)}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-50'>
<PortalToFollowElemContent className={cn('z-50', popupClassName)}>
<div className='mt-1 w-[252px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg shadow-shadow-shadow-5'>
{/* Header */}
<Header />
<Header title={title} />
{/* Time Options */}
<Options
selectedTime={selectedTime}
minuteFilter={minuteFilter}
handleSelectHour={handleSelectHour}
handleSelectMinute={handleSelectMinute}
handleSelectPeriod={handleSelectPeriod}

View File

@@ -5,6 +5,7 @@ import OptionListItem from '../common/option-list-item'
const Options: FC<TimeOptionsProps> = ({
selectedTime,
minuteFilter,
handleSelectHour,
handleSelectMinute,
handleSelectPeriod,
@@ -33,7 +34,7 @@ const Options: FC<TimeOptionsProps> = ({
{/* Minute */}
<ul className='no-scrollbar flex h-[208px] flex-col gap-y-0.5 overflow-y-auto pb-[184px]'>
{
minuteOptions.map((minute) => {
(minuteFilter ? minuteFilter(minuteOptions) : minuteOptions).map((minute) => {
const isSelected = selectedTime?.format('mm') === minute
return (
<OptionListItem
@@ -57,6 +58,7 @@ const Options: FC<TimeOptionsProps> = ({
key={period}
isSelected={isSelected}
onClick={handleSelectPeriod.bind(null, period)}
noAutoScroll // if choose PM which would hide(scrolled) AM that may make user confused that there's no am.
>
{period}
</OptionListItem>

View File

@@ -28,6 +28,7 @@ export type DatePickerProps = {
onClear: () => void
triggerWrapClassName?: string
renderTrigger?: (props: TriggerProps) => React.ReactNode
minuteFilter?: (minutes: string[]) => string[]
popupZIndexClassname?: string
}
@@ -47,13 +48,21 @@ export type DatePickerFooterProps = {
handleConfirmDate: () => void
}
export type TriggerParams = {
isOpen: boolean
inputElem: React.ReactNode
onClick: (e: React.MouseEvent) => void
}
export type TimePickerProps = {
value: Dayjs | undefined
timezone?: string
placeholder?: string
onChange: (date: Dayjs | undefined) => void
onClear: () => void
renderTrigger?: () => React.ReactNode
renderTrigger?: (props: TriggerParams) => React.ReactNode
title?: string
minuteFilter?: (minutes: string[]) => string[]
popupClassName?: string
}
export type TimePickerFooterProps = {
@@ -81,6 +90,7 @@ export type CalendarItemProps = {
export type TimeOptionsProps = {
selectedTime: Dayjs | undefined
minuteFilter?: (minutes: string[]) => string[]
handleSelectHour: (hour: string) => void
handleSelectMinute: (minute: string) => void
handleSelectPeriod: (period: Period) => void

View File

@@ -2,6 +2,7 @@ import dayjs, { type Dayjs } from 'dayjs'
import type { Day } from '../types'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import tz from '@/utils/timezone.json'
dayjs.extend(utc)
dayjs.extend(timezone)
@@ -78,3 +79,14 @@ export const getHourIn12Hour = (date: Dayjs) => {
export const getDateWithTimezone = (props: { date?: Dayjs, timezone?: string }) => {
return props.date ? dayjs.tz(props.date, props.timezone) : dayjs().tz(props.timezone)
}
// Asia/Shanghai -> UTC+8
const DEFAULT_OFFSET_STR = 'UTC+0'
export const convertTimezoneToOffsetStr = (timezone?: string) => {
if (!timezone)
return DEFAULT_OFFSET_STR
const tzItem = tz.find(item => item.value === timezone)
if(!tzItem)
return DEFAULT_OFFSET_STR
return `UTC${tzItem.name.charAt(0)}${tzItem.name.charAt(2)}`
}

View File

@@ -0,0 +1,7 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.0049 16C28.0049 20.4183 24.4231 24 20.0049 24C15.5866 24 12.0049 20.4183 12.0049 16C12.0049 11.5817 15.5866 8 20.0049 8C24.4231 8 28.0049 11.5817 28.0049 16Z" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.00488 16H6.67155" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.00488 9.33334H8.00488" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.00488 22.6667H8.00488" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26 22L29.3333 25.3333" stroke="#676F83" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 823 B

View File

@@ -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="M5.46257 4.43262C7.21556 2.91688 9.5007 2 12 2C17.5228 2 22 6.47715 22 12C22 14.1361 21.3302 16.1158 20.1892 17.7406L17 12H20C20 7.58172 16.4183 4 12 4C9.84982 4 7.89777 4.84827 6.46023 6.22842L5.46257 4.43262ZM18.5374 19.5674C16.7844 21.0831 14.4993 22 12 22C6.47715 22 2 17.5228 2 12C2 9.86386 2.66979 7.88416 3.8108 6.25944L7 12H4C4 16.4183 7.58172 20 12 20C14.1502 20 16.1022 19.1517 17.5398 17.7716L18.5374 19.5674Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.3308 16H14.2915L13.6249 13.9476H10.3761L9.70846 16H7.66918L10.7759 7H13.2281L16.3308 16ZM10.8595 12.4622H13.1435L12.0378 9.05639H11.9673L10.8595 12.4622Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 772 B

View File

@@ -75,7 +75,7 @@ Icon.displayName = '<%= svgName %>'
export default Icon
`.trim())
await writeFile(path.resolve(currentPath, `${fileName}.json`), JSON.stringify(svgData, '', '\t'))
await writeFile(path.resolve(currentPath, `${fileName}.json`), `${JSON.stringify(svgData, '', '\t')}\n`)
await writeFile(path.resolve(currentPath, `${fileName}.tsx`), `${componentRender({ svgName: fileName })}\n`)
const indexingRender = template(`

File diff suppressed because one or more lines are too long

View File

@@ -4,12 +4,16 @@
import * as React from 'react'
import data from './AliyunIcon.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'AliyunIcon'

File diff suppressed because one or more lines are too long

View File

@@ -4,12 +4,16 @@
import * as React from 'react'
import data from './AliyunIconBig.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'AliyunIconBig'

View File

@@ -4,12 +4,16 @@
import * as React from 'react'
import data from './WeaveIcon.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'WeaveIcon'

View File

@@ -4,12 +4,16 @@
import * as React from 'react'
import data from './WeaveIconBig.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'WeaveIconBig'

View File

@@ -1,3 +1,5 @@
export { default as AliyunIconBig } from './AliyunIconBig'
export { default as AliyunIcon } from './AliyunIcon'
export { default as ArizeIconBig } from './ArizeIconBig'
export { default as ArizeIcon } from './ArizeIcon'
export { default as LangfuseIconBig } from './LangfuseIconBig'
@@ -11,5 +13,3 @@ export { default as PhoenixIcon } from './PhoenixIcon'
export { default as TracingIcon } from './TracingIcon'
export { default as WeaveIconBig } from './WeaveIconBig'
export { default as WeaveIcon } from './WeaveIcon'
export { default as AliyunIconBig } from './AliyunIconBig'
export { default as AliyunIcon } from './AliyunIcon'

View File

@@ -23,4 +23,4 @@
]
},
"name": "Citations"
}
}

View File

@@ -25,4 +25,4 @@
]
},
"name": "ContentModeration"
}
}

View File

@@ -20,4 +20,4 @@
]
},
"name": "Document"
}
}

View File

@@ -23,4 +23,4 @@
]
},
"name": "FolderUpload"
}
}

View File

@@ -23,4 +23,4 @@
]
},
"name": "LoveMessage"
}
}

View File

@@ -25,4 +25,4 @@
]
},
"name": "MessageFast"
}
}

View File

@@ -34,4 +34,4 @@
]
},
"name": "Microphone01"
}
}

View File

@@ -74,4 +74,4 @@
]
},
"name": "TextToAudio"
}
}

View File

@@ -32,4 +32,4 @@
]
},
"name": "VirtualAssistant"
}
}

View File

@@ -25,4 +25,4 @@
]
},
"name": "Vision"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "AlertTriangle"
}
}

View File

@@ -63,4 +63,4 @@
]
},
"name": "ThumbsDown"
}
}

View File

@@ -63,4 +63,4 @@
]
},
"name": "ThumbsUp"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "ArrowNarrowLeft"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "ArrowUpRight"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "ChevronDownDouble"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "ChevronRight"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "ChevronSelectorVertical"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "RefreshCcw01"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "RefreshCw05"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "ReverseLeft"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "AiText"
}
}

View File

@@ -90,4 +90,4 @@
]
},
"name": "ChatBot"
}
}

View File

@@ -65,4 +65,4 @@
]
},
"name": "ChatBotSlim"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "CuteRobot"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "MessageCheckRemove"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "MessageFastPlus"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "ArtificialBrain"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "BarChartSquare02"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "BracketsX"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "CodeBrowser"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "Container"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "Database01"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "Database03"
}
}

View File

@@ -49,4 +49,4 @@
]
},
"name": "FileHeart02"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "GitBranch01"
}
}

View File

@@ -62,4 +62,4 @@
]
},
"name": "PromptEngineering"
}
}

View File

@@ -63,4 +63,4 @@
]
},
"name": "PuzzlePiece01"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "TerminalSquare"
}
}

View File

@@ -59,4 +59,4 @@
]
},
"name": "Variable"
}
}

View File

@@ -86,4 +86,4 @@
]
},
"name": "Webhooks"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "AlignLeft"
}
}

View File

@@ -35,4 +35,4 @@
]
},
"name": "BezierCurve03"
}
}

View File

@@ -59,4 +59,4 @@
]
},
"name": "Collapse"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "Colors"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "ImageIndentLeft"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "LeftIndent02"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "LetterSpacing01"
}
}

View File

@@ -35,4 +35,4 @@
]
},
"name": "TypeSquare"
}
}

View File

@@ -46,4 +46,4 @@
]
},
"name": "BookOpen01"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "File02"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "FileArrow01"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "FileCheck02"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "FileDownload02"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "FilePlus01"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "FilePlus02"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "FileText"
}
}

View File

@@ -49,4 +49,4 @@
]
},
"name": "FileUpload"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "Folder"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "Balance"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "CoinsStacked01"
}
}

View File

@@ -117,4 +117,4 @@
]
},
"name": "GoldCoin"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "ReceiptList"
}
}

View File

@@ -63,4 +63,4 @@
]
},
"name": "Tag01"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "Tag03"
}
}

View File

@@ -63,4 +63,4 @@
]
},
"name": "AtSign"
}
}

View File

@@ -26,4 +26,4 @@
]
},
"name": "Bookmark"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "Check"
}
}

View File

@@ -36,4 +36,4 @@
]
},
"name": "CheckDone01"
}
}

Some files were not shown because too many files have changed in this diff Show More