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) => {