mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
refactor(web): remove custom console contract loaders (#38284)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
4fc8f0ddd1
commit
61650d34ce
14
.github/workflows/autofix.yml
vendored
14
.github/workflows/autofix.yml
vendored
@ -51,6 +51,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
api/**
|
api/**
|
||||||
|
- name: Check frontend contract inputs
|
||||||
|
if: github.event_name != 'merge_group'
|
||||||
|
id: frontend-contract-changes
|
||||||
|
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
api/**
|
||||||
|
packages/contracts/openapi-ts.api.config.ts
|
||||||
|
packages/contracts/package.json
|
||||||
|
packages/contracts/openapi/**
|
||||||
|
pnpm-lock.yaml
|
||||||
|
pnpm-workspace.yaml
|
||||||
- name: Check dify-agent inputs
|
- name: Check dify-agent inputs
|
||||||
if: github.event_name != 'merge_group'
|
if: github.event_name != 'merge_group'
|
||||||
id: dify-agent-changes
|
id: dify-agent-changes
|
||||||
@ -143,7 +155,7 @@ jobs:
|
|||||||
uv run dev/generate_swagger_markdown_docs.py --swagger-dir ../packages/contracts/openapi --markdown-dir openapi/markdown --keep-swagger-json
|
uv run dev/generate_swagger_markdown_docs.py --swagger-dir ../packages/contracts/openapi --markdown-dir openapi/markdown --keep-swagger-json
|
||||||
|
|
||||||
- name: Generate frontend contracts
|
- name: Generate frontend contracts
|
||||||
if: github.event_name != 'merge_group' && steps.api-changes.outputs.any_changed == 'true'
|
if: github.event_name != 'merge_group' && steps.frontend-contract-changes.outputs.any_changed == 'true'
|
||||||
run: pnpm --dir packages/contracts gen-api-contract-from-openapi
|
run: pnpm --dir packages/contracts gen-api-contract-from-openapi
|
||||||
|
|
||||||
- name: ESLint autofix
|
- name: ESLint autofix
|
||||||
|
|||||||
@ -147,11 +147,15 @@ register_schema_models(
|
|||||||
InstalledAppCreatePayload,
|
InstalledAppCreatePayload,
|
||||||
InstalledAppUpdatePayload,
|
InstalledAppUpdatePayload,
|
||||||
InstalledAppsListQuery,
|
InstalledAppsListQuery,
|
||||||
|
)
|
||||||
|
register_response_schema_models(
|
||||||
|
console_ns,
|
||||||
InstalledAppInfoResponse,
|
InstalledAppInfoResponse,
|
||||||
InstalledAppResponse,
|
InstalledAppResponse,
|
||||||
InstalledAppListResponse,
|
InstalledAppListResponse,
|
||||||
|
SimpleMessageResponse,
|
||||||
|
SimpleResultMessageResponse,
|
||||||
)
|
)
|
||||||
register_response_schema_models(console_ns, SimpleMessageResponse, SimpleResultMessageResponse)
|
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/installed-apps")
|
@console_ns.route("/installed-apps")
|
||||||
|
|||||||
@ -13,7 +13,12 @@ from services.app_service import AppService
|
|||||||
|
|
||||||
|
|
||||||
class ExploreAppMetaResponse(BaseModel):
|
class ExploreAppMetaResponse(BaseModel):
|
||||||
tool_icons: dict[str, Any] = Field(default_factory=dict)
|
"""Metadata consumed by the installed-app chat UI.
|
||||||
|
|
||||||
|
Built-in tool icons are URL strings; API-based tool icons are provider-defined payload objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tool_icons: dict[str, str | dict[str, Any]] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
register_response_schema_models(console_ns, fields.Parameters, ExploreAppMetaResponse)
|
register_response_schema_models(console_ns, fields.Parameters, ExploreAppMetaResponse)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field, computed_field, field_validator
|
from pydantic import BaseModel, Field, RootModel, computed_field, field_validator
|
||||||
|
|
||||||
from constants.languages import languages
|
from constants.languages import languages
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
@ -80,15 +80,23 @@ class RecommendedAppDetailResponse(ResponseModel):
|
|||||||
can_trial: bool | None = None
|
can_trial: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class RecommendedAppDetailNullableResponse(RootModel[RecommendedAppDetailResponse | None]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
register_schema_models(
|
register_schema_models(
|
||||||
console_ns,
|
console_ns,
|
||||||
RecommendedAppsQuery,
|
RecommendedAppsQuery,
|
||||||
|
)
|
||||||
|
register_response_schema_models(
|
||||||
|
console_ns,
|
||||||
RecommendedAppInfoResponse,
|
RecommendedAppInfoResponse,
|
||||||
RecommendedAppResponse,
|
RecommendedAppResponse,
|
||||||
RecommendedAppListResponse,
|
RecommendedAppListResponse,
|
||||||
LearnDifyAppListResponse,
|
LearnDifyAppListResponse,
|
||||||
|
RecommendedAppDetailResponse,
|
||||||
|
RecommendedAppDetailNullableResponse,
|
||||||
)
|
)
|
||||||
register_response_schema_models(console_ns, RecommendedAppDetailResponse)
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_language(language: str | None, user: Account) -> str:
|
def _resolve_language(language: str | None, user: Account) -> str:
|
||||||
@ -136,7 +144,7 @@ class LearnDifyAppListApi(Resource):
|
|||||||
|
|
||||||
@console_ns.route("/explore/apps/<uuid:app_id>")
|
@console_ns.route("/explore/apps/<uuid:app_id>")
|
||||||
class RecommendedAppApi(Resource):
|
class RecommendedAppApi(Resource):
|
||||||
@console_ns.response(200, "Success", console_ns.models[RecommendedAppDetailResponse.__name__])
|
@console_ns.response(200, "Success", console_ns.models[RecommendedAppDetailNullableResponse.__name__])
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
def get(self, app_id: UUID):
|
def get(self, app_id: UUID):
|
||||||
|
|||||||
@ -6802,7 +6802,7 @@ Check if dataset is in use
|
|||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description | Schema |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Success | **application/json**: [RecommendedAppDetailResponse](#recommendedappdetailresponse)<br> |
|
| 200 | Success | **application/json**: [RecommendedAppDetailNullableResponse](#recommendedappdetailnullableresponse)<br> |
|
||||||
|
|
||||||
### [GET] /features
|
### [GET] /features
|
||||||
**Get feature configuration for current tenant**
|
**Get feature configuration for current tenant**
|
||||||
@ -17287,6 +17287,10 @@ The type of the parameter
|
|||||||
|
|
||||||
#### ExploreAppMetaResponse
|
#### ExploreAppMetaResponse
|
||||||
|
|
||||||
|
Metadata consumed by the installed-app chat UI.
|
||||||
|
|
||||||
|
Built-in tool icons are URL strings; API-based tool icons are provider-defined payload objects.
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
| tool_icons | object | | No |
|
| tool_icons | object | | No |
|
||||||
@ -17984,6 +17988,7 @@ Input field definition for snippet parameters.
|
|||||||
| icon | string | | No |
|
| icon | string | | No |
|
||||||
| icon_background | string | | No |
|
| icon_background | string | | No |
|
||||||
| icon_type | string | | No |
|
| icon_type | string | | No |
|
||||||
|
| icon_url | string | | Yes |
|
||||||
| id | string | | Yes |
|
| id | string | | Yes |
|
||||||
| mode | string | | No |
|
| mode | string | | No |
|
||||||
| name | string | | No |
|
| name | string | | No |
|
||||||
@ -20080,6 +20085,12 @@ Whitelist scopes accepted by RBAC app and dataset access config APIs.
|
|||||||
| result | string | | Yes |
|
| result | string | | Yes |
|
||||||
| updated_at | integer | | Yes |
|
| updated_at | integer | | Yes |
|
||||||
|
|
||||||
|
#### RecommendedAppDetailNullableResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| RecommendedAppDetailNullableResponse | [RecommendedAppDetailResponse](#recommendedappdetailresponse) | | |
|
||||||
|
|
||||||
#### RecommendedAppDetailResponse
|
#### RecommendedAppDetailResponse
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
@ -20099,6 +20110,7 @@ Whitelist scopes accepted by RBAC app and dataset access config APIs.
|
|||||||
| icon | string | | No |
|
| icon | string | | No |
|
||||||
| icon_background | string | | No |
|
| icon_background | string | | No |
|
||||||
| icon_type | string | | No |
|
| icon_type | string | | No |
|
||||||
|
| icon_url | string | | Yes |
|
||||||
| id | string | | Yes |
|
| id | string | | Yes |
|
||||||
| mode | string | | No |
|
| mode | string | | No |
|
||||||
| name | string | | No |
|
| name | string | | No |
|
||||||
|
|||||||
@ -204,6 +204,18 @@ def test_generate_specs_include_console_contract_shapes_for_schema_migration(tmp
|
|||||||
assert app_detail_schema["properties"]["id"]["type"] == "string"
|
assert app_detail_schema["properties"]["id"]["type"] == "string"
|
||||||
assert app_detail_schema["properties"]["export_data"]["type"] == "string"
|
assert app_detail_schema["properties"]["export_data"]["type"] == "string"
|
||||||
assert {"type": "boolean"} in app_detail_schema["properties"]["can_trial"]["anyOf"]
|
assert {"type": "boolean"} in app_detail_schema["properties"]["can_trial"]["anyOf"]
|
||||||
|
app_detail_nullable_schema = schemas["RecommendedAppDetailNullableResponse"]
|
||||||
|
assert _response_schema(paths["/explore/apps/{app_id}"]["get"])["$ref"] == (
|
||||||
|
"#/components/schemas/RecommendedAppDetailNullableResponse"
|
||||||
|
)
|
||||||
|
assert {"$ref": "#/components/schemas/RecommendedAppDetailResponse"} in app_detail_nullable_schema["anyOf"]
|
||||||
|
assert {"type": "null"} in app_detail_nullable_schema["anyOf"]
|
||||||
|
assert schemas["RecommendedAppInfoResponse"]["properties"]["icon_url"]["readOnly"] is True
|
||||||
|
assert schemas["InstalledAppInfoResponse"]["properties"]["icon_url"]["readOnly"] is True
|
||||||
|
tool_icon_schema = schemas["ExploreAppMetaResponse"]["properties"]["tool_icons"]["additionalProperties"]
|
||||||
|
assert {"type": "string"} in tool_icon_schema["anyOf"]
|
||||||
|
assert {"additionalProperties": True, "type": "object"} in tool_icon_schema["anyOf"]
|
||||||
|
assert "ToolIconResponse" not in schemas
|
||||||
|
|
||||||
plugin_versions = schemas["PluginVersionsResponse"]["properties"]["versions"]
|
plugin_versions = schemas["PluginVersionsResponse"]["properties"]["versions"]
|
||||||
assert plugin_versions["additionalProperties"]["anyOf"][0]["$ref"] == "#/components/schemas/LatestPluginCache"
|
assert plugin_versions["additionalProperties"]["anyOf"][0]["$ref"] == "#/components/schemas/LatestPluginCache"
|
||||||
|
|||||||
@ -7057,11 +7057,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"web/service/access-control/__tests__/index.spec.tsx": {
|
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"web/service/access-control/__tests__/use-app-access-control.spec.tsx": {
|
"web/service/access-control/__tests__/use-app-access-control.spec.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -7087,11 +7082,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"web/service/access-control/index.ts": {
|
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"web/service/access-control/use-app-access-control.ts": {
|
"web/service/access-control/use-app-access-control.ts": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 1
|
"count": 1
|
||||||
|
|||||||
@ -13,15 +13,7 @@ export type LearnDifyAppListResponse = {
|
|||||||
recommended_apps: Array<RecommendedAppResponse>
|
recommended_apps: Array<RecommendedAppResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RecommendedAppDetailResponse = {
|
export type RecommendedAppDetailNullableResponse = RecommendedAppDetailResponse | null
|
||||||
can_trial?: boolean | null
|
|
||||||
export_data: string
|
|
||||||
icon?: string | null
|
|
||||||
icon_background?: string | null
|
|
||||||
id: string
|
|
||||||
mode: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BannerListResponse = Array<BannerResponse>
|
export type BannerListResponse = Array<BannerResponse>
|
||||||
|
|
||||||
@ -38,6 +30,16 @@ export type RecommendedAppResponse = {
|
|||||||
privacy_policy?: string | null
|
privacy_policy?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RecommendedAppDetailResponse = {
|
||||||
|
can_trial?: boolean | null
|
||||||
|
export_data: string
|
||||||
|
icon?: string | null
|
||||||
|
icon_background?: string | null
|
||||||
|
id: string
|
||||||
|
mode: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
export type BannerResponse = {
|
export type BannerResponse = {
|
||||||
content: unknown
|
content: unknown
|
||||||
created_at?: string | null
|
created_at?: string | null
|
||||||
@ -48,6 +50,38 @@ export type BannerResponse = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type RecommendedAppInfoResponse = {
|
export type RecommendedAppInfoResponse = {
|
||||||
|
icon?: string | null
|
||||||
|
icon_background?: string | null
|
||||||
|
icon_type?: string | null
|
||||||
|
readonly icon_url: string | null
|
||||||
|
id: string
|
||||||
|
mode?: string | null
|
||||||
|
name?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RecommendedAppListResponseWritable = {
|
||||||
|
categories: Array<string>
|
||||||
|
recommended_apps: Array<RecommendedAppResponseWritable>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LearnDifyAppListResponseWritable = {
|
||||||
|
recommended_apps: Array<RecommendedAppResponseWritable>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RecommendedAppResponseWritable = {
|
||||||
|
app?: RecommendedAppInfoResponseWritable | null
|
||||||
|
app_id: string
|
||||||
|
can_trial?: boolean | null
|
||||||
|
categories?: Array<string>
|
||||||
|
copyright?: string | null
|
||||||
|
custom_disclaimer?: string | null
|
||||||
|
description?: string | null
|
||||||
|
is_listed?: boolean | null
|
||||||
|
position?: number | null
|
||||||
|
privacy_policy?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RecommendedAppInfoResponseWritable = {
|
||||||
icon?: string | null
|
icon?: string | null
|
||||||
icon_background?: string | null
|
icon_background?: string | null
|
||||||
icon_type?: string | null
|
icon_type?: string | null
|
||||||
@ -97,7 +131,7 @@ export type GetExploreAppsByAppIdData = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type GetExploreAppsByAppIdResponses = {
|
export type GetExploreAppsByAppIdResponses = {
|
||||||
200: RecommendedAppDetailResponse
|
200: RecommendedAppDetailNullableResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetExploreAppsByAppIdResponse
|
export type GetExploreAppsByAppIdResponse
|
||||||
|
|||||||
@ -15,6 +15,11 @@ export const zRecommendedAppDetailResponse = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecommendedAppDetailNullableResponse
|
||||||
|
*/
|
||||||
|
export const zRecommendedAppDetailNullableResponse = zRecommendedAppDetailResponse.nullable()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BannerResponse
|
* BannerResponse
|
||||||
*/
|
*/
|
||||||
@ -39,6 +44,7 @@ export const zRecommendedAppInfoResponse = z.object({
|
|||||||
icon: z.string().nullish(),
|
icon: z.string().nullish(),
|
||||||
icon_background: z.string().nullish(),
|
icon_background: z.string().nullish(),
|
||||||
icon_type: z.string().nullish(),
|
icon_type: z.string().nullish(),
|
||||||
|
icon_url: z.string().nullable(),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
mode: z.string().nullish(),
|
mode: z.string().nullish(),
|
||||||
name: z.string().nullish(),
|
name: z.string().nullish(),
|
||||||
@ -75,6 +81,49 @@ export const zLearnDifyAppListResponse = z.object({
|
|||||||
recommended_apps: z.array(zRecommendedAppResponse),
|
recommended_apps: z.array(zRecommendedAppResponse),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecommendedAppInfoResponse
|
||||||
|
*/
|
||||||
|
export const zRecommendedAppInfoResponseWritable = z.object({
|
||||||
|
icon: z.string().nullish(),
|
||||||
|
icon_background: z.string().nullish(),
|
||||||
|
icon_type: z.string().nullish(),
|
||||||
|
id: z.string(),
|
||||||
|
mode: z.string().nullish(),
|
||||||
|
name: z.string().nullish(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecommendedAppResponse
|
||||||
|
*/
|
||||||
|
export const zRecommendedAppResponseWritable = z.object({
|
||||||
|
app: zRecommendedAppInfoResponseWritable.nullish(),
|
||||||
|
app_id: z.string(),
|
||||||
|
can_trial: z.boolean().nullish(),
|
||||||
|
categories: z.array(z.string()).optional(),
|
||||||
|
copyright: z.string().nullish(),
|
||||||
|
custom_disclaimer: z.string().nullish(),
|
||||||
|
description: z.string().nullish(),
|
||||||
|
is_listed: z.boolean().nullish(),
|
||||||
|
position: z.int().nullish(),
|
||||||
|
privacy_policy: z.string().nullish(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecommendedAppListResponse
|
||||||
|
*/
|
||||||
|
export const zRecommendedAppListResponseWritable = z.object({
|
||||||
|
categories: z.array(z.string()),
|
||||||
|
recommended_apps: z.array(zRecommendedAppResponseWritable),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LearnDifyAppListResponse
|
||||||
|
*/
|
||||||
|
export const zLearnDifyAppListResponseWritable = z.object({
|
||||||
|
recommended_apps: z.array(zRecommendedAppResponseWritable),
|
||||||
|
})
|
||||||
|
|
||||||
export const zGetExploreAppsQuery = z.object({
|
export const zGetExploreAppsQuery = z.object({
|
||||||
language: z.string().optional(),
|
language: z.string().optional(),
|
||||||
})
|
})
|
||||||
@ -100,7 +149,7 @@ export const zGetExploreAppsByAppIdPath = z.object({
|
|||||||
/**
|
/**
|
||||||
* Success
|
* Success
|
||||||
*/
|
*/
|
||||||
export const zGetExploreAppsByAppIdResponse = zRecommendedAppDetailResponse
|
export const zGetExploreAppsByAppIdResponse = zRecommendedAppDetailNullableResponse
|
||||||
|
|
||||||
export const zGetExploreBannersQuery = z.object({
|
export const zGetExploreBannersQuery = z.object({
|
||||||
language: z.string().optional().default('en-US'),
|
language: z.string().optional().default('en-US'),
|
||||||
|
|||||||
@ -117,7 +117,11 @@ export type SuggestedQuestionsResponse = {
|
|||||||
|
|
||||||
export type ExploreAppMetaResponse = {
|
export type ExploreAppMetaResponse = {
|
||||||
tool_icons?: {
|
tool_icons?: {
|
||||||
[key: string]: unknown
|
[key: string]:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,6 +241,7 @@ export type InstalledAppInfoResponse = {
|
|||||||
icon?: string | null
|
icon?: string | null
|
||||||
icon_background?: string | null
|
icon_background?: string | null
|
||||||
icon_type?: string | null
|
icon_type?: string | null
|
||||||
|
readonly icon_url: string | null
|
||||||
id: string
|
id: string
|
||||||
mode?: string | null
|
mode?: string | null
|
||||||
name?: string | null
|
name?: string | null
|
||||||
@ -402,6 +407,33 @@ export type FileTransferMethod = 'datasource_file' | 'local_file' | 'remote_url'
|
|||||||
|
|
||||||
export type ValueSourceType = 'constant' | 'variable'
|
export type ValueSourceType = 'constant' | 'variable'
|
||||||
|
|
||||||
|
export type InstalledAppListResponseWritable = {
|
||||||
|
installed_apps: Array<InstalledAppResponseWritable>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GeneratedAppResponseWritable = JsonValue
|
||||||
|
|
||||||
|
export type InstalledAppResponseWritable = {
|
||||||
|
app: InstalledAppInfoResponseWritable
|
||||||
|
app_owner_tenant_id: string
|
||||||
|
editable: boolean
|
||||||
|
id: string
|
||||||
|
is_pinned: boolean
|
||||||
|
last_used_at?: number | null
|
||||||
|
uninstallable: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstalledAppInfoResponseWritable = {
|
||||||
|
description?: string | null
|
||||||
|
icon?: string | null
|
||||||
|
icon_background?: string | null
|
||||||
|
icon_type?: string | null
|
||||||
|
id: string
|
||||||
|
mode?: string | null
|
||||||
|
name?: string | null
|
||||||
|
use_icon_as_answer_icon?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
export type GetInstalledAppsData = {
|
export type GetInstalledAppsData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
|
|||||||
@ -113,9 +113,15 @@ export const zSuggestedQuestionsResponse = z.object({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ExploreAppMetaResponse
|
* ExploreAppMetaResponse
|
||||||
|
*
|
||||||
|
* Metadata consumed by the installed-app chat UI.
|
||||||
|
*
|
||||||
|
* Built-in tool icons are URL strings; API-based tool icons are provider-defined payload objects.
|
||||||
*/
|
*/
|
||||||
export const zExploreAppMetaResponse = z.object({
|
export const zExploreAppMetaResponse = z.object({
|
||||||
tool_icons: z.record(z.string(), z.unknown()).optional(),
|
tool_icons: z
|
||||||
|
.record(z.string(), z.union([z.string(), z.record(z.string(), z.unknown())]))
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,6 +240,7 @@ export const zInstalledAppInfoResponse = z.object({
|
|||||||
icon: z.string().nullish(),
|
icon: z.string().nullish(),
|
||||||
icon_background: z.string().nullish(),
|
icon_background: z.string().nullish(),
|
||||||
icon_type: z.string().nullish(),
|
icon_type: z.string().nullish(),
|
||||||
|
icon_url: z.string().nullable(),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
mode: z.string().nullish(),
|
mode: z.string().nullish(),
|
||||||
name: z.string().nullish(),
|
name: z.string().nullish(),
|
||||||
@ -533,6 +540,45 @@ export const zExploreMessageInfiniteScrollPagination = z.object({
|
|||||||
limit: z.int(),
|
limit: z.int(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GeneratedAppResponse
|
||||||
|
*/
|
||||||
|
export const zGeneratedAppResponseWritable = zJsonValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InstalledAppInfoResponse
|
||||||
|
*/
|
||||||
|
export const zInstalledAppInfoResponseWritable = z.object({
|
||||||
|
description: z.string().nullish(),
|
||||||
|
icon: z.string().nullish(),
|
||||||
|
icon_background: z.string().nullish(),
|
||||||
|
icon_type: z.string().nullish(),
|
||||||
|
id: z.string(),
|
||||||
|
mode: z.string().nullish(),
|
||||||
|
name: z.string().nullish(),
|
||||||
|
use_icon_as_answer_icon: z.boolean().nullish(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InstalledAppResponse
|
||||||
|
*/
|
||||||
|
export const zInstalledAppResponseWritable = z.object({
|
||||||
|
app: zInstalledAppInfoResponseWritable,
|
||||||
|
app_owner_tenant_id: z.string(),
|
||||||
|
editable: z.boolean(),
|
||||||
|
id: z.string(),
|
||||||
|
is_pinned: z.boolean(),
|
||||||
|
last_used_at: z.int().nullish(),
|
||||||
|
uninstallable: z.boolean(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InstalledAppListResponse
|
||||||
|
*/
|
||||||
|
export const zInstalledAppListResponseWritable = z.object({
|
||||||
|
installed_apps: z.array(zInstalledAppResponseWritable),
|
||||||
|
})
|
||||||
|
|
||||||
export const zGetInstalledAppsQuery = z.object({
|
export const zGetInstalledAppsQuery = z.object({
|
||||||
app_id: z.string().optional(),
|
app_id: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
109
packages/contracts/generated/api/console/router.gen.ts
Normal file
109
packages/contracts/generated/api/console/router.gen.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// This file is auto-generated by packages/contracts/openapi-ts.api.config.ts
|
||||||
|
|
||||||
|
import { contract as enterpriseContract } from '../../enterprise/orpc.gen'
|
||||||
|
import { account } from './account/orpc.gen'
|
||||||
|
import { activate } from './activate/orpc.gen'
|
||||||
|
import { agent } from './agent/orpc.gen'
|
||||||
|
import { allWorkspaces } from './all-workspaces/orpc.gen'
|
||||||
|
import { apiBasedExtension } from './api-based-extension/orpc.gen'
|
||||||
|
import { apiKeyAuth } from './api-key-auth/orpc.gen'
|
||||||
|
import { appDslVersion } from './app-dsl-version/orpc.gen'
|
||||||
|
import { app } from './app/orpc.gen'
|
||||||
|
import { apps } from './apps/orpc.gen'
|
||||||
|
import { auth } from './auth/orpc.gen'
|
||||||
|
import { billing } from './billing/orpc.gen'
|
||||||
|
import { codeBasedExtension } from './code-based-extension/orpc.gen'
|
||||||
|
import { compliance } from './compliance/orpc.gen'
|
||||||
|
import { dataSource } from './data-source/orpc.gen'
|
||||||
|
import { datasets } from './datasets/orpc.gen'
|
||||||
|
import { emailCodeLogin } from './email-code-login/orpc.gen'
|
||||||
|
import { emailRegister } from './email-register/orpc.gen'
|
||||||
|
import { explore } from './explore/orpc.gen'
|
||||||
|
import { features } from './features/orpc.gen'
|
||||||
|
import { files } from './files/orpc.gen'
|
||||||
|
import { forgotPassword } from './forgot-password/orpc.gen'
|
||||||
|
import { form } from './form/orpc.gen'
|
||||||
|
import { info } from './info/orpc.gen'
|
||||||
|
import { installedApps } from './installed-apps/orpc.gen'
|
||||||
|
import { instructionGenerate } from './instruction-generate/orpc.gen'
|
||||||
|
import { login } from './login/orpc.gen'
|
||||||
|
import { logout } from './logout/orpc.gen'
|
||||||
|
import { notification } from './notification/orpc.gen'
|
||||||
|
import { notion } from './notion/orpc.gen'
|
||||||
|
import { oauth } from './oauth/orpc.gen'
|
||||||
|
import { rag } from './rag/orpc.gen'
|
||||||
|
import { refreshToken } from './refresh-token/orpc.gen'
|
||||||
|
import { remoteFiles } from './remote-files/orpc.gen'
|
||||||
|
import { resetPassword } from './reset-password/orpc.gen'
|
||||||
|
import { ruleCodeGenerate } from './rule-code-generate/orpc.gen'
|
||||||
|
import { ruleGenerate } from './rule-generate/orpc.gen'
|
||||||
|
import { ruleStructuredOutputGenerate } from './rule-structured-output-generate/orpc.gen'
|
||||||
|
import { snippets } from './snippets/orpc.gen'
|
||||||
|
import { spec } from './spec/orpc.gen'
|
||||||
|
import { systemFeatures } from './system-features/orpc.gen'
|
||||||
|
import { tagBindings } from './tag-bindings/orpc.gen'
|
||||||
|
import { tags } from './tags/orpc.gen'
|
||||||
|
import { test } from './test/orpc.gen'
|
||||||
|
import { trialApps } from './trial-apps/orpc.gen'
|
||||||
|
import { trialModels } from './trial-models/orpc.gen'
|
||||||
|
import { website } from './website/orpc.gen'
|
||||||
|
import { workflowGenerate } from './workflow-generate/orpc.gen'
|
||||||
|
import { workflow } from './workflow/orpc.gen'
|
||||||
|
import { workspaces } from './workspaces/orpc.gen'
|
||||||
|
|
||||||
|
const communityContract = {
|
||||||
|
account,
|
||||||
|
activate,
|
||||||
|
agent,
|
||||||
|
allWorkspaces,
|
||||||
|
apiBasedExtension,
|
||||||
|
apiKeyAuth,
|
||||||
|
app,
|
||||||
|
appDslVersion,
|
||||||
|
apps,
|
||||||
|
auth,
|
||||||
|
billing,
|
||||||
|
codeBasedExtension,
|
||||||
|
compliance,
|
||||||
|
dataSource,
|
||||||
|
datasets,
|
||||||
|
emailCodeLogin,
|
||||||
|
emailRegister,
|
||||||
|
explore,
|
||||||
|
features,
|
||||||
|
files,
|
||||||
|
forgotPassword,
|
||||||
|
form,
|
||||||
|
info,
|
||||||
|
installedApps,
|
||||||
|
instructionGenerate,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
notification,
|
||||||
|
notion,
|
||||||
|
oauth,
|
||||||
|
rag,
|
||||||
|
refreshToken,
|
||||||
|
remoteFiles,
|
||||||
|
resetPassword,
|
||||||
|
ruleCodeGenerate,
|
||||||
|
ruleGenerate,
|
||||||
|
ruleStructuredOutputGenerate,
|
||||||
|
snippets,
|
||||||
|
spec,
|
||||||
|
systemFeatures,
|
||||||
|
tagBindings,
|
||||||
|
tags,
|
||||||
|
test,
|
||||||
|
trialApps,
|
||||||
|
trialModels,
|
||||||
|
website,
|
||||||
|
workflow,
|
||||||
|
workflowGenerate,
|
||||||
|
workspaces,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const consoleRouterContract = {
|
||||||
|
enterprise: enterpriseContract,
|
||||||
|
...communityContract,
|
||||||
|
}
|
||||||
@ -362,6 +362,44 @@ const writeConsoleContractEntry = (segments: string[]) => {
|
|||||||
fs.writeFileSync(entryPath, consoleContractEntryContent(segments))
|
fs.writeFileSync(entryPath, consoleContractEntryContent(segments))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const consoleRouterContractContent = (segments: string[]) => {
|
||||||
|
const contracts = segments.map((segment) => {
|
||||||
|
return {
|
||||||
|
importPath: toKebabCase(segment),
|
||||||
|
name: toCamelCase(segmentWords(segment)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const imports = contracts
|
||||||
|
.map(contract => `import { ${contract.name} } from './${contract.importPath}/orpc.gen'`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
const communityContractEntries = contracts
|
||||||
|
.map(contract => ` ${contract.name},`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
return `// This file is auto-generated by packages/contracts/openapi-ts.api.config.ts
|
||||||
|
|
||||||
|
${imports}
|
||||||
|
import { contract as enterpriseContract } from '../../enterprise/orpc.gen'
|
||||||
|
|
||||||
|
const communityContract = {
|
||||||
|
${communityContractEntries}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const consoleRouterContract = {
|
||||||
|
enterprise: enterpriseContract,
|
||||||
|
...communityContract,
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeConsoleRouterContract = (segments: string[]) => {
|
||||||
|
const routerPath = path.resolve(currentDir, 'generated/api/console/router.gen.ts')
|
||||||
|
fs.mkdirSync(path.dirname(routerPath), { recursive: true })
|
||||||
|
fs.writeFileSync(routerPath, consoleRouterContractContent(segments))
|
||||||
|
}
|
||||||
|
|
||||||
const createConsoleContractEntryJob = (document: SwaggerDocument, segments: string[]): ApiJob => {
|
const createConsoleContractEntryJob = (document: SwaggerDocument, segments: string[]): ApiJob => {
|
||||||
return {
|
return {
|
||||||
clean: false,
|
clean: false,
|
||||||
@ -369,7 +407,10 @@ const createConsoleContractEntryJob = (document: SwaggerDocument, segments: stri
|
|||||||
outputPath: 'generated/api/console',
|
outputPath: 'generated/api/console',
|
||||||
plugins: [],
|
plugins: [],
|
||||||
source: {
|
source: {
|
||||||
callback: () => writeConsoleContractEntry(segments),
|
callback: () => {
|
||||||
|
writeConsoleContractEntry(segments)
|
||||||
|
writeConsoleRouterContract(segments)
|
||||||
|
},
|
||||||
enabled: true,
|
enabled: true,
|
||||||
path: null,
|
path: null,
|
||||||
serialize: () => '',
|
serialize: () => '',
|
||||||
|
|||||||
@ -106,10 +106,14 @@ vi.mock('@/service/client', () => ({
|
|||||||
},
|
},
|
||||||
explore: {
|
explore: {
|
||||||
apps: {
|
apps: {
|
||||||
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'apps', input],
|
get: {
|
||||||
|
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'apps', 'get', input],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
banners: {
|
banners: {
|
||||||
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'banners', input],
|
get: {
|
||||||
|
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'banners', 'get', input],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -206,7 +210,7 @@ const mockMemberRole = (hasEditPermission: boolean) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const localeInput = { query: { language: 'en' } }
|
const localeInput = { query: { language: 'en' } }
|
||||||
const exploreAppListQueryKey = ['console', 'explore', 'apps', localeInput, 'en']
|
const exploreAppListQueryKey = ['console', 'explore', 'apps', 'get', localeInput, 'en']
|
||||||
const homeContinueWorkAppsInput = {
|
const homeContinueWorkAppsInput = {
|
||||||
query: {
|
query: {
|
||||||
page: 1,
|
page: 1,
|
||||||
|
|||||||
@ -81,7 +81,7 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => {
|
|||||||
export default AppTypeSelector
|
export default AppTypeSelector
|
||||||
|
|
||||||
type AppTypeIconProps = {
|
type AppTypeIconProps = {
|
||||||
type: AppModeEnum
|
type: string
|
||||||
style?: React.CSSProperties
|
style?: React.CSSProperties
|
||||||
className?: string
|
className?: string
|
||||||
wrapperClassName?: string
|
wrapperClassName?: string
|
||||||
@ -199,7 +199,7 @@ function AppTypeSelectorItem({ checked, type, onClick }: AppTypeSelectorItemProp
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAppTypeLabel(type: AppModeEnum, t: ReturnType<typeof useTranslation>['t']) {
|
function getAppTypeLabel(type: string, t: ReturnType<typeof useTranslation>['t']) {
|
||||||
if (type === AppModeEnum.CHAT)
|
if (type === AppModeEnum.CHAT)
|
||||||
return t('typeSelector.chatbot', { ns: 'app' })
|
return t('typeSelector.chatbot', { ns: 'app' })
|
||||||
if (type === AppModeEnum.AGENT_CHAT)
|
if (type === AppModeEnum.AGENT_CHAT)
|
||||||
@ -215,7 +215,7 @@ function getAppTypeLabel(type: AppModeEnum, t: ReturnType<typeof useTranslation>
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AppTypeLabelProps = {
|
type AppTypeLabelProps = {
|
||||||
type: AppModeEnum
|
type: string
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
export function AppTypeLabel({ type, className }: AppTypeLabelProps) {
|
export function AppTypeLabel({ type, className }: AppTypeLabelProps) {
|
||||||
|
|||||||
@ -12,10 +12,9 @@ import type {
|
|||||||
} from '../types'
|
} from '../types'
|
||||||
import type { HumanInputFormSubmitData } from './answer/human-input-content/type'
|
import type { HumanInputFormSubmitData } from './answer/human-input-content/type'
|
||||||
import type { InputForm } from './type'
|
import type { InputForm } from './type'
|
||||||
import type { Emoji } from '@/app/components/tools/types'
|
|
||||||
import type { HumanInputNodeType } from '@/app/components/workflow/nodes/human-input/types'
|
import type { HumanInputNodeType } from '@/app/components/workflow/nodes/human-input/types'
|
||||||
import type { Node } from '@/app/components/workflow/types'
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
import type { AppData } from '@/models/share'
|
import type { AppData, ToolIcon } from '@/models/share'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
@ -52,7 +51,7 @@ export type ChatProps = {
|
|||||||
showPromptLog?: boolean
|
showPromptLog?: boolean
|
||||||
questionIcon?: ReactNode
|
questionIcon?: ReactNode
|
||||||
answerIcon?: ReactNode
|
answerIcon?: ReactNode
|
||||||
allToolIcons?: Record<string, string | Emoji>
|
allToolIcons?: Record<string, ToolIcon>
|
||||||
onAnnotationEdited?: (question: string, answer: string, index: number) => void
|
onAnnotationEdited?: (question: string, answer: string, index: number) => void
|
||||||
onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void
|
onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void
|
||||||
onAnnotationRemoved?: (index: number) => void
|
onAnnotationRemoved?: (index: number) => void
|
||||||
|
|||||||
@ -114,10 +114,14 @@ vi.mock('@/service/client', () => ({
|
|||||||
},
|
},
|
||||||
explore: {
|
explore: {
|
||||||
apps: {
|
apps: {
|
||||||
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'apps', input],
|
get: {
|
||||||
|
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'apps', 'get', input],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
banners: {
|
banners: {
|
||||||
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'banners', input],
|
get: {
|
||||||
|
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'banners', 'get', input],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -291,8 +295,8 @@ type RenderOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const localeInput = { query: { language: 'en' } }
|
const localeInput = { query: { language: 'en' } }
|
||||||
const exploreAppListQueryKey = ['console', 'explore', 'apps', localeInput, 'en']
|
const exploreAppListQueryKey = ['console', 'explore', 'apps', 'get', localeInput, 'en']
|
||||||
const exploreBannersQueryKey = ['console', 'explore', 'banners', localeInput, 'en']
|
const exploreBannersQueryKey = ['console', 'explore', 'banners', 'get', localeInput, 'en']
|
||||||
|
|
||||||
const renderAppList = (
|
const renderAppList = (
|
||||||
hasEditPermission = false,
|
hasEditPermission = false,
|
||||||
|
|||||||
@ -61,7 +61,7 @@ function getExploreAppListQueryOptions(locale?: string) {
|
|||||||
const language = input.query?.language
|
const language = input.query?.language
|
||||||
|
|
||||||
return queryOptions<ExploreAppListData>({
|
return queryOptions<ExploreAppListData>({
|
||||||
queryKey: [...consoleQuery.explore.apps.queryKey({ input }), language],
|
queryKey: [...consoleQuery.explore.apps.get.queryKey({ input }), language],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { categories, recommended_apps } = await fetchAppList(language)
|
const { categories, recommended_apps } = await fetchAppList(language)
|
||||||
return {
|
return {
|
||||||
@ -84,7 +84,7 @@ function getBannersQueryOptions(locale?: string) {
|
|||||||
const language = input.query?.language
|
const language = input.query?.language
|
||||||
|
|
||||||
return queryOptions<BannerType[]>({
|
return queryOptions<BannerType[]>({
|
||||||
queryKey: [...consoleQuery.explore.banners.queryKey({ input }), language],
|
queryKey: [...consoleQuery.explore.banners.get.queryKey({ input }), language],
|
||||||
queryFn: () => fetchBanners(language),
|
queryFn: () => fetchBanners(language),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -246,9 +246,11 @@ const Apps = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
async ({ name, icon_type, icon, icon_background, description }) => {
|
async ({ name, icon_type, icon, icon_background, description }) => {
|
||||||
hideTryAppPanel()
|
hideTryAppPanel()
|
||||||
|
|
||||||
const { export_data, mode } = await fetchAppDetail(
|
const appId = currApp?.app.id
|
||||||
currApp?.app.id as string,
|
if (!appId)
|
||||||
)
|
return
|
||||||
|
|
||||||
|
const { export_data, mode } = await fetchAppDetail(appId)
|
||||||
currentCreateAppModeRef.current = mode
|
currentCreateAppModeRef.current = mode
|
||||||
const payload = {
|
const payload = {
|
||||||
mode: DSLImportMode.YAML_CONTENT,
|
mode: DSLImportMode.YAML_CONTENT,
|
||||||
|
|||||||
@ -1,164 +0,0 @@
|
|||||||
import type { Subject } from '@dify/contracts/enterprise/types.gen'
|
|
||||||
import type { ChatConfig } from '@/app/components/base/chat/types'
|
|
||||||
import type { AccessMode } from '@/models/access-control'
|
|
||||||
import type { Banner } from '@/models/app'
|
|
||||||
import type { App, AppCategory, InstalledApp } from '@/models/explore'
|
|
||||||
import type { AppMeta } from '@/models/share'
|
|
||||||
import type { AppModeEnum } from '@/types/app'
|
|
||||||
import { explore } from '@dify/contracts/api/console/explore/orpc.gen'
|
|
||||||
import { type } from '@orpc/contract'
|
|
||||||
import { base } from '../base'
|
|
||||||
|
|
||||||
type ExploreAppsResponse = {
|
|
||||||
categories: AppCategory[]
|
|
||||||
recommended_apps: App[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExploreAppDetailResponse = {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
icon: string
|
|
||||||
icon_background: string
|
|
||||||
mode: AppModeEnum
|
|
||||||
export_data: string
|
|
||||||
can_trial?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstalledAppsResponse = {
|
|
||||||
installed_apps: InstalledApp[]
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstalledAppMutationResponse = {
|
|
||||||
result: string
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppAccessModeResponse = {
|
|
||||||
accessMode: AccessMode
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateAppAccessModeBody = {
|
|
||||||
appId: string
|
|
||||||
accessMode: AccessMode
|
|
||||||
subjects?: Pick<Subject, 'subjectId' | 'subjectType'>[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const exploreAppsContract = base
|
|
||||||
.route({
|
|
||||||
path: '/explore/apps',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.input(type<{ query?: { language?: string } }>())
|
|
||||||
.output(type<ExploreAppsResponse>())
|
|
||||||
|
|
||||||
export const learnDifyAppsContract = base
|
|
||||||
.route({
|
|
||||||
path: '/explore/apps/learn-dify',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.input(type<{ query?: { language?: string } }>())
|
|
||||||
.output(type<ExploreAppsResponse>())
|
|
||||||
|
|
||||||
export const exploreAppDetailContract = base
|
|
||||||
.route({
|
|
||||||
path: '/explore/apps/{id}',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.input(type<{ params: { id: string } }>())
|
|
||||||
.output(type<ExploreAppDetailResponse | null>())
|
|
||||||
|
|
||||||
export const exploreInstalledAppsContract = base
|
|
||||||
.route({
|
|
||||||
path: '/installed-apps',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.input(type<{ query?: { app_id?: string } }>())
|
|
||||||
.output(type<InstalledAppsResponse>())
|
|
||||||
|
|
||||||
export const exploreInstalledAppUninstallContract = base
|
|
||||||
.route({
|
|
||||||
path: '/installed-apps/{id}',
|
|
||||||
method: 'DELETE',
|
|
||||||
})
|
|
||||||
.input(type<{ params: { id: string } }>())
|
|
||||||
.output(type<unknown>())
|
|
||||||
|
|
||||||
export const exploreInstalledAppPinContract = base
|
|
||||||
.route({
|
|
||||||
path: '/installed-apps/{id}',
|
|
||||||
method: 'PATCH',
|
|
||||||
})
|
|
||||||
.input(type<{
|
|
||||||
params: { id: string }
|
|
||||||
body: {
|
|
||||||
is_pinned: boolean
|
|
||||||
}
|
|
||||||
}>())
|
|
||||||
.output(type<InstalledAppMutationResponse>())
|
|
||||||
|
|
||||||
export const exploreInstalledAppAccessModeContract = base
|
|
||||||
.route({
|
|
||||||
path: '/enterprise/webapp/app/access-mode',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.input(type<{ query: { appId: string } }>())
|
|
||||||
.output(type<AppAccessModeResponse>())
|
|
||||||
|
|
||||||
export const exploreInstalledAppAccessModeUpdateContract = base
|
|
||||||
.route({
|
|
||||||
path: '/enterprise/webapp/app/access-mode',
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
.input(type<{ body: UpdateAppAccessModeBody }>())
|
|
||||||
.output(type<unknown>())
|
|
||||||
|
|
||||||
export const exploreInstalledAppParametersContract = base
|
|
||||||
.route({
|
|
||||||
path: '/installed-apps/{appId}/parameters',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.input(type<{
|
|
||||||
params: {
|
|
||||||
appId: string
|
|
||||||
}
|
|
||||||
}>())
|
|
||||||
.output(type<ChatConfig>())
|
|
||||||
|
|
||||||
export const exploreInstalledAppMetaContract = base
|
|
||||||
.route({
|
|
||||||
path: '/installed-apps/{appId}/meta',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.input(type<{
|
|
||||||
params: {
|
|
||||||
appId: string
|
|
||||||
}
|
|
||||||
}>())
|
|
||||||
.output(type<AppMeta>())
|
|
||||||
|
|
||||||
export const exploreBannersContract = base
|
|
||||||
.route({
|
|
||||||
path: '/explore/banners',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.input(type<{ query?: { language?: string } }>())
|
|
||||||
.output(type<Banner[]>())
|
|
||||||
|
|
||||||
export const exploreRouterContract = {
|
|
||||||
...explore,
|
|
||||||
apps: exploreAppsContract,
|
|
||||||
learnDifyApps: learnDifyAppsContract,
|
|
||||||
appDetail: exploreAppDetailContract,
|
|
||||||
installedApps: exploreInstalledAppsContract,
|
|
||||||
uninstallInstalledApp: exploreInstalledAppUninstallContract,
|
|
||||||
updateInstalledApp: exploreInstalledAppPinContract,
|
|
||||||
appAccessMode: exploreInstalledAppAccessModeContract,
|
|
||||||
updateAppAccessMode: exploreInstalledAppAccessModeUpdateContract,
|
|
||||||
installedAppParameters: exploreInstalledAppParametersContract,
|
|
||||||
installedAppMeta: exploreInstalledAppMetaContract,
|
|
||||||
banners: exploreBannersContract,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const exploreConsoleRouterContract = {
|
|
||||||
explore: exploreRouterContract,
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export const pluginsConsoleRouterContract = {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export const snippetsConsoleRouterContract = {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export const trialAppsConsoleRouterContract = {}
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
import { account } from '@dify/contracts/api/console/account/orpc.gen'
|
|
||||||
import { activate } from '@dify/contracts/api/console/activate/orpc.gen'
|
|
||||||
import { agent } from '@dify/contracts/api/console/agent/orpc.gen'
|
|
||||||
import { allWorkspaces } from '@dify/contracts/api/console/all-workspaces/orpc.gen'
|
|
||||||
import { apiBasedExtension } from '@dify/contracts/api/console/api-based-extension/orpc.gen'
|
|
||||||
import { apiKeyAuth } from '@dify/contracts/api/console/api-key-auth/orpc.gen'
|
|
||||||
import { appDslVersion } from '@dify/contracts/api/console/app-dsl-version/orpc.gen'
|
|
||||||
import { app } from '@dify/contracts/api/console/app/orpc.gen'
|
|
||||||
import { apps } from '@dify/contracts/api/console/apps/orpc.gen'
|
|
||||||
import { auth } from '@dify/contracts/api/console/auth/orpc.gen'
|
|
||||||
import { billing } from '@dify/contracts/api/console/billing/orpc.gen'
|
|
||||||
import { codeBasedExtension } from '@dify/contracts/api/console/code-based-extension/orpc.gen'
|
|
||||||
import { compliance } from '@dify/contracts/api/console/compliance/orpc.gen'
|
|
||||||
import { dataSource } from '@dify/contracts/api/console/data-source/orpc.gen'
|
|
||||||
import { datasets } from '@dify/contracts/api/console/datasets/orpc.gen'
|
|
||||||
import { emailCodeLogin } from '@dify/contracts/api/console/email-code-login/orpc.gen'
|
|
||||||
import { emailRegister } from '@dify/contracts/api/console/email-register/orpc.gen'
|
|
||||||
import { features } from '@dify/contracts/api/console/features/orpc.gen'
|
|
||||||
import { files } from '@dify/contracts/api/console/files/orpc.gen'
|
|
||||||
import { forgotPassword } from '@dify/contracts/api/console/forgot-password/orpc.gen'
|
|
||||||
import { form } from '@dify/contracts/api/console/form/orpc.gen'
|
|
||||||
import { info } from '@dify/contracts/api/console/info/orpc.gen'
|
|
||||||
import { installedApps } from '@dify/contracts/api/console/installed-apps/orpc.gen'
|
|
||||||
import { instructionGenerate } from '@dify/contracts/api/console/instruction-generate/orpc.gen'
|
|
||||||
import { login } from '@dify/contracts/api/console/login/orpc.gen'
|
|
||||||
import { logout } from '@dify/contracts/api/console/logout/orpc.gen'
|
|
||||||
import { notification } from '@dify/contracts/api/console/notification/orpc.gen'
|
|
||||||
import { notion } from '@dify/contracts/api/console/notion/orpc.gen'
|
|
||||||
import { oauth } from '@dify/contracts/api/console/oauth/orpc.gen'
|
|
||||||
import { rag } from '@dify/contracts/api/console/rag/orpc.gen'
|
|
||||||
import { refreshToken } from '@dify/contracts/api/console/refresh-token/orpc.gen'
|
|
||||||
import { remoteFiles } from '@dify/contracts/api/console/remote-files/orpc.gen'
|
|
||||||
import { resetPassword } from '@dify/contracts/api/console/reset-password/orpc.gen'
|
|
||||||
import { ruleCodeGenerate } from '@dify/contracts/api/console/rule-code-generate/orpc.gen'
|
|
||||||
import { ruleGenerate } from '@dify/contracts/api/console/rule-generate/orpc.gen'
|
|
||||||
import { ruleStructuredOutputGenerate } from '@dify/contracts/api/console/rule-structured-output-generate/orpc.gen'
|
|
||||||
import { snippets } from '@dify/contracts/api/console/snippets/orpc.gen'
|
|
||||||
import { spec } from '@dify/contracts/api/console/spec/orpc.gen'
|
|
||||||
import { systemFeatures } from '@dify/contracts/api/console/system-features/orpc.gen'
|
|
||||||
import { tagBindings } from '@dify/contracts/api/console/tag-bindings/orpc.gen'
|
|
||||||
import { tags } from '@dify/contracts/api/console/tags/orpc.gen'
|
|
||||||
import { test } from '@dify/contracts/api/console/test/orpc.gen'
|
|
||||||
import { trialApps } from '@dify/contracts/api/console/trial-apps/orpc.gen'
|
|
||||||
import { trialModels } from '@dify/contracts/api/console/trial-models/orpc.gen'
|
|
||||||
import { website } from '@dify/contracts/api/console/website/orpc.gen'
|
|
||||||
import { workflowGenerate } from '@dify/contracts/api/console/workflow-generate/orpc.gen'
|
|
||||||
import { workflow } from '@dify/contracts/api/console/workflow/orpc.gen'
|
|
||||||
import { workspaces } from '@dify/contracts/api/console/workspaces/orpc.gen'
|
|
||||||
import { contract as enterpriseContract } from '@dify/contracts/enterprise/orpc.gen'
|
|
||||||
import { exploreConsoleRouterContract } from './console/explore'
|
|
||||||
import { pluginsConsoleRouterContract } from './console/plugins'
|
|
||||||
import { snippetsConsoleRouterContract } from './console/snippets'
|
|
||||||
import { trialAppsConsoleRouterContract } from './console/try-app'
|
|
||||||
|
|
||||||
const communityContract = {
|
|
||||||
account,
|
|
||||||
activate,
|
|
||||||
agent,
|
|
||||||
allWorkspaces,
|
|
||||||
apiBasedExtension,
|
|
||||||
apiKeyAuth,
|
|
||||||
app,
|
|
||||||
appDslVersion,
|
|
||||||
apps,
|
|
||||||
auth,
|
|
||||||
billing,
|
|
||||||
codeBasedExtension,
|
|
||||||
compliance,
|
|
||||||
dataSource,
|
|
||||||
datasets,
|
|
||||||
emailCodeLogin,
|
|
||||||
emailRegister,
|
|
||||||
features,
|
|
||||||
files,
|
|
||||||
forgotPassword,
|
|
||||||
form,
|
|
||||||
info,
|
|
||||||
installedApps,
|
|
||||||
instructionGenerate,
|
|
||||||
login,
|
|
||||||
logout,
|
|
||||||
notification,
|
|
||||||
notion,
|
|
||||||
oauth,
|
|
||||||
rag,
|
|
||||||
refreshToken,
|
|
||||||
remoteFiles,
|
|
||||||
resetPassword,
|
|
||||||
ruleCodeGenerate,
|
|
||||||
ruleGenerate,
|
|
||||||
ruleStructuredOutputGenerate,
|
|
||||||
snippets,
|
|
||||||
spec,
|
|
||||||
systemFeatures,
|
|
||||||
tagBindings,
|
|
||||||
tags,
|
|
||||||
test,
|
|
||||||
trialApps,
|
|
||||||
trialModels,
|
|
||||||
website,
|
|
||||||
workflow,
|
|
||||||
workflowGenerate,
|
|
||||||
workspaces,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const consoleRouterContract = {
|
|
||||||
enterprise: enterpriseContract,
|
|
||||||
...communityContract,
|
|
||||||
...exploreConsoleRouterContract,
|
|
||||||
...pluginsConsoleRouterContract,
|
|
||||||
...snippetsConsoleRouterContract,
|
|
||||||
...trialAppsConsoleRouterContract,
|
|
||||||
}
|
|
||||||
@ -39,7 +39,7 @@ Owns the Agent V2 configure runtime used by the Agent App configure page and wor
|
|||||||
- context/app-context
|
- context/app-context
|
||||||
- context/i18n
|
- context/i18n
|
||||||
- context/modal-context
|
- context/modal-context
|
||||||
- contract/router
|
- @dify/contracts/api/console/router.gen
|
||||||
- hooks/use-format-time-from-now
|
- hooks/use-format-time-from-now
|
||||||
- hooks/use-theme
|
- hooks/use-theme
|
||||||
- hooks/use-timestamp
|
- hooks/use-timestamp
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { AppIconType, AppModeEnum } from '@/types/app'
|
import type { AppIconType } from '@/types/app'
|
||||||
|
|
||||||
type AppBasicInfo = {
|
type AppBasicInfo = {
|
||||||
id: string
|
id: string
|
||||||
mode: AppModeEnum
|
mode: string
|
||||||
icon_type: AppIconType | null
|
icon_type: AppIconType | null
|
||||||
icon: string
|
icon: string
|
||||||
icon_background: string
|
icon_background: string
|
||||||
|
|||||||
@ -27,8 +27,10 @@ export type SiteInfo = {
|
|||||||
use_icon_as_answer_icon?: boolean
|
use_icon_as_answer_icon?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ToolIcon = string | Record<string, unknown>
|
||||||
|
|
||||||
export type AppMeta = {
|
export type AppMeta = {
|
||||||
tool_icons: Record<string, string>
|
tool_icons: Record<string, ToolIcon>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomConfigValueType = string | number | boolean | null | undefined
|
export type CustomConfigValueType = string | number | boolean | null | undefined
|
||||||
|
|||||||
@ -2,11 +2,26 @@ import type { ReactNode } from 'react'
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { renderHook, waitFor } from '@testing-library/react'
|
import { renderHook, waitFor } from '@testing-library/react'
|
||||||
import { AccessMode, SubjectType } from '@/models/access-control'
|
import { AccessMode, SubjectType } from '@/models/access-control'
|
||||||
import { post } from '@/service/base'
|
import { consoleClient } from '@/service/client'
|
||||||
import { useUpdateAccessMode } from '..'
|
import { useUpdateAccessMode } from '..'
|
||||||
|
|
||||||
vi.mock('@/service/base', () => ({
|
vi.mock('@/service/client', () => ({
|
||||||
post: vi.fn(),
|
consoleClient: {
|
||||||
|
enterprise: {
|
||||||
|
webAppAuth: {
|
||||||
|
updateWebAppWhitelistSubjects: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
consoleQuery: {
|
||||||
|
enterprise: {
|
||||||
|
webAppAuth: {
|
||||||
|
getWebAppAccessMode: {
|
||||||
|
key: vi.fn(() => ['enterprise', 'web-app-auth', 'access-mode']),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const createWrapper = () => {
|
const createWrapper = () => {
|
||||||
@ -25,7 +40,7 @@ const createWrapper = () => {
|
|||||||
describe('access-control service', () => {
|
describe('access-control service', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
vi.mocked(post).mockResolvedValue({})
|
vi.mocked(consoleClient.enterprise.webAppAuth.updateWebAppWhitelistSubjects).mockResolvedValue({})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Access mode updates keep the legacy webapp whitelist payload contract.
|
// Access mode updates keep the legacy webapp whitelist payload contract.
|
||||||
@ -43,7 +58,7 @@ describe('access-control service', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(post).toHaveBeenCalledWith('/enterprise/webapp/app/access-mode', {
|
expect(consoleClient.enterprise.webAppAuth.updateWebAppWhitelistSubjects).toHaveBeenCalledWith({
|
||||||
body: {
|
body: {
|
||||||
appId: 'app-1',
|
appId: 'app-1',
|
||||||
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { AccessControlGroup, AccessMode, Subject } from '@/models/access-control'
|
import type { AccessControlGroup, AccessMode, Subject } from '@/models/access-control'
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { post } from '../base'
|
import { consoleClient, consoleQuery } from '../client'
|
||||||
import {
|
import {
|
||||||
useAppWhiteListSubjects as useAppWhiteListSubjectsBase,
|
useAppWhiteListSubjects as useAppWhiteListSubjectsBase,
|
||||||
useGetUserCanAccessApp as useGetUserCanAccessAppBase,
|
useGetUserCanAccessApp as useGetUserCanAccessAppBase,
|
||||||
@ -46,9 +46,14 @@ export const useUpdateAccessMode = () => {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: [NAME_SPACE, 'update-access-mode'],
|
mutationKey: [NAME_SPACE, 'update-access-mode'],
|
||||||
mutationFn: (params: UpdateAccessModeParams) => {
|
mutationFn: (params: UpdateAccessModeParams) => {
|
||||||
return post('/enterprise/webapp/app/access-mode', { body: params })
|
return consoleClient.enterprise.webAppAuth.updateWebAppWhitelistSubjects({
|
||||||
|
body: params,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: consoleQuery.enterprise.webAppAuth.getWebAppAccessMode.key({ type: 'query' }),
|
||||||
|
})
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: [NAME_SPACE, 'app-whitelist-subjects'],
|
queryKey: [NAME_SPACE, 'app-whitelist-subjects'],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { AgentAppPagination } from '@dify/contracts/api/console/agent/types.gen'
|
import type { AgentAppPagination } from '@dify/contracts/api/console/agent/types.gen'
|
||||||
import type { ApiBasedExtensionResponse } from '@dify/contracts/api/console/api-based-extension/types.gen'
|
import type { ApiBasedExtensionResponse } from '@dify/contracts/api/console/api-based-extension/types.gen'
|
||||||
|
import type { consoleRouterContract } from '@dify/contracts/api/console/router.gen'
|
||||||
import type { TagResponse as Tag, TagType } from '@dify/contracts/api/console/tags/types.gen'
|
import type { TagResponse as Tag, TagType } from '@dify/contracts/api/console/tags/types.gen'
|
||||||
import type {
|
import type {
|
||||||
GetReleaseResponse,
|
GetReleaseResponse,
|
||||||
@ -11,7 +12,6 @@ import type { AnyContractRouter, ContractRouterClient } from '@orpc/contract'
|
|||||||
import type { JsonifiedClient } from '@orpc/openapi-client'
|
import type { JsonifiedClient } from '@orpc/openapi-client'
|
||||||
import type { RouterUtils, TanstackQueryOperationContext } from '@orpc/tanstack-query'
|
import type { RouterUtils, TanstackQueryOperationContext } from '@orpc/tanstack-query'
|
||||||
import type { InfiniteData, QueryClient, QueryKey } from '@tanstack/react-query'
|
import type { InfiniteData, QueryClient, QueryKey } from '@tanstack/react-query'
|
||||||
import type { consoleRouterContract } from '@/contract/router'
|
|
||||||
import { createORPCClient, onError } from '@orpc/client'
|
import { createORPCClient, onError } from '@orpc/client'
|
||||||
import { OpenAPILink } from '@orpc/openapi-client/fetch'
|
import { OpenAPILink } from '@orpc/openapi-client/fetch'
|
||||||
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
||||||
@ -652,22 +652,6 @@ export const consoleQuery: RouterUtils<typeof consoleClient> = createTanstackQue
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
explore: {
|
|
||||||
updateAppAccessMode: {
|
|
||||||
mutationOptions: {
|
|
||||||
onSuccess: (_data, _variables, _onMutateResult, context) => {
|
|
||||||
return Promise.all([
|
|
||||||
context.client.invalidateQueries({
|
|
||||||
queryKey: consoleQuery.explore.appAccessMode.key({ type: 'query' }),
|
|
||||||
}),
|
|
||||||
context.client.invalidateQueries({
|
|
||||||
queryKey: ['access-control', 'app-whitelist-subjects'],
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
apiBasedExtension: {
|
apiBasedExtension: {
|
||||||
post: {
|
post: {
|
||||||
mutationOptions: {
|
mutationOptions: {
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import { agent } from '@dify/contracts/api/console/agent/orpc.gen'
|
import { agent } from '@dify/contracts/api/console/agent/orpc.gen'
|
||||||
import { apps } from '@dify/contracts/api/console/apps/orpc.gen'
|
import { apps } from '@dify/contracts/api/console/apps/orpc.gen'
|
||||||
|
import { explore } from '@dify/contracts/api/console/explore/orpc.gen'
|
||||||
|
import { installedApps } from '@dify/contracts/api/console/installed-apps/orpc.gen'
|
||||||
import { notification } from '@dify/contracts/api/console/notification/orpc.gen'
|
import { notification } from '@dify/contracts/api/console/notification/orpc.gen'
|
||||||
import { tags } from '@dify/contracts/api/console/tags/orpc.gen'
|
import { tags } from '@dify/contracts/api/console/tags/orpc.gen'
|
||||||
import { workspaces } from '@dify/contracts/api/console/workspaces/orpc.gen'
|
import { workspaces } from '@dify/contracts/api/console/workspaces/orpc.gen'
|
||||||
|
import { contract as enterpriseContract } from '@dify/contracts/enterprise/orpc.gen'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { loadConsoleContractForSegment } from './console-router-loader'
|
import { loadConsoleContractForSegment } from './console-router-loader'
|
||||||
|
|
||||||
@ -10,12 +13,20 @@ describe('loadConsoleContractForSegment', () => {
|
|||||||
it.each([
|
it.each([
|
||||||
['agent', 'agent', agent],
|
['agent', 'agent', agent],
|
||||||
['apps', 'apps', apps],
|
['apps', 'apps', apps],
|
||||||
|
['explore', 'explore', explore],
|
||||||
|
['installedApps', 'installedApps', installedApps],
|
||||||
['notification', 'notification', notification],
|
['notification', 'notification', notification],
|
||||||
['tags', 'tags', tags],
|
['tags', 'tags', tags],
|
||||||
['workspaces', 'workspaces', workspaces],
|
['workspaces', 'workspaces', workspaces],
|
||||||
] as const)('loads the generated %s contract when generated types are usable', async (segment, contractKey, generatedContract) => {
|
] as const)('loads the generated %s contract when generated types are usable', async (segment, contractKey, generatedContract) => {
|
||||||
const contract = await loadConsoleContractForSegment(segment)
|
const contract = await loadConsoleContractForSegment(segment)
|
||||||
|
|
||||||
expect((contract as Record<string, unknown>)[contractKey]).toBe(generatedContract)
|
expect(contract).toHaveProperty(contractKey, generatedContract)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loads the generated enterprise contract independently', async () => {
|
||||||
|
const contract = await loadConsoleContractForSegment('enterprise')
|
||||||
|
|
||||||
|
expect(contract).toHaveProperty('enterprise', enterpriseContract)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,33 +1,24 @@
|
|||||||
import type { AnyContractRouter } from '@orpc/contract'
|
import type { AnyContractRouter } from '@orpc/contract'
|
||||||
import { contractLoaders } from '@dify/contracts/api/console/orpc.gen'
|
import { contractLoaders } from '@dify/contracts/api/console/orpc.gen'
|
||||||
|
|
||||||
type ConsoleContractExtension = Record<string, unknown>
|
const generatedConsoleContractLoaders: Partial<Record<string, () => Promise<AnyContractRouter>>> = contractLoaders
|
||||||
|
|
||||||
const wrapConsoleContract = (segment: string, contract: unknown): ConsoleContractExtension => ({ [segment]: contract })
|
|
||||||
|
|
||||||
async function loadGeneratedConsoleContract(segment: string) {
|
async function loadGeneratedConsoleContract(segment: string) {
|
||||||
const loader = contractLoaders[segment as keyof typeof contractLoaders]
|
const loader = generatedConsoleContractLoaders[segment]
|
||||||
if (!loader)
|
if (!loader)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return loader() as Promise<AnyContractRouter>
|
return loader()
|
||||||
}
|
}
|
||||||
|
|
||||||
const customConsoleContractLoaders: Record<string, () => Promise<ConsoleContractExtension>> = {
|
async function loadEnterpriseContract(): Promise<AnyContractRouter> {
|
||||||
enterprise: () => import('@dify/contracts/enterprise/orpc.gen').then(({ contract }) => wrapConsoleContract('enterprise', contract)),
|
const { contract } = await import('@dify/contracts/enterprise/orpc.gen')
|
||||||
explore: () => import('@/contract/console/explore').then(({ exploreConsoleRouterContract }) => exploreConsoleRouterContract),
|
return { enterprise: contract }
|
||||||
plugins: () => import('@/contract/console/plugins').then(({ pluginsConsoleRouterContract }) => pluginsConsoleRouterContract),
|
|
||||||
snippets: () => import('@/contract/console/snippets').then(({ snippetsConsoleRouterContract }) => snippetsConsoleRouterContract),
|
|
||||||
trialApps: () => import('@/contract/console/try-app').then(({ trialAppsConsoleRouterContract }) => trialAppsConsoleRouterContract),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadConsoleContractForSegment(segment: string) {
|
export async function loadConsoleContractForSegment(segment: string) {
|
||||||
const customContractLoader = customConsoleContractLoaders[segment]
|
if (segment === 'enterprise')
|
||||||
if (customContractLoader) {
|
return loadEnterpriseContract()
|
||||||
const customContract = await customContractLoader()
|
|
||||||
if (Object.keys(customContract).length > 0)
|
|
||||||
return customContract as AnyContractRouter
|
|
||||||
}
|
|
||||||
|
|
||||||
const generatedContract = await loadGeneratedConsoleContract(segment)
|
const generatedContract = await loadGeneratedConsoleContract(segment)
|
||||||
if (generatedContract)
|
if (generatedContract)
|
||||||
|
|||||||
94
web/service/explore.spec.ts
Normal file
94
web/service/explore.spec.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import {
|
||||||
|
fetchAppDetail,
|
||||||
|
fetchAppList,
|
||||||
|
fetchInstalledAppMeta,
|
||||||
|
} from './explore'
|
||||||
|
|
||||||
|
const mockExploreAppsGet = vi.hoisted(() => vi.fn())
|
||||||
|
const mockExploreAppDetailGet = vi.hoisted(() => vi.fn())
|
||||||
|
const mockInstalledAppMetaGet = vi.hoisted(() => vi.fn())
|
||||||
|
|
||||||
|
vi.mock('./client', () => ({
|
||||||
|
consoleClient: {
|
||||||
|
explore: {
|
||||||
|
apps: {
|
||||||
|
get: mockExploreAppsGet,
|
||||||
|
byAppId: {
|
||||||
|
get: mockExploreAppDetailGet,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
installedApps: {
|
||||||
|
byInstalledAppId: {
|
||||||
|
meta: {
|
||||||
|
get: mockInstalledAppMetaGet,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('explore service normalizers', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves backend app modes that are not part of the legacy frontend enum', async () => {
|
||||||
|
mockExploreAppsGet.mockResolvedValue({
|
||||||
|
categories: [],
|
||||||
|
recommended_apps: [{
|
||||||
|
app_id: 'agent-app',
|
||||||
|
app: {
|
||||||
|
id: 'agent-app',
|
||||||
|
name: 'Agent app',
|
||||||
|
mode: 'agent',
|
||||||
|
icon: '',
|
||||||
|
icon_background: '',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
mockExploreAppDetailGet.mockResolvedValue({
|
||||||
|
id: 'pipeline-app',
|
||||||
|
name: 'Pipeline app',
|
||||||
|
icon: '',
|
||||||
|
icon_background: '',
|
||||||
|
mode: 'rag-pipeline',
|
||||||
|
export_data: 'kind: app',
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(fetchAppList()).resolves.toMatchObject({
|
||||||
|
recommended_apps: [{
|
||||||
|
app: {
|
||||||
|
mode: 'agent',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
await expect(fetchAppDetail('pipeline-app')).resolves.toMatchObject({
|
||||||
|
mode: 'rag-pipeline',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves provider-defined tool icon payload objects', async () => {
|
||||||
|
const providerIcon = {
|
||||||
|
type: 'custom',
|
||||||
|
value: {
|
||||||
|
content: 'tool',
|
||||||
|
background: '#fff',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mockInstalledAppMetaGet.mockResolvedValue({
|
||||||
|
tool_icons: {
|
||||||
|
builtin: '/tool.svg',
|
||||||
|
provider: providerIcon,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(fetchInstalledAppMeta('installed-app-id')).resolves.toEqual({
|
||||||
|
tool_icons: {
|
||||||
|
builtin: '/tool.svg',
|
||||||
|
provider: providerIcon,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,53 +1,400 @@
|
|||||||
|
import type {
|
||||||
|
BannerListResponse,
|
||||||
|
BannerResponse,
|
||||||
|
GetExploreAppsLearnDifyResponse,
|
||||||
|
GetExploreAppsResponse,
|
||||||
|
RecommendedAppDetailResponse,
|
||||||
|
RecommendedAppInfoResponse,
|
||||||
|
RecommendedAppResponse,
|
||||||
|
} from '@dify/contracts/api/console/explore/types.gen'
|
||||||
|
import type {
|
||||||
|
ExploreAppMetaResponse,
|
||||||
|
InstalledAppInfoResponse,
|
||||||
|
InstalledAppListResponse,
|
||||||
|
Parameters as InstalledAppParametersResponse,
|
||||||
|
InstalledAppResponse,
|
||||||
|
} from '@dify/contracts/api/console/installed-apps/types.gen'
|
||||||
import type { ChatConfig } from '@/app/components/base/chat/types'
|
import type { ChatConfig } from '@/app/components/base/chat/types'
|
||||||
import type { ExploreAppDetailResponse } from '@/contract/console/explore'
|
import type { Banner } from '@/models/app'
|
||||||
import type { AppMeta } from '@/models/share'
|
import type { App, AppCategory, InstalledApp } from '@/models/explore'
|
||||||
|
import type { AppMeta, ToolIcon } from '@/models/share'
|
||||||
|
import type { AppIconType } from '@/types/app'
|
||||||
|
import { AccessMode } from '@/models/access-control'
|
||||||
|
import { PromptMode } from '@/models/debug'
|
||||||
|
import { RETRIEVE_TYPE, TtsAutoPlay } from '@/types/app'
|
||||||
import { consoleClient } from './client'
|
import { consoleClient } from './client'
|
||||||
|
|
||||||
|
type ExploreAppsResponse = {
|
||||||
|
categories: AppCategory[]
|
||||||
|
recommended_apps: App[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type LearnDifyAppsResponse = {
|
||||||
|
recommended_apps: App[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExploreAppDetailResponse = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
icon_background: string
|
||||||
|
mode: string
|
||||||
|
export_data: string
|
||||||
|
can_trial?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstalledAppsResponse = {
|
||||||
|
installed_apps: InstalledApp[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppAccessModeResponse = {
|
||||||
|
accessMode: AccessMode
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstalledAppParametersViewModel = ChatConfig
|
||||||
|
|
||||||
|
const isRecord = (value: unknown): value is Record<string, unknown> => {
|
||||||
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getValue = (source: object, key: string): unknown => {
|
||||||
|
return Reflect.get(source, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStringProperty = (source: object, key: string, fallback = '') => {
|
||||||
|
const value = getValue(source, key)
|
||||||
|
return typeof value === 'string' ? value : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBooleanProperty = (source: object, key: string, fallback = false) => {
|
||||||
|
const value = getValue(source, key)
|
||||||
|
return typeof value === 'boolean' ? value : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeAppMode = (value: unknown) => {
|
||||||
|
return typeof value === 'string' ? value : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAppIconType = (value: unknown): value is AppIconType => {
|
||||||
|
return value === 'image' || value === 'emoji' || value === 'link'
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAccessMode = (value: unknown): value is AccessMode => {
|
||||||
|
return value === AccessMode.PUBLIC
|
||||||
|
|| value === AccessMode.SPECIFIC_GROUPS_MEMBERS
|
||||||
|
|| value === AccessMode.ORGANIZATION
|
||||||
|
|| value === AccessMode.EXTERNAL_MEMBERS
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeAccessMode = (value: unknown) => {
|
||||||
|
if (isAccessMode(value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
throw new Error('Web app access mode response returned an unsupported access mode.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeAppIconType = (value: unknown) => {
|
||||||
|
return isAppIconType(value) ? value : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeAppBasicInfo = (
|
||||||
|
source: RecommendedAppInfoResponse | InstalledAppInfoResponse | null | undefined,
|
||||||
|
fallbackId: string,
|
||||||
|
): App['app'] => {
|
||||||
|
const description = source && 'description' in source && typeof source.description === 'string'
|
||||||
|
? source.description
|
||||||
|
: ''
|
||||||
|
const useIconAsAnswerIcon = source
|
||||||
|
&& 'use_icon_as_answer_icon' in source
|
||||||
|
&& typeof source.use_icon_as_answer_icon === 'boolean'
|
||||||
|
? source.use_icon_as_answer_icon
|
||||||
|
: false
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: source?.id ?? fallbackId,
|
||||||
|
mode: normalizeAppMode(source?.mode),
|
||||||
|
icon_type: normalizeAppIconType(source?.icon_type),
|
||||||
|
icon: source?.icon ?? '',
|
||||||
|
icon_background: source?.icon_background ?? '',
|
||||||
|
icon_url: source?.icon_url ?? '',
|
||||||
|
name: source?.name ?? '',
|
||||||
|
description,
|
||||||
|
use_icon_as_answer_icon: useIconAsAnswerIcon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeRecommendedApp = (app: RecommendedAppResponse): App => {
|
||||||
|
return {
|
||||||
|
app: normalizeAppBasicInfo(app.app, app.app_id),
|
||||||
|
app_id: app.app_id,
|
||||||
|
description: app.description ?? '',
|
||||||
|
copyright: app.copyright ?? '',
|
||||||
|
privacy_policy: app.privacy_policy ?? null,
|
||||||
|
custom_disclaimer: app.custom_disclaimer ?? null,
|
||||||
|
categories: app.categories ?? [],
|
||||||
|
position: app.position ?? 0,
|
||||||
|
is_listed: app.is_listed ?? false,
|
||||||
|
install_count: 0,
|
||||||
|
installed: false,
|
||||||
|
editable: false,
|
||||||
|
is_agent: false,
|
||||||
|
can_trial: app.can_trial ?? false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeExploreAppsResponse = (response: GetExploreAppsResponse): ExploreAppsResponse => {
|
||||||
|
return {
|
||||||
|
categories: response.categories,
|
||||||
|
recommended_apps: response.recommended_apps.map(normalizeRecommendedApp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeLearnDifyAppsResponse = (response: GetExploreAppsLearnDifyResponse): LearnDifyAppsResponse => {
|
||||||
|
return {
|
||||||
|
recommended_apps: response.recommended_apps.map(normalizeRecommendedApp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeAppDetail = (response: RecommendedAppDetailResponse): ExploreAppDetailResponse => {
|
||||||
|
return {
|
||||||
|
id: response.id,
|
||||||
|
name: response.name,
|
||||||
|
icon: response.icon ?? '',
|
||||||
|
icon_background: response.icon_background ?? '',
|
||||||
|
mode: normalizeAppMode(response.mode),
|
||||||
|
export_data: response.export_data,
|
||||||
|
can_trial: response.can_trial,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeInstalledApp = (installedApp: InstalledAppResponse): InstalledApp => {
|
||||||
|
return {
|
||||||
|
app: normalizeAppBasicInfo(installedApp.app, installedApp.app.id),
|
||||||
|
id: installedApp.id,
|
||||||
|
uninstallable: installedApp.uninstallable,
|
||||||
|
is_pinned: installedApp.is_pinned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeInstalledAppsResponse = (response: InstalledAppListResponse): InstalledAppsResponse => {
|
||||||
|
return {
|
||||||
|
installed_apps: response.installed_apps.map(normalizeInstalledApp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeBannerContent = (content: unknown): Banner['content'] => {
|
||||||
|
const record = isRecord(content) ? content : {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'category': getStringProperty(record, 'category'),
|
||||||
|
'title': getStringProperty(record, 'title'),
|
||||||
|
'description': getStringProperty(record, 'description'),
|
||||||
|
'img-src': getStringProperty(record, 'img-src'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeBanner = (banner: BannerResponse): Banner => {
|
||||||
|
return {
|
||||||
|
id: banner.id,
|
||||||
|
content: normalizeBannerContent(banner.content),
|
||||||
|
link: banner.link ?? '',
|
||||||
|
sort: banner.sort,
|
||||||
|
status: banner.status,
|
||||||
|
created_at: banner.created_at ?? '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeBannersResponse = (response: BannerListResponse): Banner[] => {
|
||||||
|
return response.map(normalizeBanner)
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeToolIcons = (value: unknown) => {
|
||||||
|
const record = isRecord(value) ? value : {}
|
||||||
|
const result: Record<string, ToolIcon> = {}
|
||||||
|
|
||||||
|
Object.entries(record).forEach(([key, item]) => {
|
||||||
|
if (typeof item === 'string')
|
||||||
|
result[key] = item
|
||||||
|
else if (isRecord(item))
|
||||||
|
result[key] = item
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeAppMeta = (response: ExploreAppMetaResponse): AppMeta => {
|
||||||
|
return {
|
||||||
|
tool_icons: normalizeToolIcons(response.tool_icons),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTtsAutoPlay = (value: unknown): value is TtsAutoPlay => {
|
||||||
|
return value === TtsAutoPlay.enabled || value === TtsAutoPlay.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUserInputFormItem = (value: unknown): value is ChatConfig['user_input_form'][number] => {
|
||||||
|
if (!isRecord(value))
|
||||||
|
return false
|
||||||
|
|
||||||
|
return [
|
||||||
|
'text-input',
|
||||||
|
'select',
|
||||||
|
'paragraph',
|
||||||
|
'number',
|
||||||
|
'checkbox',
|
||||||
|
'file',
|
||||||
|
'file-list',
|
||||||
|
'external_data_tool',
|
||||||
|
'json_object',
|
||||||
|
].some(key => isRecord(getValue(value, key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const isModel = (value: unknown): value is NonNullable<ChatConfig['suggested_questions_after_answer']['model']> => {
|
||||||
|
return isRecord(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAnnotationReplyConfig = (value: unknown): value is NonNullable<ChatConfig['annotation_reply']> => {
|
||||||
|
return isRecord(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFileUploadConfig = (value: unknown): value is NonNullable<ChatConfig['file_upload']> => {
|
||||||
|
return isRecord(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultDatasetConfigs = (): ChatConfig['dataset_configs'] => ({
|
||||||
|
retrieval_model: RETRIEVE_TYPE.oneWay,
|
||||||
|
reranking_model: {
|
||||||
|
reranking_provider_name: '',
|
||||||
|
reranking_model_name: '',
|
||||||
|
},
|
||||||
|
top_k: 4,
|
||||||
|
score_threshold_enabled: false,
|
||||||
|
score_threshold: null,
|
||||||
|
datasets: {
|
||||||
|
datasets: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizeEnabledConfig = (value: unknown): { enabled: boolean } => {
|
||||||
|
const record = isRecord(value) ? value : {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...record,
|
||||||
|
enabled: getBooleanProperty(record, 'enabled'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeSuggestedQuestionsAfterAnswer = (
|
||||||
|
value: unknown,
|
||||||
|
): ChatConfig['suggested_questions_after_answer'] => {
|
||||||
|
const record = isRecord(value) ? value : {}
|
||||||
|
const model = getValue(record, 'model')
|
||||||
|
const prompt = getStringProperty(record, 'prompt')
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: getBooleanProperty(record, 'enabled'),
|
||||||
|
...(isModel(model) ? { model } : {}),
|
||||||
|
...(prompt ? { prompt } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeTextToSpeech = (value: unknown): ChatConfig['text_to_speech'] => {
|
||||||
|
const record = isRecord(value) ? value : {}
|
||||||
|
const autoPlay = getValue(record, 'autoPlay')
|
||||||
|
const normalizedAutoPlay = isTtsAutoPlay(autoPlay) ? autoPlay : undefined
|
||||||
|
|
||||||
|
return {
|
||||||
|
...record,
|
||||||
|
enabled: getBooleanProperty(record, 'enabled'),
|
||||||
|
voice: getStringProperty(record, 'voice') || undefined,
|
||||||
|
language: getStringProperty(record, 'language') || undefined,
|
||||||
|
...(normalizedAutoPlay ? { autoPlay: normalizedAutoPlay } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeSystemParameters = (
|
||||||
|
systemParameters: InstalledAppParametersResponse['system_parameters'],
|
||||||
|
): ChatConfig['system_parameters'] => {
|
||||||
|
return {
|
||||||
|
audio_file_size_limit: systemParameters.audio_file_size_limit,
|
||||||
|
file_size_limit: systemParameters.file_size_limit,
|
||||||
|
image_file_size_limit: systemParameters.image_file_size_limit,
|
||||||
|
video_file_size_limit: systemParameters.video_file_size_limit,
|
||||||
|
workflow_file_upload_limit: systemParameters.workflow_file_upload_limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeInstalledAppParametersViewModel = (
|
||||||
|
response: InstalledAppParametersResponse,
|
||||||
|
): InstalledAppParametersViewModel => {
|
||||||
|
return {
|
||||||
|
opening_statement: response.opening_statement ?? '',
|
||||||
|
suggested_questions: response.suggested_questions,
|
||||||
|
pre_prompt: '',
|
||||||
|
prompt_type: PromptMode.simple,
|
||||||
|
user_input_form: response.user_input_form.filter(isUserInputFormItem),
|
||||||
|
more_like_this: normalizeEnabledConfig(response.more_like_this),
|
||||||
|
suggested_questions_after_answer: normalizeSuggestedQuestionsAfterAnswer(response.suggested_questions_after_answer),
|
||||||
|
speech_to_text: normalizeEnabledConfig(response.speech_to_text),
|
||||||
|
text_to_speech: normalizeTextToSpeech(response.text_to_speech),
|
||||||
|
retriever_resource: normalizeEnabledConfig(response.retriever_resource),
|
||||||
|
sensitive_word_avoidance: normalizeEnabledConfig(response.sensitive_word_avoidance),
|
||||||
|
...(isAnnotationReplyConfig(response.annotation_reply) ? { annotation_reply: response.annotation_reply } : {}),
|
||||||
|
agent_mode: {
|
||||||
|
enabled: false,
|
||||||
|
tools: [],
|
||||||
|
},
|
||||||
|
dataset_configs: defaultDatasetConfigs(),
|
||||||
|
...(isFileUploadConfig(response.file_upload) ? { file_upload: response.file_upload } : {}),
|
||||||
|
system_parameters: normalizeSystemParameters(response.system_parameters),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const fetchAppList = (language?: string) => {
|
export const fetchAppList = (language?: string) => {
|
||||||
if (!language)
|
if (!language)
|
||||||
return consoleClient.explore.apps({})
|
return consoleClient.explore.apps.get({}).then(normalizeExploreAppsResponse)
|
||||||
|
|
||||||
return consoleClient.explore.apps({
|
return consoleClient.explore.apps.get({
|
||||||
query: { language },
|
query: { language },
|
||||||
})
|
}).then(normalizeExploreAppsResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchLearnDifyAppList = (language?: string) => {
|
export const fetchLearnDifyAppList = (language?: string) => {
|
||||||
if (!language)
|
if (!language)
|
||||||
return consoleClient.explore.learnDifyApps({})
|
return consoleClient.explore.apps.learnDify.get({}).then(normalizeLearnDifyAppsResponse)
|
||||||
|
|
||||||
return consoleClient.explore.learnDifyApps({
|
return consoleClient.explore.apps.learnDify.get({
|
||||||
query: { language },
|
query: { language },
|
||||||
})
|
}).then(normalizeLearnDifyAppsResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchAppDetail = async (id: string): Promise<ExploreAppDetailResponse> => {
|
export const fetchAppDetail = async (id: string): Promise<ExploreAppDetailResponse> => {
|
||||||
const response = await consoleClient.explore.appDetail({
|
const response = await consoleClient.explore.apps.byAppId.get({
|
||||||
params: { id },
|
params: { app_id: id },
|
||||||
})
|
})
|
||||||
if (!response)
|
if (!response)
|
||||||
throw new Error('Recommended app not found')
|
throw new Error('Recommended app not found')
|
||||||
return response
|
return normalizeAppDetail(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchInstalledAppList = (appId?: string | null) => {
|
export const fetchInstalledAppList = (appId?: string | null) => {
|
||||||
if (!appId)
|
if (!appId)
|
||||||
return consoleClient.explore.installedApps({})
|
return consoleClient.installedApps.get({}).then(normalizeInstalledAppsResponse)
|
||||||
|
|
||||||
return consoleClient.explore.installedApps({
|
return consoleClient.installedApps.get({
|
||||||
query: { app_id: appId },
|
query: { app_id: appId },
|
||||||
})
|
}).then(normalizeInstalledAppsResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const uninstallApp = (id: string) => {
|
export const uninstallApp = (id: string) => {
|
||||||
return consoleClient.explore.uninstallInstalledApp({
|
return consoleClient.installedApps.byInstalledAppId.delete({
|
||||||
params: { id },
|
params: { installed_app_id: id },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updatePinStatus = (id: string, isPinned: boolean) => {
|
export const updatePinStatus = (id: string, isPinned: boolean) => {
|
||||||
return consoleClient.explore.updateInstalledApp({
|
return consoleClient.installedApps.byInstalledAppId.patch({
|
||||||
params: { id },
|
params: { installed_app_id: id },
|
||||||
body: {
|
body: {
|
||||||
is_pinned: isPinned,
|
is_pinned: isPinned,
|
||||||
},
|
},
|
||||||
@ -55,28 +402,30 @@ export const updatePinStatus = (id: string, isPinned: boolean) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getAppAccessModeByAppId = (appId: string) => {
|
export const getAppAccessModeByAppId = (appId: string) => {
|
||||||
return consoleClient.explore.appAccessMode({
|
return consoleClient.enterprise.webAppAuth.getWebAppAccessMode({
|
||||||
query: { appId },
|
query: { appId },
|
||||||
})
|
}).then((response): AppAccessModeResponse => ({
|
||||||
|
accessMode: normalizeAccessMode(isRecord(response) ? getValue(response, 'accessMode') : undefined),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchInstalledAppParams = (appId: string) => {
|
export const fetchInstalledAppParams = (appId: string) => {
|
||||||
return consoleClient.explore.installedAppParameters({
|
return consoleClient.installedApps.byInstalledAppId.parameters.get({
|
||||||
params: { appId },
|
params: { installed_app_id: appId },
|
||||||
}) as Promise<ChatConfig>
|
}).then(normalizeInstalledAppParametersViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchInstalledAppMeta = (appId: string) => {
|
export const fetchInstalledAppMeta = (appId: string) => {
|
||||||
return consoleClient.explore.installedAppMeta({
|
return consoleClient.installedApps.byInstalledAppId.meta.get({
|
||||||
params: { appId },
|
params: { installed_app_id: appId },
|
||||||
}) as Promise<AppMeta>
|
}).then(normalizeAppMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchBanners = (language?: string) => {
|
export const fetchBanners = (language?: string) => {
|
||||||
if (!language)
|
if (!language)
|
||||||
return consoleClient.explore.banners({})
|
return consoleClient.explore.banners.get({}).then(normalizeBannersResponse)
|
||||||
|
|
||||||
return consoleClient.explore.banners({
|
return consoleClient.explore.banners.get({
|
||||||
query: { language },
|
query: { language },
|
||||||
})
|
}).then(normalizeBannersResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import type { consoleRouterContract } from '@dify/contracts/api/console/router.gen'
|
||||||
import type { ClientLink } from '@orpc/client'
|
import type { ClientLink } from '@orpc/client'
|
||||||
import type { AnyContractRouter, ContractRouterClient } from '@orpc/contract'
|
import type { AnyContractRouter, ContractRouterClient } from '@orpc/contract'
|
||||||
import type { JsonifiedClient } from '@orpc/openapi-client'
|
import type { JsonifiedClient } from '@orpc/openapi-client'
|
||||||
import type { consoleRouterContract } from '@/contract/router'
|
|
||||||
import { createORPCClient, onError } from '@orpc/client'
|
import { createORPCClient, onError } from '@orpc/client'
|
||||||
import { OpenAPILink } from '@orpc/openapi-client/fetch'
|
import { OpenAPILink } from '@orpc/openapi-client/fetch'
|
||||||
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export const useExploreAppList = (options: { enabled?: boolean } = {}) => {
|
|||||||
const exploreAppsLanguage = exploreAppsInput?.query?.language
|
const exploreAppsLanguage = exploreAppsInput?.query?.language
|
||||||
|
|
||||||
return useQuery<ExploreAppListData>({
|
return useQuery<ExploreAppListData>({
|
||||||
queryKey: [...consoleQuery.explore.apps.queryKey({ input: exploreAppsInput }), exploreAppsLanguage],
|
queryKey: [...consoleQuery.explore.apps.get.queryKey({ input: exploreAppsInput }), exploreAppsLanguage],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { categories, recommended_apps } = await fetchAppList(exploreAppsLanguage)
|
const { categories, recommended_apps } = await fetchAppList(exploreAppsLanguage)
|
||||||
return {
|
return {
|
||||||
@ -48,7 +48,7 @@ export const useLearnDifyAppList = () => {
|
|||||||
const learnDifyAppsLanguage = learnDifyAppsInput?.query?.language
|
const learnDifyAppsLanguage = learnDifyAppsInput?.query?.language
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [...consoleQuery.explore.learnDifyApps.queryKey({ input: learnDifyAppsInput }), learnDifyAppsLanguage],
|
queryKey: [...consoleQuery.explore.apps.learnDify.get.queryKey({ input: learnDifyAppsInput }), learnDifyAppsLanguage],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { recommended_apps } = await fetchLearnDifyAppList(learnDifyAppsLanguage)
|
const { recommended_apps } = await fetchLearnDifyAppList(learnDifyAppsLanguage)
|
||||||
return [...recommended_apps].sort((a, b) => a.position - b.position)
|
return [...recommended_apps].sort((a, b) => a.position - b.position)
|
||||||
@ -58,7 +58,7 @@ export const useLearnDifyAppList = () => {
|
|||||||
|
|
||||||
export const useGetInstalledApps = () => {
|
export const useGetInstalledApps = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: consoleQuery.explore.installedApps.queryKey({ input: {} }),
|
queryKey: consoleQuery.installedApps.get.queryKey({ input: {} }),
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return fetchInstalledAppList()
|
return fetchInstalledAppList()
|
||||||
},
|
},
|
||||||
@ -68,11 +68,11 @@ export const useGetInstalledApps = () => {
|
|||||||
export const useUninstallApp = () => {
|
export const useUninstallApp = () => {
|
||||||
const client = useQueryClient()
|
const client = useQueryClient()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: consoleQuery.explore.uninstallInstalledApp.mutationKey(),
|
mutationKey: consoleQuery.installedApps.byInstalledAppId.delete.mutationKey(),
|
||||||
mutationFn: (appId: string) => uninstallApp(appId),
|
mutationFn: (appId: string) => uninstallApp(appId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
client.invalidateQueries({
|
||||||
queryKey: consoleQuery.explore.installedApps.queryKey({ input: {} }),
|
queryKey: consoleQuery.installedApps.get.queryKey({ input: {} }),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -81,11 +81,11 @@ export const useUninstallApp = () => {
|
|||||||
export const useUpdateAppPinStatus = () => {
|
export const useUpdateAppPinStatus = () => {
|
||||||
const client = useQueryClient()
|
const client = useQueryClient()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: consoleQuery.explore.updateInstalledApp.mutationKey(),
|
mutationKey: consoleQuery.installedApps.byInstalledAppId.patch.mutationKey(),
|
||||||
mutationFn: ({ appId, isPinned }: { appId: string, isPinned: boolean }) => updatePinStatus(appId, isPinned),
|
mutationFn: ({ appId, isPinned }: { appId: string, isPinned: boolean }) => updatePinStatus(appId, isPinned),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
client.invalidateQueries({
|
||||||
queryKey: consoleQuery.explore.installedApps.queryKey({ input: {} }),
|
queryKey: consoleQuery.installedApps.get.queryKey({ input: {} }),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -102,7 +102,7 @@ export const useGetInstalledAppAccessModeByAppId = (appId: string | null) => {
|
|||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
...consoleQuery.explore.appAccessMode.queryKey({ input: appAccessModeInput }),
|
...consoleQuery.enterprise.webAppAuth.getWebAppAccessMode.queryKey({ input: appAccessModeInput }),
|
||||||
webappAuthEnabled,
|
webappAuthEnabled,
|
||||||
installedAppId,
|
installedAppId,
|
||||||
],
|
],
|
||||||
@ -122,11 +122,11 @@ export const useGetInstalledAppAccessModeByAppId = (appId: string | null) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useGetInstalledAppParams = (appId: string | null) => {
|
export const useGetInstalledAppParams = (appId: string | null) => {
|
||||||
const installedAppParamsInput = { params: { appId: appId ?? '' } }
|
const installedAppParamsInput = { params: { installed_app_id: appId ?? '' } }
|
||||||
const installedAppId = installedAppParamsInput.params.appId
|
const installedAppId = installedAppParamsInput.params.installed_app_id
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [...consoleQuery.explore.installedAppParameters.queryKey({ input: installedAppParamsInput }), installedAppId],
|
queryKey: [...consoleQuery.installedApps.byInstalledAppId.parameters.get.queryKey({ input: installedAppParamsInput }), installedAppId],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
if (!installedAppId)
|
if (!installedAppId)
|
||||||
return Promise.reject(new Error('App ID is required to get app params'))
|
return Promise.reject(new Error('App ID is required to get app params'))
|
||||||
@ -137,11 +137,11 @@ export const useGetInstalledAppParams = (appId: string | null) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useGetInstalledAppMeta = (appId: string | null) => {
|
export const useGetInstalledAppMeta = (appId: string | null) => {
|
||||||
const installedAppMetaInput = { params: { appId: appId ?? '' } }
|
const installedAppMetaInput = { params: { installed_app_id: appId ?? '' } }
|
||||||
const installedAppId = installedAppMetaInput.params.appId
|
const installedAppId = installedAppMetaInput.params.installed_app_id
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [...consoleQuery.explore.installedAppMeta.queryKey({ input: installedAppMetaInput }), installedAppId],
|
queryKey: [...consoleQuery.installedApps.byInstalledAppId.meta.get.queryKey({ input: installedAppMetaInput }), installedAppId],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
if (!installedAppId)
|
if (!installedAppId)
|
||||||
return Promise.reject(new Error('App ID is required to get app meta'))
|
return Promise.reject(new Error('App ID is required to get app meta'))
|
||||||
|
|||||||
@ -82,6 +82,17 @@ describe('create-app-tracking', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should map the current backend agent mode into the canonical app mode bucket', () => {
|
||||||
|
expect(buildCreateAppEventPayload({
|
||||||
|
source: 'explore_template_list',
|
||||||
|
appMode: 'agent',
|
||||||
|
}, null, new Date(2026, 3, 13, 9, 8, 8))).toEqual({
|
||||||
|
source: 'explore_template_list',
|
||||||
|
app_mode: 'agent',
|
||||||
|
time: '04-13-09:08:08',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should fold legacy non-agent modes into chatflow', () => {
|
it('should fold legacy non-agent modes into chatflow', () => {
|
||||||
expect(buildCreateAppEventPayload({
|
expect(buildCreateAppEventPayload({
|
||||||
source: 'studio_blank',
|
source: 'studio_blank',
|
||||||
|
|||||||
@ -31,7 +31,7 @@ type CreateAppSource
|
|||||||
|
|
||||||
export type TrackCreateAppParams = {
|
export type TrackCreateAppParams = {
|
||||||
source: CreateAppSource
|
source: CreateAppSource
|
||||||
appMode: AppModeEnum
|
appMode: string
|
||||||
templateId?: string
|
templateId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,11 +89,11 @@ const formatCreateAppTime = (date: Date) => {
|
|||||||
return `${padTimeValue(date.getMonth() + 1)}-${padTimeValue(date.getDate())}-${padTimeValue(date.getHours())}:${padTimeValue(date.getMinutes())}:${padTimeValue(date.getSeconds())}`
|
return `${padTimeValue(date.getMonth() + 1)}-${padTimeValue(date.getDate())}-${padTimeValue(date.getHours())}:${padTimeValue(date.getMinutes())}:${padTimeValue(date.getSeconds())}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapOriginalCreateAppMode = (appMode: AppModeEnum): OriginalCreateAppMode => {
|
const mapOriginalCreateAppMode = (appMode: string): OriginalCreateAppMode => {
|
||||||
if (appMode === AppModeEnum.WORKFLOW)
|
if (appMode === AppModeEnum.WORKFLOW)
|
||||||
return 'workflow'
|
return 'workflow'
|
||||||
|
|
||||||
if (appMode === AppModeEnum.AGENT_CHAT)
|
if (appMode === AppModeEnum.AGENT_CHAT || appMode === 'agent')
|
||||||
return 'agent'
|
return 'agent'
|
||||||
|
|
||||||
return 'chatflow'
|
return 'chatflow'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user