diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py
index 701fa366d81..d8b3c2e4256 100644
--- a/api/controllers/console/app/app.py
+++ b/api/controllers/console/app/app.py
@@ -58,6 +58,7 @@ from services.app_service import (
AppResponseView,
AppService,
CreateAppParams,
+ RecentAppMode,
StarredAppListParams,
)
from services.enterprise import rbac_service as enterprise_rbac_service
@@ -139,6 +140,10 @@ class AppListBaseQuery(BaseModel):
raise ValueError("Invalid UUID format in creator_ids.") from exc
+class RecentAppListQuery(BaseModel):
+ limit: int = Field(default=8, ge=1, le=8, description="Number of recently modified apps to return (1-8)")
+
+
class AppListQuery(AppListBaseQuery):
pass
@@ -411,6 +416,33 @@ class AppPartial(AppResponseModel):
return to_timestamp(value)
+class RecentAppResponse(ResponseModel):
+ id: str
+ name: str
+ icon_type: IconType | None = None
+ icon: str | None = None
+ icon_background: str | None = None
+ mode: RecentAppMode
+ author_name: str | None = None
+ updated_at: int
+ permission_keys: list[str] = Field(default_factory=list)
+ maintainer: str | None = None
+
+ @computed_field(return_type=str | None) # type: ignore[prop-decorator]
+ @property
+ def icon_url(self) -> str | None:
+ return build_icon_url(self.icon_type, self.icon)
+
+ @field_validator("updated_at", mode="before")
+ @classmethod
+ def _normalize_timestamp(cls, value: datetime | int) -> int:
+ return to_timestamp(value)
+
+
+class RecentAppListResponse(ResponseModel):
+ data: list[RecentAppResponse]
+
+
class AppDetail(AppResponseModel):
id: str
name: str
@@ -575,6 +607,8 @@ register_schema_models(
register_response_schema_models(
console_ns,
AppPartial,
+ RecentAppResponse,
+ RecentAppListResponse,
AppDetailWithSite,
AppPagination,
)
@@ -699,6 +733,49 @@ class AppListApi(Resource):
return app_detail.model_dump(mode="json"), 201
+@console_ns.route("/apps/recent")
+class RecentAppListApi(Resource):
+ @console_ns.doc("list_recent_apps")
+ @console_ns.doc(description="Get recently modified apps for the home Continue Work section")
+ @console_ns.doc(params=query_params_from_model(RecentAppListQuery))
+ @console_ns.response(200, "Success", console_ns.models[RecentAppListResponse.__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):
+ """Return the lightweight app cards needed by the Explore home page."""
+ args = query_params_from_request(RecentAppListQuery)
+ params = AppListParams(limit=args.limit)
+
+ permissions = enterprise_rbac_service.RBACService.MyPermissions.get(
+ current_tenant_id,
+ current_user_id,
+ session=session,
+ )
+ if dify_config.RBAC_ENABLED:
+ access_filter = resolve_app_access_filter(
+ current_tenant_id,
+ current_user_id,
+ session=session,
+ permissions=permissions,
+ )
+ access_filter.apply_to_params(params)
+
+ recent_apps = AppService().get_recent_apps(current_user_id, current_tenant_id, params, session)
+ permission_keys_map = permissions.app.permission_keys_by_resource_ids([app.id for app in recent_apps])
+ response_items = [
+ RecentAppResponse.model_validate(app, from_attributes=True).model_copy(
+ update={"permission_keys": permission_keys_map.get(app.id, [])}
+ )
+ for app in recent_apps
+ ]
+ return dump_response(RecentAppListResponse, {"data": response_items}), 200
+
+
@console_ns.route("/apps/starred")
class StarredAppListApi(Resource):
@console_ns.doc("list_starred_apps")
diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md
index 34478b71b7c..e4d46101bb3 100644
--- a/api/openapi/markdown/console-openapi.md
+++ b/api/openapi/markdown/console-openapi.md
@@ -1672,6 +1672,23 @@ Create a new application
| 200 | Import confirmed | **application/json**: [Import](#import)
|
| 400 | Import failed | **application/json**: [Import](#import)
|
+### [GET] /apps/recent
+**Return the lightweight app cards needed by the Explore home page**
+
+Get recently modified apps for the home Continue Work section
+
+#### Parameters
+
+| Name | Located in | Description | Required | Schema |
+| ---- | ---------- | ----------- | -------- | ------ |
+| limit | query | Number of recently modified apps to return (1-8) | No | integer,
**Default:** 8 |
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | Success | **application/json**: [RecentAppListResponse](#recentapplistresponse)
|
+
### [GET] /apps/starred
Get applications starred by the current account
@@ -21018,6 +21035,28 @@ Whitelist scopes accepted by RBAC app and dataset access config APIs.
| result | string | | Yes |
| updated_at | integer | | Yes |
+#### RecentAppListResponse
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| data | [ [RecentAppResponse](#recentappresponse) ] | | Yes |
+
+#### RecentAppResponse
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| author_name | string | | No |
+| icon | string | | No |
+| icon_background | string | | No |
+| icon_type | [IconType](#icontype) | | No |
+| icon_url | string | | Yes |
+| id | string | | Yes |
+| maintainer | string | | No |
+| mode | string,
**Available values:** "advanced-chat", "agent-chat", "chat", "completion", "workflow" | *Enum:* `"advanced-chat"`, `"agent-chat"`, `"chat"`, `"completion"`, `"workflow"` | Yes |
+| name | string | | Yes |
+| permission_keys | [ string ] | | No |
+| updated_at | integer | | Yes |
+
#### RecommendedAppDetailNullableResponse
| Name | Type | Description | Required |
diff --git a/api/services/app_service.py b/api/services/app_service.py
index 3c32d1984e2..a858163e2fb 100644
--- a/api/services/app_service.py
+++ b/api/services/app_service.py
@@ -1,6 +1,7 @@
import json
import logging
from collections.abc import Sequence
+from dataclasses import dataclass
from datetime import datetime
from typing import Any, Literal, NotRequired, TypedDict, cast, override
@@ -41,6 +42,20 @@ 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"]
+RecentAppMode = Literal[
+ AppMode.COMPLETION,
+ AppMode.WORKFLOW,
+ AppMode.CHAT,
+ AppMode.ADVANCED_CHAT,
+ AppMode.AGENT_CHAT,
+]
+RECENT_APP_MODES: tuple[RecentAppMode, ...] = (
+ AppMode.COMPLETION,
+ AppMode.WORKFLOW,
+ AppMode.CHAT,
+ AppMode.ADVANCED_CHAT,
+ AppMode.AGENT_CHAT,
+)
class AppListBaseParams(BaseModel):
@@ -65,6 +80,19 @@ class StarredAppListParams(AppListBaseParams):
pass
+@dataclass(frozen=True)
+class RecentAppListItem:
+ id: str
+ name: str
+ icon_type: IconType | None
+ icon: str | None
+ icon_background: str | None
+ mode: RecentAppMode
+ author_name: str | None
+ updated_at: datetime
+ maintainer: str | None
+
+
class CreateAppParams(BaseModel):
name: str = Field(min_length=1)
description: str | None = None
@@ -323,6 +351,62 @@ class AppService:
return app_models
+ def get_recent_apps(
+ self,
+ user_id: str,
+ tenant_id: str,
+ params: AppListParams,
+ session: Session,
+ ) -> list[RecentAppListItem]:
+ """Return recently modified apps as one lightweight, non-paginated projection."""
+ filters = self._build_app_list_filters(user_id, tenant_id, params, session)
+ if not filters:
+ return []
+
+ stmt = (
+ sa.select(
+ App.id,
+ App.name,
+ App.icon_type,
+ App.icon,
+ App.icon_background,
+ App.mode,
+ Account.name.label("author_name"),
+ App.updated_at,
+ App.maintainer,
+ )
+ .outerjoin(Account, Account.id == App.created_by)
+ .where(*filters, App.mode.in_(RECENT_APP_MODES))
+ .order_by(App.updated_at.desc())
+ .limit(params.limit)
+ )
+ rows = session.execute(stmt).all()
+
+ return [
+ RecentAppListItem(
+ id=str(app_id),
+ name=name,
+ icon_type=icon_type,
+ icon=icon,
+ icon_background=icon_background,
+ mode=cast(RecentAppMode, mode),
+ author_name=author_name,
+ updated_at=updated_at,
+ maintainer=maintainer,
+ )
+ for (
+ app_id,
+ name,
+ icon_type,
+ icon,
+ icon_background,
+ mode,
+ author_name,
+ updated_at,
+ maintainer,
+ ) in rows
+ ]
+
def get_paginate_starred_apps(
self,
user_id: str,
diff --git a/api/tests/unit_tests/controllers/console/app/test_app_response_models.py b/api/tests/unit_tests/controllers/console/app/test_app_response_models.py
index 6b1dce534ed..b5631b4c1d1 100644
--- a/api/tests/unit_tests/controllers/console/app/test_app_response_models.py
+++ b/api/tests/unit_tests/controllers/console/app/test_app_response_models.py
@@ -708,6 +708,121 @@ def test_app_list_api_attaches_permission_keys(app, app_module):
assert resp["data"][0]["permission_keys"] == ["app.acl.view_layout", "app.acl.edit"]
+def test_recent_app_list_api_returns_only_home_card_fields(app, app_module):
+ method = app_module.RecentAppListApi.get
+ while hasattr(method, "__wrapped__"):
+ method = method.__wrapped__
+
+ recent_app = SimpleNamespace(
+ id="app-1",
+ name="Recent App",
+ icon_type="emoji",
+ icon="🚀",
+ icon_background="#FFFFFF",
+ mode="chat",
+ author_name="Recent Author",
+ updated_at=_ts(15),
+ maintainer="acct-1",
+ )
+ get_recent_apps = MagicMock(return_value=[recent_app])
+
+ with app.test_request_context("/apps/recent?limit=8"):
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(dify_config, "RBAC_ENABLED", False)
+ monkeypatch.setattr(app_module.AppService, "get_recent_apps", get_recent_apps)
+ monkeypatch.setattr(
+ app_module.enterprise_rbac_service.RBACService.MyPermissions,
+ "get",
+ lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
+ app=app_module.enterprise_rbac_service.ResourcePermissionSnapshot(
+ overrides=[
+ app_module.enterprise_rbac_service.ResourcePermissionKeys(
+ resource_id="app-1",
+ permission_keys=["app.acl.monitor"],
+ )
+ ]
+ )
+ ),
+ )
+
+ resp, status = method(app_module.RecentAppListApi(), "tenant-1", "acct-1", MagicMock())
+
+ assert status == 200
+ assert resp == {
+ "data": [
+ {
+ "id": "app-1",
+ "name": "Recent App",
+ "icon_type": "emoji",
+ "icon": "🚀",
+ "icon_background": "#FFFFFF",
+ "mode": "chat",
+ "author_name": "Recent Author",
+ "updated_at": int(_ts(15).timestamp()),
+ "permission_keys": ["app.acl.monitor"],
+ "maintainer": "acct-1",
+ "icon_url": None,
+ }
+ ]
+ }
+ params = get_recent_apps.call_args.args[2]
+ assert params.limit == 8
+ assert "total" not in resp
+ assert "description" not in resp["data"][0]
+ assert "tags" not in resp["data"][0]
+ assert "workflow" not in resp["data"][0]
+
+
+@pytest.mark.parametrize("mode", ["channel", "rag-pipeline", "agent"])
+def test_recent_app_response_rejects_non_home_app_modes(app_module, mode: str) -> None:
+ with pytest.raises(ValidationError):
+ app_module.RecentAppResponse.model_validate(
+ {
+ "id": "app-1",
+ "name": "Recent App",
+ "mode": mode,
+ "updated_at": _ts(),
+ }
+ )
+
+
+def test_recent_app_list_api_applies_rbac_visibility_filter(app, app_module):
+ method = app_module.RecentAppListApi.get
+ while hasattr(method, "__wrapped__"):
+ method = method.__wrapped__
+
+ get_recent_apps = MagicMock(return_value=[])
+ with app.test_request_context("/apps/recent"):
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(dify_config, "RBAC_ENABLED", True)
+ monkeypatch.setattr(app_module.AppService, "get_recent_apps", get_recent_apps)
+ monkeypatch.setattr(
+ app_module.enterprise_rbac_service.RBACService.MyPermissions,
+ "get",
+ lambda tenant_id, account_id, session: app_module.enterprise_rbac_service.MyPermissionsResponse(
+ workspace=app_module.enterprise_rbac_service.WorkspacePermissionSnapshot(
+ permission_keys=["app.create_and_management"]
+ )
+ ),
+ )
+ monkeypatch.setattr(
+ app_module.enterprise_rbac_service.RBACService.AppAccess,
+ "whitelist_resources",
+ lambda tenant_id, account_id: SimpleNamespace(
+ unrestricted=False,
+ resource_ids=["app-shared"],
+ ),
+ )
+
+ resp, status = method(app_module.RecentAppListApi(), "tenant-1", "acct-1", MagicMock())
+
+ assert status == 200
+ assert resp == {"data": []}
+ params = get_recent_apps.call_args.args[2]
+ assert params.accessible_app_ids == ["app-shared"]
+ assert params.include_own_apps is True
+
+
def test_app_list_api_limits_to_apps_created_by_current_user_without_view_permission(app, app_module):
method = app_module.AppListApi.get
while hasattr(method, "__wrapped__"):
diff --git a/api/tests/unit_tests/services/test_app_service.py b/api/tests/unit_tests/services/test_app_service.py
index 9707855a6d7..1e2d033e6ef 100644
--- a/api/tests/unit_tests/services/test_app_service.py
+++ b/api/tests/unit_tests/services/test_app_service.py
@@ -1,19 +1,23 @@
from __future__ import annotations
from collections.abc import Callable
+from datetime import datetime
from types import SimpleNamespace
from typing import cast
from unittest.mock import MagicMock, patch
+from uuid import uuid4
import pytest
+from sqlalchemy import event
from sqlalchemy.exc import IntegrityError
+from sqlalchemy.orm import Session
from graphon.model_runtime.entities.model_entities import ModelType
from models import Account
-from models.model import App, AppMode, AppModelConfig
+from models.model import App, AppMode, AppModelConfig, IconType
from models.workflow import Workflow
from services.agent.errors import AgentNameConflictError
-from services.app_service import AppService, CreateAppParams
+from services.app_service import AppListParams, AppService, CreateAppParams
class TestCreateAppTransactionBoundary:
@@ -236,6 +240,92 @@ class TestOpenapiVisibilityHelpers:
mock_session.execute.assert_called_once()
+@pytest.mark.parametrize("sqlite_session", [(Account, App, AppModelConfig)], indirect=True)
+def test_get_recent_apps_uses_one_tenant_scoped_projection_query(sqlite_session: Session) -> None:
+ tenant_id = str(uuid4())
+ other_tenant_id = str(uuid4())
+ account = Account(name="Recent Apps Author", email="recent-apps@example.com")
+ sqlite_session.add(account)
+ sqlite_session.flush()
+
+ def create_app(*, name: str, tenant_id: str, updated_at: datetime, mode: AppMode = AppMode.CHAT) -> App:
+ app = App()
+ app.id = str(uuid4())
+ app.tenant_id = tenant_id
+ app.name = name
+ app.description = ""
+ app.mode = mode
+ app.icon_type = IconType.EMOJI
+ app.icon = "🚀"
+ app.icon_background = "#FFFFFF"
+ app.enable_site = False
+ app.enable_api = False
+ app.created_by = account.id
+ app.maintainer = account.id
+ app.created_at = updated_at
+ app.updated_at = updated_at
+ app.use_icon_as_answer_icon = False
+ return app
+
+ newest = create_app(name="Newest", tenant_id=tenant_id, updated_at=datetime(2026, 7, 3))
+ legacy_agent = AppModelConfig(app_id=newest.id)
+ legacy_agent.agent_mode = '{"enabled": true, "strategy": "react"}'
+ newest.app_model_config_id = legacy_agent.id
+ second = create_app(
+ name="Second",
+ tenant_id=tenant_id,
+ updated_at=datetime(2026, 7, 2),
+ mode=AppMode.WORKFLOW,
+ )
+ second.icon_type = None
+ second.icon = None
+ second.icon_background = None
+ second.created_by = None
+ second.maintainer = None
+ channel = create_app(
+ name="Channel",
+ tenant_id=tenant_id,
+ updated_at=datetime(2026, 7, 5),
+ mode=AppMode.CHANNEL,
+ )
+ rag_pipeline = create_app(
+ name="RAG Pipeline",
+ tenant_id=tenant_id,
+ updated_at=datetime(2026, 7, 4),
+ mode=AppMode.RAG_PIPELINE,
+ )
+ oldest = create_app(name="Oldest", tenant_id=tenant_id, updated_at=datetime(2026, 7, 1))
+ foreign = create_app(name="Foreign", tenant_id=other_tenant_id, updated_at=datetime(2026, 7, 4))
+ sqlite_session.add_all([newest, legacy_agent, second, channel, rag_pipeline, oldest, foreign])
+ sqlite_session.commit()
+
+ statements: list[str] = []
+ bind = sqlite_session.get_bind()
+
+ def record_sql(_conn, _cursor, statement, _parameters, _context, _executemany) -> None:
+ statements.append(statement)
+
+ event.listen(bind, "before_cursor_execute", record_sql)
+ try:
+ recent_apps = AppService().get_recent_apps(
+ account.id,
+ tenant_id,
+ AppListParams(limit=2),
+ sqlite_session,
+ )
+ finally:
+ event.remove(bind, "before_cursor_execute", record_sql)
+
+ assert [(app.name, app.mode, app.icon_type, app.author_name, app.maintainer) for app in recent_apps] == [
+ ("Newest", AppMode.CHAT, IconType.EMOJI, "Recent Apps Author", account.id),
+ ("Second", AppMode.WORKFLOW, None, None, None),
+ ]
+ select_statements = [statement for statement in statements if statement.lstrip().upper().startswith("SELECT")]
+ assert len(select_statements) == 1
+ assert "count(" not in select_statements[0].lower()
+ assert "app_model_configs" not in select_statements[0].lower()
+
+
class TestAppMeta:
def test_loads_workflow_with_caller_session(self):
session = MagicMock()
diff --git a/packages/contracts/generated/api/console/apps/orpc.gen.ts b/packages/contracts/generated/api/console/apps/orpc.gen.ts
index d479394c028..b84db3cbb4e 100644
--- a/packages/contracts/generated/api/console/apps/orpc.gen.ts
+++ b/packages/contracts/generated/api/console/apps/orpc.gen.ts
@@ -277,6 +277,8 @@ import {
zGetAppsImportsByAppIdCheckDependenciesPath,
zGetAppsImportsByAppIdCheckDependenciesResponse,
zGetAppsQuery,
+ zGetAppsRecentQuery,
+ zGetAppsRecentResponse,
zGetAppsResponse,
zGetAppsStarredQuery,
zGetAppsStarredResponse,
@@ -551,9 +553,31 @@ export const imports = {
}
/**
- * Get applications starred by the current account
+ * Return the lightweight app cards needed by the Explore home page
+ *
+ * Get recently modified apps for the home Continue Work section
*/
export const get2 = oc
+ .route({
+ description: 'Get recently modified apps for the home Continue Work section',
+ inputStructure: 'detailed',
+ method: 'GET',
+ operationId: 'getAppsRecent',
+ path: '/apps/recent',
+ summary: 'Return the lightweight app cards needed by the Explore home page',
+ tags: ['console'],
+ })
+ .input(z.object({ query: zGetAppsRecentQuery.optional() }))
+ .output(zGetAppsRecentResponse)
+
+export const recent = {
+ get: get2,
+}
+
+/**
+ * Get applications starred by the current account
+ */
+export const get3 = oc
.route({
description: 'Get applications starred by the current account',
inputStructure: 'detailed',
@@ -566,7 +590,7 @@ export const get2 = oc
.output(zGetAppsStarredResponse)
export const starred = {
- get: get2,
+ get: get3,
}
/**
@@ -597,7 +621,7 @@ export const workflows = {
*
* Get advanced chat workflow runs count statistics
*/
-export const get3 = oc
+export const get4 = oc
.route({
description: 'Get advanced chat workflow runs count statistics',
inputStructure: 'detailed',
@@ -616,7 +640,7 @@ export const get3 = oc
.output(zGetAppsByAppIdAdvancedChatWorkflowRunsCountResponse)
export const count = {
- get: get3,
+ get: get4,
}
/**
@@ -624,7 +648,7 @@ export const count = {
*
* Get advanced chat workflow run list
*/
-export const get4 = oc
+export const get5 = oc
.route({
description: 'Get advanced chat workflow run list',
inputStructure: 'detailed',
@@ -643,7 +667,7 @@ export const get4 = oc
.output(zGetAppsByAppIdAdvancedChatWorkflowRunsResponse)
export const workflowRuns = {
- get: get4,
+ get: get5,
count,
}
@@ -839,7 +863,7 @@ export const advancedChat = {
workflows: workflows2,
}
-export const get5 = oc
+export const get6 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -856,10 +880,10 @@ export const get5 = oc
.output(zGetAppsByAppIdAgentConfigFilesByNameDownloadResponse)
export const download = {
- get: get5,
+ get: get6,
}
-export const get6 = oc
+export const get7 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -876,7 +900,7 @@ export const get6 = oc
.output(zGetAppsByAppIdAgentConfigFilesByNamePreviewResponse)
export const preview2 = {
- get: get6,
+ get: get7,
}
export const delete_ = oc
@@ -901,7 +925,7 @@ export const byName = {
preview: preview2,
}
-export const get7 = oc
+export const get8 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -936,12 +960,12 @@ export const post9 = oc
.output(zPostAppsByAppIdAgentConfigFilesResponse)
export const files = {
- get: get7,
+ get: get8,
post: post9,
byName,
}
-export const get8 = oc
+export const get9 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -958,7 +982,7 @@ export const get8 = oc
.output(zGetAppsByAppIdAgentConfigManifestResponse)
export const manifest = {
- get: get8,
+ get: get9,
}
export const post10 = oc
@@ -983,7 +1007,7 @@ export const upload = {
post: post10,
}
-export const get9 = oc
+export const get10 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -1000,10 +1024,10 @@ export const get9 = oc
.output(zGetAppsByAppIdAgentConfigSkillsByNameDownloadResponse)
export const download2 = {
- get: get9,
+ get: get10,
}
-export const get10 = oc
+export const get11 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -1015,10 +1039,10 @@ export const get10 = oc
.output(zGetAppsByAppIdAgentConfigSkillsByNameFilesContentResponse)
export const content = {
- get: get10,
+ get: get11,
}
-export const get11 = oc
+export const get12 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -1035,10 +1059,10 @@ export const get11 = oc
.output(zGetAppsByAppIdAgentConfigSkillsByNameFilesDownloadResponse)
export const download3 = {
- get: get11,
+ get: get12,
}
-export const get12 = oc
+export const get13 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -1055,7 +1079,7 @@ export const get12 = oc
.output(zGetAppsByAppIdAgentConfigSkillsByNameFilesPreviewResponse)
export const preview3 = {
- get: get12,
+ get: get13,
}
export const files2 = {
@@ -1064,7 +1088,7 @@ export const files2 = {
preview: preview3,
}
-export const get13 = oc
+export const get14 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -1081,7 +1105,7 @@ export const get13 = oc
.output(zGetAppsByAppIdAgentConfigSkillsByNameInspectResponse)
export const inspect = {
- get: get13,
+ get: get14,
}
export const delete2 = oc
@@ -1107,7 +1131,7 @@ export const byName2 = {
inspect,
}
-export const get14 = oc
+export const get15 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -1124,7 +1148,7 @@ export const get14 = oc
.output(zGetAppsByAppIdAgentConfigSkillsResponse)
export const skills = {
- get: get14,
+ get: get15,
upload,
byName: byName2,
}
@@ -1138,7 +1162,7 @@ export const config = {
/**
* Time-limited external signed URL for one drive value (no streaming proxy)
*/
-export const get15 = oc
+export const get16 = oc
.route({
description: 'Time-limited external signed URL for one drive value (no streaming proxy)',
inputStructure: 'detailed',
@@ -1156,13 +1180,13 @@ export const get15 = oc
.output(zGetAppsByAppIdAgentDriveFilesDownloadResponse)
export const download4 = {
- get: get15,
+ get: get16,
}
/**
* Truncated text preview of one drive value (binary-safe; SKILL.md is the main case)
*/
-export const get16 = oc
+export const get17 = oc
.route({
description:
'Truncated text preview of one drive value (binary-safe; SKILL.md is the main case)',
@@ -1181,13 +1205,13 @@ export const get16 = oc
.output(zGetAppsByAppIdAgentDriveFilesPreviewResponse)
export const preview4 = {
- get: get16,
+ get: get17,
}
/**
* List agent drive entries (read-only inspector; one endpoint for both tabs)
*/
-export const get17 = oc
+export const get18 = oc
.route({
description: 'List agent drive entries (read-only inspector; one endpoint for both tabs)',
inputStructure: 'detailed',
@@ -1205,7 +1229,7 @@ export const get17 = oc
.output(zGetAppsByAppIdAgentDriveFilesResponse)
export const files3 = {
- get: get17,
+ get: get18,
download: download4,
preview: preview4,
}
@@ -1213,7 +1237,7 @@ export const files3 = {
/**
* Inspect one drive-backed skill for slash-menu hover/detail UI
*/
-export const get18 = oc
+export const get19 = oc
.route({
description: 'Inspect one drive-backed skill for slash-menu hover/detail UI',
inputStructure: 'detailed',
@@ -1231,7 +1255,7 @@ export const get18 = oc
.output(zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectResponse)
export const inspect2 = {
- get: get18,
+ get: get19,
}
export const bySkillPath = {
@@ -1241,7 +1265,7 @@ export const bySkillPath = {
/**
* List drive-backed skills for the bound agent
*/
-export const get19 = oc
+export const get20 = oc
.route({
description: 'List drive-backed skills for the bound agent',
inputStructure: 'detailed',
@@ -1259,7 +1283,7 @@ export const get19 = oc
.output(zGetAppsByAppIdAgentDriveSkillsResponse)
export const skills2 = {
- get: get19,
+ get: get20,
bySkillPath,
}
@@ -1323,7 +1347,7 @@ export const files4 = {
*
* Get agent execution logs for an application
*/
-export const get20 = oc
+export const get21 = oc
.route({
description: 'Get agent execution logs for an application',
inputStructure: 'detailed',
@@ -1337,7 +1361,7 @@ export const get20 = oc
.output(zGetAppsByAppIdAgentLogsResponse)
export const logs = {
- get: get20,
+ get: get21,
}
/**
@@ -1439,7 +1463,7 @@ export const agent = {
/**
* Get status of annotation reply action job
*/
-export const get21 = oc
+export const get22 = oc
.route({
description: 'Get status of annotation reply action job',
inputStructure: 'detailed',
@@ -1452,7 +1476,7 @@ export const get21 = oc
.output(zGetAppsByAppIdAnnotationReplyByActionStatusByJobIdResponse)
export const byJobId = {
- get: get21,
+ get: get22,
}
export const status = {
@@ -1491,7 +1515,7 @@ export const annotationReply = {
/**
* Get annotation settings for an app
*/
-export const get22 = oc
+export const get23 = oc
.route({
description: 'Get annotation settings for an app',
inputStructure: 'detailed',
@@ -1504,7 +1528,7 @@ export const get22 = oc
.output(zGetAppsByAppIdAnnotationSettingResponse)
export const annotationSetting = {
- get: get22,
+ get: get23,
}
/**
@@ -1557,7 +1581,7 @@ export const batchImport = {
/**
* Get status of batch import job
*/
-export const get23 = oc
+export const get24 = oc
.route({
description: 'Get status of batch import job',
inputStructure: 'detailed',
@@ -1570,7 +1594,7 @@ export const get23 = oc
.output(zGetAppsByAppIdAnnotationsBatchImportStatusByJobIdResponse)
export const byJobId2 = {
- get: get23,
+ get: get24,
}
export const batchImportStatus = {
@@ -1580,7 +1604,7 @@ export const batchImportStatus = {
/**
* Get count of message annotations for the app
*/
-export const get24 = oc
+export const get25 = oc
.route({
description: 'Get count of message annotations for the app',
inputStructure: 'detailed',
@@ -1593,13 +1617,13 @@ export const get24 = oc
.output(zGetAppsByAppIdAnnotationsCountResponse)
export const count2 = {
- get: get24,
+ get: get25,
}
/**
* Export all annotations for an app with CSV injection protection
*/
-export const get25 = oc
+export const get26 = oc
.route({
description: 'Export all annotations for an app with CSV injection protection',
inputStructure: 'detailed',
@@ -1612,13 +1636,13 @@ export const get25 = oc
.output(zGetAppsByAppIdAnnotationsExportResponse)
export const export_ = {
- get: get25,
+ get: get26,
}
/**
* Get hit histories for an annotation
*/
-export const get26 = oc
+export const get27 = oc
.route({
description: 'Get hit histories for an annotation',
inputStructure: 'detailed',
@@ -1636,7 +1660,7 @@ export const get26 = oc
.output(zGetAppsByAppIdAnnotationsByAnnotationIdHitHistoriesResponse)
export const hitHistories = {
- get: get26,
+ get: get27,
}
export const delete5 = oc
@@ -1692,7 +1716,7 @@ export const delete6 = oc
/**
* Get annotations for an app with pagination
*/
-export const get27 = oc
+export const get28 = oc
.route({
description: 'Get annotations for an app with pagination',
inputStructure: 'detailed',
@@ -1729,7 +1753,7 @@ export const post18 = oc
export const annotations = {
delete: delete6,
- get: get27,
+ get: get28,
post: post18,
batchImport,
batchImportStatus,
@@ -1797,7 +1821,7 @@ export const delete7 = oc
/**
* Get chat conversation details
*/
-export const get28 = oc
+export const get29 = oc
.route({
description: 'Get chat conversation details',
inputStructure: 'detailed',
@@ -1811,13 +1835,13 @@ export const get28 = oc
export const byConversationId = {
delete: delete7,
- get: get28,
+ get: get29,
}
/**
* Get chat conversations with pagination, filtering and summary
*/
-export const get29 = oc
+export const get30 = oc
.route({
description: 'Get chat conversations with pagination, filtering and summary',
inputStructure: 'detailed',
@@ -1835,14 +1859,14 @@ export const get29 = oc
.output(zGetAppsByAppIdChatConversationsResponse)
export const chatConversations = {
- get: get29,
+ get: get30,
byConversationId,
}
/**
* Get suggested questions for a message
*/
-export const get30 = oc
+export const get31 = oc
.route({
description: 'Get suggested questions for a message',
inputStructure: 'detailed',
@@ -1855,7 +1879,7 @@ export const get30 = oc
.output(zGetAppsByAppIdChatMessagesByMessageIdSuggestedQuestionsResponse)
export const suggestedQuestions = {
- get: get30,
+ get: get31,
}
export const byMessageId = {
@@ -1888,7 +1912,7 @@ export const byTaskId = {
/**
* Get chat messages for a conversation with pagination
*/
-export const get31 = oc
+export const get32 = oc
.route({
description: 'Get chat messages for a conversation with pagination',
inputStructure: 'detailed',
@@ -1903,7 +1927,7 @@ export const get31 = oc
.output(zGetAppsByAppIdChatMessagesResponse)
export const chatMessages = {
- get: get31,
+ get: get32,
byMessageId,
byTaskId,
}
@@ -1927,7 +1951,7 @@ export const delete8 = oc
/**
* Get completion conversation details with messages
*/
-export const get32 = oc
+export const get33 = oc
.route({
description: 'Get completion conversation details with messages',
inputStructure: 'detailed',
@@ -1941,13 +1965,13 @@ export const get32 = oc
export const byConversationId2 = {
delete: delete8,
- get: get32,
+ get: get33,
}
/**
* Get completion conversations with pagination and filtering
*/
-export const get33 = oc
+export const get34 = oc
.route({
description: 'Get completion conversations with pagination and filtering',
inputStructure: 'detailed',
@@ -1965,7 +1989,7 @@ export const get33 = oc
.output(zGetAppsByAppIdCompletionConversationsResponse)
export const completionConversations = {
- get: get33,
+ get: get34,
byConversationId: byConversationId2,
}
@@ -2020,7 +2044,7 @@ export const completionMessages = {
/**
* Get conversation variables for an application
*/
-export const get34 = oc
+export const get35 = oc
.route({
description: 'Get conversation variables for an application',
inputStructure: 'detailed',
@@ -2038,7 +2062,7 @@ export const get34 = oc
.output(zGetAppsByAppIdConversationVariablesResponse)
export const conversationVariables = {
- get: get34,
+ get: get35,
}
/**
@@ -2099,7 +2123,7 @@ export const copy = {
*
* Export application configuration as DSL
*/
-export const get35 = oc
+export const get36 = oc
.route({
description: 'Export application configuration as DSL',
inputStructure: 'detailed',
@@ -2115,13 +2139,13 @@ export const get35 = oc
.output(zGetAppsByAppIdExportResponse)
export const export2 = {
- get: get35,
+ get: get36,
}
/**
* Export user feedback data for Google Sheets
*/
-export const get36 = oc
+export const get37 = oc
.route({
description: 'Export user feedback data for Google Sheets',
inputStructure: 'detailed',
@@ -2139,7 +2163,7 @@ export const get36 = oc
.output(zGetAppsByAppIdFeedbacksExportResponse)
export const export3 = {
- get: get36,
+ get: get37,
}
/**
@@ -2184,7 +2208,7 @@ export const icon = {
/**
* Get message details by ID
*/
-export const get37 = oc
+export const get38 = oc
.route({
description: 'Get message details by ID',
inputStructure: 'detailed',
@@ -2197,7 +2221,7 @@ export const get37 = oc
.output(zGetAppsByAppIdMessagesByMessageIdResponse)
export const byMessageId2 = {
- get: get37,
+ get: get38,
}
export const messages = {
@@ -2269,7 +2293,7 @@ export const publishToCreatorsPlatform = {
/**
* Get MCP server configuration for an application
*/
-export const get38 = oc
+export const get39 = oc
.route({
description: 'Get MCP server configuration for an application',
inputStructure: 'detailed',
@@ -2313,7 +2337,7 @@ export const put = oc
.output(zPutAppsByAppIdServerResponse)
export const server = {
- get: get38,
+ get: get39,
post: post31,
put,
}
@@ -2414,7 +2438,7 @@ export const star = {
/**
* Get average response time statistics for an application
*/
-export const get39 = oc
+export const get40 = oc
.route({
description: 'Get average response time statistics for an application',
inputStructure: 'detailed',
@@ -2432,13 +2456,13 @@ export const get39 = oc
.output(zGetAppsByAppIdStatisticsAverageResponseTimeResponse)
export const averageResponseTime = {
- get: get39,
+ get: get40,
}
/**
* Get average session interaction statistics for an application
*/
-export const get40 = oc
+export const get41 = oc
.route({
description: 'Get average session interaction statistics for an application',
inputStructure: 'detailed',
@@ -2456,13 +2480,13 @@ export const get40 = oc
.output(zGetAppsByAppIdStatisticsAverageSessionInteractionsResponse)
export const averageSessionInteractions = {
- get: get40,
+ get: get41,
}
/**
* Get daily conversation statistics for an application
*/
-export const get41 = oc
+export const get42 = oc
.route({
description: 'Get daily conversation statistics for an application',
inputStructure: 'detailed',
@@ -2480,13 +2504,13 @@ export const get41 = oc
.output(zGetAppsByAppIdStatisticsDailyConversationsResponse)
export const dailyConversations = {
- get: get41,
+ get: get42,
}
/**
* Get daily terminal/end-user statistics for an application
*/
-export const get42 = oc
+export const get43 = oc
.route({
description: 'Get daily terminal/end-user statistics for an application',
inputStructure: 'detailed',
@@ -2504,13 +2528,13 @@ export const get42 = oc
.output(zGetAppsByAppIdStatisticsDailyEndUsersResponse)
export const dailyEndUsers = {
- get: get42,
+ get: get43,
}
/**
* Get daily message statistics for an application
*/
-export const get43 = oc
+export const get44 = oc
.route({
description: 'Get daily message statistics for an application',
inputStructure: 'detailed',
@@ -2528,13 +2552,13 @@ export const get43 = oc
.output(zGetAppsByAppIdStatisticsDailyMessagesResponse)
export const dailyMessages = {
- get: get43,
+ get: get44,
}
/**
* Get daily token cost statistics for an application
*/
-export const get44 = oc
+export const get45 = oc
.route({
description: 'Get daily token cost statistics for an application',
inputStructure: 'detailed',
@@ -2552,13 +2576,13 @@ export const get44 = oc
.output(zGetAppsByAppIdStatisticsTokenCostsResponse)
export const tokenCosts = {
- get: get44,
+ get: get45,
}
/**
* Get tokens per second statistics for an application
*/
-export const get45 = oc
+export const get46 = oc
.route({
description: 'Get tokens per second statistics for an application',
inputStructure: 'detailed',
@@ -2576,13 +2600,13 @@ export const get45 = oc
.output(zGetAppsByAppIdStatisticsTokensPerSecondResponse)
export const tokensPerSecond = {
- get: get45,
+ get: get46,
}
/**
* Get user satisfaction rate statistics for an application
*/
-export const get46 = oc
+export const get47 = oc
.route({
description: 'Get user satisfaction rate statistics for an application',
inputStructure: 'detailed',
@@ -2600,7 +2624,7 @@ export const get46 = oc
.output(zGetAppsByAppIdStatisticsUserSatisfactionRateResponse)
export const userSatisfactionRate = {
- get: get46,
+ get: get47,
}
export const statistics = {
@@ -2617,7 +2641,7 @@ export const statistics = {
/**
* Get available TTS voices for a specific language
*/
-export const get47 = oc
+export const get48 = oc
.route({
description: 'Get available TTS voices for a specific language',
inputStructure: 'detailed',
@@ -2635,7 +2659,7 @@ export const get47 = oc
.output(zGetAppsByAppIdTextToAudioVoicesResponse)
export const voices = {
- get: get47,
+ get: get48,
}
/**
@@ -2665,7 +2689,7 @@ export const textToAudio = {
*
* Get app tracing configuration
*/
-export const get48 = oc
+export const get49 = oc
.route({
description: 'Get app tracing configuration',
inputStructure: 'detailed',
@@ -2694,7 +2718,7 @@ export const post37 = oc
.output(zPostAppsByAppIdTraceResponse)
export const trace = {
- get: get48,
+ get: get49,
post: post37,
}
@@ -2725,7 +2749,7 @@ export const delete10 = oc
/**
* Get tracing configuration for an application
*/
-export const get49 = oc
+export const get50 = oc
.route({
description: 'Get tracing configuration for an application',
inputStructure: 'detailed',
@@ -2782,7 +2806,7 @@ export const post38 = oc
export const traceConfig = {
delete: delete10,
- get: get49,
+ get: get50,
patch,
post: post38,
}
@@ -2814,7 +2838,7 @@ export const triggerEnable = {
/**
* Get app triggers list
*/
-export const get50 = oc
+export const get51 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -2827,7 +2851,7 @@ export const get50 = oc
.output(zGetAppsByAppIdTriggersResponse)
export const triggers = {
- get: get50,
+ get: get51,
}
/**
@@ -2835,7 +2859,7 @@ export const triggers = {
*
* Get workflow application execution logs
*/
-export const get51 = oc
+export const get52 = oc
.route({
description: 'Get workflow application execution logs',
inputStructure: 'detailed',
@@ -2854,7 +2878,7 @@ export const get51 = oc
.output(zGetAppsByAppIdWorkflowAppLogsResponse)
export const workflowAppLogs = {
- get: get51,
+ get: get52,
}
/**
@@ -2862,7 +2886,7 @@ export const workflowAppLogs = {
*
* Get workflow archived execution logs
*/
-export const get52 = oc
+export const get53 = oc
.route({
description: 'Get workflow archived execution logs',
inputStructure: 'detailed',
@@ -2881,7 +2905,7 @@ export const get52 = oc
.output(zGetAppsByAppIdWorkflowArchivedLogsResponse)
export const workflowArchivedLogs = {
- get: get52,
+ get: get53,
}
/**
@@ -2889,7 +2913,7 @@ export const workflowArchivedLogs = {
*
* Get workflow runs count statistics
*/
-export const get53 = oc
+export const get54 = oc
.route({
description: 'Get workflow runs count statistics',
inputStructure: 'detailed',
@@ -2908,7 +2932,7 @@ export const get53 = oc
.output(zGetAppsByAppIdWorkflowRunsCountResponse)
export const count3 = {
- get: get53,
+ get: get54,
}
/**
@@ -2944,7 +2968,7 @@ export const tasks = {
/**
* Generate a download URL for an archived workflow run.
*/
-export const get54 = oc
+export const get55 = oc
.route({
description: 'Generate a download URL for an archived workflow run.',
inputStructure: 'detailed',
@@ -2957,7 +2981,7 @@ export const get54 = oc
.output(zGetAppsByAppIdWorkflowRunsByRunIdExportResponse)
export const export4 = {
- get: get54,
+ get: get55,
}
/**
@@ -2965,7 +2989,7 @@ export const export4 = {
*
* Get workflow run node execution list
*/
-export const get55 = oc
+export const get56 = oc
.route({
description: 'Get workflow run node execution list',
inputStructure: 'detailed',
@@ -2979,7 +3003,7 @@ export const get55 = oc
.output(zGetAppsByAppIdWorkflowRunsByRunIdNodeExecutionsResponse)
export const nodeExecutions = {
- get: get55,
+ get: get56,
}
/**
@@ -2987,7 +3011,7 @@ export const nodeExecutions = {
*
* Get workflow run detail
*/
-export const get56 = oc
+export const get57 = oc
.route({
description: 'Get workflow run detail',
inputStructure: 'detailed',
@@ -3001,7 +3025,7 @@ export const get56 = oc
.output(zGetAppsByAppIdWorkflowRunsByRunIdResponse)
export const byRunId = {
- get: get56,
+ get: get57,
export: export4,
nodeExecutions,
}
@@ -3009,7 +3033,7 @@ export const byRunId = {
/**
* Read a text/binary preview file in a workflow Agent node sandbox
*/
-export const get57 = oc
+export const get58 = oc
.route({
description: 'Read a text/binary preview file in a workflow Agent node sandbox',
inputStructure: 'detailed',
@@ -3027,7 +3051,7 @@ export const get57 = oc
.output(zGetAppsByAppIdWorkflowRunsByWorkflowRunIdAgentNodesByNodeIdSandboxFilesReadResponse)
export const read = {
- get: get57,
+ get: get58,
}
/**
@@ -3057,7 +3081,7 @@ export const upload3 = {
/**
* List a directory in a workflow Agent node sandbox
*/
-export const get58 = oc
+export const get59 = oc
.route({
description: 'List a directory in a workflow Agent node sandbox',
inputStructure: 'detailed',
@@ -3076,7 +3100,7 @@ export const get58 = oc
.output(zGetAppsByAppIdWorkflowRunsByWorkflowRunIdAgentNodesByNodeIdSandboxFilesResponse)
export const files5 = {
- get: get58,
+ get: get59,
read,
upload: upload3,
}
@@ -3102,7 +3126,7 @@ export const byWorkflowRunId = {
*
* Get workflow run list
*/
-export const get59 = oc
+export const get60 = oc
.route({
description: 'Get workflow run list',
inputStructure: 'detailed',
@@ -3121,7 +3145,7 @@ export const get59 = oc
.output(zGetAppsByAppIdWorkflowRunsResponse)
export const workflowRuns2 = {
- get: get59,
+ get: get60,
count: count3,
tasks,
byRunId,
@@ -3133,7 +3157,7 @@ export const workflowRuns2 = {
*
* Get all users in current tenant for mentions
*/
-export const get60 = oc
+export const get61 = oc
.route({
description: 'Get all users in current tenant for mentions',
inputStructure: 'detailed',
@@ -3147,7 +3171,7 @@ export const get60 = oc
.output(zGetAppsByAppIdWorkflowCommentsMentionUsersResponse)
export const mentionUsers = {
- get: get60,
+ get: get61,
}
/**
@@ -3272,7 +3296,7 @@ export const delete12 = oc
*
* Get a specific workflow comment
*/
-export const get61 = oc
+export const get62 = oc
.route({
description: 'Get a specific workflow comment',
inputStructure: 'detailed',
@@ -3310,7 +3334,7 @@ export const put3 = oc
export const byCommentId = {
delete: delete12,
- get: get61,
+ get: get62,
put: put3,
replies,
resolve,
@@ -3321,7 +3345,7 @@ export const byCommentId = {
*
* Get all comments for a workflow
*/
-export const get62 = oc
+export const get63 = oc
.route({
description: 'Get all comments for a workflow',
inputStructure: 'detailed',
@@ -3359,7 +3383,7 @@ export const post44 = oc
.output(zPostAppsByAppIdWorkflowCommentsResponse)
export const comments = {
- get: get62,
+ get: get63,
post: post44,
mentionUsers,
byCommentId,
@@ -3368,7 +3392,7 @@ export const comments = {
/**
* Get workflow average app interaction statistics
*/
-export const get63 = oc
+export const get64 = oc
.route({
description: 'Get workflow average app interaction statistics',
inputStructure: 'detailed',
@@ -3386,13 +3410,13 @@ export const get63 = oc
.output(zGetAppsByAppIdWorkflowStatisticsAverageAppInteractionsResponse)
export const averageAppInteractions = {
- get: get63,
+ get: get64,
}
/**
* Get workflow daily runs statistics
*/
-export const get64 = oc
+export const get65 = oc
.route({
description: 'Get workflow daily runs statistics',
inputStructure: 'detailed',
@@ -3410,13 +3434,13 @@ export const get64 = oc
.output(zGetAppsByAppIdWorkflowStatisticsDailyConversationsResponse)
export const dailyConversations2 = {
- get: get64,
+ get: get65,
}
/**
* Get workflow daily terminals statistics
*/
-export const get65 = oc
+export const get66 = oc
.route({
description: 'Get workflow daily terminals statistics',
inputStructure: 'detailed',
@@ -3434,13 +3458,13 @@ export const get65 = oc
.output(zGetAppsByAppIdWorkflowStatisticsDailyTerminalsResponse)
export const dailyTerminals = {
- get: get65,
+ get: get66,
}
/**
* Get workflow daily token cost statistics
*/
-export const get66 = oc
+export const get67 = oc
.route({
description: 'Get workflow daily token cost statistics',
inputStructure: 'detailed',
@@ -3458,7 +3482,7 @@ export const get66 = oc
.output(zGetAppsByAppIdWorkflowStatisticsTokenCostsResponse)
export const tokenCosts2 = {
- get: get66,
+ get: get67,
}
export const statistics2 = {
@@ -3478,7 +3502,7 @@ export const workflow = {
*
* Get default block configuration by type
*/
-export const get67 = oc
+export const get68 = oc
.route({
description: 'Get default block configuration by type',
inputStructure: 'detailed',
@@ -3497,7 +3521,7 @@ export const get67 = oc
.output(zGetAppsByAppIdWorkflowsDefaultWorkflowBlockConfigsByBlockTypeResponse)
export const byBlockType = {
- get: get67,
+ get: get68,
}
/**
@@ -3505,7 +3529,7 @@ export const byBlockType = {
*
* Get default block configurations for workflow
*/
-export const get68 = oc
+export const get69 = oc
.route({
description: 'Get default block configurations for workflow',
inputStructure: 'detailed',
@@ -3519,14 +3543,14 @@ export const get68 = oc
.output(zGetAppsByAppIdWorkflowsDefaultWorkflowBlockConfigsResponse)
export const defaultWorkflowBlockConfigs = {
- get: get68,
+ get: get69,
byBlockType,
}
/**
* Get conversation variables for workflow
*/
-export const get69 = oc
+export const get70 = oc
.route({
description: 'Get conversation variables for workflow',
inputStructure: 'detailed',
@@ -3559,7 +3583,7 @@ export const post45 = oc
.output(zPostAppsByAppIdWorkflowsDraftConversationVariablesResponse)
export const conversationVariables2 = {
- get: get69,
+ get: get70,
post: post45,
}
@@ -3568,7 +3592,7 @@ export const conversationVariables2 = {
*
* Get environment variables for workflow
*/
-export const get70 = oc
+export const get71 = oc
.route({
description: 'Get environment variables for workflow',
inputStructure: 'detailed',
@@ -3602,7 +3626,7 @@ export const post46 = oc
.output(zPostAppsByAppIdWorkflowsDraftEnvironmentVariablesResponse)
export const environmentVariables = {
- get: get70,
+ get: get71,
post: post46,
}
@@ -3807,7 +3831,7 @@ export const loop2 = {
nodes: nodes6,
}
-export const get71 = oc
+export const get72 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -3821,7 +3845,7 @@ export const get71 = oc
.output(zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesResponse)
export const candidates = {
- get: get71,
+ get: get72,
}
export const post53 = oc
@@ -3904,7 +3928,7 @@ export const validate = {
post: post56,
}
-export const get72 = oc
+export const get73 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -3937,7 +3961,7 @@ export const put4 = oc
.output(zPutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponse)
export const agentComposer = {
- get: get72,
+ get: get73,
put: put4,
candidates,
copyFromRoster,
@@ -3949,7 +3973,7 @@ export const agentComposer = {
/**
* Get last run result for draft workflow node
*/
-export const get73 = oc
+export const get74 = oc
.route({
description: 'Get last run result for draft workflow node',
inputStructure: 'detailed',
@@ -3962,7 +3986,7 @@ export const get73 = oc
.output(zGetAppsByAppIdWorkflowsDraftNodesByNodeIdLastRunResponse)
export const lastRun = {
- get: get73,
+ get: get74,
}
/**
@@ -4037,7 +4061,7 @@ export const delete13 = oc
/**
* Get variables for a specific node
*/
-export const get74 = oc
+export const get75 = oc
.route({
description: 'Get variables for a specific node',
inputStructure: 'detailed',
@@ -4051,7 +4075,7 @@ export const get74 = oc
export const variables = {
delete: delete13,
- get: get74,
+ get: get75,
}
export const byNodeId8 = {
@@ -4096,7 +4120,7 @@ export const run10 = {
/**
* Server-Sent Events stream of inspector deltas for a draft workflow run.
*/
-export const get75 = oc
+export const get76 = oc
.route({
description: 'Server-Sent Events stream of inspector deltas for a draft workflow run.',
inputStructure: 'detailed',
@@ -4109,13 +4133,13 @@ export const get75 = oc
.output(zGetAppsByAppIdWorkflowsDraftRunsByRunIdNodeOutputsEventsResponse)
export const events = {
- get: get75,
+ get: get76,
}
/**
* Full value for one declared output, including signed download URL for files.
*/
-export const get76 = oc
+export const get77 = oc
.route({
description: 'Full value for one declared output, including signed download URL for files.',
inputStructure: 'detailed',
@@ -4132,7 +4156,7 @@ export const get76 = oc
.output(zGetAppsByAppIdWorkflowsDraftRunsByRunIdNodeOutputsByNodeIdByOutputNamePreviewResponse)
export const preview6 = {
- get: get76,
+ get: get77,
}
export const byOutputName = {
@@ -4142,7 +4166,7 @@ export const byOutputName = {
/**
* One node's declared outputs for a draft workflow run.
*/
-export const get77 = oc
+export const get78 = oc
.route({
description: "One node's declared outputs for a draft workflow run.",
inputStructure: 'detailed',
@@ -4155,14 +4179,14 @@ export const get77 = oc
.output(zGetAppsByAppIdWorkflowsDraftRunsByRunIdNodeOutputsByNodeIdResponse)
export const byNodeId9 = {
- get: get77,
+ get: get78,
byOutputName,
}
/**
* Snapshot of every node's declared outputs for a draft workflow run.
*/
-export const get78 = oc
+export const get79 = oc
.route({
description: "Snapshot of every node's declared outputs for a draft workflow run.",
inputStructure: 'detailed',
@@ -4175,7 +4199,7 @@ export const get78 = oc
.output(zGetAppsByAppIdWorkflowsDraftRunsByRunIdNodeOutputsResponse)
export const nodeOutputs = {
- get: get78,
+ get: get79,
events,
byNodeId: byNodeId9,
}
@@ -4191,7 +4215,7 @@ export const runs = {
/**
* Get system variables for workflow
*/
-export const get79 = oc
+export const get80 = oc
.route({
description: 'Get system variables for workflow',
inputStructure: 'detailed',
@@ -4204,7 +4228,7 @@ export const get79 = oc
.output(zGetAppsByAppIdWorkflowsDraftSystemVariablesResponse)
export const systemVariables = {
- get: get79,
+ get: get80,
}
/**
@@ -4304,7 +4328,7 @@ export const delete14 = oc
/**
* Get a specific workflow variable
*/
-export const get80 = oc
+export const get81 = oc
.route({
description: 'Get a specific workflow variable',
inputStructure: 'detailed',
@@ -4338,7 +4362,7 @@ export const patch2 = oc
export const byVariableId = {
delete: delete14,
- get: get80,
+ get: get81,
patch: patch2,
reset,
}
@@ -4364,7 +4388,7 @@ export const delete15 = oc
*
* Get draft workflow variables
*/
-export const get81 = oc
+export const get82 = oc
.route({
description: 'Get draft workflow variables',
inputStructure: 'detailed',
@@ -4384,7 +4408,7 @@ export const get81 = oc
export const variables2 = {
delete: delete15,
- get: get81,
+ get: get82,
byVariableId,
}
@@ -4393,7 +4417,7 @@ export const variables2 = {
*
* Get draft workflow for an application
*/
-export const get82 = oc
+export const get83 = oc
.route({
description: 'Get draft workflow for an application',
inputStructure: 'detailed',
@@ -4430,7 +4454,7 @@ export const post62 = oc
.output(zPostAppsByAppIdWorkflowsDraftResponse)
export const draft2 = {
- get: get82,
+ get: get83,
post: post62,
conversationVariables: conversationVariables2,
environmentVariables,
@@ -4451,7 +4475,7 @@ export const draft2 = {
*
* Get published workflow for an application
*/
-export const get83 = oc
+export const get84 = oc
.route({
description: 'Get published workflow for an application',
inputStructure: 'detailed',
@@ -4485,14 +4509,14 @@ export const post63 = oc
.output(zPostAppsByAppIdWorkflowsPublishResponse)
export const publish = {
- get: get83,
+ get: get84,
post: post63,
}
/**
* Server-Sent Events stream of inspector deltas for a published workflow run.
*/
-export const get84 = oc
+export const get85 = oc
.route({
description: 'Server-Sent Events stream of inspector deltas for a published workflow run.',
inputStructure: 'detailed',
@@ -4505,13 +4529,13 @@ export const get84 = oc
.output(zGetAppsByAppIdWorkflowsPublishedRunsByRunIdNodeOutputsEventsResponse)
export const events2 = {
- get: get84,
+ get: get85,
}
/**
* Full value for one declared output of a published run.
*/
-export const get85 = oc
+export const get86 = oc
.route({
description: 'Full value for one declared output of a published run.',
inputStructure: 'detailed',
@@ -4532,7 +4556,7 @@ export const get85 = oc
)
export const preview7 = {
- get: get85,
+ get: get86,
}
export const byOutputName2 = {
@@ -4542,7 +4566,7 @@ export const byOutputName2 = {
/**
* One node's declared outputs for a published workflow run.
*/
-export const get86 = oc
+export const get87 = oc
.route({
description: "One node's declared outputs for a published workflow run.",
inputStructure: 'detailed',
@@ -4555,14 +4579,14 @@ export const get86 = oc
.output(zGetAppsByAppIdWorkflowsPublishedRunsByRunIdNodeOutputsByNodeIdResponse)
export const byNodeId10 = {
- get: get86,
+ get: get87,
byOutputName: byOutputName2,
}
/**
* Snapshot of every node's declared outputs for a published workflow run.
*/
-export const get87 = oc
+export const get88 = oc
.route({
description: "Snapshot of every node's declared outputs for a published workflow run.",
inputStructure: 'detailed',
@@ -4575,7 +4599,7 @@ export const get87 = oc
.output(zGetAppsByAppIdWorkflowsPublishedRunsByRunIdNodeOutputsResponse)
export const nodeOutputs2 = {
- get: get87,
+ get: get88,
events: events2,
byNodeId: byNodeId10,
}
@@ -4595,7 +4619,7 @@ export const published = {
/**
* Get webhook trigger for a node
*/
-export const get88 = oc
+export const get89 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@@ -4613,7 +4637,7 @@ export const get88 = oc
.output(zGetAppsByAppIdWorkflowsTriggersWebhookResponse)
export const webhook = {
- get: get88,
+ get: get89,
}
export const triggers2 = {
@@ -4689,7 +4713,7 @@ export const byWorkflowId = {
*
* Get all published workflows for an application
*/
-export const get89 = oc
+export const get90 = oc
.route({
description: 'Get all published workflows for an application',
inputStructure: 'detailed',
@@ -4708,7 +4732,7 @@ export const get89 = oc
.output(zGetAppsByAppIdWorkflowsResponse)
export const workflows3 = {
- get: get89,
+ get: get90,
defaultWorkflowBlockConfigs,
draft: draft2,
publish,
@@ -4741,7 +4765,7 @@ export const delete17 = oc
*
* Get application details
*/
-export const get90 = oc
+export const get91 = oc
.route({
description: 'Get application details',
inputStructure: 'detailed',
@@ -4774,7 +4798,7 @@ export const put6 = oc
export const byAppId2 = {
delete: delete17,
- get: get90,
+ get: get91,
put: put6,
advancedChat,
agent,
@@ -4843,7 +4867,7 @@ export const byApiKeyId = {
*
* Get all API keys for an app
*/
-export const get91 = oc
+export const get92 = oc
.route({
description: 'Get all API keys for an app',
inputStructure: 'detailed',
@@ -4876,7 +4900,7 @@ export const post65 = oc
.output(zPostAppsByResourceIdApiKeysResponse)
export const apiKeys = {
- get: get91,
+ get: get92,
post: post65,
byApiKeyId,
}
@@ -4888,7 +4912,7 @@ export const byResourceId = {
/**
* Refresh MCP server configuration and regenerate server code
*/
-export const get92 = oc
+export const get93 = oc
.route({
description: 'Refresh MCP server configuration and regenerate server code',
inputStructure: 'detailed',
@@ -4901,7 +4925,7 @@ export const get92 = oc
.output(zGetAppsByServerIdServerRefreshResponse)
export const refresh = {
- get: get92,
+ get: get93,
}
export const server2 = {
@@ -4917,7 +4941,7 @@ export const byServerId = {
*
* Get list of applications with pagination and filtering
*/
-export const get93 = oc
+export const get94 = oc
.route({
description: 'Get list of applications with pagination and filtering',
inputStructure: 'detailed',
@@ -4950,9 +4974,10 @@ export const post66 = oc
.output(zPostAppsResponse)
export const apps = {
- get: get93,
+ get: get94,
post: post66,
imports,
+ recent,
starred,
workflows,
byAppId: byAppId2,
diff --git a/packages/contracts/generated/api/console/apps/types.gen.ts b/packages/contracts/generated/api/console/apps/types.gen.ts
index abf5b16538a..c8bd2dfe64e 100644
--- a/packages/contracts/generated/api/console/apps/types.gen.ts
+++ b/packages/contracts/generated/api/console/apps/types.gen.ts
@@ -80,6 +80,10 @@ export type CheckDependenciesResult = {
leaked_dependencies?: Array
}
+export type RecentAppListResponse = {
+ data: Array
+}
+
export type WorkflowOnlineUsersPayload = {
app_ids?: Array
}
@@ -1408,6 +1412,20 @@ export type PluginDependency = {
value: Github | Marketplace | Package
}
+export type RecentAppResponse = {
+ author_name?: string | null
+ icon?: string | null
+ icon_background?: string | null
+ icon_type?: IconType | null
+ readonly icon_url: string | null
+ id: string
+ maintainer?: string | null
+ mode: 'advanced-chat' | 'agent-chat' | 'chat' | 'completion' | 'workflow'
+ name: string
+ permission_keys?: Array
+ updated_at: number
+}
+
export type WorkflowOnlineUsersByApp = {
app_id: string
users: Array
@@ -3114,6 +3132,10 @@ export type AppDetailWithSiteWritable = {
workflow?: WorkflowPartial | null
}
+export type RecentAppListResponseWritable = {
+ data: Array
+}
+
export type GeneratedAppResponseWritable = JsonValue
export type WorkflowCommentBasicListWritable = {
@@ -3196,6 +3218,19 @@ export type AppDetailSiteResponseWritable = {
use_icon_as_answer_icon?: boolean | null
}
+export type RecentAppResponseWritable = {
+ author_name?: string | null
+ icon?: string | null
+ icon_background?: string | null
+ icon_type?: IconType | null
+ id: string
+ maintainer?: string | null
+ mode: 'advanced-chat' | 'agent-chat' | 'chat' | 'completion' | 'workflow'
+ name: string
+ permission_keys?: Array
+ updated_at: number
+}
+
export type WorkflowCommentBasicWritable = {
content: string
created_at?: number | null
@@ -3356,6 +3391,21 @@ export type PostAppsImportsByImportIdConfirmResponses = {
export type PostAppsImportsByImportIdConfirmResponse =
PostAppsImportsByImportIdConfirmResponses[keyof PostAppsImportsByImportIdConfirmResponses]
+export type GetAppsRecentData = {
+ body?: never
+ path?: never
+ query?: {
+ limit?: number
+ }
+ url: '/apps/recent'
+}
+
+export type GetAppsRecentResponses = {
+ 200: RecentAppListResponse
+}
+
+export type GetAppsRecentResponse = GetAppsRecentResponses[keyof GetAppsRecentResponses]
+
export type GetAppsStarredData = {
body?: never
path?: never
diff --git a/packages/contracts/generated/api/console/apps/zod.gen.ts b/packages/contracts/generated/api/console/apps/zod.gen.ts
index 88dcfc13682..534a11ff431 100644
--- a/packages/contracts/generated/api/console/apps/zod.gen.ts
+++ b/packages/contracts/generated/api/console/apps/zod.gen.ts
@@ -1076,6 +1076,30 @@ export const zAppImportResponse = z.object({
warnings: z.array(zDslImportWarning).optional(),
})
+/**
+ * RecentAppResponse
+ */
+export const zRecentAppResponse = z.object({
+ author_name: z.string().nullish(),
+ icon: z.string().nullish(),
+ icon_background: z.string().nullish(),
+ icon_type: zIconType.nullish(),
+ icon_url: z.string().nullable(),
+ id: z.string(),
+ maintainer: z.string().nullish(),
+ mode: z.enum(['advanced-chat', 'agent-chat', 'chat', 'completion', 'workflow']),
+ name: z.string(),
+ permission_keys: z.array(z.string()).optional(),
+ updated_at: z.int(),
+})
+
+/**
+ * RecentAppListResponse
+ */
+export const zRecentAppListResponse = z.object({
+ data: z.array(zRecentAppResponse),
+})
+
export const zJsonValue = z
.union([
z.string(),
@@ -4279,6 +4303,29 @@ export const zAppDetailWithSiteWritable = z.object({
workflow: zWorkflowPartial.nullish(),
})
+/**
+ * RecentAppResponse
+ */
+export const zRecentAppResponseWritable = z.object({
+ author_name: z.string().nullish(),
+ icon: z.string().nullish(),
+ icon_background: z.string().nullish(),
+ icon_type: zIconType.nullish(),
+ id: z.string(),
+ maintainer: z.string().nullish(),
+ mode: z.enum(['advanced-chat', 'agent-chat', 'chat', 'completion', 'workflow']),
+ name: z.string(),
+ permission_keys: z.array(z.string()).optional(),
+ updated_at: z.int(),
+})
+
+/**
+ * RecentAppListResponse
+ */
+export const zRecentAppListResponseWritable = z.object({
+ data: z.array(zRecentAppResponseWritable),
+})
+
/**
* AccountWithRoleResponse
*/
@@ -4442,6 +4489,15 @@ export const zPostAppsImportsByImportIdConfirmPath = z.object({
*/
export const zPostAppsImportsByImportIdConfirmResponse = zImport
+export const zGetAppsRecentQuery = z.object({
+ limit: z.int().gte(1).lte(8).optional().default(8),
+})
+
+/**
+ * Success
+ */
+export const zGetAppsRecentResponse = zRecentAppListResponse
+
export const zGetAppsStarredQuery = z.object({
creator_ids: z.array(z.string()).optional(),
is_created_by_me: z.boolean().optional(),
diff --git a/web/app/components/explore/app-list/__tests__/index.spec.tsx b/web/app/components/explore/app-list/__tests__/index.spec.tsx
index aa034c60408..8b61e29cfbc 100644
--- a/web/app/components/explore/app-list/__tests__/index.spec.tsx
+++ b/web/app/components/explore/app-list/__tests__/index.spec.tsx
@@ -1,3 +1,4 @@
+import type { RecentAppResponse } from '@dify/contracts/api/console/apps/types.gen'
import type {
StepByStepTourStatePatchPayload,
StepByStepTourStateResponse,
@@ -9,7 +10,6 @@ import type { CreateAppModalProps } from '@/app/components/explore/create-app-mo
import type { StepByStepTourSessionState } from '@/app/components/step-by-step-tour/types'
import type { Banner as BannerType } from '@/models/app'
import type { App } from '@/models/explore'
-import type { App as WorkspaceApp } from '@/types/app'
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { createStore, Provider as JotaiProvider, useSetAtom } from 'jotai'
@@ -57,7 +57,7 @@ let mockExploreData: { categories: string[]; allList: App[] } | undefined = {
}
let mockLearnDifyApps: App[] = []
let mockLearnDifyLoading = false
-let mockWorkspaceApps: WorkspaceApp[] = []
+let mockWorkspaceApps: RecentAppResponse[] = []
let mockWorkspaceAppsLoading = false
let mockBanners: BannerType[] = []
let mockBannersLoading = false
@@ -67,6 +67,10 @@ const mockHandleImportDSL = vi.fn()
const mockHandleImportDSLConfirm = vi.fn()
const mockTrackCreateApp = vi.fn()
const mockTrackEvent = vi.hoisted(() => vi.fn())
+const mockAppQueries = vi.hoisted(() => ({
+ listQueryOptions: vi.fn(),
+ recentQueryOptions: vi.fn(),
+}))
const mockStepByStepTour = vi.hoisted(() => {
const stateQueryKey = ['console', 'onboarding', 'step-by-step-tour', 'state'] as const
const createState = (
@@ -242,13 +246,14 @@ vi.mock('@/service/client', () => ({
queryOptions: (options: {
input?: { query?: { limit?: number } }
select?: (response: {
- data: WorkspaceApp[]
+ data: RecentAppResponse[]
has_more: boolean
limit: number
page: number
total: number
}) => unknown
}) => {
+ mockAppQueries.listQueryOptions(options)
const limit = options.input?.query?.limit ?? mockWorkspaceApps.length
if (mockWorkspaceAppsLoading) {
return {
@@ -272,6 +277,33 @@ vi.mock('@/service/client', () => ({
}
},
},
+ recent: {
+ get: {
+ queryOptions: (options: {
+ input?: { query?: { limit?: number } }
+ select?: (response: { data: RecentAppResponse[] }) => unknown
+ }) => {
+ mockAppQueries.recentQueryOptions(options)
+ const limit = options.input?.query?.limit ?? mockWorkspaceApps.length
+ if (mockWorkspaceAppsLoading) {
+ return {
+ queryKey: ['console', 'apps', 'recent', 'get', options],
+ queryFn: () => new Promise(() => {}),
+ select: options.select,
+ }
+ }
+ const response = {
+ data: mockWorkspaceApps.slice(0, limit),
+ }
+ return {
+ queryKey: ['console', 'apps', 'recent', 'get', options],
+ queryFn: () => Promise.resolve(response),
+ initialData: response,
+ select: options.select,
+ }
+ },
+ },
+ },
},
onboarding: {
stepByStepTour: {
@@ -455,33 +487,19 @@ const createApp = (overrides: Partial = {}): App => ({
is_agent: overrides.is_agent ?? false,
})
-const createWorkspaceApp = (overrides: Partial = {}): WorkspaceApp =>
- ({
- id: overrides.id ?? 'workspace-app-1',
- name: overrides.name ?? 'Workspace App',
- description: overrides.description ?? 'Workspace app description',
- author_name: overrides.author_name ?? 'Evan',
- icon_type: overrides.icon_type ?? 'emoji',
- icon: overrides.icon ?? '😀',
- icon_background: overrides.icon_background ?? '#fff',
- icon_url: overrides.icon_url ?? null,
- use_icon_as_answer_icon: overrides.use_icon_as_answer_icon ?? false,
- mode: overrides.mode ?? AppModeEnum.CHAT,
- created_at: overrides.created_at ?? 1704067200,
- updated_at: overrides.updated_at ?? 1704153600,
- enable_site: overrides.enable_site ?? false,
- enable_api: overrides.enable_api ?? false,
- api_rpm: overrides.api_rpm ?? 60,
- api_rph: overrides.api_rph ?? 3600,
- is_demo: overrides.is_demo ?? false,
- model_config: overrides.model_config,
- app_model_config: overrides.app_model_config,
- site: overrides.site,
- api_base_url: overrides.api_base_url ?? '',
- tags: overrides.tags ?? [],
- access_mode: overrides.access_mode,
- permission_keys: overrides.permission_keys,
- }) as WorkspaceApp
+const createWorkspaceApp = (overrides: Partial = {}): RecentAppResponse => ({
+ id: overrides.id ?? 'workspace-app-1',
+ name: overrides.name ?? 'Workspace App',
+ author_name: overrides.author_name ?? 'Evan',
+ icon_type: overrides.icon_type ?? 'emoji',
+ icon: overrides.icon ?? '😀',
+ icon_background: overrides.icon_background ?? '#fff',
+ icon_url: overrides.icon_url ?? null,
+ mode: overrides.mode ?? 'chat',
+ updated_at: overrides.updated_at ?? 1704153600,
+ maintainer: overrides.maintainer ?? 'user-1',
+ permission_keys: overrides.permission_keys,
+})
const createBanner = (overrides: Partial = {}): BannerType => ({
id: overrides.id ?? 'banner-1',
@@ -748,6 +766,27 @@ describe('AppList', () => {
).toHaveAttribute('href', '/apps')
})
+ it('should load continue work from the lightweight recent apps query', () => {
+ mockExploreData = {
+ categories: ['Writing'],
+ allList: [createApp()],
+ }
+ mockWorkspaceApps = [createWorkspaceApp()]
+
+ renderAppList()
+
+ expect(mockAppQueries.recentQueryOptions).toHaveBeenCalledWith(
+ expect.objectContaining({
+ input: {
+ query: {
+ limit: 8,
+ },
+ },
+ }),
+ )
+ expect(mockAppQueries.listQueryOptions).not.toHaveBeenCalled()
+ })
+
it('should render preview-only continue work app as a dimmed card and warn on click', () => {
mockExploreData = {
categories: ['Writing'],
diff --git a/web/app/components/explore/app-list/explore-recommendations.tsx b/web/app/components/explore/app-list/explore-recommendations.tsx
index 8f8c9f91216..2b653007e3b 100644
--- a/web/app/components/explore/app-list/explore-recommendations.tsx
+++ b/web/app/components/explore/app-list/explore-recommendations.tsx
@@ -1,7 +1,7 @@
'use client'
+import type { RecentAppResponse } from '@dify/contracts/api/console/apps/types.gen'
import type { App } from '@/models/explore'
-import type { App as WorkspaceApp } from '@/types/app'
import type { TryAppSelection } from '@/types/try-app'
import ContinueWork from '@/app/components/explore/continue-work'
import { STEP_BY_STEP_TOUR_TARGETS } from '@/app/components/step-by-step-tour/target-registry'
@@ -17,7 +17,7 @@ export function ExploreRecommendations({
onTry,
}: {
canCreate: boolean
- continueWorkApps: WorkspaceApp[]
+ continueWorkApps: RecentAppResponse[]
forceShowLearnDify?: boolean
onCreate: (app: App) => void
onTry: (params: TryAppSelection) => void
diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx
index ed564cef852..62259226f30 100644
--- a/web/app/components/explore/app-list/index.tsx
+++ b/web/app/components/explore/app-list/index.tsx
@@ -1,10 +1,10 @@
'use client'
+import type { RecentAppResponse } from '@dify/contracts/api/console/apps/types.gen'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import type { StepByStepTourTaskId } from '@/app/components/step-by-step-tour/types'
import type { Banner as BannerType } from '@/models/app'
import type { App } from '@/models/explore'
-import type { App as WorkspaceApp } from '@/types/app'
import type { TryAppSelection } from '@/types/try-app'
import type { TrackCreateAppParams } from '@/utils/create-app-tracking'
import { cn } from '@langgenius/dify-ui/cn'
@@ -39,7 +39,6 @@ import { DSLImportMode } from '@/models/app'
import dynamic from '@/next/dynamic'
import { consoleQuery } from '@/service/client'
import { fetchAppDetail, fetchAppList, fetchBanners } from '@/service/explore'
-import { normalizeAppPagination } from '@/service/use-apps'
import { trackCreateApp } from '@/utils/create-app-tracking'
import { hasPermission } from '@/utils/permission'
import { ExploreAppListHeader } from './explore-app-list-header'
@@ -61,9 +60,7 @@ type ExploreAppListData = {
const homeContinueWorkAppsInput = {
query: {
- page: 1,
limit: 8,
- name: '',
},
}
@@ -91,9 +88,9 @@ function getExploreAppListQueryOptions(locale?: string) {
}
function getContinueWorkAppsQueryOptions() {
- return consoleQuery.apps.get.queryOptions({
+ return consoleQuery.apps.recent.get.queryOptions({
input: homeContinueWorkAppsInput,
- select: (response): WorkspaceApp[] => normalizeAppPagination(response).data,
+ select: (response): RecentAppResponse[] => response.data,
})
}
diff --git a/web/app/components/explore/continue-work/__tests__/item.spec.tsx b/web/app/components/explore/continue-work/__tests__/item.spec.tsx
index be97f56a1f3..053691f6025 100644
--- a/web/app/components/explore/continue-work/__tests__/item.spec.tsx
+++ b/web/app/components/explore/continue-work/__tests__/item.spec.tsx
@@ -1,9 +1,7 @@
+import type { RecentAppResponse } from '@dify/contracts/api/console/apps/types.gen'
import type { AnchorHTMLAttributes, ReactNode } from 'react'
-import type { App } from '@/types/app'
import { fireEvent, screen } from '@testing-library/react'
-import { AccessMode } from '@/models/access-control'
import { renderWithConsoleQuery } from '@/test/console/query-data'
-import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import ContinueWorkItem from '../item'
@@ -52,37 +50,23 @@ vi.mock('@/next/link', () => ({
),
}))
-const createApp = (overrides: Partial = {}): App => ({
+const createApp = (overrides: Partial = {}): RecentAppResponse => ({
id: 'app-1',
name: 'Continue App',
- description: 'Continue app description',
author_name: 'Alice',
icon_type: 'emoji',
icon: '🤖',
icon_background: '#FFEAD5',
icon_url: null,
- use_icon_as_answer_icon: false,
- mode: AppModeEnum.CHAT,
- enable_site: false,
- enable_api: false,
- api_rpm: 60,
- api_rph: 3600,
- is_demo: false,
- model_config: {} as App['model_config'],
- app_model_config: {} as App['app_model_config'],
- created_at: 100,
+ mode: 'chat',
maintainer: 'maintainer-1',
updated_at: 200,
- site: {} as App['site'],
- api_base_url: '',
- tags: [],
- access_mode: AccessMode.PUBLIC,
permission_keys: [AppACLPermission.Edit],
...overrides,
})
const renderItem = (
- app: App,
+ app: RecentAppResponse,
systemFeatures: NonNullable[1]>['systemFeatures'] = {
rbac_enabled: true,
},
@@ -109,12 +93,6 @@ describe('ContinueWorkItem', () => {
expect(mockFormatTimeFromNow).toHaveBeenCalledWith(200000)
})
- it('should use created time when updated time is missing', () => {
- renderItem(createApp({ updated_at: 0, created_at: 123 }))
-
- expect(mockFormatTimeFromNow).toHaveBeenCalledWith(123000)
- })
-
it('should link to access config when RBAC is enabled and only access config permission is available', () => {
renderItem(createApp({ permission_keys: [AppACLPermission.AccessConfig] }))
diff --git a/web/app/components/explore/continue-work/index.tsx b/web/app/components/explore/continue-work/index.tsx
index 8f13cb04028..13a51d8211f 100644
--- a/web/app/components/explore/continue-work/index.tsx
+++ b/web/app/components/explore/continue-work/index.tsx
@@ -1,6 +1,6 @@
'use client'
-import type { App as WorkspaceApp } from '@/types/app'
+import type { RecentAppResponse } from '@dify/contracts/api/console/apps/types.gen'
import { cn } from '@langgenius/dify-ui/cn'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
@@ -8,7 +8,7 @@ import Link from '@/next/link'
import ContinueWorkItem from './item'
type ContinueWorkProps = {
- apps: WorkspaceApp[]
+ apps: RecentAppResponse[]
className?: string
}
diff --git a/web/app/components/explore/continue-work/item.tsx b/web/app/components/explore/continue-work/item.tsx
index 0dd8629e6e8..09f217c0309 100644
--- a/web/app/components/explore/continue-work/item.tsx
+++ b/web/app/components/explore/continue-work/item.tsx
@@ -1,6 +1,6 @@
'use client'
-import type { App } from '@/types/app'
+import type { RecentAppResponse } from '@dify/contracts/api/console/apps/types.gen'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import { useSuspenseQuery } from '@tanstack/react-query'
@@ -18,7 +18,7 @@ import { getRedirectionPath } from '@/utils/app-redirection'
import { hasOnlyAppPreviewPermission } from '@/utils/permission'
type ContinueWorkItemProps = {
- app: App
+ app: RecentAppResponse
}
const ContinueWorkItem = ({ app }: ContinueWorkItemProps) => {
@@ -28,7 +28,7 @@ const ContinueWorkItem = ({ app }: ContinueWorkItemProps) => {
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
const isRbacEnabled = systemFeatures.rbac_enabled
- const updatedAt = (app.updated_at || app.created_at) * 1000
+ const updatedAt = app.updated_at * 1000
const isPreviewOnly = hasOnlyAppPreviewPermission(app.permission_keys)
const href = getRedirectionPath(app, {
currentUserId,
@@ -58,7 +58,7 @@ const ContinueWorkItem = ({ app }: ContinueWorkItemProps) => {