mirror of
https://github.com/langgenius/dify.git
synced 2026-07-19 16:44:00 -04: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:
@@ -51,6 +51,18 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
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
|
||||
if: github.event_name != 'merge_group'
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
- name: ESLint autofix
|
||||
|
||||
@@ -147,11 +147,15 @@ register_schema_models(
|
||||
InstalledAppCreatePayload,
|
||||
InstalledAppUpdatePayload,
|
||||
InstalledAppsListQuery,
|
||||
)
|
||||
register_response_schema_models(
|
||||
console_ns,
|
||||
InstalledAppInfoResponse,
|
||||
InstalledAppResponse,
|
||||
InstalledAppListResponse,
|
||||
SimpleMessageResponse,
|
||||
SimpleResultMessageResponse,
|
||||
)
|
||||
register_response_schema_models(console_ns, SimpleMessageResponse, SimpleResultMessageResponse)
|
||||
|
||||
|
||||
@console_ns.route("/installed-apps")
|
||||
|
||||
@@ -13,7 +13,12 @@ from services.app_service import AppService
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -3,7 +3,7 @@ from uuid import UUID
|
||||
|
||||
from flask import request
|
||||
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 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
|
||||
|
||||
|
||||
class RecommendedAppDetailNullableResponse(RootModel[RecommendedAppDetailResponse | None]):
|
||||
pass
|
||||
|
||||
|
||||
register_schema_models(
|
||||
console_ns,
|
||||
RecommendedAppsQuery,
|
||||
)
|
||||
register_response_schema_models(
|
||||
console_ns,
|
||||
RecommendedAppInfoResponse,
|
||||
RecommendedAppResponse,
|
||||
RecommendedAppListResponse,
|
||||
LearnDifyAppListResponse,
|
||||
RecommendedAppDetailResponse,
|
||||
RecommendedAppDetailNullableResponse,
|
||||
)
|
||||
register_response_schema_models(console_ns, RecommendedAppDetailResponse)
|
||||
|
||||
|
||||
def _resolve_language(language: str | None, user: Account) -> str:
|
||||
@@ -136,7 +144,7 @@ class LearnDifyAppListApi(Resource):
|
||||
|
||||
@console_ns.route("/explore/apps/<uuid:app_id>")
|
||||
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
|
||||
@account_initialization_required
|
||||
def get(self, app_id: UUID):
|
||||
|
||||
@@ -6802,7 +6802,7 @@ Check if dataset is in use
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [RecommendedAppDetailResponse](#recommendedappdetailresponse)<br> |
|
||||
| 200 | Success | **application/json**: [RecommendedAppDetailNullableResponse](#recommendedappdetailnullableresponse)<br> |
|
||||
|
||||
### [GET] /features
|
||||
**Get feature configuration for current tenant**
|
||||
@@ -17287,6 +17287,10 @@ The type of the parameter
|
||||
|
||||
#### 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 |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| tool_icons | object | | No |
|
||||
@@ -17984,6 +17988,7 @@ Input field definition for snippet parameters.
|
||||
| icon | string | | No |
|
||||
| icon_background | string | | No |
|
||||
| icon_type | string | | No |
|
||||
| icon_url | string | | Yes |
|
||||
| id | string | | Yes |
|
||||
| mode | string | | No |
|
||||
| name | string | | No |
|
||||
@@ -20080,6 +20085,12 @@ Whitelist scopes accepted by RBAC app and dataset access config APIs.
|
||||
| result | string | | Yes |
|
||||
| updated_at | integer | | Yes |
|
||||
|
||||
#### RecommendedAppDetailNullableResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| RecommendedAppDetailNullableResponse | [RecommendedAppDetailResponse](#recommendedappdetailresponse) | | |
|
||||
|
||||
#### RecommendedAppDetailResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -20099,6 +20110,7 @@ Whitelist scopes accepted by RBAC app and dataset access config APIs.
|
||||
| icon | string | | No |
|
||||
| icon_background | string | | No |
|
||||
| icon_type | string | | No |
|
||||
| icon_url | string | | Yes |
|
||||
| id | string | | Yes |
|
||||
| mode | 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"]["export_data"]["type"] == "string"
|
||||
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"]
|
||||
assert plugin_versions["additionalProperties"]["anyOf"][0]["$ref"] == "#/components/schemas/LatestPluginCache"
|
||||
|
||||
@@ -7057,11 +7057,6 @@
|
||||
"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": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@@ -7087,11 +7082,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/service/access-control/index.ts": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/service/access-control/use-app-access-control.ts": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
|
||||
@@ -13,15 +13,7 @@ export type LearnDifyAppListResponse = {
|
||||
recommended_apps: Array<RecommendedAppResponse>
|
||||
}
|
||||
|
||||
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 RecommendedAppDetailNullableResponse = RecommendedAppDetailResponse | null
|
||||
|
||||
export type BannerListResponse = Array<BannerResponse>
|
||||
|
||||
@@ -38,6 +30,16 @@ export type RecommendedAppResponse = {
|
||||
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 = {
|
||||
content: unknown
|
||||
created_at?: string | null
|
||||
@@ -48,6 +50,38 @@ export type BannerResponse = {
|
||||
}
|
||||
|
||||
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_background?: string | null
|
||||
icon_type?: string | null
|
||||
@@ -97,7 +131,7 @@ export type GetExploreAppsByAppIdData = {
|
||||
}
|
||||
|
||||
export type GetExploreAppsByAppIdResponses = {
|
||||
200: RecommendedAppDetailResponse
|
||||
200: RecommendedAppDetailNullableResponse
|
||||
}
|
||||
|
||||
export type GetExploreAppsByAppIdResponse
|
||||
|
||||
@@ -15,6 +15,11 @@ export const zRecommendedAppDetailResponse = z.object({
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* RecommendedAppDetailNullableResponse
|
||||
*/
|
||||
export const zRecommendedAppDetailNullableResponse = zRecommendedAppDetailResponse.nullable()
|
||||
|
||||
/**
|
||||
* BannerResponse
|
||||
*/
|
||||
@@ -39,6 +44,7 @@ export const zRecommendedAppInfoResponse = z.object({
|
||||
icon: z.string().nullish(),
|
||||
icon_background: z.string().nullish(),
|
||||
icon_type: z.string().nullish(),
|
||||
icon_url: z.string().nullable(),
|
||||
id: z.string(),
|
||||
mode: z.string().nullish(),
|
||||
name: z.string().nullish(),
|
||||
@@ -75,6 +81,49 @@ export const zLearnDifyAppListResponse = z.object({
|
||||
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({
|
||||
language: z.string().optional(),
|
||||
})
|
||||
@@ -100,7 +149,7 @@ export const zGetExploreAppsByAppIdPath = z.object({
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zGetExploreAppsByAppIdResponse = zRecommendedAppDetailResponse
|
||||
export const zGetExploreAppsByAppIdResponse = zRecommendedAppDetailNullableResponse
|
||||
|
||||
export const zGetExploreBannersQuery = z.object({
|
||||
language: z.string().optional().default('en-US'),
|
||||
|
||||
@@ -117,7 +117,11 @@ export type SuggestedQuestionsResponse = {
|
||||
|
||||
export type ExploreAppMetaResponse = {
|
||||
tool_icons?: {
|
||||
[key: string]: unknown
|
||||
[key: string]:
|
||||
| string
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +241,7 @@ export type InstalledAppInfoResponse = {
|
||||
icon?: string | null
|
||||
icon_background?: string | null
|
||||
icon_type?: string | null
|
||||
readonly icon_url: string | null
|
||||
id: string
|
||||
mode?: 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 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 = {
|
||||
body?: never
|
||||
path?: never
|
||||
|
||||
@@ -113,9 +113,15 @@ export const zSuggestedQuestionsResponse = z.object({
|
||||
|
||||
/**
|
||||
* 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({
|
||||
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_background: z.string().nullish(),
|
||||
icon_type: z.string().nullish(),
|
||||
icon_url: z.string().nullable(),
|
||||
id: z.string(),
|
||||
mode: z.string().nullish(),
|
||||
name: z.string().nullish(),
|
||||
@@ -533,6 +540,45 @@ export const zExploreMessageInfiniteScrollPagination = z.object({
|
||||
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({
|
||||
app_id: z.string().optional(),
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
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 => {
|
||||
return {
|
||||
clean: false,
|
||||
@@ -369,7 +407,10 @@ const createConsoleContractEntryJob = (document: SwaggerDocument, segments: stri
|
||||
outputPath: 'generated/api/console',
|
||||
plugins: [],
|
||||
source: {
|
||||
callback: () => writeConsoleContractEntry(segments),
|
||||
callback: () => {
|
||||
writeConsoleContractEntry(segments)
|
||||
writeConsoleRouterContract(segments)
|
||||
},
|
||||
enabled: true,
|
||||
path: null,
|
||||
serialize: () => '',
|
||||
|
||||
@@ -106,10 +106,14 @@ vi.mock('@/service/client', () => ({
|
||||
},
|
||||
explore: {
|
||||
apps: {
|
||||
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'apps', input],
|
||||
get: {
|
||||
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'apps', 'get', input],
|
||||
},
|
||||
},
|
||||
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 exploreAppListQueryKey = ['console', 'explore', 'apps', localeInput, 'en']
|
||||
const exploreAppListQueryKey = ['console', 'explore', 'apps', 'get', localeInput, 'en']
|
||||
const homeContinueWorkAppsInput = {
|
||||
query: {
|
||||
page: 1,
|
||||
|
||||
@@ -81,7 +81,7 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => {
|
||||
export default AppTypeSelector
|
||||
|
||||
type AppTypeIconProps = {
|
||||
type: AppModeEnum
|
||||
type: string
|
||||
style?: React.CSSProperties
|
||||
className?: 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)
|
||||
return t('typeSelector.chatbot', { ns: 'app' })
|
||||
if (type === AppModeEnum.AGENT_CHAT)
|
||||
@@ -215,7 +215,7 @@ function getAppTypeLabel(type: AppModeEnum, t: ReturnType<typeof useTranslation>
|
||||
}
|
||||
|
||||
type AppTypeLabelProps = {
|
||||
type: AppModeEnum
|
||||
type: string
|
||||
className?: string
|
||||
}
|
||||
export function AppTypeLabel({ type, className }: AppTypeLabelProps) {
|
||||
|
||||
@@ -12,10 +12,9 @@ import type {
|
||||
} from '../types'
|
||||
import type { HumanInputFormSubmitData } from './answer/human-input-content/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 { 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 { cn } from '@langgenius/dify-ui/cn'
|
||||
import { memo } from 'react'
|
||||
@@ -52,7 +51,7 @@ export type ChatProps = {
|
||||
showPromptLog?: boolean
|
||||
questionIcon?: ReactNode
|
||||
answerIcon?: ReactNode
|
||||
allToolIcons?: Record<string, string | Emoji>
|
||||
allToolIcons?: Record<string, ToolIcon>
|
||||
onAnnotationEdited?: (question: string, answer: string, index: number) => void
|
||||
onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void
|
||||
onAnnotationRemoved?: (index: number) => void
|
||||
|
||||
@@ -114,10 +114,14 @@ vi.mock('@/service/client', () => ({
|
||||
},
|
||||
explore: {
|
||||
apps: {
|
||||
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'apps', input],
|
||||
get: {
|
||||
queryKey: ({ input }: { input?: unknown } = {}) => ['console', 'explore', 'apps', 'get', input],
|
||||
},
|
||||
},
|
||||
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 exploreAppListQueryKey = ['console', 'explore', 'apps', localeInput, 'en']
|
||||
const exploreBannersQueryKey = ['console', 'explore', 'banners', localeInput, 'en']
|
||||
const exploreAppListQueryKey = ['console', 'explore', 'apps', 'get', localeInput, 'en']
|
||||
const exploreBannersQueryKey = ['console', 'explore', 'banners', 'get', localeInput, 'en']
|
||||
|
||||
const renderAppList = (
|
||||
hasEditPermission = false,
|
||||
|
||||
@@ -61,7 +61,7 @@ function getExploreAppListQueryOptions(locale?: string) {
|
||||
const language = input.query?.language
|
||||
|
||||
return queryOptions<ExploreAppListData>({
|
||||
queryKey: [...consoleQuery.explore.apps.queryKey({ input }), language],
|
||||
queryKey: [...consoleQuery.explore.apps.get.queryKey({ input }), language],
|
||||
queryFn: async () => {
|
||||
const { categories, recommended_apps } = await fetchAppList(language)
|
||||
return {
|
||||
@@ -84,7 +84,7 @@ function getBannersQueryOptions(locale?: string) {
|
||||
const language = input.query?.language
|
||||
|
||||
return queryOptions<BannerType[]>({
|
||||
queryKey: [...consoleQuery.explore.banners.queryKey({ input }), language],
|
||||
queryKey: [...consoleQuery.explore.banners.get.queryKey({ input }), language],
|
||||
queryFn: () => fetchBanners(language),
|
||||
})
|
||||
}
|
||||
@@ -246,9 +246,11 @@ const Apps = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
async ({ name, icon_type, icon, icon_background, description }) => {
|
||||
hideTryAppPanel()
|
||||
|
||||
const { export_data, mode } = await fetchAppDetail(
|
||||
currApp?.app.id as string,
|
||||
)
|
||||
const appId = currApp?.app.id
|
||||
if (!appId)
|
||||
return
|
||||
|
||||
const { export_data, mode } = await fetchAppDetail(appId)
|
||||
currentCreateAppModeRef.current = mode
|
||||
const payload = {
|
||||
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/i18n
|
||||
- context/modal-context
|
||||
- contract/router
|
||||
- @dify/contracts/api/console/router.gen
|
||||
- hooks/use-format-time-from-now
|
||||
- hooks/use-theme
|
||||
- hooks/use-timestamp
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { AppIconType, AppModeEnum } from '@/types/app'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
|
||||
type AppBasicInfo = {
|
||||
id: string
|
||||
mode: AppModeEnum
|
||||
mode: string
|
||||
icon_type: AppIconType | null
|
||||
icon: string
|
||||
icon_background: string
|
||||
|
||||
+3
-1
@@ -27,8 +27,10 @@ export type SiteInfo = {
|
||||
use_icon_as_answer_icon?: boolean
|
||||
}
|
||||
|
||||
export type ToolIcon = string | Record<string, unknown>
|
||||
|
||||
export type AppMeta = {
|
||||
tool_icons: Record<string, string>
|
||||
tool_icons: Record<string, ToolIcon>
|
||||
}
|
||||
|
||||
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 { renderHook, waitFor } from '@testing-library/react'
|
||||
import { AccessMode, SubjectType } from '@/models/access-control'
|
||||
import { post } from '@/service/base'
|
||||
import { consoleClient } from '@/service/client'
|
||||
import { useUpdateAccessMode } from '..'
|
||||
|
||||
vi.mock('@/service/base', () => ({
|
||||
post: vi.fn(),
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleClient: {
|
||||
enterprise: {
|
||||
webAppAuth: {
|
||||
updateWebAppWhitelistSubjects: vi.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
consoleQuery: {
|
||||
enterprise: {
|
||||
webAppAuth: {
|
||||
getWebAppAccessMode: {
|
||||
key: vi.fn(() => ['enterprise', 'web-app-auth', 'access-mode']),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const createWrapper = () => {
|
||||
@@ -25,7 +40,7 @@ const createWrapper = () => {
|
||||
describe('access-control service', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(post).mockResolvedValue({})
|
||||
vi.mocked(consoleClient.enterprise.webAppAuth.updateWebAppWhitelistSubjects).mockResolvedValue({})
|
||||
})
|
||||
|
||||
// Access mode updates keep the legacy webapp whitelist payload contract.
|
||||
@@ -43,7 +58,7 @@ describe('access-control service', () => {
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(post).toHaveBeenCalledWith('/enterprise/webapp/app/access-mode', {
|
||||
expect(consoleClient.enterprise.webAppAuth.updateWebAppWhitelistSubjects).toHaveBeenCalledWith({
|
||||
body: {
|
||||
appId: 'app-1',
|
||||
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AccessControlGroup, AccessMode, Subject } from '@/models/access-control'
|
||||
import type { App } from '@/types/app'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { post } from '../base'
|
||||
import { consoleClient, consoleQuery } from '../client'
|
||||
import {
|
||||
useAppWhiteListSubjects as useAppWhiteListSubjectsBase,
|
||||
useGetUserCanAccessApp as useGetUserCanAccessAppBase,
|
||||
@@ -46,9 +46,14 @@ export const useUpdateAccessMode = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-access-mode'],
|
||||
mutationFn: (params: UpdateAccessModeParams) => {
|
||||
return post('/enterprise/webapp/app/access-mode', { body: params })
|
||||
return consoleClient.enterprise.webAppAuth.updateWebAppWhitelistSubjects({
|
||||
body: params,
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.enterprise.webAppAuth.getWebAppAccessMode.key({ type: 'query' }),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'app-whitelist-subjects'],
|
||||
})
|
||||
|
||||
+1
-17
@@ -1,5 +1,6 @@
|
||||
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 { consoleRouterContract } from '@dify/contracts/api/console/router.gen'
|
||||
import type { TagResponse as Tag, TagType } from '@dify/contracts/api/console/tags/types.gen'
|
||||
import type {
|
||||
GetReleaseResponse,
|
||||
@@ -11,7 +12,6 @@ import type { AnyContractRouter, ContractRouterClient } from '@orpc/contract'
|
||||
import type { JsonifiedClient } from '@orpc/openapi-client'
|
||||
import type { RouterUtils, TanstackQueryOperationContext } from '@orpc/tanstack-query'
|
||||
import type { InfiniteData, QueryClient, QueryKey } from '@tanstack/react-query'
|
||||
import type { consoleRouterContract } from '@/contract/router'
|
||||
import { createORPCClient, onError } from '@orpc/client'
|
||||
import { OpenAPILink } from '@orpc/openapi-client/fetch'
|
||||
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: {
|
||||
post: {
|
||||
mutationOptions: {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { agent } from '@dify/contracts/api/console/agent/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 { tags } from '@dify/contracts/api/console/tags/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 { loadConsoleContractForSegment } from './console-router-loader'
|
||||
|
||||
@@ -10,12 +13,20 @@ describe('loadConsoleContractForSegment', () => {
|
||||
it.each([
|
||||
['agent', 'agent', agent],
|
||||
['apps', 'apps', apps],
|
||||
['explore', 'explore', explore],
|
||||
['installedApps', 'installedApps', installedApps],
|
||||
['notification', 'notification', notification],
|
||||
['tags', 'tags', tags],
|
||||
['workspaces', 'workspaces', workspaces],
|
||||
] as const)('loads the generated %s contract when generated types are usable', async (segment, contractKey, generatedContract) => {
|
||||
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 { contractLoaders } from '@dify/contracts/api/console/orpc.gen'
|
||||
|
||||
type ConsoleContractExtension = Record<string, unknown>
|
||||
|
||||
const wrapConsoleContract = (segment: string, contract: unknown): ConsoleContractExtension => ({ [segment]: contract })
|
||||
const generatedConsoleContractLoaders: Partial<Record<string, () => Promise<AnyContractRouter>>> = contractLoaders
|
||||
|
||||
async function loadGeneratedConsoleContract(segment: string) {
|
||||
const loader = contractLoaders[segment as keyof typeof contractLoaders]
|
||||
const loader = generatedConsoleContractLoaders[segment]
|
||||
if (!loader)
|
||||
return null
|
||||
|
||||
return loader() as Promise<AnyContractRouter>
|
||||
return loader()
|
||||
}
|
||||
|
||||
const customConsoleContractLoaders: Record<string, () => Promise<ConsoleContractExtension>> = {
|
||||
enterprise: () => import('@dify/contracts/enterprise/orpc.gen').then(({ contract }) => wrapConsoleContract('enterprise', contract)),
|
||||
explore: () => import('@/contract/console/explore').then(({ exploreConsoleRouterContract }) => exploreConsoleRouterContract),
|
||||
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),
|
||||
async function loadEnterpriseContract(): Promise<AnyContractRouter> {
|
||||
const { contract } = await import('@dify/contracts/enterprise/orpc.gen')
|
||||
return { enterprise: contract }
|
||||
}
|
||||
|
||||
export async function loadConsoleContractForSegment(segment: string) {
|
||||
const customContractLoader = customConsoleContractLoaders[segment]
|
||||
if (customContractLoader) {
|
||||
const customContract = await customContractLoader()
|
||||
if (Object.keys(customContract).length > 0)
|
||||
return customContract as AnyContractRouter
|
||||
}
|
||||
if (segment === 'enterprise')
|
||||
return loadEnterpriseContract()
|
||||
|
||||
const generatedContract = await loadGeneratedConsoleContract(segment)
|
||||
if (generatedContract)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
+378
-29
@@ -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 { ExploreAppDetailResponse } from '@/contract/console/explore'
|
||||
import type { AppMeta } from '@/models/share'
|
||||
import type { Banner } from '@/models/app'
|
||||
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'
|
||||
|
||||
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) => {
|
||||
if (!language)
|
||||
return consoleClient.explore.apps({})
|
||||
return consoleClient.explore.apps.get({}).then(normalizeExploreAppsResponse)
|
||||
|
||||
return consoleClient.explore.apps({
|
||||
return consoleClient.explore.apps.get({
|
||||
query: { language },
|
||||
})
|
||||
}).then(normalizeExploreAppsResponse)
|
||||
}
|
||||
|
||||
export const fetchLearnDifyAppList = (language?: string) => {
|
||||
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 },
|
||||
})
|
||||
}).then(normalizeLearnDifyAppsResponse)
|
||||
}
|
||||
|
||||
export const fetchAppDetail = async (id: string): Promise<ExploreAppDetailResponse> => {
|
||||
const response = await consoleClient.explore.appDetail({
|
||||
params: { id },
|
||||
const response = await consoleClient.explore.apps.byAppId.get({
|
||||
params: { app_id: id },
|
||||
})
|
||||
if (!response)
|
||||
throw new Error('Recommended app not found')
|
||||
return response
|
||||
return normalizeAppDetail(response)
|
||||
}
|
||||
|
||||
export const fetchInstalledAppList = (appId?: string | null) => {
|
||||
if (!appId)
|
||||
return consoleClient.explore.installedApps({})
|
||||
return consoleClient.installedApps.get({}).then(normalizeInstalledAppsResponse)
|
||||
|
||||
return consoleClient.explore.installedApps({
|
||||
return consoleClient.installedApps.get({
|
||||
query: { app_id: appId },
|
||||
})
|
||||
}).then(normalizeInstalledAppsResponse)
|
||||
}
|
||||
|
||||
export const uninstallApp = (id: string) => {
|
||||
return consoleClient.explore.uninstallInstalledApp({
|
||||
params: { id },
|
||||
return consoleClient.installedApps.byInstalledAppId.delete({
|
||||
params: { installed_app_id: id },
|
||||
})
|
||||
}
|
||||
|
||||
export const updatePinStatus = (id: string, isPinned: boolean) => {
|
||||
return consoleClient.explore.updateInstalledApp({
|
||||
params: { id },
|
||||
return consoleClient.installedApps.byInstalledAppId.patch({
|
||||
params: { installed_app_id: id },
|
||||
body: {
|
||||
is_pinned: isPinned,
|
||||
},
|
||||
@@ -55,28 +402,30 @@ export const updatePinStatus = (id: string, isPinned: boolean) => {
|
||||
}
|
||||
|
||||
export const getAppAccessModeByAppId = (appId: string) => {
|
||||
return consoleClient.explore.appAccessMode({
|
||||
return consoleClient.enterprise.webAppAuth.getWebAppAccessMode({
|
||||
query: { appId },
|
||||
})
|
||||
}).then((response): AppAccessModeResponse => ({
|
||||
accessMode: normalizeAccessMode(isRecord(response) ? getValue(response, 'accessMode') : undefined),
|
||||
}))
|
||||
}
|
||||
|
||||
export const fetchInstalledAppParams = (appId: string) => {
|
||||
return consoleClient.explore.installedAppParameters({
|
||||
params: { appId },
|
||||
}) as Promise<ChatConfig>
|
||||
return consoleClient.installedApps.byInstalledAppId.parameters.get({
|
||||
params: { installed_app_id: appId },
|
||||
}).then(normalizeInstalledAppParametersViewModel)
|
||||
}
|
||||
|
||||
export const fetchInstalledAppMeta = (appId: string) => {
|
||||
return consoleClient.explore.installedAppMeta({
|
||||
params: { appId },
|
||||
}) as Promise<AppMeta>
|
||||
return consoleClient.installedApps.byInstalledAppId.meta.get({
|
||||
params: { installed_app_id: appId },
|
||||
}).then(normalizeAppMeta)
|
||||
}
|
||||
|
||||
export const fetchBanners = (language?: string) => {
|
||||
if (!language)
|
||||
return consoleClient.explore.banners({})
|
||||
return consoleClient.explore.banners.get({}).then(normalizeBannersResponse)
|
||||
|
||||
return consoleClient.explore.banners({
|
||||
return consoleClient.explore.banners.get({
|
||||
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 { AnyContractRouter, ContractRouterClient } from '@orpc/contract'
|
||||
import type { JsonifiedClient } from '@orpc/openapi-client'
|
||||
import type { consoleRouterContract } from '@/contract/router'
|
||||
import { createORPCClient, onError } from '@orpc/client'
|
||||
import { OpenAPILink } from '@orpc/openapi-client/fetch'
|
||||
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
||||
|
||||
+14
-14
@@ -28,7 +28,7 @@ export const useExploreAppList = (options: { enabled?: boolean } = {}) => {
|
||||
const exploreAppsLanguage = exploreAppsInput?.query?.language
|
||||
|
||||
return useQuery<ExploreAppListData>({
|
||||
queryKey: [...consoleQuery.explore.apps.queryKey({ input: exploreAppsInput }), exploreAppsLanguage],
|
||||
queryKey: [...consoleQuery.explore.apps.get.queryKey({ input: exploreAppsInput }), exploreAppsLanguage],
|
||||
queryFn: async () => {
|
||||
const { categories, recommended_apps } = await fetchAppList(exploreAppsLanguage)
|
||||
return {
|
||||
@@ -48,7 +48,7 @@ export const useLearnDifyAppList = () => {
|
||||
const learnDifyAppsLanguage = learnDifyAppsInput?.query?.language
|
||||
|
||||
return useQuery({
|
||||
queryKey: [...consoleQuery.explore.learnDifyApps.queryKey({ input: learnDifyAppsInput }), learnDifyAppsLanguage],
|
||||
queryKey: [...consoleQuery.explore.apps.learnDify.get.queryKey({ input: learnDifyAppsInput }), learnDifyAppsLanguage],
|
||||
queryFn: async () => {
|
||||
const { recommended_apps } = await fetchLearnDifyAppList(learnDifyAppsLanguage)
|
||||
return [...recommended_apps].sort((a, b) => a.position - b.position)
|
||||
@@ -58,7 +58,7 @@ export const useLearnDifyAppList = () => {
|
||||
|
||||
export const useGetInstalledApps = () => {
|
||||
return useQuery({
|
||||
queryKey: consoleQuery.explore.installedApps.queryKey({ input: {} }),
|
||||
queryKey: consoleQuery.installedApps.get.queryKey({ input: {} }),
|
||||
queryFn: () => {
|
||||
return fetchInstalledAppList()
|
||||
},
|
||||
@@ -68,11 +68,11 @@ export const useGetInstalledApps = () => {
|
||||
export const useUninstallApp = () => {
|
||||
const client = useQueryClient()
|
||||
return useMutation({
|
||||
mutationKey: consoleQuery.explore.uninstallInstalledApp.mutationKey(),
|
||||
mutationKey: consoleQuery.installedApps.byInstalledAppId.delete.mutationKey(),
|
||||
mutationFn: (appId: string) => uninstallApp(appId),
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
queryKey: consoleQuery.explore.installedApps.queryKey({ input: {} }),
|
||||
queryKey: consoleQuery.installedApps.get.queryKey({ input: {} }),
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -81,11 +81,11 @@ export const useUninstallApp = () => {
|
||||
export const useUpdateAppPinStatus = () => {
|
||||
const client = useQueryClient()
|
||||
return useMutation({
|
||||
mutationKey: consoleQuery.explore.updateInstalledApp.mutationKey(),
|
||||
mutationKey: consoleQuery.installedApps.byInstalledAppId.patch.mutationKey(),
|
||||
mutationFn: ({ appId, isPinned }: { appId: string, isPinned: boolean }) => updatePinStatus(appId, isPinned),
|
||||
onSuccess: () => {
|
||||
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({
|
||||
queryKey: [
|
||||
...consoleQuery.explore.appAccessMode.queryKey({ input: appAccessModeInput }),
|
||||
...consoleQuery.enterprise.webAppAuth.getWebAppAccessMode.queryKey({ input: appAccessModeInput }),
|
||||
webappAuthEnabled,
|
||||
installedAppId,
|
||||
],
|
||||
@@ -122,11 +122,11 @@ export const useGetInstalledAppAccessModeByAppId = (appId: string | null) => {
|
||||
}
|
||||
|
||||
export const useGetInstalledAppParams = (appId: string | null) => {
|
||||
const installedAppParamsInput = { params: { appId: appId ?? '' } }
|
||||
const installedAppId = installedAppParamsInput.params.appId
|
||||
const installedAppParamsInput = { params: { installed_app_id: appId ?? '' } }
|
||||
const installedAppId = installedAppParamsInput.params.installed_app_id
|
||||
|
||||
return useQuery({
|
||||
queryKey: [...consoleQuery.explore.installedAppParameters.queryKey({ input: installedAppParamsInput }), installedAppId],
|
||||
queryKey: [...consoleQuery.installedApps.byInstalledAppId.parameters.get.queryKey({ input: installedAppParamsInput }), installedAppId],
|
||||
queryFn: () => {
|
||||
if (!installedAppId)
|
||||
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) => {
|
||||
const installedAppMetaInput = { params: { appId: appId ?? '' } }
|
||||
const installedAppId = installedAppMetaInput.params.appId
|
||||
const installedAppMetaInput = { params: { installed_app_id: appId ?? '' } }
|
||||
const installedAppId = installedAppMetaInput.params.installed_app_id
|
||||
|
||||
return useQuery({
|
||||
queryKey: [...consoleQuery.explore.installedAppMeta.queryKey({ input: installedAppMetaInput }), installedAppId],
|
||||
queryKey: [...consoleQuery.installedApps.byInstalledAppId.meta.get.queryKey({ input: installedAppMetaInput }), installedAppId],
|
||||
queryFn: () => {
|
||||
if (!installedAppId)
|
||||
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', () => {
|
||||
expect(buildCreateAppEventPayload({
|
||||
source: 'studio_blank',
|
||||
|
||||
@@ -31,7 +31,7 @@ type CreateAppSource
|
||||
|
||||
export type TrackCreateAppParams = {
|
||||
source: CreateAppSource
|
||||
appMode: AppModeEnum
|
||||
appMode: 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())}`
|
||||
}
|
||||
|
||||
const mapOriginalCreateAppMode = (appMode: AppModeEnum): OriginalCreateAppMode => {
|
||||
const mapOriginalCreateAppMode = (appMode: string): OriginalCreateAppMode => {
|
||||
if (appMode === AppModeEnum.WORKFLOW)
|
||||
return 'workflow'
|
||||
|
||||
if (appMode === AppModeEnum.AGENT_CHAT)
|
||||
if (appMode === AppModeEnum.AGENT_CHAT || appMode === 'agent')
|
||||
return 'agent'
|
||||
|
||||
return 'chatflow'
|
||||
|
||||
Reference in New Issue
Block a user